Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/webroot/rsrc/js/application/herald/HeraldRuleEditor.js
12241 views
1
/**
2
* @requires multirow-row-manager
3
* javelin-install
4
* javelin-util
5
* javelin-dom
6
* javelin-stratcom
7
* javelin-json
8
* phabricator-prefab
9
* @provides herald-rule-editor
10
* @javelin
11
*/
12
13
JX.install('HeraldRuleEditor', {
14
construct : function(config) {
15
var root = JX.$(config.root);
16
this._root = root;
17
18
JX.DOM.listen(
19
root,
20
'click',
21
'create-condition',
22
JX.bind(this, this._onnewcondition));
23
24
JX.DOM.listen(
25
root,
26
'click',
27
'create-action',
28
JX.bind(this, this._onnewaction));
29
30
JX.DOM.listen(root, 'change', null, JX.bind(this, this._onchange));
31
JX.DOM.listen(root, 'submit', null, JX.bind(this, this._onsubmit));
32
33
var conditionsTable = JX.DOM.find(root, 'table', 'rule-conditions');
34
var actionsTable = JX.DOM.find(root, 'table', 'rule-actions');
35
36
this._conditionsRowManager = new JX.MultirowRowManager(conditionsTable);
37
this._conditionsRowManager.listen(
38
'row-removed',
39
JX.bind(this, function(row_id) {
40
delete this._config.conditions[row_id];
41
}));
42
43
this._actionsRowManager = new JX.MultirowRowManager(actionsTable);
44
this._actionsRowManager.listen(
45
'row-removed',
46
JX.bind(this, function(row_id) {
47
delete this._config.actions[row_id];
48
}));
49
50
this._conditionGetters = {};
51
this._conditionTypes = {};
52
this._actionGetters = {};
53
this._actionTypes = {};
54
55
this._config = config;
56
57
var conditions = this._config.conditions;
58
this._config.conditions = [];
59
60
var actions = this._config.actions;
61
this._config.actions = [];
62
63
this._renderConditions(conditions);
64
this._renderActions(actions);
65
},
66
67
members : {
68
_config : null,
69
_root : null,
70
_conditionGetters : null,
71
_conditionTypes : null,
72
_actionGetters : null,
73
_actionTypes : null,
74
_conditionsRowManager : null,
75
_actionsRowManager : null,
76
77
_onnewcondition : function(e) {
78
this._newCondition();
79
e.kill();
80
},
81
_onnewaction : function(e) {
82
this._newAction();
83
e.kill();
84
},
85
_onchange : function(e) {
86
var target = e.getTarget();
87
88
var row = e.getNode(JX.MultirowRowManager.getRowSigil());
89
if (!row) {
90
// Changing the "when all of / any of these..." dropdown.
91
return;
92
}
93
94
if (JX.Stratcom.hasSigil(target, 'field-select')) {
95
this._onfieldchange(row);
96
} else if (JX.Stratcom.hasSigil(target, 'condition-select')) {
97
this._onconditionchange(row);
98
} else if (JX.Stratcom.hasSigil(target, 'action-select')) {
99
this._onactionchange(row);
100
}
101
},
102
_onsubmit : function() {
103
var rule = JX.DOM.find(this._root, 'input', 'rule');
104
105
var k;
106
107
for (k in this._config.conditions) {
108
this._config.conditions[k][2] = this._getConditionValue(k);
109
}
110
111
for (k in this._config.actions) {
112
this._config.actions[k][1] = this._getActionTarget(k);
113
}
114
rule.value = JX.JSON.stringify({
115
conditions: this._config.conditions,
116
actions: this._config.actions
117
});
118
},
119
120
_getConditionValue : function(id) {
121
if (this._conditionGetters[id]) {
122
return this._conditionGetters[id]();
123
}
124
return this._config.conditions[id][2];
125
},
126
127
_getActionTarget : function(id) {
128
if (this._actionGetters[id]) {
129
return this._actionGetters[id]();
130
}
131
return this._config.actions[id][1];
132
},
133
134
_onactionchange : function(r) {
135
var target = JX.DOM.find(r, 'select', 'action-select');
136
var row_id = this._actionsRowManager.getRowID(r);
137
138
this._config.actions[row_id][0] = target.value;
139
140
var target_cell = JX.DOM.find(r, 'td', 'target-cell');
141
var target_input = this._renderTargetInputForRow(row_id);
142
143
JX.DOM.setContent(target_cell, target_input);
144
},
145
_onfieldchange : function(r) {
146
var target = JX.DOM.find(r, 'select', 'field-select');
147
var row_id = this._actionsRowManager.getRowID(r);
148
149
this._config.conditions[row_id][0] = target.value;
150
151
var condition_cell = JX.DOM.find(r, 'td', 'condition-cell');
152
var condition_select = this._renderSelect(
153
this._selectKeys(
154
this._config.info.conditions,
155
this._config.info.conditionMap[target.value]),
156
this._config.conditions[row_id][1],
157
'condition-select');
158
159
JX.DOM.setContent(condition_cell, condition_select);
160
161
this._onconditionchange(r);
162
163
var condition_name = this._config.conditions[row_id][1];
164
if (condition_name == 'unconditionally') {
165
JX.DOM.hide(condition_select);
166
}
167
},
168
_onconditionchange : function(r) {
169
var target = JX.DOM.find(r, 'select', 'condition-select');
170
var row_id = this._conditionsRowManager.getRowID(r);
171
172
this._config.conditions[row_id][1] = target.value;
173
174
var value_cell = JX.DOM.find(r, 'td', 'value-cell');
175
var value_input = this._renderValueInputForRow(row_id);
176
JX.DOM.setContent(value_cell, value_input);
177
},
178
179
_renderTargetInputForRow : function(row_id) {
180
var action = this._config.actions[row_id];
181
var type = this._config.info.targets[action[0]];
182
183
var input = this._buildInput(type);
184
var node = input[0];
185
var get_fn = input[1];
186
var set_fn = input[2];
187
188
if (node) {
189
JX.Stratcom.addSigil(node, 'action-target');
190
}
191
192
var old_type = this._actionTypes[row_id];
193
if (old_type == type || !old_type) {
194
set_fn(this._getActionTarget(row_id));
195
}
196
197
this._actionTypes[row_id] = type;
198
this._actionGetters[row_id] = get_fn;
199
200
return node;
201
},
202
203
_buildInput : function(type) {
204
var spec = this._config.info.valueMap[type];
205
206
var input;
207
var get_fn;
208
var set_fn;
209
switch (spec.control) {
210
case 'herald.control.none':
211
input = null;
212
get_fn = JX.bag;
213
set_fn = JX.bag;
214
break;
215
case 'herald.control.text':
216
input = JX.$N('input', {type: 'text'});
217
get_fn = function() { return input.value; };
218
set_fn = function(v) { input.value = v; };
219
break;
220
case 'herald.control.remarkup':
221
input = JX.$N('textarea');
222
get_fn = function() { return input.value; };
223
set_fn = function(v) { input.value = v; };
224
break;
225
case 'herald.control.select':
226
var options;
227
228
// NOTE: This is a hacky special case for "Another Herald Rule",
229
// which we don't currently generate normal options for.
230
231
if (spec.key == 'select.rule') {
232
options = this._config.template.rules;
233
} else {
234
options = spec.template.options;
235
}
236
237
input = this._renderSelect(options);
238
get_fn = function() { return input.value; };
239
set_fn = function(v) { input.value = v; };
240
if (spec.template.default) {
241
set_fn(spec.template.default);
242
}
243
break;
244
case 'herald.control.tokenizer':
245
var tokenizer = this._newTokenizer(spec.template.tokenizer);
246
input = tokenizer[0];
247
get_fn = tokenizer[1];
248
set_fn = tokenizer[2];
249
break;
250
default:
251
JX.$E('No rules to build control "' + spec.control + '".');
252
break;
253
}
254
255
return [input, get_fn, set_fn];
256
},
257
258
_renderValueInputForRow : function(row_id) {
259
var cond = this._config.conditions[row_id];
260
var type = this._config.info.values[cond[0]][cond[1]];
261
262
var input = this._buildInput(type);
263
var node = input[0];
264
var get_fn = input[1];
265
var set_fn = input[2];
266
267
if (node) {
268
JX.Stratcom.addSigil(node, 'condition-value');
269
}
270
271
var old_type = this._conditionTypes[row_id];
272
if (old_type == type || !old_type) {
273
set_fn(this._getConditionValue(row_id));
274
}
275
276
this._conditionTypes[row_id] = type;
277
this._conditionGetters[row_id] = get_fn;
278
279
return node;
280
},
281
282
_newTokenizer : function(spec) {
283
var tokenizerConfig = {
284
src: spec.datasourceURI,
285
placeholder: spec.placeholder,
286
browseURI: spec.browseURI,
287
limit: spec.limit
288
};
289
290
var build = JX.Prefab.newTokenizerFromTemplate(
291
this._config.template.markup,
292
tokenizerConfig);
293
build.tokenizer.start();
294
295
return [
296
build.node,
297
function() {
298
return build.tokenizer.getTokens();
299
},
300
function(map) {
301
for (var k in map) {
302
var v = map[k];
303
304
// The control value may be set from wire values from the server,
305
// or a transformed value from another control, or a bare string
306
// value from another control.
307
if (typeof v == 'string') {
308
v = v;
309
} else if (!v.hasOwnProperty('id')) {
310
v = JX.Prefab.transformDatasourceResults(v);
311
}
312
313
build.tokenizer.addToken(k, v);
314
}
315
}];
316
},
317
_selectKeys : function(map, keys) {
318
var r = {};
319
for (var ii = 0; ii < keys.length; ii++) {
320
r[keys[ii]] = map[keys[ii]];
321
}
322
return r;
323
},
324
_renderConditions : function(conditions) {
325
for (var k in conditions) {
326
this._newCondition(conditions[k]);
327
}
328
},
329
_newCondition : function(data) {
330
var row = this._conditionsRowManager.addRow([]);
331
var row_id = this._conditionsRowManager.getRowID(row);
332
333
var default_condition = [
334
this._config.default.field,
335
this._config.default.condition,
336
null
337
];
338
this._config.conditions[row_id] = data || default_condition;
339
340
var r = this._conditionsRowManager.updateRow(
341
row_id,
342
this._renderCondition(row_id));
343
344
this._onfieldchange(r);
345
},
346
_renderCondition : function(row_id) {
347
var groups = this._config.info.fields;
348
349
var attrs = {
350
sigil: 'field-select'
351
};
352
353
var field_select = this._renderGroupSelect(
354
groups,
355
attrs,
356
this._config.conditions[row_id][0]);
357
358
var field_cell = JX.$N('td', {sigil: 'field-cell'}, field_select);
359
360
var condition_cell = JX.$N('td', {sigil: 'condition-cell'});
361
var value_cell = JX.$N('td', {className : 'value', sigil: 'value-cell'});
362
363
return [field_cell, condition_cell, value_cell];
364
},
365
_renderActions : function(actions) {
366
for (var k in actions) {
367
this._newAction(actions[k]);
368
delete actions[k];
369
}
370
},
371
372
_renderGroupSelect: function(groups, attrs, value) {
373
var optgroups = [];
374
for (var ii = 0; ii < groups.length; ii++) {
375
var group = groups[ii];
376
var options = [];
377
for (var k in group.options) {
378
var option = group.options[k];
379
380
var name = option.name;
381
var available = option.available;
382
383
// See T7961. If the option is not marked as "available", we only
384
// include it in the dropdown if the dropdown already has it as a
385
// value. We want to hide options provided by applications which are
386
// not installed, but do not want to break existing rules.
387
388
if (available || (k === value)) {
389
options.push(JX.$N('option', {value: k}, name));
390
}
391
}
392
if (options.length) {
393
optgroups.push(JX.$N('optgroup', {label: group.label}, options));
394
}
395
}
396
397
var select = JX.$N('select', attrs, optgroups);
398
399
if (value !== undefined) {
400
select.value = value;
401
}
402
403
return select;
404
},
405
406
_newAction : function(data) {
407
var default_action = [
408
this._config.default.action,
409
null
410
];
411
412
data = data || default_action;
413
var temprow = this._actionsRowManager.addRow([]);
414
var row_id = this._actionsRowManager.getRowID(temprow);
415
this._config.actions[row_id] = data;
416
var r = this._actionsRowManager.updateRow(row_id,
417
this._renderAction(data));
418
this._onactionchange(r);
419
},
420
421
_renderAction : function(action) {
422
var groups = this._config.info.actions;
423
var attrs = {
424
sigil: 'action-select'
425
};
426
427
var action_select = this._renderGroupSelect(
428
groups,
429
attrs,
430
action[0]);
431
432
var action_cell = JX.$N('td', {sigil: 'action-cell'}, action_select);
433
434
var target_cell = JX.$N(
435
'td',
436
{className : 'target', sigil : 'target-cell'});
437
438
return [action_cell, target_cell];
439
},
440
_renderSelect : function(map, selected, sigil) {
441
var attrs = {
442
sigil : sigil
443
};
444
return JX.Prefab.renderSelect(map, selected, attrs);
445
}
446
}
447
});
448
449