Javascript namespaces and conditional inclusion - javascript

I've some js files organized this way (see source):
gmaps4rails.base.js : contains all the logic
gmaps4rails.googlemaps.js : contains functions
gmaps4rails.bing.js : contains functions with the same name as the previous file
So basically, base calls createMarkers() which is in both googlemaps and bing.
From now, I load only one among gmaps4rails.googlemaps.js and gmaps4rails.googlemaps.js, depending on the map API I need, so it works fine.
Now I'd like to be able to have all files loaded (and keep them separate) BUT of course only include the code of the desired maps API.
Basically I think about something like:
if desiredApi == "googlemaps"
include GoogleMapsNameSpace content in BaseNameSpace
Thanks in advance.

If I understand correctly you just want to be able to use either the google maps API or the bing API or someother based on the user selection (or some other criteria)? For that, instead of trying to merge the correct API into your base object, why don't you just refer a 'mapService' from your base object and select the mapService based on the criteria you want?
Something like this:
Map = function(service, canvasId) {
var mapService = MapServices[service];
return {
initialize: function() {
var theMap = mapService.createMap();
mapService.render(canvasId);
}
// more functionality for your base object (that the outside world calls) goes here
}
}
var MapServices = MapServices || {};
MapServices.google = {
createMap: function() {},
render: function(canvas) {
$("#"+canvas).html("I'm a google map");
},
createMarker: function(args) {}
// all your map functionality goes here calling google specific functions
};
// this could be in a different js
MapServices.bing = {
createMap: function() {},
render: function(canvas) {
$("#"+canvas).html("I'm a bing map");
},
createMarker: function(args) {},
// all your map functionality goes here calling bing specific functions
}
$(function() {
var theMap = new Map("google", "theDiv");
theMap.initialize();
});
Is this what you want to do?
Hope this helps

I think you are looking for some kind of asynchronous loader. CommonJS has proposed Modules 1.1 and the Asynchronous Module Definition (AMD) API that do this. There are quite a few implementations of async loaders, as listed in this Google spreadsheet. Of which, a few of them are AMD-compliant, for example:
curl.js http://github.com/unscriptable/curl
RequireJS http://requirejs.org/
bdLoad http://bdframework.org/bdLoad
This blog entry has a nice discussion on this topic.
Also see this SO post: Namespace a dynamically loaded javascript file's contents

Related

One view for 2 or more pages (backbone.js)

I have two pages. One of them is dashboard with a lot of functionality. The second page is shared dashboard - the simple version of the first page.
The dashboard contains the view of the database (it can contain much other info, but the problem with this one). You can click on the filter button and modal window will be opened. So, simple version of the dashboard doesn't have this possibility. I'd like to add it, but I don't want to copy+past code from the full version of the dashboard because the code of this part is about two thousand lines. I'll add some primitive code example:
DashboardView = SomeAnotherView.extend({
initialize: function() {...},
events: {...} // huge objects of jQuery events,
render: function () {...},
... // 2k lines of functions for events
});
How can I use this View on another page? I tried to call a function from this view:
DashboardView.prototype.filterClicked(event);
But in this case event.curentTarget is null (it is necessary for this function), I also tried to send "this" to get the context, but it was failed.
Is there a possibility in Backbone.js to use one View for 2+ pages without any huge copy/past code?
Ideally if you have a simple version and full version of a view, you should have a "base view" (simple one) and the full version should extend the base view.
It'll look something like:
var SimpleDashbard = Backbone.view.extend({});
var Dashboard = SimpleDashbard.extend({});
In this way Dashboard will have access to the methods from SimpleDashbard.
Your situation sounds like you need to use a method from extended view in base view. Which is not a good idea. Ideally if it's shared you should move it to the base view/extract it into a utility method or service, and of course this involve re-writing this method to be reusable
If you have views that share a large amount of functionality, you could consider using the same View type, but opening it up to some configuration when instancing. For example:
var DashboardView = Backbone.View.extend({
initialize: function(options) {
this.allowFunctionX = (options && options.allowFunctionX);
this.allowFunctionY = (options && options.allowFunctionY);
},
// etc
functionX: function() {
if (!this.allowFunctionX) { return; }
// do the function...
},
functionY: function() {
if (!this.allowFunctionY) { return; }
// do the function...
},
});
Then on one page:
var firstDashView = new DashboardView({allowFunctionX: true});
and on another page:
var secondDashView = new DashboardView({allowFunctionY: true});
This may become not worth it if the functionality diverges too much (and there are likely better ways to configure than passing in a long list of booleans!). If your requirements are significantly different on your two pages, I feel like duplicating the code they both need is not a major sin.

