@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Custom Policy Editor

Summary:
Ref T603. This isn't remotely usable yet, but I wanted to get any feedback before I build it out anymore.

I think this is a reasonable interface for defining custom policies? It's basically similar to Herald, although it's a bit simpler.

I imagine users will rarely interact with this, but this will service the high end of policy complexity (and allow the definition of things like "is member of LDAP group" or whatever).

Test Plan: See screenshots.

Reviewers: btrahan, chad

Reviewed By: btrahan

CC: aran, asherkin

Maniphest Tasks: T603

Differential Revision: https://secure.phabricator.com/D7217

+621 -1
+28 -1
src/__celerity_resource_map__.php
··· 2265 2265 ), 2266 2266 'disk' => '/rsrc/js/application/pholio/behavior-pholio-mock-view.js', 2267 2267 ), 2268 + 'javelin-behavior-policy-rule-editor' => 2269 + array( 2270 + 'uri' => '/res/4ae4249d/rsrc/js/application/policy/behavior-policy-rule-editor.js', 2271 + 'type' => 'js', 2272 + 'requires' => 2273 + array( 2274 + 0 => 'javelin-behavior', 2275 + 1 => 'multirow-row-manager', 2276 + 2 => 'javelin-dom', 2277 + 3 => 'javelin-util', 2278 + 4 => 'phabricator-prefab', 2279 + 5 => 'javelin-tokenizer', 2280 + 6 => 'javelin-typeahead', 2281 + 7 => 'javelin-typeahead-preloaded-source', 2282 + 8 => 'javelin-json', 2283 + ), 2284 + 'disk' => '/rsrc/js/application/policy/behavior-policy-rule-editor.js', 2285 + ), 2268 2286 'javelin-behavior-ponder-votebox' => 2269 2287 array( 2270 2288 'uri' => '/res/c28daa12/rsrc/js/application/ponder/behavior-votebox.js', ··· 3886 3904 ), 3887 3905 'policy-css' => 3888 3906 array( 3889 - 'uri' => '/res/ebb12aa0/rsrc/css/application/policy/policy.css', 3907 + 'uri' => '/res/51325bff/rsrc/css/application/policy/policy.css', 3890 3908 'type' => 'css', 3891 3909 'requires' => 3892 3910 array( 3893 3911 ), 3894 3912 'disk' => '/rsrc/css/application/policy/policy.css', 3913 + ), 3914 + 'policy-edit-css' => 3915 + array( 3916 + 'uri' => '/res/1e2a2b5e/rsrc/css/application/policy/policy-edit.css', 3917 + 'type' => 'css', 3918 + 'requires' => 3919 + array( 3920 + ), 3921 + 'disk' => '/rsrc/css/application/policy/policy-edit.css', 3895 3922 ), 3896 3923 'ponder-comment-table-css' => 3897 3924 array(
+11
src/__phutil_library_map__.php
··· 1483 1483 'PhabricatorPolicyConstants' => 'applications/policy/constants/PhabricatorPolicyConstants.php', 1484 1484 'PhabricatorPolicyController' => 'applications/policy/controller/PhabricatorPolicyController.php', 1485 1485 'PhabricatorPolicyDataTestCase' => 'applications/policy/__tests__/PhabricatorPolicyDataTestCase.php', 1486 + 'PhabricatorPolicyEditController' => 'applications/policy/controller/PhabricatorPolicyEditController.php', 1486 1487 'PhabricatorPolicyException' => 'applications/policy/exception/PhabricatorPolicyException.php', 1487 1488 'PhabricatorPolicyExplainController' => 'applications/policy/controller/PhabricatorPolicyExplainController.php', 1488 1489 'PhabricatorPolicyFilter' => 'applications/policy/filter/PhabricatorPolicyFilter.php', ··· 1491 1492 'PhabricatorPolicyManagementUnlockWorkflow' => 'applications/policy/management/PhabricatorPolicyManagementUnlockWorkflow.php', 1492 1493 'PhabricatorPolicyManagementWorkflow' => 'applications/policy/management/PhabricatorPolicyManagementWorkflow.php', 1493 1494 'PhabricatorPolicyQuery' => 'applications/policy/query/PhabricatorPolicyQuery.php', 1495 + 'PhabricatorPolicyRule' => 'applications/policy/rule/PhabricatorPolicyRule.php', 1496 + 'PhabricatorPolicyRuleAdministrators' => 'applications/policy/rule/PhabricatorPolicyRuleAdministrators.php', 1497 + 'PhabricatorPolicyRuleLunarPhase' => 'applications/policy/rule/PhabricatorPolicyRuleLunarPhase.php', 1498 + 'PhabricatorPolicyRuleProjects' => 'applications/policy/rule/PhabricatorPolicyRuleProjects.php', 1499 + 'PhabricatorPolicyRuleUsers' => 'applications/policy/rule/PhabricatorPolicyRuleUsers.php', 1494 1500 'PhabricatorPolicyTestCase' => 'applications/policy/__tests__/PhabricatorPolicyTestCase.php', 1495 1501 'PhabricatorPolicyTestObject' => 'applications/policy/__tests__/PhabricatorPolicyTestObject.php', 1496 1502 'PhabricatorPolicyType' => 'applications/policy/constants/PhabricatorPolicyType.php', ··· 3672 3678 'PhabricatorPolicyConfigOptions' => 'PhabricatorApplicationConfigOptions', 3673 3679 'PhabricatorPolicyController' => 'PhabricatorController', 3674 3680 'PhabricatorPolicyDataTestCase' => 'PhabricatorTestCase', 3681 + 'PhabricatorPolicyEditController' => 'PhabricatorPolicyController', 3675 3682 'PhabricatorPolicyException' => 'Exception', 3676 3683 'PhabricatorPolicyExplainController' => 'PhabricatorPolicyController', 3677 3684 'PhabricatorPolicyManagementShowWorkflow' => 'PhabricatorPolicyManagementWorkflow', 3678 3685 'PhabricatorPolicyManagementUnlockWorkflow' => 'PhabricatorPolicyManagementWorkflow', 3679 3686 'PhabricatorPolicyManagementWorkflow' => 'PhutilArgumentWorkflow', 3680 3687 'PhabricatorPolicyQuery' => 'PhabricatorQuery', 3688 + 'PhabricatorPolicyRuleAdministrators' => 'PhabricatorPolicyRule', 3689 + 'PhabricatorPolicyRuleLunarPhase' => 'PhabricatorPolicyRule', 3690 + 'PhabricatorPolicyRuleProjects' => 'PhabricatorPolicyRule', 3691 + 'PhabricatorPolicyRuleUsers' => 'PhabricatorPolicyRule', 3681 3692 'PhabricatorPolicyTestCase' => 'PhabricatorTestCase', 3682 3693 'PhabricatorPolicyTestObject' => 'PhabricatorPolicyInterface', 3683 3694 'PhabricatorPolicyType' => 'PhabricatorPolicyConstants',
+1
src/applications/policy/application/PhabricatorApplicationPolicy.php
··· 15 15 '/policy/' => array( 16 16 'explain/(?P<phid>[^/]+)/(?P<capability>[^/]+)/' 17 17 => 'PhabricatorPolicyExplainController', 18 + 'edit/' => 'PhabricatorPolicyEditController', 18 19 ), 19 20 ); 20 21 }
+153
src/applications/policy/controller/PhabricatorPolicyEditController.php
··· 1 + <?php 2 + 3 + final class PhabricatorPolicyEditController 4 + extends PhabricatorPolicyController { 5 + 6 + public function processRequest() { 7 + $request = $this->getRequest(); 8 + $viewer = $request->getUser(); 9 + 10 + $root_id = celerity_generate_unique_node_id(); 11 + 12 + $action_options = array( 13 + 'allow' => pht('Allow'), 14 + 'deny' => pht('Deny'), 15 + ); 16 + 17 + $rules = id(new PhutilSymbolLoader()) 18 + ->setAncestorClass('PhabricatorPolicyRule') 19 + ->loadObjects(); 20 + 21 + $rules = msort($rules, 'getRuleOrder'); 22 + 23 + $default_value = 'deny'; 24 + $default_rule = array( 25 + 'action' => head_key($action_options), 26 + 'rule' => head_key($rules), 27 + 'value' => null, 28 + ); 29 + 30 + if ($request->isFormPost()) { 31 + $data = $request->getStr('rules'); 32 + $data = @json_decode($data, true); 33 + if (!is_array($data)) { 34 + throw new Exception("Failed to JSON decode rule data!"); 35 + } 36 + 37 + $rule_data = array(); 38 + foreach ($data as $rule) { 39 + $action = idx($rule, 'action'); 40 + switch ($action) { 41 + case 'allow': 42 + case 'deny': 43 + break; 44 + default: 45 + throw new Exception("Invalid action '{$action}'!"); 46 + } 47 + 48 + $rule_class = idx($rule, 'rule'); 49 + if (empty($rules[$rule_class])) { 50 + throw new Exception("Invalid rule class '{$rule_class}'!"); 51 + } 52 + 53 + $rule_obj = $rules[$rule_class]; 54 + 55 + $value = $rule_obj->getValueForStorage(idx($rule, 'value')); 56 + $value = $rule_obj->getValueForDisplay($viewer, $value); 57 + 58 + $rule_data[] = array( 59 + 'action' => $action, 60 + 'rule' => $rule_class, 61 + 'value' => $value, 62 + ); 63 + } 64 + 65 + $default_value = $request->getStr('default'); 66 + } else { 67 + $rule_data = array( 68 + $default_rule, 69 + ); 70 + } 71 + 72 + $default_select = AphrontFormSelectControl::renderSelectTag( 73 + $default_value, 74 + $action_options, 75 + array( 76 + 'name' => 'default', 77 + )); 78 + 79 + 80 + $form = id(new PHUIFormLayoutView()) 81 + ->appendChild( 82 + javelin_tag( 83 + 'input', 84 + array( 85 + 'type' => 'hidden', 86 + 'name' => 'rules', 87 + 'sigil' => 'rules', 88 + ))) 89 + ->appendChild( 90 + id(new AphrontFormInsetView()) 91 + ->setTitle(pht('Rules')) 92 + ->setRightButton( 93 + javelin_tag( 94 + 'a', 95 + array( 96 + 'href' => '#', 97 + 'class' => 'button green', 98 + 'sigil' => 'create-rule', 99 + 'mustcapture' => true 100 + ), 101 + pht('New Rule'))) 102 + ->setDescription( 103 + pht('These rules are processed in order.')) 104 + ->setContent(javelin_tag( 105 + 'table', 106 + array( 107 + 'sigil' => 'rules', 108 + 'class' => 'policy-rules-table' 109 + ), 110 + ''))) 111 + ->appendChild( 112 + id(new AphrontFormMarkupControl()) 113 + ->setLabel(pht('If No Rules Match')) 114 + ->setValue(pht( 115 + "%s all other users.", 116 + $default_select))); 117 + 118 + $form = phutil_tag( 119 + 'div', 120 + array( 121 + 'id' => $root_id, 122 + ), 123 + $form); 124 + 125 + $rule_options = mpull($rules, 'getRuleDescription'); 126 + $type_map = mpull($rules, 'getValueControlType'); 127 + $templates = mpull($rules, 'getValueControlTemplate'); 128 + 129 + require_celerity_resource('policy-edit-css'); 130 + Javelin::initBehavior( 131 + 'policy-rule-editor', 132 + array( 133 + 'rootID' => $root_id, 134 + 'actions' => $action_options, 135 + 'rules' => $rule_options, 136 + 'types' => $type_map, 137 + 'templates' => $templates, 138 + 'data' => $rule_data, 139 + 'defaultRule' => $default_rule, 140 + )); 141 + 142 + $dialog = id(new AphrontDialogView()) 143 + ->setWidth(AphrontDialogView::WIDTH_FULL) 144 + ->setUser($viewer) 145 + ->setTitle(pht('Edit Policy')) 146 + ->appendChild($form) 147 + ->addSubmitButton(pht('Save Policy')) 148 + ->addCancelButton('#'); 149 + 150 + return id(new AphrontDialogResponse())->setDialog($dialog); 151 + } 152 + 153 + }
+37
src/applications/policy/rule/PhabricatorPolicyRule.php
··· 1 + <?php 2 + 3 + abstract class PhabricatorPolicyRule { 4 + 5 + const CONTROL_TYPE_TEXT = 'text'; 6 + const CONTROL_TYPE_SELECT = 'select'; 7 + const CONTROL_TYPE_TOKENIZER = 'tokenizer'; 8 + const CONTROL_TYPE_NONE = 'none'; 9 + 10 + abstract public function getRuleDescription(); 11 + abstract public function applyRule(PhabricatorUser $viewer, $value); 12 + 13 + public function willApplyRules(PhabricatorUser $viewer, array $values) { 14 + return; 15 + } 16 + 17 + public function getValueControlType() { 18 + return self::CONTROL_TYPE_TEXT; 19 + } 20 + 21 + public function getValueControlTemplate() { 22 + return null; 23 + } 24 + 25 + public function getRuleOrder() { 26 + return 500; 27 + } 28 + 29 + public function getValueForStorage($value) { 30 + return $value; 31 + } 32 + 33 + public function getValueForDisplay(PhabricatorUser $viewer, $value) { 34 + return $value; 35 + } 36 + 37 + }
+18
src/applications/policy/rule/PhabricatorPolicyRuleAdministrators.php
··· 1 + <?php 2 + 3 + final class PhabricatorPolicyRuleAdministrators 4 + extends PhabricatorPolicyRule { 5 + 6 + public function getRuleDescription() { 7 + return pht('administrators'); 8 + } 9 + 10 + public function applyRule(PhabricatorUser $viewer, $value) { 11 + return $viewer->getIsAdmin(); 12 + } 13 + 14 + public function getValueControlType() { 15 + return self::CONTROL_TYPE_NONE; 16 + } 17 + 18 + }
+51
src/applications/policy/rule/PhabricatorPolicyRuleLunarPhase.php
··· 1 + <?php 2 + 3 + final class PhabricatorPolicyRuleLunarPhase 4 + extends PhabricatorPolicyRule { 5 + 6 + const PHASE_FULL = 'full'; 7 + const PHASE_NEW = 'new'; 8 + const PHASE_WAXING = 'waxing'; 9 + const PHASE_WANING = 'waning'; 10 + 11 + public function getRuleDescription() { 12 + return pht('when the moon'); 13 + } 14 + 15 + public function applyRule(PhabricatorUser $viewer, $value) { 16 + $moon = new PhutilLunarPhase(PhabricatorTime::getNow()); 17 + 18 + switch ($value) { 19 + case 'full': 20 + return $moon->isFull(); 21 + case 'new': 22 + return $moon->isNew(); 23 + case 'waxing': 24 + return $moon->isWaxing(); 25 + case 'waning': 26 + return $moon->isWaning(); 27 + } 28 + 29 + return false; 30 + } 31 + 32 + public function getValueControlType() { 33 + return self::CONTROL_TYPE_SELECT; 34 + } 35 + 36 + public function getValueControlTemplate() { 37 + return array( 38 + 'options' => array( 39 + self::PHASE_FULL => pht('is full'), 40 + self::PHASE_NEW => pht('is new'), 41 + self::PHASE_WAXING => pht('is waxing'), 42 + self::PHASE_WANING => pht('is waning'), 43 + ), 44 + ); 45 + } 46 + 47 + public function getRuleOrder() { 48 + return 1000; 49 + } 50 + 51 + }
+67
src/applications/policy/rule/PhabricatorPolicyRuleProjects.php
··· 1 + <?php 2 + 3 + final class PhabricatorPolicyRuleProjects 4 + extends PhabricatorPolicyRule { 5 + 6 + private $memberships = array(); 7 + 8 + public function getRuleDescription() { 9 + return pht('members of projects'); 10 + } 11 + 12 + public function willApplyRules(PhabricatorUser $viewer, array $values) { 13 + $values = array_unique(array_filter($values)); 14 + if (!$values) { 15 + return; 16 + } 17 + 18 + $projects = id(new PhabricatorProjectQuery()) 19 + ->setViewer(PhabricatorUser::getOmnipotentUser()) 20 + ->withMemberPHIDs(array($viewer->getPHID())) 21 + ->withPHIDs($values) 22 + ->execute(); 23 + foreach ($projects as $project) { 24 + $this->memberships[$viewer->getPHID()][$project->getPHID()] = true; 25 + } 26 + } 27 + 28 + public function applyRule(PhabricatorUser $viewer, $value) { 29 + foreach ($value as $project_phid) { 30 + if (isset($this->memberships[$viewer->getPHID()][$project_phid])) { 31 + return true; 32 + } 33 + } 34 + return false; 35 + } 36 + 37 + public function getValueControlType() { 38 + return self::CONTROL_TYPE_TOKENIZER; 39 + } 40 + 41 + public function getValueControlTemplate() { 42 + return array( 43 + 'markup' => new AphrontTokenizerTemplateView(), 44 + 'uri' => '/typeahead/common/projects/', 45 + 'placeholder' => pht('Type a project name...'), 46 + ); 47 + } 48 + 49 + public function getRuleOrder() { 50 + return 200; 51 + } 52 + 53 + public function getValueForStorage($value) { 54 + PhutilTypeSpec::newFromString('list<string>')->check($value); 55 + return array_values($value); 56 + } 57 + 58 + public function getValueForDisplay(PhabricatorUser $viewer, $value) { 59 + $handles = id(new PhabricatorHandleQuery()) 60 + ->setViewer($viewer) 61 + ->withPHIDs($value) 62 + ->execute(); 63 + 64 + return mpull($handles, 'getFullName', 'getPHID'); 65 + } 66 + 67 + }
+44
src/applications/policy/rule/PhabricatorPolicyRuleUsers.php
··· 1 + <?php 2 + 3 + final class PhabricatorPolicyRuleUsers 4 + extends PhabricatorPolicyRule { 5 + 6 + public function getRuleDescription() { 7 + return pht('users'); 8 + } 9 + 10 + public function applyRule(PhabricatorUser $viewer, $value) { 11 + return isset($value[$viewer->getPHID()]); 12 + } 13 + 14 + public function getValueControlType() { 15 + return self::CONTROL_TYPE_TOKENIZER; 16 + } 17 + 18 + public function getValueControlTemplate() { 19 + return array( 20 + 'markup' => new AphrontTokenizerTemplateView(), 21 + 'uri' => '/typeahead/common/accounts/', 22 + 'placeholder' => pht('Type a user name...'), 23 + ); 24 + } 25 + 26 + public function getRuleOrder() { 27 + return 100; 28 + } 29 + 30 + public function getValueForStorage($value) { 31 + PhutilTypeSpec::newFromString('list<string>')->check($value); 32 + return array_values($value); 33 + } 34 + 35 + public function getValueForDisplay(PhabricatorUser $viewer, $value) { 36 + $handles = id(new PhabricatorHandleQuery()) 37 + ->setViewer($viewer) 38 + ->withPHIDs($value) 39 + ->execute(); 40 + 41 + return mpull($handles, 'getFullName', 'getPHID'); 42 + } 43 + 44 + }
+32
webroot/rsrc/css/application/policy/policy-edit.css
··· 1 + /** 2 + * @provides policy-edit-css 3 + */ 4 + 5 + .policy-rules-table { 6 + width: 100%; 7 + } 8 + 9 + .policy-rules-table td { 10 + padding: 4px; 11 + width: 32px; 12 + vertical-align: middle; 13 + } 14 + 15 + .policy-rules-table td.action-cell { 16 + width: 120px; 17 + } 18 + 19 + .policy-rules-table td.rule-cell { 20 + width: 180px; 21 + } 22 + 23 + .policy-rules-table td.value-cell { 24 + width: auto; 25 + padding-right: 12px; 26 + } 27 + 28 + .policy-rules-table td.action-cell select, 29 + .policy-rules-table td.rule-cell select, 30 + .policy-rules-table td input { 31 + width: 100%; 32 + }
+179
webroot/rsrc/js/application/policy/behavior-policy-rule-editor.js
··· 1 + /** 2 + * @provides javelin-behavior-policy-rule-editor 3 + * @requires javelin-behavior 4 + * multirow-row-manager 5 + * javelin-dom 6 + * javelin-util 7 + * phabricator-prefab 8 + * javelin-tokenizer 9 + * javelin-typeahead 10 + * javelin-typeahead-preloaded-source 11 + * javelin-json 12 + */ 13 + JX.behavior('policy-rule-editor', function(config) { 14 + var root = JX.$(config.rootID); 15 + var rows = []; 16 + var data = {}; 17 + 18 + JX.DOM.listen( 19 + root, 20 + 'click', 21 + 'create-rule', 22 + function(e) { 23 + e.kill(); 24 + new_rule(config.defaultRule); 25 + }); 26 + 27 + JX.DOM.listen( 28 + root, 29 + 'change', 30 + 'rule-select', 31 + function(e) { 32 + e.kill(); 33 + 34 + var row = e.getNode(JX.MultirowRowManager.getRowSigil()); 35 + var row_id = rules_manager.getRowID(row); 36 + 37 + data[row_id].rule = data[row_id].ruleNode.value; 38 + data[row_id].value = null; 39 + 40 + redraw(row_id); 41 + }); 42 + 43 + JX.DOM.listen( 44 + JX.DOM.findAbove(root, 'form'), 45 + 'submit', 46 + null, 47 + function(e) { 48 + var rules = JX.DOM.find(e.getNode('tag:form'), 'input', 'rules'); 49 + 50 + var value = []; 51 + for (var ii = 0; ii < rows.length; ii++) { 52 + var row_data = data[rows[ii]]; 53 + 54 + var row_dict = { 55 + action: row_data.actionNode.value, 56 + rule: row_data.rule, 57 + value: row_data.getValue() 58 + }; 59 + 60 + value.push(row_dict); 61 + } 62 + 63 + rules.value = JX.JSON.stringify(value); 64 + }); 65 + 66 + 67 + var rules_table = JX.DOM.find(root, 'table', 'rules'); 68 + var rules_manager = new JX.MultirowRowManager(rules_table); 69 + rules_manager.listen( 70 + 'row-removed', 71 + function(row_id) { 72 + delete data[row_id]; 73 + for (var ii = 0; ii < rows.length; ii++) { 74 + if (rows[ii] == row_id) { 75 + rows.splice(ii, 1); 76 + break; 77 + } 78 + } 79 + }); 80 + 81 + 82 + function new_rule(spec) { 83 + var row = rules_manager.addRow([]); 84 + var row_id = rules_manager.getRowID(row); 85 + 86 + rows.push(row_id); 87 + data[row_id] = JX.copy({}, spec); 88 + 89 + redraw(row_id); 90 + } 91 + 92 + function redraw(row_id) { 93 + var action_content = JX.Prefab.renderSelect( 94 + config.actions, 95 + data[row_id].action); 96 + data[row_id].actionNode = action_content; 97 + var action_cell = JX.$N('td', {className: "action-cell"}, action_content); 98 + 99 + var rule_content = JX.Prefab.renderSelect( 100 + config.rules, 101 + data[row_id].rule, 102 + {sigil: 'rule-select'}); 103 + data[row_id].ruleNode = rule_content; 104 + var rule_cell = JX.$N('td', {className: "rule-cell"}, rule_content); 105 + 106 + var input = render_input(data[row_id].rule, null); 107 + 108 + var value_content = input.node; 109 + data[row_id].getValue = input.get; 110 + input.set(data[row_id].value); 111 + 112 + var value_cell = JX.$N('td', {className: "value-cell"}, value_content); 113 + 114 + rules_manager.updateRow(row_id, [action_cell, rule_cell, value_cell]); 115 + } 116 + 117 + function render_input(rule, value) { 118 + var node, get_fn, set_fn; 119 + var type = config.types[rule]; 120 + var template = config.templates[rule]; 121 + 122 + switch (type) { 123 + case 'tokenizer': 124 + node = JX.$H(template.markup).getNode(); 125 + node.id = ''; 126 + 127 + var datasource = new JX.TypeaheadPreloadedSource(template.uri); 128 + 129 + var typeahead = new JX.Typeahead(node); 130 + typeahead.setDatasource(datasource); 131 + 132 + var tokenizer = new JX.Tokenizer(node); 133 + tokenizer.setLimit(template.limit); 134 + tokenizer.setTypeahead(typeahead); 135 + tokenizer.setPlaceholder(template.placeholder); 136 + tokenizer.start(); 137 + 138 + get_fn = function() { return JX.keys(tokenizer.getTokens()); }; 139 + set_fn = function(map) { 140 + if (!map) { 141 + return; 142 + } 143 + for (var k in map) { 144 + tokenizer.addToken(k, map[k]); 145 + } 146 + }; 147 + break; 148 + case 'none': 149 + node = null; 150 + get_fn = JX.bag; 151 + set_fn = JX.bag; 152 + break; 153 + case 'select': 154 + node = JX.Prefab.renderSelect( 155 + config.templates[rule].options, 156 + value); 157 + get_fn = function() { return node.value; }; 158 + set_fn = function(v) { node.value = v; }; 159 + break; 160 + default: 161 + case 'text': 162 + node = JX.$N('input', {type: 'text'}); 163 + get_fn = function() { return node.value; }; 164 + set_fn = function(v) { node.value = v; }; 165 + break; 166 + } 167 + 168 + return { 169 + node: node, 170 + get: get_fn, 171 + set: set_fn 172 + }; 173 + } 174 + 175 + for (var ii = 0; ii < config.data.length; ii++) { 176 + new_rule(config.data[ii]); 177 + } 178 + 179 + });