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# */{
22 classes: "enyo-fit enyo-unselectable",
26 keyCapturePatterns: []
29 onListAdded: "addPulloutItem",
30 onModelChange: "modelChanged",
31 onParameterChange: "parameterDidChange",
32 onNavigatorEvent: "togglePullout",
33 onHistoryChange: "refreshHistoryPanel",
34 onHistoryItemSelected: "selectHistoryItem",
35 onSearch: "waterfallSearch",
36 onWorkspace: "waterfallWorkspace",
37 onColumnsChange: "columnsDidChange",
41 hotkey mode (triggered by alt)
42 notify popup mode (if the notify popup is showing)
45 handleKeyDown: function (inSender, inEvent) {
47 keyCode = inEvent.keyCode;
49 // remember the last 10 key presses
50 this._keyBufferArray.push(keyCode);
51 this._keyBufferArray = this._keyBufferArray.slice(-10);
54 if (this.$.postbooks.isNotifyPopupShowing()) {
55 this.$.postbooks.notifyKey(keyCode, inEvent.shiftKey);
59 if (keyCode === 189) {
60 // XXX FIXME hack. Dashes aren't coming through as dashes from my keyboard
61 keyCode = '-'.charCodeAt(0);
64 inEvent.cancelBubble = true;
65 inEvent.returnValue = false;
66 this.processHotKey(keyCode);
69 if (this._keyBufferEndPattern) {
70 // we're in record mode, so record.
71 this._keyBuffer = this._keyBuffer + String.fromCharCode(keyCode);
74 if (this._keyBufferEndPattern &&
75 _.isEqual(this._keyBufferArray.slice(-1 * this._keyBufferEndPattern.length), this._keyBufferEndPattern) &&
76 this._falsePositives) {
77 this._falsePositives--;
79 } else if (this._keyBufferEndPattern &&
80 _.isEqual(this._keyBufferArray.slice(-1 * this._keyBufferEndPattern.length), this._keyBufferEndPattern)) {
82 // first slice the end pattern off the payload
83 this._keyBuffer = this._keyBuffer.substring(0, this._keyBuffer.length - this._keyBufferEndPattern.length);
85 // we've matched an end pattern. Send the recorded buffer to the appropriate method
86 this[this._keyBufferMethod](this._keyBuffer);
88 this._keyBufferEndPattern = undefined;
89 this._falsePositives = undefined;
92 // specification of the key capture patterns themselves are up to the subkind
93 _.each(this.getKeyCapturePatterns(), function (pattern) {
94 if (_.isEqual(that._keyBufferArray.slice(-1 * pattern.start.length), pattern.start)) {
95 // we've matched a start pattern. Now we're in record mode, waiting for a match to the end pattern
96 that._keyBuffer = that._keyBuffer || "";
97 that._keyBufferEndPattern = pattern.end;
98 that._keyBufferMethod = pattern.method;
99 that._falsePositives = pattern.falsePositives;
104 // the components array is overriden by the subkind
106 {name: "postbooks", kind: "XV.ModuleContainer", onTransitionStart: "handlePullout"},
107 {name: "pullout", kind: "XV.Pullout", onAnimateFinish: "pulloutAnimateFinish"},
108 {name: "signals", kind: "enyo.Signals", onkeydown: "handleKeyDown"},
110 state: UNINITIALIZED,
112 Passes the pullout payload straight from the sender (presumably the list
113 containing the pullout parameter) to the pullout, who will deal with
116 addPulloutItem: function (inSender, inEvent) {
117 if (!this.$.pullout) {
118 this._cachePullouts.push(inEvent);
121 this.$.pullout.addPulloutItem(inSender, inEvent);
123 columnsDidChange: function (inSender, inEvent) {
124 this.$.postbooks.getNavigator().waterfall("onColumnsChange", inEvent);
126 create: function () {
127 this._cachePullouts = [];
128 this._keyBufferArray = [];
129 this.inherited(arguments);
131 // make even modal popups hear keydown (necessary for keyboarding the notify popup)
132 enyo.dispatcher.autoForwardEvents.keydown = 1;
133 window.onbeforeunload = function () {
134 return "_exitPageWarning".loc();
137 handlePullout: function (inSender, inEvent) {
138 var showing = inSender.getActive().showPullout || false;
139 this.$.pullout.setShowing(showing);
142 When a model is changed, we want to reflect the new state across
143 the app, so we bubble all the way up there and waterfall down.
144 Currently, the only things that are updated by this process are the lists.
146 modelChanged: function (inSender, inEvent) {
148 // Waterfall down to all lists to tell them to update themselves
150 this.$.postbooks.getNavigator().waterfall("onModelChange", inEvent);
152 parameterDidChange: function (inSender, inEvent) {
153 if (this.getIsStarted()) {
154 this.$.postbooks.getNavigator().waterfall("onParameterChange", inEvent);
158 Implemented by the subkind
160 processHotKey: function (keyCode) {
163 * Manages the "lit-up-ness" of the icon buttons based on the pullout.
164 * If the pull-out is put away, we want all buttons to dim. If the pull-out
165 * is activated, we want the button related to the active pullout pane
166 * to light up. The presentation of these buttons take care of themselves
167 * if the user actually clicks on the buttons.
169 pulloutAnimateFinish: function (inSender, inEvent) {
170 var activeIconButton;
172 if (inSender.value === inSender.max) {
174 if (this.$.pullout.getSelectedPanel() === 'history') {
175 activeIconButton = 'history';
177 activeIconButton = 'search';
179 } else if (inSender.value === inSender.min) {
180 // pullout is inactive
181 activeIconButton = null;
183 this.$.postbooks.getNavigator().setActiveIconButton(activeIconButton);
185 refreshHistoryPanel: function (inSender, inEvent) {
186 this.$.pullout.refreshHistoryList();
189 When a history item is selected we bubble an event way up the application.
190 Note that we create a sort of ersatz model to mimic the way the handler
191 expects to have a model with the event to know what to drill down into.
193 selectHistoryItem: function (inSender, inEvent) {
194 inEvent.eventName = "onWorkspace";
195 this.waterfall("onWorkspace", inEvent);
197 startupProcess: function () {
198 var startupManager = XT.getStartupManager(),
199 progressBar = XT.app.$.postbooks.getStartupProgressBar(),
214 extensionPrivilegeName,
216 extensionsDownloaded = 0,
217 extensionPayloads = [],
220 eachCallback = function () {
221 var completed = startupManager.get('completed').length;
222 progressBar.animateProgressTo(completed);
223 if (completed === startupTaskCount) {
224 that.startupProcess();
228 // 1: Load session data
229 if (this.state === UNINITIALIZED) {
230 this.state = LOADING_SESSION;
231 startupManager.registerCallback(eachCallback, true);
232 XT.dataSource.connect();
233 startupTaskCount = startupManager.get('queue').length + startupManager.get('completed').length;
234 progressBar.setMax(startupTaskCount);
236 // #2 is off high-wire walking the Grand Canyon
238 // 3: Initialize extensions
239 } else if (this.state === LOADING_SESSION) {
241 this.state = LOADING_EXTENSIONS;
242 text = "_loadingExtensions".loc() + "...";
243 XT.app.$.postbooks.getStartupText().setContent(text);
244 for (prop in XT.extensions) {
245 if (XT.extensions.hasOwnProperty(prop)) {
246 ext = XT.extensions[prop];
247 for (extprop in ext) {
248 if (ext.hasOwnProperty(extprop) &&
249 typeof ext[extprop] === "function") {
250 //XT.log('Installing ' + prop + ' ' + extprop);
257 this.startupProcess();
260 } else if (this.state === LOADING_EXTENSIONS) {
261 this.state = LOADING_SCHEMA;
262 text = "_loadingSchema".loc() + "...";
263 XT.app.$.postbooks.getStartupText().setContent(text);
264 XT.StartupTask.create({
265 taskName: "loadSessionSchema",
269 success: function () {
271 that.startupProcess();
274 XT.session.loadSessionObjects(XT.session.SCHEMA, options);
278 // 5 Load Application Data
279 } else if (this.state === LOADING_SCHEMA) {
281 this.state = LOADING_APP_DATA;
282 text = "_loadingApplicationData".loc() + "...";
283 XT.app.$.postbooks.getStartupText().setContent(text);
284 progressBar.setMax(XT.StartupTasks.length);
285 progressBar.setProgress(0);
287 // there's a new startup task count now that
288 // the second stage of tasks are being loaded.
289 startupTaskCount = startupManager.get('queue').length +
290 startupManager.get('completed').length +
291 XT.StartupTasks.length;
293 // create a new each callback to manage the completion of this step
294 // the previously registered callback isn't doing any harm. It would be
295 // best to unregister the previous eachCallback and replicate the code
296 // here that advances the progress bar. This would make the animation look
297 // better. There's not a great way to unregister a startup callback, though.
298 eachCallback = function () {
299 var completed = startupManager.get('completed').length;
300 if (completed === startupTaskCount) {
301 that.startupProcess();
305 startupManager.registerCallback(eachCallback, true);
306 for (i = 0; i < XT.StartupTasks.length; i++) {
307 task = XT.StartupTasks[i];
308 XT.StartupTask.create(task);
312 } else if (this.state === LOADING_APP_DATA) {
313 // Go to the navigator
314 for (i = 0; i < this._cachePullouts.length; i++) {
315 inEvent = this._cachePullouts[i];
316 this.$.pullout.addPulloutItem(null, inEvent);
318 loginInfo = XT.app.$.postbooks.getNavigator().loginInfo();
319 details = XT.session.details;
320 loginInfo.setContent(details.username + " ยท " + details.organization);
321 this.state = RUNNING;
322 XT.app.$.postbooks.activate();
325 start: function (debug) {
326 if (this.getIsStarted()) { return; }
328 this.setDebug(debug);
330 // Run through the multi-step start process
331 this.startupProcess();
333 // lets not allow this to happen again
334 this.setIsStarted(true);
337 if (this.getShowing() && this.getIsStarted()) {
338 this.renderInto(document.body);
340 this.inherited(arguments);
343 togglePullout: function (inSender, inEvent) {
344 this.$.pullout.togglePullout(inSender, inEvent);
346 waterfallSearch: function (inSender, inEvent) {
347 this.$.postbooks.waterfall("onSearch", inEvent);
348 return true; // don't want to double up
350 waterfallWorkspace: function (inSender, inEvent) {
351 this.$.postbooks.waterfall("onWorkspace", inEvent);
352 return true; // don't want to double up