What is the Proper Way to Destroy a Map Instance? - javascript

I recently developed an html5 mobile application. The application was a single page where navigation hash change events replaced the entire DOM. One section of the application was a Google Map using API v3. Before the map div is removed from the DOM, I want to remove any event handlers/listeners and free up as much memory as possible as the user may not return to that section again.
What is the best way to destroy a map instance?

I'm adding a second answer on this question, because I don't want to remove the back and forth we had via follow-up comments on my previous answer.
But I recently came across some information that directly addresses your question and so I wanted to share. I don't know if you are aware of this, but during the Google Maps API Office Hours May 9 2012 Video, Chris Broadfoot and Luke Mahe from Google discussed this very question from stackoverflow. If you set the video playback to 12:50, that is the section where they discuss your question.
Essentially, they admit that it is a bug, but also add that they don't really support use cases that involve creating/destroying successive map instances. They strongly recommend creating a single instance of the map and reusing it in any scenario of this kind. They also talk about setting the map to null, and explicitly removing event listeners. You expressed concerns about the event listeners, I thought just setting the map to null would suffice, but it looks like your concerns are valid, because they mention event listeners specifically. They also recommended completely removing the DIV that holds the map as well.
At any rate, just wanted to pass this along and make sure it is included in the stackoverflow discussion and hope it helps you and others-

The official answer is you don't. Map instances in a single page application should be reused and not destroyed then recreated.
For some single page applications, this may mean re-architecting the solution such that once a map is created it may be hidden or disconnected from the DOM, but it is never destroyed/recreated.

