[ Index ] |
PHP Cross Reference of Phabricator |
[Summary view] [Print] [Text view]
1 /** 2 * @requires javelin-stratcom 3 * javelin-request 4 * javelin-dom 5 * javelin-vector 6 * javelin-install 7 * javelin-util 8 * javelin-mask 9 * javelin-uri 10 * javelin-routable 11 * @provides javelin-workflow 12 * @javelin 13 */ 14 15 JX.install('Workflow', { 16 construct : function(uri, data) { 17 if (__DEV__) { 18 if (!uri || uri == '#') { 19 JX.$E( 20 'new JX.Workflow(<?>, ...): '+ 21 'bogus URI provided when creating workflow.'); 22 } 23 } 24 this.setURI(uri); 25 this.setData(data || {}); 26 }, 27 28 events : ['error', 'finally', 'submit'], 29 30 statics : { 31 _stack : [], 32 newFromForm : function(form, data) { 33 var pairs = JX.DOM.convertFormToListOfPairs(form); 34 for (var k in data) { 35 pairs.push([k, data[k]]); 36 } 37 38 // Disable form elements during the request 39 var inputs = [].concat( 40 JX.DOM.scry(form, 'input'), 41 JX.DOM.scry(form, 'button'), 42 JX.DOM.scry(form, 'textarea')); 43 for (var ii = 0; ii < inputs.length; ii++) { 44 if (inputs[ii].disabled) { 45 delete inputs[ii]; 46 } else { 47 inputs[ii].disabled = true; 48 } 49 } 50 51 var workflow = new JX.Workflow(form.getAttribute('action'), {}); 52 workflow.setDataWithListOfPairs(pairs); 53 workflow.setMethod(form.getAttribute('method')); 54 workflow.listen('finally', function() { 55 // Re-enable form elements 56 for (var ii = 0; ii < inputs.length; ii++) { 57 inputs[ii] && (inputs[ii].disabled = false); 58 } 59 }); 60 return workflow; 61 }, 62 newFromLink : function(link) { 63 var workflow = new JX.Workflow(link.href); 64 return workflow; 65 }, 66 _push : function(workflow) { 67 JX.Mask.show(); 68 JX.Workflow._stack.push(workflow); 69 }, 70 _pop : function() { 71 var dialog = JX.Workflow._stack.pop(); 72 (dialog.getCloseHandler() || JX.bag)(); 73 dialog._destroy(); 74 JX.Mask.hide(); 75 }, 76 disable : function() { 77 JX.Workflow._disabled = true; 78 }, 79 _onbutton : function(event) { 80 81 if (JX.Stratcom.pass()) { 82 return; 83 } 84 85 if (JX.Workflow._disabled) { 86 return; 87 } 88 89 // Get the button (which is sometimes actually another tag, like an <a />) 90 // which triggered the event. In particular, this makes sure we get the 91 // right node if there is a <button> with an <img /> inside it or 92 // or something similar. 93 var t = event.getNode('jx-workflow-button') || 94 event.getNode('tag:button'); 95 96 // If this button disables workflow (normally, because it is a file 97 // download button) let the event through without modification. 98 if (JX.Stratcom.getData(t).disableWorkflow) { 99 return; 100 } 101 102 event.prevent(); 103 104 if (t.name == '__cancel__' || t.name == '__close__') { 105 JX.Workflow._pop(); 106 } else { 107 var form = event.getNode('jx-dialog'); 108 JX.Workflow._dosubmit(form, t); 109 } 110 }, 111 _onsyntheticsubmit : function(e) { 112 if (JX.Stratcom.pass()) { 113 return; 114 } 115 if (JX.Workflow._disabled) { 116 return; 117 } 118 e.prevent(); 119 var form = e.getNode('jx-dialog'); 120 var button = JX.DOM.find(form, 'button', '__default__'); 121 JX.Workflow._dosubmit(form, button); 122 }, 123 _dosubmit : function(form, button) { 124 // Issue a DOM event first, so form-oriented handlers can act. 125 var dom_event = JX.DOM.invoke(form, 'didWorkflowSubmit'); 126 if (dom_event.getPrevented()) { 127 return; 128 } 129 130 var data = JX.DOM.convertFormToListOfPairs(form); 131 data.push([button.name, button.value || true]); 132 133 var active = JX.Workflow._getActiveWorkflow(); 134 var e = active.invoke('submit', {form: form, data: data}); 135 if (!e.getStopped()) { 136 active._destroy(); 137 active 138 .setURI(form.getAttribute('action') || active.getURI()) 139 .setDataWithListOfPairs(data) 140 .start(); 141 } 142 }, 143 _getActiveWorkflow : function() { 144 var stack = JX.Workflow._stack; 145 return stack[stack.length - 1]; 146 } 147 }, 148 149 members : { 150 _root : null, 151 _pushed : false, 152 _data : null, 153 _onload : function(r) { 154 // It is permissible to send back a falsey redirect to force a page 155 // reload, so we need to take this branch if the key is present. 156 if (r && (typeof r.redirect != 'undefined')) { 157 JX.$U(r.redirect).go(); 158 } else if (r && r.dialog) { 159 this._push(); 160 this._root = JX.$N( 161 'div', 162 {className: 'jx-client-dialog'}, 163 JX.$H(r.dialog)); 164 JX.DOM.listen( 165 this._root, 166 'click', 167 [['jx-workflow-button'], ['tag:button']], 168 JX.Workflow._onbutton); 169 JX.DOM.listen( 170 this._root, 171 'didSyntheticSubmit', 172 [], 173 JX.Workflow._onsyntheticsubmit); 174 document.body.appendChild(this._root); 175 var d = JX.Vector.getDim(this._root); 176 var v = JX.Vector.getViewport(); 177 var s = JX.Vector.getScroll(); 178 179 // Normally, we position dialogs 100px from the top of the screen. 180 // Use more space if the dialog is large (at least roughly the size 181 // of the viewport). 182 var offset = Math.min(Math.max(20, (v.y - d.y) / 2), 100); 183 JX.$V((v.x - d.x) / 2, s.y + offset).setPos(this._root); 184 185 try { 186 JX.DOM.focus(JX.DOM.find(this._root, 'button', '__default__')); 187 var inputs = JX.DOM.scry(this._root, 'input') 188 .concat(JX.DOM.scry(this._root, 'textarea')); 189 var miny = Number.POSITIVE_INFINITY; 190 var target = null; 191 for (var ii = 0; ii < inputs.length; ++ii) { 192 if (inputs[ii].type != 'hidden') { 193 // Find the topleft-most displayed element. 194 var p = JX.$V(inputs[ii]); 195 if (p.y < miny) { 196 miny = p.y; 197 target = inputs[ii]; 198 } 199 } 200 } 201 target && JX.DOM.focus(target); 202 } catch (_ignored) {} 203 204 // The `focus()` call may have scrolled the window. Scroll it back to 205 // where it was before -- we want to focus the control, but not adjust 206 // the scroll position. 207 window.scrollTo(s.x, s.y); 208 209 } else if (this.getHandler()) { 210 this.getHandler()(r); 211 this._pop(); 212 } else if (r) { 213 if (__DEV__) { 214 JX.$E('Response to workflow request went unhandled.'); 215 } 216 } 217 }, 218 _push : function() { 219 if (!this._pushed) { 220 this._pushed = true; 221 JX.Workflow._push(this); 222 } 223 }, 224 _pop : function() { 225 if (this._pushed) { 226 this._pushed = false; 227 JX.Workflow._pop(); 228 } 229 }, 230 _destroy : function() { 231 if (this._root) { 232 JX.DOM.remove(this._root); 233 this._root = null; 234 } 235 }, 236 start : function() { 237 var uri = this.getURI(); 238 var method = this.getMethod(); 239 var r = new JX.Request(uri, JX.bind(this, this._onload)); 240 var list_of_pairs = this._data; 241 list_of_pairs.push(['__wflow__', true]); 242 r.setDataWithListOfPairs(list_of_pairs); 243 r.setDataSerializer(this.getDataSerializer()); 244 if (method) { 245 r.setMethod(method); 246 } 247 r.listen('finally', JX.bind(this, this.invoke, 'finally')); 248 r.listen('error', JX.bind(this, function(error) { 249 var e = this.invoke('error', error); 250 if (e.getStopped()) { 251 return; 252 } 253 // TODO: Default error behavior? On Facebook Lite, we just shipped the 254 // user to "/error/". We could emit a blanket 'workflow-failed' type 255 // event instead. 256 })); 257 r.send(); 258 }, 259 260 getRoutable: function() { 261 var routable = new JX.Routable(); 262 routable.listen('start', JX.bind(this, function() { 263 // Pass the event to allow other listeners to "start" to configure this 264 // workflow before it fires. 265 JX.Stratcom.pass(JX.Stratcom.context()); 266 this.start(); 267 })); 268 this.listen('finally', JX.bind(routable, routable.done)); 269 return routable; 270 }, 271 272 setData : function(dictionary) { 273 this._data = []; 274 for (var k in dictionary) { 275 this._data.push([k, dictionary[k]]); 276 } 277 return this; 278 }, 279 280 setDataWithListOfPairs : function(list_of_pairs) { 281 this._data = list_of_pairs; 282 return this; 283 } 284 }, 285 286 properties : { 287 handler : null, 288 closeHandler : null, 289 dataSerializer : null, 290 method : null, 291 URI : null 292 }, 293 294 initialize : function() { 295 296 function close_dialog_when_user_presses_escape(e) { 297 if (e.getSpecialKey() != 'esc') { 298 // Some key other than escape. 299 return; 300 } 301 302 if (JX.Workflow._disabled) { 303 // Workflows are disabled on this page. 304 return; 305 } 306 307 if (JX.Stratcom.pass()) { 308 // Something else swallowed the event. 309 return; 310 } 311 312 var active = JX.Workflow._getActiveWorkflow(); 313 if (!active) { 314 // No active workflow. 315 return; 316 } 317 318 // Note: the cancel button is actually an <a /> tag. 319 var buttons = JX.DOM.scry(active._root, 'a', 'jx-workflow-button'); 320 if (!buttons.length) { 321 // No buttons in the dialog. 322 return; 323 } 324 325 var cancel = null; 326 for (var ii = 0; ii < buttons.length; ii++) { 327 if (buttons[ii].name == '__cancel__') { 328 cancel = buttons[ii]; 329 break; 330 } 331 } 332 333 if (!cancel) { 334 // No 'Cancel' button. 335 return; 336 } 337 338 JX.Workflow._pop(); 339 e.prevent(); 340 } 341 342 JX.Stratcom.listen('keydown', null, close_dialog_when_user_presses_escape); 343 } 344 345 });
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Sun Nov 30 09:20:46 2014 | Cross-referenced by PHPXref 0.7.1 |