[ Index ] |
PHP Cross Reference of Phabricator |
[Summary view] [Print] [Text view]
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 });
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 |