1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-10 08:52:39 +01:00

Add a datepicker control

Summary: I looooove JS! It makes me giddy with glee!

Test Plan: Picked dates. See screenshots.

Reviewers: btrahan

Reviewed By: btrahan

CC: aran

Differential Revision: https://secure.phabricator.com/D2086
This commit is contained in:
epriestley 2012-04-04 12:14:10 -07:00
parent 84398fc581
commit 01767c482d
11 changed files with 679 additions and 23 deletions

View file

@ -72,7 +72,7 @@ celerity_register_resource_map(array(
),
'aphront-form-view-css' =>
array(
'uri' => '/res/38bc1599/rsrc/css/aphront/form-view.css',
'uri' => '/res/aec38c95/rsrc/css/aphront/form-view.css',
'type' => 'css',
'requires' =>
array(
@ -699,6 +699,18 @@ celerity_register_resource_map(array(
),
'disk' => '/rsrc/js/application/core/behavior-error-log.js',
),
'javelin-behavior-fancy-datepicker' =>
array(
'uri' => '/res/b2d89e4c/rsrc/js/application/core/behavior-fancy-datepicker.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-util',
2 => 'javelin-dom',
),
'disk' => '/rsrc/js/application/core/behavior-fancy-datepicker.js',
),
'javelin-behavior-files-drag-and-drop' =>
array(
'uri' => '/res/3a7a2a8a/rsrc/js/application/core/behavior-files-drag-and-drop.js',
@ -2022,7 +2034,7 @@ celerity_register_resource_map(array(
), array(
'packages' =>
array(
'943d4357' =>
'803864a4' =>
array(
'name' => 'core.pkg.css',
'symbols' =>
@ -2047,7 +2059,7 @@ celerity_register_resource_map(array(
17 => 'aphront-pager-view-css',
18 => 'phabricator-transaction-view-css',
),
'uri' => '/res/pkg/943d4357/core.pkg.css',
'uri' => '/res/pkg/803864a4/core.pkg.css',
'type' => 'css',
),
'21d01ed8' =>
@ -2194,17 +2206,17 @@ celerity_register_resource_map(array(
'reverse' =>
array(
'aphront-attached-file-view-css' => 'f45e0b15',
'aphront-crumbs-view-css' => '943d4357',
'aphront-dialog-view-css' => '943d4357',
'aphront-form-view-css' => '943d4357',
'aphront-crumbs-view-css' => '803864a4',
'aphront-dialog-view-css' => '803864a4',
'aphront-form-view-css' => '803864a4',
'aphront-headsup-action-list-view-css' => '18be02e0',
'aphront-list-filter-view-css' => '943d4357',
'aphront-pager-view-css' => '943d4357',
'aphront-panel-view-css' => '943d4357',
'aphront-side-nav-view-css' => '943d4357',
'aphront-table-view-css' => '943d4357',
'aphront-tokenizer-control-css' => '943d4357',
'aphront-typeahead-control-css' => '943d4357',
'aphront-list-filter-view-css' => '803864a4',
'aphront-pager-view-css' => '803864a4',
'aphront-panel-view-css' => '803864a4',
'aphront-side-nav-view-css' => '803864a4',
'aphront-table-view-css' => '803864a4',
'aphront-tokenizer-control-css' => '803864a4',
'aphront-typeahead-control-css' => '803864a4',
'differential-changeset-view-css' => '18be02e0',
'differential-core-view-css' => '18be02e0',
'differential-inline-comment-editor' => 'b2139675',
@ -2262,23 +2274,23 @@ celerity_register_resource_map(array(
'maniphest-task-detail-css' => 'f45e0b15',
'maniphest-task-summary-css' => 'f45e0b15',
'maniphest-transaction-detail-css' => 'f45e0b15',
'phabricator-app-buttons-css' => '943d4357',
'phabricator-app-buttons-css' => '803864a4',
'phabricator-content-source-view-css' => '18be02e0',
'phabricator-core-buttons-css' => '943d4357',
'phabricator-core-css' => '943d4357',
'phabricator-directory-css' => '943d4357',
'phabricator-core-buttons-css' => '803864a4',
'phabricator-core-css' => '803864a4',
'phabricator-directory-css' => '803864a4',
'phabricator-drag-and-drop-file-upload' => 'b2139675',
'phabricator-dropdown-menu' => '21d01ed8',
'phabricator-jump-nav' => '943d4357',
'phabricator-jump-nav' => '803864a4',
'phabricator-keyboard-shortcut' => '21d01ed8',
'phabricator-keyboard-shortcut-manager' => '21d01ed8',
'phabricator-menu-item' => '21d01ed8',
'phabricator-object-selector-css' => '18be02e0',
'phabricator-paste-file-upload' => '21d01ed8',
'phabricator-remarkup-css' => '943d4357',
'phabricator-remarkup-css' => '803864a4',
'phabricator-shaped-request' => 'b2139675',
'phabricator-standard-page-view' => '943d4357',
'phabricator-transaction-view-css' => '943d4357',
'syntax-highlighting-css' => '943d4357',
'phabricator-standard-page-view' => '803864a4',
'phabricator-transaction-view-css' => '803864a4',
'syntax-highlighting-css' => '803864a4',
),
));

View file

@ -31,6 +31,7 @@ phutil_register_library_map(array(
'AphrontFileResponse' => 'aphront/response/file',
'AphrontFormCheckboxControl' => 'view/form/control/checkbox',
'AphrontFormControl' => 'view/form/control/base',
'AphrontFormDateControl' => 'view/form/control/date',
'AphrontFormDividerControl' => 'view/form/control/divider',
'AphrontFormDragAndDropUploadControl' => 'view/form/control/draganddropupload',
'AphrontFormFileControl' => 'view/form/control/file',
@ -610,6 +611,7 @@ phutil_register_library_map(array(
'PhabricatorFlagListController' => 'applications/flag/controller/list',
'PhabricatorFlagListView' => 'applications/flag/view/list',
'PhabricatorFlagQuery' => 'applications/flag/query/flag',
'PhabricatorFormExample' => 'applications/uiexample/examples/form',
'PhabricatorGarbageCollectorDaemon' => 'infrastructure/daemon/garbagecollector',
'PhabricatorGoodForNothingWorker' => 'infrastructure/daemon/workers/worker/goodfornothing',
'PhabricatorHandleObjectSelectorDataView' => 'applications/phid/handle/view/selector',
@ -976,6 +978,7 @@ phutil_register_library_map(array(
'AphrontFileResponse' => 'AphrontResponse',
'AphrontFormCheckboxControl' => 'AphrontFormControl',
'AphrontFormControl' => 'AphrontView',
'AphrontFormDateControl' => 'AphrontFormControl',
'AphrontFormDividerControl' => 'AphrontFormControl',
'AphrontFormDragAndDropUploadControl' => 'AphrontFormControl',
'AphrontFormFileControl' => 'AphrontFormControl',
@ -1447,6 +1450,7 @@ phutil_register_library_map(array(
'PhabricatorFlagEditController' => 'PhabricatorFlagController',
'PhabricatorFlagListController' => 'PhabricatorFlagController',
'PhabricatorFlagListView' => 'AphrontView',
'PhabricatorFormExample' => 'PhabricatorUIExample',
'PhabricatorGarbageCollectorDaemon' => 'PhabricatorDaemon',
'PhabricatorGoodForNothingWorker' => 'PhabricatorWorker',
'PhabricatorHelpController' => 'PhabricatorController',

View file

@ -0,0 +1,53 @@
<?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())
->setUser($user)
->setName('date')
->setLabel('Date');
$date->readValueFromRequest($request);
$form = id(new AphrontFormView())
->setUser($user)
->appendChild($date)
->appendChild(
id(new AphrontFormSubmitControl())
->setValue('Submit'));
$panel = new AphrontPanelView();
$panel->setHeader('Form');
$panel->appendChild($form);
return $panel;
}
}

View file

@ -0,0 +1,18 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'applications/uiexample/examples/base');
phutil_require_module('phabricator', 'view/form/base');
phutil_require_module('phabricator', 'view/form/control/date');
phutil_require_module('phabricator', 'view/form/control/submit');
phutil_require_module('phabricator', 'view/layout/panel');
phutil_require_module('phutil', 'utils');
phutil_require_source('PhabricatorFormExample.php');

View file

@ -0,0 +1,201 @@
<?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;
public function setUser($user) {
$this->user = $user;
return $this;
}
public function readValueFromRequest(AphrontRequest $request) {
$day = $request->getInt($this->getDayInputName());
$month = $request->getInt($this->getMonthInputName());
$year = $request->getInt($this->getYearInputName());
$err = $this->getError();
if ($day || $month || $year) {
// 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;
}
} catch (Exception $ex) {
// Ignore, already handled.
}
}
$this->setError($err);
return $err;
}
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';
}
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);
}
private function getMonthInputValue() {
return (int)idx(explode('-', $this->getValue()), 1);
}
private function getYearInputValue() {
return (int)idx(explode('-', $this->getValue()), 0);
}
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';
}
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',
array(
'root' => $id,
));
return javelin_render_tag(
'div',
array(
'id' => $id,
'class' => 'aphront-form-date-container',
),
self::renderSingleView(
array(
$days_sel,
$months_sel,
$years_sel,
$cal_icon,
)));
}
}

View file

@ -0,0 +1,19 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'infrastructure/celerity/api');
phutil_require_module('phabricator', 'infrastructure/javelin/api');
phutil_require_module('phabricator', 'infrastructure/javelin/markup');
phutil_require_module('phabricator', 'view/form/control/base');
phutil_require_module('phabricator', 'view/form/control/select');
phutil_require_module('phabricator', 'view/utils');
phutil_require_module('phutil', 'utils');
phutil_require_source('AphrontFormDateControl.php');

View file

@ -60,7 +60,7 @@ final class AphrontFormSelectControl extends AphrontFormControl {
phutil_escape_html($label));
}
return phutil_render_tag(
return javelin_render_tag(
'select',
$attrs,
implode("\n", $option_tags));

View file

@ -6,6 +6,7 @@
phutil_require_module('phabricator', 'infrastructure/javelin/markup');
phutil_require_module('phabricator', 'view/form/control/base');
phutil_require_module('phutil', 'markup');

View file

@ -167,3 +167,123 @@ table.aphront-form-control-checkbox-layout th {
background: #99ff99;
border-color: #669966;
}
.calendar-button {
padding: 11px;
right: -30px;
top: -3px;
background: url(/rsrc/image/icon/fatcow/calendar_edit.png)
no-repeat center center;
z-index: 2;
position: absolute;
border: 1px solid transparent;
}
.aphront-form-date-container {
position: relative;
display: inline;
}
.aphront-form-date-container select{
margin: 2px;
}
.fancy-datepicker {
position: absolute;
top: -10px;
right: -8px;
width: 240px;
padding-bottom: 6em;
}
.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;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 794 B

View file

@ -0,0 +1,228 @@
/**
* @provides javelin-behavior-fancy-datepicker
* @requires javelin-behavior
* javelin-util
* javelin-dom
*/
JX.behavior('fancy-datepicker', function(config) {
var picker;
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;
}
picker = JX.$N(
'div',
{className: 'fancy-datepicker'},
JX.$N('div', {className: 'fancy-datepicker-core'}));
root.appendChild(picker);
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();
};
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.DOM.listen(
root,
'click',
'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();
});
});