Workspace action addition and List actions for Issue to Shipping
[xtuple] / lib / enyo-x / source / app.js
1 /*jshint indent:2, curly:true, eqeqeq:true, immed:true, latedef:true,
2 newcap:true, noarg:true, regexp:true, undef:true, trailing:true,
3 white:true*/
4 /*global enyo:true, XT:true, _:true, document:true, window:true, XM:true */
5
6 (function () {
7
8   var UNINITIALIZED = 0;
9   var LOADING_SESSION = 1;
10   var LOADING_EXTENSIONS = 3;
11   var LOADING_SCHEMA = 4;
12   var LOADING_APP_DATA = 5;
13   var RUNNING = 6;
14
15   /**
16     Application kind. Contains two components, "postbooks", which is a module container,
17     and the pullout.
18    */
19   enyo.kind(
20     /** @lends XV.App# */{
21     name: "XV.App",
22     fit: true,
23     classes: "xt-app enyo-unselectable",
24     published: {
25       isStarted: false,
26       debug: false,
27       keyCapturePatterns: []
28     },
29     handlers: {
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"
40     },
41     /*
42       Three use cases:
43       hotkey mode (triggered by alt)
44       notify popup mode (if the notify popup is showing)
45       pattern-match mode
46     */
47     handleKeyDown: function (inSender, inEvent) {
48       var that = this,
49         keyCode = inEvent.keyCode;
50
51       // remember the last 10 key presses
52       this._keyBufferArray.push(keyCode);
53       this._keyBufferArray = this._keyBufferArray.slice(-10);
54
55       // not working
56       if (this.$.postbooks.isNotifyPopupShowing()) {
57         this.$.postbooks.notifyKey(keyCode, inEvent.shiftKey);
58         return;
59       }
60
61       if (keyCode === 189) {
62         // XXX FIXME hack. Dashes aren't coming through as dashes from my keyboard
63         keyCode = '-'.charCodeAt(0);
64       }
65       if (inEvent.altKey) {
66         inEvent.cancelBubble = true;
67         inEvent.returnValue = false;
68         this.processHotKey(keyCode);
69         return true;
70       }
71       if (this._keyBufferEndPattern) {
72         // we're in record mode, so record.
73         this._keyBuffer = this._keyBuffer + String.fromCharCode(keyCode);
74       }
75
76       if (this._keyBufferEndPattern &&
77           _.isEqual(this._keyBufferArray.slice(-1 * this._keyBufferEndPattern.length), this._keyBufferEndPattern) &&
78           this._falsePositives) {
79         this._falsePositives--;
80
81       } else if (this._keyBufferEndPattern &&
82           _.isEqual(this._keyBufferArray.slice(-1 * this._keyBufferEndPattern.length), this._keyBufferEndPattern)) {
83
84         // first slice the end pattern off the payload
85         this._keyBuffer = this._keyBuffer.substring(0, this._keyBuffer.length - this._keyBufferEndPattern.length);
86
87         // we've matched an end pattern. Send the recorded buffer to the appropriate method
88         this[this._keyBufferMethod](this._keyBuffer);
89         this._keyBuffer = "";
90         this._keyBufferEndPattern = undefined;
91         this._falsePositives = undefined;
92       }
93
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;
102         }
103       });
104
105     },
106     // the components array is overriden by the subkind
107     components: [
108       {name: "postbooks", kind: "XV.ModuleContainer",  onTransitionStart: "handlePullout"},
109       {name: "pullout", kind: "XV.Pullout", onAnimateFinish: "pulloutAnimateFinish"},
110       {name: "signals", kind: "enyo.Signals", onkeydown: "handleKeyDown"},
111     ],
112     state: UNINITIALIZED,
113     /**
114       Passes the pullout payload straight from the sender (presumably the list
115       containing the pullout parameter) to the pullout, who will deal with
116       adding it.
117      */
118     addPulloutItem: function (inSender, inEvent) {
119       if (!this.$.pullout) {
120         this._cachePullouts.push(inEvent);
121         return;
122       }
123       this.$.pullout.addPulloutItem(inSender, inEvent);
124     },
125     columnsDidChange: function (inSender, inEvent) {
126       this.$.postbooks.getNavigator().waterfall("onColumnsChange", inEvent);
127     },
128     create: function () {
129       this._cachePullouts = [];
130       this._keyBufferArray = [];
131       this.inherited(arguments);
132       XT.app = this;
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();
137       };
138     },
139     handlePullout: function (inSender, inEvent) {
140       var showing = inSender.getActive().showPullout || false;
141       this.$.pullout.setShowing(showing);
142     },
143     /*
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.
147     */
148     modelChanged: function (inSender, inEvent) {
149       //
150       // Waterfall down to all lists to tell them to update themselves
151       //
152       this.$.postbooks.getNavigator().waterfall("onModelChange", inEvent);
153     },
154     parameterDidChange: function (inSender, inEvent) {
155       if (this.getIsStarted()) {
156         this.$.postbooks.getNavigator().waterfall("onParameterChange", inEvent);
157       }
158     },
159     /**
160       Implemented by the subkind
161     */
162     processHotKey: function (keyCode) {
163     },
164     /**
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.
170      */
171     pulloutAnimateFinish: function (inSender, inEvent) {
172       var activeIconButton;
173
174       if (inSender.value === inSender.max) {
175         // pullout is active
176         if (this.$.pullout.getSelectedPanel() === 'history') {
177           activeIconButton = 'history';
178         } else {
179           activeIconButton = 'search';
180         }
181       } else if (inSender.value === inSender.min) {
182         // pullout is inactive
183         activeIconButton = null;
184       }
185       this.$.postbooks.getNavigator().setActiveIconButton(activeIconButton);
186     },
187     refreshHistoryPanel: function (inSender, inEvent) {
188       this.$.pullout.refreshHistoryList();
189     },
190     /**
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.
194     */
195     selectHistoryItem: function (inSender, inEvent) {
196       inEvent.eventName = "onWorkspace";
197       this.waterfall("onWorkspace", inEvent);
198     },
199     startupProcess: function () {
200       var startupManager = XT.getStartupManager(),
201         progressBar = XT.app.$.postbooks.getStartupProgressBar(),
202         that = this,
203         prop,
204         ext,
205         extprop,
206         i = 0,
207         task,
208         startupTaskCount,
209         text,
210         inEvent,
211         ajax,
212         extensionSuccess,
213         extensionError,
214         extensionLocation,
215         extensionName,
216         extensionPrivilegeName,
217         extensionCount = 0,
218         extensionsDownloaded = 0,
219         extensionPayloads = [],
220         loginInfo,
221         details,
222         eachCallback = function () {
223           var completed = startupManager.get('completed').length;
224           progressBar.animateProgressTo(completed);
225           if (completed === startupTaskCount) {
226             that.startupProcess();
227           }
228         };
229
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);
237
238       // #2 is off high-wire walking the Grand Canyon
239
240       // 3: Initialize extensions
241       } else if (this.state === LOADING_SESSION) {
242
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);
253                 ext[extprop]();
254               }
255             }
256           }
257           i++;
258         }
259         this.startupProcess();
260
261       // 4. Load Schema
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",
268           task: function () {
269             var task = this,
270               options = {
271                 success: function () {
272                   task.didComplete();
273                   that.startupProcess();
274                 }
275               };
276             XT.session.loadSessionObjects(XT.session.SCHEMA, options);
277           }
278         });
279
280       // 5 Load Application Data
281       } else if (this.state === LOADING_SCHEMA) {
282         // Run startup tasks
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);
288
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;
294
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();
304           }
305         };
306
307         startupManager.registerCallback(eachCallback, true);
308         for (i = 0; i < XT.StartupTasks.length; i++) {
309           task = XT.StartupTasks[i];
310           XT.StartupTask.create(task);
311         }
312
313       // 6. Finish up
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);
319         }
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();
325       }
326     },
327     start: function (debug) {
328       if (this.getIsStarted()) { return; }
329       XT.app = this;
330       this.setDebug(debug);
331
332       // Run through the multi-step start process
333       this.startupProcess();
334
335       // lets not allow this to happen again
336       this.setIsStarted(true);
337     },
338     show: function () {
339       if (this.getShowing() && this.getIsStarted()) {
340         this.renderInto(document.body);
341       } else {
342         this.inherited(arguments);
343       }
344     },
345     togglePullout: function (inSender, inEvent) {
346       this.$.pullout.togglePullout(inSender, inEvent);
347     },
348     waterfallSearch: function (inSender, inEvent) {
349       this.$.postbooks.waterfall("onSearch", inEvent);
350       return true; // don't want to double up
351     },
352     waterfallWorkspace: function (inSender, inEvent) {
353       this.$.postbooks.waterfall("onWorkspace", inEvent);
354       return true; // don't want to double up
355     },
356     waterfallWorkspaceAction: function (inSender, inEvent) {
357       this.$.postbooks.waterfall("onWorkspaceAction", inEvent);
358       return true; // don't want to double up
359     }
360   });
361 }());