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