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

Define bulk edits in terms of EditEngine, not hard-coded ad-hoc definitions

Summary:
Depends on D18862. See PHI173. Ref T13025. Fixes T10005. This redefines bulk edits in terms of EditEngine fields, rather than hard-coding the whole thing.

Only text fields -- and, specifically, only the "Title" field -- are supported after this change. Followup changes will add more bulk edit parameter types and broader field support.

However, the title field now works without any Maniphest-specific code, outside of the small amount of binding code in the `ManiphestBulkEditor` subclass.

Test Plan: Used the bulk edit workflow to change the titles of tasks.

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T13025, T10005

Differential Revision: https://secure.phabricator.com/D18863
This commit is contained in:
epriestley 2018-01-10 11:23:55 -08:00
parent 6ef45d8245
commit 09e71a4082
17 changed files with 455 additions and 288 deletions

View file

@ -81,7 +81,6 @@ return array(
'rsrc/css/application/harbormaster/harbormaster.css' => 'f491c9f4',
'rsrc/css/application/herald/herald-test.css' => 'a52e323e',
'rsrc/css/application/herald/herald.css' => 'cd8d0134',
'rsrc/css/application/maniphest/batch-editor.css' => 'b0f0b6d5',
'rsrc/css/application/maniphest/report.css' => '9b9580b7',
'rsrc/css/application/maniphest/task-edit.css' => 'fda62a9b',
'rsrc/css/application/maniphest/task-summary.css' => '11cc5344',
@ -143,6 +142,7 @@ return array(
'rsrc/css/phui/phui-basic-nav-view.css' => '98c11ab3',
'rsrc/css/phui/phui-big-info-view.css' => 'acc3492c',
'rsrc/css/phui/phui-box.css' => '4bd6cdb9',
'rsrc/css/phui/phui-bulk-editor.css' => '1fe728a8',
'rsrc/css/phui/phui-chart.css' => '6bf6f78e',
'rsrc/css/phui/phui-cms.css' => '504b4b23',
'rsrc/css/phui/phui-comment-form.css' => 'ac68149f',
@ -419,7 +419,6 @@ return array(
'rsrc/js/application/herald/HeraldRuleEditor.js' => '2dff5579',
'rsrc/js/application/herald/PathTypeahead.js' => 'f7fc67ec',
'rsrc/js/application/herald/herald-rule-editor.js' => '7ebaeed3',
'rsrc/js/application/maniphest/behavior-batch-editor.js' => '782ab6e7',
'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',
@ -477,6 +476,7 @@ return array(
'rsrc/js/core/behavior-audio-source.js' => '59b251eb',
'rsrc/js/core/behavior-autofocus.js' => '7319e029',
'rsrc/js/core/behavior-badge-view.js' => '8ff5e24c',
'rsrc/js/core/behavior-bulk-editor.js' => '5e178556',
'rsrc/js/core/behavior-choose-control.js' => '327a00d1',
'rsrc/js/core/behavior-copy.js' => 'b0b8f86d',
'rsrc/js/core/behavior-detect-timezone.js' => '4c193c96',
@ -532,7 +532,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' => '83e03671',
'rsrc/js/phuix/PHUIXFormControl.js' => '68bb05aa',
'rsrc/js/phuix/PHUIXIconView.js' => 'bff6884b',
),
'symbols' => array(
@ -595,6 +595,7 @@ return array(
'javelin-behavior-audio-source' => '59b251eb',
'javelin-behavior-audit-preview' => 'd835b03a',
'javelin-behavior-badge-view' => '8ff5e24c',
'javelin-behavior-bulk-editor' => '5e178556',
'javelin-behavior-bulk-job-reload' => 'edf8a145',
'javelin-behavior-calendar-month-view' => 'fe33e256',
'javelin-behavior-choose-control' => '327a00d1',
@ -642,7 +643,6 @@ return array(
'javelin-behavior-lightbox-attachments' => '560f41da',
'javelin-behavior-line-chart' => 'e4232876',
'javelin-behavior-load-blame' => '42126667',
'javelin-behavior-maniphest-batch-editor' => '782ab6e7',
'javelin-behavior-maniphest-batch-selector' => 'ad54037e',
'javelin-behavior-maniphest-list-editor' => 'a9f88de2',
'javelin-behavior-maniphest-subpriority-editor' => '71237763',
@ -756,7 +756,6 @@ return array(
'javelin-workboard-column' => '758b4758',
'javelin-workboard-controller' => '26167537',
'javelin-workflow' => '1e911d0f',
'maniphest-batch-editor' => 'b0f0b6d5',
'maniphest-report-css' => '9b9580b7',
'maniphest-task-edit-css' => 'fda62a9b',
'maniphest-task-summary-css' => '11cc5344',
@ -823,6 +822,7 @@ return array(
'phui-basic-nav-view-css' => '98c11ab3',
'phui-big-info-view-css' => 'acc3492c',
'phui-box-css' => '4bd6cdb9',
'phui-bulk-editor-css' => '1fe728a8',
'phui-button-bar-css' => 'f1ff5494',
'phui-button-css' => '1863cc6e',
'phui-button-simple-css' => '8e1baf68',
@ -884,7 +884,7 @@ return array(
'phuix-autocomplete' => 'e0731603',
'phuix-button-view' => '8a91e1ac',
'phuix-dropdown-menu' => '04b2ae03',
'phuix-form-control-view' => '83e03671',
'phuix-form-control-view' => '68bb05aa',
'phuix-icon-view' => 'bff6884b',
'policy-css' => '957ea14c',
'policy-edit-css' => '815c66f7',
@ -1387,6 +1387,15 @@ return array(
'javelin-stratcom',
'javelin-dom',
),
'5e178556' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'phabricator-prefab',
'multirow-row-manager',
'javelin-json',
'phuix-form-control-view',
),
'5e2634b9' => array(
'javelin-behavior',
'javelin-aphlict',
@ -1436,6 +1445,10 @@ return array(
'javelin-dom',
'phuix-button-view',
),
'68bb05aa' => array(
'javelin-install',
'javelin-dom',
),
'69adf288' => array(
'javelin-install',
),
@ -1524,14 +1537,6 @@ return array(
'javelin-request',
'javelin-util',
),
'782ab6e7' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'phabricator-prefab',
'multirow-row-manager',
'javelin-json',
),
'7927a7d3' => array(
'javelin-behavior',
'javelin-quicksand',
@ -1570,10 +1575,6 @@ return array(
'javelin-behavior',
'javelin-scrollbar',
),
'83e03671' => array(
'javelin-install',
'javelin-dom',
),
'8499b6ab' => array(
'javelin-behavior',
'javelin-dom',

View file

@ -222,6 +222,8 @@ phutil_register_library_map(array(
'AuditConduitAPIMethod' => 'applications/audit/conduit/AuditConduitAPIMethod.php',
'AuditQueryConduitAPIMethod' => 'applications/audit/conduit/AuditQueryConduitAPIMethod.php',
'AuthManageProvidersCapability' => 'applications/auth/capability/AuthManageProvidersCapability.php',
'BulkParameterType' => 'applications/transactions/bulk/type/BulkParameterType.php',
'BulkStringParameterType' => 'applications/transactions/bulk/type/BulkStringParameterType.php',
'CalendarTimeUtil' => 'applications/calendar/util/CalendarTimeUtil.php',
'CalendarTimeUtilTestCase' => 'applications/calendar/__tests__/CalendarTimeUtilTestCase.php',
'CelerityAPI' => 'applications/celerity/CelerityAPI.php',
@ -5242,6 +5244,8 @@ phutil_register_library_map(array(
'AuditConduitAPIMethod' => 'ConduitAPIMethod',
'AuditQueryConduitAPIMethod' => 'AuditConduitAPIMethod',
'AuthManageProvidersCapability' => 'PhabricatorPolicyCapability',
'BulkParameterType' => 'Phobject',
'BulkStringParameterType' => 'BulkParameterType',
'CalendarTimeUtil' => 'Phobject',
'CalendarTimeUtilTestCase' => 'PhabricatorTestCase',
'CelerityAPI' => 'Phobject',

View file

@ -18,6 +18,10 @@ final class ManiphestTaskBulkEngine
return new ManiphestTaskSearchEngine();
}
public function newEditEngine() {
return new ManiphestEditEngine();
}
public function getDoneURI() {
$board_uri = $this->getBoardURI();
if ($board_uri) {

View file

@ -178,6 +178,7 @@ EODOCS
id(new PhabricatorTextEditField())
->setKey('title')
->setLabel(pht('Title'))
->setBulkEditLabel(pht('Set title to'))
->setDescription(pht('Name of the task.'))
->setConduitDescription(pht('Rename the task.'))
->setConduitTypeDescription(pht('New task name.'))

View file

@ -10,7 +10,10 @@ abstract class PhabricatorBulkEngine extends Phobject {
private $editableList;
private $targetList;
private $rootFormID;
abstract public function newSearchEngine();
abstract public function newEditEngine();
public function getCancelURI() {
$saved_query = $this->savedQuery;
@ -118,7 +121,7 @@ abstract class PhabricatorBulkEngine extends Phobject {
array(
'action' => $this->getBulkURI(),
'method' => 'POST',
'id' => 'maniphest-batch-edit-form',
'id' => $this->getRootFormID(),
),
array(
$this->newContextInputs(),
@ -290,95 +293,60 @@ abstract class PhabricatorBulkEngine extends Phobject {
private function newBulkActionForm() {
$viewer = $this->getViewer();
$input_id = celerity_generate_unique_node_id();
$edit_engine = id($this->newEditEngine())
->setViewer($viewer);
$edit_map = $edit_engine->newBulkEditMap();
require_celerity_resource('phui-bulk-editor-css');
Javelin::initBehavior(
'bulk-editor',
array(
'rootNodeID' => $this->getRootFormID(),
'inputNodeID' => $input_id,
'edits' => $edit_map,
));
$cancel_uri = $this->getCancelURI();
$template = new AphrontTokenizerTemplateView();
$template = $template->render();
$projects_source = new PhabricatorProjectDatasource();
$mailable_source = new PhabricatorMetaMTAMailableDatasource();
$mailable_source->setViewer($viewer);
$owner_source = new ManiphestAssigneeDatasource();
$owner_source->setViewer($viewer);
$spaces_source = id(new PhabricatorSpacesNamespaceDatasource())
->setViewer($viewer);
require_celerity_resource('maniphest-batch-editor');
Javelin::initBehavior(
'maniphest-batch-editor',
array(
'root' => 'maniphest-batch-edit-form',
'tokenizerTemplate' => $template,
'sources' => array(
'project' => array(
'src' => $projects_source->getDatasourceURI(),
'placeholder' => $projects_source->getPlaceholderText(),
'browseURI' => $projects_source->getBrowseURI(),
),
'owner' => array(
'src' => $owner_source->getDatasourceURI(),
'placeholder' => $owner_source->getPlaceholderText(),
'browseURI' => $owner_source->getBrowseURI(),
'limit' => 1,
),
'cc' => array(
'src' => $mailable_source->getDatasourceURI(),
'placeholder' => $mailable_source->getPlaceholderText(),
'browseURI' => $mailable_source->getBrowseURI(),
),
'spaces' => array(
'src' => $spaces_source->getDatasourceURI(),
'placeholder' => $spaces_source->getPlaceholderText(),
'browseURI' => $spaces_source->getBrowseURI(),
'limit' => 1,
),
),
'input' => 'batch-form-actions',
'priorityMap' => ManiphestTaskPriority::getTaskPriorityMap(),
'statusMap' => ManiphestTaskStatus::getTaskStatusMap(),
));
$form = id(new PHUIFormLayoutView())
->setUser($viewer);
$form->appendChild(
phutil_tag(
'input',
array(
'type' => 'hidden',
'name' => 'actions',
'id' => 'batch-form-actions',
)));
$form->appendChild(
id(new PHUIFormInsetView())
->setTitle(pht('Bulk Edit Actions'))
->setRightButton(
javelin_tag(
'a',
array(
'href' => '#',
'class' => 'button button-green',
'sigil' => 'add-action',
'mustcapture' => true,
),
pht('Add Another Action')))
->setContent(
javelin_tag(
'table',
array(
'sigil' => 'maniphest-batch-actions',
'class' => 'maniphest-batch-actions-table',
),
'')))
return id(new PHUIFormLayoutView())
->setViewer($viewer)
->appendChild(
phutil_tag(
'input',
array(
'type' => 'hidden',
'name' => 'xactions',
'id' => $input_id,
)))
->appendChild(
id(new PHUIFormInsetView())
->setTitle(pht('Bulk Edit Actions'))
->setRightButton(
javelin_tag(
'a',
array(
'href' => '#',
'class' => 'button button-green',
'sigil' => 'add-action',
'mustcapture' => true,
),
pht('Add Another Action')))
->setContent(
javelin_tag(
'table',
array(
'sigil' => 'bulk-actions',
'class' => 'bulk-edit-table',
),
'')))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Apply Bulk Edit'))
->addCancelButton($cancel_uri));
return $form;
}
private function buildEditResponse() {
@ -405,31 +373,33 @@ abstract class PhabricatorBulkEngine extends Phobject {
'You have not selected any objects to edit.'));
}
$raw_actions = $request->getStr('actions');
if ($raw_actions) {
$actions = phutil_json_decode($raw_actions);
$raw_xactions = $request->getStr('xactions');
if ($raw_xactions) {
$raw_xactions = phutil_json_decode($raw_xactions);
} else {
$actions = array();
$raw_xactions = array();
}
if (!$actions) {
if (!$raw_xactions) {
throw new Exception(
pht(
'You have not chosen any edits to apply.'));
}
$edit_engine = id($this->newEditEngine())
->setViewer($viewer);
$xactions = $edit_engine->newRawBulkTransactions($raw_xactions);
$cancel_uri = $this->getCancelURI();
$done_uri = $this->getDoneURI();
$job = PhabricatorWorkerBulkJob::initializeNewJob(
$viewer,
// TODO: This is a Maniphest-specific job type for now, but will become
// a generic one so it gets to live here for now instead of in the task
// specific BulkEngine subclass.
new ManiphestTaskEditBulkJobType(),
new PhabricatorEditEngineBulkJobType(),
array(
'taskPHIDs' => mpull($objects, 'getPHID'),
'actions' => $actions,
'objectPHIDs' => mpull($objects, 'getPHID'),
'xactions' => $xactions,
'cancelURI' => $cancel_uri,
'doneURI' => $done_uri,
));
@ -451,4 +421,12 @@ abstract class PhabricatorBulkEngine extends Phobject {
->setURI($job->getMonitorURI());
}
private function getRootFormID() {
if (!$this->rootFormID) {
$this->rootFormID = celerity_generate_unique_node_id();
}
return $this->rootFormID;
}
}

View file

@ -0,0 +1,24 @@
<?php
abstract class BulkParameterType extends Phobject {
private $viewer;
final public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
final public function getViewer() {
return $this->viewer;
}
abstract public function getPHUIXControlType();
public function getPHUIXControlSpecification() {
return array(
'value' => null,
);
}
}

View file

@ -0,0 +1,10 @@
<?php
final class BulkStringParameterType
extends BulkParameterType {
public function getPHUIXControlType() {
return 'text';
}
}

View file

@ -2421,6 +2421,71 @@ abstract class PhabricatorEditEngine
}
/* -( Bulk Edits )--------------------------------------------------------- */
final public function newBulkEditMap() {
$config = $this->loadDefaultConfiguration();
if (!$config) {
throw new Exception(
pht('No default edit engine configuration for bulk edit.'));
}
$object = $this->newEditableObject();
$fields = $this->buildEditFields($object);
$edit_types = $this->getBulkEditTypesFromFields($fields);
$map = array();
foreach ($edit_types as $key => $type) {
$bulk_type = $type->getBulkParameterType();
if ($bulk_type === null) {
continue;
}
$bulk_label = $type->getBulkEditLabel();
if ($bulk_label === null) {
continue;
}
$map[] = array(
'label' => $bulk_label,
'xaction' => $type->getTransactionType(),
'control' => array(
'type' => $bulk_type->getPHUIXControlType(),
'spec' => (object)$bulk_type->getPHUIXControlSpecification(),
),
);
}
return $map;
}
final public function newRawBulkTransactions(array $xactions) {
return $xactions;
}
private function getBulkEditTypesFromFields(array $fields) {
$types = array();
foreach ($fields as $field) {
$field_types = $field->getBulkEditTypes();
if ($field_types === null) {
continue;
}
foreach ($field_types as $field_type) {
$field_type->setField($field);
$types[$field_type->getEditType()] = $field_type;
}
}
return $types;
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */

View file

@ -17,6 +17,7 @@ abstract class PhabricatorEditField extends Phobject {
private $previewPanel;
private $controlID;
private $controlInstructions;
private $bulkEditLabel;
private $description;
private $conduitDescription;
@ -45,6 +46,7 @@ abstract class PhabricatorEditField extends Phobject {
private $isConduitOnly = false;
private $conduitEditTypes;
private $bulkEditTypes;
public function setKey($key) {
$this->key = $key;
@ -64,6 +66,15 @@ abstract class PhabricatorEditField extends Phobject {
return $this->label;
}
public function setBulkEditLabel($bulk_edit_label) {
$this->bulkEditLabel = $bulk_edit_label;
return $this;
}
public function getBulkEditLabel() {
return $this->bulkEditLabel;
}
public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
@ -625,6 +636,22 @@ abstract class PhabricatorEditField extends Phobject {
return new AphrontStringHTTPParameterType();
}
protected function getBulkParameterType() {
$type = $this->newBulkParameterType();
if (!$type) {
return null;
}
$type->setViewer($this->getViewer());
return $type;
}
protected function newBulkParameterType() {
return null;
}
public function getConduitParameterType() {
$type = $this->newConduitParameterType();
@ -657,8 +684,15 @@ abstract class PhabricatorEditField extends Phobject {
return null;
}
return id(new PhabricatorSimpleEditType())
$edit_type = id(new PhabricatorSimpleEditType())
->setConduitParameterType($parameter_type);
$bulk_type = $this->getBulkParameterType();
if ($bulk_type) {
$edit_type->setBulkParameterType($bulk_type);
}
return $edit_type;
}
protected function getEditType() {
@ -718,6 +752,31 @@ abstract class PhabricatorEditField extends Phobject {
return array($edit_type);
}
final public function getBulkEditTypes() {
if ($this->bulkEditTypes === null) {
$edit_types = $this->newBulkEditTypes();
$edit_types = mpull($edit_types, null, 'getEditType');
foreach ($edit_types as $edit_type) {
$edit_type->setEditField($this);
}
$this->bulkEditTypes = $edit_types;
}
return $this->bulkEditTypes;
}
protected function newBulkEditTypes() {
$edit_type = $this->getEditType();
if (!$edit_type) {
return array();
}
return array($edit_type);
}
public function getCommentAction() {
$label = $this->getCommentActionLabel();
if ($label === null) {

View file

@ -104,6 +104,10 @@ abstract class PhabricatorPHIDListEditField
return $type;
}
protected function newBulkEditTypes() {
return $this->newConduitEditTypes();
}
protected function newConduitEditTypes() {
if (!$this->getUseEdgeTransactions()) {
return parent::newConduitEditTypes();

View file

@ -29,4 +29,8 @@ final class PhabricatorTextEditField
return new ConduitStringParameterType();
}
protected function newBulkParameterType() {
return new BulkStringParameterType();
}
}

View file

@ -14,6 +14,9 @@ abstract class PhabricatorEditType extends Phobject {
private $conduitTypeDescription;
private $conduitParameterType;
private $bulkParameterType;
private $bulkEditLabel;
public function setLabel($label) {
$this->label = $label;
return $this;
@ -23,6 +26,19 @@ abstract class PhabricatorEditType extends Phobject {
return $this->label;
}
public function setBulkEditLabel($bulk_edit_label) {
$this->bulkEditLabel = $bulk_edit_label;
return $this;
}
public function getBulkEditLabel() {
if ($this->bulkEditLabel !== null) {
return $this->bulkEditLabel;
}
return $this->getField()->getBulkEditLabel();
}
public function setField(PhabricatorEditField $field) {
$this->field = $field;
return $this;
@ -85,6 +101,30 @@ abstract class PhabricatorEditType extends Phobject {
return $this->editField;
}
/* -( Bulk )--------------------------------------------------------------- */
protected function newBulkParameterType() {
if ($this->bulkParameterType) {
return clone $this->bulkParameterType;
}
return null;
}
public function setBulkParameterType(BulkParameterType $type) {
$this->bulkParameterType = $type;
return $this;
}
public function getBulkParameterType() {
return $this->newBulkParameterType();
}
/* -( Conduit )------------------------------------------------------------ */

View file

@ -1,17 +0,0 @@
/**
* @provides maniphest-batch-editor
*/
.maniphest-batch-actions-table {
width: 100%;
margin: 12px 0;
}
.maniphest-batch-actions-table td {
padding: 4px 8px;
vertical-align: middle;
}
.batch-editor-input {
width: 100%;
text-align: left;
}

View file

@ -0,0 +1,22 @@
/**
* @provides phui-bulk-editor-css
*/
.bulk-edit-table {
width: 100%;
margin: 12px 0;
}
.bulk-edit-table td {
padding: 4px 8px;
vertical-align: middle;
}
.bulk-edit-input {
width: 100%;
text-align: left;
}
.bulk-edit-input input {
width: 100%;
}

View file

@ -1,158 +0,0 @@
/**
* @provides javelin-behavior-maniphest-batch-editor
* @requires javelin-behavior
* javelin-dom
* javelin-util
* phabricator-prefab
* multirow-row-manager
* javelin-json
*/
JX.behavior('maniphest-batch-editor', function(config) {
var root = JX.$(config.root);
var editor_table = JX.DOM.find(root, 'table', 'maniphest-batch-actions');
var manager = new JX.MultirowRowManager(editor_table);
var action_rows = [];
function renderRow() {
var action_select = JX.Prefab.renderSelect(
{
'add_project': 'Add Projects',
'remove_project' : 'Remove Projects',
'priority': 'Change Priority',
'status': 'Change Status',
'add_comment': 'Comment',
'assign': 'Assign',
'add_ccs' : 'Add CCs',
'remove_ccs' : 'Remove CCs',
'space': 'Shift to Space'
});
var proj_tokenizer = build_tokenizer(config.sources.project);
var owner_tokenizer = build_tokenizer(config.sources.owner);
var cc_tokenizer = build_tokenizer(config.sources.cc);
var space_tokenizer = build_tokenizer(config.sources.spaces);
var priority_select = JX.Prefab.renderSelect(config.priorityMap);
var status_select = JX.Prefab.renderSelect(config.statusMap);
var comment_input = JX.$N('input', {style: {width: '100%'}});
var cell = JX.$N('td', {className: 'batch-editor-input'});
var vfunc = null;
function update() {
switch (action_select.value) {
case 'add_project':
case 'remove_project':
JX.DOM.setContent(cell, proj_tokenizer.template);
vfunc = function() {
return JX.keys(proj_tokenizer.object.getTokens());
};
break;
case 'add_ccs':
case 'remove_ccs':
JX.DOM.setContent(cell, cc_tokenizer.template);
vfunc = function() {
return JX.keys(cc_tokenizer.object.getTokens());
};
break;
case 'assign':
JX.DOM.setContent(cell, owner_tokenizer.template);
vfunc = function() {
return JX.keys(owner_tokenizer.object.getTokens());
};
break;
case 'space':
JX.DOM.setContent(cell, space_tokenizer.template);
vfunc = function() {
return JX.keys(space_tokenizer.object.getTokens());
};
break;
case 'add_comment':
JX.DOM.setContent(cell, comment_input);
vfunc = function() {
return comment_input.value;
};
break;
case 'priority':
JX.DOM.setContent(cell, priority_select);
vfunc = function() { return priority_select.value; };
break;
case 'status':
JX.DOM.setContent(cell, status_select);
vfunc = function() { return status_select.value; };
break;
}
}
JX.DOM.listen(action_select, 'change', null, update);
update();
return {
nodes : [JX.$N('td', {}, action_select), cell],
dataCallback : function() {
return {
action: action_select.value,
value: vfunc()
};
}
};
}
function onaddaction(e) {
e.kill();
addRow({});
}
function addRow(info) {
var data = renderRow(info);
var row = manager.addRow(data.nodes);
var id = manager.getRowID(row);
action_rows[id] = data.dataCallback;
}
function onsubmit() {
var input = JX.$(config.input);
var actions = [];
for (var k in action_rows) {
actions.push(action_rows[k]());
}
input.value = JX.JSON.stringify(actions);
}
addRow({});
JX.DOM.listen(
root,
'click',
'add-action',
onaddaction);
JX.DOM.listen(
root,
'submit',
null,
onsubmit);
manager.listen(
'row-removed',
function(row_id) {
delete action_rows[row_id];
});
function build_tokenizer(tconfig) {
var built = JX.Prefab.newTokenizerFromTemplate(
config.tokenizerTemplate,
JX.copy({}, tconfig));
built.tokenizer.start();
return {
object: built.tokenizer,
template: built.node
};
}
});

View file

@ -0,0 +1,113 @@
/**
* @provides javelin-behavior-bulk-editor
* @requires javelin-behavior
* javelin-dom
* javelin-util
* phabricator-prefab
* multirow-row-manager
* javelin-json
* phuix-form-control-view
*/
JX.behavior('bulk-editor', function(config) {
var root = JX.$(config.rootNodeID);
var editor_table = JX.DOM.find(root, 'table', 'bulk-actions');
var manager = new JX.MultirowRowManager(editor_table);
var action_rows = [];
var option_map = {};
var option_order = [];
var spec_map = {};
for (var ii = 0; ii < config.edits.length; ii++) {
var edit = config.edits[ii];
option_map[edit.xaction] = edit.label;
option_order.push(edit.xaction);
spec_map[edit.xaction] = edit;
}
function renderRow() {
var action_select = JX.Prefab.renderSelect(
option_map,
null,
null,
option_order);
var cell = JX.$N('td', {className: 'bulk-edit-input'});
var vfunc = null;
function update() {
var spec = spec_map[action_select.value];
var control = spec.control;
var phuix = new JX.PHUIXFormControl()
.setControl(control.type, control.spec);
JX.DOM.setContent(cell, phuix.getRawInputNode());
vfunc = JX.bind(phuix, phuix.getValue);
}
JX.DOM.listen(action_select, 'change', null, update);
update();
return {
nodes : [JX.$N('td', {}, action_select), cell],
dataCallback : function() {
return {
type: action_select.value,
value: vfunc()
};
}
};
}
function onaddaction(e) {
e.kill();
addRow({});
}
function addRow(info) {
var data = renderRow(info);
var row = manager.addRow(data.nodes);
var id = manager.getRowID(row);
action_rows[id] = data.dataCallback;
}
function onsubmit() {
var input = JX.$(config.inputNodeID);
var actions = [];
for (var k in action_rows) {
actions.push(action_rows[k]());
}
input.value = JX.JSON.stringify(actions);
}
addRow({});
JX.DOM.listen(
root,
'click',
'add-action',
onaddaction);
JX.DOM.listen(
root,
'submit',
null,
onsubmit);
manager.listen(
'row-removed',
function(row_id) {
delete action_rows[row_id];
});
});

View file

@ -14,6 +14,7 @@ JX.install('PHUIXFormControl', {
_className: null,
_valueSetCallback: null,
_valueGetCallback: null,
_rawInputNode: null,
setLabel: function(label) {
JX.DOM.setContent(this._getLabelNode(), label);
@ -53,6 +54,9 @@ JX.install('PHUIXFormControl', {
case 'checkboxes':
input = this._newCheckboxes(spec);
break;
case 'text':
input = this._newText(spec);
break;
default:
// TODO: Default or better error?
JX.$E('Bad Input Type');
@ -62,6 +66,7 @@ JX.install('PHUIXFormControl', {
JX.DOM.setContent(node, input.node);
this._valueGetCallback = input.get;
this._valueSetCallback = input.set;
this._rawInputNode = input.node;
return this;
},
@ -75,6 +80,10 @@ JX.install('PHUIXFormControl', {
return this._valueGetCallback();
},
getRawInputNode: function() {
return this._rawInputNode;
},
getNode: function() {
if (!this._node) {
@ -281,6 +290,10 @@ JX.install('PHUIXFormControl', {
},
_newPoints: function(spec) {
return this._newText();
},
_newText: function(spec) {
var attrs = {
type: 'text',
value: spec.value