diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..30c9f5c --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.tmp* +*~ diff --git a/README.md b/README.md new file mode 100644 index 0000000..47486c1 --- /dev/null +++ b/README.md @@ -0,0 +1,379 @@ +MooTools-DatePicker +=================== + +MooTools DatePicker is a nice modular and themable DatePicker. It has many features and options, such as +year, month, day or timepicker only modes, min and max dates, Localization and a lot more. + +This Plugin makes use of MooTools' Locale and Date plugins in MooTools More, to provide a localized +datepicker, as well as easy formatting and parsing Dates. + +This DatePicker is a fork of the original [MonkeyPhysics DatePicker](http://www.monkeyphysics.com/mootools/script/2/datepicker), +and has improved a lot since then. Though it should be (almost) backward compatible. + +![Screenshot](https://github.com/arian/mootools-datepicker/raw/master/screenshot.png) + +As of version 1.60 the datepicker will only work with MooTools 1.3. + +How to use +---------- + +Below you will find a description and some docs how you can use the datepicker. +If you find any weird things, please create a ticket at github or fork and fix it! + +The DatePicker consists out of three layers, a Picker class, which can be used to create any form of Picker, a Picker.Attach class, +which handles stuff like attaching the Picker to a input or anchor element. Finally there is the DatePicker class, which you'll probably +use. Every option of the Picker or Picker.Attach classes can be used in the DatePicker class. + +Basic Example +------------- + +First you need to include the following html tags: + + #HTML + + + + + + + +Then you can simply use, for example: + + #JS + new Picker.Date($$('input'), { + timePicker: true, + positionOffset: {x: 5, y: 0}, + pickerClass: 'datepicker_dashboard', + useFadeInOut: !Browser.ie + }); + + +### Theming: + +Theming is done with CSS files, there are four themes available, which you can find in the Source folder. + +Just include the CSS file and set the `pickerClass` option. + + +### Localization + +The DatePicker uses the MooTools More Date Class, which already includes many localized strings. +For some specific strings DatePicker has its own localizations, which you can find in the Locale.__-__.DatePicker.js files. +Just include the file in your page with a script tag to use the translations. + +Currently the following languages are supported + +- af-ZA +- cs-CZ +- de-DE +- en-US +- es-ES +- fr-FR +- he-IL +- it-IT +- nl-NL +- pl-PL +- pt-BR +- ru-RU +- sv-SE +- uk-UA + +You can set the current language with: + + #JS + Locale.use('nl-NL'); + + +Class: DatePicker +----------------- + +### Syntax + + #JS + var dp = new DatePicker([element, options]); + +### Arguments + +1. element: (*element*, *string*, *array*) The element(s) to attach the datepicker to +2. options: (*object*, optional) The options object + +### Options: + +All the options of the Picker and Picker.Attach classes, and: + +- minDate: (*Date instance*, *string*, defaults to `null`) Minimum date allowed to pick. Blocks anything before. +- maxDate: (*Date instance*, *string*, defaults to `null`) Maximum date allowed to pick. Blocks anything after. +- availableDates: (*object*, defaults to `null`) When only a few dates should be selectable. An object like `{2011: {1: [19, 29, 31], 3: [5, 19, 24]}}` with all the dates (year -> months -> days). +- invertAvailable: (*boolean*, defaults to `false`) Invert the `availableDates` option. +- format: (*string*, defaults to the default localized format) The format to output into the input field. Uses [Date.format](http://mootools.net/docs/more/Types/Date#Date:format) +- timePicker: (*boolean*, defaults to 1 `false`) Enable/disable timepicker functionality. Hours/Minutes values can be changed using the scrollwheel. +- timeWheelStep: (*number*, defaults to `1`) The number of minutes the minutes field will change in the timepicker when using the scrollwheel, for example 5, 10, 15. The value will always be k * timeWheelStep. +- yearPicker: (*boolean*, defaults to `true`) Enable/disable yearpicker functionality. Makes it much easier to change years. +- yearPerPage: (*number*, defaults to `20`) Amount of years to show in the year-picking view. Be aware that this may affect your layout. +- startView: (*string*, defaults to `days`) The view that will be showed when the picker opens. The options are `time`, `days`, `months` and `years` +- openLastView: (*boolean*, defaults to `false`) Opens the last opened view after the picker is opened again, instead of the `startView` +- pickOnly: (*string*, defaults to `false`) If you just want to pick a year, month, day or time. The options are `time`, `days`, `months` and `years` +- canAlwaysGoUp: (*array*, defaults to `['months', 'days']`) The views where you can click the title to go up. The options are `time`, `days`, `months` and `years` +- updateAll (*boolean*, defaults to `false`) whether or not to update all inputs when selecting a date +- weeknumbers (*boolean*, defaults to `false`) display weeknumbers for the `days` view +- months_abbr: (*array*) An array with the month name abbreviations. If nothing is set, it will automatically use MooTools Locale to get the abbreviations +- days_abbr: (*array*) An array with the day name abbreviations. If nothing is set, it will automatically use MooTools Locale to get the abbreviations +- years_title: (*function*, defaults to a function which returns `year + '-' + (year + options.yearsPerPage - 1)`) A function that returns the title for the yearpicker with as arguments the date object and the options object. +- months_title: (*function*, defaults to a function which returns `date.format('%b %Y')`) A function that returns the title for the monthpicker with as arguments the date object and the options object. +- days_title: (*function*, defaults to a function which returns `date.format('%b %Y')`) A function that returns the title for the daypicker with as arguments the date object and the options object. +- time_title: (*function*, defaults to a function which returns `(options.pickOnly == 'time') ? Locale.get('DatePicker.select_a_time') : date.format('%d %B, %Y')`) A function that returns the title for the timepicker with as arguments the date object and the options object. + + + +### Events: + +- onSelect: Will fire when the user has selected a date + +#### signature + + #JS + onSelect(date) + +#### arguments + +1. date - A Date object. You could use [Date.format](http://mootools.net/docs/more/Types/Date#Date:format) to format it into a string. For example to set it into a hidden field which will be sent to the server. + + +### Examples + + #JS + new DatePicker('inputField', { + timePicker: true, + pickerClass: 'datepicker_jqui', + onSelect: function(date){ + myHiddenField.set('value', date.format('%s'); + } + }); + +Picker.Date method: select +-------------------------- + +Selects a date manually. + +### Syntax: + + picker.select(date[, all]); + +### Arguments: + +1. date (*Date instance*) the date instance of the new date +2. all (*boolean*, optional) Whether it should update all inputs (defaults to the *updateAll* option) + +### Returns: + +- Picker.Date instance. + + +Class: Picker.Date.Range +------------------------ + +The range picker can be used to select date ranges, with a start date and a end date. + +### Syntax: + + #JS + var dp = new Picker.Date.Range([element, options]); + +### Arguments: + +#### Options: + +All `Picker.Date` options plus: + +- getStartEndDate: (*function*) Parses the two dates in the input field to `Date` instances. Signature: `function(input)` +- setStartEndDate: (*function*) Formats the dates and sets the input field value. Signature: `function(input, dates)` +- columns: (*number*, defaults to `3`) Number of columns +- footer: (*boolean*, defaults to `true`) Creates an extra footer element + + +Class: Picker.Attach +-------------------- + +Picker.Attach handles all the links from elements to the Picker. It handles the onfocus events of input elements etc. +The Class itself is not very useful on its own, but it is useful to extend this Class. +This class adds a outerclick as well to close the Picker if you click outside the picker. + +#### Syntax: + + #JS + new Picker.Attach(attachTo, options); + +#### Options: + +- toggle: (*element*, *string*, *array*) A collection of elements which will toggle the picker when such a link is clicked. +- togglesOnly: (*boolean, defaults to `true`) If the `toggle` option is set, this option determines if the focus/blur events on the input fields are still added as well. +- blockKeydown: (*boolean*, defaults to `true`) Whether it should block keydown events, so the user can type into the input field or not. + +### Picker.Attach Method: attach + +This will attach links and input elements to the picker + +#### Syntax + + #JS + myPicker.attach(attachTo); + +#### Arguments + +1. attachTo: (*element*, *string*, *array*) The elements or element to attach to the Picker. Can be a input element for onfocus events or other elements for click events. + + +### Picker.Attach Method: detach + +This will detach links and input elements from the picker + +#### Syntax + + #JS + myPicker.detach(detach); + +#### Arguments + +1. detach: (*element*, *string*, *array*) The elements or element to detach from the Picker. Can be a input element for onfocus events or other elements for click events. + + +Class: Picker +------------- + +This is a generic Picker Class, which is used for the basic things, like positioning, Fx, open, close, stuff like that. + +#### Syntax: + + #JS + new Picker(options); + +#### Options: + +- pickerClass: (*string*, defaults to `datepicker`) CSS class for the main datepicker container element. You can use multiple classes by separating them by a space, e.g. `class1 class2 class3` +- inject: (*element*, defaults to `document.body`) This is where the Picker element will be injected to. +- anitmationDuration: (*number*, defaults to `400`) Duration of the slide/fade animations in milliseconds. +- useFadeInOut: (*boolean*, defaults to `true`) Whether to fade-in/out the datepicker popup. You might want to set this to `false` in IE. +- positionOffset: (*object*, defaults to `{x: 0, y: 0}`) Allows you to tweak the position at which the datepicker appears, relative to the input element. Formatted as an object with x and y properties. Values can be negative. +- pickerPosition: (*string*, defaults to `bottom`) If the picker is positioned relative to an element, you can choose to position it top, bottom, left or right. +- draggable: (*boolean*, defaults to `true`) Will make the picker draggable, if Drag from MooTools More is included. +- columns: (*number*, defaults to `1`) Number of columns +- footer: (*boolean*, defaults to `false`) Creates an extra footer element + +#### Events: + +- open - triggered when the Picker will open (before the fx) +- close - triggered after the Picker is will get closed (before the fx) +- show - triggered when the Picker is shown +- hide - triggered when the Picker is hidden + + + +### Picker Method: show + +A method to show the Picker manually, with a Fx. + +#### Syntax + + #JS + dp.show() + +### Picker Method: close + +Closes the Picker with a Fx. + +#### Syntax + + #JS + dp.close(); + + +### Picker Method: toggle + +Toggles the Picker with a Fx. + +#### Syntax + + #JS + picker.toggle(); + +### Picker Method: show + +Opens the Picker directly. + +#### Syntax + + #JS + dp.show(); + + +### Picker Method: hide + +Hides the Picker directly. + +#### Syntax + + #JS + dp.hide(); + + +### Picker Method: destroy + +Destroys the Picker + + #JS + picker.destroy(); + + +### Picker Method: position + +Positions the Picker. + +#### Syntax: + + #JS + picker.position(x, y); + // or + picker.position(myElement, where); + +#### Arguments + +1. x: (*number*) Number of pixels from the left +2. y: (*number*) Number of pixels from the top + +Or + +1. myElement - (*element*) A element the Picker should be positioned relative to. +2. where - (*string*, optional) Position the Picker `left` or `right` to the element. + + +### Picker Method: setContent + +Set the content of the Picker, either elements or text. + +#### Syntax: + + #JS + picker.setContent(element, fx); + +#### Arguments: + +1. element: (*element*, *string*) Set the content of the Picker with this value +2. fx: (*string*, optional) Set the content of the picker, and apply it with this Fx. Options: 'fade', 'right', 'left' + + +### Picker Method: setTitle + +Sets the Picker title text. + + #JS + picker.setTitle(text); + +#### Arguments: + +1. text: (*string*) The text which will be set into the title. + + +License +------- + +- [MIT License](http://www.opensource.org/licenses/mit-license.php) diff --git a/Source/Locale.af-ZA.DatePicker.js b/Source/Locale.af-ZA.DatePicker.js new file mode 100644 index 0000000..7395543 --- /dev/null +++ b/Source/Locale.af-ZA.DatePicker.js @@ -0,0 +1,19 @@ +/* +--- +name: Locale.af-ZA.DatePicker +description: Afrikaans Language File for DatePicker +authors: Werner Mollentze +requires: [More/Locale] +provides: Locale.af-ZA.DatePicker +... +*/ + + +Locale.define('af-ZA', 'DatePicker', { + select_a_time: 'Kies \'n tyd', + use_mouse_wheel: 'Gebruik die muiswiel om vinnig \'n waarde te verander', + time_confirm_button: 'OK', + apply_range: 'OK', + cancel: 'Kanseleer', + week: 'Wk' +}); diff --git a/Source/Locale.cs-CZ.DatePicker.js b/Source/Locale.cs-CZ.DatePicker.js new file mode 100644 index 0000000..53387f7 --- /dev/null +++ b/Source/Locale.cs-CZ.DatePicker.js @@ -0,0 +1,16 @@ +/* +--- +name: Locale.cs-CZ.DatePicker +description: Czech Language File for DatePicker +authors: Jan Cerny +requires: [More/Locale] +provides: Locale.cs-CZ.DatePicker +... +*/ + + +Locale.define('cs-CZ', 'DatePicker', { + select_a_time: 'Vyberte čas', + use_mouse_wheel: 'Použijte kolečko myši k rychlé změně hodnoty', + time_confirm_button: 'Zvolte čas' +}); diff --git a/Source/Locale.de-DE.DatePicker.js b/Source/Locale.de-DE.DatePicker.js new file mode 100644 index 0000000..78c1ebd --- /dev/null +++ b/Source/Locale.de-DE.DatePicker.js @@ -0,0 +1,16 @@ +/* +--- +name: Locale.de-DE.DatePicker +description: German Language File for DatePicker +authors: Bastian Bringenberg +requires: [More/Locale] +provides: Locale.de-DE.DatePicker +... +*/ + + +Locale.define('de-DE', 'DatePicker', { + select_a_time: 'Wähle eine Zeit', + use_mouse_wheel: 'Mit dem Mausrad kannst du schneller die Werte ändern', + time_confirm_button: 'OK' +}); diff --git a/Source/Locale.en-US.DatePicker.js b/Source/Locale.en-US.DatePicker.js new file mode 100644 index 0000000..fc69c25 --- /dev/null +++ b/Source/Locale.en-US.DatePicker.js @@ -0,0 +1,19 @@ +/* +--- +name: Locale.en-US.DatePicker +description: English Language File for DatePicker +authors: Arian Stolwijk +requires: [More/Locale] +provides: Locale.en-US.DatePicker +... +*/ + + +Locale.define('en-US', 'DatePicker', { + select_a_time: 'Select a time', + use_mouse_wheel: 'Use the mouse wheel to quickly change value', + time_confirm_button: 'OK', + apply_range: 'Apply', + cancel: 'Cancel', + week: 'Wk' +}); diff --git a/Source/Locale.es-ES-DatePicker.js b/Source/Locale.es-ES-DatePicker.js new file mode 100644 index 0000000..e05dca4 --- /dev/null +++ b/Source/Locale.es-ES-DatePicker.js @@ -0,0 +1,19 @@ +/* +--- +name: Locale.es-ES.DatePicker +description: Spanish Language File for DatePicker +authors: ["Juan Lago D.", "Carlos Cerrillo"] +requires: [More/Locale] +provides: Locale.es-ES.DatePicker +... +*/ + + +Locale.define('es-ES', 'DatePicker', { + select_a_time: 'Selecciona una fecha', + use_mouse_wheel: 'Utiliza la rueda del raton para cambiar rapidamente de valor', + time_confirm_button: 'OK', + apply_range: 'Aplicar', + cancel: 'Cancelar', + week: 'S' +}); diff --git a/Source/Locale.fr-FR.DatePicker.js b/Source/Locale.fr-FR.DatePicker.js new file mode 100644 index 0000000..f523685 --- /dev/null +++ b/Source/Locale.fr-FR.DatePicker.js @@ -0,0 +1,18 @@ +/* +--- +name: Locale.fr-FR.DatePicker +description: French Language File for DatePicker +authors: ["Arian Stolwijk", "charlouze", "Abric Armand"] +requires: [More/Locale] +provides: Locale.fr-FR.DatePicker +... +*/ + +Locale.define('fr-FR', 'DatePicker', { + select_a_time: 'Choisir l\'heure', + use_mouse_wheel: 'Utiliser la molette pour changer l\'heure rapidement', + time_confirm_button: 'OK', + apply_range: 'Appliquer', + cancel: 'Annuler', + week: 'Sem' +}); diff --git a/Source/Locale.he-IL.DatePicker.js b/Source/Locale.he-IL.DatePicker.js new file mode 100644 index 0000000..9d5297d --- /dev/null +++ b/Source/Locale.he-IL.DatePicker.js @@ -0,0 +1,19 @@ +/* +--- +name: Locale.he-IL.DatePicker +description: Hebrew Language File for DatePicker +authors: Amitay Horwitz +requires: [More/Locale] +provides: Locale.he-IL.DatePicker +... +*/ + + +Locale.define('he-IL', 'DatePicker', { + select_a_time: 'בחר זמן', + use_mouse_wheel: 'השתמש בגלגלת העכבר לשינוי מהיר', + time_confirm_button: 'אישור', + apply_range: 'החל', + cancel: 'ביטול', + week: 'שבוע' +}); diff --git a/Source/Locale.it-IT.DatePicker.js b/Source/Locale.it-IT.DatePicker.js new file mode 100644 index 0000000..7e2a146 --- /dev/null +++ b/Source/Locale.it-IT.DatePicker.js @@ -0,0 +1,15 @@ +/* +--- +name: Locale.it-IT.DatePicker +description: Italian Language File for DatePicker +authors: danielec (https://github.com/danielec) +requires: [More/Locale] +provides: Locale.it-IT.DatePicker +... +*/ + +Locale.define('it-IT', 'DatePicker', { + select_a_time: 'Scegli un orario', + use_mouse_wheel: 'Utilizza la rotellina del mouse per cambiare valore velocemente', + time_confirm_button: 'OK' +}); diff --git a/Source/Locale.nl-NL.DatePicker.js b/Source/Locale.nl-NL.DatePicker.js new file mode 100644 index 0000000..471fd72 --- /dev/null +++ b/Source/Locale.nl-NL.DatePicker.js @@ -0,0 +1,19 @@ +/* +--- +name: Locale.nl-NL.DatePicker +description: Dutch Language File for DatePicker +authors: Arian Stolwijk +requires: [More/Locale] +provides: Locale.nl-NL.DatePicker +... +*/ + + +Locale.define('nl-NL', 'DatePicker', { + select_a_time: 'Selecteer een tijd', + use_mouse_wheel: 'Gebruik uw scrollwiel om door de tijd te scrollen', + time_confirm_button: 'OK', + apply_range: 'OK', + cancel: 'Annuleer', + week: 'W' +}); diff --git a/Source/Locale.pl-PL.DatePicker.js b/Source/Locale.pl-PL.DatePicker.js new file mode 100644 index 0000000..8146a83 --- /dev/null +++ b/Source/Locale.pl-PL.DatePicker.js @@ -0,0 +1,16 @@ +/* +--- +name: Locale.pl-PL.DatePicker +description: Polish Language File for DatePicker +authors: Tomek Wójcik +requires: [More/Locale] +provides: Locale.pl-PL.DatePicker +... +*/ + + +Locale.define('pl-PL', 'DatePicker', { + select_a_time: 'Wybierz czas', + use_mouse_wheel: 'Użyj rolki myszy aby szybko zmienić wartość', + time_confirm_button: 'OK' +}); diff --git a/Source/Locale.pt-BR.DatePicker.js b/Source/Locale.pt-BR.DatePicker.js new file mode 100644 index 0000000..97d5f8e --- /dev/null +++ b/Source/Locale.pt-BR.DatePicker.js @@ -0,0 +1,19 @@ +/* +--- +name: Locale.pt-BR.DatePicker +description: Portuguese Language File for DatePicker +authors: Jonnathan Soares +requires: [More/Locale] +provides: Locale.pt-BR.DatePicker +... +*/ + + +Locale.define('pt-BR', 'DatePicker', { + select_a_time: 'Selecione uma hora', + use_mouse_wheel: 'Use a roda do mouse para rapidamente trocar de valor', + time_confirm_button: 'OK', + apply_range: 'Aplicar', + cancel: 'Cancelar', + week: 'Sem.' +}); diff --git a/Source/Locale.ru-RU.DatePicker.js b/Source/Locale.ru-RU.DatePicker.js new file mode 100644 index 0000000..31cc3d8 --- /dev/null +++ b/Source/Locale.ru-RU.DatePicker.js @@ -0,0 +1,16 @@ +/* +--- +name: Locale.ru-RU.DatePicker +description: Russian Language File for DatePicker +authors: https://github.com/rwz +requires: [More/Locale] +provides: Locale.ru-RU.DatePicker +... +*/ + + +Locale.define('ru-RU', 'DatePicker', { + select_a_time: 'Выберите время', + use_mouse_wheel: 'Используйте колесо мышки для быстрой смены значения', + time_confirm_button: 'OK' +}); diff --git a/Source/Locale.sv-SE.DatePicker.js b/Source/Locale.sv-SE.DatePicker.js new file mode 100644 index 0000000..1c11034 --- /dev/null +++ b/Source/Locale.sv-SE.DatePicker.js @@ -0,0 +1,19 @@ +/* +--- +name: Locale.sv-SE.DatePicker +description: Swedish Language File for DatePicker +authors: Leon Radley +requires: [More/Locale] +provides: Locale.sv-SE.DatePicker +... +*/ + + +Locale.define('sv-SE', 'DatePicker', { + select_a_time: 'Välj en tid', + use_mouse_wheel: 'Scrolla för att snabbt ändra värde', + time_confirm_button: 'OK', + apply_range: 'Välj', + cancel: 'Avbryt', + week: 'v.' +}); diff --git a/Source/Locale.uk-UA.DatePicker.js b/Source/Locale.uk-UA.DatePicker.js new file mode 100644 index 0000000..b73055a --- /dev/null +++ b/Source/Locale.uk-UA.DatePicker.js @@ -0,0 +1,19 @@ +/* +--- +name: Locale.en-US.DatePicker +description: English Language File for DatePicker +authors: Arian Stolwijk +requires: [More/Locale] +provides: Locale.en-US.DatePicker +... +*/ + + +Locale.define('uk-UA', 'DatePicker', { + select_a_time: 'Встановіть час', + use_mouse_wheel: 'Використовуйте прокрутку для швидкої зміни значення', + time_confirm_button: 'Гаразд', + apply_range: 'Застосувати', + cancel: 'Скасувати', + week: 'Т-нь' +}); diff --git a/Source/Picker.Attach.js b/Source/Picker.Attach.js new file mode 100644 index 0000000..900159e --- /dev/null +++ b/Source/Picker.Attach.js @@ -0,0 +1,161 @@ +/* +--- +name: Picker.Attach +description: Adds attach and detach methods to the Picker, to attach it to element events +authors: Arian Stolwijk +requires: [Picker, Core/Element.Event] +provides: Picker.Attach +... +*/ + + +Picker.Attach = new Class({ + + Extends: Picker, + + options: {/* + onAttached: function(event){}, + + toggleElements: null, // deprecated + toggle: null, // When set it deactivate toggling by clicking on the input */ + togglesOnly: true, // set to false to always make calendar popup on input element, if true, it depends on the toggles elements set. + showOnInit: false, // overrides the Picker option + blockKeydown: true + }, + + initialize: function(attachTo, options){ + this.parent(options); + + this.attachedEvents = []; + this.attachedElements = []; + this.toggles = []; + this.inputs = []; + + var documentEvent = function(event){ + if (this.attachedElements.contains(event.target)) return; + this.close(); + }.bind(this); + var document = this.picker.getDocument().addEvent('click', documentEvent); + + var preventPickerClick = function(event){ + event.stopPropagation(); + return false; + }; + this.picker.addEvent('click', preventPickerClick); + + // Support for deprecated toggleElements + if (this.options.toggleElements) this.options.toggle = document.getElements(this.options.toggleElements); + + this.attach(attachTo, this.options.toggle); + }, + + attach: function(attachTo, toggle){ + if (typeOf(attachTo) == 'string') attachTo = document.id(attachTo); + if (typeOf(toggle) == 'string') toggle = document.id(toggle); + + var elements = Array.from(attachTo), + toggles = Array.from(toggle), + allElements = [].append(elements).combine(toggles), + self = this; + + var closeEvent = function(event){ + var stopInput = self.options.blockKeydown + && event.type == 'keydown' + && !(['tab', 'esc'].contains(event.key)), + isCloseKey = event.type == 'keydown' + && (['tab', 'esc'].contains(event.key)), + isA = event.target.get('tag') == 'a'; + + if (stopInput || isA) event.preventDefault(); + if (isCloseKey || isA) self.close(); + }; + + var getOpenEvent = function(element){ + return function(event){ + var tag = event.target.get('tag'); + if (tag == 'input' && event.type == 'click' && !element.match(':focus') || (self.opened && self.input == element)) return; + if (tag == 'a') event.stop(); + self.position(element); + self.open(); + self.fireEvent('attached', [event, element]); + }; + }; + + var getToggleEvent = function(open, close){ + return function(event){ + if (self.opened) close(event); + else open(event); + }; + }; + + allElements.each(function(element){ + + // The events are already attached! + if (self.attachedElements.contains(element)) return; + + var events = {}, + tag = element.get('tag'), + openEvent = getOpenEvent(element), + // closeEvent does not have a depency on element + toggleEvent = getToggleEvent(openEvent, closeEvent); + + if (tag == 'input'){ + // Fix in order to use togglers only + if (!self.options.togglesOnly || !toggles.length){ + events = { + focus: openEvent, + click: openEvent, + keydown: closeEvent + }; + } + self.inputs.push(element); + } else { + if (toggles.contains(element)){ + self.toggles.push(element); + events.click = toggleEvent + } else { + events.click = openEvent; + } + } + element.addEvents(events); + self.attachedElements.push(element); + self.attachedEvents.push(events); + }); + return this; + }, + + detach: function(attachTo, toggle){ + if (typeOf(attachTo) == 'string') attachTo = document.id(attachTo); + if (typeOf(toggle) == 'string') toggle = document.id(toggle); + + var elements = Array.from(attachTo), + toggles = Array.from(toggle), + allElements = [].append(elements).combine(toggles), + self = this; + + if (!allElements.length) allElements = self.attachedElements; + + allElements.each(function(element){ + var i = self.attachedElements.indexOf(element); + if (i < 0) return; + + var events = self.attachedEvents[i]; + element.removeEvents(events); + delete self.attachedEvents[i]; + delete self.attachedElements[i]; + + var toggleIndex = self.toggles.indexOf(element); + if (toggleIndex != -1) delete self.toggles[toggleIndex]; + + var inputIndex = self.inputs.indexOf(element); + if (toggleIndex != -1) delete self.inputs[inputIndex]; + }); + return this; + }, + + destroy: function(){ + this.detach(); + return this.parent(); + } + +}); diff --git a/Source/Picker.Date.Range.js b/Source/Picker.Date.Range.js new file mode 100644 index 0000000..1e31b43 --- /dev/null +++ b/Source/Picker.Date.Range.js @@ -0,0 +1,132 @@ +/* +--- +name: Picker.Date.Range +description: Select a Range of Dates +authors: Arian Stolwijk +requires: [Picker, Picker.Date] +provides: Picker.Date.Range +... +*/ + +Picker.Date.Range = new Class({ + + Extends: Picker.Date, + + options: { + getStartEndDate: function(input){ + return input.get('value').split('-').map(function(date){ + var parsed = Date.parse(date); + return Date.isValid(parsed) ? parsed : null; + }).clean(); + }, + setStartEndDate: function(input, dates){ + input.set('value', dates.map(function(date){ + return date.format(this.options.format); + }, this).join(' - ')); + }, + footer: true, + columns: 3 + }, + + getInputDate: function(input){ + if (!input) return; + + var dates = input.retrieve('datepicker:value'); + if (dates && dates.length) dates = dates.map(Date.parse); + if (!dates || !dates.length || dates.some(function(date){ + return !Date.isValid(date); + })){ + dates = this.options.getStartEndDate.call(this, input); + if (!dates.length || !dates.every(function(date){ + return Date.isValid(date); + })) dates = [this.date]; + } + if (dates.length == 1) this.date = this.startDate = this.endDate = dates[0]; + else if (dates.length == 2){ + this.date = this.startDate = dates[0]; + this.endDate = dates[1]; + } + }, + + constructPicker: function(){ + this.parent(); + var footer = this.footer, self = this; + if (!footer) return; + + var events = { + click: function(){ + this.focus(); + }, + blur: function(){ + var date = Date.parse(this.get('value')); + if (date.isValid) self[(this == startInput ? 'start' : 'end') + 'Date'] = date; + self.updateRangeSelection(); + }, + keydown: function(event){ + if (event.key == 'enter') self.selectRange(); + } + }; + + var startInput = this.startInput = new Element('input', {events: events}).inject(footer); + new Element('span', {text: ' - '}).inject(footer); + var endInput = this.endInput = new Element('input', {events: events}).inject(footer); + + this.applyButton = new Element('button.apply', { + text: Locale.get('DatePicker.apply_range'), + events: {click: self.selectRange.pass([], self)} + }).inject(footer); + + this.cancelButton = new Element('button.cancel', { + text: Locale.get('DatePicker.cancel'), + events: {click: self.close.pass(false, self)} + }).inject(footer); + }, + + renderDays: function(){ + this.parent.apply(this, arguments); + this.updateRangeSelection(); + }, + + select: function(date){ + if (this.startDate && (this.endDate == this.startDate || date > this.endDate) && date >= this.startDate) this.endDate = date; + else { + this.startDate = date; + this.endDate = date; + } + this.updateRangeSelection(); + }, + + selectRange: function(){ + this.date = this.startDate; + var dates = [this.startDate, this.endDate], input = this.input; + + this.options.setStartEndDate.call(this, input, dates); + input.store('datepicker:value', dates.map(function(date){ + return date.strftime(); + })).fireEvent('change'); + + this.fireEvent('select', dates, input); + this.close(); + return this; + }, + + updateRangeSelection: function(){ + var start = this.startDate, + end = this.endDate || start; + + if (this.dateElements) for (var i = this.dateElements.length; i--;){ + var el = this.dateElements[i]; + if (el.time >= start && el.time <= end) el.element.addClass('selected'); + else el.element.removeClass('selected'); + } + + var formattedFirst = start.format(this.options.format) + formattedEnd = end.format(this.options.format); + + this.startInput.set('value', formattedFirst); + this.endInput.set('value', formattedEnd); + + return this; + } + +}); diff --git a/Source/Picker.Date.js b/Source/Picker.Date.js new file mode 100644 index 0000000..8b4558e --- /dev/null +++ b/Source/Picker.Date.js @@ -0,0 +1,669 @@ +/* +--- +name: Picker.Date +description: Creates a DatePicker, can be used for picking years/months/days and time, or all of them +authors: Arian Stolwijk +requires: [Picker, Picker.Attach, Locale.en-US.DatePicker, More/Locale, More/Date] +provides: Picker.Date +... +*/ + + +(function(){ + +this.DatePicker = Picker.Date = new Class({ + + Extends: Picker.Attach, + + options: {/* + onSelect: function(date){}, + + minDate: new Date('3/4/2010'), // Date object or a string + maxDate: new Date('3/4/2011'), // same as minDate + availableDates: {}, // + invertAvailable: false, + + format: null,*/ + + timePicker: false, + timePickerOnly: false, // deprecated, use onlyView = 'time' + timeWheelStep: 1, // 10,15,20,30 + + yearPicker: true, + yearsPerPage: 20, + + startDay: 1, // Sunday (0) through Saturday (6) - be aware that this may affect your layout, since the days on the right might have a different margin + rtl: false, + + startView: 'days', // allowed values: {time, days, months, years} + openLastView: false, + pickOnly: false, // 'years', 'months', 'days', 'time' + canAlwaysGoUp: ['months', 'days'], + updateAll : false, //whether or not to update all inputs when selecting a date + + weeknumbers: false, + + // if you like to use your own translations + months_abbr: null, + days_abbr: null, + years_title: function(date, options){ + var year = date.get('year'); + return year + '-' + (year + options.yearsPerPage - 1); + }, + months_title: function(date, options){ + return date.get('year'); + }, + days_title: function(date, options){ + return date.format('%b %Y'); + }, + time_title: function(date, options){ + return (options.pickOnly == 'time') ? Locale.get('DatePicker.select_a_time') : date.format('%d %B, %Y'); + } + }, + + initialize: function(attachTo, options){ + this.parent(attachTo, options); + + this.setOptions(options); + options = this.options; + + // If we only want to use one picker / backwards compatibility + ['year', 'month', 'day', 'time'].some(function(what){ + if (options[what + 'PickerOnly']){ + options.pickOnly = what; + return true; + } + return false; + }); + if (options.pickOnly){ + options[options.pickOnly + 'Picker'] = true; + options.startView = options.pickOnly; + } + + // backward compatibility for startView + var newViews = ['days', 'months', 'years']; + ['month', 'year', 'decades'].some(function(what, i){ + return (options.startView == what) && (options.startView = newViews[i]); + }); + + options.canAlwaysGoUp = options.canAlwaysGoUp ? Array.from(options.canAlwaysGoUp) : []; + + // Set the min and max dates as Date objects + if (options.minDate){ + if (!(options.minDate instanceof Date)) options.minDate = Date.parse(options.minDate); + options.minDate.clearTime(); + } + if (options.maxDate){ + if (!(options.maxDate instanceof Date)) options.maxDate = Date.parse(options.maxDate); + options.maxDate.clearTime(); + } + + if (!options.format){ + options.format = (options.pickOnly != 'time') ? Locale.get('Date.shortDate') : ''; + if (options.timePicker) options.format = (options.format) + (options.format ? ' ' : '') + Locale.get('Date.shortTime'); + } + + // Some link or input has fired an event! + this.addEvent('attached', function(event, element){ + + // This is where we store the selected date + if (!this.currentView || !options.openLastView) this.currentView = options.startView; + + this.date = limitDate(new Date(), options.minDate, options.maxDate); + var tag = element.get('tag'), input; + if (tag == 'input') input = element; + else { + var index = this.toggles.indexOf(element); + if (this.inputs[index]) input = this.inputs[index]; + } + this.getInputDate(input); + this.input = input; + this.setColumns(this.originalColumns); + }.bind(this), true); + + }, + + getInputDate: function(input){ + this.date = new Date(); + if (!input) return; + var date = Date.parse(input.get('value')); + if (date == null || !date.isValid()){ + var storeDate = input.retrieve('datepicker:value'); + if (storeDate) date = Date.parse(storeDate); + } + if (date != null && date.isValid()) this.date = date; + }, + + // Control the previous and next elements + + constructPicker: function(){ + this.parent(); + + if (!this.options.rtl){ + this.previous = new Element('div.previous[html=«]').inject(this.header); + this.next = new Element('div.next[html=»]').inject(this.header); + } else { + this.next = new Element('div.previous[html=«]').inject(this.header); + this.previous = new Element('div.next[html=»]').inject(this.header); + } + }, + + hidePrevious: function(_next, _show){ + this[_next ? 'next' : 'previous'].setStyle('display', _show ? 'block' : 'none'); + return this; + }, + + showPrevious: function(_next){ + return this.hidePrevious(_next, true); + }, + + setPreviousEvent: function(fn, _next){ + this[_next ? 'next' : 'previous'].removeEvents('click'); + if (fn) this[_next ? 'next' : 'previous'].addEvent('click', fn); + return this; + }, + + hideNext: function(){ + return this.hidePrevious(true); + }, + + showNext: function(){ + return this.showPrevious(true); + }, + + setNextEvent: function(fn){ + return this.setPreviousEvent(fn, true); + }, + + setColumns: function(columns, view, date, viewFx){ + var ret = this.parent(columns), method; + + if ((view || this.currentView) + && (method = 'render' + (view || this.currentView).capitalize()) + && this[method] + ) this[method](date || this.date.clone(), viewFx); + + return ret; + }, + + // Render the Pickers + + renderYears: function(date, fx){ + var options = this.options, pages = options.columns, perPage = options.yearsPerPage, + _columns = [], _dates = []; + this.dateElements = []; + + // start neatly at interval (eg. 1980 instead of 1987) + date = date.clone().decrement('year', date.get('year') % perPage); + + var iterateDate = date.clone().decrement('year', Math.floor((pages - 1) / 2) * perPage); + + for (var i = pages; i--;){ + var _date = iterateDate.clone(); + _dates.push(_date); + _columns.push(renderers.years( + timesSelectors.years(options, _date.clone()), + options, + this.date.clone(), + this.dateElements, + function(date){ + if (options.pickOnly == 'years') this.select(date); + else this.renderMonths(date, 'fade'); + this.date = date; + }.bind(this) + )); + iterateDate.increment('year', perPage); + } + + this.setColumnsContent(_columns, fx); + this.setTitle(_dates, options.years_title); + + // Set limits + var limitLeft = (options.minDate && date.get('year') <= options.minDate.get('year')), + limitRight = (options.maxDate && (date.get('year') + options.yearsPerPage) >= options.maxDate.get('year')); + this[(limitLeft ? 'hide' : 'show') + 'Previous'](); + this[(limitRight ? 'hide' : 'show') + 'Next'](); + + this.setPreviousEvent(function(){ + this.renderYears(date.decrement('year', perPage), 'left'); + }.bind(this)); + + this.setNextEvent(function(){ + this.renderYears(date.increment('year', perPage), 'right'); + }.bind(this)); + + // We can't go up! + this.setTitleEvent(null); + + this.currentView = 'years'; + }, + + renderMonths: function(date, fx){ + var options = this.options, years = options.columns, _columns = [], _dates = [], + iterateDate = date.clone().decrement('year', Math.floor((years - 1) / 2)); + this.dateElements = []; + + for (var i = years; i--;){ + var _date = iterateDate.clone(); + _dates.push(_date); + _columns.push(renderers.months( + timesSelectors.months(options, _date.clone()), + options, + this.date.clone(), + this.dateElements, + function(date){ + if (options.pickOnly == 'months') this.select(date); + else this.renderDays(date, 'fade'); + this.date = date; + }.bind(this) + )); + iterateDate.increment('year', 1); + } + + this.setColumnsContent(_columns, fx); + this.setTitle(_dates, options.months_title); + + // Set limits + var year = date.get('year'), + limitLeft = (options.minDate && year <= options.minDate.get('year')), + limitRight = (options.maxDate && year >= options.maxDate.get('year')); + this[(limitLeft ? 'hide' : 'show') + 'Previous'](); + this[(limitRight ? 'hide' : 'show') + 'Next'](); + + this.setPreviousEvent(function(){ + this.renderMonths(date.decrement('year', years), 'left'); + }.bind(this)); + + this.setNextEvent(function(){ + this.renderMonths(date.increment('year', years), 'right'); + }.bind(this)); + + var canGoUp = options.yearPicker && (options.pickOnly != 'months' || options.canAlwaysGoUp.contains('months')); + var titleEvent = (canGoUp) ? function(){ + this.renderYears(date, 'fade'); + }.bind(this) : null; + this.setTitleEvent(titleEvent); + + this.currentView = 'months'; + }, + + renderDays: function(date, fx){ + var options = this.options, months = options.columns, _columns = [], _dates = [], + iterateDate = date.clone().decrement('month', Math.floor((months - 1) / 2)); + this.dateElements = []; + + for (var i = months; i--;){ + _date = iterateDate.clone(); + _dates.push(_date); + _columns.push(renderers.days( + timesSelectors.days(options, _date.clone()), + options, + this.date.clone(), + this.dateElements, + function(date){ + if (options.pickOnly == 'days' || !options.timePicker) this.select(date) + else this.renderTime(date, 'fade'); + this.date = date; + }.bind(this) + )); + iterateDate.increment('month', 1); + } + + this.setColumnsContent(_columns, fx); + this.setTitle(_dates, options.days_title); + + var yearmonth = date.format('%Y%m').toInt(), + limitLeft = (options.minDate && yearmonth <= options.minDate.format('%Y%m')), + limitRight = (options.maxDate && yearmonth >= options.maxDate.format('%Y%m')); + this[(limitLeft ? 'hide' : 'show') + 'Previous'](); + this[(limitRight ? 'hide' : 'show') + 'Next'](); + + this.setPreviousEvent(function(){ + this.renderDays(date.decrement('month', months), 'left'); + }.bind(this)); + + this.setNextEvent(function(){ + this.renderDays(date.increment('month', months), 'right'); + }.bind(this)); + + var canGoUp = options.pickOnly != 'days' || options.canAlwaysGoUp.contains('days'); + var titleEvent = (canGoUp) ? function(){ + this.renderMonths(date, 'fade'); + }.bind(this) : null; + this.setTitleEvent(titleEvent); + + this.currentView = 'days'; + }, + + renderTime: function(date, fx){ + var options = this.options; + this.setTitle(date, options.time_title); + + var originalColumns = this.originalColumns = options.columns; + this.currentView = null; // otherwise you'd get crazy recursion + if (originalColumns != 1) this.setColumns(1); + + this.setContent(renderers.time( + options, + date.clone(), + function(date){ + this.select(date); + }.bind(this) + ), fx); + + // Hide « and » buttons + this.hidePrevious() + .hideNext() + .setPreviousEvent(null) + .setNextEvent(null); + + var canGoUp = options.pickOnly != 'time' || options.canAlwaysGoUp.contains('time'); + var titleEvent = (canGoUp) ? function(){ + this.setColumns(originalColumns, 'days', date, 'fade'); + }.bind(this) : null; + this.setTitleEvent(titleEvent); + + this.currentView = 'time'; + }, + + select: function(date, all){ + this.date = date; + var formatted = date.format(this.options.format), + time = date.strftime(), + inputs = (!this.options.updateAll && !all && this.input) ? [this.input] : this.inputs; + + inputs.each(function(input){ + input.set('value', formatted).store('datepicker:value', time).fireEvent('change'); + }, this); + + this.fireEvent('select', [date].concat(inputs)); + this.close(); + return this; + } + +}); + + +// Renderers only output elements and calculate the limits! + +var timesSelectors = { + + years: function(options, date){ + var times = []; + for (var i = 0; i < options.yearsPerPage; i++){ + times.push(+date); + date.increment('year', 1); + } + return times; + }, + + months: function(options, date){ + var times = []; + date.set('month', 0); + for (var i = 0; i <= 11; i++){ + times.push(+date); + date.increment('month', 1); + } + return times; + }, + + days: function(options, date){ + var times = []; + date.set('date', 1); + while (date.get('day') != options.startDay) date.set('date', date.get('date') - 1); + for (var i = 0; i < 42; i++){ + times.push(+date); + date.increment('day', 1); + } + return times; + } + +}; + +var renderers = { + + years: function(years, options, currentDate, dateElements, fn){ + var container = new Element('div.years'), + today = new Date(), element, classes; + + years.each(function(_year, i){ + var date = new Date(_year), year = date.get('year'); + + classes = '.year.year' + i; + if (year == today.get('year')) classes += '.today'; + if (year == currentDate.get('year')) classes += '.selected'; + element = new Element('div' + classes, {text: year}).inject(container); + + dateElements.push({element: element, time: _year}); + + if (isUnavailable('year', date, options)) element.addClass('unavailable'); + else element.addEvent('click', fn.pass(date)); + }); + + return container; + }, + + months: function(months, options, currentDate, dateElements, fn){ + var today = new Date(), + month = today.get('month'), + thisyear = today.get('year'), + selectedyear = currentDate.get('year'), + container = new Element('div.months'), + monthsAbbr = options.months_abbr || Locale.get('Date.months_abbr'), + element, classes; + + months.each(function(_month, i){ + var date = new Date(_month), year = date.get('year'); + + classes = '.month.month' + (i + 1); + if (i == month && year == thisyear) classes += '.today'; + if (i == currentDate.get('month') && year == selectedyear) classes += '.selected'; + element = new Element('div' + classes, {text: monthsAbbr[i]}).inject(container); + + dateElements.push({element: element, time: _month}); + + if (isUnavailable('month', date, options)) element.addClass('unavailable'); + else element.addEvent('click', fn.pass(date)); + }); + + return container; + }, + + days: function(days, options, currentDate, dateElements, fn){ + var month = new Date(days[14]).get('month'), + todayString = new Date().toDateString(), + currentString = currentDate.toDateString(), + weeknumbers = options.weeknumbers, + container = new Element('table.days' + (weeknumbers ? '.weeknumbers' : ''), { + role: 'grid', 'aria-labelledby': this.titleID + }), + header = new Element('thead').inject(container), + body = new Element('tbody').inject(container), + titles = new Element('tr.titles').inject(header), + localeDaysShort = options.days_abbr || Locale.get('Date.days_abbr'), + day, classes, element, weekcontainer, dateString, + where = options.rtl ? 'top' : 'bottom'; + + if (weeknumbers) new Element('th.title.day.weeknumber', { + text: Locale.get('DatePicker.week') + }).inject(titles); + + for (day = options.startDay; day < (options.startDay + 7); day++){ + new Element('th.title.day.day' + (day % 7), { + text: localeDaysShort[(day % 7)], + role: 'columnheader' + }).inject(titles, where); + } + + days.each(function(_date, i){ + var date = new Date(_date); + + if (i % 7 == 0){ + weekcontainer = new Element('tr.week.week' + (Math.floor(i / 7))).set('role', 'row').inject(body); + if (weeknumbers) new Element('th.day.weeknumber', {text: date.get('week'), scope: 'row', role: 'rowheader'}).inject(weekcontainer); + } + + dateString = date.toDateString(); + classes = '.day.day' + date.get('day'); + if (dateString == todayString) classes += '.today'; + if (date.get('month') != month) classes += '.otherMonth'; + element = new Element('td' + classes, {text: date.getDate(), role: 'gridcell'}).inject(weekcontainer, where); + + if (dateString == currentString) element.addClass('selected').set('aria-selected', 'true'); + else element.set('aria-selected', 'false'); + + dateElements.push({element: element, time: _date}); + + if (isUnavailable('date', date, options)) element.addClass('unavailable'); + else element.addEvent('click', fn.pass(date.clone())); + }); + + return container; + }, + + time: function(options, date, fn){ + var container = new Element('div.time'), + // make sure that the minutes are timeWheelStep * k + initMinutes = (date.get('minutes') / options.timeWheelStep).round() * options.timeWheelStep + + if (initMinutes >= 60) initMinutes = 0; + date.set('minutes', initMinutes); + + var hoursInput = new Element('input.hour[type=text]', { + title: Locale.get('DatePicker.use_mouse_wheel'), + value: date.format('%H'), + events: { + click: function(event){ + event.target.focus(); + event.stop(); + }, + mousewheel: function(event){ + event.stop(); + hoursInput.focus(); + var value = hoursInput.get('value').toInt(); + value = (event.wheel > 0) ? ((value < 23) ? value + 1 : 0) + : ((value > 0) ? value - 1 : 23) + date.set('hours', value); + hoursInput.set('value', date.format('%H')); + }.bind(this) + }, + maxlength: 2 + }).inject(container); + + var minutesInput = new Element('input.minutes[type=text]', { + title: Locale.get('DatePicker.use_mouse_wheel'), + value: date.format('%M'), + events: { + click: function(event){ + event.target.focus(); + event.stop(); + }, + mousewheel: function(event){ + event.stop(); + minutesInput.focus(); + var value = minutesInput.get('value').toInt(); + value = (event.wheel > 0) ? ((value < 59) ? (value + options.timeWheelStep) : 0) + : ((value > 0) ? (value - options.timeWheelStep) : (60 - options.timeWheelStep)); + if (value >= 60) value = 0; + date.set('minutes', value); + minutesInput.set('value', date.format('%M')); + }.bind(this) + }, + maxlength: 2 + }).inject(container); + + new Element('div.separator[text=:]').inject(container); + + new Element('input.ok', { + 'type': 'input', + value: Locale.get('DatePicker.time_confirm_button'), + events: {click: function(event){ + event.stop(); + date.set({ + hours: hoursInput.get('value').toInt(), + minutes: minutesInput.get('value').toInt() + }); + fn(date.clone()); + }} + }).inject(container); + + return container; + } + +}; + + +Picker.Date.defineRenderer = function(name, fn){ + renderers[name] = fn; + return this; +}; + +var limitDate = function(date, min, max){ + if (min && date < min) return min; + if (max && date > max) return max; + return date; +}; + +var isUnavailable = function(type, date, options){ + var minDate = options.minDate, + maxDate = options.maxDate, + availableDates = options.availableDates, + year, month, day, ms; + + if (!minDate && !maxDate && !availableDates) return false; + date.clearTime(); + + if (type == 'year'){ + year = date.get('year'); + return ( + (minDate && year < minDate.get('year')) || + (maxDate && year > maxDate.get('year')) || + ( + (availableDates != null && !options.invertAvailable) && ( + availableDates[year] == null || + Object.getLength(availableDates[year]) == 0 || + Object.getLength( + Object.filter(availableDates[year], function(days){ + return (days.length > 0); + }) + ) == 0 + ) + ) + ); + } + + if (type == 'month'){ + year = date.get('year'); + month = date.get('month') + 1; + ms = date.format('%Y%m').toInt(); + return ( + (minDate && ms < minDate.format('%Y%m').toInt()) || + (maxDate && ms > maxDate.format('%Y%m').toInt()) || + ( + (availableDates != null && !options.invertAvailable) && ( + availableDates[year] == null || + availableDates[year][month] == null || + availableDates[year][month].length == 0 + ) + ) + ); + } + + // type == 'date' + year = date.get('year'); + month = date.get('month') + 1; + day = date.get('date'); + + var dateAllow = (minDate && date < minDate) || (maxDate && date > maxDate); + if (availableDates != null){ + dateAllow = dateAllow + || availableDates[year] == null + || availableDates[year][month] == null + || !availableDates[year][month].contains(day); + if (options.invertAvailable) dateAllow = !dateAllow; + } + + return dateAllow; +}; + +})(); diff --git a/Source/Picker.js b/Source/Picker.js new file mode 100644 index 0000000..398a559 --- /dev/null +++ b/Source/Picker.js @@ -0,0 +1,344 @@ +/* +--- +name: Picker +description: Creates a Picker, which can be used for anything +authors: Arian Stolwijk +requires: [Core/Element.Dimensions, Core/Fx.Tween, Core/Fx.Transitions] +provides: Picker +... +*/ + + +var Picker = new Class({ + + Implements: [Options, Events], + + options: {/* + onShow: function(){}, + onOpen: function(){}, + onHide: function(){}, + onClose: function(){},*/ + + pickerClass: 'datepicker', + inject: null, + animationDuration: 400, + useFadeInOut: true, + positionOffset: {x: 0, y: 0}, + pickerPosition: 'bottom', + draggable: true, + showOnInit: true, + columns: 1, + footer: false + }, + + initialize: function(options){ + this.setOptions(options); + this.constructPicker(); + if (this.options.showOnInit) this.show(); + }, + + constructPicker: function(){ + var options = this.options; + + var picker = this.picker = new Element('div', { + 'class': options.pickerClass, + styles: { + left: 0, + top: 0, + display: 'none', + opacity: 0 + } + }).inject(options.inject || document.body); + picker.addClass('column_' + options.columns); + + if (options.useFadeInOut){ + picker.set('tween', { + duration: options.animationDuration, + link: 'cancel' + }); + } + + // Build the header + var header = this.header = new Element('div.header').inject(picker); + + var title = this.title = new Element('div.title').inject(header); + var titleID = this.titleID = 'pickertitle-' + String.uniqueID(); + this.titleText = new Element('div', { + 'role': 'heading', + 'class': 'titleText', + 'id': titleID, + 'aria-live': 'assertive', + 'aria-atomic': 'true' + }).inject(title); + + this.closeButton = new Element('div.closeButton[text=x][role=button]') + .addEvent('click', this.close.pass(false, this)) + .inject(header); + + // Build the body of the picker + var body = this.body = new Element('div.body').inject(picker); + + if (options.footer){ + this.footer = new Element('div.footer').inject(picker); + picker.addClass('footer'); + } + + // oldContents and newContents are used to slide from the old content to a new one. + var slider = this.slider = new Element('div.slider', { + styles: { + position: 'absolute', + top: 0, + left: 0 + } + }).set('tween', { + duration: options.animationDuration, + transition: Fx.Transitions.Quad.easeInOut + }).inject(body); + + this.newContents = new Element('div', { + styles: { + position: 'absolute', + top: 0, + left: 0 + } + }).inject(slider); + + this.oldContents = new Element('div', { + styles: { + position: 'absolute', + top: 0 + } + }).inject(slider); + + this.originalColumns = options.columns; + this.setColumns(options.columns); + + // IFrameShim for select fields in IE + var shim = this.shim = window['IframeShim'] ? new IframeShim(picker) : null; + + // Dragging + if (options.draggable && typeOf(picker.makeDraggable) == 'function'){ + this.dragger = picker.makeDraggable(shim ? { + onDrag: shim.position.bind(shim) + } : null); + picker.setStyle('cursor', 'move'); + } + }, + + open: function(noFx){ + if (this.opened == true) return this; + this.opened = true; + var self = this, + picker = this.picker.setStyle('display', 'block').set('aria-hidden', 'false') + if (this.shim) this.shim.show(); + this.fireEvent('open'); + if (this.options.useFadeInOut && !noFx){ + picker.get('tween').start('opacity', 1).chain(function(){ + self.fireEvent('show'); + this.callChain(); + }); + } else { + picker.setStyle('opacity', 1); + this.fireEvent('show'); + } + return this; + }, + + show: function(){ + return this.open(true); + }, + + close: function(noFx){ + if (this.opened == false) return this; + this.opened = false; + this.fireEvent('close'); + var self = this, picker = this.picker, hide = function(){ + picker.setStyle('display', 'none').set('aria-hidden', 'true'); + if (self.shim) self.shim.hide(); + self.fireEvent('hide'); + }; + if (this.options.useFadeInOut && !noFx){ + picker.get('tween').start('opacity', 0).chain(hide); + } else { + picker.setStyle('opacity', 0); + hide(); + } + return this; + }, + + hide: function(){ + return this.close(true); + }, + + toggle: function(){ + return this[this.opened == true ? 'close' : 'open'](); + }, + + destroy: function(){ + this.picker.destroy(); + if (this.shim) this.shim.destroy(); + }, + + position: function(x, y){ + var offset = this.options.positionOffset, + scroll = document.getScroll(), + size = document.getSize(), + pickersize = this.picker.getSize(); + + if (typeOf(x) == 'element'){ + var element = x, + where = y || this.options.pickerPosition; + + var elementCoords = element.getCoordinates(); + + x = (where == 'left') ? elementCoords.left - pickersize.x + : (where == 'bottom' || where == 'top') ? elementCoords.left + : elementCoords.right + y = (where == 'bottom') ? elementCoords.bottom + : (where == 'top') ? elementCoords.top - pickersize.y + : elementCoords.top; + } + + x += offset.x * ((where && where == 'left') ? -1 : 1); + y += offset.y * ((where && where == 'top') ? -1: 1); + + if ((x + pickersize.x) > (size.x + scroll.x)) x = (size.x + scroll.x) - pickersize.x; + if ((y + pickersize.y) > (size.y + scroll.y)) y = (size.y + scroll.y) - pickersize.y; + if (x < 0) x = 0; + if (y < 0) y = 0; + + this.picker.setStyles({ + left: x, + top: y + }); + if (this.shim) this.shim.position(); + return this; + }, + + setBodySize: function(){ + var bodysize = this.bodysize = this.body.getSize(); + + this.slider.setStyles({ + width: 2 * bodysize.x, + height: bodysize.y + }); + this.oldContents.setStyles({ + left: bodysize.x, + width: bodysize.x, + height: bodysize.y + }); + this.newContents.setStyles({ + width: bodysize.x, + height: bodysize.y + }); + }, + + setColumnContent: function(column, content){ + var columnElement = this.columns[column]; + if (!columnElement) return this; + + var type = typeOf(content); + if (['string', 'number'].contains(type)) columnElement.set('text', content); + else columnElement.empty().adopt(content); + + return this; + }, + + setColumnsContent: function(content, fx){ + var old = this.columns; + this.columns = this.newColumns; + this.newColumns = old; + + content.forEach(function(_content, i){ + this.setColumnContent(i, _content); + }, this); + return this.setContent(null, fx); + }, + + setColumns: function(columns){ + var _columns = this.columns = new Elements, _newColumns = this.newColumns = new Elements; + for (var i = columns; i--;){ + _columns.push(new Element('div.column').addClass('column_' + (columns - i))); + _newColumns.push(new Element('div.column').addClass('column_' + (columns - i))); + } + + var oldClass = 'column_' + this.options.columns, newClass = 'column_' + columns; + this.picker.removeClass(oldClass).addClass(newClass); + + this.options.columns = columns; + return this; + }, + + setContent: function(content, fx){ + if (content) return this.setColumnsContent([content], fx); + + // swap contents so we can fill the newContents again and animate + var old = this.oldContents; + this.oldContents = this.newContents; + this.newContents = old; + this.newContents.empty(); + + this.newContents.adopt(this.columns); + + this.setBodySize(); + + if (fx){ + this.fx(fx); + } else { + this.slider.setStyle('left', 0); + this.oldContents.setStyles({left: 0, opacity: 0}); + this.newContents.setStyles({left: 0, opacity: 1}); + } + return this; + }, + + fx: function(fx){ + var oldContents = this.oldContents, + newContents = this.newContents, + slider = this.slider, + bodysize = this.bodysize; + if (fx == 'right'){ + oldContents.setStyles({left: 0, opacity: 1}); + newContents.setStyles({left: bodysize.x, opacity: 1}); + slider.setStyle('left', 0).tween('left', 0, -bodysize.x); + } else if (fx == 'left'){ + oldContents.setStyles({left: bodysize.x, opacity: 1}); + newContents.setStyles({left: 0, opacity: 1}); + slider.setStyle('left', -bodysize.x).tween('left', -bodysize.x, 0); + } else if (fx == 'fade'){ + slider.setStyle('left', 0); + oldContents.setStyle('left', 0).set('tween', { + duration: this.options.animationDuration / 2 + }).tween('opacity', 1, 0).get('tween').chain(function(){ + oldContents.setStyle('left', bodysize.x); + }); + newContents.setStyles({opacity: 0, left: 0}).set('tween', { + duration: this.options.animationDuration + }).tween('opacity', 0, 1); + } + }, + + toElement: function(){ + return this.picker; + }, + + setTitle: function(content, fn){ + if (!fn) fn = Function.from; + this.titleText.empty().adopt( + Array.from(content).map(function(item, i){ + return typeOf(item) == 'element' + ? item + : new Element('div.column', {text: fn(item, this.options)}).addClass('column_' + (i + 1)); + }, this) + ); + return this; + }, + + setTitleEvent: function(fn){ + this.titleText.removeEvents('click'); + if (fn) this.titleText.addEvent('click', fn); + this.titleText.setStyle('cursor', fn ? 'pointer' : ''); + return this; + } + +}); diff --git a/datepicker.css b/Source/datepicker.css similarity index 76% rename from datepicker.css rename to Source/datepicker.css index 1c54ebb..e182958 100644 --- a/datepicker.css +++ b/Source/datepicker.css @@ -7,6 +7,23 @@ height: 221px; background: #fff; line-height: normal; + z-index: 3003; +} + +.datepicker.column_2 { + width: 393px; +} + +.datepicker.column_3 { + width: 592px; +} + +.datepicker.column_4 { + width: 791px; +} + +.datepicker.column_5 { + width: 990px; } /* header @@ -17,17 +34,19 @@ height: 21px; padding-top: 4px; margin-bottom: 3px; + overflow: hidden; } .datepicker .header .title { text-align: center; padding-top: 1px; - margin: 0px 42px 0 20px; + position: absolute; + color: #fff; + font-weight: bold; + width: 99999px; } .datepicker .header .titleText { - color: #fff; - font-weight: bold; } .datepicker .header .next, .datepicker .header .previous, @@ -57,12 +76,45 @@ position: relative; top: 0px; left: 0px; - width: 194px; - border-right: 2px solid #fff; height: 193px; overflow: hidden; } +/* Columns */ + +.datepicker .body .column { + float: left; + width: 194px; + min-height: 193px; + margin-left: 5px; +} + +.datepicker .body .column.column_1 { + margin-left: 0; +} + +.datepicker .titleText .column { + float: left; + width: 194px; + margin-left: 5px; +} + +.datepicker .titleText .column.column_1 { + margin-left: 0; +} + +/* Footer */ + +.datepicker.footer { + height: 280px; +} + +.datepicker .footer { + margin-top: 3px; + padding: 15px 5px; + height: 26px; +} + /* time ********************************************************/ .datepicker .time { @@ -207,7 +259,12 @@ color: #fff !important; } -.datepicker .unavailable { +.datepicker .days .otherMonth.selected { + background: #bbbfc8 !important; +} + +.datepicker .unavailable, +.datepicker .body .days .week .day.unavailable:hover { background: #edd !important; color: #b88 !important; cursor: default !important; @@ -218,4 +275,24 @@ .datepicker .years .year:hover { background: #5D6E95 !important; color: #fff !important; -} \ No newline at end of file +} + +.datepicker .days.weeknumbers .day { + width: 22px; +} + +.datepicker .days.weeknumbers .day.weeknumber, +.datepicker .days.weeknumbers .day.weeknumber:hover { + color: #AAA !important; + width: 16px !important; + background: #EEE !important; +} + +.datepicker table { + border-spacing: 0; +} + +.datepicker th, +.datepicker td { + padding: 0; +} diff --git a/Source/datepicker_dashboard/buttons.png b/Source/datepicker_dashboard/buttons.png new file mode 100644 index 0000000..8de1438 Binary files /dev/null and b/Source/datepicker_dashboard/buttons.png differ diff --git a/datepicker_dashboard/datepicker_dashboard.css b/Source/datepicker_dashboard/datepicker_dashboard.css similarity index 97% rename from datepicker_dashboard/datepicker_dashboard.css rename to Source/datepicker_dashboard/datepicker_dashboard.css index 3b8f06f..760910d 100644 --- a/datepicker_dashboard/datepicker_dashboard.css +++ b/Source/datepicker_dashboard/datepicker_dashboard.css @@ -130,6 +130,7 @@ padding-top: 1px; height: 14px; margin: 0 1px 1px 0; + font-weight: normal; } .datepicker_dashboard .days .titles { height: 15px; @@ -243,3 +244,12 @@ cursor: default !important; text-decoration: line-through; } + +.datepicker_dashboard table { + border-spacing: 0; +} + +.datepicker_dashboard th, +.datepicker_dashboard td { + padding: 0; +} diff --git a/Source/datepicker_dashboard/frame.png b/Source/datepicker_dashboard/frame.png new file mode 100644 index 0000000..841e247 Binary files /dev/null and b/Source/datepicker_dashboard/frame.png differ diff --git a/Source/datepicker_jqui/arrows.png b/Source/datepicker_jqui/arrows.png new file mode 100644 index 0000000..d0d259b Binary files /dev/null and b/Source/datepicker_jqui/arrows.png differ diff --git a/datepicker_jqui/datepicker_jqui.css b/Source/datepicker_jqui/datepicker_jqui.css similarity index 98% rename from datepicker_jqui/datepicker_jqui.css rename to Source/datepicker_jqui/datepicker_jqui.css index 2d12d5b..902db5b 100644 --- a/datepicker_jqui/datepicker_jqui.css +++ b/Source/datepicker_jqui/datepicker_jqui.css @@ -278,3 +278,12 @@ color: #ccc !important; border: 1px solid #ccc !important; } + +.datepicker_jqui table { + border-spacing: 0; +} + +.datepicker_jqui th, +.datepicker_jqui td { + padding: 0; +} diff --git a/Source/datepicker_jqui/frame.png b/Source/datepicker_jqui/frame.png new file mode 100644 index 0000000..c8187af Binary files /dev/null and b/Source/datepicker_jqui/frame.png differ diff --git a/Source/datepicker_minimal/datepicker_minimal.css b/Source/datepicker_minimal/datepicker_minimal.css new file mode 100644 index 0000000..b232d0b --- /dev/null +++ b/Source/datepicker_minimal/datepicker_minimal.css @@ -0,0 +1,208 @@ +/* + Minimal Theme + Author: Leon Radley (github.com/leon) + + Selected: #95adb8 + Hover: #c6d2d8 + Current: #f60 +*/ +.datepicker_minimal { + position: absolute; + border: 1px solid #ddd; + font-size: 11px; + width: 220px; + height: 200px; + background: #fff; + line-height: normal; + z-index: 3003; + border-radius: 2px; + -webkit-border-radius: 2px; + -moz-border-radius: 2px; +} + .datepicker_minimal th, + .datepicker_minimal td { + margin: 0; + padding: 0; + } + .datepicker_minimal .selected { + background: #95adb8 !important; + color: #fff !important; + } + + .datepicker_minimal .unavailable, + .datepicker_minimal .day.unavailable:hover { + background: #edd !important; + color: #b88 !important; + cursor: default !important; + } + + .datepicker_minimal td.day:hover, + .datepicker_minimal .month:hover, + .datepicker_minimal .year:hover { + background: #c6d2d8 !important; + color: #222 !important; + } + +/* + Header +*/ +.datepicker_minimal .header { + position: relative; + background: #333; + height: 25px; +} + .datepicker_minimal .header .title { + margin: 0 50px; + text-align: center; + line-height: 25px; + } + .datepicker_minimal .header .titleText { + color: #fff; + font-weight: bold; + } + .datepicker_minimal .header .next, + .datepicker_minimal .header .previous, + .datepicker_minimal .header .closeButton { + position: absolute; + top: 0; + width: 25px; + height: 25px; + line-height: 25px; + text-align: center; + color: #fff; + cursor: pointer; + } + .datepicker_minimal .header .previous { + left: 0; + } + .datepicker_minimal .header .next { + right: 25px; + } + .datepicker_minimal .header .closeButton { + right: 0; + } + +/* + Body +*/ +.datepicker_minimal .body { + position: relative; + top: 0; + left: 0; + width: 100%; + height: 175px; + overflow: hidden; +} + .datepicker_minimal .body .column { + height: 100%; + } + +/* + Days +*/ +.datepicker_minimal .days { + width: 100%; + border: 0; + border-spacing: 0; +} + .datepicker_minimal .days .title { + font-weight: bold; + color: #444; + cursor: default; + width: 14.2857142857143%; /* 100/7 */ + } + .datepicker_minimal .days.weeknumbers .title { + width: 12.5%; + } + .datepicker_minimal .days .otherMonth { + background: #eee; + color: #aaa; + } + .datepicker_minimal .day { + cursor: pointer; + text-align: center; + overflow: hidden; + line-height: 25px; + } + .datepicker_minimal .today { + color: #f60; + font-weight: bold; + } + .datepicker_minimal .day0 { + margin-right: 0; + } + .datepicker_minimal .days .week5 .day { + margin-bottom: 0; + } + +/* + Months +*/ +.datepicker_minimal .months { + height: 100%; +} + .datepicker_minimal .month { + float: left; + display: inline; + width: 33.33333333%; + line-height: 44px; + cursor: pointer; + vertical-align: middle; + text-align: center; + overflow: hidden; + } + +/* + Years +*/ +.datepicker_minimal .years { + height: 100%; +} + .datepicker_minimal .year { + float: left; + display: inline; + width: 25%; + line-height: 35px; + cursor: pointer; + text-align: center; + overflow: hidden; + } + +/* + Time +*/ +.datepicker_minimal .time { + width: 100%; + height: 100%; + background: #fff; +} + .datepicker_minimal .time .hour, + .datepicker_minimal .time .separator, + .datepicker_minimal .time .minutes { + position: absolute; + top: 50px; + width: 50px; + border: 1px dashed #ddd; + font-size: 32px; + text-align: center; + } + .datepicker_minimal .time .hour { + left: 40px; + } + .datepicker_minimal .time .separator { + background: transparent; + border: 0px; + width: 10px; + left: 100px; + } + + .datepicker_minimal .time .minutes { + left: 120px; + } + .datepicker_minimal .time .ok { + position: absolute; + top: 105px; + width: 136px; + left: 40px; + font-size: 20px; + } diff --git a/Source/datepicker_vista/buttons.png b/Source/datepicker_vista/buttons.png new file mode 100644 index 0000000..a26e8fa Binary files /dev/null and b/Source/datepicker_vista/buttons.png differ diff --git a/datepicker_vista/datepicker_vista.css b/Source/datepicker_vista/datepicker_vista.css similarity index 97% rename from datepicker_vista/datepicker_vista.css rename to Source/datepicker_vista/datepicker_vista.css index ae02e6f..9276730 100644 --- a/datepicker_vista/datepicker_vista.css +++ b/Source/datepicker_vista/datepicker_vista.css @@ -245,3 +245,12 @@ color: #fbb !important; cursor: default !important; } + +.datepicker_vista table { + border-spacing: 0; +} + +.datepicker_vista th, +.datepicker_vista td { + padding: 0; +} diff --git a/Source/datepicker_vista/days.png b/Source/datepicker_vista/days.png new file mode 100644 index 0000000..ce768f6 Binary files /dev/null and b/Source/datepicker_vista/days.png differ diff --git a/Source/datepicker_vista/frame.png b/Source/datepicker_vista/frame.png new file mode 100644 index 0000000..eff17d8 Binary files /dev/null and b/Source/datepicker_vista/frame.png differ diff --git a/Source/datepicker_vista/months.png b/Source/datepicker_vista/months.png new file mode 100644 index 0000000..0a1585b Binary files /dev/null and b/Source/datepicker_vista/months.png differ diff --git a/Source/datepicker_vista/years.png b/Source/datepicker_vista/years.png new file mode 100644 index 0000000..cfb061d Binary files /dev/null and b/Source/datepicker_vista/years.png differ diff --git a/Test/Locale.nl-NL.Date.js b/Test/Locale.nl-NL.Date.js new file mode 100644 index 0000000..b8c4e06 --- /dev/null +++ b/Test/Locale.nl-NL.Date.js @@ -0,0 +1,67 @@ +/* +--- + +name: Locale.nl-NL.Date + +description: Date messages for Dutch. + +license: MIT-style license + +authors: + - Lennart Pilon + - Tim Wienk + +requires: + - /Locale + +provides: [Locale.nl-NL.Date] + +... +*/ + +Locale.define('nl-NL', 'Date', { + + months: ['januari', 'februari', 'maart', 'april', 'mei', 'juni', 'juli', 'augustus', 'september', 'oktober', 'november', 'december'], + months_abbr: ['jan', 'feb', 'mrt', 'apr', 'mei', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'], + days: ['zondag', 'maandag', 'dinsdag', 'woensdag', 'donderdag', 'vrijdag', 'zaterdag'], + days_abbr: ['zo', 'ma', 'di', 'wo', 'do', 'vr', 'za'], + + // Culture's date order: DD-MM-YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d-%m-%Y', + shortTime: '%H:%M', + AM: 'AM', + PM: 'PM', + + // Date.Extras + ordinal: 'e', + + lessThanMinuteAgo: 'minder dan een minuut geleden', + minuteAgo: 'ongeveer een minuut geleden', + minutesAgo: '{delta} minuten geleden', + hourAgo: 'ongeveer een uur geleden', + hoursAgo: 'ongeveer {delta} uur geleden', + dayAgo: 'een dag geleden', + daysAgo: '{delta} dagen geleden', + weekAgo: 'een week geleden', + weeksAgo: '{delta} weken geleden', + monthAgo: 'een maand geleden', + monthsAgo: '{delta} maanden geleden', + yearAgo: 'een jaar geleden', + yearsAgo: '{delta} jaar geleden', + + lessThanMinuteUntil: 'over minder dan een minuut', + minuteUntil: 'over ongeveer een minuut', + minutesUntil: 'over {delta} minuten', + hourUntil: 'over ongeveer een uur', + hoursUntil: 'over {delta} uur', + dayUntil: 'over ongeveer een dag', + daysUntil: 'over {delta} dagen', + weekUntil: 'over een week', + weeksUntil: 'over {delta} weken', + monthUntil: 'over een maand', + monthsUntil: 'over {delta} maanden', + yearUntil: 'over een jaar', + yearsUntil: 'over {delta} jaar' + +}); diff --git a/Test/columns.html b/Test/columns.html new file mode 100644 index 0000000..136f9e6 --- /dev/null +++ b/Test/columns.html @@ -0,0 +1,39 @@ + + +
+ +
+ Inputs:
+
+
+ This shows the different Events DatePicker fires. You can use the onSelect
event if you want to
+ display the selected date elsewhere for example.
+
+ Inputs: + +
+ + + + \ No newline at end of file diff --git a/Test/full.html b/Test/full.html new file mode 100644 index 0000000..5ff6097 --- /dev/null +++ b/Test/full.html @@ -0,0 +1,51 @@ + + + + +
+ This is a demo of a all the pickers, including years, months, days and time.
+ You should begin at the days view, but you can click the title to go up to months and years.
+ If you've selected a day, you should see the timepicker.
+
+ Inputs:
+
+
+ Empty default value:
+
+
+ Select one of the pickers you would like to see. The demo will load in the iframe. +
+ + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Click to attach the datepicker to this field
+Click to detach the datepicker from this field
+Show datepicker (after you attached it)
+
+
+
+
+
+
+
+
+Toggle
+
+
+
+
+
+
+
+
+
+
+ This datepicker should parse the date as february 8th 1991 (dd-mm-yyyy). And should format it that way when it is ready. + The month and day names should be localized as well. +
+ ++ Inputs: + +
+ ++ This demo uses a custom set of day/month name abbreviations +
+ ++ Inputs: + +
+ ++ This demo uses custom titlebar texts +
+ ++ Inputs: +
+ + + \ No newline at end of file diff --git a/Test/minmaxdate.html b/Test/minmaxdate.html new file mode 100644 index 0000000..4f717d7 --- /dev/null +++ b/Test/minmaxdate.html @@ -0,0 +1,122 @@ + + + + +
+ This demo shows the minDate
and maxDate
options. You can set them to a certain date so the user
+ cannot select a date outside that limits. The limits are set to and
.
+
+ Input:
+
+
+ Input (option is set as string):
+
+
+ Input (only a few dates are available):
+
+
+ Input (invert previously available dates):
+
+
+ Input with only maxDate:
+
+
+ This test shows a monthpicker only. It should parse the default month (December) and it should format it that way.
+ You can go up to the years picker, but you cannot pick a specific day.
+
+ Inputs: + +
+ + + + \ No newline at end of file diff --git a/Test/mootools-core.js b/Test/mootools-core.js new file mode 100644 index 0000000..74540cb --- /dev/null +++ b/Test/mootools-core.js @@ -0,0 +1,4942 @@ +/* +--- +MooTools: the javascript framework + +web build: + - http://mootools.net/core/1b88678fe598d7ab8e7d33b28e786893 + +packager build: + - packager build Core/Core Core/Array Core/String Core/Number Core/Function Core/Object Core/Event Core/Browser Core/Class Core/Class.Extras Core/Slick.Parser Core/Slick.Finder Core/Element Core/Element.Style Core/Element.Event Core/Element.Dimensions Core/Fx Core/Fx.CSS Core/Fx.Tween Core/Fx.Morph Core/Fx.Transitions Core/DOMReady + +/* +--- + +name: Core + +description: The heart of MooTools. + +license: MIT-style license. + +copyright: Copyright (c) 2006-2010 [Valerio Proietti](http://mad4milk.net/). + +authors: The MooTools production team (http://mootools.net/developers/) + +inspiration: + - Class implementation inspired by [Base.js](http://dean.edwards.name/weblog/2006/03/base/) Copyright (c) 2006 Dean Edwards, [GNU Lesser General Public License](http://opensource.org/licenses/lgpl-license.php) + - Some functionality inspired by [Prototype.js](http://prototypejs.org) Copyright (c) 2005-2007 Sam Stephenson, [MIT License](http://opensource.org/licenses/mit-license.php) + +provides: [Core, MooTools, Type, typeOf, instanceOf, Native] + +... +*/ + +(function(){ + +this.MooTools = { + version: '1.4.1', + build: 'd1fb25710e3c5482a219ab9dc675a4e0ad2176b6' +}; + +// typeOf, instanceOf + +var typeOf = this.typeOf = function(item){ + if (item == null) return 'null'; + if (item.$family) return item.$family(); + + if (item.nodeName){ + if (item.nodeType == 1) return 'element'; + if (item.nodeType == 3) return (/\S/).test(item.nodeValue) ? 'textnode' : 'whitespace'; + } else if (typeof item.length == 'number'){ + if (item.callee) return 'arguments'; + if ('item' in item) return 'collection'; + } + + return typeof item; +}; + +var instanceOf = this.instanceOf = function(item, object){ + if (item == null) return false; + var constructor = item.$constructor || item.constructor; + while (constructor){ + if (constructor === object) return true; + constructor = constructor.parent; + } + return item instanceof object; +}; + +// Function overloading + +var Function = this.Function; + +var enumerables = true; +for (var i in {toString: 1}) enumerables = null; +if (enumerables) enumerables = ['hasOwnProperty', 'valueOf', 'isPrototypeOf', 'propertyIsEnumerable', 'toLocaleString', 'toString', 'constructor']; + +Function.prototype.overloadSetter = function(usePlural){ + var self = this; + return function(a, b){ + if (a == null) return this; + if (usePlural || typeof a != 'string'){ + for (var k in a) self.call(this, k, a[k]); + if (enumerables) for (var i = enumerables.length; i--;){ + k = enumerables[i]; + if (a.hasOwnProperty(k)) self.call(this, k, a[k]); + } + } else { + self.call(this, a, b); + } + return this; + }; +}; + +Function.prototype.overloadGetter = function(usePlural){ + var self = this; + return function(a){ + var args, result; + if (usePlural || typeof a != 'string') args = a; + else if (arguments.length > 1) args = arguments; + if (args){ + result = {}; + for (var i = 0; i < args.length; i++) result[args[i]] = self.call(this, args[i]); + } else { + result = self.call(this, a); + } + return result; + }; +}; + +Function.prototype.extend = function(key, value){ + this[key] = value; +}.overloadSetter(); + +Function.prototype.implement = function(key, value){ + this.prototype[key] = value; +}.overloadSetter(); + +// From + +var slice = Array.prototype.slice; + +Function.from = function(item){ + return (typeOf(item) == 'function') ? item : function(){ + return item; + }; +}; + +Array.from = function(item){ + if (item == null) return []; + return (Type.isEnumerable(item) && typeof item != 'string') ? (typeOf(item) == 'array') ? item : slice.call(item) : [item]; +}; + +Number.from = function(item){ + var number = parseFloat(item); + return isFinite(number) ? number : null; +}; + +String.from = function(item){ + return item + ''; +}; + +// hide, protect + +Function.implement({ + + hide: function(){ + this.$hidden = true; + return this; + }, + + protect: function(){ + this.$protected = true; + return this; + } + +}); + +// Type + +var Type = this.Type = function(name, object){ + if (name){ + var lower = name.toLowerCase(); + var typeCheck = function(item){ + return (typeOf(item) == lower); + }; + + Type['is' + name] = typeCheck; + if (object != null){ + object.prototype.$family = (function(){ + return lower; + }).hide(); + + } + } + + if (object == null) return null; + + object.extend(this); + object.$constructor = Type; + object.prototype.$constructor = object; + + return object; +}; + +var toString = Object.prototype.toString; + +Type.isEnumerable = function(item){ + return (item != null && typeof item.length == 'number' && toString.call(item) != '[object Function]' ); +}; + +var hooks = {}; + +var hooksOf = function(object){ + var type = typeOf(object.prototype); + return hooks[type] || (hooks[type] = []); +}; + +var implement = function(name, method){ + if (method && method.$hidden) return; + + var hooks = hooksOf(this); + + for (var i = 0; i < hooks.length; i++){ + var hook = hooks[i]; + if (typeOf(hook) == 'type') implement.call(hook, name, method); + else hook.call(this, name, method); + } + + var previous = this.prototype[name]; + if (previous == null || !previous.$protected) this.prototype[name] = method; + + if (this[name] == null && typeOf(method) == 'function') extend.call(this, name, function(item){ + return method.apply(item, slice.call(arguments, 1)); + }); +}; + +var extend = function(name, method){ + if (method && method.$hidden) return; + var previous = this[name]; + if (previous == null || !previous.$protected) this[name] = method; +}; + +Type.implement({ + + implement: implement.overloadSetter(), + + extend: extend.overloadSetter(), + + alias: function(name, existing){ + implement.call(this, name, this.prototype[existing]); + }.overloadSetter(), + + mirror: function(hook){ + hooksOf(this).push(hook); + return this; + } + +}); + +new Type('Type', Type); + +// Default Types + +var force = function(name, object, methods){ + var isType = (object != Object), + prototype = object.prototype; + + if (isType) object = new Type(name, object); + + for (var i = 0, l = methods.length; i < l; i++){ + var key = methods[i], + generic = object[key], + proto = prototype[key]; + + if (generic) generic.protect(); + + if (isType && proto){ + delete prototype[key]; + prototype[key] = proto.protect(); + } + } + + if (isType) object.implement(prototype); + + return force; +}; + +force('String', String, [ + 'charAt', 'charCodeAt', 'concat', 'indexOf', 'lastIndexOf', 'match', 'quote', 'replace', 'search', + 'slice', 'split', 'substr', 'substring', 'trim', 'toLowerCase', 'toUpperCase' +])('Array', Array, [ + 'pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift', 'concat', 'join', 'slice', + 'indexOf', 'lastIndexOf', 'filter', 'forEach', 'every', 'map', 'some', 'reduce', 'reduceRight' +])('Number', Number, [ + 'toExponential', 'toFixed', 'toLocaleString', 'toPrecision' +])('Function', Function, [ + 'apply', 'call', 'bind' +])('RegExp', RegExp, [ + 'exec', 'test' +])('Object', Object, [ + 'create', 'defineProperty', 'defineProperties', 'keys', + 'getPrototypeOf', 'getOwnPropertyDescriptor', 'getOwnPropertyNames', + 'preventExtensions', 'isExtensible', 'seal', 'isSealed', 'freeze', 'isFrozen' +])('Date', Date, ['now']); + +Object.extend = extend.overloadSetter(); + +Date.extend('now', function(){ + return +(new Date); +}); + +new Type('Boolean', Boolean); + +// fixes NaN returning as Number + +Number.prototype.$family = function(){ + return isFinite(this) ? 'number' : 'null'; +}.hide(); + +// Number.random + +Number.extend('random', function(min, max){ + return Math.floor(Math.random() * (max - min + 1) + min); +}); + +// forEach, each + +var hasOwnProperty = Object.prototype.hasOwnProperty; +Object.extend('forEach', function(object, fn, bind){ + for (var key in object){ + if (hasOwnProperty.call(object, key)) fn.call(bind, object[key], key, object); + } +}); + +Object.each = Object.forEach; + +Array.implement({ + + forEach: function(fn, bind){ + for (var i = 0, l = this.length; i < l; i++){ + if (i in this) fn.call(bind, this[i], i, this); + } + }, + + each: function(fn, bind){ + Array.forEach(this, fn, bind); + return this; + } + +}); + +// Array & Object cloning, Object merging and appending + +var cloneOf = function(item){ + switch (typeOf(item)){ + case 'array': return item.clone(); + case 'object': return Object.clone(item); + default: return item; + } +}; + +Array.implement('clone', function(){ + var i = this.length, clone = new Array(i); + while (i--) clone[i] = cloneOf(this[i]); + return clone; +}); + +var mergeOne = function(source, key, current){ + switch (typeOf(current)){ + case 'object': + if (typeOf(source[key]) == 'object') Object.merge(source[key], current); + else source[key] = Object.clone(current); + break; + case 'array': source[key] = current.clone(); break; + default: source[key] = current; + } + return source; +}; + +Object.extend({ + + merge: function(source, k, v){ + if (typeOf(k) == 'string') return mergeOne(source, k, v); + for (var i = 1, l = arguments.length; i < l; i++){ + var object = arguments[i]; + for (var key in object) mergeOne(source, key, object[key]); + } + return source; + }, + + clone: function(object){ + var clone = {}; + for (var key in object) clone[key] = cloneOf(object[key]); + return clone; + }, + + append: function(original){ + for (var i = 1, l = arguments.length; i < l; i++){ + var extended = arguments[i] || {}; + for (var key in extended) original[key] = extended[key]; + } + return original; + } + +}); + +// Object-less types + +['Object', 'WhiteSpace', 'TextNode', 'Collection', 'Arguments'].each(function(name){ + new Type(name); +}); + +// Unique ID + +var UID = Date.now(); + +String.extend('uniqueID', function(){ + return (UID++).toString(36); +}); + + + +})(); + + +/* +--- + +name: Array + +description: Contains Array Prototypes like each, contains, and erase. + +license: MIT-style license. + +requires: Type + +provides: Array + +... +*/ + +Array.implement({ + + /**/ + every: function(fn, bind){ + for (var i = 0, l = this.length >>> 0; i < l; i++){ + if ((i in this) && !fn.call(bind, this[i], i, this)) return false; + } + return true; + }, + + filter: function(fn, bind){ + var results = []; + for (var i = 0, l = this.length >>> 0; i < l; i++){ + if ((i in this) && fn.call(bind, this[i], i, this)) results.push(this[i]); + } + return results; + }, + + indexOf: function(item, from){ + var length = this.length >>> 0; + for (var i = (from < 0) ? Math.max(0, length + from) : from || 0; i < length; i++){ + if (this[i] === item) return i; + } + return -1; + }, + + map: function(fn, bind){ + var length = this.length >>> 0, results = Array(length); + for (var i = 0; i < length; i++){ + if (i in this) results[i] = fn.call(bind, this[i], i, this); + } + return results; + }, + + some: function(fn, bind){ + for (var i = 0, l = this.length >>> 0; i < l; i++){ + if ((i in this) && fn.call(bind, this[i], i, this)) return true; + } + return false; + }, + /*!ES5>*/ + + clean: function(){ + return this.filter(function(item){ + return item != null; + }); + }, + + invoke: function(methodName){ + var args = Array.slice(arguments, 1); + return this.map(function(item){ + return item[methodName].apply(item, args); + }); + }, + + associate: function(keys){ + var obj = {}, length = Math.min(this.length, keys.length); + for (var i = 0; i < length; i++) obj[keys[i]] = this[i]; + return obj; + }, + + link: function(object){ + var result = {}; + for (var i = 0, l = this.length; i < l; i++){ + for (var key in object){ + if (object[key](this[i])){ + result[key] = this[i]; + delete object[key]; + break; + } + } + } + return result; + }, + + contains: function(item, from){ + return this.indexOf(item, from) != -1; + }, + + append: function(array){ + this.push.apply(this, array); + return this; + }, + + getLast: function(){ + return (this.length) ? this[this.length - 1] : null; + }, + + getRandom: function(){ + return (this.length) ? this[Number.random(0, this.length - 1)] : null; + }, + + include: function(item){ + if (!this.contains(item)) this.push(item); + return this; + }, + + combine: function(array){ + for (var i = 0, l = array.length; i < l; i++) this.include(array[i]); + return this; + }, + + erase: function(item){ + for (var i = this.length; i--;){ + if (this[i] === item) this.splice(i, 1); + } + return this; + }, + + empty: function(){ + this.length = 0; + return this; + }, + + flatten: function(){ + var array = []; + for (var i = 0, l = this.length; i < l; i++){ + var type = typeOf(this[i]); + if (type == 'null') continue; + array = array.concat((type == 'array' || type == 'collection' || type == 'arguments' || instanceOf(this[i], Array)) ? Array.flatten(this[i]) : this[i]); + } + return array; + }, + + pick: function(){ + for (var i = 0, l = this.length; i < l; i++){ + if (this[i] != null) return this[i]; + } + return null; + }, + + hexToRgb: function(array){ + if (this.length != 3) return null; + var rgb = this.map(function(value){ + if (value.length == 1) value += value; + return value.toInt(16); + }); + return (array) ? rgb : 'rgb(' + rgb + ')'; + }, + + rgbToHex: function(array){ + if (this.length < 3) return null; + if (this.length == 4 && this[3] == 0 && !array) return 'transparent'; + var hex = []; + for (var i = 0; i < 3; i++){ + var bit = (this[i] - 0).toString(16); + hex.push((bit.length == 1) ? '0' + bit : bit); + } + return (array) ? hex : '#' + hex.join(''); + } + +}); + + + + +/* +--- + +name: String + +description: Contains String Prototypes like camelCase, capitalize, test, and toInt. + +license: MIT-style license. + +requires: Type + +provides: String + +... +*/ + +String.implement({ + + test: function(regex, params){ + return ((typeOf(regex) == 'regexp') ? regex : new RegExp('' + regex, params)).test(this); + }, + + contains: function(string, separator){ + return (separator) ? (separator + this + separator).indexOf(separator + string + separator) > -1 : String(this).indexOf(string) > -1; + }, + + trim: function(){ + return String(this).replace(/^\s+|\s+$/g, ''); + }, + + clean: function(){ + return String(this).replace(/\s+/g, ' ').trim(); + }, + + camelCase: function(){ + return String(this).replace(/-\D/g, function(match){ + return match.charAt(1).toUpperCase(); + }); + }, + + hyphenate: function(){ + return String(this).replace(/[A-Z]/g, function(match){ + return ('-' + match.charAt(0).toLowerCase()); + }); + }, + + capitalize: function(){ + return String(this).replace(/\b[a-z]/g, function(match){ + return match.toUpperCase(); + }); + }, + + escapeRegExp: function(){ + return String(this).replace(/([-.*+?^${}()|[\]\/\\])/g, '\\$1'); + }, + + toInt: function(base){ + return parseInt(this, base || 10); + }, + + toFloat: function(){ + return parseFloat(this); + }, + + hexToRgb: function(array){ + var hex = String(this).match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/); + return (hex) ? hex.slice(1).hexToRgb(array) : null; + }, + + rgbToHex: function(array){ + var rgb = String(this).match(/\d{1,3}/g); + return (rgb) ? rgb.rgbToHex(array) : null; + }, + + substitute: function(object, regexp){ + return String(this).replace(regexp || (/\\?\{([^{}]+)\}/g), function(match, name){ + if (match.charAt(0) == '\\') return match.slice(1); + return (object[name] != null) ? object[name] : ''; + }); + } + +}); + + +/* +--- + +name: Number + +description: Contains Number Prototypes like limit, round, times, and ceil. + +license: MIT-style license. + +requires: Type + +provides: Number + +... +*/ + +Number.implement({ + + limit: function(min, max){ + return Math.min(max, Math.max(min, this)); + }, + + round: function(precision){ + precision = Math.pow(10, precision || 0).toFixed(precision < 0 ? -precision : 0); + return Math.round(this * precision) / precision; + }, + + times: function(fn, bind){ + for (var i = 0; i < this; i++) fn.call(bind, i, this); + }, + + toFloat: function(){ + return parseFloat(this); + }, + + toInt: function(base){ + return parseInt(this, base || 10); + } + +}); + +Number.alias('each', 'times'); + +(function(math){ + var methods = {}; + math.each(function(name){ + if (!Number[name]) methods[name] = function(){ + return Math[name].apply(null, [this].concat(Array.from(arguments))); + }; + }); + Number.implement(methods); +})(['abs', 'acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'exp', 'floor', 'log', 'max', 'min', 'pow', 'sin', 'sqrt', 'tan']); + + +/* +--- + +name: Function + +description: Contains Function Prototypes like create, bind, pass, and delay. + +license: MIT-style license. + +requires: Type + +provides: Function + +... +*/ + +Function.extend({ + + attempt: function(){ + for (var i = 0, l = arguments.length; i < l; i++){ + try { + return arguments[i](); + } catch (e){} + } + return null; + } + +}); + +Function.implement({ + + attempt: function(args, bind){ + try { + return this.apply(bind, Array.from(args)); + } catch (e){} + + return null; + }, + + /**/ + bind: function(that){ + var self = this, + args = arguments.length > 1 ? Array.slice(arguments, 1) : null, + F = function(){}; + + var bound = function(){ + var context = that, length = arguments.length; + if (this instanceof bound){ + F.prototype = self.prototype; + context = new F; + } + var result = (!args && !length) + ? self.call(context) + : self.apply(context, args && length ? args.concat(Array.slice(arguments)) : args || arguments); + return context == that ? result : context; + }; + return bound; + }, + /*!ES5-bind>*/ + + pass: function(args, bind){ + var self = this; + if (args != null) args = Array.from(args); + return function(){ + return self.apply(bind, args || arguments); + }; + }, + + delay: function(delay, bind, args){ + return setTimeout(this.pass((args == null ? [] : args), bind), delay); + }, + + periodical: function(periodical, bind, args){ + return setInterval(this.pass((args == null ? [] : args), bind), periodical); + } + +}); + + + + +/* +--- + +name: Object + +description: Object generic methods + +license: MIT-style license. + +requires: Type + +provides: [Object, Hash] + +... +*/ + +(function(){ + +var hasOwnProperty = Object.prototype.hasOwnProperty; + +Object.extend({ + + subset: function(object, keys){ + var results = {}; + for (var i = 0, l = keys.length; i < l; i++){ + var k = keys[i]; + if (k in object) results[k] = object[k]; + } + return results; + }, + + map: function(object, fn, bind){ + var results = {}; + for (var key in object){ + if (hasOwnProperty.call(object, key)) results[key] = fn.call(bind, object[key], key, object); + } + return results; + }, + + filter: function(object, fn, bind){ + var results = {}; + for (var key in object){ + var value = object[key]; + if (hasOwnProperty.call(object, key) && fn.call(bind, value, key, object)) results[key] = value; + } + return results; + }, + + every: function(object, fn, bind){ + for (var key in object){ + if (hasOwnProperty.call(object, key) && !fn.call(bind, object[key], key)) return false; + } + return true; + }, + + some: function(object, fn, bind){ + for (var key in object){ + if (hasOwnProperty.call(object, key) && fn.call(bind, object[key], key)) return true; + } + return false; + }, + + keys: function(object){ + var keys = []; + for (var key in object){ + if (hasOwnProperty.call(object, key)) keys.push(key); + } + return keys; + }, + + values: function(object){ + var values = []; + for (var key in object){ + if (hasOwnProperty.call(object, key)) values.push(object[key]); + } + return values; + }, + + getLength: function(object){ + return Object.keys(object).length; + }, + + keyOf: function(object, value){ + for (var key in object){ + if (hasOwnProperty.call(object, key) && object[key] === value) return key; + } + return null; + }, + + contains: function(object, value){ + return Object.keyOf(object, value) != null; + }, + + toQueryString: function(object, base){ + var queryString = []; + + Object.each(object, function(value, key){ + if (base) key = base + '[' + key + ']'; + var result; + switch (typeOf(value)){ + case 'object': result = Object.toQueryString(value, key); break; + case 'array': + var qs = {}; + value.each(function(val, i){ + qs[i] = val; + }); + result = Object.toQueryString(qs, key); + break; + default: result = key + '=' + encodeURIComponent(value); + } + if (value != null) queryString.push(result); + }); + + return queryString.join('&'); + } + +}); + +})(); + + + + +/* +--- + +name: Browser + +description: The Browser Object. Contains Browser initialization, Window and Document, and the Browser Hash. + +license: MIT-style license. + +requires: [Array, Function, Number, String] + +provides: [Browser, Window, Document] + +... +*/ + +(function(){ + +var document = this.document; +var window = document.window = this; + +var UID = 1; + +this.$uid = (window.ActiveXObject) ? function(item){ + return (item.uid || (item.uid = [UID++]))[0]; +} : function(item){ + return item.uid || (item.uid = UID++); +}; + +$uid(window); +$uid(document); + +var ua = navigator.userAgent.toLowerCase(), + platform = navigator.platform.toLowerCase(), + UA = ua.match(/(opera|ie|firefox|chrome|version)[\s\/:]([\w\d\.]+)?.*?(safari|version[\s\/:]([\w\d\.]+)|$)/) || [null, 'unknown', 0], + mode = UA[1] == 'ie' && document.documentMode; + +var Browser = this.Browser = { + + extend: Function.prototype.extend, + + name: (UA[1] == 'version') ? UA[3] : UA[1], + + version: mode || parseFloat((UA[1] == 'opera' && UA[4]) ? UA[4] : UA[2]), + + Platform: { + name: ua.match(/ip(?:ad|od|hone)/) ? 'ios' : (ua.match(/(?:webos|android)/) || platform.match(/mac|win|linux/) || ['other'])[0] + }, + + Features: { + xpath: !!(document.evaluate), + air: !!(window.runtime), + query: !!(document.querySelector), + json: !!(window.JSON) + }, + + Plugins: {} + +}; + +Browser[Browser.name] = true; +Browser[Browser.name + parseInt(Browser.version, 10)] = true; +Browser.Platform[Browser.Platform.name] = true; + +// Request + +Browser.Request = (function(){ + + var XMLHTTP = function(){ + return new XMLHttpRequest(); + }; + + var MSXML2 = function(){ + return new ActiveXObject('MSXML2.XMLHTTP'); + }; + + var MSXML = function(){ + return new ActiveXObject('Microsoft.XMLHTTP'); + }; + + return Function.attempt(function(){ + XMLHTTP(); + return XMLHTTP; + }, function(){ + MSXML2(); + return MSXML2; + }, function(){ + MSXML(); + return MSXML; + }); + +})(); + +Browser.Features.xhr = !!(Browser.Request); + +// Flash detection + +var version = (Function.attempt(function(){ + return navigator.plugins['Shockwave Flash'].description; +}, function(){ + return new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version'); +}) || '0 r0').match(/\d+/g); + +Browser.Plugins.Flash = { + version: Number(version[0] || '0.' + version[1]) || 0, + build: Number(version[2]) || 0 +}; + +// String scripts + +Browser.exec = function(text){ + if (!text) return text; + if (window.execScript){ + window.execScript(text); + } else { + var script = document.createElement('script'); + script.setAttribute('type', 'text/javascript'); + script.text = text; + document.head.appendChild(script); + document.head.removeChild(script); + } + return text; +}; + +String.implement('stripScripts', function(exec){ + var scripts = ''; + var text = this.replace(/ + + + + + + + + + + + + ++ Manual: + Open + Close + Toggle +
+ ++ Links: + Open 1 + Open 2 + Open 3 +
+ ++ Inputs: + + + +
+ ++ The picker should float over the select item in IE6. + You can drag the Picker over the select box. +
+ +
+ You should be able to edit this text. The text of the above input fields
+ should not be editable.
+
+