Integrating Google map with dynamic markers using AJAX is a very handy feature to have when you want to increase the user experience. Visualising is paramount when it comes to location, address, distance etc in this modern technology. In particularly when it comes to the mobile responsive website and apps.
In this article, I will try to explain step by step guide implementing the Google Map with ASP.NET MVC and AJAX. I am using Visual Studio 2015 Express edition and MS SQL Express database. In any case, hope this will help young developers.
The finish product will look like this –
Create table
First thing first, I am using the code first migration in this application even though I just want to show you the table structure of the table that I will be working with. Hopefully, this will reduce the confusion and make it easier to understand. I am using Parks table to demonstrate this example, this table holds GPS coordinates along with other attributes.
Here is the script for the parks table –
CREATE TABLE [dbo].[Parks]( [parkId] [int] IDENTITY(1,1) NOT NULL, [Name] [nvarchar](120) NULL, [Region] [nvarchar](max) NULL, [District] [nvarchar](max) NULL, [Description] [nvarchar](max) NULL, [Tenure] [nvarchar](max) NULL, [Longitude] [decimal](18, 10) NOT NULL, [Latitude] [decimal](18, 10) NOT NULL, [Status] [nvarchar](max) NULL, [CreateDate] [datetime] NOT NULL, [CreatedBy] [nvarchar](max) NULL, CONSTRAINT [PK_dbo.Parks] PRIMARY KEY CLUSTERED ( [parkId] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY]
Insert Data
Once the table is created, I have imported data from another database (Oracle) using export-import methodology. I use the popular SQL Server Management Tool for managing my SQL databases and TOAD for oracle databases. I must say, TOAD is the best database management tool that I have found in the market. Unfortunately, the downside of this – it is not free :(. Anyway regardless of what tool you use as long as it does the same things.
Model
Once we have the table and data ready. Let’s move on to the next task. I must say since I am using MVC framework and methodology for this app. I have created a model for this. I assume you have good understanding on how MVC (Model View Controller) works. Here is the model code –
using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Web; using System.Web.Mvc; namespace dpaw_alerts.Models { public class Park { [Key] public int parkId { get; set; } [Display(Name ="RATIS ID")] public int RPrkId { get; set; } [Display(Name = "Park Name"), MaxLength(120, ErrorMessage ="Park name is too long")] public string Name { get; set; } public string Region { get; set; } public string District { get; set; } public string Description { get; set; } public string Tenure { get; set; } [DisplayFormat(DataFormatString = "{0:N10}", ApplyFormatInEditMode = true)] public decimal Longitude { get; set; } [DisplayFormat(DataFormatString = "{0:N10}", ApplyFormatInEditMode = true)] public decimal Latitude { get; set; } public string Status { get; set; } [DisplayFormat(DataFormatString = "{0:dd/MM/yyyy}")] public DateTime CreateDate { get { return (_createdOn == DateTime.MinValue) ? DateTime.Now : _createdOn; } set { _createdOn = value; } } public string CreatedBy { get; set; } // Private private DateTime _createdOn = DateTime.Now; public IEnumerable<SelectListItem> sts { get { return new SelectList(new[] { new {status= "Active" }, new {status= "Inactive" }, }, "status", "status", "status"); } } } }
[tip title=”Note”]Please note I have omitted some of the columns in my code snippets since they are not necessary in this example.[/tip]
Controller
Now, let’s create the controller. I have to create two ActionResult events – One for the Index.cshtml page another one just to produce the JSON data so that I can access this through AJAX request from the index.cshtml page. You can create a scaffold controller based on the model with create, edit, and delete (CRUD) operations.
The controller code is given below. Please note in my index.cshtml page I am displaying all the parks in table as default view and using tab to switch to mapview.
using System; using System.Collections.Generic; using System.Data; using System.Data.Entity; using System.Linq; using System.Threading.Tasks; using System.Net; using System.Web; using System.Web.Mvc; using dpaw_alerts.Models; namespace dpaw_alerts.Controllers { [Authorize] public class ParksController : Controller { private ApplicationDbContext db = new ApplicationDbContext(); // GET: Parks public async Task<ActionResult> Index() { return View(await db.Parks.ToListAsync()); } // GET: Parks public ActionResult listMap() { return PartialView("_listMap",db.Parks.ToList()); } // GET: Parks [HttpGet] public ActionResult map() { var q = from t in db.Parks select new { t.Name, t.Latitude, t.Longitude, t.Description }; // return PartialView("_map", q.ToList()); return Json(q, JsonRequestBehavior.AllowGet); } // GET: Parks/Details/5 public async Task<ActionResult> Details(int? id) { if (id == null) { return new HttpStatusCodeResult(HttpStatusCode.BadRequest); } Park park = await db.Parks.FindAsync(id); if (park == null) { return HttpNotFound(); } return View(park); } // GET: Parks/Create public ActionResult Create() { ViewBag.status = new SelectListItem[]{ new SelectListItem() {Text = "Active", Value="Active"}, new SelectListItem() {Text = "Inactive ", Value="Inactive"} }; return View(); } // POST: Parks/Create // To protect from overposting attacks, please enable the specific properties you want to bind to, for // more details see http://go.microsoft.com/fwlink/?LinkId=317598. [HttpPost] [ValidateAntiForgeryToken] public async Task<ActionResult> Create([Bind(Include = "parkId,RPrkId,Name,Region,District,Description,Tenure,Longitude,Latitude,Status,CreatedBy")] Park park) { ViewBag.status = new SelectListItem[]{ new SelectListItem() {Text = "Active", Value="Active"}, new SelectListItem() {Text = "Inactive ", Value="Inactive"} }; if (ModelState.IsValid) { var usr = User.Identity.Name; // park.CreatedBy = usr; db.Parks.Add(park); await db.SaveChangesAsync(); return RedirectToAction("Index"); } return View(park); } // GET: Parks/Edit/5 public async Task<ActionResult> Edit(int? id) { ViewBag.status = new SelectListItem[]{ new SelectListItem() {Text = "Active", Value="Active"}, new SelectListItem() {Text = "Inactive ", Value="Inactive"} }; if (id == null) { return new HttpStatusCodeResult(HttpStatusCode.BadRequest); } Park park = await db.Parks.FindAsync(id); if (park == null) { return HttpNotFound(); } return View(park); } // POST: Parks/Edit/5 // To protect from overposting attacks, please enable the specific properties you want to bind to, for // more details see http://go.microsoft.com/fwlink/?LinkId=317598. [HttpPost] [ValidateAntiForgeryToken] public async Task<ActionResult> Edit([Bind(Include = "parkId,RPrkId,Name,Region,District,Description,Tenure,Longitude,Latitude,Status")] Park park) { ViewBag.status = new SelectListItem[]{ new SelectListItem() {Text = "Active", Value="Active"}, new SelectListItem() {Text = "Inactive ", Value="Inactive"} }; if (ModelState.IsValid) { db.Entry(park).State = EntityState.Modified; await db.SaveChangesAsync(); return RedirectToAction("Index"); } return View(park); } // GET: Parks/Delete/5 public async Task<ActionResult> Delete(int? id) { if (id == null) { return new HttpStatusCodeResult(HttpStatusCode.BadRequest); } Park park = await db.Parks.FindAsync(id); if (park == null) { return HttpNotFound(); } return View(park); } // POST: Parks/Delete/5 [HttpPost, ActionName("Delete")] [ValidateAntiForgeryToken] public async Task<ActionResult> DeleteConfirmed(int id) { Park park = await db.Parks.FindAsync(id); db.Parks.Remove(park); await db.SaveChangesAsync(); return RedirectToAction("Index"); } [HttpPost] // [ChildActionOnly] public async Task<ActionResult> AjaxDelete(int? parkId) { if (parkId == null) { return Json(new { success = false }); } Park park = await db.Parks.FindAsync(parkId); db.Parks.Remove(park); await db.SaveChangesAsync(); return Json(new { success = true }); } [ChildActionOnly] public ActionResult Status(List<String> sts) { return View(sts); } protected override void Dispose(bool disposing) { if (disposing) { db.Dispose(); } base.Dispose(disposing); } } }
I personally like to use an async method in my application. Of course, it has some pros and cons. I have included the full codes from my controller. You might notice, I am giving access to authorize users only to this controller.
In this example, I am particularly interested in the following events –
private ApplicationDbContext db = new ApplicationDbContext(); // GET: Parks public async Task<ActionResult> Index() { return View(await db.Parks.ToListAsync()); } // GET: Parks [HttpGet] public ActionResult map() { var q = from t in db.Parks select new { t.Name, t.Latitude, t.Longitude, t.Description }; return Json(q, JsonRequestBehavior.AllowGet); }
First index method basically provides me the list of parks using Entity Framework select method. Very simple and easy to use. This will select all the columns from the table and return the data.
The Get request map() will produces the JSON data and make it available for use through GET request. If you try the URL http://localhost:16659/parks/map in the browser, you will get the JSON data like below. You should test it or debug it before you try the next step.
[attention title=”Note”]You must note here, I didn’t include all the table columns since I only need GPS coordinates along with name and description fields. This is to reduce the data size and improve the app performance.[/attention]
View
The controller seems to be working as we expected and producing JSON data through URI or CURL request. Now it is time to create the view. Before you create the view, make sure you have JQuery library installed in your application. If you are using Visual Studio 2015, it should include the library with default packages. Check your bundleConfig.cs file, it should have something like below –
[note title=”Note”]I have added some custom bundles for the theme I am working with. You need to have at least the highlighted bundles in your app to work.[/note]
In the Index.cshtml page, I have placed the following codes. I am using tab to switch between table view and map view. I don’t want to load the map by default since it might slow the down page a bit instead I am using gmap id to trigger the AJAX request and load the map when a user clicks on the tab.
the DIV with id map_canvas will be filled with map content including markers.
In the above code, I have used a loading overlay to give a bit better user experience when the browser loading content in the map section.
Place the following javascript at the bottom of the Index.cshtml page.
<script> $("#gmap").click(function () { // $(".loading-spinner").show(); Initialize(); }); // Where all the fun happens function Initialize() { // Google has tweaked their interface somewhat - this tells the api to use that new UI google.maps.visualRefresh = true; var Tunisie = new google.maps.LatLng(-30.81881, 116.16596); // These are options that set initial zoom level, where the map is centered globally to start, and the type of map to show var mapOptions = { zoom: 6, center: Tunisie, mapTypeId: google.maps.MapTypeId.G_NORMAL_MAP }; // This makes the div with id "map_canvas" a google map var map = new google.maps.Map(document.getElementById("map_canvas"), mapOptions); // put in some information about each json object - in this case, the opening hours. var infowindow; $.ajax({ type: "GET", url: '@Url.Action("map", "Parks")', dataType: 'json', contentType: 'application/x-www-form-urlencoded', success: function (data) { // data1 = JSON.stringify(data); // $("#data").html(data1); $.each(data, function (i, item) { // alert(item.Longitude); var marker = new google.maps.Marker({ 'position': new google.maps.LatLng(item.Latitude, item.Longitude), 'map': map, 'title': item.Name }); // Make the marker-pin blue! marker.setIcon('http://maps.google.com/mapfiles/ms/icons/blue-dot.png') // finally hook up an "OnClick" listener to the map so it pops up out info-window when the marker-pin is clicked! google.maps.event.addListener(marker, 'click', function () { if (infowindow) infowindow.close(); infowindow = new google.maps.InfoWindow({ content: "<div class='infoDiv'><h3>" + item.Name + "</h3>" + item.Description + "</div>" }); infowindow.open(map, marker); }); }); } }) .done(function() { $(".loadingOverlay").hide(); }); } </script>
Happy programming.
source code demo. thanks
Send source code demo for me. Thanks!
Please send me source code. Thanks
Kindly share the source code
Great Work, Please source code @ sajjadahmed_bhatti@hotmail.com
Thanks