Get JSON WebMap from ArcGIS JavaScript API Map object

I'm trying to get a WebMap object (as JSON) from a JavaScript Map object in the ArcGIS JavaScript API. Is there any way to do this within the API, without using ArcGIS.com? Ideally something like:
webMapAsJSON = map.toWebMap();
From the "Export Web Map Task" documentation in the REST API, there's this line that suggests it should exist:
"The ArcGIS web APIs (for JavaScript, Flex, Silverlight, etc.) allow developers to easily get this JSON string from the map."
However, I don't see anything in the Map object or elsewhere in the API that would do this.
You can't. At least not officially. The steps outlined below are not recommended. They use part of the ArcGIS JS library that is not part of the public API and therefore this behavior may not work in the next version of the API or they may back-patch a previous version of the API and this could stop working even on something that previously did work.
That said, sometimes you need some "future" functionality right now and this is actually a pretty straightforward way of getting what you want using the common proxy pattern
Use the undocumented "private" function _getPrintDefinition
var proxy_getPrintDefinition = printTask._getPrintDefinition;
printTask._getPrintDefinition = function() {
var getPrintDefResult = proxy_getPrintDefinition.apply(this, arguments);
//Now you can do what you want with getPrintDefResults
//which should contain the Web_Map_as_JSON
console.log(Json.stringify(getPrintDefResult));
//make sure you return the result or you'll break this print task.
return getPrintDefResult;
}
_getPrintDefinition takes the map as the first argument and a PrintParameters object as the second.
so you'll have to create a PrintTask, redefine the _getPrintDefinition function on the newly created print task as outlined above, create a PrintParameters and then run:
myPrintTask._getPrintDefinition(myMap,myPrintParameters);
The results of this on my little test are:
{"mapOptions":{"showAttribution":false,"extent":{"xmin":-7967955.990468411,"ymin":5162705.099750506,"xmax":-7931266.216891576,"ymax":5184470.54355468,
"spatialReference":{"wkid":102100,"latestWkid":3857}},"spatialReference":{"wkid":102100,"latestWkid":3857}},
"operationalLayers":[
{"id":"layer0","title":"layer0","opacity":1,"minScale":591657527.591555,"maxScale":70.5310735,"url":"http://services.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer"},
{"id":"XXX-Redacted-XXX","title":"serviceTitle","opacity":1,"minScale":0,"maxScale":0,"token":"XXX-Redacted-XXX","url":"http://XXX-Redacted-XXX/arcgis/rest/services/TestService/MapServer"},
{"id":"XXX-Redacted-XXX","opacity":1,"minScale":0,"maxScale":0,"featureCollection":{"layers":[]}},
{"id":"featureGraphics","opacity":1,"minScale":0,"maxScale":0,"featureCollection":{"layers":[]}},
{"id":"map_graphics","opacity":1,"minScale":0,"maxScale":0,"featureCollection":{"layers":[]}}
]}
if you don't need to do any operations on the web map json and just need the output then you don't even need to use the proxy pattern.
#Suttikeat Witchayakul's answer above should work if your goal is to print the map using a print service.
However, if you are trying to export the map to the web map JSON spec so that you can save it to ArcGIS Online/Portal, or re-instantiate a map object from it later, you may have some problems. This is because the web map specification is not the same as the export web map specification, which what the print task generates and sends to printing services.
Unfortunately, the ArcGIS API for JavaScript does not provide any methods to export a map object to web map JSON. This is supposed to be coming in version 4... at some point. Until then, you can use the all but abandoned cereal library. However, if your map uses layer types that are not fully supported by cereal, it may not work for you as is and you would have to extend it.
If you want to use "esri/tasks/PrintTask" to export your map, you must use "esri/tasks/PrintParameters" for execute the printTask. Just set your map object directly to printParameter.
require([
"esri/map", "esri/tasks/PrintTemplate", "esri/tasks/PrintParameters", ...
], function(Map, PrintTemplate, PrintParameters, ... ) {
var map = new Map( ... );
var template = new PrintTemplate();
template.exportOptions = {
width: 500,
height: 400,
dpi: 96
};
template.format = "PDF";
template.layout = "MAP_ONLY";
template.preserveScale = false;
var params = new PrintParameters();
params.map = map;
params.template = template;
printTask.execute(params, printResult);
});

