Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2890453
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Advanced/Developer...
View Handle
View Hovercard
Size
23 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
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)
Attached To
Mode
rP Phorge
Attached
Detach File
Event Timeline
Log In to Comment