[ Index ]

PHP Cross Reference of Phabricator

title

Body

[close]

/webroot/rsrc/js/core/ -> Prefab.js (source)

   1  /**
   2   * @provides phabricator-prefab
   3   * @requires javelin-install
   4   *           javelin-util
   5   *           javelin-dom
   6   *           javelin-typeahead
   7   *           javelin-tokenizer
   8   *           javelin-typeahead-preloaded-source
   9   *           javelin-typeahead-ondemand-source
  10   *           javelin-dom
  11   *           javelin-stratcom
  12   *           javelin-util
  13   * @javelin
  14   */
  15  
  16  /**
  17   * Utilities for client-side rendering (the greatest thing in the world).
  18   */
  19  JX.install('Prefab', {
  20  
  21    statics : {
  22      renderSelect : function(map, selected, attrs) {
  23        var select = JX.$N('select', attrs || {});
  24        for (var k in map) {
  25          select.options[select.options.length] = new Option(map[k], k);
  26          if (k == selected) {
  27            select.value = k;
  28          }
  29        }
  30        select.value = select.value || JX.keys(map)[0];
  31        return select;
  32      },
  33  
  34  
  35      /**
  36       * Build a Phabricator tokenizer out of a configuration with application
  37       * sorting, datasource and placeholder rules.
  38       *
  39       *   - `id` Root tokenizer ID (alternatively, pass `root`).
  40       *   - `root` Root tokenizer node (replaces `id`).
  41       *   - `src` Datasource URI.
  42       *   - `ondemand` Optional, use an ondemand source.
  43       *   - `value` Optional, initial value.
  44       *   - `limit` Optional, token limit.
  45       *   - `placeholder` Optional, placeholder text.
  46       *   - `username` Optional, username to sort first (i.e., viewer).
  47       *   - `icons` Optional, map of icons.
  48       *
  49       */
  50      buildTokenizer : function(config) {
  51        config.icons = config.icons || {};
  52  
  53        var root;
  54  
  55        try {
  56          root = config.root || JX.$(config.id);
  57        } catch (ex) {
  58          // If the root element does not exist, just return without building
  59          // anything. This happens in some cases -- like Conpherence -- where we
  60          // may load a tokenizer but not put it in the document.
  61          return;
  62        }
  63  
  64        var datasource;
  65  
  66        // Default to an ondemand source if no alternate configuration is
  67        // provided.
  68        var ondemand = true;
  69        if ('ondemand' in config) {
  70          ondemand = config.ondemand;
  71        }
  72  
  73        if (ondemand) {
  74          datasource = new JX.TypeaheadOnDemandSource(config.src);
  75        } else {
  76          datasource = new JX.TypeaheadPreloadedSource(config.src);
  77        }
  78  
  79        // Sort results so that the viewing user always comes up first; after
  80        // that, prefer unixname matches to realname matches.
  81  
  82        var sort_handler = function(value, list, cmp) {
  83          var priority_hits = {};
  84          var self_hits     = {};
  85  
  86          var tokens = this.tokenize(value);
  87  
  88          for (var ii = 0; ii < list.length; ii++) {
  89            var item = list[ii];
  90            if (!item.priority) {
  91              continue;
  92            }
  93  
  94            if (config.username && item.priority == config.username) {
  95              self_hits[item.id] = true;
  96            }
  97  
  98            for (var jj = 0; jj < tokens.length; jj++) {
  99              if (item.priority.substr(0, tokens[jj].length) == tokens[jj]) {
 100                priority_hits[item.id] = true;
 101              }
 102            }
 103          }
 104  
 105          list.sort(function(u, v) {
 106            if (self_hits[u.id] != self_hits[v.id]) {
 107              return self_hits[v.id] ? 1 : -1;
 108            }
 109  
 110            // If one result is open and one is closed, show the open result
 111            // first. The "!" tricks here are becaused closed values are display
 112            // strings, so the value is either `null` or some truthy string. If
 113            // we compare the values directly, we'll apply this rule to two
 114            // objects which are both closed but for different reasons, like
 115            // "Archived" and "Disabled".
 116  
 117            var u_open = !u.closed;
 118            var v_open = !v.closed;
 119  
 120            if (u_open != v_open) {
 121              if (u_open) {
 122                return -1;
 123              } else {
 124                return 1;
 125              }
 126            }
 127  
 128            if (priority_hits[u.id] != priority_hits[v.id]) {
 129              return priority_hits[v.id] ? 1 : -1;
 130            }
 131  
 132            // Sort users ahead of other result types.
 133            if (u.priorityType != v.priorityType) {
 134              if (u.priorityType == 'user') {
 135                return -1;
 136              }
 137              if (v.priorityType == 'user') {
 138                return 1;
 139              }
 140            }
 141  
 142            return cmp(u, v);
 143          });
 144        };
 145  
 146        datasource.setSortHandler(JX.bind(datasource, sort_handler));
 147        datasource.setFilterHandler(JX.Prefab.filterClosedResults);
 148        datasource.setTransformer(JX.Prefab.transformDatasourceResults);
 149  
 150        var typeahead = new JX.Typeahead(
 151          root,
 152          JX.DOM.find(root, 'input', 'tokenizer-input'));
 153        typeahead.setDatasource(datasource);
 154  
 155        var tokenizer = new JX.Tokenizer(root);
 156        tokenizer.setTypeahead(typeahead);
 157        tokenizer.setRenderTokenCallback(function(value, key) {
 158          var result = datasource.getResult(key);
 159  
 160          var icon;
 161          if (result) {
 162            icon = result.icon;
 163            value = result.displayName;
 164          } else {
 165            icon = config.icons[key];
 166          }
 167  
 168          if (icon) {
 169            icon = JX.Prefab._renderIcon(icon);
 170          }
 171  
 172          // TODO: Maybe we should render these closed tags in grey? Figure out
 173          // how we're going to use color.
 174  
 175          return [icon, value];
 176        });
 177  
 178        if (config.placeholder) {
 179          tokenizer.setPlaceholder(config.placeholder);
 180        }
 181  
 182        if (config.limit) {
 183          tokenizer.setLimit(config.limit);
 184        }
 185  
 186        if (config.value) {
 187          tokenizer.setInitialValue(config.value);
 188        }
 189  
 190        JX.Stratcom.addData(root, {'tokenizer' : tokenizer});
 191  
 192        return {
 193          tokenizer: tokenizer
 194        };
 195      },
 196  
 197      /**
 198       * Filter callback for tokenizers and typeaheads which filters out closed
 199       * or disabled objects unless they are the only options.
 200       */
 201      filterClosedResults: function(value, list) {
 202        // Look for any open result.
 203        var has_open = false;
 204        var ii;
 205        for (ii = 0; ii < list.length; ii++) {
 206          if (!list[ii].closed) {
 207            has_open = true;
 208            break;
 209          }
 210        }
 211  
 212        if (!has_open) {
 213          // Everything is closed, so just use it as-is.
 214          return list;
 215        }
 216  
 217        // Otherwise, only display the open results.
 218        var results = [];
 219        for (ii = 0; ii < list.length; ii++) {
 220          if (!list[ii].closed) {
 221            results.push(list[ii]);
 222          }
 223        }
 224  
 225        return results;
 226      },
 227  
 228      /**
 229       * Transform results from a wire format into a usable format in a standard
 230       * way.
 231       */
 232      transformDatasourceResults: function(fields) {
 233        var closed = fields[9];
 234        var closed_ui;
 235        if (closed) {
 236          closed_ui = JX.$N(
 237            'div',
 238            {className: 'tokenizer-closed'},
 239            closed);
 240        }
 241  
 242        var icon = fields[8];
 243        var icon_ui;
 244        if (icon) {
 245          icon_ui = JX.Prefab._renderIcon(icon);
 246        }
 247  
 248        var display = JX.$N(
 249          'div',
 250          {className: 'tokenizer-result'},
 251          [icon_ui, fields[4] || fields[0], closed_ui]);
 252        if (closed) {
 253          JX.DOM.alterClass(display, 'tokenizer-result-closed', true);
 254        }
 255  
 256        return {
 257          name: fields[0],
 258          displayName: fields[4] || fields[0],
 259          display: display,
 260          uri: fields[1],
 261          id: fields[2],
 262          priority: fields[3],
 263          priorityType: fields[7],
 264          imageURI: fields[6],
 265          icon: icon,
 266          closed: closed,
 267          type: fields[5],
 268          sprite: fields[10]
 269        };
 270      },
 271  
 272      _renderIcon: function(icon) {
 273        return JX.$N(
 274          'span',
 275          {className: 'phui-icon-view phui-font-fa ' + icon});
 276      }
 277  
 278    }
 279  
 280  });


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