the best pattern to keep references to global collections in Backbone

I'm developing an application in Marionette, but the topic refers to raw Backbone as well.
In my app, I've got many collections. Among them, there are 3 important ones, that are used all over the application (e.g. user data, such as names, which is displayed in most of views). The main question is: what is the best way (a good pattern to follow) to keep references to collections in Backbone/Marionette? I came up with few ideas below:
should I attach them as attributes of the Application object? If so, I'd have to pass the reference to Application object to all views, then views to their subviews, subviews to subsubviews, etc. But this seems a lame and ugly design.
instead, I could pass each collection separately, but this seems even worse solution, because I can't predict which (sub)view will ever need a collection. Keeping those nested references in order would be much more difficult than passing the Application object which I can pass once, always.
the is also a possibility to import the Application as a singleton. I'm using requireJS, most of the modules return constructors now (constructors of views, models and collections). Now the app module returns Application constructor, but instead, it could return the Application object. Then if a view requires to display some data from the collections, it could just require the app module and that's all.
finally, basing on the previous point, I thought I could create a plain map of collections and make it a singleton just as before. This is only to disable all views to have access to Application object, which still seems a bad pattern.
Please, suggest what you think is the best (commentson points above are welcome as well). I just need a good design pattern here. As far as I know, Marionette docs doesn't suggest anything here.
I follow the suggestions made in David Sulc's book Backbone.Marionette.js: A Gentle Introduction. (also the next book on goes into how to then sturcutre the same project with require https://leanpub.com/structuring-backbone-with-requirejs-and-marionette)The code examples he uses are available on github so you could look at the final example which to get an idea of what he does if you didn;t want to buy the book but i really recommend it as it really helped me with how to structure my projects.
To start I have setup an Entities module. The structure of the files also follows this I have an Entities folder which has separate entities.
Each Entity file concerns all actions and stucture of that particular entity. I like this approach as I when i want to edit an entities strcuture or method of getting data from the server i only need to go to one place to make this change.
Interactions with entity are handled through marionettes req/res system. In this way you can expose a handler to the rest of your app but they don;t need to be concerned with how that req gets handled as long as it brings back the required response.
Here is an example of one of my entities to show what i mean - My app needs a collection called positions at various stages so this is something that is loaded early in the app then is available through-out it's life-cycle.
//i am using require so i define my app to use
define(["app", ], function(MyApp) {
//All of this will be added to the Entities module so if i want direct access
//i can go Myapp.Entities.whateverEntityIhaveDeclared
MyApp.module("Entities", function(Entities, MyApp, Backbone, Marionette, $, _) {
//model (nothing new here)
Entities.Position = Backbone.Model.extend({
urlRoot: "api/positions",
defaults: {
name: "",
}
});
//collection again nothing new here
Entities.PositionCollection = Backbone.Collection.extend({
url: "api/positions",
model: Entities.Position,
comparator: "name"
});
//an init function to attach a position collection onto Entities module so it can be available throughout my app
var initializePositions = function() {
if (Entities.positions === undefined) {
Entities.positions = new Entities.PositionCollection();
}
};
//
var API = {
//returns a jquery deferred promise so that this request can easily be handled async
loadPositionEntitiesRemote: function() {
//init positions make's sure i have the collectoin avaliable if it
//has not yet been defined
initializePositions();
//setup defer object
var defer = $.Deferred();
//I actually use a custom sever object here when dealing
//with ajax requests
//but because my app always go through this API i can
//easily swap out how the collection is retrieved.
// Here is an example using backbones fetch
Entities.positions.fetch({
success: function() {
defer.resolve();
},
error: function(data) {
defer.reject(data);
}
});
//setup promise to return
var promise = defer.promise();
return promise;
},
//get the positions collection from here i could
//directly access the attributes or add to the collection
refrencePositionEntities: function() {
initializePositions();
return Entities.positions;
},
//get a position from the collection based on id
//
getPositionEntity: function(positionId) {
initializePositions();
return Entities.positions.get(positionId);
}
};
//setup handlers for the app to use
MyApp.reqres.setHandler("position:entities:remote", function() {
return API.loadPositionEntitiesRemote();
});
MyApp.reqres.setHandler("position:entities:refrence", function() {
return API.refrencePositionEntities();
});
MyApp.reqres.setHandler("position:entity", function(id) {
return API.getPositionEntity(id);
});
MyApp.reqres.setHandler("position:entity:new", function(position) {
return new Entities.Position(position);
});
});
return;
});
now to use this in my app here is an example of how it can now be used
someFunction: function(){
//so early in the app i will get the positions
var positionPromise = MyApp.request("position:entities:remote");
$.when(positionPromise).done(function() {
//do what ever as data has been loaded
}).fail(function(data){
//something failed so handle here might through up an error page but up to you
}).always(function(){
//something to always do no matter if fail or sucess
});
}
anotherFunction: function(){
//later in the app in another controller i might to get the collection
// I could also get it through MyApp.Entities.positions but i rather use the
// API set up so if i ever decided i want to add so checks or something in
// when retrieving the collection its super easy
var positionsCollection = MyApp.request("position:entities:refrence");
}
Not sure if i've done a great job explaining this but if you are looking for a ideas on good design in marionette check out the book as it explains this a lot better than i have just done

