source : attr2-map-options.js

  1. /**
  2. * @ngdoc service
  3. * @name Attr2MapOptions
  4. * @description
  5. * Converts tag attributes to options used by google api v3 objects
  6. */
  7. /* global google */
  8. (function() {
  9. 'use strict';
  10. //i.e. "2015-08-12T06:12:40.858Z"
  11. var isoDateRE =
  12. /^(\d{4}\-\d\d\-\d\d([tT][\d:\.]*)?)([zZ]|([+\-])(\d\d):?(\d\d))?$/;
  13. var Attr2MapOptions = function(
  14. $parse, $timeout, $log, $interpolate, NavigatorGeolocation, GeoCoder,
  15. camelCaseFilter, jsonizeFilter, escapeRegExp
  16. ) {
  17. var exprStartSymbol = $interpolate.startSymbol();
  18. var exprEndSymbol = $interpolate.endSymbol();
  19. /**
  20. * Returns the attributes of an element as hash
  21. * @memberof Attr2MapOptions
  22. * @param {HTMLElement} el html element
  23. * @returns {Hash} attributes
  24. */
  25. var orgAttributes = function(el) {
  26. (el.length > 0) && (el = el[0]);
  27. var orgAttributes = {};
  28. for (var i=0; i<el.attributes.length; i++) {
  29. var attr = el.attributes[i];
  30. orgAttributes[attr.name] = attr.value;
  31. }
  32. return orgAttributes;
  33. };
  34. var getJSON = function(input) {
  35. var re =/^[\+\-]?[0-9\.]+,[ ]*\ ?[\+\-]?[0-9\.]+$/; //lat,lng
  36. if (input.match(re)) {
  37. input = "["+input+"]";
  38. }
  39. return JSON.parse(jsonizeFilter(input));
  40. };
  41. var getLatLng = function(input) {
  42. var output = input;
  43. if (input[0].constructor == Array) {
  44. if ((input[0][0].constructor == Array && input[0][0].length == 2) || input[0][0].constructor == Object) {
  45. var preoutput;
  46. var outputArray = [];
  47. for (var i = 0; i < input.length; i++) {
  48. preoutput = input[i].map(function(el){
  49. return new google.maps.LatLng(el[0], el[1]);
  50. });
  51. outputArray.push(preoutput);
  52. }
  53. output = outputArray;
  54. } else {
  55. output = input.map(function(el) {
  56. return new google.maps.LatLng(el[0], el[1]);
  57. });
  58. }
  59. } else if (!isNaN(parseFloat(input[0])) && isFinite(input[0])) {
  60. output = new google.maps.LatLng(output[0], output[1]);
  61. }
  62. return output;
  63. };
  64. var toOptionValue = function(input, options) {
  65. var output;
  66. try { // 1. Number?
  67. output = getNumber(input);
  68. } catch(err) {
  69. try { // 2. JSON?
  70. var output = getJSON(input);
  71. if (output instanceof Array) {
  72. if (output[0].constructor == Object) {
  73. output = output;
  74. } else if (output[0] instanceof Array) {
  75. if (output[0][0].constructor == Object) {
  76. output = output;
  77. } else {
  78. output = getLatLng(output);
  79. }
  80. } else {
  81. output = getLatLng(output);
  82. }
  83. }
  84. // JSON is an object (not array or null)
  85. else if (output === Object(output)) {
  86. // check for nested hashes and convert to Google API options
  87. var newOptions = options;
  88. newOptions.doNotConverStringToNumber = true;
  89. output = getOptions(output, newOptions);
  90. }
  91. } catch(err2) {
  92. // 3. Google Map Object function Expression. i.e. LatLng(80,-49)
  93. if (input.match(/^[A-Z][a-zA-Z0-9]+\(.*\)$/)) {
  94. try {
  95. var exp = "new google.maps."+input;
  96. output = eval(exp); /* jshint ignore:line */
  97. } catch(e) {
  98. output = input;
  99. }
  100. // 4. Google Map Object constant Expression. i.e. MayTypeId.HYBRID
  101. } else if (input.match(/^([A-Z][a-zA-Z0-9]+)\.([A-Z]+)$/)) {
  102. try {
  103. var matches = input.match(/^([A-Z][a-zA-Z0-9]+)\.([A-Z]+)$/);
  104. output = google.maps[matches[1]][matches[2]];
  105. } catch(e) {
  106. output = input;
  107. }
  108. // 5. Google Map Object constant Expression. i.e. HYBRID
  109. } else if (input.match(/^[A-Z]+$/)) {
  110. try {
  111. var capitalizedKey = options.key.charAt(0).toUpperCase() +
  112. options.key.slice(1);
  113. if (options.key.match(/temperatureUnit|windSpeedUnit|labelColor/)) {
  114. capitalizedKey = capitalizedKey.replace(/s$/,"");
  115. output = google.maps.weather[capitalizedKey][input];
  116. } else {
  117. output = google.maps[capitalizedKey][input];
  118. }
  119. } catch(e) {
  120. output = input;
  121. }
  122. // 6. Date Object as ISO String
  123. } else if (input.match(isoDateRE)) {
  124. try {
  125. output = new Date(input);
  126. } catch(e) {
  127. output = input;
  128. }
  129. // 7. evaluate dynamically bound values
  130. } else if (input.match(new RegExp('^' + escapeRegExp(exprStartSymbol))) && options.scope) {
  131. try {
  132. var expr = input.replace(new RegExp(escapeRegExp(exprStartSymbol)),'').replace(new RegExp(escapeRegExp(exprEndSymbol), 'g'),'');
  133. output = options.scope.$eval(expr);
  134. } catch (err) {
  135. output = input;
  136. }
  137. } else {
  138. output = input;
  139. }
  140. } // catch(err2)
  141. } // catch(err)
  142. // convert output more for center and position
  143. if (
  144. (options.key == 'center' || options.key == 'position') &&
  145. output instanceof Array
  146. ) {
  147. output = new google.maps.LatLng(output[0], output[1]);
  148. }
  149. // convert output more for shape bounds
  150. if (options.key == 'bounds' && output instanceof Array) {
  151. output = new google.maps.LatLngBounds(output[0], output[1]);
  152. }
  153. // convert output more for shape icons
  154. if (options.key == 'icons' && output instanceof Array) {
  155. for (var i=0; i<output.length; i++) {
  156. var el = output[i];
  157. if (el.icon.path.match(/^[A-Z_]+$/)) {
  158. el.icon.path = google.maps.SymbolPath[el.icon.path];
  159. }
  160. }
  161. }
  162. // convert output more for marker icon
  163. if (options.key == 'icon' && output instanceof Object) {
  164. if ((""+output.path).match(/^[A-Z_]+$/)) {
  165. output.path = google.maps.SymbolPath[output.path];
  166. }
  167. for (var key in output) { //jshint ignore:line
  168. var arr = output[key];
  169. if (key == "anchor" || key == "origin" || key == "labelOrigin") {
  170. output[key] = new google.maps.Point(arr[0], arr[1]);
  171. } else if (key == "size" || key == "scaledSize") {
  172. output[key] = new google.maps.Size(arr[0], arr[1]);
  173. }
  174. }
  175. }
  176. return output;
  177. };
  178. var getAttrsToObserve = function(attrs) {
  179. var attrsToObserve = [];
  180. var exprRegExp = new RegExp(escapeRegExp(exprStartSymbol) + '.*' + escapeRegExp(exprEndSymbol), 'g');
  181. if (!attrs.noWatcher) {
  182. for (var attrName in attrs) { //jshint ignore:line
  183. var attrValue = attrs[attrName];
  184. if (attrValue && attrValue.match(exprRegExp)) { // if attr value is {{..}}
  185. attrsToObserve.push(camelCaseFilter(attrName));
  186. }
  187. }
  188. }
  189. return attrsToObserve;
  190. };
  191. /**
  192. * filters attributes by skipping angularjs methods $.. $$..
  193. * @memberof Attr2MapOptions
  194. * @param {Hash} attrs tag attributes
  195. * @returns {Hash} filterd attributes
  196. */
  197. var filter = function(attrs) {
  198. var options = {};
  199. for(var key in attrs) {
  200. if (key.match(/^\$/) || key.match(/^ng[A-Z]/)) {
  201. void(0);
  202. } else {
  203. options[key] = attrs[key];
  204. }
  205. }
  206. return options;
  207. };
  208. /**
  209. * converts attributes hash to Google Maps API v3 options
  210. * ```
  211. * . converts numbers to number
  212. * . converts class-like string to google maps instance
  213. * i.e. `LatLng(1,1)` to `new google.maps.LatLng(1,1)`
  214. * . converts constant-like string to google maps constant
  215. * i.e. `MapTypeId.HYBRID` to `google.maps.MapTypeId.HYBRID`
  216. * i.e. `HYBRID"` to `google.maps.MapTypeId.HYBRID`
  217. * ```
  218. * @memberof Attr2MapOptions
  219. * @param {Hash} attrs tag attributes
  220. * @param {Hash} options
  221. * @returns {Hash} options converted attributess
  222. */
  223. var getOptions = function(attrs, params) {
  224. params = params || {};
  225. var options = {};
  226. for(var key in attrs) {
  227. if (attrs[key] || attrs[key] === 0) {
  228. if (key.match(/^on[A-Z]/)) { //skip events, i.e. on-click
  229. continue;
  230. } else if (key.match(/ControlOptions$/)) { // skip controlOptions
  231. continue;
  232. } else {
  233. // nested conversions need to be typechecked
  234. // (non-strings are fully converted)
  235. if (typeof attrs[key] !== 'string') {
  236. options[key] = attrs[key];
  237. } else {
  238. if (params.doNotConverStringToNumber &&
  239. attrs[key].match(/^[0-9]+$/)
  240. ) {
  241. options[key] = attrs[key];
  242. } else {
  243. options[key] = toOptionValue(attrs[key], {key: key, scope: params.scope});
  244. }
  245. }
  246. }
  247. } // if (attrs[key])
  248. } // for(var key in attrs)
  249. return options;
  250. };
  251. /**
  252. * converts attributes hash to scope-specific event function
  253. * @memberof Attr2MapOptions
  254. * @param {scope} scope angularjs scope
  255. * @param {Hash} attrs tag attributes
  256. * @returns {Hash} events converted events
  257. */
  258. var getEvents = function(scope, attrs) {
  259. var events = {};
  260. var toLowercaseFunc = function($1){
  261. return "_"+$1.toLowerCase();
  262. };
  263. var EventFunc = function(attrValue) {
  264. // funcName(argsStr)
  265. var matches = attrValue.match(/([^\(]+)\(([^\)]*)\)/);
  266. var funcName = matches[1];
  267. var argsStr = matches[2].replace(/event[ ,]*/,''); //remove string 'event'
  268. var argsExpr = $parse("["+argsStr+"]"); //for perf when triggering event
  269. return function(event) {
  270. var args = argsExpr(scope); //get args here to pass updated model values
  271. function index(obj,i) {return obj[i];}
  272. var f = funcName.split('.').reduce(index, scope);
  273. f && f.apply(this, [event].concat(args));
  274. $timeout( function() {
  275. scope.$apply();
  276. });
  277. };
  278. };
  279. for(var key in attrs) {
  280. if (attrs[key]) {
  281. if (!key.match(/^on[A-Z]/)) { //skip if not events
  282. continue;
  283. }
  284. //get event name as underscored. i.e. zoom_changed
  285. var eventName = key.replace(/^on/,'');
  286. eventName = eventName.charAt(0).toLowerCase() + eventName.slice(1);
  287. eventName = eventName.replace(/([A-Z])/g, toLowercaseFunc);
  288. var attrValue = attrs[key];
  289. events[eventName] = new EventFunc(attrValue);
  290. }
  291. }
  292. return events;
  293. };
  294. /**
  295. * control means map controls, i.e streetview, pan, etc, not a general control
  296. * @memberof Attr2MapOptions
  297. * @param {Hash} filtered filtered tag attributes
  298. * @returns {Hash} Google Map options
  299. */
  300. var getControlOptions = function(filtered) {
  301. var controlOptions = {};
  302. if (typeof filtered != 'object') {
  303. return false;
  304. }
  305. for (var attr in filtered) {
  306. if (filtered[attr]) {
  307. if (!attr.match(/(.*)ControlOptions$/)) {
  308. continue; // if not controlOptions, skip it
  309. }
  310. //change invalid json to valid one, i.e. {foo:1} to {"foo": 1}
  311. var orgValue = filtered[attr];
  312. var newValue = orgValue.replace(/'/g, '"');
  313. newValue = newValue.replace(/([^"]+)|("[^"]+")/g, function($0, $1, $2) {
  314. if ($1) {
  315. return $1.replace(/([a-zA-Z0-9]+?):/g, '"$1":');
  316. } else {
  317. return $2;
  318. }
  319. });
  320. try {
  321. var options = JSON.parse(newValue);
  322. for (var key in options) { //assign the right values
  323. if (options[key]) {
  324. var value = options[key];
  325. if (typeof value === 'string') {
  326. value = value.toUpperCase();
  327. } else if (key === "mapTypeIds") {
  328. value = value.map( function(str) {
  329. if (str.match(/^[A-Z]+$/)) { // if constant
  330. return google.maps.MapTypeId[str.toUpperCase()];
  331. } else { // else, custom map-type
  332. return str;
  333. }
  334. });
  335. }
  336. if (key === "style") {
  337. var str = attr.charAt(0).toUpperCase() + attr.slice(1);
  338. var objName = str.replace(/Options$/,'')+"Style";
  339. options[key] = google.maps[objName][value];
  340. } else if (key === "position") {
  341. options[key] = google.maps.ControlPosition[value];
  342. } else {
  343. options[key] = value;
  344. }
  345. }
  346. }
  347. controlOptions[attr] = options;
  348. } catch (e) {
  349. console.error('invald option for', attr, newValue, e, e.stack);
  350. }
  351. }
  352. } // for
  353. return controlOptions;
  354. };
  355. return {
  356. filter: filter,
  357. getOptions: getOptions,
  358. getEvents: getEvents,
  359. getControlOptions: getControlOptions,
  360. toOptionValue: toOptionValue,
  361. getAttrsToObserve: getAttrsToObserve,
  362. orgAttributes: orgAttributes
  363. }; // return
  364. };
  365. Attr2MapOptions.$inject= [
  366. '$parse', '$timeout', '$log', '$interpolate', 'NavigatorGeolocation', 'GeoCoder',
  367. 'camelCaseFilter', 'jsonizeFilter', 'escapeRegexpFilter'
  368. ];
  369. angular.module('ngMap').service('Attr2MapOptions', Attr2MapOptions);
  370. })();