12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445 |
- /**
- * 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
- }
- ]
- });
|