Ext.ns('the_conf_app', 'the_conf_app.views'); launchApp = function(){ var splash = Ext.create({xtype:'splashscreen'}); splash.splashIn(); maybelog('launching app'); if (false && typeof WebKitPoint != 'function' && typeof WebKitPoint != 'object'){ document.body.innerHTML = 'This app works only in Webkit Browsers, like Safari, Chrome, iPhone, Android. It does not work in Firefox or Internet Explorer. Sorry. Go back .'; return; } var options = { title: get_option('title'), shortUrl: 'innovation-2013', twitterSearch: get_option('twitter_search'), }; maybeCheckForDataUpdates(); loadStores(); maybelog('instantiating app'); if (the_conf_app.App != undefined){ maybelog('doing layout'); the_conf_app.App.doLayout(); } else{ the_conf_app.App = new the_conf_app.AppObject(options); the_conf_app.App.on('show',splash.splashOut,splash); the_conf_app.App.body.on('mousewheel', function(event, el) { var offset, scroller, _results; offset = Ext.util.Offset(); _results = []; while (el !== document.body) { if (el.className.indexOf("x-scroller-parent") > 0) { scroller = Ext.ScrollManager.get(el.firstChild.id); if (scroller) { scroller.fireEvent('scrollstart', scroller, event); offset.y = event.browserEvent.wheelDelta; scroller.scrollBy(offset, true); scroller.snapToBoundary(true); scroller.fireEvent('scrollend', scroller, offset); break; } } _results.push(el = el.parentNode); } return _results; }); setTimeout(function(){the_conf_app.App.show('fade');},2000); // The delay is literally just to give the splash screen a chance to show. } } var loadedStoresAlready = false; loadStores = function(forceOnline,specificStore){ if (forceOnline === undefined){ forceOnline = false; } // Load the the_conf_app.SpeakerStore Store the_conf_app.SpeakerStore.load(); // Load the the_conf_app.AboutListStore Store // Load the the_conf_app.SessionListStore Store the_conf_app.SessionListStore.load(); // Load the the_conf_app.MyScheduleStore Store loadedStoresAlready = true; } reloadStore = function(key){ loadStores(true,key); } maybeCheckForDataUpdates = function(){ if (get_option('use_manifest') == 1){ checkForDataUpdates(); } } checkForDataUpdates = function(){ the_conf_app.StoreStatusStore.on('load',lambalambdalambda = function(_this,records,successful){ // We'll load the online Store Status Store to get the timestamps for // the speakers and sessions. We can then compare then against // the offline data (if any exists), and if there's a change, then // we can trigger loading the online Speaker and Session stores var offline_record,offline_index,stores_to_update = new Array; for(var r = 0; r < records.length; r++){ offline_index = the_conf_app.OfflineStoreStatusStore.find('store',records[r].data.store); if (offline_index >= 0){ offline_record = the_conf_app.OfflineStoreStatusStore.getAt(offline_index); if (offline_record.data.timestamp < records[r].data.timestamp){ stores_to_update.push(records[r].data.store);; maybelog('update available for ' + records[r].data.store); } } } if (stores_to_update.length){ // when we've discovered there are updates, we don't actually want to write // the new timestamps to LocalStorage, so the same state is entered next time // the app loads. the_conf_app.App.showPopup({ id: 'updating', title: 'Updating', html: 'Downloading updates from the server. Please standby.', spinner: 'black x48' }); // Great. I've got a mask showing. Now, let's update the stores var stores_done_updating = 0; for (var s = 0; s < stores_to_update.length; s++){ switch(stores_to_update[s]){ } } // Now, setup a little loop to check when the number of stores_done_updating // equals the length of stores_to_update var elapsed = 0, interval = 500, allowed = 60000, display_for = 2000, action_completed = false; var updateInterval = setInterval(function(){ elapsed += interval; if (!action_completed && stores_done_updating >= stores_to_update.length){ action_completed = true; _this.un('load',lambalambdalambda); _this.fireEvent('load',_this,records,successful); } if (elapsed >= display_for){ if (action_completed){ clearInterval(updateInterval); the_conf_app.App.hidePopup('updating'); var current_page = the_conf_app.App.getActiveItem(); if (current_page.xtype == 'sessionlist'){ maybelog('updating session list'); current_page.hasInitializedDate = false; current_page.checkActiveDate(); } } else if (elapsed >= allowed){ clearInterval(updateInterval); maybelog('problem updating data'); the_conf_app.App.hidePopup('updating'); the_conf_app.App.showPopup({ id: 'update_error', title: 'What Happened?', html: 'Could not communicate with the Mothership', hideOnMaskTap: true }); } } },interval); return false; } }); maybelog('navigator: '+(navigator.onLine ? 'online' : 'offline')); if (navigator.onLine){ maybelog('loading Stores Status Store'); the_conf_app.StoreStatusStore.load(); } } logCacheEvent = function(e){ var cacheStatusValues = []; cacheStatusValues[0] = 'uncached'; cacheStatusValues[1] = 'idle'; cacheStatusValues[2] = 'checking'; cacheStatusValues[3] = 'downloading'; cacheStatusValues[4] = 'updateready'; cacheStatusValues[5] = 'obsolete'; var online, status, type, message; online = (navigator.onLine) ? 'yes' : 'no'; status = cacheStatusValues[this.status]; type = e.type; message = 'online: ' + online; message+= ', event: ' + type; message+= ', status: ' + status; if (type == 'error' && navigator.onLine) { message+= ' There was an unknown error, check your Cache Manifest.'; } maybelog('Event: ' + message); } newCacheReady = function(){ maybelog('It is ready...'); this.swapCache(); Ext.Msg.confirm( 'Software Update', 'A new version of this app is ready. The app must be restarted. Restart now?', function(button){ if (button == 'yes'){ window.location.reload(); } } ); } if (!window.applicationCache) { maybelog('No Cache Manifest listed on the tag.') } else{ window.applicationCache.addEventListener('cached', logCacheEvent, false); window.applicationCache.addEventListener('checking', logCacheEvent, false); window.applicationCache.addEventListener('downloading', logCacheEvent, false); window.applicationCache.addEventListener('error', logCacheEvent, false); window.applicationCache.addEventListener('noupdate', logCacheEvent, false); window.applicationCache.addEventListener('obsolete', logCacheEvent, false); window.applicationCache.addEventListener('progress', logCacheEvent, false); window.applicationCache.addEventListener('updateready', logCacheEvent, false); window.applicationCache.addEventListener('updateready', newCacheReady, false); } Ext.setup({ statusBarStyle: 'black', tabletStartupScreen: 'http://www.innovationpartnership.ca/wp-content/plugins/the-conference-app/the-app/resources/img/startup_640.png', phoneStartupScreen: 'http://www.innovationpartnership.ca/wp-content/plugins/the-conference-app/the-app/resources/img/startup.png', icon: 'http://www.innovationpartnership.ca/wp-content/plugins/the-conference-app/the-app/resources/img/icon_114.png', glossOnIcon: false, onReady: launchApp }); /** * @class Ext.data.TwitterProxy * @extends Ext.data.ScriptTagProxy * * This simple proxy allows us to use Twitter's JSON-P API to search for tweets. All we're really doing in this * class is setting a few defaults (such as the number of tweets per page and a simple JSON Reader), and using * any Filters attached to the read Operation to modify the request url (see buildRequest). * */ Ext.data.TwitterProxy = Ext.extend(Ext.data.ScriptTagProxy, { //this is the url we always query when searching for tweets url: 'http://www.innovationpartnership.ca/app/innovation-2013/tweets', filterParam: undefined, nextResults: {}, constructor: function(config) { config = config || {}; Ext.applyIf(config, { extraParams: { suppress_response_codes: true }, reader: { type: 'json', root: function(d){ var looper = (typeof d.results != 'undefined' ? d.results : d.statuses) if (!looper){ return []; } var now = new Date(); var now_utc = Date.parse(now.toUTCString()); for(var i = 0; i < looper.length; i++){ looper.created_ago = getCreatedAgo(looper.created_at,now_utc); } return looper; } } }); Ext.data.TwitterProxy.superclass.constructor.call(this, config); }, /** * We need to add a slight customization to buildRequest - we're just checking for a filter on the * Operation and adding it to the request params/url, and setting the start/limit if paging */ buildRequest: function(operation) { var request = Ext.data.TwitterProxy.superclass.buildRequest.apply(this, arguments), filter = operation.filters[0], params = request.params; Ext.apply(params, { count: operation.limit, page: operation.page, next_results: ((operation.page == 1 || typeof operation.page == 'undefined' || typeof this.nextResults[filter.value] == 'undefined') ? '' : this.nextResults[filter.value]) }); if (filter) { Ext.apply(params, { q: filter.value }); //as we're modified the request params, we need to regenerate the url now request.url = this.buildUrl(request); } return request; }, createRequestCallback: function(request, operation, callback, scope) { var me = this; return function(response) { var reader = me.getReader(), result = reader.read(response); me.nextResults[request.params.q] = (typeof response.search_metadata == 'object' ? response.search_metadata.next_results : ''); Ext.apply(operation, { response : response, resultSet: result }); operation.setCompleted(); operation.setSuccessful(); if (typeof callback == 'function') { callback.call(scope || me, operation); } me.afterRequest(request, true); }; } }); Ext.data.ProxyMgr.registerType('twitter', Ext.data.TwitterProxy); getCreatedAgo = function(created_at,now_utc){ var created_at_utc = Date.parse(created_at); var difference = Math.round((now_utc - created_at_utc)/1000); var difference_str, unit; if (difference < 60){ difference_str = difference + ' ' + (difference == 1 ? 'second' : 'seconds'); } else if (difference < 60*60){ difference = Math.round(difference/(60)); difference_str = difference + ' ' + (difference == 1 ? 'minute' : 'minutes'); } else if (difference < 60*60*24){ difference = Math.round(difference/(60*60)); difference_str = difference + ' ' + (difference == 1 ? 'hour' : 'hours'); } else if (difference < 60*60*24*7){ difference = Math.round(difference/(60*60*24)); difference_str = difference + ' ' + (difference == 1 ? 'day' : 'days'); } else{ difference = Math.round(difference/(60*60*24*7)); difference_str = difference + ' ' + (difference == 1 ? 'week' : 'weeks'); } return difference_str + ' ' + 'ago';; } Ext.regModel('Proposal', { fields: ["id","title","url","description","day","time","end_time","pretty_time","date","topics","room","proposal_type","sponsor","speakers","proposals","parent_id"] }); Ext.regModel('Speaker', { fields: ["id","first_name","last_name","name","position","affiliation","bio","twitter","url","photo","website","pretty_website","proposals","group","members","hide_from_lineup"] }); Ext.regModel('Tweet', { fields: ["id","id_str","text","to_user_id","to_user","from_user","created_at","profile_image_url","created_ago"] ,proxy: "twitter" }); Ext.regModel('TwitterSearch', { fields: ["id","query"] ,hasMany: {"model":"Tweet","name":"tweets","filterProperty":"query","storeConfig":{"pageSize":20,"remoteFilter":true,"clearOnPageLoad":false}} }); Ext.regModel('MySchedule', { fields: ["id","proposal_id","speaker_id"] }); Ext.data.OfflineStore = Ext.extend(Ext.data.Store, { // It turns out that running a sync on a store // bound to a list in the app without suspending // events causes lag in the app, to the point of // javascript execution timeout. This method // suspends events, runs its own version of the sync // then resumes events. sync_from_store: function(store){ maybelog('syncing from store '+store.model.modelName); // Suspend events, discarding anything that is fired this.suspendEvents(false); // remove all records from this (offline) store this.clearFilter(); this.remove(this.getRange()); // add all records from the passed (online) store store.each(function (record) { this.add(record.data); },this); // Ooops... Can't run sync with events suspended. Have to run it manually var options = {}, toUpdate = this.getUpdatedRecords(), toDestroy = this.getRemovedRecords(), toCreate = this.getNewRecords(); options.create = toCreate; options.update = toUpdate; options.destroy = toDestroy; this.proxy.batchOrder = 'destroy,create,update'; maybelog(this.proxy.batchOrder); this.proxy.batch(options, this.getBatchListeners()); this.resumeEvents(); this.fireEvent('load',this); } }); the_conf_app.SpeakerStore = new Ext.data.Store({ model: "Speaker" ,sorters: "last_name" ,getGroupString: function(r){if (r.get('last_name') == '') return '-'; else return r.get('last_name')[0];} ,proxy: {"type":"scripttag","url":"http:\/\/www.innovationpartnership.ca\/app\/innovation-2013\/speakers","reader":{"type":"json","root":"speakers"}} ,isLoadedEh: false }); the_conf_app.SpeakerStore.on('load',function(){this.isLoadedEh = true;}); getSpeakerStore = function(){return the_conf_app.SpeakerStore; } the_conf_app.AboutListStore = new Ext.data.Store({ fields: ["name","card"] ,isLoadedEh: false }); the_conf_app.AboutListStore.on('load',function(){this.isLoadedEh = true;}); getAboutListStore = function(){return the_conf_app.AboutListStore; } the_conf_app.SessionListStore = new Ext.data.Store({ model: "Proposal" ,sorters: "time" ,getGroupString: function(r){return r.get('pretty_time');} ,proxy: {"type":"scripttag","url":"http:\/\/www.innovationpartnership.ca\/app\/innovation-2013\/proposals","reader":{"type":"json","root":"proposals"}} ,isLoadedEh: false }); the_conf_app.SessionListStore.on('load',function(){this.isLoadedEh = true;}); getSessionListStore = function(){return the_conf_app.SessionListStore; } the_conf_app.MyScheduleStore = new Ext.data.Store({ model: "MySchedule" ,proxy: {"type":"localstorage","id":"innovation-2013_MySchedule_1e08e"} ,isLoadedEh: false }); the_conf_app.MyScheduleStore.on('load',function(){this.isLoadedEh = true;}); getMyScheduleStore = function(){return the_conf_app.MyScheduleStore; } the_conf_app.cfg = {}; the_conf_app.AppObject = Ext.extend(Ext.TabPanel, { fullscreen: true, cache: null, hidden: true, tabBar: { ui: 'gray', dock: 'bottom', layout: { pack: 'center' } }, listeners: { beforerender: function(){ if (get_option('use_manifest') == '1'){ this.addDocked({ xtype: 'downloadprogress' }); } } }, cardSwitchAnimation: false, initComponent: function() { if (true || navigator.onLine) { this.items = [{"xtype":"sessionlist","iconCls":"time","title":"Sessions","confTitle":"INNOVATION 2013","shortUrl":"innovation-2013","listeners":{"afterrender":{"fn":this.setupTabBarClickEvent,"scope":this}}},{"xtype":"speakerlist","iconCls":"team1","title":"Speakers","listeners":{"afterrender":{"fn":this.setupTabBarClickEvent,"scope":this}}},{"xtype":"tweetlist","iconCls":"chat","title":"Tweets","hashtag":"innovationgroup"},{"title":"About","xtype":"aboutlist","iconCls":"info","pages":[{"title":"Welcome","card":{"xtype":"htmlpage","content":"
\n \n

<\/h3>\n

<\/p>\n

Welcome to INNOVATION 2013!<\/strong><\/p>\n

This year's conference theme, “The Changing Landscape of Partnerships & Commercialization,” guided the Program Committee in the design of sessions throughout the conference that will challenge and inform attendees on the broad spectrum of subjects related to relationships among and between research organizations, industry, investors and entrepreneurs.<\/p>\n

You will meet experts in social venture financing and student entrepreneurship in addition to many others including those in the more traditional mandates of business development, intellectual asset management and new business creation. Be sure to watch for and engage young entrepreneurs participating in the conference; reconnect with colleagues and make new connections.<\/p>\n

INNOVATION 2013<\/strong> will generate practical ideas and real-world solutions to stimulate Canada’s innovation performance. Keynote speakers, interactive panel sessions, debates and thought provoking interviews will examine the challenges and opportunities for creating productive partnerships across a shifting innovation landscape.<\/p>\n

On behalf of the Board of Directors of ACCT Canada and the INNOVATION 2013 Program Committee, we hope you enjoy the conference.<\/p>\n<\/div>"}},{"title":"Organizing Committee","card":{"xtype":"htmlpage","content":"

\n \n

Organizing Committee <\/strong> <\/h3>\n

Jennifer Fraser, University of Toronto
\n John Wilson, Brock University
\n Carol Miernicki, PARTEQ Innovations\/Queens University
\n Suzanne Pellerin, Canadian Intellectual Property Office
\n Stace Wills, Innovate Calgary
\n Helen Shannon, Innovate Calgary
\n Catherine Geci, University of Ottawa
\n Catherine Eckenswiller, National Research Council of Canada
\n Mike Fenwick, Bereskin & Parr
\n Karen Zavitz, OPIC\/University of Ontario Institute of Technology
\n Mary Beshai, Canadian Institutes of Health Research
\n Krissy Davidge, Canadian Institutes of Health Research
\n Janet E. Scholz, President & CEO, ACCT Canada <\/p>\n

Program Coordinator: <\/strong>
\n Beverley Sheridan, Technology Now<\/p>\n

Sponsorship Coordinator: <\/strong>
\n Trish Mongeon, Philanthropy Touch\/Mongeon Consulting <\/p>\n

Conference Management by Funnel Communications Inc: <\/strong>
\n Anthony Laycock, Jason Bell & John Chagnon <\/p>\n

<\/p>\n

Contacts<\/strong><\/p>\n

Event Coordinator<\/strong>
\n Anthony Laycock, President, Funnel Communications Inc.,
\n 189 Queen Street East, Suite 1, Toronto, ON M5A 1S2
\n Tel: 416-968-0260 • anthony@funnel.ca<\/a> • www.funnel.ca<\/a><\/p>\n

Conference Coordinator<\/strong>
\n Janet Scholz, President, ACCT Canada,
\n130-3553 31st Street NW, Calgary, AB T2L 2K7
\n Tel: (403) 270-2449 •
janet.scholz@acctcanada.ca<\/a> <\/p>\n

Webmaster<\/strong>
\n Jason Bell, Webmaster, Funnel Communications Inc.,
\n 189 Queen Street East, Suite 1, Toronto, ON M5A 1S2
\n Tel: 416-968-0260 •
jason@funnel.ca<\/a> • www.funnel.ca<\/a><\/p>\n

<\/p>\n<\/div>"}},{"title":"Partners & Supporters","card":{"xtype":"htmlpage","content":"

\n \n

Thank you to our conference partners & supporters!<\/strong><\/h3>\n

<\/p>\n\n

<\/p>\n<\/div>"}}],"listeners":{"afterrender":{"fn":this.setupTabBarClickEvent,"scope":this}}}]; this.on('cardswitch',function(c){ this.cardJustSwitched = true; }, this); } else { this.on('render', function(){ this.el.mask('No internet connection.'); }, this); } the_conf_app.cfg = {}; the_conf_app.cfg.shortUrl = this.shortUrl; the_conf_app.cfg.title = this.title; the_conf_app.AppObject.superclass.initComponent.call(this); }, setupTabBarClickEvent: function(c){ var tabs = c.ownerCt, bar = tabs.tabBar, tab; bar.items.each(function(item){ if(item.card == c){ tab = item; return false; } }); if(tab){ tab.el.on('click', function(){ // This event fires after the 'cardswitch' event above. // The following if statement makes sure the reset only happens // if the user clicks the tabbar icon while already on that tab. if (!this.cardJustSwitched){ // this == Overall (parent) TabPanel // this.getActiveItem() == The Current Tab this.getActiveItem().setActiveItem(0); // return to the first item (the list) this.getActiveItem().items.each(function(item,i){ // Destroy all but the first item if (i > 0){ item.destroy(); } }); } this.cardJustSwitched = false; },this); } }, isOnline: function(){ return navigator.onLine; }, checkForUpdates: function(){ this.cache.update(); }, showPopup: function(options){ if (this.popupStack == undefined){ this.popupStack = {}; } if (this.popupStack[options.id] == undefined){ this.popupStack[options.id] = new Ext.Panel({ floating: true, modal: true, hideOnMaskTap: (options.hideOnMaskTap ? true : false), centered: true, width: (options.width ? options.width : 300), height: (options.height ? options.height : 200), styleHtmlContent: true, scroll: 'vertical', html: options.html+(options.spinner ? '

' : ''), cls: 'popup '+options.id, dockedItems: [{ dock: 'top', xtype: 'toolbar', title: options.title }] }); } this.popupStack[options.id].show('pop'); }, hidePopup: function(id){ if (this.popupStack != undefined && this.popupStack[id] != undefined){ this.popupStack[id].hide('pop'); } } }); the_conf_app.views.SessionList = Ext.extend(Ext.Panel, { layout: 'card', groupByDay: true, isActivated: false, hasInitializedDate: false, startDateIndex: 0, scroll: 'horizontal', initComponent: function() { //getSessionListStore().addListener('load',this.initializeData,this); this.list = new Ext.List({ grouped: true, itemTpl: ' {title} {room} ', loadingText: false, store: getSessionListStore() }); this.list.on('selectionchange', this.onSelect, this); this.list.on('render', function(){ //this.list.store.load(); this.list.el.mask(' ', 'x-spinner', false); }, this); this.listpanel = new Ext.Panel({ items: this.list, layout: 'fit', dockedItems: [{ xtype: 'toolbar', title: 'INNOVATION 2013' }], listeners: { activate: { fn: function(){ this.list.getSelectionModel().deselectAll(); Ext.repaint(); }, scope: this } } }) this.items = this.listpanel; this.on('activate', this.doActivation, this); the_conf_app.views.SessionList.superclass.initComponent.call(this); }, doActivation: function(){ if (!this.isActivated){ if (this.list.store.isLoadedEh){ this.initializeData(this.list.store); } else{ this.list.store.addListener('load',this.initializeData,this); } this.isActivated = true; } }, checkActiveDate: function(){ if (!this.hasInitializedDate && this.dateButtons) { var currentTime = new Date(), month = currentTime.getMonth() + 1, day = currentTime.getDate(), year = currentTime.getFullYear(); var dateIndex = this.dateButtons.items.findIndex('dateData', month+'/'+day+'/'+year); if (dateIndex !== -1) this.startDateIndex = dateIndex; this.dateButtons.setPressed(this.startDateIndex,true); this.changeDate(this.dateButtons.items.getAt(this.startDateIndex)); //this.doComponentLayout(); this.hasInitializedDate = true; } }, initializeData: function(data) { if (this.groupByDay) { // Gather dates, create a splitbutton around them var dates = data.collect('date'), buttons = [], length = dates.length, i; if (length > 1){ for (i = 0; i < length; i++) { buttons.push({ text: the_conference_day(dates[i]), dateData: dates[i], index: i, scope: this, handler: this.changeDate }); } this.dateButtons = new Ext.SegmentedButton({ items: buttons, defaults: { flex: 1 }, style: 'width: 100%', }); the_conf_app.App.on('orientationchange',this.resizeDateButtons,this); this.listpanel.addDocked({ xtype: 'toolbar', ui: 'gray', items: this.dateButtons, layout: { pack: 'center' } }); if (this.listpanel.isVisible()) { this.checkActiveDate(); } } } // Take off the spinner this.list.el.unmask(); }, changeDate: function(btn) { this.list.store.clearFilter(); this.list.store.filter(new Ext.util.Filter({ filterFn: function(item) { return item.data.date == btn.dateData && (item.data.parent_id == undefined || item.data.parent_id == ''); } })); //'date', btn.dateData); this.list.scroller.scrollTo({y: 0}, false); }, resizeDateButtons: function(){ this.dateButtons.doLayout(); }, onSelect: function(selectionmodel, records){ if (records[0] !== undefined) { var sessionCard = this.add({ xtype: 'sessiondetail', prevCard: this.listpanel, record: records[0] }); // Tell the parent panel to animate to the new card this.setActiveItem(sessionCard, get_option('transition')); } } }); Ext.reg('sessionlist', the_conf_app.views.SessionList);the_conf_app.views.SessionDetail = Ext.extend(Ext.Panel, { scroll: 'vertical', layout: { type: 'vbox', align: 'stretch' }, showSpeakerData: true, cls: 'session-detail', initComponent: function(){ this.dockedItems = [{ xtype: 'toolbar', items: [{ ui: 'back', text: 'Back', scope: this, handler: function(){ this.ownerCt.setActiveItem(this.prevCard, { type: get_option('transition'), reverse: true, scope: this, after: function(){ this.destroy(); } }); } } // TODO: Reimplement faves // , {xtype: 'spacer'}, { // iconCls: 'star', // cls: 'favestar' + (the_conf_app.faveStore.find('proposal_id', this.record.data.id) == -1 ? '' : ' favorited'), // iconMask: true, // ui: 'plain', // scope: this, // handler: function(btn){ // var idx = the_conf_app.faveStore.find('proposal_id', this.record.data.id); // if (idx == -1) { // the_conf_app.faveStore.create({ // proposal_id: this.record.data.id // }); // btn.addCls('favorited'); // } else { // the_conf_app.faveStore.removeAt(idx); // the_conf_app.faveStore.sync(); // btn.removeCls('favorited'); // } // } // } ] }]; this.items = [{ tpl: new Ext.XTemplate( '

{title} {room}

{proposal_type}, {day} at {pretty_time}

{description} '), data: this.record.data, styleHtmlContent: true }]; if (this.showSpeakerData && this.record.get('speakers') && this.record.get('speakers').length) { var speaker_ids = new Array(); var speakers = this.record.get('speakers'); for (var s = 0; s < speakers.length; s++){ speaker_ids.push(speakers[s].id); } var speaker_store = new Ext.data.JsonStore({ model : 'Speaker' }); speaker_store.data = getSpeakerStore().queryBy(function(record,id){ var _id = record.get('id')+''; return (_id.match(new RegExp('^('+speaker_ids.join('|')+')$'))); }); this.speakerList = new Ext.List({ itemTpl: '
style="background-image: url({photo})" >
{name}
{position} , {affiliation}
', store: speaker_store, listeners: { selectionchange: {fn: this.onSpeakerSelect, scope: this} }, scroll: false, autoHeight: true, style: 'width: 100%;' }); this.items.push({ xtype: 'toolbar', title: 'Speakers', ui: 'gray', cls: 'small_title' }) this.items.push(this.speakerList); Ext.repaint(); } if (this.record.get('proposals')){ var embedded_session_ids = new Array(); var embedded_sessions = this.record.get('proposals'); for (var s = 0; s < embedded_sessions.length; s++){ embedded_session_ids.push(embedded_sessions[s]); } var embedded_session_store = new Ext.data.JsonStore({ model : 'Proposal' }); embedded_session_store.data = getSessionListStore().queryBy(function(record,id){ var _id = record.get('id')+''; return (_id.match(new RegExp('^('+embedded_session_ids.join('|')+')$'))); }); embedded_session_store.sort('time'); this.sessionList = new Ext.List({ itemTpl: ' {title} {room}
{day} @ {pretty_time}
', store: embedded_session_store, listeners: { selectionchange: {fn: this.onSessionSelect, scope: this} }, scroll: false, autoHeight: true, style: 'width: 100%;' }); this.items.push({ xtype: 'toolbar', title: 'Sessions', ui: 'gray', cls: 'small_title' }) this.items.push(this.sessionList); Ext.repaint(); } this.listeners = { activate: { fn: function(){ if (this.speakerList) { this.speakerList.getSelectionModel().deselectAll(); } if (this.sessionList) { this.sessionList.getSelectionModel().deselectAll(); } }, scope: this }, afterrender: { fn: function(){ // Somewhere along the way, something creeped in that made // the session details not layout properly on initial click, // but did layout properly when the window was resized. That // made me think to add this in here. Wish I didn't have to. this.doLayout(); }, scope: this }, }; the_conf_app.views.SessionDetail.superclass.initComponent.call(this); }, onSpeakerSelect: function(selectionmodel, records){ if (records[0] !== undefined) { var speakerCard = this.add({ xtype: 'speakerdetail', prevCard: this, record: records[0], showSessionData: true }); // Tell the parent panel to animate to the new card this.ownerCt.setActiveItem(speakerCard, get_option('transition')); } }, onSessionSelect: function(selectModel, records){ if (records[0] !== undefined) { var sessionCard = this.add({ xtype: 'sessiondetail', prevCard: this, record: records[0], showSpeakerData: true }); this.ownerCt.setActiveItem(sessionCard, get_option('transition')); } } }); Ext.reg('sessiondetail', the_conf_app.views.SessionDetail); the_conf_app.views.SpeakerList = Ext.extend(Ext.Panel, { layout: 'card', isActivated: false, initComponent: function() { this.list = new Ext.List({ grouped: true, indexBar: true, itemTpl: '
style="background-image: url({photo})" >
{name}
{position} , {affiliation}
', store: getSpeakerStore(), loadingText: false, listeners: { selectionchange: {fn: this.onSelect, scope: this} } }); this.list.on('render', function(){ this.list.el.mask(' ', 'x-spinner', false); }, this); this.listpanel = new Ext.Panel({ layout: 'fit', items: this.list, dockedItems: [{ xtype: 'toolbar', title: 'Speakers' }], listeners: { activate: { fn: function(){ this.list.getSelectionModel().deselectAll(); Ext.repaint(); }, scope: this } } }); this.items = this.listpanel; this.on('activate', this.doActivation, this); the_conf_app.views.SpeakerList.superclass.initComponent.call(this); }, doActivation: function(){ if (!this.isActivated){ if (this.list.store.isLoadedEh){ this.initializeData(this.list.store); } else{ this.list.store.addListener('load',this.initializeData,this); } this.isActivated = true; } }, initializeData: function(data) { this.list.store.filterBy(function(record,id){ return (!record.get('group') && !record.get('hide_from_lineup')); }); this.list.store.sort(); // Take off the spinner this.list.el.unmask(); }, onSelect: function(sel, records){ if (records[0] !== undefined) { var bioCard = this.add({ xtype: 'speakerdetail', prevCard: this.listpanel, record: records[0] }); this.setActiveItem(bioCard, get_option('transition')); } } }); Ext.reg('speakerlist', the_conf_app.views.SpeakerList); the_conf_app.views.SpeakerDetail = Ext.extend(Ext.Panel, { scroll: 'vertical', showSessionData: true, initComponent: function(){ if (this.record.data.group){ this.record = getSpeakerStore().getById(this.record.data.group); } var toolbar_buttons = new Array(); toolbar_buttons.push({ ui: 'back', text: 'Back', scope: this, handler: function(){ this.ownerCt.setActiveItem(this.prevCard, { type: get_option('transition'), reverse: true, scope: this, after: function(){ this.destroy(); } }); } }); if (this.record.get('twitter')){ toolbar_buttons.push({xtype: 'spacer'}); toolbar_buttons.push({ text: 'Twitter', scope: this, handler: function(){ var TwitterPanel = this.add({ xtype: 'tweetlist', hashtag: this.record.get('twitter'), prevCard: this }); this.ownerCt.setActiveItem(TwitterPanel, { type: 'slide', direction: 'up', scope: this }); } }); } this.dockedItems = [{ xtype: 'toolbar', items: toolbar_buttons }]; this.items = [{ styleHtmlContent: true, tpl: new Ext.XTemplate( '
{bio}'), data: this.record.data }]; if (this.showSessionData && this.record.get('proposals') && this.record.get('proposals').length) { var sessionList = this.getSessionList(this.record.get('proposals')); this.items.push({ xtype: 'toolbar', title: 'Sessions', ui: 'gray', cls: 'small_title' }); this.items.push(sessionList); }; if (this.record.get('members')){ var member_ids = new Array(); var members = this.record.get('members'); for (var m = 0; m < members.length; m++){ member_ids.push(members[m]); } var member_store = new Ext.data.JsonStore({ model : 'Speaker' }); member_store.data = getSpeakerStore().queryBy(function(record,id){ var _id = record.get('id')+''; return (_id.match(new RegExp('^('+member_ids.join('|')+')$'))); }); member_store.each(function(member){ var memberSessionList = this.getSessionList(member.get('proposals')); if (memberSessionList.store.data.length){ this.items.push({ xtype: 'toolbar', title: 'Sessions with ' +member.get('name'), ui: 'gray', cls: 'small_title' }); this.items.push(memberSessionList); } },this); } this.listeners = { activate: { fn: function(){ if (this.selectModel) { this.selectModel.deselectAll(); } }, scope: this } }; the_conf_app.views.SpeakerDetail.superclass.initComponent.call(this); }, viewSession: function(selectModel, records){ if (records[0] !== undefined) { var sessionCard = this.add({ xtype: 'sessiondetail', prevCard: this, record: records[0], showSpeakerData: true }); this.selectModel = selectModel; this.ownerCt.setActiveItem(sessionCard, get_option('transition')); } }, getSessionList: function(sessions){ var session_ids = new Array(); for (var s = 0; s < sessions.length; s++){ if (sessions[s].id != undefined){ session_ids.push(sessions[s].id); } else{ session_ids.push(sessions[s]); } } var session_store = new Ext.data.JsonStore({ model : 'Proposal' }); session_store.data = getSessionListStore().queryBy(function(record,id){ var _id = record.get('id')+''; return (_id.match(new RegExp('^('+session_ids.join('|')+')$'))); }); var sessionList = new Ext.List({ singleSelect: true, itemTpl: ' {title} {room}
{day} @ {pretty_time}
', store: session_store, scroll: false, autoHeight: true, style: 'width: 100%;' }); sessionList.on('selectionchange', this.viewSession, this) return sessionList; } }); Ext.reg('speakerdetail', the_conf_app.views.SpeakerDetail); the_conf_app.views.AboutList = Ext.extend(Ext.Panel, { layout: 'card', initComponent: function(){ the_conf_app.AboutListStore.loadData(this.pages); the_conf_app.AboutListStore.fireEvent('load'); this.list = new Ext.List({ itemTpl: '
{title}
', ui: 'round', store: the_conf_app.AboutListStore, listeners: { selectionchange: {fn: this.onSelect, scope: this} }, title: 'About' }); this.listpanel = new Ext.Panel({ title: 'About', items: this.list, layout: 'fit', dockedItems: { xtype: 'toolbar', title: 'About' } }) this.listpanel.on('activate', function(){ this.list.getSelectionModel().deselectAll(); }, this); this.items = [this.listpanel]; the_conf_app.views.AboutList.superclass.initComponent.call(this); }, onSelect: function(sel, records){ if (records[0] !== undefined) { var newCard = Ext.apply({}, records[0].data.card, { prevCard: this.listpanel, title: records[0].data.title }); this.setActiveItem(Ext.create(newCard), get_option('transition')); } } }); Ext.reg('aboutlist', the_conf_app.views.AboutList);the_conf_app.views.HtmlPage = Ext.extend(Ext.Panel, { scroll: 'vertical', styleHtmlContent: true, initComponent: function(){ var toolbarBase = { xtype: 'toolbar', title: this.title }; if (this.prevCard !== undefined) { toolbarBase.items = { ui: 'back', text: this.prevCard.title, scope: this, handler: function(){ this.ownerCt.setActiveItem(this.prevCard, { type: get_option('transition'), reverse: true }); } } } this.dockedItems = toolbarBase; this.update(this.content); the_conf_app.views.HtmlPage.superclass.initComponent.call(this); } }); Ext.reg('htmlpage', the_conf_app.views.HtmlPage); the_conf_app.views.TweetList = Ext.extend(Ext.Panel, { hashtag: '', layout: 'fit', initComponent: function(){ var toolbarBase = { xtype: 'toolbar', title: this.hashtag, items: [] }; if (this.prevCard){ toolbarBase.items.push({xtype: 'spacer'}); toolbarBase.items.push({ text: 'Close', scope: this, handler: function(){ this.hide({ type: 'slide', direction: 'up', scope: this, after: function(){ this.ownerCt.setActiveItem(this.prevCard); this.destroy(); } }); } }); } this.dockedItems = toolbarBase; var searchModel = Ext.ModelMgr.getModel("TwitterSearch"); var search = new searchModel({ query: this.hashtag }); var store = search.tweets(); var tweetList = { cls: 'timeline', emptyText : '

No tweets found matching that search

', disableSelection: true, xonItemDisclosure: function(record, btn, index) { Ext.Msg.alert('Tap', 'Disclose more info', Ext.emptyFn); }, store: store, plugins: [{ ptype: 'listpaging', autoPaging: false }, { ptype: 'pullrefresh' }], itemCls: 'tweet', itemTpl: new Ext.XTemplate('
style="background-image: url({profile_image_url})" >
{from_user} {created_ago}
{text:this.linkify}
', { linkify: function(value) { value = value.replace(/(http:\/\/[^\s]*)/g, " $1 "); value = value.replace(/@([^ ]+)/g," @$1 "); value = value.replace(/(^| )#([^ ]+)/g,"$1 #$2 "); return value; } } ) }; this.list = new Ext.List(Ext.apply(tweetList,{})); store.load(); this.items = [this.list]; the_conf_app.views.TweetList.superclass.initComponent.call(this); } }); Ext.reg('tweetlist', the_conf_app.views.TweetList); the_conf_app.views.SplashScreen = Ext.extend(Ext.Panel, { fullscreen: true, cls: 'loading', hidden:true, listeners:{ render: function(){ this.el.mask(' ', 'x-spinner', false); } }, initComponent: function(){ the_conf_app.views.SplashScreen.superclass.initComponent.call(this); }, splashIn: function(){ this.show('fade'); }, splashOut: function(e){ this.el.unmask(); var that = this; setTimeout(function(){ that.destroy(); Ext.get(document.body).addCls('loaded'); },2000); } }); Ext.reg('splashscreen', the_conf_app.views.SplashScreen); the_conf_app.views.DownloadProgress = Ext.extend(Ext.Panel, { layout: { type: 'hbox', padding: '5', align: 'left', pack: 'center', styleHtmlContent: true }, dock:'bottom', cls: 'download-progress', id: 'download-progress', hidden: true, showAnimation: {type:'slide',direction:'up',duration:1000}, initComponent: function(){ this.items = [{ html: 'Preparing app for offline usage ', id: 'download-progress-msg' }]; the_conf_app.views.DownloadProgress.superclass.initComponent.call(this); window.applicationCache.addEventListener('cached', this.handleCacheEvent, false); window.applicationCache.addEventListener('checking', this.handleCacheEvent, false); window.applicationCache.addEventListener('downloading', this.handleCacheEvent, false); window.applicationCache.addEventListener('error', this.handleCacheEvent, false); window.applicationCache.addEventListener('noupdate', this.handleCacheEvent, false); window.applicationCache.addEventListener('obsolete', this.handleCacheEvent, false); window.applicationCache.addEventListener('progress', this.handleCacheEvent, false); window.applicationCache.addEventListener('updateready', this.handleCacheEvent, false); }, handleCacheEvent: function(e){ var _this = Ext.getCmp('download-progress'); switch(e.type){ case 'progress': if (_this.isHidden()){ if (_this.rendered){ _this.show(); } else{ _this.on('afterrender',function(){this.show();}); } } if (Ext.get('download-progress-current')){ var current_message = ''; if (typeof e.loaded != 'undefined'){ current_message+= e.loaded; if (typeof e.total != 'undefined'){ current_message+= ' / '+e.total; } } else{ var current_message = Ext.get('download-progress-current').getHTML(); current_message+='.'; if (current_message == '..........'){ current_message = '.'; } } Ext.get('download-progress-current').setHTML(current_message); } break; case 'updateready': case 'cached': try{ _this.down('#download-progress-msg').getEl().setHTML('App is now ready for offline use!!'); } catch(error){} _this.hideMe(); break; case 'error': _this.hideMe(); break; try{ _this.down('#download-progress-msg').getEl().setHTML('Aww snap. Offline caching has failed.'); } catch(error){} _this.hideMe(); break; } }, hideMe: function(delay){ var _this = this; if (typeof delay == 'undefined'){ delay = 3000; } setTimeout(function(){ _this.hide({type:'slide',direction:'up',duration:100}); // Thank you http://www.sencha.com/forum/showthread.php?147839-A-kludge-for-showing-hiding-docked-items&p=650976#post650976 setTimeout(function(){ var parent = _this.up(); if (parent && parent.dockedItems !== undefined) { var i = parent.dockedItems.indexOf(_this); if (i !== -1) { parent.removeDocked(_this, false); parent.insertDocked(i, _this); } } },1000); },delay); } }); Ext.reg('downloadprogress', the_conf_app.views.DownloadProgress);// The Session Detail Pages the_conf_app.views.SessionDetailWithMySchedule = Ext.extend(the_conf_app.views.SessionDetail, { initComponent: function(){ the_conf_app.views.SessionDetailWithMySchedule.superclass.initComponent.call(this); setupMySchedule(this); }, addMyScheduleButton: function(){ this.getDockedItems()[0].add([ {xtype: 'spacer'}, getMyScheduleConfig('proposal',this.record) ]); } }); Ext.ComponentMgr.unregister('sessiondetail'); Ext.reg('sessiondetail', the_conf_app.views.SessionDetailWithMySchedule); // The Speaker Detail Pages the_conf_app.views.SpeakerDetailWithMySchedule = Ext.extend(the_conf_app.views.SpeakerDetail, { initComponent: function(){ the_conf_app.views.SpeakerDetailWithMySchedule.superclass.initComponent.call(this); setupMySchedule(this); }, addMyScheduleButton: function(){ this.getDockedItems()[0].add([ {xtype: 'spacer'}, getMyScheduleConfig('speaker',this.record) ]); } }); Ext.ComponentMgr.unregister('speakerdetail'); Ext.reg('speakerdetail', the_conf_app.views.SpeakerDetailWithMySchedule); // The Session List page the_conf_app.views.SessionListWithMySchedule = Ext.extend(the_conf_app.views.SessionList, { initComponent: function(){ the_conf_app.views.SessionListWithMySchedule.superclass.initComponent.call(this); setupMySchedule(this); }, addMyScheduleButton: function(){ this.MyScheduleButton = this.listpanel.getDockedItems()[0].add([ {xtype: 'spacer'}, getMyScheduleListConfig('sessionlist',this) ])[1]; }, changeDate: function(btn) { this.list.store.clearFilter(); if(this.MyScheduleButton.getEl().hasCls('favorited')){ var filterDate; if (this.dateButtons){ filterDate = this.dateButtons.getPressed().dateData; } this.list.store.filterBy(function(record,id){ return isOnMySchedule('sessionlist',record,id,filterDate); },this); } else{ this.list.store.filter('date', btn.dateData); } this.list.scroller.scrollTo({y: 0}, false); } }); Ext.ComponentMgr.unregister('sessionlist'); Ext.reg('sessionlist', the_conf_app.views.SessionListWithMySchedule); // The Speaker List page the_conf_app.views.SpeakerListWithMySchedule = Ext.extend(the_conf_app.views.SpeakerList, { initComponent: function(){ the_conf_app.views.SpeakerListWithMySchedule.superclass.initComponent.call(this); setupMySchedule(this); }, addMyScheduleButton: function(){ this.listpanel.getDockedItems()[0].add([ {xtype: 'spacer'}, getMyScheduleListConfig('speakerlist',this) ]); } }); Ext.ComponentMgr.unregister('speakerlist'); Ext.reg('speakerlist', the_conf_app.views.SpeakerListWithMySchedule); setupMySchedule = function(_this){ if (the_conf_app.MyScheduleStore.isLoadedEh){ _this.addMyScheduleButton(); } else{ the_conf_app.MyScheduleStore.addListener('load',_this.addMyScheduleButton,_this); the_conf_app.MyScheduleStore.load(); } } getMyScheduleConfig = function(type,record){ var create_config; switch(type){ case 'speaker': create_config = {speaker_id:record.data.id}; break; case 'proposal': create_config = {proposal_id:record.data.id}; break; } var config; if (type == 'speaker' || 'proposal'){ config = { iconCls: 'star', cls: 'favestar' + (the_conf_app.MyScheduleStore.find(type+'_id', record.data.id,0,false,true,true) == -1 ? '' : ' favorited'), iconMask: true, ui: 'plain', scope: this, handler: function(btn){ var idx = the_conf_app.MyScheduleStore.find(type+'_id',record.data.id,0,false,true,true); if (idx == -1) { the_conf_app.MyScheduleStore.create(create_config); btn.addCls('favorited'); } else { the_conf_app.MyScheduleStore.removeAt(idx); the_conf_app.MyScheduleStore.sync(); btn.removeCls('favorited'); } } }; } return config; } getMyScheduleListConfig = function(type,_this){ var the_store = (type == 'sessionlist' ? getSessionListStore() : getSpeakerStore()); var config = { iconCls: 'star', cls: 'favestar', iconMask: true, ui: 'plain', scope: _this, handler: function(btn){ var filterDate; if (type == 'sessionlist' && _this.dateButtons){ filterDate = _this.dateButtons.getPressed().dateData; } if (btn.getEl().hasCls('favorited')){ btn.removeCls('favorited'); if (type == 'sessionlist' && _this.dateButtons){ _this.changeDate(_this.dateButtons.getPressed()); } else{ the_store.clearFilter(); the_store.sort(); // Turns out it's necessary to resort the store. } } else{ btn.addCls('favorited'); the_store.filterBy(function(record,id){ return isOnMySchedule(type,record,id,filterDate); },_this); _this.list.scroller.scrollTo({y: 0}, false); } } } return config; } isOnMySchedule = function(type,record,id,filterDate){ if (type == 'sessionlist' && filterDate != undefined && record.data.date != filterDate){ return false; } return (the_conf_app.MyScheduleStore.find((type == 'sessionlist' ? 'proposal' : 'speaker')+'_id', record.data.id,0,false,true,true) != -1); }