google-debug.js 53 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447
  1. /**
  2. * See https://developers.google.com/api-client-library/javascript/
  3. * See https://developers.google.com/apis-explorer/#p/
  4. *
  5. * googleApis: { 'calendar': { version: 'v3' } }
  6. */
  7. Ext.define('Ext.google.ux.Client', {
  8. extend: 'Ext.Mixin',
  9. mixins: [
  10. 'Ext.mixin.Mashup'
  11. ],
  12. requiredScripts: [
  13. '//apis.google.com/js/client.js?onload=_ext_google_ux_client_initialize_'
  14. ],
  15. statics: {
  16. getApiVersion: function(api) {
  17. var library = this.libraries[api];
  18. return library && library.state == 2 ? library.version : null;
  19. }
  20. },
  21. mixinConfig: {
  22. extended: function(baseClass, derivedClass, classBody) {
  23. this.load(classBody.googleApis);
  24. }
  25. },
  26. onClassMixedIn: function(cls) {
  27. this.load(cls.prototype.googleApis);
  28. },
  29. privates: {
  30. statics: {
  31. /**
  32. * @property {Boolean} initialized
  33. * `true` if the google client has been loaded and initialized.
  34. * @private
  35. */
  36. initialized: false,
  37. /**
  38. * @property {Boolean} blocked
  39. * `true` if this class has blocked Ext.env.Ready, else false.
  40. * @private
  41. */
  42. blocked: false,
  43. /**
  44. * @property {Number} loading
  45. * Keep track of how many libraries are loading.
  46. * @private
  47. */
  48. loading: 0,
  49. /**
  50. * @property {Object} libraries
  51. * Information about required libraries.
  52. * { `api_name`: { version: string, state: int }
  53. * state: 0 (pending), 1 (loading), 2 (loaded)
  54. * Example: { calendar: { version: 'v1', state: 1 } }
  55. * @private
  56. */
  57. libraries: {},
  58. load: function(apis) {
  59. var libraries = this.libraries,
  60. version, library;
  61. if (!Ext.isObject(apis)) {
  62. return;
  63. }
  64. Ext.Object.each(apis, function(api, cfg) {
  65. version = cfg.version || 'v1';
  66. library = libraries[api];
  67. if (!Ext.isDefined(library)) {
  68. libraries[api] = {
  69. version: version,
  70. state: 0
  71. };
  72. } else if (library.version !== version) {
  73. Ext.log.error('Google API: failed to load version "' + version + '" of the', '"' + api + '" API: "' + library.version + '" already loaded.');
  74. }
  75. });
  76. this.refresh();
  77. },
  78. refresh: function() {
  79. var me = this;
  80. if (!me.initialized) {
  81. return;
  82. }
  83. if (!me.blocked) {
  84. Ext.env.Ready.block();
  85. me.blocked = true;
  86. }
  87. Ext.Object.each(me.libraries, function(api, library) {
  88. if (library.state == 0) {
  89. library.state = 1;
  90. // loading
  91. gapi.client.load(api, library.version, function() {
  92. library.state = 2;
  93. // loaded
  94. if (!--me.loading) {
  95. me.refresh();
  96. }
  97. });
  98. }
  99. if (library.state == 1) {
  100. me.loading++;
  101. }
  102. });
  103. if (!me.loading && me.blocked) {
  104. Ext.env.Ready.unblock();
  105. me.blocked = false;
  106. }
  107. },
  108. initialize: function() {
  109. this.initialized = true;
  110. this.refresh();
  111. }
  112. }
  113. }
  114. });
  115. // See https://developers.google.com/api-client-library/javascript/features/authentication
  116. _ext_google_ux_client_initialize_ = function() {
  117. gapi.auth.init(function() {
  118. Ext.google.ux.Client.initialize();
  119. });
  120. };
  121. /**
  122. * Base proxy for accessing **[Google API](https://developers.google.com/apis-explorer/#p/)** resources.
  123. */
  124. Ext.define('Ext.google.data.AbstractProxy', {
  125. extend: 'Ext.data.proxy.Server',
  126. mixins: [
  127. 'Ext.google.ux.Client'
  128. ],
  129. // TODO: Batch actions
  130. // https://developers.google.com/api-client-library/javascript/features/batch
  131. /**
  132. * @cfg batchActions
  133. * @inheritdoc
  134. */
  135. batchActions: false,
  136. /**
  137. * @cfg reader
  138. * @inheritdoc
  139. */
  140. reader: {
  141. type: 'json',
  142. rootProperty: 'items',
  143. messageProperty: 'error'
  144. },
  145. /**
  146. * @method buildApiRequests
  147. * Returns a list of API request(s), **not executed**.
  148. * @param {Ext.data.Request} request The data request
  149. * @return {Object[]} API request(s)
  150. * @abstract
  151. */
  152. /**
  153. * @protected
  154. * @inheritdoc
  155. */
  156. doRequest: function(operation) {
  157. var me = this,
  158. request = me.buildRequest(operation),
  159. writer = me.getWriter(),
  160. error = false;
  161. if (writer && operation.allowWrite()) {
  162. request = writer.write(request);
  163. }
  164. me.execute(me.buildApiRequests(request)).then(function(response) {
  165. me.processApiResponse(operation, request, response);
  166. });
  167. return request;
  168. },
  169. /**
  170. * @method buildUrl
  171. * @protected
  172. * @inheritdoc
  173. */
  174. buildUrl: function(request) {
  175. return '';
  176. },
  177. privates: {
  178. execute: function(requests) {
  179. requests = [].concat(requests);
  180. // BUG: when using the gapi batch feature and trying to modify the same event
  181. // more than one time, the request partially fails and returns a 502 error.
  182. // See https://code.google.com/a/google.com/p/apps-api-issues/issues/detail?id=4528
  183. // TODO: use the following code once fixed! also check that it doesn't break
  184. // maxResults limit for event list requests.
  185. //var batch = gapi.client.newBatch();
  186. //Ext.Array.each(requests, function(r, i) { batch.add(r, { id: i }); });
  187. //return batch.execute();
  188. // WORKAROUND for the issue above (REMOVE ME)
  189. var results = [];
  190. return Ext.Array.reduce(requests, function(sequence, r) {
  191. return sequence.then(function() {
  192. return r.then(function(result) {
  193. results.push(result);
  194. });
  195. });
  196. }, Ext.Deferred.resolved()).then(function() {
  197. return {
  198. result: results
  199. };
  200. });
  201. },
  202. processApiResponse: function(operation, request, responses) {
  203. var error = false,
  204. results = [];
  205. // responses.result is not a regular Object, can't iterate with Ext.Object.each()
  206. Ext.each(Object.keys(responses.result), function(index) {
  207. var result = responses.result[index].result;
  208. if (result.error) {
  209. error = result.error.message;
  210. return false;
  211. }
  212. results.push(result);
  213. });
  214. this.processResponse(true, operation, request, {
  215. results: error ? [] : results,
  216. success: !error,
  217. error: error
  218. });
  219. },
  220. sanitizeItems: function(items) {
  221. var results = [],
  222. ids = [];
  223. // Batch can return different versions of the same record, only keep the last one.
  224. Ext.Array.each(items, function(item) {
  225. if (!Ext.Array.contains(ids, item.id)) {
  226. results.push(item);
  227. ids.push(item.id);
  228. }
  229. }, this, true);
  230. return results;
  231. }
  232. }
  233. });
  234. /**
  235. * Proxy to access Google **[event resources](https://developers.google.com/google-apps/calendar/v3/reference/events)**.
  236. */
  237. Ext.define('Ext.google.data.EventsProxy', {
  238. extend: 'Ext.google.data.AbstractProxy',
  239. alias: 'proxy.google-events',
  240. googleApis: {
  241. 'calendar': {
  242. version: 'v3'
  243. }
  244. },
  245. /**
  246. * @method buildApiRequests
  247. * @protected
  248. * @inheritdoc
  249. */
  250. buildApiRequests: function(request) {
  251. var me = this,
  252. action = request.getAction();
  253. switch (action) {
  254. case 'read':
  255. return me.buildReadApiRequests(request);
  256. case 'create':
  257. return me.buildCreateApiRequests(request);
  258. case 'update':
  259. return me.buildUpdateApiRequests(request);
  260. case 'destroy':
  261. return me.buildDestroyApiRequests(request);
  262. default:
  263. Ext.raise('unsupported request: events.' + action);
  264. return null;
  265. }
  266. },
  267. /**
  268. * @method extractResponseData
  269. * @protected
  270. * @inheritdoc
  271. */
  272. extractResponseData: function(response) {
  273. var me = this,
  274. data = me.callParent(arguments),
  275. items = [];
  276. Ext.each(data.results, function(result) {
  277. switch (result.kind) {
  278. case 'calendar#events':
  279. items = items.concat(result.items.map(me.fromApiEvent.bind(me)));
  280. break;
  281. case 'calendar#event':
  282. items.push(me.fromApiEvent(result));
  283. break;
  284. default:
  285. break;
  286. }
  287. });
  288. return {
  289. items: me.sanitizeItems(items),
  290. success: data.success,
  291. error: data.error
  292. };
  293. },
  294. privates: {
  295. // https://developers.google.com/google-apps/calendar/v3/reference/events
  296. toApiEvent: function(data, allDay) {
  297. var res = {};
  298. Ext.Object.each(data, function(key, value) {
  299. var dateTime = null,
  300. date = null;
  301. switch (key) {
  302. case 'calendarId':
  303. case 'description':
  304. res[key] = value;
  305. break;
  306. case 'id':
  307. res.eventId = value;
  308. break;
  309. case 'title':
  310. res.summary = value;
  311. break;
  312. case 'startDate':
  313. case 'endDate':
  314. if (allDay) {
  315. date = new Date(value);
  316. date.setHours(0, -date.getTimezoneOffset());
  317. date = Ext.Date.format(date, 'Y-m-d');
  318. } else {
  319. dateTime = Ext.Date.format(new Date(value), 'c');
  320. };
  321. // Need to explicitly set unused date field to null
  322. // http://stackoverflow.com/a/35658479
  323. res[key.slice(0, -4)] = {
  324. date: date,
  325. dateTime: dateTime
  326. };
  327. break;
  328. default:
  329. break;
  330. }
  331. });
  332. return res;
  333. },
  334. // https://developers.google.com/google-apps/calendar/v3/reference/events
  335. fromApiEvent: function(data) {
  336. var res = {
  337. allDay: true
  338. };
  339. Ext.Object.each(data, function(key, value) {
  340. var date, offset, allDay;
  341. switch (key) {
  342. case 'id':
  343. case 'description':
  344. res[key] = value;
  345. break;
  346. case 'summary':
  347. res.title = value;
  348. break;
  349. case 'start':
  350. case 'end':
  351. date = Ext.Date.parse(value.dateTime || value.date, 'C');
  352. offset = date.getTimezoneOffset();
  353. allDay = !!value.date;
  354. // IMPORTANT: all day events must have their time equal to 00:00 GMT
  355. if (allDay && offset !== 0) {
  356. date.setHours(0, -offset);
  357. };
  358. res[key + 'Date'] = date;
  359. res.allDay = res.allDay && allDay;
  360. break;
  361. default:
  362. break;
  363. }
  364. });
  365. return res;
  366. },
  367. // See https://developers.google.com/google-apps/calendar/v3/reference/events/list
  368. buildReadApiRequests: function(request) {
  369. // by default, the API returns max 250 events per request, up to 2500. Since we
  370. // don't have control on the min & max requested times, and don't know how many
  371. // events will be returned, let's split requests per 3 months and set maxResults
  372. // to 2500 (~26 events per day - should be enough!?).
  373. var rparams = request.getParams(),
  374. start = new Date(rparams.startDate),
  375. end = new Date(rparams.endDate),
  376. requests = [],
  377. next;
  378. while (start < end) {
  379. next = Ext.Date.add(start, Ext.Date.MONTH, 3);
  380. if (next > end) {
  381. next = end;
  382. }
  383. requests.push(gapi.client.calendar.events.list({
  384. calendarId: rparams.calendar,
  385. timeMin: Ext.Date.format(start, 'C'),
  386. timeMax: Ext.Date.format(next, 'C'),
  387. singleEvents: true,
  388. maxResults: 2500
  389. }));
  390. start = next;
  391. }
  392. return requests;
  393. },
  394. // https://developers.google.com/google-apps/calendar/v3/reference/events/insert
  395. buildCreateApiRequests: function(request) {
  396. var record = request.getRecords()[0];
  397. // batch not currently supported!
  398. return gapi.client.calendar.events.insert(this.toApiEvent(request.getJsonData(), record.get('allDay')));
  399. },
  400. // https://developers.google.com/google-apps/calendar/v3/reference/events/patch
  401. // https://developers.google.com/google-apps/calendar/v3/reference/events/move
  402. buildUpdateApiRequests: function(request) {
  403. var record = request.getRecords()[0],
  404. // batch not currently supported!
  405. params = this.toApiEvent(request.getJsonData(), record.get('allDay')),
  406. prevCalendarId = record.getModified('calendarId'),
  407. currCalendarId = record.get('calendarId'),
  408. eventId = record.getId(),
  409. requests = [];
  410. // REQUIRED fields for the patch API
  411. params.calendarId = currCalendarId;
  412. params.eventId = eventId;
  413. if (prevCalendarId && prevCalendarId !== currCalendarId) {
  414. // The event has been moved to another calendar
  415. requests.push(gapi.client.calendar.events.move({
  416. destination: currCalendarId,
  417. calendarId: prevCalendarId,
  418. eventId: eventId
  419. }));
  420. }
  421. if (Object.keys(params).length > 2) {
  422. // There is fields to update other than the calendarId + eventId
  423. requests.push(gapi.client.calendar.events.patch(params));
  424. }
  425. return requests;
  426. },
  427. // https://developers.google.com/google-apps/calendar/v3/reference/events/delete
  428. buildDestroyApiRequests: function(request) {
  429. var record = request.getRecords()[0];
  430. // batch not currently supported!
  431. data = request.getJsonData();
  432. // The current calendar implementation nullifies the calendar ID before deleting
  433. // it, so let's get it from the previous values if not anymore in data.
  434. data.calendarId = data.calendarId || record.get('calendarId') || record.getPrevious('calendarId');
  435. // ['delete'] to make YUI happy
  436. return gapi.client.calendar.events['delete']({
  437. 'calendarId': data.calendarId,
  438. 'eventId': data.id
  439. });
  440. }
  441. }
  442. });
  443. /**
  444. * Proxy to access Google **[calendar resources](https://developers.google.com/google-apps/calendar/v3/reference/calendarList)**.
  445. */
  446. Ext.define('Ext.google.data.CalendarsProxy', {
  447. extend: 'Ext.google.data.AbstractProxy',
  448. alias: 'proxy.google-calendars',
  449. requires: [
  450. 'Ext.google.data.EventsProxy'
  451. ],
  452. googleApis: {
  453. 'calendar': {
  454. version: 'v3'
  455. }
  456. },
  457. /**
  458. * @method buildApiRequests
  459. * @protected
  460. * @inheritdoc
  461. */
  462. buildApiRequests: function(request) {
  463. var me = this,
  464. action = request.getAction();
  465. switch (action) {
  466. case 'read':
  467. return me.buildReadApiRequests(request);
  468. case 'update':
  469. return me.buildUpdateApiRequests(request);
  470. default:
  471. Ext.raise('unsupported request: calendars.' + action);
  472. return null;
  473. }
  474. },
  475. /**
  476. * @method extractResponseData
  477. * @protected
  478. * @inheritdoc
  479. */
  480. extractResponseData: function(response) {
  481. var me = this,
  482. data = me.callParent(arguments),
  483. items = [];
  484. // We assume that the response contains only results of the same kind.
  485. Ext.each(data.results, function(result) {
  486. switch (result.kind) {
  487. case 'calendar#calendarList':
  488. items = items.concat(result.items.map(me.fromApiCalendar.bind(me)));
  489. break;
  490. default:
  491. break;
  492. }
  493. });
  494. return {
  495. items: me.sanitizeItems(items),
  496. success: data.success,
  497. error: data.error
  498. };
  499. },
  500. privates: {
  501. // https://developers.google.com/google-apps/calendar/v3/reference/calendarList#resource
  502. toApiCalendar: function(data) {
  503. var res = {};
  504. Ext.Object.each(data, function(key, value) {
  505. switch (key) {
  506. case 'id':
  507. res.calendarId = value;
  508. break;
  509. case 'hidden':
  510. res.selected = !value;
  511. break;
  512. default:
  513. break;
  514. }
  515. });
  516. return res;
  517. },
  518. // https://developers.google.com/google-apps/calendar/v3/reference/calendarList#resource
  519. fromApiCalendar: function(data) {
  520. var record = {
  521. hidden: !data.selected,
  522. editable: false,
  523. eventStore: {
  524. autoSync: true,
  525. proxy: {
  526. type: 'google-events',
  527. resourceTypes: 'events'
  528. }
  529. }
  530. };
  531. Ext.Object.each(data, function(key, value) {
  532. switch (key) {
  533. case 'id':
  534. case 'description':
  535. record[key] = value;
  536. break;
  537. case 'backgroundColor':
  538. record.color = value;
  539. break;
  540. case 'summary':
  541. record.title = value;
  542. break;
  543. case 'accessRole':
  544. record.editable = (value == 'owner' || value == 'writer');
  545. break;
  546. default:
  547. break;
  548. }
  549. });
  550. return record;
  551. },
  552. // https://developers.google.com/google-apps/calendar/v3/reference/calendarList/list
  553. buildReadApiRequests: function(request) {
  554. return gapi.client.calendar.calendarList.list();
  555. },
  556. // https://developers.google.com/google-apps/calendar/v3/reference/calendarList/patch
  557. buildUpdateApiRequests: function(request) {
  558. var data = this.toApiCalendar(request.getJsonData());
  559. return gapi.client.calendar.calendarList.patch(data);
  560. }
  561. }
  562. });
  563. /**
  564. * This base class can be used by derived classes to dynamically require Google API's.
  565. */
  566. Ext.define('Ext.ux.google.Api', {
  567. mixins: [
  568. 'Ext.mixin.Mashup'
  569. ],
  570. requiredScripts: [
  571. '//www.google.com/jsapi'
  572. ],
  573. statics: {
  574. loadedModules: {}
  575. },
  576. /*
  577. * feeds: [ callback1, callback2, .... ] transitions to -> feeds : true (when complete)
  578. */
  579. onClassExtended: function(cls, data, hooks) {
  580. var onBeforeClassCreated = hooks.onBeforeCreated,
  581. Api = this;
  582. // the Ext.ux.google.Api class
  583. hooks.onBeforeCreated = function(cls, data) {
  584. var me = this,
  585. apis = [],
  586. requiresGoogle = Ext.Array.from(data.requiresGoogle),
  587. loadedModules = Api.loadedModules,
  588. remaining = 0,
  589. callback = function() {
  590. if (!--remaining) {
  591. onBeforeClassCreated.call(me, cls, data, hooks);
  592. }
  593. Ext.env.Ready.unblock();
  594. },
  595. api, i, length;
  596. /*
  597. * requiresGoogle: [
  598. * 'feeds',
  599. * { api: 'feeds', version: '1.x',
  600. * callback : fn, nocss : true } //optionals
  601. * ]
  602. */
  603. length = requiresGoogle.length;
  604. for (i = 0; i < length; ++i) {
  605. if (Ext.isString(api = requiresGoogle[i])) {
  606. apis.push({
  607. api: api
  608. });
  609. } else if (Ext.isObject(api)) {
  610. apis.push(Ext.apply({}, api));
  611. }
  612. }
  613. Ext.each(apis, function(api) {
  614. var name = api.api,
  615. version = String(api.version || '1.x'),
  616. module = loadedModules[name];
  617. if (!module) {
  618. ++remaining;
  619. Ext.env.Ready.block();
  620. loadedModules[name] = module = [
  621. callback
  622. ].concat(api.callback || []);
  623. delete api.api;
  624. delete api.version;
  625. if (!window.google) {
  626. Ext.raise("'google' is not defined.");
  627. return false;
  628. }
  629. google.load(name, version, Ext.applyIf({
  630. callback: function() {
  631. loadedModules[name] = true;
  632. for (var n = module.length; n-- > 0; ) {
  633. module[n]();
  634. }
  635. }
  636. }, //iterate callbacks in reverse
  637. api));
  638. } else if (module !== true) {
  639. module.push(callback);
  640. }
  641. });
  642. if (!remaining) {
  643. onBeforeClassCreated.call(me, cls, data, hooks);
  644. }
  645. };
  646. }
  647. });
  648. /**
  649. * Wraps a Google Map in an Ext.Component using the [Google Maps API](http://code.google.com/apis/maps/documentation/v3/introduction.html).
  650. *
  651. * This component will automatically include the google maps API script from:
  652. * `//maps.google.com/maps/api/js`
  653. *
  654. * ## Example
  655. *
  656. * Ext.Viewport.add({
  657. * xtype: 'map',
  658. * useCurrentLocation: true
  659. * });
  660. */
  661. Ext.define('Ext.ux.google.Map', {
  662. extend: 'Ext.Container',
  663. xtype: [
  664. 'map',
  665. 'google-map'
  666. ],
  667. alternateClassName: 'Ext.Map',
  668. requires: [
  669. 'Ext.util.Geolocation'
  670. ],
  671. mixins: [
  672. 'Ext.mixin.Mashup'
  673. ],
  674. requires: [
  675. 'Ext.data.StoreManager'
  676. ],
  677. requiredScripts: [
  678. '//maps.googleapis.com/maps/api/js{options}'
  679. ],
  680. isMap: true,
  681. /**
  682. * @event maprender
  683. * Fired when Map initially rendered.
  684. * @param {Ext.ux.google.Map} this
  685. * @param {google.maps.Map} map The rendered google.map.Map instance
  686. */
  687. /**
  688. * @event centerchange
  689. * Fired when map is panned around.
  690. * @param {Ext.ux.google.Map} this
  691. * @param {google.maps.Map} map The rendered google.map.Map instance
  692. * @param {google.maps.LatLng} center The current LatLng center of the map
  693. */
  694. /**
  695. * @event typechange
  696. * Fired when display type of the map changes.
  697. * @param {Ext.ux.google.Map} this
  698. * @param {google.maps.Map} map The rendered google.map.Map instance
  699. * @param {Number} mapType The current display type of the map
  700. */
  701. /**
  702. * @event zoomchange
  703. * Fired when map is zoomed.
  704. * @param {Ext.ux.google.Map} this
  705. * @param {google.maps.Map} map The rendered google.map.Map instance
  706. * @param {Number} zoomLevel The current zoom level of the map
  707. */
  708. /**
  709. * @event markerclick
  710. * Fired when the marker icon was clicked.
  711. * @param {Ext.ux.google.Map} map This map instance
  712. * @param {Object} info Information about this event
  713. * @param {Number} info.index The index of the marker record
  714. * @param {Ext.data.Model} info.record The record associated to the marker
  715. * @param {google.maps.Marker} info.marker The [Google Map marker](https://developers.google.com/maps/documentation/javascript/3.exp/reference#Marker)
  716. * @param {google.maps.MouseEvent} info.event The [Google Map event](https://developers.google.com/maps/documentation/javascript/3.exp/reference#MouseEvent)
  717. */
  718. /**
  719. * @event markerdblclick
  720. * Fired when the marker icon was double clicked.
  721. * @param {Ext.ux.google.Map} map This map instance
  722. * @param {Object} info Information about this event
  723. * @param {Number} info.index The index of the marker record
  724. * @param {Ext.data.Model} info.record The record associated to the marker
  725. * @param {google.maps.Marker} info.marker The [Google Map marker](https://developers.google.com/maps/documentation/javascript/3.exp/reference#Marker)
  726. * @param {google.maps.MouseEvent} info.event The [Google Map event](https://developers.google.com/maps/documentation/javascript/3.exp/reference#MouseEvent)
  727. */
  728. /**
  729. * @event markerdrag
  730. * Repeatedly fired while the user drags the marker.
  731. * @param {Ext.ux.google.Map} map This map instance
  732. * @param {Object} info Information about this event
  733. * @param {Number} info.index The index of the marker record
  734. * @param {Ext.data.Model} info.record The record associated to the marker
  735. * @param {google.maps.Marker} info.marker The [Google Map marker](https://developers.google.com/maps/documentation/javascript/3.exp/reference#Marker)
  736. * @param {google.maps.MouseEvent} info.event The [Google Map event](https://developers.google.com/maps/documentation/javascript/3.exp/reference#MouseEvent)
  737. */
  738. /**
  739. * @event markerdragend
  740. * Fired when the user stops dragging the marker.
  741. * @param {Ext.ux.google.Map} map This map instance
  742. * @param {Object} info Information about this event
  743. * @param {Number} info.index The index of the marker record
  744. * @param {Ext.data.Model} info.record The record associated to the marker
  745. * @param {google.maps.Marker} info.marker The [Google Map marker](https://developers.google.com/maps/documentation/javascript/3.exp/reference#Marker)
  746. * @param {google.maps.MouseEvent} info.event The [Google Map event](https://developers.google.com/maps/documentation/javascript/3.exp/reference#MouseEvent)
  747. */
  748. /**
  749. * @event markerdragstart
  750. * Fired when the user starts dragging the marker.
  751. * @param {Ext.ux.google.Map} map This map instance
  752. * @param {Object} info Information about this event
  753. * @param {Number} info.index The index of the marker record
  754. * @param {Ext.data.Model} info.record The record associated to the marker
  755. * @param {google.maps.Marker} info.marker The [Google Map marker](https://developers.google.com/maps/documentation/javascript/3.exp/reference#Marker)
  756. * @param {google.maps.MouseEvent} info.event The [Google Map event](https://developers.google.com/maps/documentation/javascript/3.exp/reference#MouseEvent)
  757. */
  758. /**
  759. * @event markermousedown
  760. * Fired for a mousedown on the marker.
  761. * @param {Ext.ux.google.Map} map This map instance
  762. * @param {Object} info Information about this event
  763. * @param {Number} info.index The index of the marker record
  764. * @param {Ext.data.Model} info.record The record associated to the marker
  765. * @param {google.maps.Marker} info.marker The [Google Map marker](https://developers.google.com/maps/documentation/javascript/3.exp/reference#Marker)
  766. * @param {google.maps.MouseEvent} info.event The [Google Map event](https://developers.google.com/maps/documentation/javascript/3.exp/reference#MouseEvent)
  767. */
  768. /**
  769. * @event markermouseout
  770. * Fired when the mouse leaves the area of the marker icon.
  771. * @param {Ext.ux.google.Map} map This map instance
  772. * @param {Object} info Information about this event
  773. * @param {Number} info.index The index of the marker record
  774. * @param {Ext.data.Model} info.record The record associated to the marker
  775. * @param {google.maps.Marker} info.marker The [Google Map marker](https://developers.google.com/maps/documentation/javascript/3.exp/reference#Marker)
  776. * @param {google.maps.MouseEvent} info.event The [Google Map event](https://developers.google.com/maps/documentation/javascript/3.exp/reference#MouseEvent)
  777. */
  778. /**
  779. * @event markermouseover
  780. * Fired when the mouse enters the area of the marker icon.
  781. * @param {Ext.ux.google.Map} map This map instance
  782. * @param {Object} info Information about this event
  783. * @param {Number} info.index The index of the marker record
  784. * @param {Ext.data.Model} info.record The record associated to the marker
  785. * @param {google.maps.Marker} info.marker The [Google Map marker](https://developers.google.com/maps/documentation/javascript/3.exp/reference#Marker)
  786. * @param {google.maps.MouseEvent} info.event The [Google Map event](https://developers.google.com/maps/documentation/javascript/3.exp/reference#MouseEvent)
  787. */
  788. /**
  789. * @event markermouseup
  790. * Fired for a mouseup on the marker.
  791. * @param {Ext.ux.google.Map} map This map instance
  792. * @param {Object} info Information about this event
  793. * @param {Number} info.index The index of the marker record
  794. * @param {Ext.data.Model} info.record The record associated to the marker
  795. * @param {google.maps.Marker} info.marker The [Google Map marker](https://developers.google.com/maps/documentation/javascript/3.exp/reference#Marker)
  796. * @param {google.maps.MouseEvent} info.event The [Google Map event](https://developers.google.com/maps/documentation/javascript/3.exp/reference#MouseEvent)
  797. */
  798. /**
  799. * @event markerrightclick
  800. * Fired for a rightclick on the marker.
  801. * @param {Ext.ux.google.Map} map This map instance
  802. * @param {Object} info Information about this event
  803. * @param {Number} info.index The index of the marker record
  804. * @param {Ext.data.Model} info.record The record associated to the marker
  805. * @param {google.maps.Marker} info.marker The [Google Map marker](https://developers.google.com/maps/documentation/javascript/3.exp/reference#Marker)
  806. * @param {google.maps.MouseEvent} info.event The [Google Map event](https://developers.google.com/maps/documentation/javascript/3.exp/reference#MouseEvent)
  807. */
  808. config: {
  809. /**
  810. * @cfg {Boolean/Ext.util.Geolocation} useCurrentLocation
  811. * Pass in true to center the map based on the geolocation coordinates or pass a
  812. * {@link Ext.util.Geolocation GeoLocation} config to have more control over your GeoLocation options
  813. * @accessor
  814. */
  815. useCurrentLocation: false,
  816. /**
  817. * @cfg {google.maps.Map} map
  818. * The wrapped map.
  819. * @accessor
  820. */
  821. map: null,
  822. /**
  823. * @cfg {Ext.util.Geolocation} geo
  824. * Geolocation provider for the map.
  825. * @accessor
  826. */
  827. geo: null,
  828. /**
  829. * @cfg {Object} mapOptions
  830. * MapOptions as specified by the Google Documentation:
  831. * [http://code.google.com/apis/maps/documentation/v3/reference.html](http://code.google.com/apis/maps/documentation/v3/reference.html)
  832. * @accessor
  833. */
  834. mapOptions: {},
  835. /**
  836. * @cfg {Object} mapListeners
  837. * Listeners for any Google Maps events specified by the Google Documentation:
  838. * [http://code.google.com/apis/maps/documentation/v3/reference.html](http://code.google.com/apis/maps/documentation/v3/reference.html)
  839. *
  840. * @accessor
  841. */
  842. mapListeners: null,
  843. /**
  844. * @cfg {Ext.data.Store/Object/Ext.data.Model[]/Ext.ux.google.map.Marker} markers
  845. * Can be either a Store instance, a configuration object that will be turned into a
  846. * store, an array of model or a single model (in which case a store will be created).
  847. * The Store is used to populate the set of markers that will be rendered in the map.
  848. * Marker options are read through the {@link #markerTemplate} config.
  849. */
  850. markers: null,
  851. /**
  852. * @cfg {Object/Ext.util.ObjectTemplate} markerTemplate
  853. * This is a template used to produce marker options from the {@link #markers} records.
  854. * See {@link Ext.ux.google.map.Marker} for details.
  855. */
  856. markerTemplate: {
  857. title: '{title}',
  858. position: '{position}',
  859. animation: '{animation}',
  860. // google.maps.Animation.DROP
  861. clickable: '{clickable}',
  862. draggable: '{draggable}',
  863. visible: '{visible}'
  864. }
  865. },
  866. baseCls: Ext.baseCSSPrefix + 'map',
  867. constructor: function(config) {
  868. this.callParent([
  869. config
  870. ]);
  871. if (!(window.google || {}).maps) {
  872. this.setHtml('Google Maps API is required');
  873. }
  874. },
  875. initialize: function() {
  876. this.callParent();
  877. this.initMap();
  878. this.on({
  879. painted: 'onPainted',
  880. scope: this
  881. });
  882. this.bodyElement.on('touchstart', 'onTouchStart', this);
  883. },
  884. initMap: function() {
  885. var map = this.getMap();
  886. if (!map) {
  887. var gm = (window.google || {}).maps;
  888. if (!gm) {
  889. return null;
  890. }
  891. var element = this.mapContainer,
  892. mapOptions = this.getMapOptions(),
  893. event = gm.event,
  894. me = this;
  895. //Remove the API Required div
  896. if (element.dom.firstChild) {
  897. Ext.fly(element.dom.firstChild).destroy();
  898. }
  899. if (Ext.os.is.iPad) {
  900. Ext.merge({
  901. navigationControlOptions: {
  902. style: gm.NavigationControlStyle.ZOOM_PAN
  903. }
  904. }, mapOptions);
  905. }
  906. mapOptions.mapTypeId = mapOptions.mapTypeId || gm.MapTypeId.ROADMAP;
  907. mapOptions.center = mapOptions.center || new gm.LatLng(37.381592, -122.135672);
  908. // Palo Alto
  909. if (mapOptions.center && mapOptions.center.latitude && !Ext.isFunction(mapOptions.center.lat)) {
  910. mapOptions.center = new gm.LatLng(mapOptions.center.latitude, mapOptions.center.longitude);
  911. }
  912. mapOptions.zoom = mapOptions.zoom || 12;
  913. map = new gm.Map(element.dom, mapOptions);
  914. this.setMap(map);
  915. event.addListener(map, 'zoom_changed', Ext.bind(me.onZoomChange, me));
  916. event.addListener(map, 'maptypeid_changed', Ext.bind(me.onTypeChange, me));
  917. event.addListener(map, 'center_changed', Ext.bind(me.onCenterChange, me));
  918. event.addListenerOnce(map, 'tilesloaded', Ext.bind(me.onTilesLoaded, me));
  919. this.addMapListeners();
  920. }
  921. return this.getMap();
  922. },
  923. // added for backwards compatibility for touch < 2.3
  924. renderMap: function() {
  925. this.initMap();
  926. },
  927. getElementConfig: function() {
  928. return {
  929. reference: 'element',
  930. className: 'x-container',
  931. children: [
  932. {
  933. reference: 'bodyElement',
  934. className: 'x-inner',
  935. children: [
  936. {
  937. reference: 'mapContainer',
  938. className: Ext.baseCSSPrefix + 'map-container'
  939. }
  940. ]
  941. }
  942. ]
  943. };
  944. },
  945. onTouchStart: function(e) {
  946. e.makeUnpreventable();
  947. },
  948. updateMap: function(map) {
  949. var markers = this.getMarkers();
  950. if (markers) {
  951. markers.each(function(record) {
  952. var marker = this.getMarkerForRecord(record);
  953. if (marker) {
  954. marker.setMap(map);
  955. }
  956. }, this);
  957. }
  958. },
  959. applyMapOptions: function(options) {
  960. return Ext.merge({}, this.options, options);
  961. },
  962. updateMapOptions: function(newOptions) {
  963. var gm = (window.google || {}).maps,
  964. map = this.getMap();
  965. if (gm && map) {
  966. map.setOptions(newOptions);
  967. }
  968. },
  969. applyMarkers: function(value) {
  970. if (!value) {
  971. return null;
  972. }
  973. if (value.isStore) {
  974. return value;
  975. }
  976. if (Ext.isArray(value)) {
  977. value = {
  978. data: value
  979. };
  980. } else if (Ext.isObject(value)) {
  981. value = {
  982. data: [
  983. value
  984. ]
  985. };
  986. }
  987. return Ext.getStore(value);
  988. },
  989. updateMarkers: function(curr, prev) {
  990. var me = this,
  991. listeners = {
  992. add: 'onMarkersAdd',
  993. remove: 'onMarkersRemove',
  994. itemchange: 'onMarkerChange',
  995. scope: this
  996. };
  997. if (prev && prev.isStore) {
  998. prev.getData().un(listeners);
  999. me.removeMarkers(prev.getRange());
  1000. }
  1001. if (curr && curr.isStore) {
  1002. me.addMarkers(curr.getRange());
  1003. curr.getData().on(listeners);
  1004. }
  1005. },
  1006. applyMarkerTemplate: function(value) {
  1007. return Ext.util.ObjectTemplate.create(value);
  1008. },
  1009. updateMarkerTemplate: function(value) {
  1010. var markers = this.getMarkers();
  1011. if (markers) {
  1012. this.refreshMarkers(markers.getRange());
  1013. }
  1014. },
  1015. doMapCenter: function() {
  1016. this.setMapCenter(this.getMapOptions().center);
  1017. },
  1018. getMapOptions: function() {
  1019. return Ext.merge({}, this.options || this.getInitialConfig('mapOptions'));
  1020. },
  1021. updateUseCurrentLocation: function(useCurrentLocation) {
  1022. this.setGeo(useCurrentLocation);
  1023. if (!useCurrentLocation) {
  1024. this.setMapCenter();
  1025. }
  1026. },
  1027. applyGeo: function(config) {
  1028. return Ext.factory(config, Ext.util.Geolocation, this.getGeo());
  1029. },
  1030. updateGeo: function(newGeo, oldGeo) {
  1031. var events = {
  1032. locationupdate: 'onGeoUpdate',
  1033. locationerror: 'onGeoError',
  1034. scope: this
  1035. };
  1036. if (oldGeo) {
  1037. oldGeo.un(events);
  1038. }
  1039. if (newGeo) {
  1040. newGeo.on(events);
  1041. newGeo.updateLocation();
  1042. }
  1043. },
  1044. /**
  1045. * @private
  1046. */
  1047. onPainted: function() {
  1048. var gm = (window.google || {}).maps,
  1049. map = this.getMap(),
  1050. center;
  1051. if (gm && map) {
  1052. center = map.getCenter();
  1053. gm.event.trigger(map, 'resize');
  1054. if (center) {
  1055. map.setCenter(center);
  1056. }
  1057. }
  1058. },
  1059. /**
  1060. * @private
  1061. */
  1062. onTilesLoaded: function() {
  1063. this.fireEvent('maprender', this, this.getMap());
  1064. },
  1065. /**
  1066. * @private
  1067. */
  1068. addMapListeners: function() {
  1069. var gm = (window.google || {}).maps,
  1070. map = this.getMap(),
  1071. mapListeners = this.getMapListeners();
  1072. if (gm) {
  1073. var event = gm.event,
  1074. me = this,
  1075. listener, scope, fn, callbackFn, handle;
  1076. if (Ext.isSimpleObject(mapListeners)) {
  1077. for (var eventType in mapListeners) {
  1078. listener = mapListeners[eventType];
  1079. if (Ext.isSimpleObject(listener)) {
  1080. scope = listener.scope;
  1081. fn = listener.fn;
  1082. } else if (Ext.isFunction(listener)) {
  1083. scope = null;
  1084. fn = listener;
  1085. }
  1086. if (fn) {
  1087. callbackFn = function() {
  1088. this.fn.apply(this.scope, [
  1089. me
  1090. ]);
  1091. if (this.handle) {
  1092. event.removeListener(this.handle);
  1093. delete this.handle;
  1094. delete this.fn;
  1095. delete this.scope;
  1096. }
  1097. };
  1098. handle = event.addListener(map, eventType, Ext.bind(callbackFn, callbackFn));
  1099. callbackFn.fn = fn;
  1100. callbackFn.scope = scope;
  1101. if (listener.single === true) {
  1102. callbackFn.handle = handle;
  1103. }
  1104. }
  1105. }
  1106. }
  1107. }
  1108. },
  1109. /**
  1110. * @private
  1111. */
  1112. onGeoUpdate: function(geo) {
  1113. if (geo) {
  1114. this.setMapCenter(new google.maps.LatLng(geo.getLatitude(), geo.getLongitude()));
  1115. }
  1116. },
  1117. /**
  1118. * @method
  1119. * @private
  1120. */
  1121. onGeoError: Ext.emptyFn,
  1122. /**
  1123. * Moves the map center to a google.maps.LatLng object representing to the target location,
  1124. * a marker record from the {@link #cfg-markers markers} store, or to the designated
  1125. * coordinates hash of the form:
  1126. *
  1127. * { latitude: 37.381592, longitude: -122.135672 }
  1128. *
  1129. * @param {Object/Ext.data.Model/google.maps.LatLng} coordinates Object representing the
  1130. * desired latitude and longitude upon which to center the map.
  1131. */
  1132. setMapCenter: function(coordinates) {
  1133. var me = this,
  1134. map = me.getMap(),
  1135. mapOptions = me.getMapOptions(),
  1136. gm = (window.google || {}).maps,
  1137. marker;
  1138. if (gm) {
  1139. if (!coordinates) {
  1140. if (map && map.getCenter) {
  1141. coordinates = map.getCenter();
  1142. } else if (mapOptions.hasOwnProperty('center')) {
  1143. coordinates = mapOptions.center;
  1144. } else {
  1145. coordinates = new gm.LatLng(37.381592, -122.135672);
  1146. }
  1147. }
  1148. // Palo Alto
  1149. else if (coordinates.isModel) {
  1150. var marker = me.getMarkerForRecord(coordinates);
  1151. coordinates = marker && marker.position;
  1152. }
  1153. if (coordinates && !(coordinates instanceof gm.LatLng) && 'longitude' in coordinates) {
  1154. coordinates = new gm.LatLng(coordinates.latitude, coordinates.longitude);
  1155. }
  1156. if (!map) {
  1157. mapOptions.center = mapOptions.center || coordinates;
  1158. me.renderMap();
  1159. map = me.getMap();
  1160. }
  1161. if (map && coordinates instanceof gm.LatLng) {
  1162. map.panTo(coordinates);
  1163. } else {
  1164. this.options = Ext.apply(this.getMapOptions(), {
  1165. center: coordinates
  1166. });
  1167. }
  1168. }
  1169. },
  1170. /**
  1171. * Scales and pans the view to ensure that the given markers fits inside the map view.
  1172. * @param {Ext.data.Model[]} records The markers records to fit in view.
  1173. */
  1174. fitMarkersInView: function(records) {
  1175. var me = this,
  1176. map = me.getMap(),
  1177. b2 = map.getBounds(),
  1178. markers = me.getMarkers(),
  1179. gm = (window.google || {}).maps,
  1180. b1, b1ne, b1sw, b2ne, b2sw;
  1181. if (!map || !b2 || !markers) {
  1182. return;
  1183. }
  1184. if (Ext.isEmpty(records)) {
  1185. records = markers.getRange();
  1186. if (Ext.isEmpty(records)) {
  1187. return;
  1188. }
  1189. }
  1190. b1 = new gm.LatLngBounds();
  1191. Ext.each(records, function(record) {
  1192. var marker = me.getMarkerForRecord(record);
  1193. if (marker) {
  1194. b1.extend(marker.getPosition());
  1195. }
  1196. });
  1197. b1ne = b1.getNorthEast();
  1198. b1sw = b1.getSouthWest();
  1199. b2ne = b2.getNorthEast();
  1200. b2sw = b2.getSouthWest();
  1201. if ((b1ne.lat() - b1sw.lat()) > (b2ne.lat() - b2sw.lat()) || (b1ne.lng() - b1sw.lng()) > (b2ne.lng() - b2sw.lng())) {
  1202. map.fitBounds(b1);
  1203. } else {
  1204. map.panToBounds(b1);
  1205. }
  1206. },
  1207. /**
  1208. * @private
  1209. */
  1210. onZoomChange: function() {
  1211. var mapOptions = this.getMapOptions(),
  1212. map = this.getMap(),
  1213. zoom;
  1214. zoom = (map && map.getZoom) ? map.getZoom() : mapOptions.zoom || 10;
  1215. this.options = Ext.apply(mapOptions, {
  1216. zoom: zoom
  1217. });
  1218. this.fireEvent('zoomchange', this, map, zoom);
  1219. },
  1220. /**
  1221. * @private
  1222. */
  1223. onTypeChange: function() {
  1224. var mapOptions = this.getMapOptions(),
  1225. map = this.getMap(),
  1226. mapTypeId;
  1227. mapTypeId = (map && map.getMapTypeId) ? map.getMapTypeId() : mapOptions.mapTypeId;
  1228. this.options = Ext.apply(mapOptions, {
  1229. mapTypeId: mapTypeId
  1230. });
  1231. this.fireEvent('typechange', this, map, mapTypeId);
  1232. },
  1233. /**
  1234. * @private
  1235. */
  1236. onCenterChange: function() {
  1237. var mapOptions = this.getMapOptions(),
  1238. map = this.getMap(),
  1239. center;
  1240. center = (map && map.getCenter) ? map.getCenter() : mapOptions.center;
  1241. this.options = Ext.apply(mapOptions, {
  1242. center: center
  1243. });
  1244. this.fireEvent('centerchange', this, map, center);
  1245. },
  1246. doDestroy: function() {
  1247. Ext.destroy(this.getGeo());
  1248. var map = this.getMap();
  1249. if (map && (window.google || {}).maps) {
  1250. google.maps.event.clearInstanceListeners(map);
  1251. }
  1252. this.callParent();
  1253. },
  1254. privates: {
  1255. // See google.map.Marker API
  1256. // https://developers.google.com/maps/documentation/javascript/3.exp/reference#Marker
  1257. markerEvents: [
  1258. 'click',
  1259. 'dblclick',
  1260. 'drag',
  1261. 'dragend',
  1262. 'dragstart',
  1263. 'mousedown',
  1264. 'mouseout',
  1265. 'mouseover',
  1266. 'mouseup',
  1267. 'rightclick'
  1268. ],
  1269. getMarkerForRecord: function(record) {
  1270. var expando = record && Ext.getExpando(record, this.getId());
  1271. return (expando && expando.marker) || null;
  1272. },
  1273. buildMarkerOptions: function(record, tpl) {
  1274. var options = tpl.apply(record.getData(true)),
  1275. gm = (window.google || {}).maps,
  1276. animation = options.animation;
  1277. if (typeof animation === 'string') {
  1278. options.animation = gm.Animation[animation] || null;
  1279. }
  1280. return options;
  1281. },
  1282. addMarkers: function(records) {
  1283. var me = this,
  1284. eid = me.getId(),
  1285. map = me.getMap(),
  1286. tpl = me.getMarkerTemplate(),
  1287. gm = (window.google || {}).maps,
  1288. store = me.getMarkers(),
  1289. events = me.markerEvents;
  1290. Ext.each(records, function(record) {
  1291. var index = store.indexOf(record),
  1292. options = me.buildMarkerOptions(record, tpl),
  1293. marker = new gm.Marker(Ext.apply(options, {
  1294. map: map
  1295. })),
  1296. listeners = events.map(function(type) {
  1297. return marker.addListener(type, function(event) {
  1298. me.fireEvent('marker' + type, me, {
  1299. index: index,
  1300. record: record,
  1301. marker: marker,
  1302. event: event
  1303. });
  1304. });
  1305. });
  1306. Ext.setExpando(record, eid, {
  1307. listeners: listeners,
  1308. marker: marker
  1309. });
  1310. });
  1311. },
  1312. removeMarkers: function(records) {
  1313. var eid = this.getId();
  1314. Ext.each(records, function(record) {
  1315. var expando = Ext.getExpando(record, eid),
  1316. marker = expando && expando.marker;
  1317. if (marker) {
  1318. marker.setMap(null);
  1319. Ext.each(expando.listeners || [], function(listener) {
  1320. listener.remove();
  1321. });
  1322. }
  1323. Ext.setExpando(record, eid, undefined);
  1324. });
  1325. },
  1326. refreshMarkers: function(records) {
  1327. var me = this,
  1328. tpl = me.getMarkerTemplate(),
  1329. count = records.length,
  1330. record, marker, i;
  1331. for (i = 0; i < count; ++i) {
  1332. record = records[i];
  1333. marker = me.getMarkerForRecord(record);
  1334. if (marker) {
  1335. marker.setOptions(me.buildMarkerOptions(record, tpl));
  1336. }
  1337. }
  1338. },
  1339. onMarkersAdd: function(collection, details) {
  1340. this.addMarkers(details.items);
  1341. },
  1342. onMarkersRemove: function(collection, details) {
  1343. this.removeMarkers(details.items);
  1344. },
  1345. onMarkerChange: function(collection, details) {
  1346. this.refreshMarkers([
  1347. details.item
  1348. ]);
  1349. }
  1350. }
  1351. });
  1352. /**
  1353. * Provided for convenience, this model exposes the default Google Map Marker options.
  1354. *
  1355. * See https://developers.google.com/maps/documentation/javascript/3.exp/reference#MarkerOptions
  1356. *
  1357. * ## Fields
  1358. *
  1359. * - position {Object} - The marker position (required)
  1360. * - position.lat {Number} - Latitude in degrees
  1361. * - position.lng {Number} - Longitude in degrees
  1362. * - title {String} - The rollover text (default: null)
  1363. * - animation {String/Number} - The animation to play when the marker is added to the map.
  1364. * Can be either null (no animation), a string ("BOUNCE" or "DROP"") or a value from
  1365. * [google.maps.Animation](https://developers.google.com/maps/documentation/javascript/3.exp/reference#Animation)
  1366. * - clickable {Boolean} - Whether the marker receives mouse and touch events (default: true)
  1367. * - draggable {Boolean} - Whether the marker can be dragged (default: false)
  1368. * - draggable {Boolean} - Whether the marker is visible (default: true)
  1369. *
  1370. * ## Custom model
  1371. *
  1372. * It's not required to inherit from this model in order to display markers. By providing the
  1373. * suitable {@link Ext.ux.google.Map#cfg-markerTemplate markerTemplate}, marker options can
  1374. * be extracted from any records, for example:
  1375. *
  1376. * Ext.define('MyApp.model.Office', {
  1377. * extend: 'Ext.data.Model',
  1378. * fields: [
  1379. * 'name',
  1380. * 'address',
  1381. * 'latitute',
  1382. * 'longitude'
  1383. * ]
  1384. * });
  1385. *
  1386. * and the associated view config:
  1387. *
  1388. * {
  1389. * xtype: 'map',
  1390. * store: 'offices',
  1391. * markerTemplate: {
  1392. * title: '{name}',
  1393. * position: {
  1394. * lat: '{latitute}',
  1395. * lng: '{longitude}'
  1396. * }
  1397. * }
  1398. * }
  1399. *
  1400. */
  1401. Ext.define('Ext.ux.google.map.Marker', {
  1402. extend: 'Ext.data.Model',
  1403. fields: [
  1404. {
  1405. name: 'position',
  1406. type: 'auto'
  1407. },
  1408. {
  1409. name: 'title',
  1410. type: 'string',
  1411. defaultValue: null
  1412. },
  1413. {
  1414. name: 'animation',
  1415. type: 'number',
  1416. defaultValue: 'DROP',
  1417. persist: false
  1418. },
  1419. {
  1420. name: 'clickable',
  1421. type: 'boolean',
  1422. defaultValue: true,
  1423. persist: false
  1424. },
  1425. {
  1426. name: 'draggable',
  1427. type: 'boolean',
  1428. defaultValue: false,
  1429. persist: false
  1430. },
  1431. {
  1432. name: 'visible',
  1433. type: 'boolean',
  1434. defaultValue: true,
  1435. persist: false
  1436. }
  1437. ]
  1438. });