大家好,又见面了,我是你们的朋友全栈君。
map和stack
介绍 (Introduction)
“MEAN Apps with Google Maps” (A tongue twister to be true).
“带有Google Maps的MEAN应用”(确实是绕口令)。
And yet, whether you’re building an application to visualize bike lanes in your city, designing a tool to chart oil wells across the globe, or are simply creating an app to help choose your next date — having access to interactive, data-rich maps can be a critical asset. Thus, in this two-part tutorial, we’ll be writing code that directly integrates Google Maps with the views, controllers, and data of a MEAN-based application.
但是,无论您是要构建一个应用程序来可视化城市中的自行车道,还是要设计一种用于绘制全球油井图的工具,还是只是创建一个应用程序来帮助您选择下一个日期-可以访问交互式数据,丰富的地图可能是至关重要的资产。 因此,在这个分为两部分的教程中,我们将编写直接将Google Maps与基于MEAN的应用程序的视图,控制器和数据集成的代码。
In Part I of the tutorial, we’ll be building the initial interface and data-binding the HTML and Angular elements with MongoDB and Google Maps. In Part II of the tutorial, we’ll be utilizing MongoDB’s geospatial and other querying tools to create complex filters on the map itself.
在本教程的第一部分中,我们将构建初始界面,并使用MongoDB和Google Maps对HTML和Angular元素进行数据绑定。 在本教程的第二部分中,我们将利用MongoDB的地理空间和其他查询工具在地图本身上创建复杂的过滤器。
As you follow along, feel encouraged to grab the source code in the link provided. Just note: The code found in the Github link has a few additional tweaks compared to today’s tutorial. To get an exact replica of what we’re building today, use the TutorialMaterial/PartI link.
在继续学习时,建议您在提供的链接中获取源代码。 只需注意:与今天的教程相比,在Github链接中找到的代码还有一些其他调整。 要获得我们今天正在构建的内容的精确副本,请使用TutorialMaterial / PartI链接。
Google Maps API简介 (Intro to the Google Maps API)
As the title clearly suggests, we’ll be using the Google Maps Javascript API throughout. The API is richly documented, easy to learn, and free for low-volume usage. It may be worth flipping through the documentation guides now to see what’s possible. Once you’re ready to begin, head to the API homepage and sign-up to “Get a Key”. Simply follow the instructions for creating a Project and you will be granted a unique API key. Hang onto this key!
正如标题所明确表明的那样,我们将在整个过程中使用Google Maps Javascript API 。 该API有丰富的文档,易于学习,并且对于小批量使用免费。 现在可能值得翻阅文档指南,以了解有什么可能。 准备好开始后,请访问API主页并注册“获取密钥”。 只需按照创建项目的说明进行操作,您将获得唯一的API密钥。 挂到这个键上!
Once you have your key, head to the APIs section of the Google Developer Console and click the link to the Google Maps Javascript API. Make sure the API is Enabled under your new Project. (If it says: “Disable API” then you’re good).
拥有密钥后,请转到Google Developer Console的API部分,然后单击指向Google Maps Javascript API的链接。 确保新项目下的API已启用。 (如果显示:“ Disable API”,则表示您很好)。
整体应用程序框架 (Overall App Skeleton)
The final product we’ll build is a basic two panel application. On the left is a map and on the right is a control panel. In this first part of the tutorial, the control panel will be used to add new users to the map. (In the second part, the control panel will also be used to filter the map results).
我们将构建的最终产品是一个基本的两面板应用程序。 左侧是地图,右侧是控制面板。 在本教程的第一部分中,将使用控制面板将新用户添加到地图。 (在第二部分中,控制面板也将用于过滤地图结果)。
Before we start working, go ahead and create an app directory as follows:
在开始工作之前,请继续按照以下步骤创建一个应用程序目录:
MapApp
-- app // Backend
---- model.js
---- routes.js
-- public // Frontend
---- index.html
---- js
------ app.js
------ addCtrl.js
------ gservice.js
---- style.css
-- server.js // Express Server
-- package.json
We’ll try to keep things simple throughout. The app will be composed of three sections:
我们将尽力使事情变得简单。 该应用程序将由三个部分组成:
- A frontend handling what’s displayed to the user,
前端处理向用户显示的内容,
- A controller and factory for AJAX calls and maintaining the Google Map,
AJAX呼叫和维护Google Map的控制器和工厂,
- A server and database for hosting user data
用于托管用户数据的服务器和数据库
抓住依赖关系并设置HTML (Grabbing Dependencies and Setting up the HTML)
Once you’re ready, run the following commands in your terminal to grab the necessary dependencies from bower:
准备就绪后,在终端中运行以下命令,以从bower获取必要的依赖项:
bower install angular-route#1.4.6
bower install angularjs-geolocation#0.1.1
bower install bootstrap#3.3.5
bower install modernizr#3.0.0
Next, add the following content to your package.json file
接下来,将以下内容添加到package.json文件中
{
"name": "MeanMapsApp",
"main": "server.js",
"dependencies" : {
"express" : "~4.7.2",
"mongoose" : "~4.1.0",
"morgan" : "~1.2.2",
"body-parser": "~1.5.2",
"jsonwebtoken": "^5.0.2",
"method-override": "~2.1.2"
}
}
Then, run npm install
to install the relevant node packages.
然后,运行npm install
安装相关的节点软件包。
Now, replace the content of your index.html file with the content below. There’s nothing too fancy here, so just paste and wait for the explanation after.
现在,用下面的内容替换index.html文件的内容。 这里没有什么太花哨的,因此只需粘贴并等待解释。
<!doctype html>
<!-- Declares meanMapApp as the starting Angular module -->
<html class="no-js" ng-app="meanMapApp">
<head>
<meta charset="utf-8">
<title>Scotch MEAN Map</title>
<meta name="description" content="An example demonstrating Google Map integration with MEAN Apps">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- CSS -->
<link rel="stylesheet" href="../bower_components/bootstrap/dist/css/bootstrap.css"/>
<link rel="stylesheet" href="style.css"/>
<!-- Holder JS -->
<script src="../bower_components/holderjs/holder.js"></script>
<!-- Google Maps API -->
<script src="https://maps.proxy.ustclug.org/maps/api/js?key=YOUR_API_HERE"></script>
<!-- Modernizr -->
<script src="../bower_components/modernizr/bin/modernizr"></script>
<!-- JS Source -->
<script src="../bower_components/jquery/jquery.js"></script>
<script src="../bower_components/angular/angular.js"></script>
<script src="../bower_components/angular-route/angular-route.js"></script>
<script src="../bower_components/angularjs-geolocation/dist/angularjs-geolocation.min.js"></script>
</head>
<body>
<div class="container">
<div class="header">
<ul class="nav nav-pills pull-right">
<li active><a href="">Join the Team</a></li>
<li disabled><a href="">Find Teammates</a></li>
</ul>
<h3 class="text-muted">The Scotch MEAN MapApp</h3>
</div>
<!-- Map and Side Panel -->
<div class="row content">
<!-- Google Map -->
<div class="col-md-7">
<div id="map"><img src="holder.js/645x645"></div>
</div>
<!-- Side Panel -->
<div class="col-md-5">
<div class="panel panel-default">
<!-- Panel Title -->
<div class="panel-heading">
<h2 class="panel-title text-center">Join the Scotch Team! <span class="glyphicon glyphicon-map-marker"></span></h2>
</div>
<!-- Panel Body -->
<div class="panel-body">
<!-- Creates Form (novalidate disables HTML validation, Angular will control) -->
<form name ="addForm" novalidate>
<!-- Text Boxes and Other User Inputs. Note ng-model binds the values to Angular $scope -->
<div class="form-group">
<label for="username">Username <span class="badge">All fields required</span></label>
<input type="text" class="form-control" id="username" placeholder="OldandGold" ng-model="formData.username" required>
</div>
<label class="radio control-label">Gender</label>
<div class="radio">
<label>
<input type="radio" name="optionsRadios" id="radiomale" value="Male" ng-model="formData.gender">
Male
</label>
</div>
<div class="radio" required>
<label>
<input type="radio" name="optionsRadios" id="radiofemale" value="Female" ng-model="formData.gender">
Female
</label>
</div>
<div class="radio">
<label>
<input type="radio" name="optionsRadios" id="radioother" value="What's it to ya?" ng-model="formData.gender">
What's it to ya?
</label>
</div>
<div class="form-group">
<label for="age">Age</label>
<input type="number" class="form-control" id="age" placeholder="72" ng-model="formData.age" required>
</div>
<div class="form-group">
<label for="language">Favorite Language</label>
<input type="text" class="form-control" id="language" placeholder="Fortran" ng-model="formData.favlang" required>
</div>
<div class="form-group">
<label for="latitude">Latitude</label>
<input type="text" class="form-control" id="latitude" value="39.500" ng-model="formData.latitude" readonly>
</div>
<div class="form-group">
<label for="longitude">Longitude</label>
<input type="text" class="form-control" id="longitude" value="-98.350" ng-model="formData.longitude" readonly>
</div>
<div class="form-group">
<label for="verified">HTML5 Verified Location? <span><button class="btn btn-default btn-xs"><span class="glyphicon glyphicon-refresh"></span></button></span></label>
<input type="text" class="form-control" id="verified" placeholder= "Nope (Thanks for spamming my map...)" ng-model="formData.htmlverified" readonly>
</div>
<!-- Submit button. Note that its tied to createUser() function from addCtrl. Also note ng-disabled logic which prevents early submits. -->
<button type="submit" class="btn btn-danger btn-block" ng-disabled="addForm.$invalid">Submit</button>
</form>
</div>
</div>
</div>
</div>
<hr/>
<!-- Footer -->
<div class="footer">
<p class="text-center"><span class="glyphicon glyphicon-check"></span> Created by Ahmed Haque for Scotch IO -
<a href="https://scotch.io/">App Tutorial</a> | <a href="https://github.com/afhaque/MeanMapAppV2.0">Github Repo</a></p>
</div>
</div>
</body>
</html>
At this point, we have a basic HTML page with a div for a map and a div with an HTML form for adding users. That said, there are a few things to note:
至此,我们有了一个基本HTML页面,其中包含用于地图的div和具有用于添加用户HTML表单的div。 就是说,有几件事要注意:
-
We’ve included a link to the Google Maps API at the top of the page. Be sure to insert your own API into the script!
我们在页面顶部提供了指向Google Maps API的链接。 确保将自己的API插入脚本!
-
We’ve included the Angular directive
ng-app = meanMapApp
at the top of the page. We’ll use this to reference our Angular module later on.我们在页面顶部添加了Angular指令
ng-app = meanMapApp
。 稍后我们将使用它来引用我们的Angular模块。 -
We’re using a temporary holderjs image in place of our map for now. This will let us get a quick visual of what we have.
目前,我们正在使用临时的holderjs图像代替地图。 这将使我们快速了解所拥有的东西。
-
We’ve included a
noValidate
attribute in the HTML form element. This disables HTML form validation. Instead, we’ll be using Angular to validate our form.我们在HTML表单元素中包含了
noValidate
属性。 这将禁用HTML表单验证。 相反,我们将使用Angular来验证表单。 -
Most importantly, note the repeated use of
ng-model
throughout the form. Each of these takes content in a textbox or control element and uses it to set the value of an associated property in the scope variableformData
. So if a user sets his username to be “WackaWackaMan”, then the variable$scope.formData.username
would equal “WackaWackaMan” as well.最重要的是,请注意在整个表单中重复使用
ng-model
。 它们中的每一个都在文本框或控件元素中获取内容,并使用它在范围变量formData
设置关联属性的值。 因此,如果用户将其用户名设置为“ WackaWackaMan”,则变量$scope.formData.username
也将等于“ WackaWackaMan”。 -
Lastly, note the function
ng-disabled="addForm.$invalid"
. This will prevent a user from clicking the Submit button unless the form is completely valid. In our case, since all fields have the attribute ofrequired
, this means that all fields will need to be populated before the button is enabled.最后,请注意函数
ng-disabled="addForm.$invalid"
。 除非表单完全有效,否则这将阻止用户单击“提交”按钮。 在我们的例子中,由于所有字段都具有required
的属性,这意味着在启用按钮之前需要填充所有字段。
See. Nothing fancy!
看到。 没有什么花哨!
But at this point, you should be able to do a quick browser inspection.
但是在这一点上,您应该可以对浏览器进行快速检查。
设置节点/ Express服务器 (Setting up the Node/Express Server)
Now that we have an initial HTML template, it’s time to create the Node and Express server that will handle GET and POST requests for data. Paste the below code in the server.js
file. This code is a great template for building quick express servers, so keep it handy. It includes morgan for handling request logs, body-parser for parsing JSON POST bodies, and specifies the location of the index.html file and bower_components.
现在我们有了一个初始HTML模板,是时候创建可以处理GET和POST数据请求的Node and Express服务器了。 将以下代码粘贴到server.js
文件中。 这段代码是构建快速快递服务器的理想模板,因此请随时使用。 它包括用于处理请求日志的morgan,用于解析JSON POST正文的主体解析器,并指定index.html文件和bower_components的位置。
Worth noting is that the server is configured to use localhost:3000
in displaying the app and that we’ll be connecting to a local instance of MongoDB. (Remember, since we’re running this in localhost
to always initiate Mongod
during testing).
值得注意的是,服务器被配置为使用localhost:3000
来显示应用程序,并且我们将连接到MongoDB的本地实例。 (请记住,因为我们在localhost
运行此命令,以便在测试期间始终启动Mongod
)。
// Dependencies
// -----------------------------------------------------
var express = require('express');
var mongoose = require('mongoose');
var port = process.env.PORT || 3000;
var morgan = require('morgan');
var bodyParser = require('body-parser');
var methodOverride = require('method-override');
var app = express();
// Express Configuration
// -----------------------------------------------------
// Sets the connection to MongoDB
mongoose.connect("mongodb://localhost/MeanMapApp");
// Logging and Parsing
app.use(express.static(__dirname + '/public')); // sets the static files location to public
app.use('/bower_components', express.static(__dirname + '/bower_components')); // Use BowerComponents
app.use(morgan('dev')); // log with Morgan
app.use(bodyParser.json()); // parse application/json
app.use(bodyParser.urlencoded({extended: true})); // parse application/x-www-form-urlencoded
app.use(bodyParser.text()); // allows bodyParser to look at raw text
app.use(bodyParser.json({ type: 'application/vnd.api+json'})); // parse application/vnd.api+json as json
app.use(methodOverride());
// Routes
// ------------------------------------------------------
// require('./app/routes.js')(app);
// Listen
// -------------------------------------------------------
app.listen(port);
console.log('App listening on port ' + port);
Boot up mongod
in the terminal. Then run a quick test of the server using the command node server.js
in your terminal window. If all goes well, you should see our earlier HTML content when you navigate to localhost:3000
in your browser.
在终端中启动mongod
。 然后在终端窗口中使用命令node server.js
对服务器进行快速测试。 如果一切顺利,在浏览器中导航到localhost:3000
时,您应该会看到我们较早HTML内容。
创建猫鼬模式 (Create the Mongoose Schema)
Next up, let’s create a Mongoose Schema that we can use to interact with the user data we’ll be dumping into MongoDB. Navigate to your model.js
file and paste the following code
接下来,让我们创建一个Mongoose Schema,该模型可用于与将要转储到MongoDB中的用户数据进行交互。 导航到您的model.js
文件并粘贴以下代码
// Pulls Mongoose dependency for creating schemas
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
// Creates a User Schema. This will be the basis of how user data is stored in the db
var UserSchema = new Schema({
username: {type: String, required: true},
gender: {type: String, required: true},
age: {type: Number, required: true},
favlang: {type: String, required: true},
location: {type: [Number], required: true}, // [Long, Lat]
htmlverified: String,
created_at: {type: Date, default: Date.now},
updated_at: {type: Date, default: Date.now}
});
// Sets the created_at parameter equal to the current time
UserSchema.pre('save', function(next){
now = new Date();
this.updated_at = now;
if(!this.created_at) {
this.created_at = now
}
next();
});
// Indexes this schema in 2dsphere format (critical for running proximity searches)
UserSchema.index({location: '2dsphere'});
// Exports the UserSchema for use elsewhere. Sets the MongoDB collection to be used as: "scotch-users"
module.exports = mongoose.model('scotch-user', UserSchema);
Here we’ve established the structure we’ll be expecting (and enforcing) our user JSON to maintain. As you can see, we’re expecting six different fields:
在这里,我们建立了我们期望(并强制执行)用户JSON维护的结构。 如您所见,我们期待六个不同的领域:
Username
,Username
Gender
,Gender
Age
,Age
Favorite language
,Favorite language
,- Whether or not a user’s location has been
html5 verified
用户的位置是否已通过
html5 verified
We’ve also created pre-save logic which initially sets the created_at
and updated_at
fields equal to the datetime of insertion.
我们还创建了预保存逻辑,该逻辑最初将created_at
和updated_at
字段设置为等于插入的日期时间。
Importantly, we’ve also established that the UserSchema
should be indexed using a 2dsphere
approach. This line is critical, because it allows MongoDB and Mongoose to run geospatial queries on our user data. This means being able to query users based on geographic inclusion, intersection, and proximity. Check out the reference docs on 2dsphere indexes for more information. As an example, we’ll be using the $near query condition in Part II to identify users that fall within so many miles of a given location.
重要的是 ,我们还确定应使用2dsphere
方法对UserSchema
进行索引。 这行很关键,因为它允许MongoDB和Mongoose对我们的用户数据运行地理空间查询。 这意味着能够基于地理包含,交集和邻近性来查询用户。 有关2dsphere索引的更多信息,请参阅参考文档。 例如,我们将在第二部分中使用$ near查询条件来识别落在给定位置这么多英里范围内的用户。
Also, important is the fact that MongoDB requires coordinates to be ordered in [Long, Lat]
format. (Backwards-seeming. I know. But very important.) This is especially important to remember because Google Maps requires coordinates in the other direction [Lat, Long]
. Just try to keep things straight as you’re working.
同样重要的是,MongoDB要求坐标以[Long, Lat]
格式排序。 (向后看。我知道。但是非常重要。)记住这一点特别重要,因为Google Maps需要在另一个方向[Lat, Long]
坐标。 在工作时,尽量保持直截了当。
Finally, the model.js
file ends, with us exporting the Mongoose model and establishing a MongoDB collection of scotch-users
as the holding location for our data. (Note: “scotch-users” isn’t a typo. Mongoose adds an extra letter ‘s’ when creating collections).
最后, model.js
文件结束,我们导出了Mongoose模型并建立了scotch-users
的MongoDB集合作为数据的保存位置。 (注意:“苏格兰用户”不是错字。猫鼬在创建收藏集时会添加一个额外的字母“ s”)。
设置路由+服务器+测试API (Setup the Routes + Server + Testing the API)
We’re making great progress! Next up, we need to create the Express routes for retrieving and creating new users in our MongoDB database. For the purpose of this tutorial, we’re going to create the bare minimum routes: one route to retrieve a list of all users (GET) and one route to add new users (POST). Paste the below code in your routes.js
file to set this up.
我们正在进步! 接下来,我们需要创建Express路由以在MongoDB数据库中检索和创建新用户。 就本教程而言,我们将创建最简单的最小路由:一种路由来检索所有用户的列表(GET),另一种路由来添加新用户(POST)。 将以下代码粘贴到您的routes.js
文件中进行设置。
// Dependencies
var mongoose = require('mongoose');
var User = require('./model.js');
// Opens App Routes
module.exports = function(app) {
// GET Routes
// --------------------------------------------------------
// Retrieve records for all users in the db
app.get('/users', function(req, res){
// Uses Mongoose schema to run the search (empty conditions)
var query = User.find({});
query.exec(function(err, users){
if(err)
res.send(err);
// If no errors are found, it responds with a JSON of all users
res.json(users);
});
});
// POST Routes
// --------------------------------------------------------
// Provides method for saving new users in the db
app.post('/users', function(req, res){
// Creates a new User based on the Mongoose schema and the post bo.dy
var newuser = new User(req.body);
// New User is saved in the db.
newuser.save(function(err){
if(err)
res.send(err);
// If no errors are found, it responds with a JSON of the new user
res.json(req.body);
});
});
};
Then, in your server.js
file, uncomment the line associated with routing to connect our routes to the server:
然后,在您的server.js
文件中,取消注释与路由相关的行以将我们的路由连接到服务器:
require('./app/routes.js')(app);
Things to note here:
这里要注意的事情:
-
We’re declaring our Mongoose model (here titled: “User”) created earlier as a dependency. In the subsequent GET and POST routes we use Mongoose and the User model to query for records and create new records with simple syntax. We create users using the line:
var newuser = new User(req.body);
and retrieve all users with the linevar query = User.find({});
. (Note: These users are NOT yet added to our map. We’re just dealing with the MongoDB database at this point.)我们声明之前创建为依赖项的Mongoose模型(此处为“ User”)。 在随后的GET和POST路由中,我们使用Mongoose和User模型查询记录并使用简单的语法创建新记录。 我们使用以下行创建用户:
var newuser = new User(req.body);
并使用var query = User.find({});
行检索所有用户var query = User.find({});
。 (注意:这些用户尚未添加到我们的地图中。我们现在仅在处理MongoDB数据库。) -
We’re using the route
/user
for both the GET and POST requests. At any point during testing we can direct our browser tolocalhost:3000/users
to see what’s in the database.我们将路由
/user
用于GET和POST请求。 在测试期间的任何时候,我们都可以将浏览器定向到localhost:3000/users
以查看数据库中的内容。
Speaking of testing, now’s a good time to test the routes. Let’s open up Postman (or a similar HTTP testing client) and run two tests.
说到测试,现在是测试路线的好时机。 让我们打开Postman (或类似的HTTP测试客户端)并运行两个测试。
First, let’s run a POST request to create a new user. Paste the below content into the body of a POST request and send it to localhost:3000/users
. Remember to set the content type to “Raw” and “JSON (application/json)” before sending.
首先,让我们运行POST请求以创建一个新用户。 将以下内容粘贴到POST请求的正文中,并将其发送到localhost:3000/users
。 在发送之前,请记住将内容类型设置为“ Raw”和“ JSON(application / json)”。
{
"username": "scotcher",
"gender": "Female",
"age": "25",
"favlang": "Javascript",
"location": [-95.56, 29.735]
}
Once you’ve sent the request, navigate your browser to localhost:3000/users
. If all went well, you should see the JSON you just sent, displayed before you.
发送请求后,将浏览器导航到localhost:3000/users
。 如果一切顺利,您应该会在前面看到刚刚发送的JSON。
Huzzah!
晕!
创建添加控制器 (Create the Add Controller)
We’re slowly but surely clawing forward. Now it’s time to bring in Angular. First things first, let’s declare the intial angular module in public->js->app.js
. This file will serve as the starting Angular module. Once again, let’s keep it simple.
我们正在缓慢但肯定地向前迈进。 现在是时候引入Angular了。 首先,让我们在public->js->app.js
声明初始角度模块。 该文件将用作启动Angular模块。 再次让我们保持简单。
// Declares the initial angular module "meanMapApp". Module grabs other controllers and services.
var app = angular.module('meanMapApp', ['addCtrl', 'geolocation']);
For now the module will pull only from addCtrl
(the controller for our Add User Form) and geolocation, a module we downloaded earlier through Bower. We’ll be using the ‘Geolocation’ module to provide a user’s HTML5 verified location later.
现在,该模块将仅从addCtrl
(我们的“添加用户表单”的控制器)和地理位置(这是我们之前通过Bower下载的模块)中提取的。 稍后,我们将使用“地理位置”模块为用户提供经过HTML5验证的位置。
Next, let’s open up the addCtrl.js
file and begin creating our controller. Let’s begin with the basics: creating the function needed to add users to our database.
接下来,让我们打开addCtrl.js
文件并开始创建我们的控制器。 让我们从基础开始:创建将用户添加到我们的数据库所需的功能。
// Creates the addCtrl Module and Controller. Note that it depends on the 'geolocation' module and service.
var addCtrl = angular.module('addCtrl', ['geolocation']);
addCtrl.controller('addCtrl', function($scope, $http, geolocation){
// Initializes Variables
// ----------------------------------------------------------------------------
$scope.formData = {};
var coords = {};
var lat = 0;
var long = 0;
// Set initial coordinates to the center of the US
$scope.formData.latitude = 39.500;
$scope.formData.longitude = -98.350;
// Functions
// ----------------------------------------------------------------------------
// Creates a new user based on the form fields
$scope.createUser = function() {
// Grabs all of the text box fields
var userData = {
username: $scope.formData.username,
gender: $scope.formData.gender,
age: $scope.formData.age,
favlang: $scope.formData.favlang,
location: [$scope.formData.longitude, $scope.formData.latitude],
htmlverified: $scope.formData.htmlverified
};
// Saves the user data to the db
$http.post('/users', userData)
.success(function (data) {
// Once complete, clear the form (except location)
$scope.formData.username = "";
$scope.formData.gender = "";
$scope.formData.age = "";
$scope.formData.favlang = "";
})
.error(function (data) {
console.log('Error: ' + data);
});
};
});
The logic here is straightforward if you’ve worked with Angular before, but for those who haven’t, the code essentially refers back to each of the textboxes and control elements using the $scope.formData.VAR
format. These are initially set to blanks (except location, which has initial dummy numbers).
如果您以前使用过Angular,那么这里的逻辑很简单,但是对于那些还没有使用过Angular的人,代码实际上使用$scope.formData.VAR
格式引用了每个文本框和控件元素。 最初将它们设置为空白(具有初始虚拟数字的位置除外)。
Once a user hits a button associated with the createUser()
function, the Angular controller initiates a process of grabbing each of the textbox and control values and storing them in the object userData
. From there, an http post request is made to the '/users
route we created earlier. The form is cleared (except for location) and the function completes.
一旦用户单击与createUser()
函数关联的按钮,Angular控制器就会启动一个过程,以获取每个文本框和控件值并将其存储在对象userData
。 从那里,对我们先前创建的'/users
路由发出了一个http post请求。 清除表格(位置除外),然后功能完成。
The next step is for us to return to our index.html
file and attach our “Submit” button to the controller. To do this we need to make two modifications to our index.html
file.
下一步是我们返回到index.html
文件,并将“提交”按钮附加到控制器。 为此,我们需要对我们的index.html
文件进行两次修改。
First, we need to include the scripts associated with app.js
and addCtrl.js
in our index.html
file.
首先,我们需要在index.html
文件中包括与app.js
和addCtrl.js
相关的脚本。
<!-- Angular Scripts -->
<script src="js/app.js"></script>
<script src="js/addCtrl.js"></script>
Then, we need to attach our addCtrl
controller to the HTML body.
然后,我们需要将addCtrl
控制器附加到HTML主体。
<body ng-controller="addCtrl">
Next, we need to attach our createUser()
function through the “Submit” button’s ng-click
event.
接下来,我们需要通过“提交”按钮的ng-click
事件附加createUser()
函数。
<button type="submit" class="btn btn-danger btn-block" ng-click="createUser()" ng-disabled="addForm.$invalid">Submit</button>
Great. Now it’s time to run a quick test. Fire up your server using node server.js
then navigate to localhost:3000
. At this point, try adding users via the HTML form. If everything’s been coded correctly, you should be able to see your newest user on the localhost:3000/users
page.
大。 现在是时候进行快速测试了。 使用node server.js
启动服务器,然后导航到localhost:3000
。 此时,请尝试通过HTML表单添加用户。 如果一切均已正确编码,则应该可以在localhost:3000/users
页面上看到最新的用户。
Eureka! Now onto the real reason you’re here….
尤里卡! 现在,您来到这里的真正原因是…。
创建Google Maps Factory服务 (Create the Google Maps Factory Service)
Maps! This is where things get tricky. So follow closely.
地图! 这是棘手的地方。 因此,请密切注意。
At a high-level, what we need to do next is to take the user data we’ve collected to this point… 1) Convert each into a Google Maps readable format and 2) Drop Google Map markers to the correct coordinates. Additionally, we’re going to need to build functionality for pop-ups and clickable map coordinates.
从高层次上讲,我们下一步要做的就是获取我们收集到的用户数据… 1)将每个数据转换为Google Maps可读格式,2)将Google Map标记拖放到正确的坐标。 此外,我们将需要为弹出窗口和可点击的地图坐标构建功能。
To handle all of this, we’re going to create a new Angular Factory. This factory will be used by our addCtrl
controller to complete all of the logic associated with map building.
为了处理所有这些,我们将创建一个新的Angular Factory。 我们的addCtrl
控制器将使用此工厂来完成与地图构建相关的所有逻辑。
Go ahead and open your public->app->gservice.js file
. Then paste the below code in place.
继续并打开您的public->app->gservice.js file
。 然后将以下代码粘贴到位。
// Creates the gservice factory. This will be the primary means by which we interact with Google Maps
angular.module('gservice', [])
.factory('gservice', function($http){
// Initialize Variables
// -------------------------------------------------------------
// Service our factory will return
var googleMapService = {};
// Array of locations obtained from API calls
var locations = [];
// Selected Location (initialize to center of America)
var selectedLat = 39.50;
var selectedLong = -98.35;
// Functions
// --------------------------------------------------------------
// Refresh the Map with new data. Function will take new latitude and longitude coordinates.
googleMapService.refresh = function(latitude, longitude){
// Clears the holding array of locations
locations = [];
// Set the selected lat and long equal to the ones provided on the refresh() call
selectedLat = latitude;
selectedLong = longitude;
// Perform an AJAX call to get all of the records in the db.
$http.get('/users').success(function(response){
// Convert the results into Google Map Format
locations = convertToMapPoints(response);
// Then initialize the map.
initialize(latitude, longitude);
}).error(function(){});
};
// Private Inner Functions
// --------------------------------------------------------------
// Convert a JSON of users into map points
var convertToMapPoints = function(response){
// Clear the locations holder
var locations = [];
// Loop through all of the JSON entries provided in the response
for(var i= 0; i < response.length; i++) {
var user = response[i];
// Create popup windows for each record
var contentString =
'<p><b>Username</b>: ' + user.username +
'<br><b>Age</b>: ' + user.age +
'<br><b>Gender</b>: ' + user.gender +
'<br><b>Favorite Language</b>: ' + user.favlang +
'</p>';
// Converts each of the JSON records into Google Maps Location format (Note [Lat, Lng] format).
locations.push({
latlon: new google.maps.LatLng(user.location[1], user.location[0]),
message: new google.maps.InfoWindow({
content: contentString,
maxWidth: 320
}),
username: user.username,
gender: user.gender,
age: user.age,
favlang: user.favlang
});
}
// location is now an array populated with records in Google Maps format
return locations;
};
// Initializes the map
var initialize = function(latitude, longitude) {
// Uses the selected lat, long as starting point
var myLatLng = {lat: selectedLat, lng: selectedLong};
// If map has not been created already...
if (!map){
// Create a new map and place in the index.html page
var map = new google.maps.Map(document.getElementById('map'), {
zoom: 3,
center: myLatLng
});
}
// Loop through each location in the array and place a marker
locations.forEach(function(n, i){
var marker = new google.maps.Marker({
position: n.latlon,
map: map,
title: "Big Map",
icon: "http://maps.google.com/mapfiles/ms/icons/blue-dot.png",
});
// For each marker created, add a listener that checks for clicks
google.maps.event.addListener(marker, 'click', function(e){
// When clicked, open the selected marker's message
currentSelectedMarker = n;
n.message.open(map, marker);
});
});
// Set initial location as a bouncing red marker
var initialLocation = new google.maps.LatLng(latitude, longitude);
var marker = new google.maps.Marker({
position: initialLocation,
animation: google.maps.Animation.BOUNCE,
map: map,
icon: 'http://maps.google.com/mapfiles/ms/icons/red-dot.png'
});
lastMarker = marker;
};
// Refresh the page upon window load. Use the initial latitude and longitude
google.maps.event.addDomListener(window, 'load',
googleMapService.refresh(selectedLat, selectedLong));
return googleMapService;
});
Let’s breakdown what’s going on in here.
让我们分解一下这里发生的事情。
1) First, we created a generic Angular module and factory called gservice
and specified that it depends on the$http
service.
1)首先,我们创建了一个通用的Angular模块和名为gservice
工厂,并指定它依赖于$http
服务。
2) We initialized a set of key variables:
2)我们初始化了一组关键变量:
- The
googleMapService
object that will hold therefresh
function we’ll use to rebuild the map,拥有
refresh
功能的googleMapService
对象,我们将使用它来重建地图, - The
locations
array which will hold all of the converted locations in the database.locations
数组将保存数据库中所有已转换的位置。 - The
selectedLat
andselectedLong
variables, which hold the specific location we’re looking at during any point in time.selectedLat
和selectedLong
变量,用于保存我们在任何时间点上要查看的特定位置。
3) We then created a refresh()
function, which takes new coordinate data and uses it to refresh the map. To do this, the function performs an AJAX call to the database and pulls all of the saved records. It then takes these records and passes them to a function called convertToMapPoints
, which loops through each record — creating an array of Google formatted coordinates with pre-built pop-up messages. (Note that Google formats its coordinates [Lat, Long]
, so we needed to flip the order from how we saved things in MongoDb).
3)然后,我们创建了refresh()
函数,该函数获取新的坐标数据并将其用于刷新地图。 为此,该函数对数据库执行AJAX调用并提取所有保存的记录。 然后,它将这些记录获取,并将它们传递给一个名为convertToMapPoints
的函数,该函数循环遍历每条记录-使用预先构建的弹出消息创建Google格式化坐标数组。 (请注意,Google格式化其坐标[Lat, Long]
,因此我们需要从在MongoDb中保存内容的方式翻转顺序)。
4) Once the values have been converted, the refresh
function sets off the initialize()
function. This function, creates a generic Google Map and places it in the index.html
file where the div id of map
exists. The initialize function, then loops through each of the locations in the locations
array and places a blue-dot marker on that location’s geographic position. These markers are each given a listener that opens their message boxes on click. Finally, the initialize()
function ends with a bouncing red marker being placed at the initial location (pre-set to center of America as of now).
4)值转换后, refresh
函数将initialize()
函数。 此函数创建通用的Google Map,并将其放置在map
的div ID存在的index.html
文件中。 然后,初始化函数将循环遍历locations
数组中的每个位置,并在该位置的地理位置上放置一个蓝点标记。 这些标记分别具有一个侦听器,可在单击时打开其消息框。 最后, initialize()
函数以在初始位置(自目前预设为美国中部)放置的红色反弹标记结束。
5) The refresh()
function is run immediately upon window load, allowing the map to be seen right away.
5) refresh()
函数在加载窗口后立即运行,从而可以立即看到地图。
If you’re still with me, then let’s incorporate our gservice
and refresh()
functions in the rest of the app. Since we’ll be using the refresh()
function whenever we add a new user, it makes sense to include it in our addCtrl.js
file.
如果您仍然与我在一起,那么让我们将gservice
和refresh()
函数合并到该应用程序的其余部分中。 由于每当添加新用户时都会使用refresh()
函数,因此将其包含在addCtrl.js
文件中是addCtrl.js
。
Modify the initial call in addCtrl
so it includes both the gservice
module and factory.
修改addCtrl
的初始调用,使其包含gservice
模块和工厂。
var addCtrl = angular.module('addCtrl', ['geolocation', 'gservice']);
addCtrl.controller('addCtrl', function($scope, $http, geolocation, gservice){ ...
Additionally, add the below line in the $http.post
function beneath the lines where the form is cleared. This will immediately refresh the map when a new user is added.
此外,在清除表单的行下方的$http.post
函数中添加以下行。 添加新用户后,这将立即刷新地图。
// Logic for Clearing the FOrm
// ...
// Refresh the map with new data
gservice.refresh($scope.formData.latitude, $scope.formData.longitude);
Next add the gservice
module to our main app.js
file.
接下来,将gservice
模块添加到我们的主app.js
文件中。
var app = angular.module('meanMapApp', ['addCtrl', 'geolocation', 'gservice']);
Then add the ‘gservice’ script to our index.html
file and delete the reference to our holder image, and include the css to set our map’s height and width.
然后将’gservice’脚本添加到我们的index.html
文件中,并删除对我们的持有人图像的引用,并包括CSS以设置地图的高度和宽度。
<script src="js/gservice.js"></script>
<!-- Google Map -->
<div class="col-md-7">
<div id="map" style="width:645px; height:645px"></div>
</div>
And finally… it’s time to test. Fire up your server.js
and let’s see what localhost:3000
looks like now. If all went well, you should be seeing two blue dots correlating to the two users we added to your database.
最后…是时候测试了。 启动您的server.js
,让我们看看localhost:3000
现在是什么样子。 如果一切顺利,您应该会看到两个蓝点,它们与我们添加到数据库中的两个用户相关。
Woohoo! We are mapping now! As a further test, go ahead and click any one of the markers you see. You should see a pop-up window with info.
oo! 我们正在映射! 作为进一步的测试,请继续并单击您看到的任何一个标记。 您应该会看到一个包含信息的弹出窗口。
Yay! Now we’re popping now as well!
好极了! 现在我们也在弹出!
增加点击性 (Adding Clickability)
We’ve done some great things here, but right now — there is no way for users to actually set their location on the map before submitting. Everyone is just stuck in the center of Kansas. So let’s create some functionality for map clicks.
我们在这里做了一些很棒的事情,但是现在-用户无法在提交之前在地图上实际设置其位置。 每个人都被困在堪萨斯州的中心。 因此,让我们为地图点击创建一些功能。
First, add the following service properties in the section for initializing variables:
首先,在本节中添加以下服务属性以初始化变量:
// Handling Clicks and location selection
googleMapService.clickLat = 0;
googleMapService.clickLong = 0;
Next, add the below listener to the bottom of the initialize()
function, right under the section that set the initial location as a bouncing red marker.
接下来,将以下侦听器添加到initialize()
函数的底部,就在将初始位置设置为红色反弹标记的部分的正下方。
// Bouncing Red Marker Logic
// ...
// Function for moving to a selected location
map.panTo(new google.maps.LatLng(latitude, longitude));
// Clicking on the Map moves the bouncing red marker
google.maps.event.addListener(map, 'click', function(e){
var marker = new google.maps.Marker({
position: e.latLng,
animation: google.maps.Animation.BOUNCE,
map: map,
icon: 'http://maps.google.com/mapfiles/ms/icons/red-dot.png'
});
// When a new spot is selected, delete the old red bouncing marker
if(lastMarker){
lastMarker.setMap(null);
}
// Create a new red bouncing marker and move to it
lastMarker = marker;
map.panTo(marker.position);
});
Run your server.js
file and test it out. If everything is working, you should be able to move the red dot around the map.
运行您的server.js
文件并进行测试。 如果一切正常,则应该可以在地图上移动红点。
This looks great, but astute readers may have noticed that the coordinates in our form never changed to reflect the dot’s movement. The coordinates always point to Kansas. This makes sense because we never created any logic linking the marker’s movement with our Angular controller. Let’s do that now.
这看起来不错,但精明的读者可能已经注意到,我们表格中的坐标从未更改过以反映点的移动。 坐标始终指向堪萨斯州。 这是有道理的,因为我们从未创建任何将标记的运动与Angular控制器联系起来的逻辑。 现在开始吧。
First, let’s add the $rootScope
to the dependency list of the gservice
factory. The reason we’re including $rootScope
here is because we’ll be broadcasting the result of clicks back to our original Angular form, so we can see the coordinates clicked onto.
首先,让我们将$rootScope
添加到gservice
工厂的依赖项列表中。 之所以在此处包含$rootScope
,是因为我们会将点击的结果传播回原始的Angular表单,因此我们可以看到点击的坐标。
angular.module('gservice', [])
.factory('gservice', function($rootScope, $http){
Then add the logic associated with the broadcasting to $rootScope
at the conclusion of the map’s click listener event we just created.
然后,在我们刚刚创建的地图的click listener事件结束时,将与广播关联的逻辑添加到$rootScope
。
// Update Broadcasted Variable (lets the panels know to change their lat, long values)
googleMapService.clickLat = marker.getPosition().lat();
googleMapService.clickLong = marker.getPosition().lng();
$rootScope.$broadcast("clicked");
Now, return back to the addCtrl.js
file and include the $rootScope
service in our addCtrl
controller.
现在,返回到addCtrl.js
文件,并将$rootScope
服务包含在我们的addCtrl
控制器中。
addCtrl.controller('addCtrl', function($scope, $http, $rootScope, geolocation, gservice){
Finally, add the below function on top of the createUser
function. Here you can see that this function listens for when the gservice
function broadcasts the “click” event. On click, the addCtrl
controller will set the value of the latitude and longitude of the form equal to the click coordinates (rounded to 3). It will also note that the location has not been HTML Verified (to differentiate between spam and authentic locations).
最后,在createUser
函数的顶部添加以下函数。 在这里,您可以看到gservice
函数广播“ click”事件时该函数正在侦听。 单击时, addCtrl
控制器将设置表单的纬度和经度的值等于单击坐标(四舍五入)。 还应注意,该位置尚未经过HTML验证(以区分垃圾邮件位置和真实位置)。
// Get coordinates based on mouse click. When a click event is detected....
$rootScope.$on("clicked", function(){
// Run the gservice functions associated with identifying coordinates
$scope.$apply(function(){
$scope.formData.latitude = parseFloat(gservice.clickLat).toFixed(3);
$scope.formData.longitude = parseFloat(gservice.clickLong).toFixed(3);
$scope.formData.htmlverified = "Nope (Thanks for spamming my map...)";
});
});
// Create User Function
// ...
Go ahead and test it now.
继续进行测试。
Looking good!
看起来不错!
获取HTML5验证位置 (Getting HTML5 Verified Locations)
At this point, we have a pretty slick app on our hands. So if you feel like checking-out, I won’t stop you. But for those hungry for the cherry on top, let’s add one last function. Up until now, we’re leaving it to users to provide us with their actual location. This might be fine if we’re okay with crappy, spam data. But if we want a way to discriminate true locations — we need something better.
至此,我们手上有了一个漂亮的应用程序。 因此,如果您想退房,我不会阻止您。 但是,对于那些渴望获得顶层樱桃的人,让我们添加最后一个功能。 到目前为止,我们将其留给用户以向我们提供其实际位置。 如果我们可以接收垃圾数据,这可能很好。 但是,如果我们想要一种区分真实位置的方法-我们需要更好的东西。
This is where HTML5 Geolocation comes in. With the latest version of HTML comes the ability to identify precisely where a user is located–so long as they grant permission in the browser. Let’s add one last function to our app that sets the initial location of our user’s dot to their HTML5 verified location.
这就是HTML5地理位置的用处 。最新版本HTML附带了能够精确识别用户所在位置的功能-只要他们在浏览器中授予许可即可。 让我们向应用程序添加最后一个函数,该函数将用户点的初始位置设置为经过HTML5验证的位置。
To do this, we’ll be utilizing the open-source angularjs-geolocation library. The library makes it easy to incorporate HTML5 geolocation requests in Angular applications. Since we’ve already added references to the geolocation
service in our addCtrl
and app.js
files, all we need to do is include the logic associated with such a call in addCtrl
. Simply paste the below code in the initialization section of addCtrl.js
after the initial coordinates are set.
为此,我们将利用开源的angularjs-geolocation库。 该库使在Angular应用程序中合并HTML5地理位置请求变得容易。 由于我们已经在addCtrl
和app.js
文件中添加了对geolocation
服务的引用,因此我们所需要做的就是在addCtrl
包括与此类调用相关的逻辑。 设置初始坐标后,只需将以下代码粘贴到addCtrl.js
的初始化部分中addCtrl.js
。
// Initial Coordinates set
// ...
// Get User's actual coordinates based on HTML5 at window load
geolocation.getLocation().then(function(data){
// Set the latitude and longitude equal to the HTML5 coordinates
coords = {lat:data.coords.latitude, long:data.coords.longitude};
// Display coordinates in location textboxes rounded to three decimal points
$scope.formData.longitude = parseFloat(coords.long).toFixed(3);
$scope.formData.latitude = parseFloat(coords.lat).toFixed(3);
// Display message confirming that the coordinates verified.
$scope.formData.htmlverified = "Yep (Thanks for giving us real data!)";
gservice.refresh($scope.formData.latitude, $scope.formData.longitude);
});
The logic here uses a simple geolocation.getLocation
function to return coordinate data. This coordinate data is then parsed, rounded to three decimal points (for privacy reasons), and then passed to the $scope.formData.longitude
and $scope.formData.latitude
. Once this takes place, we refresh the map and pass in the newest coordinates to be added.
这里的逻辑使用简单的geolocation.getLocation
函数返回坐标数据。 然后解析此坐标数据,四舍五入到三个小数点(出于隐私原因),然后传递给$scope.formData.longitude
和$scope.formData.latitude
。 一旦发生这种情况,我们将刷新地图并传递要添加的最新坐标。
Let’s test this out. If things went correctly, your browser should have asked you for location access and then moved your red dot near your precise location.
让我们测试一下。 如果一切正常,您的浏览器应该要求您访问位置,然后将红点移到您的精确位置附近。
Voila!
瞧!
…进入第二部分! (… onto Part II!)
Phew. We really covered a lot today. But hopefully, this exercise has left you empowered to chart your own map-making path and to lay your mark on the world at large. (Puns definitely intended.)
ew 今天我们真的涵盖了很多。 但希望这项练习使您有权绘制自己的地图制作路线,并在整个世界上树立自己的烙印。 (绝对是双关语。)
We’ll be back in a week or so for Part II, where we’ll enhance our newly created Map App with querying and filtering tools. In the meantime, keep experimenting and adding new features at your own pace. Stay tuned!
我们将在大约一周后的第二部分回来,在该部分中,我们将使用查询和过滤工具来增强我们新创建的Map App。 同时,请按照自己的进度继续尝试并添加新功能。 敬请关注!
翻译自: https://scotch.io/tutorials/making-mean-apps-with-google-maps-part-i
map和stack
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/139605.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...