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":"
<\/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":"
Jennifer Fraser, University of Toronto
Program Coordinator: <\/strong>
Sponsorship Coordinator: <\/strong>
Conference Management by Funnel Communications Inc: <\/strong>
<\/p>\n
Contacts<\/strong><\/p>\n
Event Coordinator<\/strong>
Conference Coordinator<\/strong>
Webmaster<\/strong>
<\/p>\n<\/div>"}},{"title":"Partners & Supporters","card":{"xtype":"htmlpage","content":"
<\/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 ? '
No tweets found matching that search
Organizing Committee <\/strong> <\/h3>\n
\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
\n Beverley Sheridan, Technology Now<\/p>\n
\n Trish Mongeon, Philanthropy Touch\/Mongeon Consulting <\/p>\n
\n Anthony Laycock, Jason Bell & John Chagnon <\/p>\n
\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
\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
\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
Thank you to our conference partners & supporters!<\/strong><\/h3>\n
{title}
{room}
{proposal_type}, {day} at {pretty_time}
{description}
{position}
{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: '
{position}
{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: '