@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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

+679 -23
+34 -22
src/__celerity_resource_map__.php
··· 72 72 ), 73 73 'aphront-form-view-css' => 74 74 array( 75 - 'uri' => '/res/38bc1599/rsrc/css/aphront/form-view.css', 75 + 'uri' => '/res/aec38c95/rsrc/css/aphront/form-view.css', 76 76 'type' => 'css', 77 77 'requires' => 78 78 array( ··· 698 698 0 => 'javelin-dom', 699 699 ), 700 700 'disk' => '/rsrc/js/application/core/behavior-error-log.js', 701 + ), 702 + 'javelin-behavior-fancy-datepicker' => 703 + array( 704 + 'uri' => '/res/b2d89e4c/rsrc/js/application/core/behavior-fancy-datepicker.js', 705 + 'type' => 'js', 706 + 'requires' => 707 + array( 708 + 0 => 'javelin-behavior', 709 + 1 => 'javelin-util', 710 + 2 => 'javelin-dom', 711 + ), 712 + 'disk' => '/rsrc/js/application/core/behavior-fancy-datepicker.js', 701 713 ), 702 714 'javelin-behavior-files-drag-and-drop' => 703 715 array( ··· 2022 2034 ), array( 2023 2035 'packages' => 2024 2036 array( 2025 - '943d4357' => 2037 + '803864a4' => 2026 2038 array( 2027 2039 'name' => 'core.pkg.css', 2028 2040 'symbols' => ··· 2047 2059 17 => 'aphront-pager-view-css', 2048 2060 18 => 'phabricator-transaction-view-css', 2049 2061 ), 2050 - 'uri' => '/res/pkg/943d4357/core.pkg.css', 2062 + 'uri' => '/res/pkg/803864a4/core.pkg.css', 2051 2063 'type' => 'css', 2052 2064 ), 2053 2065 '21d01ed8' => ··· 2194 2206 'reverse' => 2195 2207 array( 2196 2208 'aphront-attached-file-view-css' => 'f45e0b15', 2197 - 'aphront-crumbs-view-css' => '943d4357', 2198 - 'aphront-dialog-view-css' => '943d4357', 2199 - 'aphront-form-view-css' => '943d4357', 2209 + 'aphront-crumbs-view-css' => '803864a4', 2210 + 'aphront-dialog-view-css' => '803864a4', 2211 + 'aphront-form-view-css' => '803864a4', 2200 2212 'aphront-headsup-action-list-view-css' => '18be02e0', 2201 - 'aphront-list-filter-view-css' => '943d4357', 2202 - 'aphront-pager-view-css' => '943d4357', 2203 - 'aphront-panel-view-css' => '943d4357', 2204 - 'aphront-side-nav-view-css' => '943d4357', 2205 - 'aphront-table-view-css' => '943d4357', 2206 - 'aphront-tokenizer-control-css' => '943d4357', 2207 - 'aphront-typeahead-control-css' => '943d4357', 2213 + 'aphront-list-filter-view-css' => '803864a4', 2214 + 'aphront-pager-view-css' => '803864a4', 2215 + 'aphront-panel-view-css' => '803864a4', 2216 + 'aphront-side-nav-view-css' => '803864a4', 2217 + 'aphront-table-view-css' => '803864a4', 2218 + 'aphront-tokenizer-control-css' => '803864a4', 2219 + 'aphront-typeahead-control-css' => '803864a4', 2208 2220 'differential-changeset-view-css' => '18be02e0', 2209 2221 'differential-core-view-css' => '18be02e0', 2210 2222 'differential-inline-comment-editor' => 'b2139675', ··· 2262 2274 'maniphest-task-detail-css' => 'f45e0b15', 2263 2275 'maniphest-task-summary-css' => 'f45e0b15', 2264 2276 'maniphest-transaction-detail-css' => 'f45e0b15', 2265 - 'phabricator-app-buttons-css' => '943d4357', 2277 + 'phabricator-app-buttons-css' => '803864a4', 2266 2278 'phabricator-content-source-view-css' => '18be02e0', 2267 - 'phabricator-core-buttons-css' => '943d4357', 2268 - 'phabricator-core-css' => '943d4357', 2269 - 'phabricator-directory-css' => '943d4357', 2279 + 'phabricator-core-buttons-css' => '803864a4', 2280 + 'phabricator-core-css' => '803864a4', 2281 + 'phabricator-directory-css' => '803864a4', 2270 2282 'phabricator-drag-and-drop-file-upload' => 'b2139675', 2271 2283 'phabricator-dropdown-menu' => '21d01ed8', 2272 - 'phabricator-jump-nav' => '943d4357', 2284 + 'phabricator-jump-nav' => '803864a4', 2273 2285 'phabricator-keyboard-shortcut' => '21d01ed8', 2274 2286 'phabricator-keyboard-shortcut-manager' => '21d01ed8', 2275 2287 'phabricator-menu-item' => '21d01ed8', 2276 2288 'phabricator-object-selector-css' => '18be02e0', 2277 2289 'phabricator-paste-file-upload' => '21d01ed8', 2278 - 'phabricator-remarkup-css' => '943d4357', 2290 + 'phabricator-remarkup-css' => '803864a4', 2279 2291 'phabricator-shaped-request' => 'b2139675', 2280 - 'phabricator-standard-page-view' => '943d4357', 2281 - 'phabricator-transaction-view-css' => '943d4357', 2282 - 'syntax-highlighting-css' => '943d4357', 2292 + 'phabricator-standard-page-view' => '803864a4', 2293 + 'phabricator-transaction-view-css' => '803864a4', 2294 + 'syntax-highlighting-css' => '803864a4', 2283 2295 ), 2284 2296 ));
+4
src/__phutil_library_map__.php
··· 31 31 'AphrontFileResponse' => 'aphront/response/file', 32 32 'AphrontFormCheckboxControl' => 'view/form/control/checkbox', 33 33 'AphrontFormControl' => 'view/form/control/base', 34 + 'AphrontFormDateControl' => 'view/form/control/date', 34 35 'AphrontFormDividerControl' => 'view/form/control/divider', 35 36 'AphrontFormDragAndDropUploadControl' => 'view/form/control/draganddropupload', 36 37 'AphrontFormFileControl' => 'view/form/control/file', ··· 610 611 'PhabricatorFlagListController' => 'applications/flag/controller/list', 611 612 'PhabricatorFlagListView' => 'applications/flag/view/list', 612 613 'PhabricatorFlagQuery' => 'applications/flag/query/flag', 614 + 'PhabricatorFormExample' => 'applications/uiexample/examples/form', 613 615 'PhabricatorGarbageCollectorDaemon' => 'infrastructure/daemon/garbagecollector', 614 616 'PhabricatorGoodForNothingWorker' => 'infrastructure/daemon/workers/worker/goodfornothing', 615 617 'PhabricatorHandleObjectSelectorDataView' => 'applications/phid/handle/view/selector', ··· 976 978 'AphrontFileResponse' => 'AphrontResponse', 977 979 'AphrontFormCheckboxControl' => 'AphrontFormControl', 978 980 'AphrontFormControl' => 'AphrontView', 981 + 'AphrontFormDateControl' => 'AphrontFormControl', 979 982 'AphrontFormDividerControl' => 'AphrontFormControl', 980 983 'AphrontFormDragAndDropUploadControl' => 'AphrontFormControl', 981 984 'AphrontFormFileControl' => 'AphrontFormControl', ··· 1447 1450 'PhabricatorFlagEditController' => 'PhabricatorFlagController', 1448 1451 'PhabricatorFlagListController' => 'PhabricatorFlagController', 1449 1452 'PhabricatorFlagListView' => 'AphrontView', 1453 + 'PhabricatorFormExample' => 'PhabricatorUIExample', 1450 1454 'PhabricatorGarbageCollectorDaemon' => 'PhabricatorDaemon', 1451 1455 'PhabricatorGoodForNothingWorker' => 'PhabricatorWorker', 1452 1456 'PhabricatorHelpController' => 'PhabricatorController',
+53
src/applications/uiexample/examples/form/PhabricatorFormExample.php
··· 1 + <?php 2 + 3 + /* 4 + * Copyright 2012 Facebook, Inc. 5 + * 6 + * Licensed under the Apache License, Version 2.0 (the "License"); 7 + * you may not use this file except in compliance with the License. 8 + * You may obtain a copy of the License at 9 + * 10 + * http://www.apache.org/licenses/LICENSE-2.0 11 + * 12 + * Unless required by applicable law or agreed to in writing, software 13 + * distributed under the License is distributed on an "AS IS" BASIS, 14 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 + * See the License for the specific language governing permissions and 16 + * limitations under the License. 17 + */ 18 + 19 + final class PhabricatorFormExample extends PhabricatorUIExample { 20 + 21 + public function getName() { 22 + return 'Form'; 23 + } 24 + 25 + public function getDescription() { 26 + return 'Use <tt>AphrontFormView</tt> to render forms.'; 27 + } 28 + 29 + public function renderExample() { 30 + $request = $this->getRequest(); 31 + $user = $request->getUser(); 32 + 33 + $date = id(new AphrontFormDateControl()) 34 + ->setUser($user) 35 + ->setName('date') 36 + ->setLabel('Date'); 37 + 38 + $date->readValueFromRequest($request); 39 + 40 + $form = id(new AphrontFormView()) 41 + ->setUser($user) 42 + ->appendChild($date) 43 + ->appendChild( 44 + id(new AphrontFormSubmitControl()) 45 + ->setValue('Submit')); 46 + 47 + $panel = new AphrontPanelView(); 48 + $panel->setHeader('Form'); 49 + $panel->appendChild($form); 50 + 51 + return $panel; 52 + } 53 + }
+18
src/applications/uiexample/examples/form/__init__.php
··· 1 + <?php 2 + /** 3 + * This file is automatically generated. Lint this module to rebuild it. 4 + * @generated 5 + */ 6 + 7 + 8 + 9 + phutil_require_module('phabricator', 'applications/uiexample/examples/base'); 10 + phutil_require_module('phabricator', 'view/form/base'); 11 + phutil_require_module('phabricator', 'view/form/control/date'); 12 + phutil_require_module('phabricator', 'view/form/control/submit'); 13 + phutil_require_module('phabricator', 'view/layout/panel'); 14 + 15 + phutil_require_module('phutil', 'utils'); 16 + 17 + 18 + phutil_require_source('PhabricatorFormExample.php');
+201
src/view/form/control/date/AphrontFormDateControl.php
··· 1 + <?php 2 + 3 + /* 4 + * Copyright 2012 Facebook, Inc. 5 + * 6 + * Licensed under the Apache License, Version 2.0 (the "License"); 7 + * you may not use this file except in compliance with the License. 8 + * You may obtain a copy of the License at 9 + * 10 + * http://www.apache.org/licenses/LICENSE-2.0 11 + * 12 + * Unless required by applicable law or agreed to in writing, software 13 + * distributed under the License is distributed on an "AS IS" BASIS, 14 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 + * See the License for the specific language governing permissions and 16 + * limitations under the License. 17 + */ 18 + 19 + final class AphrontFormDateControl extends AphrontFormControl { 20 + 21 + private $user; 22 + 23 + public function setUser($user) { 24 + $this->user = $user; 25 + return $this; 26 + } 27 + 28 + public function readValueFromRequest(AphrontRequest $request) { 29 + 30 + $day = $request->getInt($this->getDayInputName()); 31 + $month = $request->getInt($this->getMonthInputName()); 32 + $year = $request->getInt($this->getYearInputName()); 33 + 34 + $err = $this->getError(); 35 + 36 + if ($day || $month || $year) { 37 + 38 + // Assume invalid. 39 + $err = 'Invalid'; 40 + 41 + $tz = new DateTimeZone('UTC'); 42 + try { 43 + $date = new DateTime("{$year}-{$month}-{$day} 12:00:00 AM", $tz); 44 + $value = $date->format('Y-m-d'); 45 + if ($value) { 46 + $this->setValue($value); 47 + $err = null; 48 + } 49 + } catch (Exception $ex) { 50 + // Ignore, already handled. 51 + } 52 + } 53 + 54 + $this->setError($err); 55 + 56 + return $err; 57 + } 58 + 59 + public function getValue() { 60 + if (!parent::getValue()) { 61 + $this->setValue($this->formatTime(time(), 'Y-m-d')); 62 + } 63 + return parent::getValue(); 64 + } 65 + 66 + 67 + protected function getCustomControlClass() { 68 + return 'aphront-form-control-date'; 69 + } 70 + 71 + private function getMinYear() { 72 + $cur_year = $this->formatTime( 73 + time(), 74 + 'Y'); 75 + $val_year = $this->getYearInputValue(); 76 + 77 + return min($cur_year, $val_year) - 3; 78 + } 79 + 80 + private function getMaxYear() { 81 + $cur_year = $this->formatTime( 82 + time(), 83 + 'Y'); 84 + $val_year = $this->getYearInputValue(); 85 + 86 + return max($cur_year, $val_year) + 3; 87 + } 88 + 89 + private function getDayInputValue() { 90 + return (int)idx(explode('-', $this->getValue()), 2); 91 + } 92 + 93 + private function getMonthInputValue() { 94 + return (int)idx(explode('-', $this->getValue()), 1); 95 + } 96 + 97 + private function getYearInputValue() { 98 + return (int)idx(explode('-', $this->getValue()), 0); 99 + } 100 + 101 + private function formatTime($epoch, $fmt) { 102 + return phabricator_format_local_time( 103 + $epoch, 104 + $this->user, 105 + $fmt); 106 + } 107 + 108 + private function getDayInputName() { 109 + return $this->getName().'_d'; 110 + } 111 + 112 + private function getMonthInputName() { 113 + return $this->getName().'_m'; 114 + } 115 + 116 + private function getYearInputName() { 117 + return $this->getName().'_y'; 118 + } 119 + 120 + protected function renderInput() { 121 + $min_year = $this->getMinYear(); 122 + $max_year = $this->getMaxYear(); 123 + 124 + $days = range(1, 31); 125 + $days = array_combine($days, $days); 126 + 127 + $months = array( 128 + 1 => 'Jan', 129 + 2 => 'Feb', 130 + 3 => 'Mar', 131 + 4 => 'Apr', 132 + 5 => 'May', 133 + 6 => 'Jun', 134 + 7 => 'Jul', 135 + 8 => 'Aug', 136 + 9 => 'Sep', 137 + 10 => 'Oct', 138 + 11 => 'Nov', 139 + 12 => 'Dec', 140 + ); 141 + 142 + $years = range($this->getMinYear(), $this->getMaxYear()); 143 + $years = array_combine($years, $years); 144 + 145 + $days_sel = AphrontFormSelectControl::renderSelectTag( 146 + $this->getDayInputValue(), 147 + $days, 148 + array( 149 + 'name' => $this->getDayInputName(), 150 + 'sigil' => 'day-input', 151 + )); 152 + 153 + $months_sel = AphrontFormSelectControl::renderSelectTag( 154 + $this->getMonthInputValue(), 155 + $months, 156 + array( 157 + 'name' => $this->getMonthInputName(), 158 + 'sigil' => 'month-input', 159 + )); 160 + 161 + $years_sel = AphrontFormSelectControl::renderSelectTag( 162 + $this->getYearInputValue(), 163 + $years, 164 + array( 165 + 'name' => $this->getYearInputName(), 166 + 'sigil' => 'year-input', 167 + )); 168 + 169 + $cal_icon = javelin_render_tag( 170 + 'a', 171 + array( 172 + 'href' => '#', 173 + 'class' => 'calendar-button', 174 + 'sigil' => 'calendar-button', 175 + ), 176 + ''); 177 + 178 + $id = celerity_generate_unique_node_id(); 179 + 180 + Javelin::initBehavior( 181 + 'fancy-datepicker', 182 + array( 183 + 'root' => $id, 184 + )); 185 + 186 + return javelin_render_tag( 187 + 'div', 188 + array( 189 + 'id' => $id, 190 + 'class' => 'aphront-form-date-container', 191 + ), 192 + self::renderSingleView( 193 + array( 194 + $days_sel, 195 + $months_sel, 196 + $years_sel, 197 + $cal_icon, 198 + ))); 199 + } 200 + 201 + }
+19
src/view/form/control/date/__init__.php
··· 1 + <?php 2 + /** 3 + * This file is automatically generated. Lint this module to rebuild it. 4 + * @generated 5 + */ 6 + 7 + 8 + 9 + phutil_require_module('phabricator', 'infrastructure/celerity/api'); 10 + phutil_require_module('phabricator', 'infrastructure/javelin/api'); 11 + phutil_require_module('phabricator', 'infrastructure/javelin/markup'); 12 + phutil_require_module('phabricator', 'view/form/control/base'); 13 + phutil_require_module('phabricator', 'view/form/control/select'); 14 + phutil_require_module('phabricator', 'view/utils'); 15 + 16 + phutil_require_module('phutil', 'utils'); 17 + 18 + 19 + phutil_require_source('AphrontFormDateControl.php');
+1 -1
src/view/form/control/select/AphrontFormSelectControl.php
··· 60 60 phutil_escape_html($label)); 61 61 } 62 62 63 - return phutil_render_tag( 63 + return javelin_render_tag( 64 64 'select', 65 65 $attrs, 66 66 implode("\n", $option_tags));
+1
src/view/form/control/select/__init__.php
··· 6 6 7 7 8 8 9 + phutil_require_module('phabricator', 'infrastructure/javelin/markup'); 9 10 phutil_require_module('phabricator', 'view/form/control/base'); 10 11 11 12 phutil_require_module('phutil', 'markup');
+120
webroot/rsrc/css/aphront/form-view.css
··· 167 167 background: #99ff99; 168 168 border-color: #669966; 169 169 } 170 + 171 + .calendar-button { 172 + padding: 11px; 173 + right: -30px; 174 + top: -3px; 175 + 176 + background: url(/rsrc/image/icon/fatcow/calendar_edit.png) 177 + no-repeat center center; 178 + z-index: 2; 179 + position: absolute; 180 + border: 1px solid transparent; 181 + } 182 + 183 + .aphront-form-date-container { 184 + position: relative; 185 + display: inline; 186 + } 187 + 188 + .aphront-form-date-container select{ 189 + margin: 2px; 190 + } 191 + 192 + .fancy-datepicker { 193 + position: absolute; 194 + top: -10px; 195 + right: -8px; 196 + width: 240px; 197 + padding-bottom: 6em; 198 + } 199 + 200 + .fancy-datepicker-core { 201 + padding: 1px; 202 + font-size: 11px; 203 + font-family: Verdana; 204 + text-align: center; 205 + } 206 + 207 + .fancy-datepicker-core .month-table, 208 + .fancy-datepicker-core .day-table { 209 + margin: 0 auto; 210 + border-collapse: separate; 211 + border-spacing: 1px; 212 + width: 100%; 213 + } 214 + 215 + .fancy-datepicker-core .month-table { 216 + margin-bottom: 6px; 217 + } 218 + 219 + .fancy-datepicker-core .month-table td.lrbutton { 220 + width: 20%; 221 + } 222 + 223 + .fancy-datepicker-core .month-table td { 224 + padding: 4px; 225 + font-weight: bold; 226 + color: #444444; 227 + } 228 + 229 + .fancy-datepicker-core .month-table td.lrbutton { 230 + background: #e6e6e6; 231 + border: 1px solid; 232 + border-color: #a6a6a6 #969696 #868686 #a6a6a6; 233 + } 234 + 235 + .fancy-datepicker-core .day-table td { 236 + overflow: hidden; 237 + background: #f6f6f6; 238 + vertical-align: center; 239 + text-align: center; 240 + border: 1px solid #d6d6d6; 241 + padding: 4px 0; 242 + } 243 + 244 + .fancy-datepicker-core .day-table td.day-placeholder { 245 + border-color: transparent; 246 + background: transparent; 247 + } 248 + 249 + .fancy-datepicker-core .day-table td.weekend { 250 + color: #666666; 251 + border-color: #e6e6e6; 252 + } 253 + 254 + .fancy-datepicker-core .day-table td.day-name { 255 + background: transparent; 256 + border: 1px transparent; 257 + vertical-align: bottom; 258 + color: #888888; 259 + } 260 + 261 + .fancy-datepicker-core .day-table td.today { 262 + background: #eeee99; 263 + border-color: #aaaa66; 264 + } 265 + 266 + .fancy-datepicker-core .day-table td.datepicker-selected { 267 + background: #0099ff; 268 + border-color: #0066cc; 269 + } 270 + 271 + .fancy-datepicker-core td { 272 + cursor: pointer; 273 + } 274 + 275 + .fancy-datepicker-core td.novalue { 276 + cursor: inherit; 277 + } 278 + 279 + .picker-open .calendar-button, 280 + .fancy-datepicker-core { 281 + background-color: white; 282 + border: 1px solid #777777; 283 + 284 + box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.25); 285 + } 286 + 287 + .picker-open .calendar-button { 288 + border-left: 1px solid white; 289 + }
webroot/rsrc/image/icon/fatcow/calendar_edit.png

This is a binary file and will not be displayed.

+228
webroot/rsrc/js/application/core/behavior-fancy-datepicker.js
··· 1 + /** 2 + * @provides javelin-behavior-fancy-datepicker 3 + * @requires javelin-behavior 4 + * javelin-util 5 + * javelin-dom 6 + */ 7 + 8 + JX.behavior('fancy-datepicker', function(config) { 9 + 10 + var picker; 11 + 12 + var value_y; 13 + var value_m; 14 + var value_d; 15 + 16 + var onopen = function(e) { 17 + e.kill(); 18 + 19 + // If you click the calendar icon while the date picker is open, close it 20 + // without writing the change. 21 + 22 + if (picker) { 23 + onclose(e); 24 + return; 25 + } 26 + 27 + picker = JX.$N( 28 + 'div', 29 + {className: 'fancy-datepicker'}, 30 + JX.$N('div', {className: 'fancy-datepicker-core'})); 31 + root.appendChild(picker); 32 + 33 + JX.DOM.alterClass(root, 'picker-open', true); 34 + 35 + read_date(); 36 + render(); 37 + }; 38 + 39 + var onclose = function(e) { 40 + if (!picker) { 41 + return; 42 + } 43 + 44 + JX.DOM.remove(picker); 45 + picker = null; 46 + JX.DOM.alterClass(root, 'picker-open', false); 47 + e.kill(); 48 + }; 49 + 50 + var get_inputs = function() { 51 + return { 52 + y: JX.DOM.find(root, 'select', 'year-input'), 53 + m: JX.DOM.find(root, 'select', 'month-input'), 54 + d: JX.DOM.find(root, 'select', 'day-input') 55 + }; 56 + } 57 + 58 + var read_date = function() { 59 + var i = get_inputs(); 60 + value_y = i.y.value; 61 + value_m = i.m.value; 62 + value_d = i.d.value; 63 + }; 64 + 65 + var write_date = function() { 66 + var i = get_inputs(); 67 + i.y.value = value_y; 68 + i.m.value = value_m; 69 + i.d.value = value_d; 70 + }; 71 + 72 + var render = function() { 73 + JX.DOM.setContent( 74 + picker.firstChild, 75 + [ 76 + render_month(), 77 + render_day(), 78 + ]); 79 + }; 80 + 81 + // Render a cell for the date picker. 82 + var cell = function(label, value, selected, class_name) { 83 + 84 + class_name = class_name || ''; 85 + 86 + if (selected) { 87 + class_name += ' datepicker-selected'; 88 + } 89 + if (!value) { 90 + class_name += ' novalue'; 91 + } 92 + 93 + return JX.$N('td', {meta: {value: value}, className: class_name}, label); 94 + } 95 + 96 + 97 + // Render the top bar which allows you to pick a month and year. 98 + var render_month = function() { 99 + var months = [ 100 + 'January', 101 + 'February', 102 + 'March', 103 + 'April', 104 + 'May', 105 + 'June', 106 + 'July', 107 + 'August', 108 + 'September', 109 + 'October', 110 + 'November', 111 + 'December']; 112 + 113 + var buttons = [ 114 + cell("\u25C0", 'm:-1', false, 'lrbutton'), 115 + cell(months[value_m - 1] + ' ' + value_y, null), 116 + cell("\u25B6", 'm:1', false, 'lrbutton')]; 117 + 118 + return JX.$N( 119 + 'table', 120 + {className: 'month-table'}, 121 + JX.$N('tr', {}, buttons)); 122 + }; 123 + 124 + 125 + // Render the day-of-week and calendar views. 126 + var render_day = function() { 127 + var weeks = []; 128 + 129 + // First, render the weekday names. 130 + var weekdays = 'SMTWTFS'; 131 + var weekday_names = []; 132 + for (var ii = 0; ii < weekdays.length; ii++) { 133 + weekday_names.push(cell(weekdays.charAt(ii), null, false, 'day-name')); 134 + } 135 + weeks.push(JX.$N('tr', {}, weekday_names)); 136 + 137 + 138 + // Render the calendar itself. NOTE: Javascript uses 0-based month indexes 139 + // while we use 1-based month indexes, so we have to adjust for that. 140 + var days = []; 141 + var start = new Date(value_y, value_m - 1, 1).getDay(); 142 + while (start--) { 143 + days.push(cell('', null, false, 'day-placeholder')); 144 + } 145 + 146 + var today = new Date(); 147 + 148 + for (var ii = 1; ii <= 31; ii++) { 149 + var date = new Date(value_y, value_m - 1, ii); 150 + if (date.getMonth() != (value_m - 1)) { 151 + // We've spilled over into the next month, so stop rendering. 152 + break; 153 + } 154 + 155 + var is_today = (today.getYear() == date.getYear() && 156 + today.getMonth() == date.getMonth() && 157 + today.getDate() == date.getDate()); 158 + 159 + var classes = []; 160 + if (is_today) { 161 + classes.push('today'); 162 + } 163 + if (date.getDay() == 0 || date.getDay() == 6) { 164 + classes.push('weekend'); 165 + } 166 + 167 + days.push(cell(ii, 'd:'+ii, value_d == ii, classes.join(' '))); 168 + } 169 + 170 + // Slice the days into weeks. 171 + for (var ii = 0; ii < days.length; ii += 7) { 172 + weeks.push(JX.$N('tr', {}, days.slice(ii, ii + 7))); 173 + } 174 + 175 + return JX.$N('table', {className: 'day-table'}, weeks); 176 + }; 177 + 178 + 179 + var root = JX.$(config.root); 180 + 181 + JX.DOM.listen( 182 + root, 183 + 'click', 184 + 'calendar-button', 185 + onopen); 186 + 187 + JX.DOM.listen( 188 + root, 189 + 'click', 190 + 'tag:td', 191 + function(e) { 192 + e.kill(); 193 + 194 + var data = e.getNodeData('tag:td'); 195 + if (!data.value) { 196 + return; 197 + } 198 + 199 + var p = data.value.split(':'); 200 + switch (p[0]) { 201 + case 'm': 202 + // User clicked left or right month selection buttons. 203 + value_m = value_m - 1; 204 + value_m = value_m + parseInt(p[1]); 205 + if (value_m >= 12) { 206 + value_m -= 12; 207 + value_y += 1; 208 + } else if (value_m < 0) { 209 + value_m += 12; 210 + value_y -= 1; 211 + } 212 + value_m = value_m + 1; 213 + break; 214 + case 'd': 215 + // User clicked a day. 216 + value_d = parseInt(p[1]); 217 + write_date(); 218 + 219 + // Wait a moment to close the selector so they can see the effect 220 + // of their action. 221 + setTimeout(JX.bind(null, onclose, e), 150); 222 + break; 223 + } 224 + 225 + render(); 226 + }); 227 + 228 + });