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,
48 exoticTransforms = {},
49 numberShiftCharacters = ")!@#$%^&*(";
51 // exotic transforms. sorta hackish, but the inEvent codes coming
52 // through are not always the ones we want
54 // http://stackoverflow.com/questions/1898129/javascript-subtract-keycode
55 exoticTransforms[XT.ASCII.VULGAR_HALF] = XT.ASCII.N_DASH;
56 if (_.contains(Object.keys(exoticTransforms), "" + keyCode)) {
57 // account for inexplicably wrongly mapped keys
58 keyCode = exoticTransforms[keyCode];
59 } else if (inEvent.shiftKey && keyCode >= XT.ASCII.ZERO && keyCode <= XT.ASCII.NINE) {
60 // shift-number should be transformed into the appropriate character
61 // XXX jingoistic implementation
62 keyCode = numberShiftCharacters.charCodeAt(keyCode - XT.ASCII.ZERO);
65 // remember the last 10 key presses
66 this._keyBufferArray.push(keyCode);
67 this._keyBufferArray = this._keyBufferArray.slice(-10);
70 if (this.$.postbooks.isNotifyPopupShowing()) {
71 this.$.postbooks.notifyKey(keyCode, inEvent.shiftKey);
76 inEvent.cancelBubble = true;
77 inEvent.returnValue = false;
78 this.processHotKey(keyCode);
81 if (this._keyBufferEndPattern) {
82 // we're in record mode, so record.
83 this._keyBuffer = this._keyBuffer + String.fromCharCode(keyCode);
86 if (this._keyBufferEndPattern &&
87 _.isEqual(this._keyBufferArray.slice(-1 * this._keyBufferEndPattern.length), this._keyBufferEndPattern) &&
88 this._falsePositives) {
89 this._falsePositives--;
91 } else if (this._keyBufferEndPattern &&
92 _.isEqual(this._keyBufferArray.slice(-1 * this._keyBufferEndPattern.length), this._keyBufferEndPattern)) {
94 // first slice the end pattern off the payload
95 this._keyBuffer = this._keyBuffer.substring(0, this._keyBuffer.length - this._keyBufferEndPattern.length);
97 // we've matched an end pattern. Send the recorded buffer to the appropriate method
98 this[this._keyBufferMethod](this._keyBuffer);
100 this._keyBufferEndPattern = undefined;
101 this._falsePositives = undefined;
104 // specification of the key capture patterns themselves are up to the subkind
105 _.each(this.getKeyCapturePatterns(), function (pattern) {
106 if (_.isEqual(that._keyBufferArray.slice(-1 * pattern.start.length), pattern.start)) {
107 // we've matched a start pattern. Now we're in record mode, waiting for a match to the end pattern
108 that._keyBuffer = that._keyBuffer || "";
109 that._keyBufferEndPattern = pattern.end;
110 that._keyBufferMethod = pattern.method;
111 that._falsePositives = pattern.falsePositives;
116 // the components array is overriden by the subkind
118 {name: "postbooks", kind: "XV.ModuleContainer", onTransitionStart: "handlePullout"},
119 {name: "pullout", kind: "XV.Pullout", onAnimateFinish: "pulloutAnimateFinish"},
120 {name: "signals", kind: "enyo.Signals", onkeydown: "handleKeyDown"},
122 state: UNINITIALIZED,
124 Passes the pullout payload straight from the sender (presumably the list
125 containing the pullout parameter) to the pullout, who will deal with
128 addPulloutItem: function (inSender, inEvent) {
129 if (!this.$.pullout) {
130 this._cachePullouts.push(inEvent);
133 this.$.pullout.addPulloutItem(inSender, inEvent);
135 columnsDidChange: function (inSender, inEvent) {
136 this.$.postbooks.getNavigator().waterfall("onColumnsChange", inEvent);
138 create: function () {
139 this._cachePullouts = [];
140 this._keyBufferArray = [];
141 this.inherited(arguments);
143 // make even modal popups hear keydown (necessary for keyboarding the notify popup)
144 enyo.dispatcher.autoForwardEvents.keydown = 1;
145 window.onbeforeunload = function () {
146 return "_exitPageWarning".loc();
149 handlePullout: function (inSender, inEvent) {
150 var showing = inSender.getActive().showPullout || false;
151 this.$.pullout.setShowing(showing);
154 When a model is changed, we want to reflect the new state across
155 the app, so we bubble all the way up there and waterfall down.
156 Currently, the only things that are updated by this process are the lists.
158 modelChanged: function (inSender, inEvent) {
160 // Waterfall down to all lists to tell them to update themselves
162 this.$.postbooks.getNavigator().waterfall("onModelChange", inEvent);
164 parameterDidChange: function (inSender, inEvent) {
165 if (this.getIsStarted()) {
166 this.$.postbooks.getNavigator().waterfall("onParameterChange", inEvent);
170 Implemented by the subkind
172 processHotKey: function (keyCode) {
175 * Manages the "lit-up-ness" of the icon buttons based on the pullout.
176 * If the pull-out is put away, we want all buttons to dim. If the pull-out
177 * is activated, we want the button related to the active pullout pane
178 * to light up. The presentation of these buttons take care of themselves
179 * if the user actually clicks on the buttons.
181 pulloutAnimateFinish: function (inSender, inEvent) {
182 var activeIconButton;
184 if (inSender.value === inSender.max) {
186 if (this.$.pullout.getSelectedPanel() === 'history') {
187 activeIconButton = 'history';
189 activeIconButton = 'search';
191 } else if (inSender.value === inSender.min) {
192 // pullout is inactive
193 activeIconButton = null;
195 this.$.postbooks.getNavigator().setActiveIconButton(activeIconButton);
197 refreshHistoryPanel: function (inSender, inEvent) {
198 this.$.pullout.refreshHistoryList();
201 When a history item is selected we bubble an event way up the application.
202 Note that we create a sort of ersatz model to mimic the way the handler
203 expects to have a model with the event to know what to drill down into.
205 selectHistoryItem: function (inSender, inEvent) {
206 inEvent.eventName = "onWorkspace";
207 this.waterfall("onWorkspace", inEvent);
209 startupProcess: function () {
210 var startupManager = XT.getStartupManager(),
211 progressBar = XT.app.$.postbooks.getStartupProgressBar(),
226 extensionPrivilegeName,
228 extensionsDownloaded = 0,
229 extensionPayloads = [],
232 eachCallback = function () {
233 var completed = startupManager.get('completed').length;
234 progressBar.animateProgressTo(completed);
235 if (completed === startupTaskCount) {
236 that.startupProcess();
240 // 1: Load session data
241 if (this.state === UNINITIALIZED) {
242 this.state = LOADING_SESSION;
243 startupManager.registerCallback(eachCallback, true);
244 XT.dataSource.connect();
245 startupTaskCount = startupManager.get('queue').length + startupManager.get('completed').length;
246 progressBar.setMax(startupTaskCount);
248 // #2 is off high-wire walking the Grand Canyon
250 // 3: Initialize extensions
251 } else if (this.state === LOADING_SESSION) {
253 this.state = LOADING_EXTENSIONS;
254 text = "_loadingExtensions".loc() + "...";
255 XT.app.$.postbooks.getStartupText().setContent(text);
256 for (prop in XT.extensions) {
257 if (XT.extensions.hasOwnProperty(prop)) {
258 ext = XT.extensions[prop];
259 for (extprop in ext) {
260 if (ext.hasOwnProperty(extprop) &&
261 typeof ext[extprop] === "function") {
262 //XT.log('Installing ' + prop + ' ' + extprop);
269 this.startupProcess();
272 } else if (this.state === LOADING_EXTENSIONS) {
273 this.state = LOADING_SCHEMA;
274 text = "_loadingSchema".loc() + "...";
275 XT.app.$.postbooks.getStartupText().setContent(text);
276 XT.StartupTask.create({
277 taskName: "loadSessionSchema",
281 success: function () {
283 that.startupProcess();
286 XT.session.loadSessionObjects(XT.session.SCHEMA, options);
290 // 5 Load Application Data
291 } else if (this.state === LOADING_SCHEMA) {
293 this.state = LOADING_APP_DATA;
294 text = "_loadingApplicationData".loc() + "...";
295 XT.app.$.postbooks.getStartupText().setContent(text);
296 progressBar.setMax(XT.StartupTasks.length);
297 progressBar.setProgress(0);
299 // there's a new startup task count now that
300 // the second stage of tasks are being loaded.
301 startupTaskCount = startupManager.get('queue').length +
302 startupManager.get('completed').length +
303 XT.StartupTasks.length;
305 // create a new each callback to manage the completion of this step
306 // the previously registered callback isn't doing any harm. It would be
307 // best to unregister the previous eachCallback and replicate the code
308 // here that advances the progress bar. This would make the animation look
309 // better. There's not a great way to unregister a startup callback, though.
310 eachCallback = function () {
311 var completed = startupManager.get('completed').length;
312 if (completed === startupTaskCount) {
313 that.startupProcess();
317 startupManager.registerCallback(eachCallback, true);
318 for (i = 0; i < XT.StartupTasks.length; i++) {
319 task = XT.StartupTasks[i];
320 XT.StartupTask.create(task);
324 } else if (this.state === LOADING_APP_DATA) {
325 // Go to the navigator
326 for (i = 0; i < this._cachePullouts.length; i++) {
327 inEvent = this._cachePullouts[i];
328 this.$.pullout.addPulloutItem(null, inEvent);
330 loginInfo = XT.app.$.postbooks.getNavigator().loginInfo();
331 details = XT.session.details;
332 loginInfo.setContent(details.username + " ยท " + details.organization);
333 this.state = RUNNING;
334 that.startupProcess();
336 // TODO - 7. Initiate Empty Database Startup "checklist" process for empty/quick start databases
337 } else if (this.state === RUNNING) {
338 // No process in place so proceed to activate
339 XM.Tuplespace.trigger("activate");
340 XT.app.$.postbooks.activate();
342 // Send no Base Currency event
343 if (!XT.baseCurrency()) {
344 this.waterfallNoBaseCurr();
348 start: function (debug) {
349 if (this.getIsStarted()) {return; }
351 this.setDebug(debug);
353 // Run through the multi-step start process
354 this.startupProcess();
356 // lets not allow this to happen again
357 this.setIsStarted(true);
360 if (this.getShowing() && this.getIsStarted()) {
361 this.renderInto(document.body);
363 this.inherited(arguments);
366 togglePullout: function (inSender, inEvent) {
367 this.$.pullout.togglePullout(inSender, inEvent);
369 waterfallNoBaseCurr: function (inSender, inEvent) {
370 this.$.postbooks.waterfall("onNoBaseCurr", inEvent);
373 waterfallSearch: function (inSender, inEvent) {
374 this.$.postbooks.waterfall("onSearch", inEvent);
375 return true; // don't want to double up
377 waterfallWorkspace: function (inSender, inEvent) {
378 this.$.postbooks.waterfall("onWorkspace", inEvent);
379 return true; // don't want to double up