1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-26 00:32:42 +01:00

Use a tokenizer, not a gigantic poorly-ordered "<select />", to choose repositories in Owners

Summary: Depends on D19190. Fixes T12590. Ref T13099. Replaces the barely-usable, gigantic, poorly ordered "<select />" control with a tokenizer. Attempts to fix various minor issues.

Test Plan:
  - Edited paths: include/exclude paths, from different repositories, different actual paths.
  - Used "Add New Path" to add rows, got repository selector prepopulated with last value.
  - Used "remove".
  - Used validation typeahead, got reasonable behaviors?

The error behavior if you delete the repository for a path is a little sketchy still, but roughly okay.

Maniphest Tasks: T13099, T12590

Differential Revision: https://secure.phabricator.com/D19191
This commit is contained in:
epriestley 2018-03-07 17:29:06 -08:00
parent b41a0e6ddd
commit a4cc1373d3
8 changed files with 310 additions and 198 deletions

View file

@ -10,7 +10,7 @@ return array(
'conpherence.pkg.css' => 'e68cf1fa',
'conpherence.pkg.js' => '15191c65',
'core.pkg.css' => '2fa91e14',
'core.pkg.js' => 'e4d73c62',
'core.pkg.js' => '32bb68e9',
'darkconsole.pkg.js' => '1f9a31bc',
'differential.pkg.css' => '113e692c',
'differential.pkg.js' => 'f6d809c0',
@ -85,7 +85,7 @@ return array(
'rsrc/css/application/maniphest/task-edit.css' => 'fda62a9b',
'rsrc/css/application/maniphest/task-summary.css' => '11cc5344',
'rsrc/css/application/objectselector/object-selector.css' => '85ee8ce6',
'rsrc/css/application/owners/owners-path-editor.css' => '2f00933b',
'rsrc/css/application/owners/owners-path-editor.css' => '9c136c29',
'rsrc/css/application/paste/paste.css' => '9fcc9773',
'rsrc/css/application/people/people-picture-menu-item.css' => 'a06f7f34',
'rsrc/css/application/people/people-profile.css' => '4df76faf',
@ -268,7 +268,7 @@ return array(
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadCompositeSource.js' => '503e17fd',
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadOnDemandSource.js' => '013ffff9',
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadPreloadedSource.js' => '54f314a0',
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadSource.js' => '0fcf201c',
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadSource.js' => 'ab9e0a82',
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadStaticSource.js' => '6c0e62fa',
'rsrc/favicons/apple-touch-icon-114x114.png' => '12a24178',
'rsrc/favicons/apple-touch-icon-120x120.png' => '0d1543c7',
@ -418,13 +418,13 @@ return array(
'rsrc/js/application/files/behavior-launch-icon-composer.js' => '48086888',
'rsrc/js/application/harbormaster/behavior-harbormaster-log.js' => '191b4909',
'rsrc/js/application/herald/HeraldRuleEditor.js' => 'dca75c0e',
'rsrc/js/application/herald/PathTypeahead.js' => '78039abe',
'rsrc/js/application/herald/PathTypeahead.js' => '662e9cea',
'rsrc/js/application/herald/herald-rule-editor.js' => '7ebaeed3',
'rsrc/js/application/maniphest/behavior-batch-selector.js' => 'ad54037e',
'rsrc/js/application/maniphest/behavior-line-chart.js' => 'e4232876',
'rsrc/js/application/maniphest/behavior-list-edit.js' => 'a9f88de2',
'rsrc/js/application/maniphest/behavior-subpriorityeditor.js' => '71237763',
'rsrc/js/application/owners/OwnersPathEditor.js' => '52b9cbc4',
'rsrc/js/application/owners/OwnersPathEditor.js' => 'c96502cf',
'rsrc/js/application/owners/owners-path-editor.js' => '7a68dda3',
'rsrc/js/application/passphrase/passphrase-credential-control.js' => '3cb0b2fc',
'rsrc/js/application/pholio/behavior-pholio-mock-edit.js' => 'bee502c8',
@ -534,7 +534,7 @@ return array(
'rsrc/js/phuix/PHUIXButtonView.js' => '8a91e1ac',
'rsrc/js/phuix/PHUIXDropdownMenu.js' => '04b2ae03',
'rsrc/js/phuix/PHUIXExample.js' => '68af71ca',
'rsrc/js/phuix/PHUIXFormControl.js' => '16ad6224',
'rsrc/js/phuix/PHUIXFormControl.js' => '210a16c1',
'rsrc/js/phuix/PHUIXIconView.js' => 'bff6884b',
),
'symbols' => array(
@ -744,7 +744,7 @@ return array(
'javelin-typeahead-normalizer' => '185bbd53',
'javelin-typeahead-ondemand-source' => '013ffff9',
'javelin-typeahead-preloaded-source' => '54f314a0',
'javelin-typeahead-source' => '0fcf201c',
'javelin-typeahead-source' => 'ab9e0a82',
'javelin-typeahead-static-source' => '6c0e62fa',
'javelin-uri' => 'c989ade3',
'javelin-util' => '93cc50d6',
@ -764,10 +764,10 @@ return array(
'maniphest-task-edit-css' => 'fda62a9b',
'maniphest-task-summary-css' => '11cc5344',
'multirow-row-manager' => 'b5d57730',
'owners-path-editor' => '52b9cbc4',
'owners-path-editor-css' => '2f00933b',
'owners-path-editor' => 'c96502cf',
'owners-path-editor-css' => '9c136c29',
'paste-css' => '9fcc9773',
'path-typeahead' => '78039abe',
'path-typeahead' => '662e9cea',
'people-picture-menu-item-css' => 'a06f7f34',
'people-profile-css' => '4df76faf',
'phabricator-action-list-view-css' => '0bcd9a45',
@ -888,7 +888,7 @@ return array(
'phuix-autocomplete' => '7fa5c915',
'phuix-button-view' => '8a91e1ac',
'phuix-dropdown-menu' => '04b2ae03',
'phuix-form-control-view' => '16ad6224',
'phuix-form-control-view' => '210a16c1',
'phuix-icon-view' => 'bff6884b',
'policy-css' => '957ea14c',
'policy-edit-css' => '815c66f7',
@ -998,20 +998,10 @@ return array(
'javelin-install',
'javelin-util',
),
'0fcf201c' => array(
'javelin-install',
'javelin-util',
'javelin-dom',
'javelin-typeahead-normalizer',
),
'15d5ff71' => array(
'aphront-typeahead-control-css',
'phui-tag-view-css',
),
'16ad6224' => array(
'javelin-install',
'javelin-dom',
),
'17bb8539' => array(
'javelin-behavior',
'javelin-stratcom',
@ -1061,6 +1051,10 @@ return array(
'javelin-install',
'javelin-dom',
),
'210a16c1' => array(
'javelin-install',
'javelin-dom',
),
'2290aeef' => array(
'javelin-install',
'javelin-dom',
@ -1337,14 +1331,6 @@ return array(
'javelin-vector',
'javelin-typeahead-static-source',
),
'52b9cbc4' => array(
'multirow-row-manager',
'javelin-install',
'path-typeahead',
'javelin-dom',
'javelin-util',
'phabricator-prefab',
),
'54b612ba' => array(
'javelin-color',
'javelin-install',
@ -1439,6 +1425,14 @@ return array(
'javelin-workflow',
'javelin-dom',
),
'662e9cea' => array(
'javelin-install',
'javelin-typeahead',
'javelin-dom',
'javelin-request',
'javelin-typeahead-ondemand-source',
'javelin-util',
),
'66a6def1' => array(
'javelin-behavior',
'javelin-dom',
@ -1542,14 +1536,6 @@ return array(
'javelin-request',
'javelin-util',
),
'78039abe' => array(
'javelin-install',
'javelin-typeahead',
'javelin-dom',
'javelin-request',
'javelin-typeahead-ondemand-source',
'javelin-util',
),
'7927a7d3' => array(
'javelin-behavior',
'javelin-quicksand',
@ -1799,6 +1785,12 @@ return array(
'javelin-util',
'phabricator-busy',
),
'ab9e0a82' => array(
'javelin-install',
'javelin-util',
'javelin-dom',
'javelin-typeahead-normalizer',
),
'acd29eee' => array(
'javelin-behavior',
'javelin-stratcom',
@ -1974,6 +1966,15 @@ return array(
'javelin-install',
'javelin-util',
),
'c96502cf' => array(
'multirow-row-manager',
'javelin-install',
'path-typeahead',
'javelin-dom',
'javelin-util',
'phabricator-prefab',
'phuix-form-control-view',
),
'c989ade3' => array(
'javelin-install',
'javelin-util',

View file

@ -45,19 +45,6 @@ final class DiffusionPathValidateController extends DiffusionController {
'valid' => (bool)$valid,
);
if (!$valid) {
$branch = $drequest->getBranch();
if ($branch) {
$message = pht('Not found in %s', $branch);
} else {
$message = pht('Not found at %s', 'HEAD');
}
} else {
$message = pht('OK');
}
$output['message'] = $message;
return id(new AphrontAjaxResponse())->setContent($output);
}
}

View file

@ -27,7 +27,7 @@ final class PhabricatorOwnersPathsController
$path_refs = array();
foreach ($paths as $key => $path) {
if (!isset($repos[$key])) {
if (!isset($repos[$key]) || !strlen($repos[$key])) {
throw new Exception(
pht(
'No repository PHID for path "%s"!',
@ -70,17 +70,39 @@ final class PhabricatorOwnersPathsController
$path_refs = mpull($paths, 'getRef');
}
$repos = id(new PhabricatorRepositoryQuery())
->setViewer($viewer)
->execute();
$template = new AphrontTokenizerTemplateView();
$repo_map = array();
foreach ($repos as $key => $repo) {
$monogram = $repo->getMonogram();
$name = $repo->getName();
$repo_map[$repo->getPHID()] = "{$monogram} {$name}";
$datasource = id(new DiffusionRepositoryDatasource())
->setViewer($viewer);
$tokenizer_spec = array(
'markup' => $template->render(),
'config' => array(
'src' => $datasource->getDatasourceURI(),
'browseURI' => $datasource->getBrowseURI(),
'placeholder' => $datasource->getPlaceholderText(),
'limit' => 1,
),
);
foreach ($path_refs as $key => $path_ref) {
$path_refs[$key]['repositoryValue'] = $datasource->getWireTokens(
array(
$path_ref['repositoryPHID'],
));
}
asort($repos);
$icon_test = id(new PHUIIconView())
->setIcon('fa-spinner grey')
->setTooltip(pht('Validating...'));
$icon_okay = id(new PHUIIconView())
->setIcon('fa-check-circle green')
->setTooltip(pht('Path Exists in Repository'));
$icon_fail = id(new PHUIIconView())
->setIcon('fa-question-circle-o red')
->setTooltip(pht('Path Not Found On Default Branch'));
$template = new AphrontTypeaheadTemplateView();
$template = $template->render();
@ -88,15 +110,23 @@ final class PhabricatorOwnersPathsController
Javelin::initBehavior(
'owners-path-editor',
array(
'root' => 'path-editor',
'table' => 'paths',
'add_button' => 'addpath',
'repositories' => $repo_map,
'input_template' => $template,
'pathRefs' => $path_refs,
'completeURI' => '/diffusion/services/path/complete/',
'validateURI' => '/diffusion/services/path/validate/',
'root' => 'path-editor',
'table' => 'paths',
'add_button' => 'addpath',
'input_template' => $template,
'pathRefs' => $path_refs,
'completeURI' => '/diffusion/services/path/complete/',
'validateURI' => '/diffusion/services/path/validate/',
'repositoryTokenizerSpec' => $tokenizer_spec,
'icons' => array(
'test' => hsprintf('%s', $icon_test),
'okay' => hsprintf('%s', $icon_okay),
'fail' => hsprintf('%s', $icon_fail),
),
'modeOptions' => array(
0 => pht('Include'),
1 => pht('Exclude'),
),
));
require_celerity_resource('owners-path-editor-css');

View file

@ -3,7 +3,8 @@
*/
.owners-path-editor-table {
margin: 10px;
margin: 10px 0;
width: 100%;
}
.owners-path-editor-table td {
@ -11,27 +12,38 @@
vertical-align: middle;
}
.owners-path-editor-table select.owners-repo {
width: 150px;
.owners-path-editor-table td.owners-path-mode-control {
width: 180px;
}
.owners-path-editor-table input {
width: 400px;
.owners-path-editor-table td.owners-path-mode-control select {
width: 100%;
}
.owners-path-editor-table div.error-display {
padding: 4px 12px 0;
.owners-path-editor-table td.owners-path-repo-control {
width: 280px;
}
.owners-path-editor-table div.validating {
color: {$greytext};
.owners-path-editor-table td.owners-path-path-control {
width: auto;
}
.owners-path-editor-table div.invalid {
color: #aa0000;
.owners-path-editor-table td.owners-path-path-control input {
width: 100%;
}
.owners-path-editor-table div.valid {
color: #00aa00;
font-weight: bold;
.owners-path-editor-table td.owners-path-path-control .jx-typeahead-results a {
padding: 4px;
}
.owners-path-editor-table td.owners-path-icon-control {
width: 18px;
}
.owners-path-editor-table td.remove-column {
width: 100px;
}
.owners-path-editor-table td.remove-column a {
display: block;
}

View file

@ -9,8 +9,7 @@
JX.install('TypeaheadSource', {
construct : function() {
this._raw = {};
this._lookup = {};
this.resetResults();
this.setNormalizer(JX.TypeaheadNormalizer.normalize);
this._excludeIDs = {};
},
@ -359,6 +358,12 @@ JX.install('TypeaheadSource', {
}
return str.split(/\s+/g);
},
resetResults: function() {
this._raw = {};
this._lookup = {};
},
_defaultTransformer : function(object) {
return {
name : object[0],

View file

@ -11,7 +11,7 @@
JX.install('PathTypeahead', {
construct : function(config) {
this._repositorySelect = config.repo_select;
this._repositoryTokenizer = config.repositoryTokenizer;
this._hardpoint = config.hardpoint;
this._input = config.path_input;
this._completeURI = config.completeURI;
@ -19,14 +19,14 @@ JX.install('PathTypeahead', {
this._errorDisplay = config.error_display;
this._textInputValues = {};
this._icons = config.icons;
this._initializeDatasource();
this._initializeTypeahead(this._input);
},
members : {
/*
* DOM <select> elem for choosing the repository of a path.
*/
_repositorySelect : null,
_repositoryTokenizer : null,
/*
* DOM parent div "hardpoint" to be passed to the JX.Typeahead.
*/
@ -79,31 +79,28 @@ JX.install('PathTypeahead', {
*/
start : function() {
if (this._typeahead.getValue()) {
this._textInputValues[this._repositorySelect.value] =
this._typeahead.getValue();
var phid = this._getRepositoryPHID();
if (phid) {
this._textInputValues[phid] = this._typeahead.getValue();
}
}
this._typeahead.listen(
'change',
JX.bind(this, function(value) {
this._textInputValues[this._repositorySelect.value] = value;
this._validate();
}));
var phid = this._getRepositoryPHID();
if (phid) {
this._textInputValues[phid] = value;
}
this._typeahead.listen(
'choose',
JX.bind(this, function() {
setTimeout(JX.bind(this._typeahead, this._typeahead.refresh), 0);
this._validate();
}));
var repo_set_input = JX.bind(this, this._onrepochange);
this._typeahead.listen('start', repo_set_input);
JX.DOM.listen(
this._repositorySelect,
'change',
null,
repo_set_input);
this._repositoryTokenizer.listen('change', repo_set_input);
this._typeahead.start();
this._validate();
@ -115,13 +112,18 @@ JX.install('PathTypeahead', {
this._textInputValues);
this._datasource.setAuxiliaryData(
{repositoryPHID : this._repositorySelect.value}
);
{
repositoryPHID: this._getRepositoryPHID()
});
// Since we've changed the repository, reset the results.
this._datasource.resetResults();
},
_setPathInputBasedOnRepository : function(typeahead, lookup) {
if (lookup[this._repositorySelect.value]) {
typeahead.setValue(lookup[this._repositorySelect.value]);
var phid = this._getRepositoryPHID();
if (phid && lookup[phid]) {
typeahead.setValue(lookup[phid]);
} else {
typeahead.setValue('/');
}
@ -147,9 +149,24 @@ JX.install('PathTypeahead', {
return ('' + str).replace(/[\/]+/g, '\/');
},
_getRepositoryPHID: function() {
var tokens = this._repositoryTokenizer.getTokens();
var keys = JX.keys(tokens);
if (keys.length) {
return keys[0];
}
return null;
},
_validate : function() {
var repo_phid = this._getRepositoryPHID();
if (!repo_phid) {
return;
}
var input = this._input;
var repo_id = this._repositorySelect.value;
var input_value = input.value;
var error_display = this._errorDisplay;
@ -165,33 +182,32 @@ JX.install('PathTypeahead', {
var validation_request = new JX.Request(
this._validateURI,
function(payload) {
JX.bind(this, function(payload) {
// Don't change validation display state if the input has been
// changed since we started validation
if (input.value === input_value) {
if (payload.valid) {
JX.DOM.alterClass(error_display, 'invalid', false);
JX.DOM.alterClass(error_display, 'valid', true);
} else {
JX.DOM.alterClass(error_display, 'invalid', true);
JX.DOM.alterClass(error_display, 'valid', false);
}
JX.DOM.setContent(error_display, payload.message);
if (input.value !== input_value) {
return;
}
});
if (payload.valid) {
JX.DOM.setContent(error_display, JX.$H(this._icons.okay));
} else {
JX.DOM.setContent(error_display, JX.$H(this._icons.fail));
}
}));
validation_request.listen('finally', function() {
JX.DOM.alterClass(error_display, 'validating', false);
this._validationInflight = null;
});
validation_request.setData(
{
repositoryPHID : repo_id,
repositoryPHID : repo_phid,
path : input_value
});
this._validationInflight = validation_request;
JX.DOM.setContent(error_display, JX.$H(this._icons.test));
validation_request.setTimeout(750);
validation_request.send();

View file

@ -5,6 +5,7 @@
* javelin-dom
* javelin-util
* phabricator-prefab
* phuix-form-control-view
* @provides owners-path-editor
* @javelin
*/
@ -23,11 +24,13 @@ JX.install('OwnersPathEditor', {
JX.bind(this, this._onaddpath));
this._count = 0;
this._repositories = config.repositories;
this._inputTemplate = config.input_template;
this._repositoryTokenizerSpec = config.repositoryTokenizerSpec;
this._completeURI = config.completeURI;
this._validateURI = config.validateURI;
this._icons = config.icons;
this._modeOptions = config.modeOptions;
this._initializePaths(config.pathRefs);
},
@ -37,12 +40,6 @@ JX.install('OwnersPathEditor', {
*/
_rowManager : null,
/*
* Array of objects with 'name' and 'repo_id' keys for
* selecting the repository of a path.
*/
_repositories : null,
/*
* How many rows have been created, for form name generation.
*/
@ -65,6 +62,8 @@ JX.install('OwnersPathEditor', {
* default for future rows.
*/
_lastRepositoryChoice : null,
_icons: null,
_modeOptions: null,
/*
* Initialize with 0 or more rows.
@ -85,68 +84,34 @@ JX.install('OwnersPathEditor', {
addPath : function(path_ref) {
// Smart default repository. See _lastRepositoryChoice.
if (path_ref) {
this._lastRepositoryChoice = path_ref.repositoryPHID;
this._lastRepositoryChoice = path_ref.repositoryValue;
} else {
path_ref = {
repositoryValue: this._lastRepositoryChoice || {}
};
}
path_ref = path_ref || {};
var selected_repository = path_ref.repositoryPHID ||
this._lastRepositoryChoice;
var options = this._buildRepositoryOptions(selected_repository);
var attrs = {
name : 'repo[' + this._count + ']',
className : 'owners-repo'
};
var repo_select = JX.$N('select', attrs, options);
JX.DOM.listen(repo_select, 'change', null, JX.bind(this, function() {
this._lastRepositoryChoice = repo_select.value;
}));
var repo_cell = JX.$N('td', {}, repo_select);
var typeahead_cell = JX.$N(
'td',
JX.$H(this._inputTemplate));
// Text input for path.
var path_input = JX.DOM.find(typeahead_cell, 'input');
JX.copy(
path_input,
{
value : path_ref.display || '',
name : 'path[' + this._count + ']'
});
// The Typeahead requires a display div called hardpoint.
var hardpoint = JX.DOM.find(
typeahead_cell,
'div',
'typeahead-hardpoint');
var error_display = JX.$N(
'div',
{
className : 'error-display validating'
},
'Validating...');
var error_display_cell = JX.$N('td', {}, error_display);
var exclude = JX.Prefab.renderSelect(
{'0' : 'Include', '1' : 'Exclude'},
path_ref.excluded,
{name : 'exclude[' + this._count + ']'});
var exclude_cell = JX.$N('td', {}, exclude);
var repo = this._newRepoCell(path_ref.repositoryValue);
var path = this._newPathCell(path_ref.display);
var icon = this._newIconCell();
var mode_cell = this._newModeCell(path_ref.excluded);
var row = this._rowManager.addRow(
[exclude_cell, repo_cell, typeahead_cell, error_display_cell]);
[
mode_cell,
repo.cell,
path.cell,
icon.cell
]);
new JX.PathTypeahead({
repo_select : repo_select,
path_input : path_input,
hardpoint : hardpoint,
error_display : error_display,
repositoryTokenizer: repo.tokenizer,
path_input : path.input,
hardpoint : path.hardpoint,
error_display : icon.cell,
completeURI : this._completeURI,
validateURI : this._validateURI
validateURI : this._validateURI,
icons: this._icons
}).start();
this._count++;
@ -158,20 +123,109 @@ JX.install('OwnersPathEditor', {
this.addPath();
},
/**
* Helper to build the options for the repository choice dropdown.
*/
_buildRepositoryOptions : function(selected) {
var repos = this._repositories;
var result = [];
for (var k in repos) {
var attr = {
value : k,
selected : (selected == k)
};
result.push(JX.$N('option', attr, repos[k]));
_newModeCell: function(value) {
var options = this._modeOptions;
var name = 'exclude[' + this._count + ']';
var control = JX.Prefab.renderSelect(options, value, {name: name});
return JX.$N(
'td',
{
className: 'owners-path-mode-control'
},
control);
},
_newRepoCell: function(value) {
var repo_control = new JX.PHUIXFormControl()
.setControl('tokenizer', this._repositoryTokenizerSpec)
.setValue(value);
var repo_tokenizer = repo_control.getTokenizer();
var name = 'repo[' + this._count + ']';
function get_phid() {
var phids = repo_control.getValue();
if (!phids.length) {
return null;
}
return phids[0];
}
return result;
var input = JX.$N(
'input',
{
type: 'hidden',
name: name,
value: get_phid()
});
repo_tokenizer.listen('change', JX.bind(this, function() {
this._lastRepositoryChoice = repo_tokenizer.getTokens();
input.value = get_phid();
}));
var cell = JX.$N(
'td',
{
className: 'owners-path-repo-control'
},
[
repo_control.getRawInputNode(),
input
]);
return {
cell: cell,
tokenizer: repo_tokenizer
};
},
_newPathCell: function(value) {
var path_cell = JX.$N(
'td',
{
className: 'owners-path-path-control'
},
JX.$H(this._inputTemplate));
var path_input = JX.DOM.find(path_cell, 'input');
JX.copy(
path_input,
{
value: value || '',
name: 'path[' + this._count + ']'
});
var hardpoint = JX.DOM.find(
path_cell,
'div',
'typeahead-hardpoint');
return {
cell: path_cell,
input: path_input,
hardpoint: hardpoint
};
},
_newIconCell: function() {
var cell = JX.$N(
'td',
{
className: 'owners-path-icon-control'
});
return {
cell: cell
};
}
}
});

View file

@ -15,6 +15,7 @@ JX.install('PHUIXFormControl', {
_valueSetCallback: null,
_valueGetCallback: null,
_rawInputNode: null,
_tokenizer: null,
setLabel: function(label) {
JX.DOM.setContent(this._getLabelNode(), label);
@ -70,6 +71,7 @@ JX.install('PHUIXFormControl', {
this._valueGetCallback = input.get;
this._valueSetCallback = input.set;
this._rawInputNode = input.node;
this._tokenizer = input.tokenizer || null;
return this;
},
@ -87,6 +89,10 @@ JX.install('PHUIXFormControl', {
return this._rawInputNode;
},
getTokenizer: function() {
return this._tokenizer;
},
getNode: function() {
if (!this._node) {
@ -168,7 +174,8 @@ JX.install('PHUIXFormControl', {
return {
node: build.node,
get: get_value,
set: set_value
set: set_value,
tokenizer: build.tokenizer
};
},