1
0
Fork 0
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:
epriestley 2015-05-04 10:08:49 -07:00
parent ba6b1376f2
commit 948d69364a
7 changed files with 194 additions and 49 deletions

View file

@ -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',

View file

@ -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;
}
}

View file

@ -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(

View file

@ -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,

View file

@ -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;

View file

@ -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;
}

View file

@ -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) {