Closed ArthurianX closed 9 years ago
Just realized, if we do this, the fixes from task #220 aren't necessarry at all :)
This is nice, but it lacks some features like:
If you're willing to work this out, just fork this repo and issue a PR. We will consider this.
Ok, I'll try my hand at this.
Never made a pull request, I don't know exactly how it works.
I made a fork yesterday, a pull request means that my fork is somehow like a new branch on my account, a branch that can be integrated back in your repo?
On 23 January 2014 14:47, Nicolas Laplante notifications@github.com wrote:
This is nice, but it lacks some features like:
- ability to bypass completely if the user prefers to load the Google Maps API manually
- configuration for sensor and api key
- use $window instead of window
- it introduces a global function
If you're willing to work this out, just fork this repo and issue a PR. We will consider this.
— Reply to this email directly or view it on GitHubhttps://github.com/nlaplante/angular-google-maps/issues/221#issuecomment-33120775 .
Arthur Kovacs - FrontEnd Development linkedin.com/in/arthurianx
FrontEnd Developer PITECH+PLUS Coratim Business Center Str. Câmpul Pâinii nr.3-5, et.5 Cluj Napoca ROMANIA
Web: www.pitechnologies.ro Mobile : +4(0721).55.66.00
Yahoo: m3s_4ev3r@yahoo.com Skype: arthur.kovacs85
If window.google.maps is already existing, the above factory will not load it again and simply resolve the promise. The global function is the only way to know that google.maps was fully loaded.
I started to work on this,
At the end you might have to do some coffee conversion for the sake of consistency, I'll try myself but not sure if I'm prepared to start and learn coffee too, even if it seems like a breeze.
So, what I was thinking about:
We need two more parameters in the directive
async="true"
and
apisettings ="{OBJECT}"
which contains:
key
language
version
sensor
If there is no async in the scope, we would guess that the user loads its own js files.
If there is async, and the apisettings object is not present, we're putting automatically sensor to false
and version to 3.14
.
If the apisettings object exists, [INSIDE THE FACTORY SERVICE] I'm checking it's values and placing the passed value, if not the default value, and for the optional parameters (language and key) I'm placing nothing if they are not present.
Now, this whole thing is the factory service with parameters, WHICH I'm thinking of calling inside your link
function from the main directive -> If !async, run normally, if async==true go through the factory service?
Sound thinking? or have I missed something ?
I'm thinking this way because I want not to get in the way of the many things that are happening after this step, so this factory service is placed like a bottleneck before everything runs.
I'm going with the assumption that I have the bottleneck in the right spot :)
Cool idea, but just putting in url="whateverToLoad" may be easier. Even though we are pretty tied to google maps. It would be nice if the api is slightly abstracted so that other map engines could be loaded. Like http://www.openstreetmap.org/ .
BTW Link is called once per directive. It is a function that a directive is expected to call when it is loaded. This is why element,ctrl, and scope are passed into it. Scope is specific to that directive as well as the html element for it.
The in the API there are parentModels which have the same scope of the directive but create children each with their own scope derived from the parent.
Keep in mind we need to load thinks like weather and other libraries.
https://github.com/nlaplante/angular-google-maps/blob/master/example/example.html#L298
Why not create a $googleMapsProvider which we could use to set the api options in the config phase of the application instead? This way we would have no glue between the directive and how we load the api.
I thought it like this so we can inject the configurable tags directly in the directive, nothing outside it, to keep it easier for the user.
To be honest what you say is cleaner, but I have no idea how to pass those configuration items from the directive in the config phase ? (which runs before?) ... A lot of your code is still unclear to me, I have to go through it.
angular.module() .config($someProvider)
ahhh.. that config :dance:
I though it was something specific to your library.
I'm already calling parameters inside, but with config I think it;s better.
IT's not done yet, I haven't completed the querying for the parameter's individual objects:
angular.module('googleMapsFactory', [])
.factory('googleMapsFactory', function ($q, $window) {
return {
fn: function(apis) {
var deferred = $q.defer();
if(typeof $window.google !== 'undefined' && typeof $window.google.maps !== 'undefined') {
console.log('yes, google is undefined, creating promise');
// Early-resolve the promise for googleMaps
deferred.resolve($window.google.maps);
return deferred.promise;
}
var randomizedFunctionName = 'onGoogleMapsReady' + Math.round(Math.random()*1000);
window[randomizedFunctionName] = function() {
window[randomizedFunctionName] = null;
// Resolve the promise for googleMaps
deferred.resolve(window.google.maps);
};
//Factory parameters, we're concatenating values here for optional parameters and checking presence
if (apis === undefined) {
//var apis = {sensor: false; version: "3.14"};
return deferred.promise;
} else if (apis.length < 4){
if (apis.key === undefined) {
apis.key = "";
}
if (apis.sensor === undefined) {
apis.sensor = false;
}
} else {
apis.key = "key=" + apis.key;
apis.language = "&language=" + apis.language;
}
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = 'https://maps.googleapis.com/maps/api/js?' + apis.key + 'v=' + apis.version + '&sensor=' + apis.sensor + apis.language + '&callback='+randomizedFunctionName;
console.log(script.src);
document.body.appendChild(script);
// Return a promise for googleMaps
return deferred.promise;
}
};
})
;
Here one of the best examples I have ever seen worth more than words.
http://jsbin.com/ohamub/611/edit
angular.module("google-maps")
.provider "$googleMapsProvider", ["$log", ($log) -> ()->
$get: () ->
map: #some map that we know is ready
onReady:(map) ->
]
#rest of directives can do the same as below
.directive "googleMap", ["$log", "$timeout","$googleMapsProvider", ($log, $timeout, $googleMapsProvider) ->
### Existing code base ]
One thing to consider on choosing factory or provider is which case is the easiest with dealing with multiple google maps loading. IE is the maps api being provided by a singleton object or multiple objects?
Yes, you're right :)
Hmmm, I've a lot of code going through my brain now. I researched a bit about how to do this with a provider, and I got to two possible conclusions.
I'll make this short, with no code pastes, but if I don't make myself clear properly I'll paste some code.
With a default provider I can write the map in three stages:
DRAWBACKS - we'll call the map at least two times, one the default one, so the script won't get jammed, and the second one with the custom parameters, this is not ok.
this.$get = function($q) {}
We create a provider where we can listen to its success status with .then()
inside the directive, it gets the job done, it halts the execution, or we can also make a run with the default map.
But this one, in its essence, it does the same things as my factory service does.
I'm certain I'm missing something here. What is it? :)
Sorry for all this talking but I need to get the notions straight. :)
EDIT: Is it weird [and therefore needed to use a provider] because I parametrized my factory service in a NonAngular friendly way?
.factory('googleMapsFactory', function ($q, $window) {
return {
fn: function(apis) {
Creating a "googleMapsProvider" using module.provider() will result in a provider which is only accessible in the .config() phase. What will this provider return from the $get() method will be available in our directives and other controllers as "googleMaps" (remove Provider from its name).
Sent from Gmail web
On Thu, Jan 23, 2014 at 3:54 PM, Arthur Kovacs notifications@github.comwrote:
Hmmm, I've a lot of code going through my brain now. I researched a bit about how to do this with a provider, and I got to two possible conclusions.
I'll make this short, with no code pastes, but if I don't make myself clear properly I'll paste some code. Regular provider
With a default provider I can write the map in three stages:
- DEFAULT MAP, inside the provider, after declaration
- DEFAULT MAP, in the .config() stage (I'm saying default because I have thought of no way to get the directive's scope params OUTSIDE, and nonetheless, the config runs before the directive kicks in)
- CUSTOM MAP with my parameters, while calling the provider from inside the directive
DRAWBACKS - we'll call the map at least two times, one the default one, so the script won't get jammed, and the second one with the custom parameters, this is not ok. Deferred provider this.$get = function($q) {}
We create a provider where we can listen to its success status with .then() inside the directive, it gets the job done, it halts the execution, or we can also make a run with the default map.
But this one, in its essence, it does the same things as my factory
service does.
I'm certain I'm missing something here. What is it? :)
Sorry for all this talking but I need to get the notions straight. :)
— Reply to this email directly or view it on GitHubhttps://github.com/nlaplante/angular-google-maps/issues/221#issuecomment-33167367 .
I realized the thing with Provider and without in front of the name:
//Creation
.provider('googleMaps', function() {
this.map = 'Default';
this.$get = function() {
var map = this.map;
return {
loadMap: function() {
return map
}
}
};
//Run it the first time
this.loadMap = function(map) {
this.map = map;
};
})
//Configuration
.config(function(googleMapsProvider){
googleMapsProvider.loadMap('');
});
//Run from inside directive
googleMaps.loadMap(mapWithCustomParameters);
What I was saying I DON'T GET is:
EDIT: Not knowing, I'm difficult like that, sorry :) I'm trying to find the practicality in doing this over a factory service (with a map loaded from the start with custom params.)
There is no map in the provider. The provider only offers some kind of configuration api for the directives to use.
I think I get it.
Ok, I stopped asking questions :) . On to the code, we'll se there if what I understood matches what I should do.
There are 2 things in a provider:
So, suppose we want a GoogleMaps service, and declare it via a provider:
angular.module('google-maps')
.provider('googleMaps', function () {
var useSensor = false, apiKey = null;
// here, we make the provider api (not the service api)
this.sensor = function () {
if (arguments.length) {
// called as a setter, set value and chain
useSensor = arguments[0];
return this;
}
// called as a getter
return useSensor;
};
// same for api key
this.apiKey= function () {
if (arguments.length) {
// called as a setter, set value and chain
apiKey= arguments[0];
return this;
}
// called as a getter
return apiKey;
};
// here we define the GoogleMaps service
this.$get = function ($log, $q, $whateverAngularService) {
return {
serviceMethod1: function () { /* ... */ },
serviceMethod2: function () { /* ... */ }
};
};
});
Now, in the config phase:
angular.module('googleMaps')
.config(function (googleMapsProvider) {
googleMapsProvider.useSensor(true).apiKey('someApiKey');
});
And finally in our controllers:
angular.module('googleMaps')
.controller('SomeCtrl', function ($scope, googleMaps) {
googleMaps.serviceMethod1();
googleMaps.serviceMethod2();
});
Check the website branch, there's a GitHub provider over there. Pretty straightforward.
This is pretty clear indeed.
Now I realize there was a misunderstanding here, this whole time I was making assumptions and putting questions by keeping in mind that we want the user to do as little as possible, I wanted to have everything simple for the end user of the library, therefore everything called inside the directive declaration
And, from the code below, the API key needs to be inputed in a different piece of code by the user, but what you are saying makes more sense, like this, there is more flexibility.
angular.module('googleMaps')
.config(function (googleMapsProvider) {
googleMapsProvider.useSensor(true).apiKey('someApiKey');
});
I'm not making any fun or anything, I learned a lot today.
Chances are, ppl are already using .config() for the html5mode and hashPrefix, even for debugEnabled() on the $logProvider. I don't think it requires that much more effort.
Sent from Gmail web
On Thu, Jan 23, 2014 at 4:53 PM, Arthur Kovacs notifications@github.comwrote:
This is pretty clear indeed.
Now I realize there was a misunderstanding here, this whole time I was making assumptions and putting questions by keeping in mind that we want the user to do as little as possible, I wanted to have everything simple for the end user, therefore everything called inside the directive declaration
And, from the code below, the API key needs to be inputed in a different piece of code by the user, but what you are saying makes more sense, like this, there is more flexibility.
angular.module('googleMaps') .config(function (googleMapsProvider) { googleMapsProvider.useSensor(true).apiKey('someApiKey'); });
Awesome example with the github provider. Real world. Thank you very much!
I'm not making any fun or anything, I learned a lot today.
— Reply to this email directly or view it on GitHubhttps://github.com/nlaplante/angular-google-maps/issues/221#issuecomment-33173167 .
Hello,
I made the factory provider, I'll put this here and in the next comment the details.
var app;
app = angular.module("google-maps", []);
return app.provider('googleMaps', function () {
var useSensor = false;
var apiKey = null;
var useLanguage = null;
var useLibrary = null;
var useVersion = null;
//I'm adding all the configs to this object, I hope this is alright, then pass it inside the constructor function.
var options = {};
// Public Config API's
this.useSensor = function (sensor) {
if (sensor) {
options.sensor = sensor;
return this;
}
return useSensor;
};
this.apiKey = function (key) {
if (key) {
options.key = key;
return this;
}
return apiKey;
};
this.useLanguage = function (language) {
if (language) {
options.language = language;
return this;
}
return useLanguage;
};
this.useLibrary = function (libraries) {
if (libraries) {
options.libraries = libraries;
return this;
}
return useLibrary;
};
this.useVersion = function (v) {
if (v) {
options.v = v;
return this;
}
return useVersion;
};
// Private Constructor
function GoogleMapsService ($q, $window) {
makeMap = function() {
//Default map url.
var url = 'https://maps.googleapis.com/maps/api/js?';
var concatURL = function() {
angular.forEach(options, function(value, key) {
url += ('&' + key + '=' + value);
});
};
if (options && ('sensor' in options)) {
//Check to see if we have a sensor value, maybe the user forgot ... stranger things have happened.
concatURL();
} else if (options) {
//If there is no sensor value, add a default false one.
options.sensor = false;
concatURL();
} else {
//if no config options
url += '?sensor=false&v=3.14';
}
return url;
};
this.runMap = function() {
var deferred = $q.defer();
var randomizedFunctionName = 'onGoogleMapsReady' + Math.round(Math.random()*1000);
window[randomizedFunctionName] = function() {
window[randomizedFunctionName] = null;
// Resolve the promise for googleMaps
deferred.resolve($window.google.maps);
};
if(typeof $window.google !== 'undefined' && typeof $window.google.maps !== 'undefined') {
// Early-resolve the promise for googleMaps
deferred.resolve($window.google.maps);
return deferred.promise;
}
//TODO: need to add the map only once, for some reason the provider runs twice and ads the map again (in my app, inserted in the module it works flawlessly)
var script = document.createElement('script');
script.type = 'text/javascript';
// VVV hmmmm, not good, but let's go with it.
script.src = makeMap() + '&callback='+randomizedFunctionName;
document.body.appendChild(script);
// Return a promise for googleMaps
return deferred.promise;
};
}
//Provider instantiator
this.$get = function ($q, $window) {
return new GoogleMapsService($q, $window);
};
})
.config(function (googleMapsProvider) {
googleMapsProvider
.useSensor(true)
.apiKey('xxxxxxxxxxxxxxxxxxxx')
.useLanguage('en')
.useLibrary('weather,visualization')
.useVersion('3.14');
})
var mapCall = false;
if (!mapCall) {
console.log('ran once');
googleMaps.runMap().then(function() {
console.log('shit is on.');
//Add lat/long and other options to the map
populateMap();
});
mapCall = true;
}
After many hours and drawings, because I needed to understand what and where happens ($github provider was an awesome example btw) I finally made the provider, but there still are some issues.
I already tested the provider in your library,
What I tested, all the code inside the link
from the main google maps directive ran after successfully adding the script to the page.
The Map appeared, but the marker directive was parsed before .then(), so, as I've stated yesterday, I might gave the wrong bottleneck...
It's madness to add this to every directive, and purely stupid, how could I stop execution of everything up until the script is added ? You talked about a global variable ?
Should every directives and whatnot there be dependent to the status of a variable that is pushed out (UP through the scope) of the main directive??
I'm gonna give it some more though this evening, I'm going back to my day work untill this evening.
Well I guess there are 3 options:
1), this is the angular way. Just like passing $log and $scope and $wathever
Sent from my Nexus 4 On 24 Jan 2014 17:40, "nmccready" notifications@github.com wrote:
Well I guess there are 3 options:
- pass provider into every directive (just like main map)
- use link (ctrl) to get the mapCtrl which already has the provider and maybe a .ready() callback
- worst case global object that does the same as option 2
— Reply to this email directly or view it on GitHubhttps://github.com/nlaplante/angular-google-maps/issues/221#issuecomment-33268939 .
I think option two is also angular and seems like less work. However, the provider being passed like option 1 would setup a good example for people using the same provider outside of the map directive's scope.
I didn't received notifications of you writing here, sorry.
Hmm, yes, I think 1. is the answer (more work, but hey), I'll get on with that first thing tomorrow.
I'd have to get knee deep in your coffee files, I'l do that, run grunt then test to see if everything is ok on the compiled distribution file
I added the factory provider to your library (written in coffee, might I say :) ) (https://github.com/ArthurianX/angular-google-maps/commit/e6d141ab260872cf0a8f45fb311fa5436940ffe3) and for the past hours I'm hitting a lot of bumps with the option 1: "wait for provider in each directive" :)), it partially works, but there's a ton of buggy places and issues.
I still can't get over the fact of how elegant is to work with providers, omg.
For once, there's a small problem in my provider, with this approach a map is added to the dom in each .then call, this is a small issue, it can be rewritten.
BUT, there's a ton of other problems, you created API's for programmatically creating directives that run at the beginning of everything, anyhow, overall, with this approach I think I need to touch a lot of places there, and there's a lot of possible errors just waiting to happen. In short, it would take a lot of rewriting to get this done in my opinion.
In principle this should be very straightforward to accomplish, I think.
The first should be stop calling ALL the functions on the script load, the helpers for the directives than the directives themselves, they should all run after either on demand either ALL after we've gotten gmaps api js in dom.
Will come back with further reading material after reading myself.
The only thing I want is to remain inside angular's scope, not to use anything else external.
I'm imagining things like this:
-- call your script: ---- it loads a module that has the provider ------ the provider succesfully loads the map js (with / out configs) -------- then your module is running, calling all other code.
Arthur I will make a branch for you to commit your work too so I can help out. Did u branch from master or develop? This probably should be from develop as it is a major change.
I think I worked on master, I'll switch to develop and apply the changes so far there.
Switched to develop and added the factory provider, https://github.com/ArthurianX/angular-google-maps/commit/e61c3388d2eaf45cc1bca0d4ea1033d15d13c407
The rest of the code I've done is a pretty mess, nothing to upload yet on that part.
Made the pull request.
EDIT: Crap, I made into your master, I specifically switch on the develop branch and then hit the pull request button...
Be aware that on develop markers and windows directives now require an id within a model. This was done to speed things up. Basically an array is being used less and a map/object instead.
I haven't forgotten about you. I am going to look at this now, and start working on it with you in the branch we merged to.
Sorry to post on a slightly old issue but I didn't see a more recent place to pose this question.
I am wondering if the provider you guys are working on adding into this AngularJS module supports the outlined use case below.
I am working on an web application that the API Key/Client ID is not known until after a request is made and returned from a RESTful service through Restangular, which means after page load. Would the provider be able to handle this, or would the JS script that includes the configuration of the provider essentially have to have these details 'hard coded' still? Later in the future our SAAS application will support different API Keys/Client IDs per logged in user.
Currently how we handle this is by storing the API Key/Client ID into the JSession object and using a JSP page to add these details to the end of the Google Maps Lib JS import script tag.
@starr0stealer I think it can solve your issue.
On the dev-branch is a newer version that already lets me integrate angular-google-maps with a browserify-build. Only thing missing: Lazy-load the google-api when the map is realy needed. I have to intergrate this over the weekend. I plan to do a PR for it. And if we can lazy-load the google-api, we can set api-keys..
There is something like THIS already started. But I didn't t had time to finish and my focus shifted (bosses ). Check my comments on this repo. — Sent from Mailbox for iPhone
On Fri, Mar 7, 2014 at 6:37 PM, Oliver Leics notifications@github.com wrote:
@starr0stealer I think it can solve your issue.
On the dev-branch is a newer version that already lets me integrate angular-google-maps with a browserify-build. Only thing missing: Lazy-load the google-api when the map is realy needed. I have to intergrate this over the weekend. I plan to do a PR for it. And if we can lazy-load the google-api, we can set api-keys..
Reply to this email directly or view it on GitHub: https://github.com/nlaplante/angular-google-maps/issues/221#issuecomment-37041321
When do you guys think you can have this done? I have not had much time to look at this and I plan on doing the polyline stuff this weekend. If this can't be done by Sunday I think this should be moved to post 1.1.0 .
@oleics do you have time to finish implementing this or doing it your own way? Otherwise I may hack something together for #303 .
or @ArthurianX
@nmccready No. Problem was/is: Coffeescript.
But: I've mostly completed the google-maps-service in plain JavaScript. It is now a service-provider. Use it in .config() of a angular-module to configure google-maps.
https://gist.github.com/oleics/9529516
Only thing missing: Error-handling, but that is not that hard to implemented. Feel free to port that google-maps-service-provider to coffeescript and use in angular-google-maps.
And if I can help, just ping me.
Cool I'll take a look. If you have a high desire to keep it js I can pull it as is.
Nicholas McCready
Personal Email: nmccready@gmail.com
Web site: http://codemonkeyseedo.blogspot.com/
Twitter: nmccready
On Mar 13, 2014 10:41 AM, "Oliver Leics" notifications@github.com wrote:
@nmccready https://github.com/nmccready No. Problem was/is: Coffeescript.
But: I've mostly completed the google-maps-service in plain JavaScript. It is now a service-provider. Use it in .config() of a angular-module to configure google-maps.
https://gist.github.com/oleics/9529516
Only thing missing: Error-handling, but that is not that hard to implemented. Feel free to port that google-maps-service-provider to coffeescript and use in angular-google-maps.
And if I can help, just ping me.
Reply to this email directly or view it on GitHubhttps://github.com/nlaplante/angular-google-maps/issues/221#issuecomment-37540496 .
Just port it to cs, I don't mind. It would be a good read for me, just to see the coffee-version of the script.. And again, ping me if you need help.
I am dead in the water.
Three ongoing projects at which I am working and a hackathon in the company this weekend :( — Sent from Mailbox for iPhone
On Thu, Mar 13, 2014 at 6:26 PM, Oliver Leics notifications@github.com wrote:
Just port it to cs, I don't mind. It would be a good read for me, just to see the coffee-version of the script.. And again, ping me if you need help.
Reply to this email directly or view it on GitHub: https://github.com/nlaplante/angular-google-maps/issues/221#issuecomment-37553749
We need to integrate this factory service in the whole library, so everything runs after the file is available.
Also, we need to have some variables passed before getting the gMaps JS, with parameters like API's, version, language etc. And a FRIENDLY way for the user to enter those parameters