AngularJS Google Map directive map instance

I'm using http://angular-google-maps.org/ it's nice angular google maps library. But i want use map instance which is loaded not in angularjs context by something like this:
$scope.map = {
events: {
tilesloaded: function (map) {
$scope.$apply(function () {
$scope.mapInstance = map;
});
}
}
}
Ok nice i have mapInstance and I CAN use it programmatically. But in application lifecycle this fire to late- so in other words I want to load whole directive (and get map instance) before other code- where I just wan't to use other map events.
In recently looking up ways to get the map instance from the example on the docs page, I came across this instead:
$scope.map.control.getGMap().
Make sure on your google-maps HTML markup, you have the options attribute set as control="map.control" and an empty object set in your $scope.map object.
$scope.map= { control : {}, ...other map options...};
That empty objects is filled when google map is initiated. I hope this helps and isn't too late.
Enjoy Angular!!!

Auto-load/include for JavaScript

I have file called common.js and it's included in each page of my site using <script />.
It will grow fast as my sites functionality will grow (I hope; I imagine). :)
Lets example I have a jQuery event:
$('#that').click(function() {
one_of_many_functions($(this));
}
For the moment, I have that one_of_many_functions() in common.js.
Is it somehow possible that JavaScript automatically loads file one_of_many_functions.js when such function is called, but it doesn't exist? Like auto-loader. :)
The second option I see is to do something like:
$('#that').click(function() {
include('one_of_many_functions');
one_of_many_functions($(this));
}
That not so automatically, but still - includes wanted file.
Is any of this possible? Thanks in an advice! :)
It is not possible to directly auto-load external javascripts on demand. It is, however, possible to implement a dynamic inclusion mechanism similar to the second route you mentioned.
There are some challenges though. When you "include" a new external script, you aren't going to be able to immediately use the included functionality, you'll have to wait until the script loads. This means that you'll have to fragment your code somewhat, which means that you'll have to make some decisions about what should just be included in the core vs. what can be included on demand.
You'll need to set up a central object that keeps track of which assets are already loaded. Here's a quick mockup of that:
var assets = {
assets: {},
include: function (asset_name, callback) {
if (typeof callback != 'function')
callback = function () { return false; };
if (typeof this.assets[asset_name] != 'undefined' )
return callback();
var html_doc = document.getElementsByTagName('head')[0];
var st = document.createElement('script');
st.setAttribute('language', 'javascript');
st.setAttribute('type', 'text/javascript');
st.setAttribute('src', asset_name);
st.onload = function () { assets._script_loaded(asset_name, callback); };
html_doc.appendChild(st);
},
_script_loaded: function (asset_name, callback) {
this.assets[asset_name] = true;
callback();
}
};
assets.inlude('myfile.js', function () {
/* do stuff that depends on myfile.js */
});
Sure it's possible -- but this can become painful to manage. In order to implement something like this, you're going to have to maintain an index of functions and their corresponding source file. As your project grows, this can be troublesome for a few reasons -- the 2 that stick out in my mind are:
A) You have the added responsibility of maintaining your index object/lookup mechanism so that your scripts know where to look when the function you're calling cannot be found.
B) This is one more thing that can go wrong when debugging your growing project.
I'm sure that someone else will mention this by the time I'm finished writing this, but your time would probably be better spent figuring out how to combine all of your code into a single .js file. The benefits to doing so are well-documented.
I have created something close to that a year ago. In fact, I have found this thread by search if that is something new on the field. You can see what I have created here: https://github.com/thiagomata/CanvasBox/blob/master/src/main/New.js
My project are, almost 100% OOP. So, I used this fact to focus my solution. I create this "Class" with the name "New" what is used to, first load and after instance the objects.
Here a example of someone using it:
var objSquare = New.Square(); // Square is loaded and after that instance is created
objSquare.x = objBox.width / 2;
objSquare.y = objBox.height / 2;
var objSomeExample = New.Stuff("some parameters can be sent too");
In this version I am not using some json with all js file position. The mapping is hardcore as you can see here:
New.prototype.arrMap = {
CanvasBox: "" + window.MAIN_PATH + "CanvasBox",
CanvasBoxBehavior: "" + window.MAIN_PATH + "CanvasBoxBehavior",
CanvasBoxButton: "" + window.MAIN_PATH + "CanvasBoxButton",
// (...)
};
But make this more automatic, using gulp or grunt is something what I am thinking to do, and it is not that hard.
This solution was created to be used into the project. So, the code may need some changes to be able to be used into any project. But may be a start.
Hope this helps.
As I said before, this still is a working progress. But I have created a more independent module what use gulp to keep it updated.
All the magic que be found in this links:
https://github.com/thiagomata/CanvasBox/blob/master/src/coffee/main/Instance.coffee
https://github.com/thiagomata/CanvasBox/blob/master/src/node/scripts.js
https://github.com/thiagomata/CanvasBox/blob/master/gulpfile.js
A special look should be in this lines of the Instance.coffee
###
# Create an instance of the object passing the argument
###
instaceObject = (->
ClassElement = (args) ->
window[args["0"]].apply this, args["1"]
->
ClassElement:: = (window[arguments["0"]])::
objElement = new ClassElement(arguments)
return objElement
)()
This lines allows me to initialize a instance of some object after load its file. As is used in the create method:
create:()->
#load()
return instaceObject(#packageName, arguments)

Categories

Resources