[ Index ]

PHP Cross Reference of Phabricator

title

Body

[close]

/webroot/rsrc/externals/javelin/lib/ -> Workflow.js (source)

   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  });


Generated: Sun Nov 30 09:20:46 2014 Cross-referenced by PHPXref 0.7.1