Page MenuHomePhorge

No OneTemporary

diff --git a/src/applications/uiexample/examples/PhabricatorFormExample.php b/src/applications/uiexample/examples/PhabricatorFormExample.php
index 0cb2fdf040..168666e1c0 100644
--- a/src/applications/uiexample/examples/PhabricatorFormExample.php
+++ b/src/applications/uiexample/examples/PhabricatorFormExample.php
@@ -1,53 +1,58 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
final class PhabricatorFormExample extends PhabricatorUIExample {
public function getName() {
return 'Form';
}
public function getDescription() {
return 'Use <tt>AphrontFormView</tt> to render forms.';
}
public function renderExample() {
$request = $this->getRequest();
$user = $request->getUser();
- $date = id(new AphrontFormDateControl())
+ $start_time = id(new AphrontFormDateControl())
->setUser($user)
- ->setName('date')
- ->setLabel('Date');
+ ->setName('start')
+ ->setLabel('Start')
+ ->setInitialTime(AphrontFormDateControl::TIME_START_OF_BUSINESS);
+ $start_value = $start_time->readValueFromRequest($request);
- $date->readValueFromRequest($request);
+ $end_time = id(new AphrontFormDateControl())
+ ->setUser($user)
+ ->setName('end')
+ ->setLabel('End')
+ ->setInitialTime(AphrontFormDateControl::TIME_END_OF_BUSINESS);
+ $end_value = $end_time->readValueFromRequest($request);
$form = id(new AphrontFormView())
->setUser($user)
- ->appendChild($date)
+ ->setFlexible(true)
+ ->appendChild($start_time)
+ ->appendChild($end_time)
->appendChild(
id(new AphrontFormSubmitControl())
->setValue('Submit'));
- $panel = new AphrontPanelView();
- $panel->setHeader('Form');
- $panel->appendChild($form);
-
- return $panel;
+ return $form;
}
}
diff --git a/src/view/form/control/AphrontFormDateControl.php b/src/view/form/control/AphrontFormDateControl.php
index e5372317ed..1f51e57225 100644
--- a/src/view/form/control/AphrontFormDateControl.php
+++ b/src/view/form/control/AphrontFormDateControl.php
@@ -1,201 +1,288 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
final class AphrontFormDateControl extends AphrontFormControl {
private $user;
+ private $initialTime;
- public function setUser($user) {
+ private $valueDay;
+ private $valueMonth;
+ private $valueYear;
+ private $valueTime;
+
+ const TIME_START_OF_DAY = 'start-of-day';
+ const TIME_END_OF_DAY = 'end-of-day';
+ const TIME_START_OF_BUSINESS = 'start-of-business';
+ const TIME_END_OF_BUSINESS = 'end-of-business';
+
+ public function setUser(PhabricatorUser $user) {
$this->user = $user;
return $this;
}
+ public function setInitialTime($time) {
+ $this->initialTime = $time;
+ return $this;
+ }
+
public function readValueFromRequest(AphrontRequest $request) {
+ $user = $this->user;
+ if (!$this->user) {
+ throw new Exception(
+ "Call setUser() before readValueFromRequest()!");
+ }
+
+ $user_zone = $user->getTimezoneIdentifier();
+ $zone = new DateTimeZone($user_zone);
$day = $request->getInt($this->getDayInputName());
$month = $request->getInt($this->getMonthInputName());
$year = $request->getInt($this->getYearInputName());
+ $time = $request->getStr($this->getTimeInputName());
$err = $this->getError();
- if ($day || $month || $year) {
+ if ($day || $month || $year || $time) {
+ $this->valueDay = $day;
+ $this->valueMonth = $month;
+ $this->valueYear = $year;
+ $this->valueTime = $time;
// Assume invalid.
$err = 'Invalid';
- $tz = new DateTimeZone('UTC');
try {
- $date = new DateTime("{$year}-{$month}-{$day} 12:00:00 AM", $tz);
- $value = $date->format('Y-m-d');
- if ($value) {
- $this->setValue($value);
- $err = null;
- }
+ $date = new DateTime("{$year}-{$month}-{$day} {$time}", $zone);
+ $value = $date->format('U');
} catch (Exception $ex) {
- // Ignore, already handled.
+ $value = null;
+ }
+
+ if ($value) {
+ $this->setValue($value);
+ $err = null;
+ } else {
+ $this->setValue(null);
+ }
+ } else {
+ // TODO: We could eventually allow these to be customized per install or
+ // per user or both, but let's wait and see.
+ switch ($this->initialTime) {
+ case self::TIME_START_OF_DAY:
+ default:
+ $time = '12:00 AM';
+ break;
+ case self::TIME_START_OF_BUSINESS:
+ $time = '9:00 AM';
+ break;
+ case self::TIME_END_OF_BUSINESS:
+ $time = '5:00 PM';
+ break;
+ case self::TIME_END_OF_DAY:
+ $time = '11:59 PM';
+ break;
+ }
+
+ $today = $this->formatTime(time(), 'Y-m-d');
+ try {
+ $date = new DateTime("{$today} {$time}", $zone);
+ $value = $date->format('U');
+ } catch (Exception $ex) {
+ $value = null;
+ }
+
+ if ($value) {
+ $this->setValue($value);
+ } else {
+ $this->setValue(null);
}
}
$this->setError($err);
- return $err;
+ return $this->getValue();
}
- public function getValue() {
- if (!parent::getValue()) {
- $this->setValue($this->formatTime(time(), 'Y-m-d'));
- }
- return parent::getValue();
+ protected function getCustomControlClass() {
+ return 'aphront-form-control-date';
}
+ public function setValue($epoch) {
+ $result = parent::setValue($epoch);
- protected function getCustomControlClass() {
- return 'aphront-form-control-date';
+ if ($epoch === null) {
+ return;
+ }
+
+ $readable = $this->formatTime($epoch, 'Y!m!d!g:i A');
+ $readable = explode('!', $readable, 4);
+
+ $this->valueYear = $readable[0];
+ $this->valueMonth = $readable[1];
+ $this->valueDay = $readable[2];
+ $this->valueTime = $readable[3];
+
+ return $result;
}
private function getMinYear() {
$cur_year = $this->formatTime(
time(),
'Y');
$val_year = $this->getYearInputValue();
return min($cur_year, $val_year) - 3;
}
private function getMaxYear() {
$cur_year = $this->formatTime(
time(),
'Y');
$val_year = $this->getYearInputValue();
return max($cur_year, $val_year) + 3;
}
private function getDayInputValue() {
- return (int)idx(explode('-', $this->getValue()), 2);
+ return $this->valueDay;
}
private function getMonthInputValue() {
- return (int)idx(explode('-', $this->getValue()), 1);
+ return $this->valueMonth;
}
private function getYearInputValue() {
- return (int)idx(explode('-', $this->getValue()), 0);
+ return $this->valueYear;
+ }
+
+ private function getTimeInputValue() {
+ return $this->valueTime;
}
private function formatTime($epoch, $fmt) {
return phabricator_format_local_time(
$epoch,
$this->user,
$fmt);
}
private function getDayInputName() {
return $this->getName().'_d';
}
private function getMonthInputName() {
return $this->getName().'_m';
}
private function getYearInputName() {
return $this->getName().'_y';
}
+ private function getTimeInputName() {
+ return $this->getName().'_t';
+ }
+
protected function renderInput() {
$min_year = $this->getMinYear();
$max_year = $this->getMaxYear();
$days = range(1, 31);
$days = array_combine($days, $days);
$months = array(
1 => 'Jan',
2 => 'Feb',
3 => 'Mar',
4 => 'Apr',
5 => 'May',
6 => 'Jun',
7 => 'Jul',
8 => 'Aug',
9 => 'Sep',
10 => 'Oct',
11 => 'Nov',
12 => 'Dec',
);
$years = range($this->getMinYear(), $this->getMaxYear());
$years = array_combine($years, $years);
$days_sel = AphrontFormSelectControl::renderSelectTag(
$this->getDayInputValue(),
$days,
array(
'name' => $this->getDayInputName(),
'sigil' => 'day-input',
));
$months_sel = AphrontFormSelectControl::renderSelectTag(
$this->getMonthInputValue(),
$months,
array(
'name' => $this->getMonthInputName(),
'sigil' => 'month-input',
));
$years_sel = AphrontFormSelectControl::renderSelectTag(
$this->getYearInputValue(),
$years,
array(
'name' => $this->getYearInputName(),
'sigil' => 'year-input',
));
$cal_icon = javelin_render_tag(
'a',
array(
'href' => '#',
'class' => 'calendar-button',
'sigil' => 'calendar-button',
),
'');
- $id = celerity_generate_unique_node_id();
-
- Javelin::initBehavior(
- 'fancy-datepicker',
+ $time_sel = phutil_render_tag(
+ 'input',
array(
- 'root' => $id,
- ));
+ 'name' => $this->getTimeInputName(),
+ 'sigil' => 'time-input',
+ 'value' => $this->getTimeInputValue(),
+ 'type' => 'text',
+ 'class' => 'aphront-form-date-time-input',
+ ),
+ '');
+
+ Javelin::initBehavior('fancy-datepicker', array());
return javelin_render_tag(
'div',
array(
- 'id' => $id,
'class' => 'aphront-form-date-container',
+ 'sigil' => 'phabricator-date-control',
),
self::renderSingleView(
array(
$days_sel,
$months_sel,
$years_sel,
$cal_icon,
+ $time_sel,
)));
}
}
diff --git a/webroot/rsrc/css/aphront/form-view.css b/webroot/rsrc/css/aphront/form-view.css
index 84dcf6fe9a..d690d119d9 100644
--- a/webroot/rsrc/css/aphront/form-view.css
+++ b/webroot/rsrc/css/aphront/form-view.css
@@ -1,314 +1,317 @@
/**
* @provides aphront-form-view-css
*/
/**
* These styles are overrides for .aphront-form-view
*/
.aphront-form-view-shaded {
border: 1px solid #c4c4c4;
background: #e7e7e7;
}
.aphront-form-view-padded {
padding: 1em;
}
.aphront-form-view label.aphront-form-label {
padding-top: 4px;
width: 19%;
float: left;
text-align: right;
font-weight: bold;
font-size: 13px;
color: #666666;
}
.aphront-form-input {
margin-left: 20%;
margin-right: 25%;
width: 55%;
}
.aphront-form-error {
width: 23%;
float: right;
color: #aa0000;
font-weight: bold;
padding-top: 4px;
}
.aphront-form-required {
font-weight: normal;
color: #888888;
font-size: 11px;
}
.aphront-form-input input,
.aphront-form-input textarea {
font-size: 12px;
display: block;
width: 100%;
box-sizing: border-box;
}
.aphront-form-input textarea {
height: 12em;
}
.aphront-form-control {
padding: 4px;
}
.aphront-form-control-submit button,
.aphront-form-control-submit a.button {
float: right;
margin: 0.5em 0 0em 2%;
}
.aphront-form-control-textarea textarea.aphront-textarea-very-short {
height: 3em;
}
.aphront-form-control-textarea textarea.aphront-textarea-very-tall {
height: 24em;
}
.aphront-form-control-select .aphront-form-input {
padding-top: 2px;
}
.aphront-form-view .aphront-form-caption {
font-size: 11px;
color: #444444;
text-align: right;
margin-right: 25%;
margin-left: 20%;
}
/* override for when inside an aphront-panel-view */
.aphront-panel-view .aphront-form-view h1 {
padding: 0em 0em .8em 0em;
}
.aphront-form-instructions {
margin: 0.75em 3% 1.25em;
}
.aphront-form-important {
margin: .5em 0;
background: #ffffdd;
padding: .5em 1em;
}
.aphront-form-important code {
display: block;
padding: .25em;
margin: .5em 2em;
}
.aphront-form-control-static .aphront-form-input,
.aphront-form-control-markup .aphront-form-input {
padding-top: 4px;
font-size: 13px;
}
.aphront-form-control-togglebuttons .aphront-form-input {
padding-top: 5px;
}
table.aphront-form-control-radio-layout,
table.aphront-form-control-checkbox-layout {
margin-top: 3px;
font-size: 13px;
}
table.aphront-form-control-radio-layout th,
table.aphront-form-control-checkbox-layout th {
padding-top: 2px;
padding-left: 0.35em;
padding-bottom: 4px;
}
.aphront-form-control-radio-layout td input,
.aphront-form-control-checkbox-layout td input {
margin-top: 4px;
width: auto;
}
.aphront-form-radio-caption {
font-size: 11px;
color: #444444;
max-width: 400px;
}
.aphront-form-control-image .image {
width: 164px;
}
.aphront-form-control-image span {
margin: 0px 4px 0px 2px;
}
.aphront-form-control-image .default-image {
width: 12px;
}
.aphront-form-input hr {
border: none;
background: #bbbbbb;
height: 1px;
position: relative;
}
.aphront-form-inset {
margin: 0 0 1em;
padding: .75em;
background: #f3f3f3;
border: 1px solid #afafaf;
}
.aphront-form-drag-and-drop-file-list {
width: 400px;
}
.drag-and-drop-instructions {
color: #333333;
font-size: 11px;
padding: 6px 8px;
}
.drag-and-drop-file-target {
border: 1px dashed #bfbfbf;
padding-top: 10px;
padding-bottom: 10px;
}
.aphront-textarea-drag-and-drop {
background: #99ff99;
border-color: #669966;
}
.calendar-button {
- padding: 11px;
- right: -30px;
- top: -3px;
-
+ display: inline;
background: url(/rsrc/image/icon/fatcow/calendar_edit.png)
no-repeat center center;
- z-index: 2;
- position: absolute;
+ padding: 8px 12px;
+ margin: 2px 8px 2px 2px;
+ position: relative;
+ z-index: 8;
border: 1px solid transparent;
}
.aphront-form-date-container {
position: relative;
display: inline;
}
-.aphront-form-date-container select{
+.aphront-form-date-container select {
margin: 2px;
+ display: inline;
+}
+
+.aphront-form-date-container input.aphront-form-date-time-input {
+ width: 7em;
+ display: inline;
}
.fancy-datepicker {
position: absolute;
- top: -10px;
- right: -8px;
width: 240px;
- padding-bottom: 6em;
+ z-index: 7;
}
.fancy-datepicker-core {
padding: 1px;
font-size: 11px;
font-family: Verdana;
text-align: center;
}
.fancy-datepicker-core .month-table,
.fancy-datepicker-core .day-table {
margin: 0 auto;
border-collapse: separate;
border-spacing: 1px;
width: 100%;
}
.fancy-datepicker-core .month-table {
margin-bottom: 6px;
}
.fancy-datepicker-core .month-table td.lrbutton {
width: 20%;
}
.fancy-datepicker-core .month-table td {
padding: 4px;
font-weight: bold;
color: #444444;
}
.fancy-datepicker-core .month-table td.lrbutton {
background: #e6e6e6;
border: 1px solid;
border-color: #a6a6a6 #969696 #868686 #a6a6a6;
}
.fancy-datepicker-core .day-table td {
overflow: hidden;
background: #f6f6f6;
vertical-align: center;
text-align: center;
border: 1px solid #d6d6d6;
padding: 4px 0;
}
.fancy-datepicker-core .day-table td.day-placeholder {
border-color: transparent;
background: transparent;
}
.fancy-datepicker-core .day-table td.weekend {
color: #666666;
border-color: #e6e6e6;
}
.fancy-datepicker-core .day-table td.day-name {
background: transparent;
border: 1px transparent;
vertical-align: bottom;
color: #888888;
}
.fancy-datepicker-core .day-table td.today {
background: #eeee99;
border-color: #aaaa66;
}
.fancy-datepicker-core .day-table td.datepicker-selected {
background: #0099ff;
border-color: #0066cc;
}
.fancy-datepicker-core td {
cursor: pointer;
}
.fancy-datepicker-core td.novalue {
cursor: inherit;
}
.picker-open .calendar-button,
.fancy-datepicker-core {
background-color: white;
border: 1px solid #777777;
box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.25);
}
.picker-open .calendar-button {
border-left: 1px solid white;
}
diff --git a/webroot/rsrc/js/application/core/behavior-fancy-datepicker.js b/webroot/rsrc/js/application/core/behavior-fancy-datepicker.js
index 000fe17f95..2eb1eb90fe 100644
--- a/webroot/rsrc/js/application/core/behavior-fancy-datepicker.js
+++ b/webroot/rsrc/js/application/core/behavior-fancy-datepicker.js
@@ -1,228 +1,244 @@
/**
* @provides javelin-behavior-fancy-datepicker
* @requires javelin-behavior
* javelin-util
* javelin-dom
+ * javelin-stratcom
+ * javelin-vector
*/
JX.behavior('fancy-datepicker', function(config) {
var picker;
+ var button;
+ var root;
var value_y;
var value_m;
var value_d;
var onopen = function(e) {
e.kill();
// If you click the calendar icon while the date picker is open, close it
// without writing the change.
if (picker) {
- onclose(e);
- return;
+ if (root == e.getNode('phabricator-date-control')) {
+ // If the user clicked the same control, just close it.
+ onclose(e);
+ return;
+ } else {
+ // If the user clicked a different control, close the old one but then
+ // open the new one.
+ onclose(e);
+ }
}
+
+ root = e.getNode('phabricator-date-control');
+
picker = JX.$N(
'div',
- {className: 'fancy-datepicker'},
+ {className: 'fancy-datepicker', sigil: 'phabricator-datepicker'},
JX.$N('div', {className: 'fancy-datepicker-core'}));
- root.appendChild(picker);
+ document.body.appendChild(picker);
+
+ var button = e.getNode('calendar-button');
+ var p = JX.$V(button);
+ var d = JX.Vector.getDim(picker);
+
+ picker.style.left = (p.x - d.x + 2) + 'px';
+ picker.style.top = (p.y - 10) + 'px';
JX.DOM.alterClass(root, 'picker-open', true);
read_date();
render();
};
var onclose = function(e) {
if (!picker) {
return;
}
JX.DOM.remove(picker);
picker = null;
JX.DOM.alterClass(root, 'picker-open', false);
e.kill();
+
+ root = null;
};
var get_inputs = function() {
return {
y: JX.DOM.find(root, 'select', 'year-input'),
m: JX.DOM.find(root, 'select', 'month-input'),
d: JX.DOM.find(root, 'select', 'day-input')
};
}
var read_date = function() {
var i = get_inputs();
value_y = i.y.value;
value_m = i.m.value;
value_d = i.d.value;
};
var write_date = function() {
var i = get_inputs();
i.y.value = value_y;
i.m.value = value_m;
i.d.value = value_d;
};
var render = function() {
JX.DOM.setContent(
picker.firstChild,
[
render_month(),
render_day(),
]);
};
// Render a cell for the date picker.
var cell = function(label, value, selected, class_name) {
class_name = class_name || '';
if (selected) {
class_name += ' datepicker-selected';
}
if (!value) {
class_name += ' novalue';
}
return JX.$N('td', {meta: {value: value}, className: class_name}, label);
}
// Render the top bar which allows you to pick a month and year.
var render_month = function() {
var months = [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December'];
var buttons = [
cell("\u25C0", 'm:-1', false, 'lrbutton'),
cell(months[value_m - 1] + ' ' + value_y, null),
cell("\u25B6", 'm:1', false, 'lrbutton')];
return JX.$N(
'table',
{className: 'month-table'},
JX.$N('tr', {}, buttons));
};
// Render the day-of-week and calendar views.
var render_day = function() {
var weeks = [];
// First, render the weekday names.
var weekdays = 'SMTWTFS';
var weekday_names = [];
for (var ii = 0; ii < weekdays.length; ii++) {
weekday_names.push(cell(weekdays.charAt(ii), null, false, 'day-name'));
}
weeks.push(JX.$N('tr', {}, weekday_names));
// Render the calendar itself. NOTE: Javascript uses 0-based month indexes
// while we use 1-based month indexes, so we have to adjust for that.
var days = [];
var start = new Date(value_y, value_m - 1, 1).getDay();
while (start--) {
days.push(cell('', null, false, 'day-placeholder'));
}
var today = new Date();
for (var ii = 1; ii <= 31; ii++) {
var date = new Date(value_y, value_m - 1, ii);
if (date.getMonth() != (value_m - 1)) {
// We've spilled over into the next month, so stop rendering.
break;
}
var is_today = (today.getYear() == date.getYear() &&
today.getMonth() == date.getMonth() &&
today.getDate() == date.getDate());
var classes = [];
if (is_today) {
classes.push('today');
}
if (date.getDay() == 0 || date.getDay() == 6) {
classes.push('weekend');
}
days.push(cell(ii, 'd:'+ii, value_d == ii, classes.join(' ')));
}
// Slice the days into weeks.
for (var ii = 0; ii < days.length; ii += 7) {
weeks.push(JX.$N('tr', {}, days.slice(ii, ii + 7)));
}
return JX.$N('table', {className: 'day-table'}, weeks);
};
- var root = JX.$(config.root);
-
- JX.DOM.listen(
- root,
- 'click',
- 'calendar-button',
- onopen);
+ JX.Stratcom.listen('click', 'calendar-button', onopen);
- JX.DOM.listen(
- root,
+ JX.Stratcom.listen(
'click',
- 'tag:td',
+ ['phabricator-datepicker', 'tag:td'],
function(e) {
e.kill();
var data = e.getNodeData('tag:td');
if (!data.value) {
return;
}
var p = data.value.split(':');
switch (p[0]) {
case 'm':
// User clicked left or right month selection buttons.
value_m = value_m - 1;
value_m = value_m + parseInt(p[1]);
if (value_m >= 12) {
value_m -= 12;
value_y += 1;
} else if (value_m < 0) {
value_m += 12;
value_y -= 1;
}
value_m = value_m + 1;
break;
case 'd':
// User clicked a day.
value_d = parseInt(p[1]);
write_date();
// Wait a moment to close the selector so they can see the effect
// of their action.
setTimeout(JX.bind(null, onclose, e), 150);
break;
}
render();
});
});

File Metadata

Mime Type
text/x-diff
Expires
Sun, Jan 19, 13:36 (3 w, 3 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1125124
Default Alt Text
(23 KB)

Event Timeline