mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-22 23:02:42 +01:00
Manage date control enabled state as part of DateControlValue
Summary: Ref T8024. Allow `DateControlValue` to manage enabled/disabled state, so we can eventually delete the copy of this logic in `DateControl`. Test Plan: - Used Calendar ApplicationSearch queries to observe improved behaviors: - Error for invalid start date, if enabled. - Error for invalid end date, if enabled. - Error for invalid date range, if both enabled. - When submitting an invalid date (for example, with the time "Tea Time"), form retains invalid date verbatim instead of discarding information. - Created an event, using existing date controls to check that I didn't break anything. Reviewers: chad, lpriestley, btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T8024 Differential Revision: https://secure.phabricator.com/D12673
This commit is contained in:
parent
ba6b1376f2
commit
948d69364a
7 changed files with 194 additions and 49 deletions
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
return array(
|
||||
'names' => array(
|
||||
'core.pkg.css' => 'd445f9b8',
|
||||
'core.pkg.css' => 'ca3f6a60',
|
||||
'core.pkg.js' => '3331b919',
|
||||
'darkconsole.pkg.js' => 'e7393ebb',
|
||||
'differential.pkg.css' => '0a253fbe',
|
||||
|
@ -132,7 +132,7 @@ return array(
|
|||
'rsrc/css/phui/phui-document.css' => '94d5dcd8',
|
||||
'rsrc/css/phui/phui-feed-story.css' => 'c9f3a0b5',
|
||||
'rsrc/css/phui/phui-fontkit.css' => 'dd8ddf27',
|
||||
'rsrc/css/phui/phui-form-view.css' => 'b147d2ed',
|
||||
'rsrc/css/phui/phui-form-view.css' => '17eace76',
|
||||
'rsrc/css/phui/phui-form.css' => 'f535f938',
|
||||
'rsrc/css/phui/phui-header-view.css' => 'da4586b1',
|
||||
'rsrc/css/phui/phui-icon.css' => 'bc766998',
|
||||
|
@ -463,7 +463,7 @@ return array(
|
|||
'rsrc/js/core/behavior-device.js' => 'a205cf28',
|
||||
'rsrc/js/core/behavior-drag-and-drop-textarea.js' => '6d49590e',
|
||||
'rsrc/js/core/behavior-error-log.js' => '6882e80a',
|
||||
'rsrc/js/core/behavior-fancy-datepicker.js' => 'c51ae228',
|
||||
'rsrc/js/core/behavior-fancy-datepicker.js' => '5c0f680f',
|
||||
'rsrc/js/core/behavior-file-tree.js' => '88236f00',
|
||||
'rsrc/js/core/behavior-form.js' => '5c54cbf3',
|
||||
'rsrc/js/core/behavior-gesture.js' => '3ab51e2c',
|
||||
|
@ -590,7 +590,7 @@ return array(
|
|||
'javelin-behavior-doorkeeper-tag' => 'e5822781',
|
||||
'javelin-behavior-durable-column' => '657c2b50',
|
||||
'javelin-behavior-error-log' => '6882e80a',
|
||||
'javelin-behavior-fancy-datepicker' => 'c51ae228',
|
||||
'javelin-behavior-fancy-datepicker' => '5c0f680f',
|
||||
'javelin-behavior-global-drag-and-drop' => 'c203e6ee',
|
||||
'javelin-behavior-herald-rule-editor' => '7ebaeed3',
|
||||
'javelin-behavior-high-security-warning' => 'a464fe03',
|
||||
|
@ -789,7 +789,7 @@ return array(
|
|||
'phui-font-icon-base-css' => '3dad2ae3',
|
||||
'phui-fontkit-css' => 'dd8ddf27',
|
||||
'phui-form-css' => 'f535f938',
|
||||
'phui-form-view-css' => 'b147d2ed',
|
||||
'phui-form-view-css' => '17eace76',
|
||||
'phui-header-view-css' => 'da4586b1',
|
||||
'phui-icon-view-css' => 'bc766998',
|
||||
'phui-image-mask-css' => '5a8b09c8',
|
||||
|
@ -1215,6 +1215,13 @@ return array(
|
|||
'javelin-uri',
|
||||
'javelin-routable',
|
||||
),
|
||||
'5c0f680f' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-util',
|
||||
'javelin-dom',
|
||||
'javelin-stratcom',
|
||||
'javelin-vector',
|
||||
),
|
||||
'5c54cbf3' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-stratcom',
|
||||
|
@ -1768,13 +1775,6 @@ return array(
|
|||
'javelin-mask',
|
||||
'phabricator-drag-and-drop-file-upload',
|
||||
),
|
||||
'c51ae228' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-util',
|
||||
'javelin-dom',
|
||||
'javelin-stratcom',
|
||||
'javelin-vector',
|
||||
),
|
||||
'c90a04fc' => array(
|
||||
'javelin-dom',
|
||||
'javelin-dynval',
|
||||
|
|
|
@ -53,16 +53,8 @@ final class PhabricatorCalendarEventSearchEngine
|
|||
$query = id(new PhabricatorCalendarEventQuery());
|
||||
$viewer = $this->requireViewer();
|
||||
|
||||
$min_range = null;
|
||||
$max_range = null;
|
||||
|
||||
if ($saved->getParameter('rangeStart')) {
|
||||
$min_range = $saved->getParameter('rangeStart');
|
||||
}
|
||||
|
||||
if ($saved->getParameter('rangeEnd')) {
|
||||
$max_range = $saved->getParameter('rangeEnd');
|
||||
}
|
||||
$min_range = $this->getDateFrom($saved)->getEpoch();
|
||||
$max_range = $this->getDateTo($saved)->getEpoch();
|
||||
|
||||
if ($saved->getParameter('display') == 'month') {
|
||||
list($start_month, $start_year) = $this->getDisplayMonthAndYear($saved);
|
||||
|
@ -130,8 +122,31 @@ final class PhabricatorCalendarEventSearchEngine
|
|||
AphrontFormView $form,
|
||||
PhabricatorSavedQuery $saved) {
|
||||
|
||||
$range_start = $saved->getParameter('rangeStart');
|
||||
$range_end = $saved->getParameter('rangeEnd');
|
||||
$range_start = $this->getDateFrom($saved);
|
||||
$e_start = null;
|
||||
|
||||
$range_end = $this->getDateTo($saved);
|
||||
$e_end = null;
|
||||
|
||||
if (!$range_start->isValid()) {
|
||||
$this->addError(pht('Start date is not valid.'));
|
||||
$e_start = pht('Invalid');
|
||||
}
|
||||
|
||||
if (!$range_end->isValid()) {
|
||||
$this->addError(pht('End date is not valid.'));
|
||||
$e_end = pht('Invalid');
|
||||
}
|
||||
|
||||
$start_epoch = $range_start->getEpoch();
|
||||
$end_epoch = $range_end->getEpoch();
|
||||
|
||||
if ($start_epoch && $end_epoch && ($start_epoch > $end_epoch)) {
|
||||
$this->addError(pht('End date must be after start date.'));
|
||||
$e_start = pht('Invalid');
|
||||
$e_end = pht('Invalid');
|
||||
}
|
||||
|
||||
$upcoming = $saved->getParameter('upcoming');
|
||||
$is_cancelled = $saved->getParameter('isCancelled', 'active');
|
||||
$display = $saved->getParameter('display', 'month');
|
||||
|
@ -167,14 +182,14 @@ final class PhabricatorCalendarEventSearchEngine
|
|||
->setLabel(pht('Occurs After'))
|
||||
->setUser($this->requireViewer())
|
||||
->setName('rangeStart')
|
||||
->setAllowNull(true)
|
||||
->setError($e_start)
|
||||
->setValue($range_start))
|
||||
->appendChild(
|
||||
id(new AphrontFormDateControl())
|
||||
->setLabel(pht('Occurs Before'))
|
||||
->setUser($this->requireViewer())
|
||||
->setName('rangeEnd')
|
||||
->setAllowNull(true)
|
||||
->setError($e_end)
|
||||
->setValue($range_end))
|
||||
->appendChild(
|
||||
id(new AphrontFormCheckboxControl())
|
||||
|
@ -391,9 +406,9 @@ final class PhabricatorCalendarEventSearchEngine
|
|||
$start_year = $this->calendarYear;
|
||||
$start_month = $this->calendarMonth;
|
||||
} else {
|
||||
$epoch = $query->getParameter('rangeStart');
|
||||
$epoch = $this->getDateFrom($query)->getEpoch();
|
||||
if (!$epoch) {
|
||||
$epoch = $query->getParameter('rangeEnd');
|
||||
$epoch = $this->getDateTo($query)->getEpoch();
|
||||
if (!$epoch) {
|
||||
$epoch = time();
|
||||
}
|
||||
|
@ -431,4 +446,30 @@ final class PhabricatorCalendarEventSearchEngine
|
|||
return $saved->getParameter('limit', 1000);
|
||||
}
|
||||
|
||||
private function getDateFrom(PhabricatorSavedQuery $saved) {
|
||||
return $this->getDate($saved, 'rangeStart');
|
||||
}
|
||||
|
||||
private function getDateTo(PhabricatorSavedQuery $saved) {
|
||||
return $this->getDate($saved, 'rangeEnd');
|
||||
}
|
||||
|
||||
private function getDate(PhabricatorSavedQuery $saved, $key) {
|
||||
$viewer = $this->requireViewer();
|
||||
|
||||
$wild = $saved->getParameter($key);
|
||||
if ($wild) {
|
||||
$value = AphrontFormDateControlValue::newFromWild($viewer, $wild);
|
||||
} else {
|
||||
$value = AphrontFormDateControlValue::newFromEpoch(
|
||||
$viewer,
|
||||
PhabricatorTime::getNow());
|
||||
$value->setEnabled(false);
|
||||
}
|
||||
|
||||
$value->setOptional(true);
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -566,11 +566,13 @@ abstract class PhabricatorApplicationSearchEngine {
|
|||
AphrontRequest $request,
|
||||
$key) {
|
||||
|
||||
return id(new AphrontFormDateControl())
|
||||
->setUser($this->requireViewer())
|
||||
->setName($key)
|
||||
->setAllowNull(true)
|
||||
->readValueFromRequest($request);
|
||||
$value = AphrontFormDateControlValue::newFromRequest($request, $key);
|
||||
|
||||
if ($value->isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $value->getDictionary();
|
||||
}
|
||||
|
||||
protected function readBoolFromRequest(
|
||||
|
|
|
@ -11,6 +11,7 @@ final class AphrontFormDateControl extends AphrontFormControl {
|
|||
private $valueTime;
|
||||
private $allowNull;
|
||||
private $continueOnInvalidDate = false;
|
||||
private $isDisabled;
|
||||
|
||||
public function setAllowNull($allow_null) {
|
||||
$this->allowNull = $allow_null;
|
||||
|
@ -91,6 +92,8 @@ final class AphrontFormDateControl extends AphrontFormControl {
|
|||
$this->valueMonth = $epoch->getValueMonth();
|
||||
$this->valueDay = $epoch->getValueDay();
|
||||
$this->valueTime = $epoch->getValueTime();
|
||||
$this->allowNull = $epoch->getOptional();
|
||||
$this->isDisabled = $epoch->isDisabled();
|
||||
|
||||
return parent::setValue($epoch->getEpoch());
|
||||
}
|
||||
|
@ -183,6 +186,10 @@ final class AphrontFormDateControl extends AphrontFormControl {
|
|||
}
|
||||
}
|
||||
|
||||
if ($this->isDisabled) {
|
||||
$disabled = 'disabled';
|
||||
}
|
||||
|
||||
$min_year = $this->getMinYear();
|
||||
$max_year = $this->getMaxYear();
|
||||
|
||||
|
@ -227,7 +234,6 @@ final class AphrontFormDateControl extends AphrontFormControl {
|
|||
array(
|
||||
'name' => $this->getDayInputName(),
|
||||
'sigil' => 'day-input',
|
||||
'disabled' => $disabled,
|
||||
));
|
||||
|
||||
$months_sel = AphrontFormSelectControl::renderSelectTag(
|
||||
|
@ -236,7 +242,6 @@ final class AphrontFormDateControl extends AphrontFormControl {
|
|||
array(
|
||||
'name' => $this->getMonthInputName(),
|
||||
'sigil' => 'month-input',
|
||||
'disabled' => $disabled,
|
||||
));
|
||||
|
||||
$years_sel = AphrontFormSelectControl::renderSelectTag(
|
||||
|
@ -245,7 +250,6 @@ final class AphrontFormDateControl extends AphrontFormControl {
|
|||
array(
|
||||
'name' => $this->getYearInputName(),
|
||||
'sigil' => 'year-input',
|
||||
'disabled' => $disabled,
|
||||
));
|
||||
|
||||
$cicon = id(new PHUIIconView())
|
||||
|
@ -268,16 +272,21 @@ final class AphrontFormDateControl extends AphrontFormControl {
|
|||
'value' => $this->getTimeInputValue(),
|
||||
'type' => 'text',
|
||||
'class' => 'aphront-form-date-time-input',
|
||||
'disabled' => $disabled,
|
||||
),
|
||||
'');
|
||||
|
||||
Javelin::initBehavior('fancy-datepicker', array());
|
||||
|
||||
$classes = array();
|
||||
$classes[] = 'aphront-form-date-container';
|
||||
if ($disabled) {
|
||||
$classes[] = 'datepicker-disabled';
|
||||
}
|
||||
|
||||
return javelin_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'aphront-form-date-container',
|
||||
'class' => implode(' ', $classes),
|
||||
'sigil' => 'phabricator-date-control',
|
||||
'meta' => array(
|
||||
'disabled' => (bool)$disabled,
|
||||
|
|
|
@ -6,9 +6,11 @@ final class AphrontFormDateControlValue extends Phobject {
|
|||
private $valueMonth;
|
||||
private $valueYear;
|
||||
private $valueTime;
|
||||
private $valueEnabled;
|
||||
|
||||
private $viewer;
|
||||
private $zone;
|
||||
private $optional;
|
||||
|
||||
public function getValueDay() {
|
||||
return $this->valueDay;
|
||||
|
@ -27,27 +29,70 @@ final class AphrontFormDateControlValue extends Phobject {
|
|||
}
|
||||
|
||||
public function isValid() {
|
||||
if ($this->isDisabled()) {
|
||||
return true;
|
||||
}
|
||||
return ($this->getEpoch() !== null);
|
||||
}
|
||||
|
||||
public function isEmpty() {
|
||||
if ($this->valueDay) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->valueMonth) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->valueYear) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->valueTime) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function isDisabled() {
|
||||
return ($this->optional && !$this->valueEnabled);
|
||||
}
|
||||
|
||||
public function setEnabled($enabled) {
|
||||
$this->valueEnabled = $enabled;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setOptional($optional) {
|
||||
$this->optional = $optional;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getOptional() {
|
||||
return $this->optional;
|
||||
}
|
||||
|
||||
public static function newFromParts(
|
||||
PhabricatorUser $viewer,
|
||||
$year,
|
||||
$month,
|
||||
$day,
|
||||
$time = '12:00 AM') {
|
||||
$time = null,
|
||||
$enabled = true) {
|
||||
|
||||
$value = new AphrontFormDateControlValue();
|
||||
$value->viewer = $viewer;
|
||||
$value->valueYear = $year;
|
||||
$value->valueMonth = $month;
|
||||
$value->valueDay = $day;
|
||||
$value->valueTime = $time;
|
||||
$value->valueTime = coalesce($time, '12:00 AM');
|
||||
$value->valueEnabled = $enabled;
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
public static function newFromRequest($request, $key) {
|
||||
public static function newFromRequest(AphrontRequest $request, $key) {
|
||||
$value = new AphrontFormDateControlValue();
|
||||
$value->viewer = $request->getViewer();
|
||||
|
||||
|
@ -55,6 +100,7 @@ final class AphrontFormDateControlValue extends Phobject {
|
|||
$value->valueMonth = $request->getInt($key.'_m');
|
||||
$value->valueYear = $request->getInt($key.'_y');
|
||||
$value->valueTime = $request->getStr($key.'_t');
|
||||
$value->valueEnabled = $request->getStr($key.'_e');
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
@ -73,6 +119,44 @@ final class AphrontFormDateControlValue extends Phobject {
|
|||
return $value;
|
||||
}
|
||||
|
||||
public static function newFromDictionary(
|
||||
PhabricatorUser $viewer,
|
||||
array $dictionary) {
|
||||
$value = new AphrontFormDateControlValue();
|
||||
$value->viewer = $viewer;
|
||||
|
||||
$value->valueYear = idx($dictionary, 'y');
|
||||
$value->valueMonth = idx($dictionary, 'm');
|
||||
$value->valueDay = idx($dictionary, 'd');
|
||||
$value->valueTime = idx($dictionary, 't');
|
||||
$value->valueEnabled = idx($dictionary, 'e');
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
public static function newFromWild(PhabricatorUser $viewer, $wild) {
|
||||
if (is_array($wild)) {
|
||||
return self::newFromDictionary($viewer, $wild);
|
||||
} else if (is_numeric($wild)) {
|
||||
return self::newFromEpoch($viewer, $wild);
|
||||
} else {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Unable to construct a date value from value of type "%s".',
|
||||
gettype($wild)));
|
||||
}
|
||||
}
|
||||
|
||||
public function getDictionary() {
|
||||
return array(
|
||||
'y' => $this->valueYear,
|
||||
'm' => $this->valueMonth,
|
||||
'd' => $this->valueDay,
|
||||
't' => $this->valueTime,
|
||||
'e' => $this->valueEnabled,
|
||||
);
|
||||
}
|
||||
|
||||
private function formatTime($epoch, $format) {
|
||||
return phabricator_format_local_time(
|
||||
$epoch,
|
||||
|
@ -81,6 +165,10 @@ final class AphrontFormDateControlValue extends Phobject {
|
|||
}
|
||||
|
||||
public function getEpoch() {
|
||||
if ($this->isDisabled()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$year = $this->valueYear;
|
||||
$month = $this->valueMonth;
|
||||
$day = $this->valueDay;
|
||||
|
|
|
@ -446,6 +446,18 @@ table.aphront-form-control-checkbox-layout th {
|
|||
border-radius: 3px;
|
||||
}
|
||||
|
||||
/* When the activation checkbox for the control is toggled off, visually
|
||||
disable the individual controls. We don't actually use the "disabled" property
|
||||
because we still want the values to submit. This is just a visual hint that
|
||||
the controls won't be used. The controls themselves are still live, work
|
||||
properly, and submit values. */
|
||||
.datepicker-disabled select,
|
||||
.datepicker-disabled .calendar-button,
|
||||
.datepicker-disabled input[type="text"] {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
|
||||
.login-to-comment {
|
||||
margin: 12px;
|
||||
}
|
||||
|
|
|
@ -109,15 +109,8 @@ JX.behavior('fancy-datepicker', function() {
|
|||
};
|
||||
|
||||
var redraw_inputs = function() {
|
||||
var inputs = get_inputs();
|
||||
var disabled = JX.Stratcom.getData(root).disabled;
|
||||
for (var k in inputs) {
|
||||
if (disabled) {
|
||||
inputs[k].setAttribute('disabled', 'disabled');
|
||||
} else {
|
||||
inputs[k].removeAttribute('disabled');
|
||||
}
|
||||
}
|
||||
JX.DOM.alterClass(root, 'datepicker-disabled', disabled);
|
||||
|
||||
var box = JX.DOM.scry(root, 'input', 'calendar-enable');
|
||||
if (box.length) {
|
||||
|
|
Loading…
Reference in a new issue