1 /*jshint indent:2, curly:true, eqeqeq:true, immed:true, latedef:true,
2 newcap:true, noarg:true, regexp:true, undef:true, trailing:true,
4 /*global enyo:true, XT:true, _:true, document:true, window:true, XM:true */
9 var LOADING_SESSION = 1;
10 var LOADING_EXTENSIONS = 3;
11 var LOADING_SCHEMA = 4;
12 var LOADING_APP_DATA = 5;
16 Application kind. Contains two components, "postbooks", which is a module container,
20 /** @lends XV.App# */{
23 classes: "xt-app enyo-unselectable",
27 keyCapturePatterns: []
30 onListAdded: "addPulloutItem",
31 onModelChange: "modelChanged",
32 onParameterChange: "parameterDidChange",
33 onNavigatorEvent: "togglePullout",
34 onHistoryChange: "refreshHistoryPanel",
35 onHistoryItemSelected: "selectHistoryItem",
36 onSearch: "waterfallSearch",
37 onWorkspace: "waterfallWorkspace",
38 onColumnsChange: "columnsDidChange",
39 onWorkspaceAction: "waterfallWorkspaceAction"
43 hotkey mode (triggered by alt)
44 notify popup mode (if the notify popup is showing)
47 handleKeyDown: function (inSender, inEvent) {
49 keyCode = inEvent.keyCode;
51 // remember the last 10 key presses
52 this._keyBufferArray.push(keyCode);
53 this._keyBufferArray = this._keyBufferArray.slice(-10);
56 if (this.$.postbooks.isNotifyPopupShowing()) {
57 this.$.postbooks.notifyKey(keyCode, inEvent.shiftKey);
61 if (keyCode === 189) {
62 // XXX FIXME hack. Dashes aren't coming through as dashes from my keyboard
63 keyCode = '-'.charCodeAt(0);
66 inEvent.cancelBubble = true;
67 inEvent.returnValue = false;
68 this.processHotKey(keyCode);
71 if (this._keyBufferEndPattern) {
72 // we're in record mode, so record.
73 this._keyBuffer = this._keyBuffer + String.fromCharCode(keyCode);
76 if (this._keyBufferEndPattern &&
77 _.isEqual(this._keyBufferArray.slice(-1 * this._keyBufferEndPattern.length), this._keyBufferEndPattern) &&
78 this._falsePositives) {
79 this._falsePositives--;
81 } else if (this._keyBufferEndPattern &&
82 _.isEqual(this._keyBufferArray.slice(-1 * this._keyBufferEndPattern.length), this._keyBufferEndPattern)) {
84 // first slice the end pattern off the payload
85 this._keyBuffer = this._keyBuffer.substring(0, this._keyBuffer.length - this._keyBufferEndPattern.length);
87 // we've matched an end pattern. Send the recorded buffer to the appropriate method
88 this[this._keyBufferMethod](this._keyBuffer);
90 this._keyBufferEndPattern = undefined;
91 this._falsePositives = undefined;
94 // specification of the key capture patterns themselves are up to the subkind
95 _.each(this.getKeyCapturePatterns(), function (pattern) {
96 if (_.isEqual(that._keyBufferArray.slice(-1 * pattern.start.length), pattern.start)) {
97 // we've matched a start pattern. Now we're in record mode, waiting for a match to the end pattern
98 that._keyBuffer = that._keyBuffer || "";
99 that._keyBufferEndPattern = pattern.end;
100 that._keyBufferMethod = pattern.method;
101 that._falsePositives = pattern.falsePositives;
106 // the components array is overriden by the subkind
108 {name: "postbooks", kind: "XV.ModuleContainer", onTransitionStart: "handlePullout"},
109 {name: "pullout", kind: "XV.Pullout", onAnimateFinish: "pulloutAnimateFinish"},
110 {name: "signals", kind: "enyo.Signals", onkeydown: "handleKeyDown"},
112 state: UNINITIALIZED,
114 Passes the pullout payload straight from the sender (presumably the list
115 containing the pullout parameter) to the pullout, who will deal with
118 addPulloutItem: function (inSender, inEvent) {
119 if (!this.$.pullout) {
120 this._cachePullouts.push(inEvent);
123 this.$.pullout.addPulloutItem(inSender, inEvent);
125 columnsDidChange: function (inSender, inEvent) {
126 this.$.postbooks.getNavigator().waterfall("onColumnsChange", inEvent);
128 create: function () {
129 this._cachePullouts = [];
130 this._keyBufferArray = [];
131 this.inherited(arguments);
133 // make even modal popups hear keydown (necessary for keyboarding the notify popup)
134 enyo.dispatcher.autoForwardEvents.keydown = 1;
135 window.onbeforeunload = function () {
136 return "_exitPageWarning".loc();
139 handlePullout: function (inSender, inEvent) {
140 var showing = inSender.getActive().showPullout || false;
141 this.$.pullout.setShowing(showing);
144 When a model is changed, we want to reflect the new state across
145 the app, so we bubble all the way up there and waterfall down.
146 Currently, the only things that are updated by this process are the lists.
148 modelChanged: function (inSender, inEvent) {
150 // Waterfall down to all lists to tell them to update themselves
152 this.$.postbooks.getNavigator().waterfall("onModelChange", inEvent);
154 parameterDidChange: function (inSender, inEvent) {
155 if (this.getIsStarted()) {
156 this.$.postbooks.getNavigator().waterfall("onParameterChange", inEvent);
160 Implemented by the subkind
162 processHotKey: function (keyCode) {
165 * Manages the "lit-up-ness" of the icon buttons based on the pullout.
166 * If the pull-out is put away, we want all buttons to dim. If the pull-out
167 * is activated, we want the button related to the active pullout pane
168 * to light up. The presentation of these buttons take care of themselves
169 * if the user actually clicks on the buttons.
171 pulloutAnimateFinish: function (inSender, inEvent) {
172 var activeIconButton;
174 if (inSender.value === inSender.max) {
176 if (this.$.pullout.getSelectedPanel() === 'history') {
177 activeIconButton = 'history';
179 activeIconButton = 'search';
181 } else if (inSender.value === inSender.min) {
182 // pullout is inactive
183 activeIconButton = null;
185 this.$.postbooks.getNavigator().setActiveIconButton(activeIconButton);
187 refreshHistoryPanel: function (inSender, inEvent) {
188 this.$.pullout.refreshHistoryList();
191 When a history item is selected we bubble an event way up the application.
192 Note that we create a sort of ersatz model to mimic the way the handler
193 expects to have a model with the event to know what to drill down into.
195 selectHistoryItem: function (inSender, inEvent) {
196 inEvent.eventName = "onWorkspace";
197 this.waterfall("onWorkspace", inEvent);
199 startupProcess: function () {
200 var startupManager = XT.getStartupManager(),
201 progressBar = XT.app.$.postbooks.getStartupProgressBar(),
216 extensionPrivilegeName,
218 extensionsDownloaded = 0,
219 extensionPayloads = [],
222 eachCallback = function () {
223 var completed = startupManager.get('completed').length;
224 progressBar.animateProgressTo(completed);
225 if (completed === startupTaskCount) {
226 that.startupProcess();
230 // 1: Load session data
231 if (this.state === UNINITIALIZED) {
232 this.state = LOADING_SESSION;
233 startupManager.registerCallback(eachCallback, true);
234 XT.dataSource.connect();
235 startupTaskCount = startupManager.get('queue').length + startupManager.get('completed').length;
236 progressBar.setMax(startupTaskCount);
238 // #2 is off high-wire walking the Grand Canyon
240 // 3: Initialize extensions
241 } else if (this.state === LOADING_SESSION) {
243 this.state = LOADING_EXTENSIONS;
244 text = "_loadingExtensions".loc() + "...";
245 XT.app.$.postbooks.getStartupText().setContent(text);
246 for (prop in XT.extensions) {
247 if (XT.extensions.hasOwnProperty(prop)) {
248 ext = XT.extensions[prop];
249 for (extprop in ext) {
250 if (ext.hasOwnProperty(extprop) &&
251 typeof ext[extprop] === "function") {
252 //XT.log('Installing ' + prop + ' ' + extprop);
259 this.startupProcess();
262 } else if (this.state === LOADING_EXTENSIONS) {
263 this.state = LOADING_SCHEMA;
264 text = "_loadingSchema".loc() + "...";
265 XT.app.$.postbooks.getStartupText().setContent(text);
266 XT.StartupTask.create({
267 taskName: "loadSessionSchema",
271 success: function () {
273 that.startupProcess();
276 XT.session.loadSessionObjects(XT.session.SCHEMA, options);
280 // 5 Load Application Data
281 } else if (this.state === LOADING_SCHEMA) {
283 this.state = LOADING_APP_DATA;
284 text = "_loadingApplicationData".loc() + "...";
285 XT.app.$.postbooks.getStartupText().setContent(text);
286 progressBar.setMax(XT.StartupTasks.length);
287 progressBar.setProgress(0);
289 // there's a new startup task count now that
290 // the second stage of tasks are being loaded.
291 startupTaskCount = startupManager.get('queue').length +
292 startupManager.get('completed').length +
293 XT.StartupTasks.length;
295 // create a new each callback to manage the completion of this step
296 // the previously registered callback isn't doing any harm. It would be
297 // best to unregister the previous eachCallback and replicate the code
298 // here that advances the progress bar. This would make the animation look
299 // better. There's not a great way to unregister a startup callback, though.
300 eachCallback = function () {
301 var completed = startupManager.get('completed').length;
302 if (completed === startupTaskCount) {
303 that.startupProcess();
307 startupManager.registerCallback(eachCallback, true);
308 for (i = 0; i < XT.StartupTasks.length; i++) {
309 task = XT.StartupTasks[i];
310 XT.StartupTask.create(task);
314 } else if (this.state === LOADING_APP_DATA) {
315 // Go to the navigator
316 for (i = 0; i < this._cachePullouts.length; i++) {
317 inEvent = this._cachePullouts[i];
318 this.$.pullout.addPulloutItem(null, inEvent);
320 loginInfo = XT.app.$.postbooks.getNavigator().loginInfo();
321 details = XT.session.details;
322 loginInfo.setContent(details.username + " ยท " + details.organization);
323 this.state = RUNNING;
324 XT.app.$.postbooks.activate();
327 start: function (debug) {
328 if (this.getIsStarted()) { return; }
330 this.setDebug(debug);
332 // Run through the multi-step start process
333 this.startupProcess();
335 // lets not allow this to happen again
336 this.setIsStarted(true);
339 if (this.getShowing() && this.getIsStarted()) {
340 this.renderInto(document.body);
342 this.inherited(arguments);
345 togglePullout: function (inSender, inEvent) {
346 this.$.pullout.togglePullout(inSender, inEvent);
348 waterfallSearch: function (inSender, inEvent) {
349 this.$.postbooks.waterfall("onSearch", inEvent);
350 return true; // don't want to double up
352 waterfallWorkspace: function (inSender, inEvent) {
353 this.$.postbooks.waterfall("onWorkspace", inEvent);
354 return true; // don't want to double up
356 waterfallWorkspaceAction: function (inSender, inEvent) {
357 this.$.postbooks.waterfall("onWorkspaceAction", inEvent);
358 return true; // don't want to double up