]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - plugins/Mapstraction/js/mxn.js
Merge branch '0.9.x' of git@gitorious.org:statusnet/mainline into 0.9.x
[quix0rs-gnu-social.git] / plugins / Mapstraction / js / mxn.js
1 // Auto-load scripts\r
2 //\r
3 // specify which map providers to load by using\r
4 // <script src="mxn.js?(provider1,provider2,[module1,module2])" ...\r
5 // in your HTML\r
6 //\r
7 // for each provider mxn.provider.module.js and mxn.module.js will be loaded\r
8 // module 'core' is always loaded\r
9 //\r
10 // NOTE: if you call without providers\r
11 // <script src="mxn.js" ...\r
12 // no scripts will be loaded at all and it is then up to you to load the scripts independently\r
13 (function() {\r
14         var providers = null;\r
15         var modules = 'core';\r
16         var scriptBase;\r
17         var scripts = document.getElementsByTagName('script');\r
18 \r
19         // Determine which scripts we need to load      \r
20         for (var i = 0; i < scripts.length; i++) {\r
21                 var match = scripts[i].src.replace(/%20/g , '').match(/^(.*?)mxn\.js(\?\(\[?(.*?)\]?\))?$/);\r
22                 if (match != null) {\r
23                         scriptBase = match[1];\r
24                         if (match[3]) {\r
25                                 var settings = match[3].split(',[');\r
26                                 providers = settings[0].replace(']' , '');\r
27                                 if (settings[1]) modules += ',' + settings[1];\r
28                         }\r
29                         break;\r
30            }\r
31         }\r
32         \r
33     if (providers == null || providers == 'none') return; // Bail out if no auto-load has been found\r
34         providers = providers.replace(/ /g, '').split(',');\r
35         modules = modules.replace(/ /g, '').split(',');\r
36 \r
37         // Actually load the scripts\r
38         for (i = 0; i < modules.length; i++) {\r
39             document.write("<script type='text/javascript' src='" + scriptBase + 'mxn.' + modules[i] + '.js' + "'></script>");\r
40             for (var j = 0; j < providers.length; j++) document.write("<script type='text/javascript' src='" + scriptBase + 'mxn.' + providers[j] + '.' + modules[i] + '.js' + "'></script>");\r
41         }\r
42 })();\r
43 \r
44 (function(){\r
45 \r
46 // holds all our implementing functions\r
47 var apis = {};\r
48 \r
49 // Our special private methods\r
50 /**\r
51  * Calls the API specific implementation of a particular method.\r
52  * @private\r
53  */\r
54 var invoke = function(sApiId, sObjName, sFnName, oScope, args){\r
55         if(!hasImplementation(sApiId, sObjName, sFnName)) {\r
56                 throw 'Method ' + sFnName + ' of object ' + sObjName + ' is not supported by API ' + sApiId + '. Are you missing a script tag?';\r
57         }\r
58         return apis[sApiId][sObjName][sFnName].apply(oScope, args);\r
59 };\r
60         \r
61 /**\r
62  * Determines whether the specified API provides an implementation for the \r
63  * specified object and function name.\r
64  * @private\r
65  */\r
66 var hasImplementation = function(sApiId, sObjName, sFnName){\r
67         if(typeof(apis[sApiId]) == 'undefined') {\r
68                 throw 'API ' + sApiId + ' not loaded. Are you missing a script tag?';\r
69         }\r
70         if(typeof(apis[sApiId][sObjName]) == 'undefined') {\r
71                 throw 'Object definition ' + sObjName + ' in API ' + sApiId + ' not loaded. Are you missing a script tag?'; \r
72         }\r
73         return typeof(apis[sApiId][sObjName][sFnName]) == 'function';\r
74 };\r
75 \r
76 /**\r
77  * @name mxn\r
78  * @namespace\r
79  */\r
80 var mxn = window.mxn = /** @lends mxn */ {\r
81         \r
82         /**\r
83          * Registers a set of provider specific implementation functions.\r
84          * @function\r
85          * @param {String} sApiId The API ID to register implementing functions for.\r
86          * @param {Object} oApiImpl An object containing the API implementation.\r
87          */\r
88         register: function(sApiId, oApiImpl){\r
89                 if(!apis.hasOwnProperty(sApiId)){\r
90                         apis[sApiId] = {};\r
91                 }\r
92                 mxn.util.merge(apis[sApiId], oApiImpl);\r
93         },              \r
94         \r
95         /**\r
96          * Adds a list of named proxy methods to the prototype of a \r
97          * specified constructor function.\r
98          * @function\r
99          * @param {Function} func Constructor function to add methods to\r
100          * @param {Array} aryMethods Array of method names to create\r
101          * @param {Boolean} bWithApiArg Optional. Whether the proxy methods will use an API argument\r
102          */\r
103         addProxyMethods: function(func, aryMethods, bWithApiArg){\r
104                 for(var i = 0; i < aryMethods.length; i++) {\r
105                         var sMethodName = aryMethods[i];\r
106                         if(bWithApiArg){\r
107                                 func.prototype[sMethodName] = new Function('return this.invoker.go(\'' + sMethodName + '\', arguments, { overrideApi: true } );');\r
108                         }\r
109                         else {\r
110                                 func.prototype[sMethodName] = new Function('return this.invoker.go(\'' + sMethodName + '\', arguments);');\r
111                         }\r
112                 }\r
113         },\r
114         \r
115         /*\r
116         checkLoad: function(funcDetails){\r
117                 if(this.loaded[this.api] === false) {\r
118                         var scope = this;\r
119                         this.onload[this.api].push( function() { funcDetails.callee.apply(scope, funcDetails); } );\r
120                         return true;\r
121                 }\r
122                 return false;\r
123         },\r
124         */\r
125                         \r
126         /**\r
127          * Bulk add some named events to an object.\r
128          * @function\r
129          * @param {Object} oEvtSrc The event source object.\r
130          * @param {String[]} aEvtNames Event names to add.\r
131          */\r
132         addEvents: function(oEvtSrc, aEvtNames){\r
133                 for(var i = 0; i < aEvtNames.length; i++){\r
134                         var sEvtName = aEvtNames[i];\r
135                         if(sEvtName in oEvtSrc){\r
136                                 throw 'Event or method ' + sEvtName + ' already declared.';\r
137                         }\r
138                         oEvtSrc[sEvtName] = new mxn.Event(sEvtName, oEvtSrc);\r
139                 }\r
140         }\r
141         \r
142 };\r
143 \r
144 /**\r
145  * Instantiates a new Event \r
146  * @constructor\r
147  * @param {String} sEvtName The name of the event.\r
148  * @param {Object} oEvtSource The source object of the event.\r
149  */\r
150 mxn.Event = function(sEvtName, oEvtSource){\r
151         var handlers = [];\r
152         if(!sEvtName){\r
153                 throw 'Event name must be provided';\r
154         }\r
155         /**\r
156          * Add a handler to the Event.\r
157          * @param {Function} fn The handler function.\r
158          * @param {Object} ctx The context of the handler function.\r
159          */\r
160         this.addHandler = function(fn, ctx){\r
161                 handlers.push({context: ctx, handler: fn});\r
162         };\r
163         /**\r
164          * Remove a handler from the Event.\r
165          * @param {Function} fn The handler function.\r
166          * @param {Object} ctx The context of the handler function.\r
167          */\r
168         this.removeHandler = function(fn, ctx){\r
169                 for(var i = 0; i < handlers.length; i++){\r
170                         if(handlers[i].handler == fn && handlers[i].context == ctx){\r
171                                 handlers.splice(i, 1);\r
172                         }\r
173                 }\r
174         };\r
175         /**\r
176          * Remove all handlers from the Event.\r
177          */\r
178         this.removeAllHandlers = function(){\r
179                 handlers = [];\r
180         };\r
181         /**\r
182          * Fires the Event.\r
183          * @param {Object} oEvtArgs Event arguments object to be passed to the handlers.\r
184          */\r
185         this.fire = function(oEvtArgs){\r
186                 var args = [sEvtName, oEvtSource, oEvtArgs];\r
187                 for(var i = 0; i < handlers.length; i++){\r
188                         handlers[i].handler.apply(handlers[i].context, args);\r
189                 }\r
190         };\r
191 };\r
192 \r
193 /**\r
194  * Creates a new Invoker, a class which helps with on-the-fly \r
195  * invocation of the correct API methods.\r
196  * @constructor\r
197  * @param {Object} aobj The core object whose methods will make cals to go()\r
198  * @param {String} asClassName The name of the Mapstraction class to be invoked, normally the same name as aobj's constructor function\r
199  * @param {Function} afnApiIdGetter The function on object aobj which will return the active API ID\r
200  */\r
201 mxn.Invoker = function(aobj, asClassName, afnApiIdGetter){\r
202         var obj = aobj;\r
203         var sClassName = asClassName;\r
204         var fnApiIdGetter = afnApiIdGetter;\r
205         var defOpts = { \r
206                 overrideApi: false, // {Boolean} API ID is overridden by value in first argument\r
207                 context: null, // {Object} Local vars can be passed from the body of the method to the API method within this object\r
208                 fallback: null // {Function} If an API implementation doesn't exist this function is run instead\r
209         };\r
210         \r
211         /**\r
212          * Invoke the API implementation of a specific method.\r
213          * @param {String} sMethodName The method name to invoke\r
214          * @param {Array} args Arguments to pass on\r
215          * @param {Object} oOptions Optional. Extra options for invocation\r
216          * @param {Boolean} oOptions.overrideApi When true the first argument is used as the API ID.\r
217          * @param {Object} oOptions.context A context object for passing extra information on to the provider implementation.\r
218          * @param {Function} oOptions.fallback A fallback function to run if the provider implementation is missing.\r
219          */\r
220         this.go = function(sMethodName, args, oOptions){\r
221                 \r
222                 if(typeof(oOptions) == 'undefined'){\r
223                         oOptions = defOpts;\r
224                 }\r
225                                                 \r
226                 var sApiId = oOptions.overrideApi ? args[0] : fnApiIdGetter.apply(obj);\r
227                 \r
228                 if(typeof(sApiId) != 'string'){\r
229                         throw 'API ID not available.';\r
230                 }\r
231                 \r
232                 if(typeof(oOptions.context) != 'undefined' && oOptions.context !== null){\r
233                         // make sure args is an array\r
234                         args = Array.prototype.slice.apply(args);\r
235                         args.push(oOptions.context);\r
236                 }\r
237                 \r
238                 if(typeof(oOptions.fallback) == 'function' && !hasImplementation(sApiId, sClassName, sMethodName)){\r
239                         // we've got no implementation but have got a fallback function\r
240                         return oOptions.fallback.apply(obj, args);\r
241                 }\r
242                 else {                          \r
243                         return invoke(sApiId, sClassName, sMethodName, obj, args);\r
244                 }\r
245                 \r
246         };\r
247         \r
248 };\r
249 \r
250 /**\r
251  * @namespace\r
252  */\r
253 mxn.util = {\r
254                         \r
255         /**\r
256          * Merges properties of one object into another recursively.\r
257          * @param {Object} oRecv The object receiveing properties\r
258          * @param {Object} oGive The object donating properties\r
259          */\r
260         merge: function(oRecv, oGive){\r
261                 for (var sPropName in oGive){\r
262                         if (oGive.hasOwnProperty(sPropName)) {\r
263                                 if(!oRecv.hasOwnProperty(sPropName)){\r
264                                         oRecv[sPropName] = oGive[sPropName];\r
265                                 }\r
266                                 else {\r
267                                         mxn.util.merge(oRecv[sPropName], oGive[sPropName]);\r
268                                 }\r
269                         }\r
270                 }\r
271         },\r
272         \r
273         /**\r
274          * $m, the dollar function, elegantising getElementById()\r
275          * @return An HTML element or array of HTML elements\r
276          */\r
277         $m: function() {\r
278                 var elements = [];\r
279                 for (var i = 0; i < arguments.length; i++) {\r
280                         var element = arguments[i];\r
281                         if (typeof(element) == 'string') {\r
282                                 element = document.getElementById(element);\r
283                         }\r
284                         if (arguments.length == 1) {\r
285                                 return element;\r
286                         }\r
287                         elements.push(element);\r
288                 }\r
289                 return elements;\r
290         },\r
291 \r
292         /**\r
293          * loadScript is a JSON data fetcher\r
294          * @param {String} src URL to JSON file\r
295          * @param {Function} callback Callback function\r
296          */\r
297         loadScript: function(src, callback) {\r
298                 var script = document.createElement('script');\r
299                 script.type = 'text/javascript';\r
300                 script.src = src;\r
301                 if (callback) {\r
302                         if(script.addEventListener){\r
303                                 script.addEventListener('load', callback, true);\r
304                         }\r
305                         else if(script.attachEvent){\r
306                                 var done = false;\r
307                                 script.attachEvent("onreadystatechange",function(){\r
308                                         if ( !done && document.readyState === "complete" ) {\r
309                                                 done = true;\r
310                                                 callback();\r
311                                         }\r
312                                 });\r
313                         }                       \r
314                 }\r
315                 var h = document.getElementsByTagName('head')[0];\r
316                 h.appendChild( script );\r
317                 return;\r
318         },\r
319 \r
320         /**\r
321          *\r
322          * @param {Object} point\r
323          * @param {Object} level\r
324          */\r
325         convertLatLonXY_Yahoo: function(point, level) { //Mercator\r
326                 var size = 1 << (26 - level);\r
327                 var pixel_per_degree = size / 360.0;\r
328                 var pixel_per_radian = size / (2 * Math.PI);\r
329                 var origin = new YCoordPoint(size / 2 , size / 2);\r
330                 var answer = new YCoordPoint();\r
331                 answer.x = Math.floor(origin.x + point.lon * pixel_per_degree);\r
332                 var sin = Math.sin(point.lat * Math.PI / 180.0);\r
333                 answer.y = Math.floor(origin.y + 0.5 * Math.log((1 + sin) / (1 - sin)) * -pixel_per_radian);\r
334                 return answer;\r
335         },\r
336 \r
337         /**\r
338          * Load a stylesheet from a remote file.\r
339          * @param {String} href URL to the CSS file\r
340          */\r
341         loadStyle: function(href) {\r
342                 var link = document.createElement('link');\r
343                 link.type = 'text/css';\r
344                 link.rel = 'stylesheet';\r
345                 link.href = href;\r
346                 document.getElementsByTagName('head')[0].appendChild(link);\r
347                 return;\r
348         },\r
349 \r
350         /**\r
351          * getStyle provides cross-browser access to css\r
352          * @param {Object} el HTML Element\r
353          * @param {String} prop Style property name\r
354          */\r
355         getStyle: function(el, prop) {\r
356                 var y;\r
357                 if (el.currentStyle) {\r
358                         y = el.currentStyle[prop];\r
359                 }\r
360                 else if (window.getComputedStyle) {\r
361                         y = window.getComputedStyle( el, '').getPropertyValue(prop);\r
362                 }\r
363                 return y;\r
364         },\r
365 \r
366         /**\r
367          * Convert longitude to metres\r
368          * http://www.uwgb.edu/dutchs/UsefulData/UTMFormulas.HTM\r
369          * "A degree of longitude at the equator is 111.2km... For other latitudes,\r
370          * multiply by cos(lat)"\r
371          * assumes the earth is a sphere but good enough for our purposes\r
372          * @param {Float} lon\r
373          * @param {Float} lat\r
374          */\r
375         lonToMetres: function(lon, lat) {\r
376                 return lon * (111200 * Math.cos(lat * (Math.PI / 180)));\r
377         },\r
378 \r
379         /**\r
380          * Convert metres to longitude\r
381          * @param {Object} m\r
382          * @param {Object} lat\r
383          */\r
384         metresToLon: function(m, lat) {\r
385                 return m / (111200 * Math.cos(lat * (Math.PI / 180)));\r
386         },\r
387 \r
388         /**\r
389          * Convert kilometres to miles\r
390          * @param {Float} km\r
391          * @returns {Float} miles\r
392          */\r
393         KMToMiles: function(km) {\r
394                 return km / 1.609344;\r
395         },\r
396 \r
397         /**\r
398          * Convert miles to kilometres\r
399          * @param {Float} miles\r
400          * @returns {Float} km\r
401          */\r
402         milesToKM: function(miles) {\r
403                 return miles * 1.609344;\r
404         },\r
405 \r
406         // stuff to convert google zoom levels to/from degrees\r
407         // assumes zoom 0 = 256 pixels = 360 degrees\r
408         //               zoom 1 = 256 pixels = 180 degrees\r
409         // etc.\r
410 \r
411         /**\r
412          *\r
413          * @param {Object} pixels\r
414          * @param {Object} zoom\r
415          */\r
416         getDegreesFromGoogleZoomLevel: function(pixels, zoom) {\r
417                 return (360 * pixels) / (Math.pow(2, zoom + 8));\r
418         },\r
419 \r
420         /**\r
421          *\r
422          * @param {Object} pixels\r
423          * @param {Object} degrees\r
424          */\r
425         getGoogleZoomLevelFromDegrees: function(pixels, degrees) {\r
426                 return mxn.util.logN((360 * pixels) / degrees, 2) - 8;\r
427         },\r
428 \r
429         /**\r
430          *\r
431          * @param {Object} number\r
432          * @param {Object} base\r
433          */\r
434         logN: function(number, base) {\r
435                 return Math.log(number) / Math.log(base);\r
436         },\r
437                         \r
438         /**\r
439          * Returns array of loaded provider apis\r
440          * @returns {Array} providers\r
441          */\r
442         getAvailableProviders : function () {\r
443                 var providers = [];\r
444                 for (var propertyName in apis){\r
445                         if (apis.hasOwnProperty(propertyName)) {\r
446                                 providers.push(propertyName);\r
447                         }\r
448                 }\r
449                 return providers;\r
450         }\r
451         \r
452 };\r
453 \r
454 /**\r
455  * Class for converting between HTML and RGB integer color formats.\r
456  * Accepts either a HTML color string argument or three integers for R, G and B.\r
457  * @constructor\r
458  */\r
459 mxn.util.Color = function() {   \r
460         if(arguments.length == 3) {\r
461                 this.red = arguments[0];\r
462                 this.green = arguments[1];\r
463                 this.blue = arguments[2];\r
464         }\r
465         else if(arguments.length == 1) {\r
466                 this.setHexColor(arguments[0]);\r
467         }\r
468 };\r
469 \r
470 mxn.util.Color.prototype.reHex = /^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;\r
471 \r
472 /**\r
473  * Set the color from the supplied HTML hex string.\r
474  * @param {String} strHexColor A HTML hex color string e.g. '#00FF88'.\r
475  */\r
476 mxn.util.Color.prototype.setHexColor = function(strHexColor) {\r
477         var match = strHexColor.match(this.reHex);\r
478         if(match) {\r
479                 strHexColor = match[1];\r
480         }\r
481         else {\r
482                 throw 'Invalid HEX color format, expected #000, 000, #000000 or 000000';\r
483         }\r
484         if(strHexColor.length == 3) {\r
485                 strHexColor = strHexColor.replace(/\w/g, function(str){return str.concat(str);});\r
486         }\r
487         this.red = parseInt(strHexColor.substr(0,2), 16);\r
488         this.green = parseInt(strHexColor.substr(2,2), 16);\r
489         this.blue = parseInt(strHexColor.substr(4,2), 16);\r
490 };\r
491 \r
492 /**\r
493  * Retrieve the color value as an HTML hex string.\r
494  * @returns {String} Format '00FF88' - note no preceding #.\r
495  */\r
496 mxn.util.Color.prototype.getHexColor = function() {\r
497         var vals = [this.red.toString(16), this.green.toString(16), this.blue.toString(16)];\r
498         for(var i = 0; i < vals.length; i++) {\r
499                 vals[i] = (vals[i].length == 1) ? '0' + vals[i] : vals[i];\r
500                 vals[i] = vals[i].toUpperCase();\r
501         }\r
502         return vals.join('');\r
503 };\r
504 \r
505 })();