Since apparently you cannot really destroy map instances, a way to reduce this problem if
you need to show several maps at once on a website
the number of maps may change with user interaction
the maps need to be hidden and re-shown together with other components (ie they do not appear in a fixed position in the DOM)
is keeping a pool of map instances.
The pool keeps tracks of instances being used, and when it is requested a new instance, it checks if any of the available map instances is free: if it is, it will return an existing one, if it is not, it will create a new map instance and return it, adding it to the pool. This way you will only have a maximum number of instances equal to the maximum number of maps you ever show simultaneously on screen.
I'm using this code (it requires jQuery):
var mapInstancesPool = {
pool: [],
used: 0,
getInstance: function(options){
if(mapInstancesPool.used >= mapInstancesPool.pool.length){
mapInstancesPool.used++;
mapInstancesPool.pool.push (mapInstancesPool.createNewInstance(options));
} else {
mapInstancesPool.used++;
}
return mapInstancesPool.pool[mapInstancesPool.used-1];
},
reset: function(){
mapInstancesPool.used = 0;
},
createNewInstance: function(options){
var div = $("<div></div>").addClass("myDivClassHereForStyling");
var map = new google.maps.Map(div[0], options);
return {
map: map,
div: div
}
}
}
You pass it the starting map options (as per the second argument of google.maps.Map's constructor), and it returns both the map instance (on which you can call functions pertaining to google.maps.Map), and the container , which you can style using the class "myDivClassHereForStyling", and you can dinamically append to the DOM.
If you need to reset the system, you can use mapInstancesPool.reset(). It will reset the counter to 0, while keeping all existing instances in the pool for reuse.
In my application I needed to remove all maps at once and create a new set of maps, so there's no function to recycle a specific map instance: your mileage may vary.
To remove the maps from the screen, I use jQuery's detach, which doesn't destroy the map's container .
By using this system, and using
google.maps.event.clearInstanceListeners(window);
google.maps.event.clearInstanceListeners(document);
and running
google.maps.event.clearInstanceListeners(divReference[0]);
divReference.detach()
(where divReference is the div's jQuery object returned from the Instance Pool)
on every div I'm removing, I managed to keep Chrome's memory usage more or less stable, as opposed to it increasing every time I delete maps and add new ones.

I would have suggested removing the content of the map div and using delete on the variable holding the reference to the map, and probably explicitly deleteing any event listeners.
There is an acknowledged bug, though, and this may not work.

As google doesnt provide gunload() for api v3 better use iframe in html and assign map.html as a source to this iframe. after use make src as null. That will definitely free the memory consumed by map.

When you remove the div, that removes the display panel and the map will disappear. To remove the map instance, just make sure that your reference to the map is set to null and that any references to other parts of the map are set to null. At that point, JavaScript garbage collection will take care of cleaning up, as described in: How does garbage collection work in JavaScript?.

I guess you're talking about addEventListener. When you remove the DOM elements, some browsers leak these events and doesn't remove them. This is why jQuery does several things when removing an element:
It removes the events when it can using removeEventListener. That means it's keeping an array with the event listeners it added on this element.
It deletes the attributes about events (onclick, onblur, etc) using delete on the DOM element when addEventListener is not available (still, it has an array where it stores the events added).
It sets the element to null to avoid IE 6/7/8 memory leaks.
It then removes the element.

Related

Paginating firestore data when using vuex and appending new data to the state

I implemented the following to display a paginated query (this was suggested by Tony O'Hagan in this post: How to get the last document from a VueFire query):
bindUsers: firestoreAction(({ bindFirestoreRef }) => {
return bindFirestoreRef('users',
Firebase.firestore().collection('users').limit(8), { serialize })
}),
bindMoreUsers: firestoreAction(context => {
return context.bindFirestoreRef('users', Firebase.firestore().collection('users').startAfter(context.state.users[context.state.users.length - 1]._doc).limit(8), { serialize })
})
When the user scrolls to the end of the page, I call bindMoreUsers which updates the state.users to the next set of 8 documents. I need to be able to append to the state.users as opposed to overwrite the original set of 8 documents. How can I do this?
Confession: I've not yet implemented pagination on my current app but here's how I'd approach it.
In my previous answer I explained how to keep references to the Firestore doc objects inside each element of the state array that is bound by VuexFire or VueFire. In Solution #1 below we use these doc objects to implement Firestore's recommended cursor based pagination of a query result sets using startAfter(doc) query condition instead of the slower more expensive offset clause.
Keep in mind that since we're using Vuexfire/Vuefire we're saying that we wish to subscribe to live changes to our query so our bound query will define precisely what ends up in our bound array.
Solution #1. Paging forward/backward loads and displays a horizontal slice of the full dataset (our bound array maintains the same size = page size). This is not what you requested but might be a preferred solution given the Cons of other solutions.
Pros: Server: For large datasets, this pagination query will execute with least cost and delay.
Pros: Client: Maintains a small in memory footprint and will render fastest.
Cons: Pagination will likely not feel like scrolling. UI will likely just have buttons to go fwd/backward.
Page Forward: Get the doc object from the last element of our state array and apply a startAfter(doc) condition to our updated view query that binds our array to the next page.
Page Backward: Bit Harder! Get the doc object from the first element of our bound state array. Run our page query with startAfter(doc), limit (1), offset(pagesize-1) and reverse sort order. The result is the starting doc (pageDoc) of the previous page. Now use startAfter(pageDoc) and forward sort order and limit(pageSize) to rebind the state array (same query as Page Forward but with doc = pageDoc).
NOTE: In the general case, I'd argue that we can't just keep the pageDoc values from previous pages (to avoid our reverse query) since we're treating this as a 'live' update filtered list so the number of items still remaining from previous pages could have radically changed since we scrolled down. Your specific application might not expect this rate of change so perhaps keeping past pageDoc values would be smarter.
Solution #2. Paging forward, extends the size of the query result and bound array.
Pros: UX feels like normal scrolling since our array grows.
Pros: Don't need to use serializer trick since we're not using startAfter() or endBefore()
Cons: Server: You're reloading from Firestore the entire array up to the new page every time you rebind to a new page and then getting live updates for growing array. All those doc reads could get pricey!
Cons: Client: Rendering may get slower as you page forward - though shadow DOM may fix this. UI might flicker as you reload each time so more UI magic tricks needed (delay rendering until array is fully updated).
Pros: Might work well if we're using an infinite scrolling feature. I'd have to test it.
Page Forward: Add pageSize to our query limit and rebind - which will re-query Firestore and reload everything.
Page Backward: Subtract pageSize from our query limit and rebind/reload (or not!). May also need to update our scroll position.
Solution #3. Hybrid of Solution #1 and #2. We could elect to use live Vuexfire/Vuefire binding for just a slice of our query/collection (like solution #1) and use a computed function to concat it with an array containing the pages of data we've already loaded.
Pros: Reduces the Firestore query cost and query delay but now with a smooth scrolling look and feel so can use Infinite scrolling UI. Hand me a Koolaid!
Cons: We'll have to try to keep track of which part of our array is displayed and make that part bound and so live updated.
Page Forward/Backward: Same deal as Solution #1 for binding the current page of data, except we now have to copy the previous page of data into our non-live array of data and code a small computed function to concat() the two arrays and then bind the UI list to this computed array.
Solution #3a We can cheat and not actually keep the invisible earlier pages of data. Instead we just replace each page with a div (or similar) of the same height ;) so our scrolling looks we've scrolled down the same distance. As we scroll back we'll need to remove our sneaky previous page div and replace it with the newly bound data. If you're using infinite scrolling, to make the scrolling UX nice and smooth you will need to preload an additional page ahead or behind so it's already loaded well before you scroll to the page break. Some infinite scroll APIs don't support this.
Solution #1 & #3 probably needs a Cookbook PR to VueFire or a nice MIT'd / NPM library. Any takers?

What is the difference between Container and DisplayObjectContainer?

I am using a JavaScript library called PIXI and am looking for a way to "zoom" in my game. A search on the internet suggested that I put everything inside a DisplayObjectContainerand then resize it to simulate a zoom-effect.
The thing is that I already have Container (aka stage), and I think that is resizable too. So I don't understand the reason behind using a DisplayObjectContainerwhen you have a Containeralready. And frankly, I don't even see the difference between them.
This page says the following about DisplayObjectContainer:
A DisplayObjectContainer represents a collection of display objects. It is the base class of all display objects that act as a container for other objects.
This other page says the following about Container:
A Container represents a collection of display objects. It is the base class of all display objects that act as a container for other objects.
The only possible scenario I can imagine is that one of these container-objects is outdated and belongs to an older version of PIXI, which isn't too unrealistic since PIXI is rather new and could change a lot. But this is just a guess.
The guess is correct. DisplayObjectContainer is outdated and replaced with Container.

Google Maps API and DynamicMapsEngineLayer: Loop Over Features in the Map

[Edit APRIL 2, 2014]
I want to emphasize something. My scenario does not have a user event driving it to trigger an event listener. I know the Maps API documentation shows how to get a feature's featureId property from a MapsEngineMouseEvent. But my scenario needs to get the feature's featureId programmatically.
I've also tried programmatically "faking" a click, but that doesn't work either. (It seems the event object can't be instantiated??)
My hunch is, the solution to this either 1) doesn't exist, 2) is deceptively simple, or 3) will only be discovered if a minimum level 8 mage rolls a natural 20 wisdom check..
[Original Problem Statement]
I have few scenarios in a customized Google Maps client where I need to loop over features in a DynamicMapsEngineLayer and modify their style traits. The DynamicMapsEngineLayer works by performing..
..client-side rendering of vector data, allowing the developer to
dynamically restyle the vector layer in response to user interactions
like hover and click.
The Maps API documentation describes how to restyle individual features using event listeners, which expose a special featureId value assigned by Google servers. But my scenario doesn't have user-driven events. For example, consider this hypothetical link:
http://www.acme-map.com/index.php?ZoomToAndHighlightFeatureWithId=12345
FeatureWithId is our own unique id, not Google's special
featureId, which we don't have at this point in the runtime.
I need the map to load right above a feature and highlight it by changing its style trait. It needs to do this programmatically when the map first loads, without any user interaction. If these vector features are truly rendered in the DOM, then surely there's a way, no matter how cryptic, to reach into the map's guts and access these objects?
Is there a way to loop over individual features in a DynamicMapsEngineLayer or get the featureId property without an event listener?
I may be missing something here, but if you already know the feature ID, you can restyle it directly without an event. Just call getFeatureStyle() directly and set the style as you wish:
var style = dynamicLayer.getFeatureStyle('1234');
style.strokeColor = '#FF0000';
style.iconImage = 'url(images/myIcon.png)';
And if you don't know the feature ID, but you do have some other attribute to query against, you can make a call out to the Maps Engine API to fetch it.

JavaScript/jQuery - performance issue

I have some kind of webapp here, which of course incorporates JavaScript and jQuery.
On load, a functions wraps a span around every letter of a text - about 150 letters. Then the user can select the letters and after a confirmation, a result is displayed. Everything works nice and smooth, only the last part really kills the performance.
The results are saved in three arrays. After on click the functions fires which adds classes to the clicked elements (this is the confirmation).
I do it like this (3 times, for each array):
$.each(myArr, function( i, v ){
$(v).addClass( "my-class" );
});
It works this way, but because I manipulate the DOM heavily it kills the performance.
I am on a MacBook with 2.26 GHz and 2 GB RAM and I am not able to run a simple tooltip or anything after the classes has been added. Especially if one array is really full this has a negative performance impact.
I already tried to optimize the script. I cached every DOM object that is used more then once, but this is not possible in every case (I think...). I also used selectors like .my-class and #my-id instead of span[class = my-class] and span[id = my-id] to speed up everything. Only the last array part is bad.
Is there any way to optimize this $.each part? Caching or somehting? Or maybe using another technique?
I don't expect the script to be SUPER fast - but adding a simple tooltip after the results are shown should be possible.
What seems to be the problem is that you cause a lot of browser reflows. This happens every time you make certain changes to an object in the DOM (like changing its size, removing/appending it etc). To prevent this from happening you can:
Remove all objects at once by removing the parent object containing all objects (this causes one reflow).
Make all the neccessary changes while the objects aren't part of the DOM.
Put the parent object back in the DOM (which once again causes a reflow).
If removing the parent object causes visual distractions you can instead deep clone it (var clone = parent.cloneNode(true)), make all the changes to the clone and then replace the parent object (parent.parentNode.replaceChild(clone, parent)). Be aware if the objects have any javascript event listeners you need to rebind these to the cloned objects.
Why do not concat the arraies:
myArr = myArr[0].concat(myArr[1],myArr[2]);
$(myArr).addClass( "my-class" );
I think it can be faster.

Making Google Maps driving directions behave more like actual Google Maps

I've been doing driving directions in my map app using the directionsRenderer, so it renders both the path (on the map) and the html list of directions. My app works basically like this example: http://code.google.com/apis/maps/documentation/javascript/examples/directions-draggable.html
However, now I've been asked to make it a little more like the directions on Google Maps proper, for instance here
My client would like the little popups when you hover over the html items, as well as the little icons showing right turn, bear left, merge, etc.
I've managed to render my own html from the DirectionsService response, and hook up events for hovering and associate them with points on the map, but where I could use help is:
Getting the turn by turn icons. I imagine this isn't easy because I get each step as html text ("Take exit 433 on the left to merge onto I-80 E toward Bay Bridge/Oakland"), and I imagine that could be challenging to parse reasonably to determine which icon to show
Making the little mini-popups over the map. Although I can make the popups themselves, it's probably challenging or impossible to do it the exact same way because I don't have a short version of the instructions.
In any case, I thought I'd check if anyone knows a way to do this sort of thing -- not necessarily exactly, but just closer to it -- or if I'm just out of luck because google hasn't made any of that sort of functionality available via their api.
You are correct that you will have to examine strings to get turn icons. You can parse the DirectionsResult object yourself (it is "JSON-like" according to Google's documentation) rather than using the DirectionsRenderer if you wish, but I don't think it will get you anything much. Here's how it would go:
The DirectionsResult.route property will be an array of DirectionsRoute objects. If you didn't set provideRouteAlternatives to true, then there will only be one DirectionsRoute object in the array.
The DirectionsRoute object, in turn, has a property called legs. That property is an array of DirectionsLeg objects. If you have specified no waypoints (i.e., an intermediary destination), just a start and end point, then this array will also only have one object in it.
The DirectionsLeg object, in turn, has a property called steps. It will be an array where each element will be a DirectionsStep object.
The DirectionsStep object has a property called instructions. That is a string and is what you will have to examine using a regexp or whatever to figure out what turn icon to use. (It's possible that this may be easier to work with than the HTML you mention that I imagine is coming from the DirectionsRenderer. Or it may be possible that it isn't any easier whatsoever. I'm not sure. I've never actually done it.)

Categories

Resources