store.providerFor(calendar, function(err, provider) { var caps = provider.calendarCapabilities( calendar ); if (!caps.canCreateEvent) { if (callback) { nextTick(callback); } return; } var option; var element = this.getEl('calendarId'); option = document.createElement('option'); if (id === Local.calendarId) { option.text = navigator.mozL10n.get('calendar-local'); option.setAttribute('data-l10n-id', 'calendar-local'); } else { option.text = calendar.remote.name; } option.value = id; element.add(option); if (callback) { nextTick(callback); } if (this.onaddcalendar) { this.onaddcalendar(calendar); } }.bind(this));
handleAuthenticate: function(account, callback) { if (!account) { return console.error('attempting to trigger reauth without an account'); } // only trigger notification the first time there is an error. if (!account.error || account.error.count !== 1) { return nextTick(callback); } var lock = navigator.requestWakeLock('cpu'); var title = navigator.mozL10n.get('notification-error-sync-title'); var description = navigator.mozL10n.get('notification-error-sync-description'); var url = this.accountErrorUrl + account._id; notification.app = this.app; notification.send(title, description, url, () => { callback && callback(); lock.unlock(); }); }
syncEvents: function(account, calendar, callback) { var self = this; if (this.bailWhenOffline(callback)) { return; } if (!calendar._id) { throw new Error('calendar must be assigned an _id'); } // Don't attempt to sync when provider cannot // or we have matching tokens if ((calendar.lastEventSyncToken && calendar.lastEventSyncToken === calendar.remote.syncToken)) { return nextTick(callback); } this._cachedEventsFor(calendar, function(err, results) { if (err) { callback(err); return; } self._syncEvents( account, calendar, results, callback ); }); },
eventCapabilities: function(event, callback) { if (event.remote.isRecurring) { // XXX: for now recurring events cannot be edited nextTick(function() { callback(null, { canUpdate: false, canDelete: false, canCreate: false }); }); } else { var calendarStore = this.app.store('Calendar'); calendarStore.get(event.calendarId, function(err, calendar) { if (err) { return callback(err); } var caps = this.calendarCapabilities( calendar ); callback(null, { canCreate: caps.canCreateEvent, canUpdate: caps.canUpdateEvent, canDelete: caps.canDeleteEvent }); }.bind(this)); } },
test('multiple expansions', function(done) { var dates = []; subject.once('expandComplete', function() { done(function() { assert.equal(expandStartEvents, 1, 'calls expand once'); assert.deepEqual( dates, [ new Date(2012, 1, 1), new Date(2012, 8, 1), new Date(2012, 10, 7) ] ); }); }); subject.expand = function(date, cb) { dates.push(date); nextTick(cb); }; // should actually trigger because its the first // item in the queue... subject.queueExpand(new Date(2012, 1, 1)); nextTick(function() { subject.queueExpand(new Date(2012, 7, 7)); }); // should be skipped because the next is greater and // we are still processing the first item in th queue. subject.queueExpand(new Date(2012, 2, 7)); // queued subject.queueExpand(new Date(2012, 8, 1)); // should be skipped its less then others subject.queueExpand(new Date(2012, 1, 2)); nextTick(function() { // after the second expansion this fires // so should the final expansion this tests // some complicated async ordering. subject.queueExpand(new Date(2012, 10, 7)); }); });
_init: function() { // quick hack for today button var tablist = document.querySelector('#view-selector'); var today = tablist.querySelector('.today a'); var tabs = tablist.querySelectorAll('[role="tab"]'); this._showTodayDate(); this._syncTodayDate(); today.addEventListener('click', (e) => { var date = new Date(); this.timeController.move(date); this.timeController.selectedDay = date; e.preventDefault(); }); // Handle aria-selected attribute for tabs. tablist.addEventListener('click', (event) => { if (event.target !== today) { AccessibilityHelper.setAriaSelected(event.target, tabs); } }); this.setCurrentTimeFormat(); // re-localize dates on screen this.observeDateLocalization(); this.timeController.observe(); notificationsController.observe(); periodicSyncController.observe(); // turn on the auto queue this means that when // alarms are added to the database we manage them // transparently. Defaults to off for tests. this.store('Alarm').autoQueue = true; this.timeController.move(new Date()); this.view('TimeHeader', (header) => header.render()); this.view('CalendarColors', (colors) => colors.render()); document.body.classList.remove('loading'); // at this point we remove the .loading class and user will see the main // app frame performance.domLoaded(); this._routes(); var recurringEventsController = new RecurringEventsController(this); this.observePendingObject(recurringEventsController); recurringEventsController.observe(); this.recurringEventsController = recurringEventsController; // go ahead and show the first time use view if necessary this.view('FirstTimeUse', (ftu) => ftu.doFirstTime()); nextTick(() => this.view('Errors')); },
subject.handleAuthenticate(account, function() { nextTick(function() { done(function() { assert.ok(lock.mIsUnlocked, 'unlocks'); assert.ok(sent, 'sends notification'); assert.equal(sent[2], expectedURL, 'sends to modify account'); }); }); });
subject.getAccount(account, function() { assert.ok(firedBefore, 'fires before event'); assert.ok(!firedAfter, 'after fires after callback'); nextTick(function() { done(function() { assert.ok(firedAfter, 'fires after'); }); }); });
eventCapabilities: function(event, callback) { var caps = this.calendarCapabilities(); nextTick(function() { callback(null, { canCreate: caps.canCreateEvent, canUpdate: caps.canUpdateEvent, canDelete: caps.canDeleteEvent }); }); }
calendar.sync = function(acc, calendar, callback) { assert.equal(accModel._id, acc._id); assert.equal(calendar.accountId, acc._id); nextTick(function() { callback(); if (!--pendingCalendarSync) { assert.notEqual(lastCalendar._id, calendar._id); } else { lastCalendar = calendar; assertDoesNotEmit('syncComplete'); } }); };
view: function(name, cb) { if (name in this._views) { debug('Found view named ', name); var view = this._views[name]; return cb && nextTick(() => cb.call(this, view)); } if (name in Views) { debug('Must initialize view', name); this._initView(name); return this.view(name, cb); } var snake = snakeCase(name); debug('Will try to load view', name); require([ 'views/' + snake ], (aView) => { debug('Loaded view', name); Views[name] = aView; return this.view(name, cb); }); },
trans.oncomplete = function sendNotification() { // its possible that there are no results // to the get operations (because events were removed) // we gracefully handle that by ignoring the alarm // when no associated records can be found. if (!dbAlarm || !event || !busytime) { debug('failed to load records', dbAlarm, event, busytime); return nextTick(callback); } var endDate = Calc.dateFromTransport(busytime.end); debug('trigger?', endDate, now); // if event has not ended yet we can send an alarm if (endDate > now) { // we need a lock to ensure we actually fire the notification self._sendAlarmNotification(alarm, event, busytime, callback); } else { callback(); } };
controller.queryCache = function() { // wait for _renderBusytime to complete nextTick(done); calledCachedWith = arguments; return slice; };
subject.expand = function(date, cb) { dates.push(date); nextTick(cb); };
spyHandler = function(cb) { nextTick(cb.bind(this, null, true)); };
_allCached: function(callback) { var list = this._cached; nextTick(function() { callback(null, list); }); },
function checkAsync(id, value) { nextTick(function() { checkboxes[id].checked = !!value; triggerEvent(checkboxes[id], 'change'); }); }
account.sync = function() { var args = Array.slice(arguments); var cb = args.pop(); nextTick(cb.bind(this, err)); };
provider[providerMethod] = function() { var args = Array.slice(arguments); var cb = args.pop(); nextTick(cb.bind(null, err)); };
provider.deleteEvent = function(model, callback) { nextTick(callback.bind(null, err)); };
_dispatchAlarm: function(alarm, trans, callback) { // a valid busytimeId will never be zero so ! is safe. if (!alarm._id || !alarm.busytimeId || !alarm.eventId) { return nextTick(callback); } var now = new Date(); var busytimeStore = this.app.store('Busytime'); var eventStore = this.app.store('Event'); var alarmStore = this.app.store('Alarm'); var self = this; if (!trans) { trans = eventStore.db.transaction(['events', 'busytimes', 'alarms']); } var event; var busytime; var dbAlarm; // trigger callback in all cases... trans.onerror = trans.onabort = callback; trans.oncomplete = function sendNotification() { // its possible that there are no results // to the get operations (because events were removed) // we gracefully handle that by ignoring the alarm // when no associated records can be found. if (!dbAlarm || !event || !busytime) { debug('failed to load records', dbAlarm, event, busytime); return nextTick(callback); } var endDate = Calc.dateFromTransport(busytime.end); debug('trigger?', endDate, now); // if event has not ended yet we can send an alarm if (endDate > now) { // we need a lock to ensure we actually fire the notification self._sendAlarmNotification(alarm, event, busytime, callback); } else { callback(); } }; alarmStore.get(alarm._id, trans, function getAlarm(err, record) { dbAlarm = record; }); eventStore.get(alarm.eventId, trans, function getEvent(err, record) { event = record; }); busytimeStore.get(alarm.busytimeId, trans, function getBusytime (err, record) { busytime = record; }); },
subject.onrender = function() { children = subject.calendars.children; nextTick(done); };