1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447 |
- /**
- * See https://developers.google.com/api-client-library/javascript/
- * See https://developers.google.com/apis-explorer/#p/
- *
- * googleApis: { 'calendar': { version: 'v3' } }
- */
- Ext.define('Ext.google.ux.Client', {
- extend: 'Ext.Mixin',
- mixins: [
- 'Ext.mixin.Mashup'
- ],
- requiredScripts: [
- '//apis.google.com/js/client.js?onload=_ext_google_ux_client_initialize_'
- ],
- statics: {
- getApiVersion: function(api) {
- var library = this.libraries[api];
- return library && library.state == 2 ? library.version : null;
- }
- },
- mixinConfig: {
- extended: function(baseClass, derivedClass, classBody) {
- this.load(classBody.googleApis);
- }
- },
- onClassMixedIn: function(cls) {
- this.load(cls.prototype.googleApis);
- },
- privates: {
- statics: {
- /**
- * @property {Boolean} initialized
- * `true` if the google client has been loaded and initialized.
- * @private
- */
- initialized: false,
- /**
- * @property {Boolean} blocked
- * `true` if this class has blocked Ext.env.Ready, else false.
- * @private
- */
- blocked: false,
- /**
- * @property {Number} loading
- * Keep track of how many libraries are loading.
- * @private
- */
- loading: 0,
- /**
- * @property {Object} libraries
- * Information about required libraries.
- * { `api_name`: { version: string, state: int }
- * state: 0 (pending), 1 (loading), 2 (loaded)
- * Example: { calendar: { version: 'v1', state: 1 } }
- * @private
- */
- libraries: {},
- load: function(apis) {
- var libraries = this.libraries,
- version, library;
- if (!Ext.isObject(apis)) {
- return;
- }
- Ext.Object.each(apis, function(api, cfg) {
- version = cfg.version || 'v1';
- library = libraries[api];
- if (!Ext.isDefined(library)) {
- libraries[api] = {
- version: version,
- state: 0
- };
- } else if (library.version !== version) {
- Ext.log.error('Google API: failed to load version "' + version + '" of the', '"' + api + '" API: "' + library.version + '" already loaded.');
- }
- });
- this.refresh();
- },
- refresh: function() {
- var me = this;
- if (!me.initialized) {
- return;
- }
- if (!me.blocked) {
- Ext.env.Ready.block();
- me.blocked = true;
- }
- Ext.Object.each(me.libraries, function(api, library) {
- if (library.state == 0) {
- library.state = 1;
- // loading
- gapi.client.load(api, library.version, function() {
- library.state = 2;
- // loaded
- if (!--me.loading) {
- me.refresh();
- }
- });
- }
- if (library.state == 1) {
- me.loading++;
- }
- });
- if (!me.loading && me.blocked) {
- Ext.env.Ready.unblock();
- me.blocked = false;
- }
- },
- initialize: function() {
- this.initialized = true;
- this.refresh();
- }
- }
- }
- });
- // See https://developers.google.com/api-client-library/javascript/features/authentication
- _ext_google_ux_client_initialize_ = function() {
- gapi.auth.init(function() {
- Ext.google.ux.Client.initialize();
- });
- };
- /**
- * Base proxy for accessing **[Google API](https://developers.google.com/apis-explorer/#p/)** resources.
- */
- Ext.define('Ext.google.data.AbstractProxy', {
- extend: 'Ext.data.proxy.Server',
- mixins: [
- 'Ext.google.ux.Client'
- ],
- // TODO: Batch actions
- // https://developers.google.com/api-client-library/javascript/features/batch
- /**
- * @cfg batchActions
- * @inheritdoc
- */
- batchActions: false,
- /**
- * @cfg reader
- * @inheritdoc
- */
- reader: {
- type: 'json',
- rootProperty: 'items',
- messageProperty: 'error'
- },
- /**
- * @method buildApiRequests
- * Returns a list of API request(s), **not executed**.
- * @param {Ext.data.Request} request The data request
- * @return {Object[]} API request(s)
- * @abstract
- */
- /**
- * @protected
- * @inheritdoc
- */
- doRequest: function(operation) {
- var me = this,
- request = me.buildRequest(operation),
- writer = me.getWriter(),
- error = false;
- if (writer && operation.allowWrite()) {
- request = writer.write(request);
- }
- me.execute(me.buildApiRequests(request)).then(function(response) {
- me.processApiResponse(operation, request, response);
- });
- return request;
- },
- /**
- * @method buildUrl
- * @protected
- * @inheritdoc
- */
- buildUrl: function(request) {
- return '';
- },
- privates: {
- execute: function(requests) {
- requests = [].concat(requests);
- // BUG: when using the gapi batch feature and trying to modify the same event
- // more than one time, the request partially fails and returns a 502 error.
- // See https://code.google.com/a/google.com/p/apps-api-issues/issues/detail?id=4528
- // TODO: use the following code once fixed! also check that it doesn't break
- // maxResults limit for event list requests.
- //var batch = gapi.client.newBatch();
- //Ext.Array.each(requests, function(r, i) { batch.add(r, { id: i }); });
- //return batch.execute();
- // WORKAROUND for the issue above (REMOVE ME)
- var results = [];
- return Ext.Array.reduce(requests, function(sequence, r) {
- return sequence.then(function() {
- return r.then(function(result) {
- results.push(result);
- });
- });
- }, Ext.Deferred.resolved()).then(function() {
- return {
- result: results
- };
- });
- },
- processApiResponse: function(operation, request, responses) {
- var error = false,
- results = [];
- // responses.result is not a regular Object, can't iterate with Ext.Object.each()
- Ext.each(Object.keys(responses.result), function(index) {
- var result = responses.result[index].result;
- if (result.error) {
- error = result.error.message;
- return false;
- }
- results.push(result);
- });
- this.processResponse(true, operation, request, {
- results: error ? [] : results,
- success: !error,
- error: error
- });
- },
- sanitizeItems: function(items) {
- var results = [],
- ids = [];
- // Batch can return different versions of the same record, only keep the last one.
- Ext.Array.each(items, function(item) {
- if (!Ext.Array.contains(ids, item.id)) {
- results.push(item);
- ids.push(item.id);
- }
- }, this, true);
- return results;
- }
- }
- });
- /**
- * Proxy to access Google **[event resources](https://developers.google.com/google-apps/calendar/v3/reference/events)**.
- */
- Ext.define('Ext.google.data.EventsProxy', {
- extend: 'Ext.google.data.AbstractProxy',
- alias: 'proxy.google-events',
- googleApis: {
- 'calendar': {
- version: 'v3'
- }
- },
- /**
- * @method buildApiRequests
- * @protected
- * @inheritdoc
- */
- buildApiRequests: function(request) {
- var me = this,
- action = request.getAction();
- switch (action) {
- case 'read':
- return me.buildReadApiRequests(request);
- case 'create':
- return me.buildCreateApiRequests(request);
- case 'update':
- return me.buildUpdateApiRequests(request);
- case 'destroy':
- return me.buildDestroyApiRequests(request);
- default:
- Ext.raise('unsupported request: events.' + action);
- return null;
- }
- },
- /**
- * @method extractResponseData
- * @protected
- * @inheritdoc
- */
- extractResponseData: function(response) {
- var me = this,
- data = me.callParent(arguments),
- items = [];
- Ext.each(data.results, function(result) {
- switch (result.kind) {
- case 'calendar#events':
- items = items.concat(result.items.map(me.fromApiEvent.bind(me)));
- break;
- case 'calendar#event':
- items.push(me.fromApiEvent(result));
- break;
- default:
- break;
- }
- });
- return {
- items: me.sanitizeItems(items),
- success: data.success,
- error: data.error
- };
- },
- privates: {
- // https://developers.google.com/google-apps/calendar/v3/reference/events
- toApiEvent: function(data, allDay) {
- var res = {};
- Ext.Object.each(data, function(key, value) {
- var dateTime = null,
- date = null;
- switch (key) {
- case 'calendarId':
- case 'description':
- res[key] = value;
- break;
- case 'id':
- res.eventId = value;
- break;
- case 'title':
- res.summary = value;
- break;
- case 'startDate':
- case 'endDate':
- if (allDay) {
- date = new Date(value);
- date.setHours(0, -date.getTimezoneOffset());
- date = Ext.Date.format(date, 'Y-m-d');
- } else {
- dateTime = Ext.Date.format(new Date(value), 'c');
- };
- // Need to explicitly set unused date field to null
- // http://stackoverflow.com/a/35658479
- res[key.slice(0, -4)] = {
- date: date,
- dateTime: dateTime
- };
- break;
- default:
- break;
- }
- });
- return res;
- },
- // https://developers.google.com/google-apps/calendar/v3/reference/events
- fromApiEvent: function(data) {
- var res = {
- allDay: true
- };
- Ext.Object.each(data, function(key, value) {
- var date, offset, allDay;
- switch (key) {
- case 'id':
- case 'description':
- res[key] = value;
- break;
- case 'summary':
- res.title = value;
- break;
- case 'start':
- case 'end':
- date = Ext.Date.parse(value.dateTime || value.date, 'C');
- offset = date.getTimezoneOffset();
- allDay = !!value.date;
- // IMPORTANT: all day events must have their time equal to 00:00 GMT
- if (allDay && offset !== 0) {
- date.setHours(0, -offset);
- };
- res[key + 'Date'] = date;
- res.allDay = res.allDay && allDay;
- break;
- default:
- break;
- }
- });
- return res;
- },
- // See https://developers.google.com/google-apps/calendar/v3/reference/events/list
- buildReadApiRequests: function(request) {
- // by default, the API returns max 250 events per request, up to 2500. Since we
- // don't have control on the min & max requested times, and don't know how many
- // events will be returned, let's split requests per 3 months and set maxResults
- // to 2500 (~26 events per day - should be enough!?).
- var rparams = request.getParams(),
- start = new Date(rparams.startDate),
- end = new Date(rparams.endDate),
- requests = [],
- next;
- while (start < end) {
- next = Ext.Date.add(start, Ext.Date.MONTH, 3);
- if (next > end) {
- next = end;
- }
- requests.push(gapi.client.calendar.events.list({
- calendarId: rparams.calendar,
- timeMin: Ext.Date.format(start, 'C'),
- timeMax: Ext.Date.format(next, 'C'),
- singleEvents: true,
- maxResults: 2500
- }));
- start = next;
- }
- return requests;
- },
- // https://developers.google.com/google-apps/calendar/v3/reference/events/insert
- buildCreateApiRequests: function(request) {
- var record = request.getRecords()[0];
- // batch not currently supported!
- return gapi.client.calendar.events.insert(this.toApiEvent(request.getJsonData(), record.get('allDay')));
- },
- // https://developers.google.com/google-apps/calendar/v3/reference/events/patch
- // https://developers.google.com/google-apps/calendar/v3/reference/events/move
- buildUpdateApiRequests: function(request) {
- var record = request.getRecords()[0],
- // batch not currently supported!
- params = this.toApiEvent(request.getJsonData(), record.get('allDay')),
- prevCalendarId = record.getModified('calendarId'),
- currCalendarId = record.get('calendarId'),
- eventId = record.getId(),
- requests = [];
- // REQUIRED fields for the patch API
- params.calendarId = currCalendarId;
- params.eventId = eventId;
- if (prevCalendarId && prevCalendarId !== currCalendarId) {
- // The event has been moved to another calendar
- requests.push(gapi.client.calendar.events.move({
- destination: currCalendarId,
- calendarId: prevCalendarId,
- eventId: eventId
- }));
- }
- if (Object.keys(params).length > 2) {
- // There is fields to update other than the calendarId + eventId
- requests.push(gapi.client.calendar.events.patch(params));
- }
- return requests;
- },
- // https://developers.google.com/google-apps/calendar/v3/reference/events/delete
- buildDestroyApiRequests: function(request) {
- var record = request.getRecords()[0];
- // batch not currently supported!
- data = request.getJsonData();
- // The current calendar implementation nullifies the calendar ID before deleting
- // it, so let's get it from the previous values if not anymore in data.
- data.calendarId = data.calendarId || record.get('calendarId') || record.getPrevious('calendarId');
- // ['delete'] to make YUI happy
- return gapi.client.calendar.events['delete']({
- 'calendarId': data.calendarId,
- 'eventId': data.id
- });
- }
- }
- });
- /**
- * Proxy to access Google **[calendar resources](https://developers.google.com/google-apps/calendar/v3/reference/calendarList)**.
- */
- Ext.define('Ext.google.data.CalendarsProxy', {
- extend: 'Ext.google.data.AbstractProxy',
- alias: 'proxy.google-calendars',
- requires: [
- 'Ext.google.data.EventsProxy'
- ],
- googleApis: {
- 'calendar': {
- version: 'v3'
- }
- },
- /**
- * @method buildApiRequests
- * @protected
- * @inheritdoc
- */
- buildApiRequests: function(request) {
- var me = this,
- action = request.getAction();
- switch (action) {
- case 'read':
- return me.buildReadApiRequests(request);
- case 'update':
- return me.buildUpdateApiRequests(request);
- default:
- Ext.raise('unsupported request: calendars.' + action);
- return null;
- }
- },
- /**
- * @method extractResponseData
- * @protected
- * @inheritdoc
- */
- extractResponseData: function(response) {
- var me = this,
- data = me.callParent(arguments),
- items = [];
- // We assume that the response contains only results of the same kind.
- Ext.each(data.results, function(result) {
- switch (result.kind) {
- case 'calendar#calendarList':
- items = items.concat(result.items.map(me.fromApiCalendar.bind(me)));
- break;
- default:
- break;
- }
- });
- return {
- items: me.sanitizeItems(items),
- success: data.success,
- error: data.error
- };
- },
- privates: {
- // https://developers.google.com/google-apps/calendar/v3/reference/calendarList#resource
- toApiCalendar: function(data) {
- var res = {};
- Ext.Object.each(data, function(key, value) {
- switch (key) {
- case 'id':
- res.calendarId = value;
- break;
- case 'hidden':
- res.selected = !value;
- break;
- default:
- break;
- }
- });
- return res;
- },
- // https://developers.google.com/google-apps/calendar/v3/reference/calendarList#resource
- fromApiCalendar: function(data) {
- var record = {
- hidden: !data.selected,
- editable: false,
- eventStore: {
- autoSync: true,
- proxy: {
- type: 'google-events',
- resourceTypes: 'events'
- }
- }
- };
- Ext.Object.each(data, function(key, value) {
- switch (key) {
- case 'id':
- case 'description':
- record[key] = value;
- break;
- case 'backgroundColor':
- record.color = value;
- break;
- case 'summary':
- record.title = value;
- break;
- case 'accessRole':
- record.editable = (value == 'owner' || value == 'writer');
- break;
- default:
- break;
- }
- });
- return record;
- },
- // https://developers.google.com/google-apps/calendar/v3/reference/calendarList/list
- buildReadApiRequests: function(request) {
- return gapi.client.calendar.calendarList.list();
- },
- // https://developers.google.com/google-apps/calendar/v3/reference/calendarList/patch
- buildUpdateApiRequests: function(request) {
- var data = this.toApiCalendar(request.getJsonData());
- return gapi.client.calendar.calendarList.patch(data);
- }
- }
- });
- /**
- * This base class can be used by derived classes to dynamically require Google API's.
- */
- Ext.define('Ext.ux.google.Api', {
- mixins: [
- 'Ext.mixin.Mashup'
- ],
- requiredScripts: [
- '//www.google.com/jsapi'
- ],
- statics: {
- loadedModules: {}
- },
- /*
- * feeds: [ callback1, callback2, .... ] transitions to -> feeds : true (when complete)
- */
- onClassExtended: function(cls, data, hooks) {
- var onBeforeClassCreated = hooks.onBeforeCreated,
- Api = this;
- // the Ext.ux.google.Api class
- hooks.onBeforeCreated = function(cls, data) {
- var me = this,
- apis = [],
- requiresGoogle = Ext.Array.from(data.requiresGoogle),
- loadedModules = Api.loadedModules,
- remaining = 0,
- callback = function() {
- if (!--remaining) {
- onBeforeClassCreated.call(me, cls, data, hooks);
- }
- Ext.env.Ready.unblock();
- },
- api, i, length;
- /*
- * requiresGoogle: [
- * 'feeds',
- * { api: 'feeds', version: '1.x',
- * callback : fn, nocss : true } //optionals
- * ]
- */
- length = requiresGoogle.length;
- for (i = 0; i < length; ++i) {
- if (Ext.isString(api = requiresGoogle[i])) {
- apis.push({
- api: api
- });
- } else if (Ext.isObject(api)) {
- apis.push(Ext.apply({}, api));
- }
- }
- Ext.each(apis, function(api) {
- var name = api.api,
- version = String(api.version || '1.x'),
- module = loadedModules[name];
- if (!module) {
- ++remaining;
- Ext.env.Ready.block();
- loadedModules[name] = module = [
- callback
- ].concat(api.callback || []);
- delete api.api;
- delete api.version;
- if (!window.google) {
- Ext.raise("'google' is not defined.");
- return false;
- }
- google.load(name, version, Ext.applyIf({
- callback: function() {
- loadedModules[name] = true;
- for (var n = module.length; n-- > 0; ) {
- module[n]();
- }
- }
- }, //iterate callbacks in reverse
- api));
- } else if (module !== true) {
- module.push(callback);
- }
- });
- if (!remaining) {
- onBeforeClassCreated.call(me, cls, data, hooks);
- }
- };
- }
- });
- /**
- * Wraps a Google Map in an Ext.Component using the [Google Maps API](http://code.google.com/apis/maps/documentation/v3/introduction.html).
- *
- * This component will automatically include the google maps API script from:
- * `//maps.google.com/maps/api/js`
- *
- * ## Example
- *
- * Ext.Viewport.add({
- * xtype: 'map',
- * useCurrentLocation: true
- * });
- */
- Ext.define('Ext.ux.google.Map', {
- extend: 'Ext.Container',
- xtype: [
- 'map',
- 'google-map'
- ],
- alternateClassName: 'Ext.Map',
- requires: [
- 'Ext.util.Geolocation'
- ],
- mixins: [
- 'Ext.mixin.Mashup'
- ],
- requires: [
- 'Ext.data.StoreManager'
- ],
- requiredScripts: [
- '//maps.googleapis.com/maps/api/js{options}'
- ],
- isMap: true,
- /**
- * @event maprender
- * Fired when Map initially rendered.
- * @param {Ext.ux.google.Map} this
- * @param {google.maps.Map} map The rendered google.map.Map instance
- */
- /**
- * @event centerchange
- * Fired when map is panned around.
- * @param {Ext.ux.google.Map} this
- * @param {google.maps.Map} map The rendered google.map.Map instance
- * @param {google.maps.LatLng} center The current LatLng center of the map
- */
- /**
- * @event typechange
- * Fired when display type of the map changes.
- * @param {Ext.ux.google.Map} this
- * @param {google.maps.Map} map The rendered google.map.Map instance
- * @param {Number} mapType The current display type of the map
- */
- /**
- * @event zoomchange
- * Fired when map is zoomed.
- * @param {Ext.ux.google.Map} this
- * @param {google.maps.Map} map The rendered google.map.Map instance
- * @param {Number} zoomLevel The current zoom level of the map
- */
- /**
- * @event markerclick
- * Fired when the marker icon was clicked.
- * @param {Ext.ux.google.Map} map This map instance
- * @param {Object} info Information about this event
- * @param {Number} info.index The index of the marker record
- * @param {Ext.data.Model} info.record The record associated to the marker
- * @param {google.maps.Marker} info.marker The [Google Map marker](https://developers.google.com/maps/documentation/javascript/3.exp/reference#Marker)
- * @param {google.maps.MouseEvent} info.event The [Google Map event](https://developers.google.com/maps/documentation/javascript/3.exp/reference#MouseEvent)
- */
- /**
- * @event markerdblclick
- * Fired when the marker icon was double clicked.
- * @param {Ext.ux.google.Map} map This map instance
- * @param {Object} info Information about this event
- * @param {Number} info.index The index of the marker record
- * @param {Ext.data.Model} info.record The record associated to the marker
- * @param {google.maps.Marker} info.marker The [Google Map marker](https://developers.google.com/maps/documentation/javascript/3.exp/reference#Marker)
- * @param {google.maps.MouseEvent} info.event The [Google Map event](https://developers.google.com/maps/documentation/javascript/3.exp/reference#MouseEvent)
- */
- /**
- * @event markerdrag
- * Repeatedly fired while the user drags the marker.
- * @param {Ext.ux.google.Map} map This map instance
- * @param {Object} info Information about this event
- * @param {Number} info.index The index of the marker record
- * @param {Ext.data.Model} info.record The record associated to the marker
- * @param {google.maps.Marker} info.marker The [Google Map marker](https://developers.google.com/maps/documentation/javascript/3.exp/reference#Marker)
- * @param {google.maps.MouseEvent} info.event The [Google Map event](https://developers.google.com/maps/documentation/javascript/3.exp/reference#MouseEvent)
- */
- /**
- * @event markerdragend
- * Fired when the user stops dragging the marker.
- * @param {Ext.ux.google.Map} map This map instance
- * @param {Object} info Information about this event
- * @param {Number} info.index The index of the marker record
- * @param {Ext.data.Model} info.record The record associated to the marker
- * @param {google.maps.Marker} info.marker The [Google Map marker](https://developers.google.com/maps/documentation/javascript/3.exp/reference#Marker)
- * @param {google.maps.MouseEvent} info.event The [Google Map event](https://developers.google.com/maps/documentation/javascript/3.exp/reference#MouseEvent)
- */
- /**
- * @event markerdragstart
- * Fired when the user starts dragging the marker.
- * @param {Ext.ux.google.Map} map This map instance
- * @param {Object} info Information about this event
- * @param {Number} info.index The index of the marker record
- * @param {Ext.data.Model} info.record The record associated to the marker
- * @param {google.maps.Marker} info.marker The [Google Map marker](https://developers.google.com/maps/documentation/javascript/3.exp/reference#Marker)
- * @param {google.maps.MouseEvent} info.event The [Google Map event](https://developers.google.com/maps/documentation/javascript/3.exp/reference#MouseEvent)
- */
- /**
- * @event markermousedown
- * Fired for a mousedown on the marker.
- * @param {Ext.ux.google.Map} map This map instance
- * @param {Object} info Information about this event
- * @param {Number} info.index The index of the marker record
- * @param {Ext.data.Model} info.record The record associated to the marker
- * @param {google.maps.Marker} info.marker The [Google Map marker](https://developers.google.com/maps/documentation/javascript/3.exp/reference#Marker)
- * @param {google.maps.MouseEvent} info.event The [Google Map event](https://developers.google.com/maps/documentation/javascript/3.exp/reference#MouseEvent)
- */
- /**
- * @event markermouseout
- * Fired when the mouse leaves the area of the marker icon.
- * @param {Ext.ux.google.Map} map This map instance
- * @param {Object} info Information about this event
- * @param {Number} info.index The index of the marker record
- * @param {Ext.data.Model} info.record The record associated to the marker
- * @param {google.maps.Marker} info.marker The [Google Map marker](https://developers.google.com/maps/documentation/javascript/3.exp/reference#Marker)
- * @param {google.maps.MouseEvent} info.event The [Google Map event](https://developers.google.com/maps/documentation/javascript/3.exp/reference#MouseEvent)
- */
- /**
- * @event markermouseover
- * Fired when the mouse enters the area of the marker icon.
- * @param {Ext.ux.google.Map} map This map instance
- * @param {Object} info Information about this event
- * @param {Number} info.index The index of the marker record
- * @param {Ext.data.Model} info.record The record associated to the marker
- * @param {google.maps.Marker} info.marker The [Google Map marker](https://developers.google.com/maps/documentation/javascript/3.exp/reference#Marker)
- * @param {google.maps.MouseEvent} info.event The [Google Map event](https://developers.google.com/maps/documentation/javascript/3.exp/reference#MouseEvent)
- */
- /**
- * @event markermouseup
- * Fired for a mouseup on the marker.
- * @param {Ext.ux.google.Map} map This map instance
- * @param {Object} info Information about this event
- * @param {Number} info.index The index of the marker record
- * @param {Ext.data.Model} info.record The record associated to the marker
- * @param {google.maps.Marker} info.marker The [Google Map marker](https://developers.google.com/maps/documentation/javascript/3.exp/reference#Marker)
- * @param {google.maps.MouseEvent} info.event The [Google Map event](https://developers.google.com/maps/documentation/javascript/3.exp/reference#MouseEvent)
- */
- /**
- * @event markerrightclick
- * Fired for a rightclick on the marker.
- * @param {Ext.ux.google.Map} map This map instance
- * @param {Object} info Information about this event
- * @param {Number} info.index The index of the marker record
- * @param {Ext.data.Model} info.record The record associated to the marker
- * @param {google.maps.Marker} info.marker The [Google Map marker](https://developers.google.com/maps/documentation/javascript/3.exp/reference#Marker)
- * @param {google.maps.MouseEvent} info.event The [Google Map event](https://developers.google.com/maps/documentation/javascript/3.exp/reference#MouseEvent)
- */
- config: {
- /**
- * @cfg {Boolean/Ext.util.Geolocation} useCurrentLocation
- * Pass in true to center the map based on the geolocation coordinates or pass a
- * {@link Ext.util.Geolocation GeoLocation} config to have more control over your GeoLocation options
- * @accessor
- */
- useCurrentLocation: false,
- /**
- * @cfg {google.maps.Map} map
- * The wrapped map.
- * @accessor
- */
- map: null,
- /**
- * @cfg {Ext.util.Geolocation} geo
- * Geolocation provider for the map.
- * @accessor
- */
- geo: null,
- /**
- * @cfg {Object} mapOptions
- * MapOptions as specified by the Google Documentation:
- * [http://code.google.com/apis/maps/documentation/v3/reference.html](http://code.google.com/apis/maps/documentation/v3/reference.html)
- * @accessor
- */
- mapOptions: {},
- /**
- * @cfg {Object} mapListeners
- * Listeners for any Google Maps events specified by the Google Documentation:
- * [http://code.google.com/apis/maps/documentation/v3/reference.html](http://code.google.com/apis/maps/documentation/v3/reference.html)
- *
- * @accessor
- */
- mapListeners: null,
- /**
- * @cfg {Ext.data.Store/Object/Ext.data.Model[]/Ext.ux.google.map.Marker} markers
- * Can be either a Store instance, a configuration object that will be turned into a
- * store, an array of model or a single model (in which case a store will be created).
- * The Store is used to populate the set of markers that will be rendered in the map.
- * Marker options are read through the {@link #markerTemplate} config.
- */
- markers: null,
- /**
- * @cfg {Object/Ext.util.ObjectTemplate} markerTemplate
- * This is a template used to produce marker options from the {@link #markers} records.
- * See {@link Ext.ux.google.map.Marker} for details.
- */
- markerTemplate: {
- title: '{title}',
- position: '{position}',
- animation: '{animation}',
- // google.maps.Animation.DROP
- clickable: '{clickable}',
- draggable: '{draggable}',
- visible: '{visible}'
- }
- },
- baseCls: Ext.baseCSSPrefix + 'map',
- constructor: function(config) {
- this.callParent([
- config
- ]);
- if (!(window.google || {}).maps) {
- this.setHtml('Google Maps API is required');
- }
- },
- initialize: function() {
- this.callParent();
- this.initMap();
- this.on({
- painted: 'onPainted',
- scope: this
- });
- this.bodyElement.on('touchstart', 'onTouchStart', this);
- },
- initMap: function() {
- var map = this.getMap();
- if (!map) {
- var gm = (window.google || {}).maps;
- if (!gm) {
- return null;
- }
-
- var element = this.mapContainer,
- mapOptions = this.getMapOptions(),
- event = gm.event,
- me = this;
- //Remove the API Required div
- if (element.dom.firstChild) {
- Ext.fly(element.dom.firstChild).destroy();
- }
- if (Ext.os.is.iPad) {
- Ext.merge({
- navigationControlOptions: {
- style: gm.NavigationControlStyle.ZOOM_PAN
- }
- }, mapOptions);
- }
- mapOptions.mapTypeId = mapOptions.mapTypeId || gm.MapTypeId.ROADMAP;
- mapOptions.center = mapOptions.center || new gm.LatLng(37.381592, -122.135672);
- // Palo Alto
- if (mapOptions.center && mapOptions.center.latitude && !Ext.isFunction(mapOptions.center.lat)) {
- mapOptions.center = new gm.LatLng(mapOptions.center.latitude, mapOptions.center.longitude);
- }
- mapOptions.zoom = mapOptions.zoom || 12;
- map = new gm.Map(element.dom, mapOptions);
- this.setMap(map);
- event.addListener(map, 'zoom_changed', Ext.bind(me.onZoomChange, me));
- event.addListener(map, 'maptypeid_changed', Ext.bind(me.onTypeChange, me));
- event.addListener(map, 'center_changed', Ext.bind(me.onCenterChange, me));
- event.addListenerOnce(map, 'tilesloaded', Ext.bind(me.onTilesLoaded, me));
- this.addMapListeners();
- }
- return this.getMap();
- },
- // added for backwards compatibility for touch < 2.3
- renderMap: function() {
- this.initMap();
- },
- getElementConfig: function() {
- return {
- reference: 'element',
- className: 'x-container',
- children: [
- {
- reference: 'bodyElement',
- className: 'x-inner',
- children: [
- {
- reference: 'mapContainer',
- className: Ext.baseCSSPrefix + 'map-container'
- }
- ]
- }
- ]
- };
- },
- onTouchStart: function(e) {
- e.makeUnpreventable();
- },
- updateMap: function(map) {
- var markers = this.getMarkers();
- if (markers) {
- markers.each(function(record) {
- var marker = this.getMarkerForRecord(record);
- if (marker) {
- marker.setMap(map);
- }
- }, this);
- }
- },
- applyMapOptions: function(options) {
- return Ext.merge({}, this.options, options);
- },
- updateMapOptions: function(newOptions) {
- var gm = (window.google || {}).maps,
- map = this.getMap();
- if (gm && map) {
- map.setOptions(newOptions);
- }
- },
- applyMarkers: function(value) {
- if (!value) {
- return null;
- }
- if (value.isStore) {
- return value;
- }
- if (Ext.isArray(value)) {
- value = {
- data: value
- };
- } else if (Ext.isObject(value)) {
- value = {
- data: [
- value
- ]
- };
- }
- return Ext.getStore(value);
- },
- updateMarkers: function(curr, prev) {
- var me = this,
- listeners = {
- add: 'onMarkersAdd',
- remove: 'onMarkersRemove',
- itemchange: 'onMarkerChange',
- scope: this
- };
- if (prev && prev.isStore) {
- prev.getData().un(listeners);
- me.removeMarkers(prev.getRange());
- }
- if (curr && curr.isStore) {
- me.addMarkers(curr.getRange());
- curr.getData().on(listeners);
- }
- },
- applyMarkerTemplate: function(value) {
- return Ext.util.ObjectTemplate.create(value);
- },
- updateMarkerTemplate: function(value) {
- var markers = this.getMarkers();
- if (markers) {
- this.refreshMarkers(markers.getRange());
- }
- },
- doMapCenter: function() {
- this.setMapCenter(this.getMapOptions().center);
- },
- getMapOptions: function() {
- return Ext.merge({}, this.options || this.getInitialConfig('mapOptions'));
- },
- updateUseCurrentLocation: function(useCurrentLocation) {
- this.setGeo(useCurrentLocation);
- if (!useCurrentLocation) {
- this.setMapCenter();
- }
- },
- applyGeo: function(config) {
- return Ext.factory(config, Ext.util.Geolocation, this.getGeo());
- },
- updateGeo: function(newGeo, oldGeo) {
- var events = {
- locationupdate: 'onGeoUpdate',
- locationerror: 'onGeoError',
- scope: this
- };
- if (oldGeo) {
- oldGeo.un(events);
- }
- if (newGeo) {
- newGeo.on(events);
- newGeo.updateLocation();
- }
- },
- /**
- * @private
- */
- onPainted: function() {
- var gm = (window.google || {}).maps,
- map = this.getMap(),
- center;
- if (gm && map) {
- center = map.getCenter();
- gm.event.trigger(map, 'resize');
- if (center) {
- map.setCenter(center);
- }
- }
- },
- /**
- * @private
- */
- onTilesLoaded: function() {
- this.fireEvent('maprender', this, this.getMap());
- },
- /**
- * @private
- */
- addMapListeners: function() {
- var gm = (window.google || {}).maps,
- map = this.getMap(),
- mapListeners = this.getMapListeners();
- if (gm) {
- var event = gm.event,
- me = this,
- listener, scope, fn, callbackFn, handle;
- if (Ext.isSimpleObject(mapListeners)) {
- for (var eventType in mapListeners) {
- listener = mapListeners[eventType];
- if (Ext.isSimpleObject(listener)) {
- scope = listener.scope;
- fn = listener.fn;
- } else if (Ext.isFunction(listener)) {
- scope = null;
- fn = listener;
- }
- if (fn) {
- callbackFn = function() {
- this.fn.apply(this.scope, [
- me
- ]);
- if (this.handle) {
- event.removeListener(this.handle);
- delete this.handle;
- delete this.fn;
- delete this.scope;
- }
- };
- handle = event.addListener(map, eventType, Ext.bind(callbackFn, callbackFn));
- callbackFn.fn = fn;
- callbackFn.scope = scope;
- if (listener.single === true) {
- callbackFn.handle = handle;
- }
-
- }
- }
- }
- }
- },
- /**
- * @private
- */
- onGeoUpdate: function(geo) {
- if (geo) {
- this.setMapCenter(new google.maps.LatLng(geo.getLatitude(), geo.getLongitude()));
- }
- },
- /**
- * @method
- * @private
- */
- onGeoError: Ext.emptyFn,
- /**
- * Moves the map center to a google.maps.LatLng object representing to the target location,
- * a marker record from the {@link #cfg-markers markers} store, or to the designated
- * coordinates hash of the form:
- *
- * { latitude: 37.381592, longitude: -122.135672 }
- *
- * @param {Object/Ext.data.Model/google.maps.LatLng} coordinates Object representing the
- * desired latitude and longitude upon which to center the map.
- */
- setMapCenter: function(coordinates) {
- var me = this,
- map = me.getMap(),
- mapOptions = me.getMapOptions(),
- gm = (window.google || {}).maps,
- marker;
- if (gm) {
- if (!coordinates) {
- if (map && map.getCenter) {
- coordinates = map.getCenter();
- } else if (mapOptions.hasOwnProperty('center')) {
- coordinates = mapOptions.center;
- } else {
- coordinates = new gm.LatLng(37.381592, -122.135672);
- }
- }
- // Palo Alto
- else if (coordinates.isModel) {
- var marker = me.getMarkerForRecord(coordinates);
- coordinates = marker && marker.position;
- }
- if (coordinates && !(coordinates instanceof gm.LatLng) && 'longitude' in coordinates) {
- coordinates = new gm.LatLng(coordinates.latitude, coordinates.longitude);
- }
- if (!map) {
- mapOptions.center = mapOptions.center || coordinates;
- me.renderMap();
- map = me.getMap();
- }
- if (map && coordinates instanceof gm.LatLng) {
- map.panTo(coordinates);
- } else {
- this.options = Ext.apply(this.getMapOptions(), {
- center: coordinates
- });
- }
- }
- },
- /**
- * Scales and pans the view to ensure that the given markers fits inside the map view.
- * @param {Ext.data.Model[]} records The markers records to fit in view.
- */
- fitMarkersInView: function(records) {
- var me = this,
- map = me.getMap(),
- b2 = map.getBounds(),
- markers = me.getMarkers(),
- gm = (window.google || {}).maps,
- b1, b1ne, b1sw, b2ne, b2sw;
- if (!map || !b2 || !markers) {
- return;
- }
- if (Ext.isEmpty(records)) {
- records = markers.getRange();
- if (Ext.isEmpty(records)) {
- return;
- }
- }
- b1 = new gm.LatLngBounds();
- Ext.each(records, function(record) {
- var marker = me.getMarkerForRecord(record);
- if (marker) {
- b1.extend(marker.getPosition());
- }
- });
- b1ne = b1.getNorthEast();
- b1sw = b1.getSouthWest();
- b2ne = b2.getNorthEast();
- b2sw = b2.getSouthWest();
- if ((b1ne.lat() - b1sw.lat()) > (b2ne.lat() - b2sw.lat()) || (b1ne.lng() - b1sw.lng()) > (b2ne.lng() - b2sw.lng())) {
- map.fitBounds(b1);
- } else {
- map.panToBounds(b1);
- }
- },
- /**
- * @private
- */
- onZoomChange: function() {
- var mapOptions = this.getMapOptions(),
- map = this.getMap(),
- zoom;
- zoom = (map && map.getZoom) ? map.getZoom() : mapOptions.zoom || 10;
- this.options = Ext.apply(mapOptions, {
- zoom: zoom
- });
- this.fireEvent('zoomchange', this, map, zoom);
- },
- /**
- * @private
- */
- onTypeChange: function() {
- var mapOptions = this.getMapOptions(),
- map = this.getMap(),
- mapTypeId;
- mapTypeId = (map && map.getMapTypeId) ? map.getMapTypeId() : mapOptions.mapTypeId;
- this.options = Ext.apply(mapOptions, {
- mapTypeId: mapTypeId
- });
- this.fireEvent('typechange', this, map, mapTypeId);
- },
- /**
- * @private
- */
- onCenterChange: function() {
- var mapOptions = this.getMapOptions(),
- map = this.getMap(),
- center;
- center = (map && map.getCenter) ? map.getCenter() : mapOptions.center;
- this.options = Ext.apply(mapOptions, {
- center: center
- });
- this.fireEvent('centerchange', this, map, center);
- },
- doDestroy: function() {
- Ext.destroy(this.getGeo());
- var map = this.getMap();
- if (map && (window.google || {}).maps) {
- google.maps.event.clearInstanceListeners(map);
- }
- this.callParent();
- },
- privates: {
- // See google.map.Marker API
- // https://developers.google.com/maps/documentation/javascript/3.exp/reference#Marker
- markerEvents: [
- 'click',
- 'dblclick',
- 'drag',
- 'dragend',
- 'dragstart',
- 'mousedown',
- 'mouseout',
- 'mouseover',
- 'mouseup',
- 'rightclick'
- ],
- getMarkerForRecord: function(record) {
- var expando = record && Ext.getExpando(record, this.getId());
- return (expando && expando.marker) || null;
- },
- buildMarkerOptions: function(record, tpl) {
- var options = tpl.apply(record.getData(true)),
- gm = (window.google || {}).maps,
- animation = options.animation;
- if (typeof animation === 'string') {
- options.animation = gm.Animation[animation] || null;
- }
- return options;
- },
- addMarkers: function(records) {
- var me = this,
- eid = me.getId(),
- map = me.getMap(),
- tpl = me.getMarkerTemplate(),
- gm = (window.google || {}).maps,
- store = me.getMarkers(),
- events = me.markerEvents;
- Ext.each(records, function(record) {
- var index = store.indexOf(record),
- options = me.buildMarkerOptions(record, tpl),
- marker = new gm.Marker(Ext.apply(options, {
- map: map
- })),
- listeners = events.map(function(type) {
- return marker.addListener(type, function(event) {
- me.fireEvent('marker' + type, me, {
- index: index,
- record: record,
- marker: marker,
- event: event
- });
- });
- });
- Ext.setExpando(record, eid, {
- listeners: listeners,
- marker: marker
- });
- });
- },
- removeMarkers: function(records) {
- var eid = this.getId();
- Ext.each(records, function(record) {
- var expando = Ext.getExpando(record, eid),
- marker = expando && expando.marker;
- if (marker) {
- marker.setMap(null);
- Ext.each(expando.listeners || [], function(listener) {
- listener.remove();
- });
- }
- Ext.setExpando(record, eid, undefined);
- });
- },
- refreshMarkers: function(records) {
- var me = this,
- tpl = me.getMarkerTemplate(),
- count = records.length,
- record, marker, i;
- for (i = 0; i < count; ++i) {
- record = records[i];
- marker = me.getMarkerForRecord(record);
- if (marker) {
- marker.setOptions(me.buildMarkerOptions(record, tpl));
- }
- }
- },
- onMarkersAdd: function(collection, details) {
- this.addMarkers(details.items);
- },
- onMarkersRemove: function(collection, details) {
- this.removeMarkers(details.items);
- },
- onMarkerChange: function(collection, details) {
- this.refreshMarkers([
- details.item
- ]);
- }
- }
- });
- /**
- * Provided for convenience, this model exposes the default Google Map Marker options.
- *
- * See https://developers.google.com/maps/documentation/javascript/3.exp/reference#MarkerOptions
- *
- * ## Fields
- *
- * - position {Object} - The marker position (required)
- * - position.lat {Number} - Latitude in degrees
- * - position.lng {Number} - Longitude in degrees
- * - title {String} - The rollover text (default: null)
- * - animation {String/Number} - The animation to play when the marker is added to the map.
- * Can be either null (no animation), a string ("BOUNCE" or "DROP"") or a value from
- * [google.maps.Animation](https://developers.google.com/maps/documentation/javascript/3.exp/reference#Animation)
- * - clickable {Boolean} - Whether the marker receives mouse and touch events (default: true)
- * - draggable {Boolean} - Whether the marker can be dragged (default: false)
- * - draggable {Boolean} - Whether the marker is visible (default: true)
- *
- * ## Custom model
- *
- * It's not required to inherit from this model in order to display markers. By providing the
- * suitable {@link Ext.ux.google.Map#cfg-markerTemplate markerTemplate}, marker options can
- * be extracted from any records, for example:
- *
- * Ext.define('MyApp.model.Office', {
- * extend: 'Ext.data.Model',
- * fields: [
- * 'name',
- * 'address',
- * 'latitute',
- * 'longitude'
- * ]
- * });
- *
- * and the associated view config:
- *
- * {
- * xtype: 'map',
- * store: 'offices',
- * markerTemplate: {
- * title: '{name}',
- * position: {
- * lat: '{latitute}',
- * lng: '{longitude}'
- * }
- * }
- * }
- *
- */
- Ext.define('Ext.ux.google.map.Marker', {
- extend: 'Ext.data.Model',
- fields: [
- {
- name: 'position',
- type: 'auto'
- },
- {
- name: 'title',
- type: 'string',
- defaultValue: null
- },
- {
- name: 'animation',
- type: 'number',
- defaultValue: 'DROP',
- persist: false
- },
- {
- name: 'clickable',
- type: 'boolean',
- defaultValue: true,
- persist: false
- },
- {
- name: 'draggable',
- type: 'boolean',
- defaultValue: false,
- persist: false
- },
- {
- name: 'visible',
- type: 'boolean',
- defaultValue: true,
- persist: false
- }
- ]
- });
|