diff --git a/ionic/components.ios.scss b/ionic/components.ios.scss
index 797def8943..d92468af94 100644
--- a/ionic/components.ios.scss
+++ b/ionic/components.ios.scss
@@ -14,6 +14,7 @@
"components/checkbox/checkbox.ios",
"components/chip/chip.ios",
"components/content/content.ios",
+ "components/datetime/datetime.ios",
"components/input/input.ios",
"components/item/item.ios",
"components/label/label.ios",
diff --git a/ionic/components.ts b/ionic/components.ts
index cb5185b5d3..75898f1580 100644
--- a/ionic/components.ts
+++ b/ionic/components.ts
@@ -5,6 +5,7 @@ export * from './components/badge/badge';
export * from './components/button/button';
export * from './components/checkbox/checkbox';
export * from './components/content/content';
+export * from './components/datetime/datetime';
export * from './components/icon/icon';
export * from './components/img/img';
export * from './components/infinite-scroll/infinite-scroll';
diff --git a/ionic/components/datetime/datetime.ios.scss b/ionic/components/datetime/datetime.ios.scss
new file mode 100644
index 0000000000..a51b089746
--- /dev/null
+++ b/ionic/components/datetime/datetime.ios.scss
@@ -0,0 +1,15 @@
+@import "../../globals.ios";
+@import "./datetime";
+
+// iOS DateTime
+// --------------------------------------------------
+
+$datetime-ios-padding-top: $item-ios-padding-top !default;
+$datetime-ios-padding-right: ($item-ios-padding-right / 2) !default;
+$datetime-ios-padding-bottom: $item-ios-padding-bottom !default;
+$datetime-ios-padding-left: $item-ios-padding-left !default;
+
+
+ion-datetime {
+ padding: $datetime-ios-padding-top $datetime-ios-padding-right $datetime-ios-padding-bottom $datetime-ios-padding-left;
+}
diff --git a/ionic/components/datetime/datetime.scss b/ionic/components/datetime/datetime.scss
new file mode 100644
index 0000000000..4abc2cc8a2
--- /dev/null
+++ b/ionic/components/datetime/datetime.scss
@@ -0,0 +1,30 @@
+@import "../../globals.core";
+
+// DateTime
+// --------------------------------------------------
+
+ion-datetime {
+ display: flex;
+ overflow: hidden;
+
+ max-width: 45%;
+}
+
+.datetime-text {
+ overflow: hidden;
+
+ flex: 1;
+
+ min-width: 16px;
+
+ font-size: inherit;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.datetime-disabled,
+.item-datetime-disabled ion-label {
+ opacity: .4;
+
+ pointer-events: none;
+}
diff --git a/ionic/components/datetime/datetime.ts b/ionic/components/datetime/datetime.ts
new file mode 100644
index 0000000000..a870ea5f46
--- /dev/null
+++ b/ionic/components/datetime/datetime.ts
@@ -0,0 +1,888 @@
+import {Component, Optional, ElementRef, Renderer, Input, Output, Provider, forwardRef, EventEmitter, HostListener, ViewEncapsulation} from 'angular2/core';
+import {NG_VALUE_ACCESSOR} from 'angular2/common';
+
+import {Config} from '../../config/config';
+import {Picker, PickerColumn, PickerColumnOption} from '../picker/picker';
+import {Form} from '../../util/form';
+import {Item} from '../item/item';
+import {merge, isBlank, isPresent, isTrueProperty, isArray, isString} from '../../util/util';
+import {dateValueRange, renderDateTime, renderTextFormat, convertFormatToKey, getValueFromFormat, parseTemplate, parseDate, updateDate, DateTimeData, convertDataToISO, daysInMonth, dateSortValue, dateDataSortValue, LocaleData} from '../../util/datetime-util';
+import {NavController} from '../nav/nav-controller';
+
+const DATETIME_VALUE_ACCESSOR = new Provider(
+ NG_VALUE_ACCESSOR, {useExisting: forwardRef(() => DateTime), multi: true});
+
+
+/**
+ * @name DateTime
+ * @description
+ * The `ion-datetime` component is similar to an HTML ` `
+ * input, however, Ionic's datetime component makes it easier for developers to
+ * display an exact datetime input format and manage values within JavaScript.
+ * Additionally, the datetime component makes it easier for users to scroll through
+ * and individually select parts of date and time values from an easy to user interface.
+ *
+ * ```html
+ *
+ * Date
+ *
+ *
+ *
+ * ```
+ *
+ *
+ * ### Display and Picker Formats
+ *
+ * How datetime values can be displayed can come in many variations formats,
+ * therefore it is best to let the app decide exactly how to display it. To do
+ * so, `ion-datetime` uses a common format seen in many other libraries and
+ * programming languages:
+ *
+ * | Format | Description | Examples |
+ * |----------|---------------------|----------------|
+ * | `YYYY` | Year, 4 digits | `2018` |
+ * | `YY` | Year, 2 digits | `18` |
+ * | `M` | Month, 1 digit | `1` .. `12` |
+ * | `MM` | Month, 2 digit | `01` .. `12` |
+ * | `MMM` | Month, short name | `Jan` * |
+ * | `MMMM` | Month, full name | `January` * |
+ * | `D` | Day, 1 digit | `1` .. `31` |
+ * | `DD` | Day, 2 digit | `01` .. `31` |
+ * | `DDD` | Day, short name | `Fri` * |
+ * | `DDDD` | Day, full name | `Friday` * |
+ * | `H` | 24-hour, 1 digit | `0` .. `23` |
+ * | `HH` | 24-hour, 2 digit | `00` .. `23` |
+ * | `h` | 12-hour, 1 digit | `1` .. `12` |
+ * | `hh` | 12-hour, 2 digit | `01` .. `12` |
+ * | `a` | am/pm, lower case | `am` `pm` |
+ * | `A` | AM/PM, upper case | `AM` `PM` |
+ * | `m` | minute, 1 digit | `1` .. `59` |
+ * | `mm` | minute, 2 digit | `01` .. `59` |
+ * | `s` | seconds, 1 digit | `1` .. `59` |
+ * | `ss` | seconds, 2 digit | `01` .. `59` |
+ * | `Z` | UTC Timezone Offset | |
+ *
+ * * See the "Month Names and Day of the Week Names" section below on how to
+ * use names other than the default English month and day names.
+ *
+ * The `displayFormat` input allows developers to specify how a date's value
+ * should be displayed within the `ion-datetime`. The `pickerFormat` decides
+ * which datetime picker columns should be shown, the order of the columns, and
+ * which format to display the value in. If a `pickerFormat` is not provided
+ * then it'll use the `displayFormat` instead. In most cases only providing the
+ * `displayFormat` is needed.
+ *
+ * In the example below, the datetime's display would use the month's short
+ * name, the 1 digit day in the month, and a 4 digit year.
+ *
+ * ```html
+ *
+ * Date
+ *
+ *
+ *
+ * ```
+ *
+ * In this example, the datetime's display would only show hours and minutes,
+ * and the hours would be in the 24-hour format. Note that the divider between
+ * the hours and minutes, in this case the `:` character, can be have any
+ * character which the app chooses to use as the divider.
+ *
+ * ```html
+ *
+ * Date
+ *
+ *
+ *
+ * ```
+ *
+ *
+ * ### Datetime Data
+ *
+ * Historically, handling datetime data within JavaScript, or even within HTML
+ * inputs, has always been a challenge. Specifically, JavaScript's `Date` object is
+ * notoriously difficult to correctly parse apart datetime strings or to format
+ * datetime values. Even worse is how different browsers and JavaScript versions
+ * parse various datetime strings differently, especially per locale. Additional,
+ * developers face even more challenges when dealing with timezones using
+ * JavaScript's core `Date` object.
+ *
+ * But no worries, all is not lost! Ionic's datetime input has been designed so
+ * developers can avoid the common pitfalls, allowing developers to easily format
+ * datetime data within the input, and give the user a simple datetime picker for a
+ * great user experience. Oddly enough, one of the best ways to work with datetime
+ * values in JavaScript is to not use the `Date` object at all.
+ *
+ * ##### ISO 8601 Datetime Format: YYYY-MM-DDTHH:mmZ
+ *
+ * For all the reasons above, and how datetime data is commonly saved within databases,
+ * Ionic uses the [ISO 8601 datetime format](https://www.w3.org/TR/NOTE-datetime)
+ * for both its input value, and output value. The value is simply a string, rather
+ * than using JavaScript's `Date` object, and it strictly follows the standardized
+ * ISO 8601 format. Additionally, when using the ISO datetime string format, it makes
+ * it easier on developers when passing data within JSON objects, and sending databases
+ * a standardized datetime format which it can be easily parse apart and formatted.
+ * Because of the strict adherence to the ISO 8601 format, and not involving the hundreds
+ * of other format possibilities and locales, this approach actually makes it easier
+ * for Ionic apps and backend-services to manage datetime data.
+ *
+ * An ISO format can be used as a simple year, or just the hour and minute, or get more
+ * detailed down to the millisecond and timezone. Any of the ISO formats below can be used,
+ * and after a user selects a new date, Ionic will continue to use the same ISO format
+ * which datetime value was originally given as.
+ *
+ * | Description | Format | Datetime Value Example |
+ * |----------------------|------------------------|------------------------------|
+ * | Year | YYYY | 1994 |
+ * | Year and Month | YYYY-MM | 1994-12 |
+ * | Complete Date | YYYY-MM-DD | 1994-12-15 |
+ * | Date and Time | YYYY-MM-DDTHH:mm | 1994-12-15T13:47 |
+ * | UTC Timezone | YYYY-MM-DDTHH:mm:ssTZD | 1994-12-15T13:47:20.789Z |
+ * | Timezone Offset | YYYY-MM-DDTHH:mm:ssTZD | 1994-12-15T13:47:20.789+5:00 |
+ * | Hour and Minute | HH:mm | 13:47 |
+ * | Hour, Minute, Second | HH:mm:ss | 13:47:20 |
+ *
+ * Note that the year is always four-digits, milliseconds (if it's added) is always
+ * three-digits, and all others are always two-digits. So the number representing
+ * January always has a leading zero, such as `01`. Additionally, the hour is always
+ * in the 24-hour format, so `00` is `12am` on a 12-hour clock, `13` means `1pm`,
+ * and `23` means `11pm`.
+ *
+ * It's also important to note that neither the `displayFormat` or `pickerFormat` can
+ * set the datetime value's output, which is the value that sent the the component's
+ * `ngModel`. The format's are merely for displaying the value as text and the picker's
+ * interface, but the datetime's value is always persisted as a valid ISO 8601 datetime
+ * string.
+ *
+ *
+ * ### Min and Max Datetimes
+ *
+ * Dates are infinite in either direction, so for a user selection there should be at
+ * least some form of restricting the dates can be selected. Be default, the maximum
+ * date is to the end of the current year, and the minimum date is from the beginning
+ * of the year that was 100 years ago.
+ *
+ * To customize the minimum and maximum datetime values, the `min` and `max` component
+ * inputs can be provided which may make more sense for the app's use-case, rather
+ * than the default of the last 100 years. Following the same IS0 8601 format listed
+ * in the table above, each component can restrict which dates can be selected by the
+ * user. Below is an example of restricting the date selection between the beginning
+ * of 2016, and October 31st of 2020:
+ *
+ * ```html
+ *
+ * Date
+ *
+ *
+ *
+ * ```
+ *
+ *
+ * ### Month Names and Day of the Week Names
+ *
+ * At this time, there is no one-size-fits-all standard to automatically choose the correct
+ * language/spelling for a month name, or day of the week name, depending on the language
+ * or locale. Good news is that there is an
+ * [Intl.DateTimeFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat)
+ * standard which *most* browsers have adopted. However, at this time the standard has not
+ * been fully implemented by all popular browsers so Ionic is unavailable to take advantage
+ * of it *yet*. Additionally, Angular also provides an internationalization service, but it
+ * is still under heavy development so Ionic does not depend on it at this time.
+ *
+ * All things considered, the by far easiest solution is to just provide an array of names
+ * if the app needs to use names other than the default English version of month and day
+ * names. The month names and day names can be either configurated at the app level, or
+ * individual `ion-datetime` level.
+ *
+ * ##### App Config Level
+ *
+ * ```ts
+ * @App({
+ * config: {
+ * monthNames: ['janeiro, 'fevereiro', 'mar\u00e7o', ... ],
+ * monthShortNames: ['jan', 'fev', 'mar', ... ],
+ * dayNames: ['domingo', 'segunda-feira', 'ter\u00e7a-feira', ... ],
+ * dayShortNames: ['dom', 'seg', 'ter', ... ],
+ * }
+ * })
+ * ```
+ *
+ * ##### Component Input Level
+ *
+ * ```html
+ *
+ * PerĂodo
+ *
+ *
+ * ```
+ *
+ *
+ * ### Advanced Datetime Validation and Manipulation
+ *
+ * The datetime picker provides the simplicity of selecting an exact format, and persists
+ * the datetime values as a string using the standardized
+ * [ISO 8601 datetime format](https://www.w3.org/TR/NOTE-datetime).
+ * However, it's important to note that `ion-datetime` does not attempt to solve all
+ * situtations when validating and manipulating datetime values. If datetime values need
+ * to be parsed from a certain format, or manipulated (such as adding 5 days to a date,
+ * subtracting 30 minutes), or even formatting data to a specific locale, then we highly
+ * recommend using [moment.js](http://momentjs.com/) to "Parse, validate, manipulate, and
+ * display dates in JavaScript". [Moment.js](http://momentjs.com/) has quickly become
+ * our goto standard when dealing with datetimes within JavaScript, but Ionic does not
+ * prepackage this dependency since most apps will not require it, and its locale
+ * configuration should be decided by the end-developer.
+ *
+ *
+ */
+@Component({
+ selector: 'ion-datetime',
+ template:
+ '
{{_text}}
' +
+ '' +
+ ' ',
+ host: {
+ '[class.datetime-disabled]': '_disabled'
+ },
+ providers: [DATETIME_VALUE_ACCESSOR],
+ encapsulation: ViewEncapsulation.None,
+})
+export class DateTime {
+ private _disabled: any = false;
+ private _labelId: string;
+ private _text: string = '';
+ private _fn: Function;
+ private _isOpen: boolean = false;
+ private _min: DateTimeData;
+ private _max: DateTimeData;
+ private _value: DateTimeData = {};
+ private _locale: LocaleData = {};
+
+ /**
+ * @private
+ */
+ id: string;
+
+ /**
+ * @input {string} The minimum datetime allowed. Value must be a date string
+ * following the
+ * [ISO 8601 datetime format standard](https://www.w3.org/TR/NOTE-datetime),
+ * such as `1996-12-19`. The format does not have to be specific to an exact
+ * datetime. For example, the minimum could just be the year, such as `1994`.
+ * Defaults to the beginning of the year, 100 years ago from today.
+ */
+ @Input() min: string;
+
+ /**
+ * @input {string} The maximum datetime allowed. Value must be a date string
+ * following the
+ * [ISO 8601 datetime format standard](https://www.w3.org/TR/NOTE-datetime),
+ * `1996-12-19`. The format does not have to be specific to an exact
+ * datetime. For example, the maximum could just be the year, such as `1994`.
+ * Defaults to the end of this year.
+ */
+ @Input() max: string;
+
+ /**
+ * @input {string} The display format of the date and time as text that shows
+ * within the item. When the `pickerFormat` input is not used, then the
+ * `displayFormat` is used for both display the formatted text, and determining
+ * the datetime picker's columns. See the `pickerFormat` input description for
+ * more info. Defaults to `MMM D, YYYY`.
+ */
+ @Input() displayFormat: string = 'MMM D, YYYY';
+
+ /**
+ * @input {string} The format of the date and time picker columns the user selects.
+ * A datetime input can have one or many datetime parts, each getting their
+ * own column which allow individual selection of that particular datetime part. For
+ * example, year and month columns are two individually selectable columns which help
+ * choose an exact date from the datetime picker. Each column follows the string
+ * parse format. Defaults to use `displayFormat`.
+ */
+ @Input() pickerFormat: string;
+
+ /**
+ * @input {string} The text to display on the picker's cancel button. Default: `Cancel`.
+ */
+ @Input() cancelText: string = 'Cancel';
+
+ /**
+ * @input {string} The text to display on the picker's "Done" button. Default: `Done`.
+ */
+ @Input() doneText: string = 'Done';
+
+ /**
+ * @input {array | string} Values used to create the list of selectable years. By default
+ * the year values range between the `min` and `max` datetime inputs. However, to
+ * control exactly which years to display, the `yearValues` input can take either an array
+ * of numbers, or string of comma separated numbers. For example, to show upcoming and
+ * recent leap years, then this input's value would be `yearValues="2024,2020,2016,2012,2008"`.
+ */
+ @Input() yearValues: any;
+
+ /**
+ * @input {array | string} Values used to create the list of selectable months. By default
+ * the month values range from `1` to `12`. However, to control exactly which months to
+ * display, the `monthValues` input can take either an array of numbers, or string of
+ * comma separated numbers. For example, if only summer months should be shown, then this
+ * input value would be `monthValues="6,7,8"`. Note that month numbers do *not* have a
+ * zero-based index, meaning January's value is `1`, and December's is `12`.
+ */
+ @Input() monthValues: any;
+
+ /**
+ * @input {array | string} Values used to create the list of selectable days. By default
+ * every day is shown for the given month. However, to control exactly which days of
+ * the month to display, the `dayValues` input can take either an array of numbers, or
+ * string of comma separated numbers. Note that even if the array days have an invalid
+ * number for the selected month, like `31` in February, it will correctly not show
+ * days which are not valid for the selected month.
+ */
+ @Input() dayValues: any;
+
+ /**
+ * @input {array | string} Values used to create the list of selectable hours. By default
+ * the hour values range from `1` to `23` for 24-hour, or `1` to `12` for 12-hour. However,
+ * to control exactly which hours to display, the `hourValues` input can take either an
+ * array of numbers, or string of comma separated numbers.
+ */
+ @Input() hourValues: any;
+
+ /**
+ * @input {array | string} Values used to create the list of selectable minutes. By default
+ * the mintues range from `1` to `59`. However, to control exactly which minutes to display,
+ * the `minuteValues` input can take either an array of numbers, or string of comma separated
+ * numbers. For example, if the minute selections should only be every 15 minutes, then
+ * this input value would be `minuteValues="0,15,30,45"`.
+ */
+ @Input() minuteValues: any;
+
+ /**
+ * @input {array} Full names for each month name. This can be used to provide
+ * locale month names. Defaults to English.
+ */
+ @Input() monthNames: any;
+
+ /**
+ * @input {array} Short abbreviated names for each month name. This can be used to provide
+ * locale month names. Defaults to English.
+ */
+ @Input() monthShortNames: any;
+
+ /**
+ * @input {array} Full day of the week names. This can be used to provide
+ * locale names for each day in the week. Defaults to English.
+ */
+ @Input() dayNames: any;
+
+ /**
+ * @input {array} Short abbreviated day of the week names. This can be used to provide
+ * locale names for each day in the week. Defaults to English.
+ */
+ @Input() dayShortNames: any;
+
+ /**
+ * @input {any} Any addition options that the picker interface can accept.
+ * See the [Picker API docs](../../picker/Picker) for the picker options.
+ */
+ @Input() pickerOptions: any = {};
+
+ /**
+ * @output {any} Any expression to evaluate when the datetime selection has changed.
+ */
+ @Output() change: EventEmitter = new EventEmitter();
+
+ /**
+ * @output {any} Any expression to evaluate when the datetime selection was cancelled.
+ */
+ @Output() cancel: EventEmitter = new EventEmitter();
+
+ constructor(
+ private _form: Form,
+ private _config: Config,
+ @Optional() private _item: Item,
+ @Optional() private _nav: NavController
+ ) {
+ this._form.register(this);
+ if (_item) {
+ this.id = 'dt-' + _item.registerInput('datetime');
+ this._labelId = 'lbl-' + _item.id;
+ this._item.setCssClass('item-datetime', true);
+ }
+
+ if (!_nav) {
+ console.error('parent required for ');
+ }
+ }
+
+ @HostListener('click', ['$event'])
+ private _click(ev) {
+ if (ev.detail === 0) {
+ // do not continue if the click event came from a form submit
+ return;
+ }
+ ev.preventDefault();
+ ev.stopPropagation();
+ this.open();
+ }
+
+ @HostListener('keyup.space', ['$event'])
+ private _keyup(ev) {
+ if (!this._isOpen) {
+ this.open();
+ }
+ }
+
+ /**
+ * @private
+ */
+ open() {
+ if (this._disabled) {
+ return;
+ }
+
+ console.debug('datetime, open picker');
+
+ // the user may have assigned some options specifically for the alert
+ let pickerOptions = merge({}, this.pickerOptions);
+
+ let picker = Picker.create(pickerOptions);
+ pickerOptions.buttons = [
+ {
+ text: this.cancelText,
+ role: 'cancel',
+ handler: () => {
+ this.cancel.emit(null);
+ }
+ },
+ {
+ text: this.doneText,
+ handler: (data) => {
+ console.log('datetime, done', data);
+ this.onChange(data);
+ this.change.emit(data);
+ }
+ }
+ ];
+
+ this.generate(picker);
+ this.validate(picker);
+
+ picker.change.subscribe(() => {
+ this.validate(picker);
+ });
+
+ this._nav.present(picker, pickerOptions);
+
+ this._isOpen = true;
+ picker.onDismiss(() => {
+ this._isOpen = false;
+ });
+ }
+
+ /**
+ * @private
+ */
+ generate(picker: Picker) {
+ // if a picker format wasn't provided, then fallback
+ // to use the display format
+ let template = this.pickerFormat || this.displayFormat;
+
+ if (isPresent(template)) {
+ // make sure we've got up to date sizing information
+ this.calcMinMax();
+
+ // does not support selecting by day name
+ // automaticallly remove any day name formats
+ template = template.replace('DDDD', '{~}').replace('DDD', '{~}');
+ if (template.indexOf('D') === -1) {
+ // there is not a day in the template
+ // replace the day name with a numeric one if it exists
+ template = template.replace('{~}', 'D');
+ }
+ // make sure no day name replacer is left in the string
+ template = template.replace(/{~}/g, '');
+
+ // parse apart the given template into an array of "formats"
+ parseTemplate(template).forEach(format => {
+ // loop through each format in the template
+ // create a new picker column to build up with data
+ let key = convertFormatToKey(format);
+ let values: any[];
+
+ // first see if they have exact values to use for this input
+ if (isPresent(this[key + 'Values'])) {
+ // user provide exact values for this date part
+ values = convertToArrayOfNumbers(this[key + 'Values'], key);
+
+ } else {
+ // use the default date part values
+ values = dateValueRange(format, this._min, this._max);
+ }
+
+ let column: PickerColumn = {
+ name: key,
+ options: values.map(val => {
+ return {
+ value: val,
+ text: renderTextFormat(format, val, null, this._locale),
+ };
+ })
+ };
+
+ if (column.options.length) {
+ // cool, we've loaded up the columns with options
+ // preselect the option for this column
+ var selected = column.options.find(opt => opt.value === getValueFromFormat(this._value, format));
+ if (selected) {
+ // set the select index for this column's options
+ column.selectedIndex = column.options.indexOf(selected);
+ }
+
+ // add our newly created column to the picker
+ picker.addColumn(column);
+ }
+ });
+
+ this.divyColumns(picker);
+ }
+ }
+
+ /**
+ * @private
+ */
+ validate(picker: Picker) {
+ let i: number;
+ let today = new Date();
+ let columns = picker.getColumns();
+
+ // find the columns used
+ let yearCol = columns.find(col => col.name === 'year');
+ let monthCol = columns.find(col => col.name === 'month');
+ let dayCol = columns.find(col => col.name === 'day');
+
+ let yearOpt: PickerColumnOption;
+ let monthOpt: PickerColumnOption;
+ let dayOpt: PickerColumnOption;
+
+ // default to assuming today's year
+ let selectedYear = today.getFullYear();
+ if (yearCol) {
+ yearOpt = yearCol.options[yearCol.selectedIndex];
+ if (yearOpt) {
+ // they have a selected year value
+ selectedYear = yearOpt.value;
+ }
+ }
+
+ // default to assuming this month has 31 days
+ let numDaysInMonth = 31;
+ let selectedMonth;
+ if (monthCol) {
+ monthOpt = monthCol.options[monthCol.selectedIndex];
+ if (monthOpt) {
+ // they have a selected month value
+ selectedMonth = monthOpt.value;
+
+ // calculate how many days are in this month
+ numDaysInMonth = daysInMonth(selectedMonth, selectedYear);
+ }
+ }
+
+ // create sort values for the min/max datetimes
+ let minCompareVal = dateDataSortValue(this._min);
+ let maxCompareVal = dateDataSortValue(this._max);
+
+ if (monthCol) {
+ // enable/disable which months are valid
+ // to show within the min/max date range
+ for (i = 0; i < monthCol.options.length; i++) {
+ monthOpt = monthCol.options[i];
+
+ // loop through each month and see if it
+ // is within the min/max date range
+ monthOpt.disabled = (dateSortValue(selectedYear, monthOpt.value, 31) < minCompareVal ||
+ dateSortValue(selectedYear, monthOpt.value, 1) > maxCompareVal);
+ }
+ }
+
+ if (dayCol) {
+ if (isPresent(selectedMonth)) {
+ // enable/disable which days are valid
+ // to show within the min/max date range
+ for (i = 0; i < 31; i++) {
+ dayOpt = dayCol.options[i];
+
+ // loop through each day and see if it
+ // is within the min/max date range
+ var compareVal = dateSortValue(selectedYear, selectedMonth, dayOpt.value);
+
+ dayOpt.disabled = (compareVal < minCompareVal ||
+ compareVal > maxCompareVal ||
+ numDaysInMonth <= i);
+ }
+
+ } else {
+ // enable/disable which numbers of days to show in this month
+ for (i = 0; i < 31; i++) {
+ dayCol.options[i].disabled = (numDaysInMonth <= i);
+ }
+ }
+ }
+
+ picker.refresh();
+ }
+
+ /**
+ * @private
+ */
+ divyColumns(picker: Picker) {
+ let pickerColumns = picker.getColumns();
+ let columns = [];
+
+ pickerColumns.forEach((col, i) => {
+ columns.push(0);
+
+ col.options.forEach(opt => {
+ if (opt.text.length > columns[i]) {
+ columns[i] = opt.text.length;
+ }
+ });
+
+ });
+
+ if (columns.length === 2) {
+ var width = Math.max(columns[0], columns[1]);
+ pickerColumns[0].columnWidth = pickerColumns[1].columnWidth = `${width * 16}px`;
+
+ } else if (columns.length === 3) {
+ var width = Math.max(columns[0], columns[2]);
+ pickerColumns[1].columnWidth = `${columns[1] * 16}px`;
+ pickerColumns[0].columnWidth = pickerColumns[2].columnWidth = `${width * 16}px`;
+
+ } else if (columns.length > 3) {
+ columns.forEach((col, i) => {
+ pickerColumns[i].columnWidth = `${col * 12}px`;
+ });
+ }
+ }
+
+ /**
+ * @private
+ */
+ setValue(newData: any) {
+ updateDate(this._value, newData);
+ }
+
+ /**
+ * @private
+ */
+ getValue(): DateTimeData {
+ return this._value;
+ }
+
+ /**
+ * @private
+ */
+ updateText() {
+ // create the text of the formatted data
+ this._text = renderDateTime(this.displayFormat, this._value, this._locale);
+ }
+
+ /**
+ * @private
+ */
+ calcMinMax() {
+ let todaysYear = new Date().getFullYear();
+
+ if (isBlank(this.min)) {
+ if (isPresent(this.yearValues)) {
+ this.min = Math.min.apply(Math, convertToArrayOfNumbers(this.yearValues, 'year'));
+
+ } else {
+ this.min = (todaysYear - 100).toString();
+ }
+ }
+
+ if (isBlank(this.max)) {
+ if (isPresent(this.yearValues)) {
+ this.max = Math.max.apply(Math, convertToArrayOfNumbers(this.yearValues, 'year'));
+
+ } else {
+ this.max = todaysYear.toString();
+ }
+ }
+
+ let min = this._min = parseDate(this.min);
+ let max = this._max = parseDate(this.max);
+
+ min.month = min.month || 1;
+ min.day = min.day || 1;
+ min.hour = min.hour || 0;
+ min.minute = min.minute || 0;
+ min.second = min.second || 0;
+
+ max.month = max.month || 12;
+ max.day = max.day || 31;
+ max.hour = max.hour || 23;
+ max.minute = max.minute || 59;
+ max.second = max.second || 59;
+ }
+
+ /**
+ * @input {boolean} Whether or not the datetime component is disabled. Default `false`.
+ */
+ @Input()
+ get disabled() {
+ return this._disabled;
+ }
+
+ set disabled(val) {
+ this._disabled = isTrueProperty(val);
+ this._item && this._item.setCssClass('item-datetime-disabled', this._disabled);
+ }
+
+ /**
+ * @private
+ */
+ writeValue(val: any) {
+ console.debug('datetime, writeValue', val);
+ this.setValue(val);
+ this.updateText();
+ }
+
+ /**
+ * @private
+ */
+ ngAfterContentInit() {
+ // first see if locale names were provided in the inputs
+ // then check to see if they're in the config
+ // if neither were provided then it will use default English names
+ ['monthNames', 'monthShortNames', 'dayNames', 'dayShortNames'].forEach(type => {
+ this._locale[type] = convertToArrayOfStrings(isPresent(this[type]) ? this[type] : this._config.get(type), type);
+ });
+
+ // update how the datetime value is displayed as formatted text
+ this.updateText();
+ }
+
+ /**
+ * @private
+ */
+ registerOnChange(fn: Function): void {
+ this._fn = fn;
+ this.onChange = (val: any) => {
+ console.debug('datetime, onChange', val);
+ this.setValue(val);
+ this.updateText();
+
+ // convert DateTimeData value to iso datetime format
+ fn(convertDataToISO(this._value));
+
+ this.onTouched();
+ };
+ }
+
+ /**
+ * @private
+ */
+ registerOnTouched(fn) { this.onTouched = fn; }
+
+ /**
+ * @private
+ */
+ onChange(val: any) {
+ // onChange used when there is not an ngControl
+ console.debug('datetime, onChange w/out ngControl', val);
+ this.setValue(val);
+ this.updateText();
+ this.onTouched();
+ }
+
+ /**
+ * @private
+ */
+ onTouched() { }
+
+ /**
+ * @private
+ */
+ ngOnDestroy() {
+ this._form.deregister(this);
+ }
+}
+
+/**
+ * @private
+ * Use to convert a string of comma separated numbers or
+ * an array of numbers, and clean up any user input
+ */
+function convertToArrayOfNumbers(input: any, type: string): number[] {
+ var values: number[] = [];
+
+ if (isString(input)) {
+ // convert the string to an array of strings
+ // auto remove any whitespace and [] characters
+ input = input.replace(/\[|\]|\s/g, '').split(',');
+ }
+
+ if (isArray(input)) {
+ // ensure each value is an actual number in the returned array
+ input.forEach(num => {
+ num = parseInt(num, 10);
+ if (!isNaN(num)) {
+ values.push(num);
+ }
+ });
+ }
+
+ if (!values.length) {
+ console.warn(`Invalid "${type}Values". Must be an array of numbers, or a comma separated string of numbers.`);
+ }
+
+ return values;
+}
+
+/**
+ * @private
+ * Use to convert a string of comma separated strings or
+ * an array of strings, and clean up any user input
+ */
+function convertToArrayOfStrings(input: any, type: string): string[] {
+ if (isPresent(input)) {
+ var values: string[] = [];
+
+ if (isString(input)) {
+ // convert the string to an array of strings
+ // auto remove any [] characters
+ input = input.replace(/\[|\]/g, '').split(',');
+ }
+
+ if (isArray(input)) {
+ // trim up each string value
+ input.forEach(val => {
+ val = val.trim();
+ if (val) {
+ values.push(val);
+ }
+ });
+ }
+
+ if (!values.length) {
+ console.warn(`Invalid "${type}Names". Must be an array of strings, or a comma separated string.`);
+ }
+
+ return values;
+ }
+}
diff --git a/ionic/components/datetime/test/basic/e2e.ts b/ionic/components/datetime/test/basic/e2e.ts
new file mode 100644
index 0000000000..d572f477d2
--- /dev/null
+++ b/ionic/components/datetime/test/basic/e2e.ts
@@ -0,0 +1,8 @@
+
+it('should open basic datetime picker', function() {
+ element(by.css('.e2eOpenMMDDYYYY')).click();
+});
+
+it('should close with Done button click', function() {
+ element(by.css('.picker-button:last-child')).click();
+});
diff --git a/ionic/components/datetime/test/basic/index.ts b/ionic/components/datetime/test/basic/index.ts
new file mode 100644
index 0000000000..474f203e30
--- /dev/null
+++ b/ionic/components/datetime/test/basic/index.ts
@@ -0,0 +1,41 @@
+import {App, Page} from '../../../../../ionic';
+
+
+@Page({
+ templateUrl: 'main.html'
+})
+class E2EPage {
+ wwwInvented = '1989';
+ time = '13:47';
+ netscapeReleased = '1994-12-15T13:47:20.789';
+ operaReleased = '1995-04-15';
+ firefoxReleased = '2002-09-23T15:03:46.789';
+ webkitOpenSourced = '2005-06-17T11:06Z';
+ chromeReleased = '2008-09-02';
+ leapYearsSummerMonths = '';
+
+ leapYearsArray = [2020, 2016, 2008, 2004, 2000, 1996];
+
+ customShortDay = [
+ 's\u00f8n',
+ 'man',
+ 'tir',
+ 'ons',
+ 'tor',
+ 'fre',
+ 'l\u00f8r'
+ ];
+
+}
+
+
+@App({
+ template: ' '
+})
+class E2EApp {
+ root;
+
+ constructor() {
+ this.root = E2EPage;
+ }
+}
diff --git a/ionic/components/datetime/test/basic/main.html b/ionic/components/datetime/test/basic/main.html
new file mode 100644
index 0000000000..46969834a5
--- /dev/null
+++ b/ionic/components/datetime/test/basic/main.html
@@ -0,0 +1,68 @@
+
+ Datetime
+
+
+
+
+
+ YYYY
+
+
+
+
+ MMMM YY
+
+
+
+
+ MMM DD, YYYY
+
+
+
+
+ DDD. MM/DD/YY (locale day)
+
+
+
+
+ D MMM YYYY H:mm
+
+
+
+
+ DDDD MMM D, YYYY
+
+
+
+
+ HH:mm
+
+
+
+
+ h:mm a
+
+
+
+
+ hh:mm A (15 min steps)
+
+
+
+
+ Leap years, summer months
+
+
+
+
+ wwwInvented: {{wwwInvented}}
+ netscapeReleased: {{netscapeReleased}}
+ operaReleased: {{operaReleased}}
+ firefoxReleased: {{firefoxReleased}}
+ webkitOpenSourced: {{webkitOpenSourced}}
+ chromeReleased: {{chromeReleased}}
+ time: {{time}}
+ Leap year, summer months: {{leapYearsSummerMonths}}
+
+
+
\ No newline at end of file
diff --git a/ionic/components/datetime/test/datetime.spec.ts b/ionic/components/datetime/test/datetime.spec.ts
new file mode 100644
index 0000000000..c8da46a3ce
--- /dev/null
+++ b/ionic/components/datetime/test/datetime.spec.ts
@@ -0,0 +1,518 @@
+import {DateTime, Form, Picker, Config, NavController} from '../../../../ionic';
+import * as datetime from '../../../../ionic/util/datetime-util';
+
+export function run() {
+
+describe('DateTime', () => {
+
+ describe('validate', () => {
+
+ it('should restrict January 1-14, 2000 from selection, then allow it, and restrict December 15-31, 2001', () => {
+ datetime.max = '2001-12-15';
+ datetime.min = '2000-01-15';
+ datetime.pickerFormat = 'MM DD YYYY';
+ var picker = new Picker();
+ datetime.generate(picker);
+
+ var columns = picker.getColumns();
+ columns[0].selectedIndex = 0; // January
+ columns[1].selectedIndex = 0; // January 1st
+ columns[2].selectedIndex = 1; // January 1st, 2000
+
+ datetime.validate(picker);
+
+ expect(columns[1].options[0].disabled).toEqual(true);
+ expect(columns[1].options[13].disabled).toEqual(true);
+ expect(columns[1].options[14].disabled).toEqual(false);
+
+ columns[0].selectedIndex = 11; // December
+ columns[2].selectedIndex = 0; // December 1st, 2001
+
+ datetime.validate(picker);
+
+ expect(columns[0].options[11].disabled).toEqual(false);
+
+ expect(columns[1].options[14].disabled).toEqual(false);
+ expect(columns[1].options[15].disabled).toEqual(true);
+ expect(columns[1].options[30].disabled).toEqual(true);
+ });
+
+ it('should restrict January 2000 from selection, then allow it, and restrict December 2010', () => {
+ datetime.max = '2010-11-15';
+ datetime.min = '2000-02-15';
+ datetime.pickerFormat = 'MM DD YYYY';
+ var picker = new Picker();
+ datetime.generate(picker);
+
+ var columns = picker.getColumns();
+ columns[0].selectedIndex = 1; // February
+ columns[1].selectedIndex = 0; // February 1st
+ columns[2].selectedIndex = columns[2].options.length - 1; // February 1st, 2000
+
+ datetime.validate(picker);
+
+ expect(columns[0].options[0].disabled).toEqual(true);
+ expect(columns[0].options[1].disabled).toEqual(false);
+ expect(columns[0].options[11].disabled).toEqual(false);
+
+ columns[2].selectedIndex = 0; // December 1st, 2010
+
+ datetime.validate(picker);
+
+ expect(columns[0].options[0].disabled).toEqual(false);
+ expect(columns[0].options[10].disabled).toEqual(false);
+ expect(columns[0].options[11].disabled).toEqual(true);
+ });
+
+ it('should only show 31 valid days in the selected 31 day month, then reset for 28 day, then to 30', () => {
+ datetime.max = '2010-12-31';
+ datetime.min = '2000-01-01';
+ datetime.pickerFormat = 'MM DD YYYY';
+
+ var picker = new Picker();
+ datetime.generate(picker);
+
+ var columns = picker.getColumns();
+ columns[0].selectedIndex = 0; // January
+ columns[1].selectedIndex = 0; // January 1st
+ columns[2].selectedIndex = 0; // January 1st, 2010
+
+ datetime.validate(picker);
+
+ for (var i = 0; i < 31; i++) {
+ expect(columns[1].options[i].disabled).toEqual(false);
+ }
+
+ columns[0].selectedIndex = 1; // February
+ datetime.validate(picker);
+
+ for (var i = 0; i < 28; i++) {
+ expect(columns[1].options[i].disabled).toEqual(false);
+ }
+ expect(columns[1].options[28].disabled).toEqual(true);
+ expect(columns[1].options[29].disabled).toEqual(true);
+ expect(columns[1].options[30].disabled).toEqual(true);
+
+ columns[0].selectedIndex = 3; // April
+ datetime.validate(picker);
+
+ for (var i = 0; i < 30; i++) {
+ expect(columns[1].options[i].disabled).toEqual(false);
+ }
+ expect(columns[1].options[30].disabled).toEqual(true);
+ });
+
+ });
+
+ describe('generate', () => {
+
+ it('should generate with custom locale short month names from input property', () => {
+ datetime.monthShortNames = customLocale.monthShortNames;
+ datetime.ngAfterContentInit();
+ datetime.pickerFormat = 'MMM YYYY';
+ datetime.setValue('1994-12-15T13:47:20.789Z');
+
+ var picker = new Picker();
+ datetime.generate(picker);
+ var columns = picker.getColumns();
+
+ expect(columns.length).toEqual(2);
+ expect(columns[0].name).toEqual('month');
+ expect(columns[0].options[0].value).toEqual(1);
+ expect(columns[0].options[0].text).toEqual('jan');
+ });
+
+ it('should generate with custom locale full month names from input property', () => {
+ datetime.monthNames = customLocale.monthNames;
+ datetime.ngAfterContentInit();
+ datetime.pickerFormat = 'MMMM YYYY';
+ datetime.setValue('1994-12-15T13:47:20.789Z');
+
+ var picker = new Picker();
+ datetime.generate(picker);
+ var columns = picker.getColumns();
+
+ expect(columns.length).toEqual(2);
+ expect(columns[0].name).toEqual('month');
+ expect(columns[0].options[0].value).toEqual(1);
+ expect(columns[0].options[0].text).toEqual('janeiro');
+ });
+
+ it('should replace a picker format with both a day name and a numeric day to use only the numeric day', () => {
+ datetime.pickerFormat = 'DDDD D M YYYY';
+ datetime.setValue('1994-12-15T13:47:20.789Z');
+
+ var picker = new Picker();
+ datetime.generate(picker);
+ var columns = picker.getColumns();
+
+ expect(columns.length).toEqual(3);
+ expect(columns[0].name).toEqual('day');
+ expect(columns[0].options[0].value).toEqual(1);
+ expect(columns[0].options[0].text).toEqual('1');
+ });
+
+ it('should replace a picker format with only a day name to use a numeric day instead', () => {
+ datetime.pickerFormat = 'DDDD M YYYY';
+ datetime.setValue('1994-12-15T13:47:20.789Z');
+
+ var picker = new Picker();
+ datetime.generate(picker);
+ var columns = picker.getColumns();
+
+ expect(columns.length).toEqual(3);
+ expect(columns[0].name).toEqual('day');
+ expect(columns[0].options[0].value).toEqual(1);
+ expect(columns[0].options[0].text).toEqual('1');
+ });
+
+ it('should generate MM DD YYYY pickerFormat with min/max', () => {
+ datetime.max = '2010-12-31';
+ datetime.min = '2000-01-01';
+ datetime.pickerFormat = 'MM DD YYYY';
+
+ var picker = new Picker();
+ datetime.generate(picker);
+ var columns = picker.getColumns();
+
+ expect(columns.length).toEqual(3);
+ expect(columns[0].options.length).toEqual(12);
+ expect(columns[0].options[0].value).toEqual(1);
+ expect(columns[0].options[11].value).toEqual(12);
+
+ expect(columns[1].options.length).toEqual(31);
+ expect(columns[1].options[0].value).toEqual(1);
+ expect(columns[1].options[30].value).toEqual(31);
+
+ expect(columns[2].options.length).toEqual(11);
+ expect(columns[2].options[0].value).toEqual(2010);
+ expect(columns[2].options[10].value).toEqual(2000);
+ });
+
+ it('should generate YYYY pickerFormat with min/max', () => {
+ datetime.max = '2010-01-01';
+ datetime.min = '2000-01-01';
+ datetime.pickerFormat = 'YYYY';
+
+ var picker = new Picker();
+ datetime.generate(picker);
+ var columns = picker.getColumns();
+
+ expect(columns.length).toEqual(1);
+ expect(columns[0].options.length).toEqual(11);
+ expect(columns[0].options[0].value).toEqual(2010);
+ expect(columns[0].options[10].value).toEqual(2000);
+ });
+
+ });
+
+ describe('calcMinMax', () => {
+
+ it('should max date with no max input, but has yearValues input', () => {
+ datetime.yearValues = '2000,1996,1992';
+ datetime.calcMinMax();
+ expect(datetime._max.year).toEqual(2000);
+ expect(datetime._max.month).toEqual(12);
+ expect(datetime._max.day).toEqual(31);
+ expect(datetime._max.hour).toEqual(23);
+ expect(datetime._max.minute).toEqual(59);
+ expect(datetime._max.second).toEqual(59);
+ });
+
+ it('should min date with no min input, but has yearValues input', () => {
+ datetime.yearValues = '2000,1996,1992';
+ datetime.calcMinMax();
+ expect(datetime._min.year).toEqual(1992);
+ expect(datetime._min.month).toEqual(1);
+ expect(datetime._min.day).toEqual(1);
+ expect(datetime._min.hour).toEqual(0);
+ expect(datetime._min.minute).toEqual(0);
+ expect(datetime._min.second).toEqual(0);
+ });
+
+ it('should min date with only YYYY', () => {
+ datetime.min = '1994';
+ datetime.calcMinMax();
+ expect(datetime._min.year).toEqual(1994);
+ expect(datetime._min.month).toEqual(1);
+ expect(datetime._min.day).toEqual(1);
+ expect(datetime._min.hour).toEqual(0);
+ expect(datetime._min.minute).toEqual(0);
+ expect(datetime._min.second).toEqual(0);
+ });
+
+ it('should max date with only YYYY', () => {
+ datetime.max = '1994';
+ datetime.calcMinMax();
+ expect(datetime._max.year).toEqual(1994);
+ expect(datetime._max.month).toEqual(12);
+ expect(datetime._max.day).toEqual(31);
+ expect(datetime._max.hour).toEqual(23);
+ expect(datetime._max.minute).toEqual(59);
+ expect(datetime._max.second).toEqual(59);
+ });
+
+ it('should max date from max input string', () => {
+ datetime.max = '1994-12-15T13:47:20.789Z';
+ datetime.calcMinMax();
+ expect(datetime._max.year).toEqual(1994);
+ expect(datetime._max.month).toEqual(12);
+ expect(datetime._max.day).toEqual(15);
+ expect(datetime._max.hour).toEqual(13);
+ expect(datetime._max.minute).toEqual(47);
+ expect(datetime._max.second).toEqual(20);
+ expect(datetime._max.millisecond).toEqual(789);
+ });
+
+ it('should min date from max input string', () => {
+ datetime.min = '0123-01-05T00:05:00.009Z';
+ datetime.calcMinMax();
+ expect(datetime._min.year).toEqual(123);
+ expect(datetime._min.month).toEqual(1);
+ expect(datetime._min.day).toEqual(5);
+ expect(datetime._min.hour).toEqual(0);
+ expect(datetime._min.minute).toEqual(5);
+ expect(datetime._min.second).toEqual(0);
+ expect(datetime._min.millisecond).toEqual(9);
+ });
+
+ it('should default max date when not set', () => {
+ datetime.calcMinMax();
+ expect(datetime._max.year).toEqual(new Date().getFullYear());
+ expect(datetime._max.month).toEqual(12);
+ expect(datetime._max.day).toEqual(31);
+ expect(datetime._max.hour).toEqual(23);
+ expect(datetime._max.minute).toEqual(59);
+ expect(datetime._max.second).toEqual(59);
+ });
+
+ it('should default min date when not set', () => {
+ datetime.calcMinMax();
+ expect(datetime._min.year).toEqual(new Date().getFullYear() - 100);
+ expect(datetime._min.month).toEqual(1);
+ expect(datetime._min.day).toEqual(1);
+ expect(datetime._min.hour).toEqual(0);
+ expect(datetime._min.minute).toEqual(0);
+ expect(datetime._min.second).toEqual(0);
+ });
+
+ });
+
+ describe('setValue', () => {
+
+ it('should update existing time value with 12-hour PM DateTimeData value', () => {
+ var d = '13:47:20.789Z';
+ datetime.setValue(d);
+
+ var dateTimeData = {
+ hour: {
+ text: '12',
+ value: 12,
+ },
+ minute: {
+ text: '09',
+ value: 9,
+ },
+ ampm: {
+ text: 'pm',
+ value: 'pm',
+ },
+ };
+ datetime.setValue(dateTimeData);
+
+ expect(datetime.getValue().hour).toEqual(12);
+ expect(datetime.getValue().minute).toEqual(9);
+ expect(datetime.getValue().second).toEqual(20);
+
+ dateTimeData.hour.value = 1;
+ datetime.setValue(dateTimeData);
+
+ expect(datetime.getValue().hour).toEqual(13);
+ expect(datetime.getValue().minute).toEqual(9);
+ expect(datetime.getValue().second).toEqual(20);
+ });
+
+ it('should update existing time value with 12-hour AM DateTimeData value', () => {
+ var d = '13:47:20.789Z';
+ datetime.setValue(d);
+
+ var dateTimeData = {
+ hour: {
+ text: '12',
+ value: 12,
+ },
+ minute: {
+ text: '09',
+ value: 9,
+ },
+ ampm: {
+ text: 'am',
+ value: 'am',
+ },
+ };
+ datetime.setValue(dateTimeData);
+
+ expect(datetime.getValue().hour).toEqual(0);
+ expect(datetime.getValue().minute).toEqual(9);
+ expect(datetime.getValue().second).toEqual(20);
+
+ dateTimeData.hour.value = 11;
+ datetime.setValue(dateTimeData);
+
+ expect(datetime.getValue().hour).toEqual(11);
+ expect(datetime.getValue().minute).toEqual(9);
+ expect(datetime.getValue().second).toEqual(20);
+ });
+
+ it('should update existing time value with new DateTimeData value', () => {
+ var d = '13:47:20.789Z';
+ datetime.setValue(d);
+
+ expect(datetime.getValue().hour).toEqual(13);
+ expect(datetime.getValue().minute).toEqual(47);
+ expect(datetime.getValue().second).toEqual(20);
+
+ var dateTimeData = {
+ hour: {
+ text: '15',
+ value: 15,
+ },
+ minute: {
+ text: '09',
+ value: 9,
+ },
+ };
+ datetime.setValue(dateTimeData);
+
+ expect(datetime.getValue().year).toEqual(null);
+ expect(datetime.getValue().month).toEqual(null);
+ expect(datetime.getValue().day).toEqual(null);
+ expect(datetime.getValue().hour).toEqual(15);
+ expect(datetime.getValue().minute).toEqual(9);
+ expect(datetime.getValue().second).toEqual(20);
+ });
+
+ it('should update existing DateTimeData value with new DateTimeData value', () => {
+ var d = '1994-12-15T13:47:20.789Z';
+ datetime.setValue(d);
+
+ expect(datetime.getValue().year).toEqual(1994);
+
+ var dateTimeData = {
+ year: {
+ text: '1995',
+ value: 1995,
+ },
+ month: {
+ text: 'December',
+ value: 12,
+ },
+ day: {
+ text: '20',
+ value: 20
+ },
+ whatevaIDoWhatIWant: -99,
+ };
+ datetime.setValue(dateTimeData);
+
+ expect(datetime.getValue().year).toEqual(1995);
+ expect(datetime.getValue().month).toEqual(12);
+ expect(datetime.getValue().day).toEqual(20);
+ expect(datetime.getValue().hour).toEqual(13);
+ expect(datetime.getValue().minute).toEqual(47);
+ });
+
+ it('should parse a ISO date string with no existing DateTimeData value', () => {
+ var d = '1994-12-15T13:47:20.789Z';
+ datetime.setValue(d);
+ expect(datetime.getValue().year).toEqual(1994);
+ expect(datetime.getValue().month).toEqual(12);
+ expect(datetime.getValue().day).toEqual(15);
+ });
+
+ it('should not parse a Date object', () => {
+ var d = new Date(1994, 11, 15);
+ datetime.setValue(d);
+ expect(datetime.getValue()).toEqual({});
+ });
+
+ it('should not parse a value with bad data', () => {
+ var d = 'umm 1994 i think';
+ datetime.setValue(d);
+ expect(datetime.getValue()).toEqual({});
+ });
+
+ it('should not parse a value with blank value', () => {
+ datetime.setValue(null);
+ expect(datetime.getValue()).toEqual({});
+
+ datetime.setValue(undefined);
+ expect(datetime.getValue()).toEqual({});
+
+ datetime.setValue('');
+ expect(datetime.getValue()).toEqual({});
+ });
+
+ });
+
+ var datetime: DateTime;
+
+ beforeEach(() => {
+ datetime = new DateTime(new Form(), new Config(), null, {});
+ });
+
+ console.warn = function(){};
+
+ // pt-br
+ var customLocale: datetime.LocaleData = {
+ dayShort: [
+ 'domingo',
+ 'segunda-feira',
+ 'ter\u00e7a-feira',
+ 'quarta-feira',
+ 'quinta-feira',
+ 'sexta-feira',
+ 's\u00e1bado'
+ ],
+ dayShortNames: [
+ 'dom',
+ 'seg',
+ 'ter',
+ 'qua',
+ 'qui',
+ 'sex',
+ 's\u00e1b'
+ ],
+ monthNames: [
+ 'janeiro',
+ 'fevereiro',
+ 'mar\u00e7o',
+ 'abril',
+ 'maio',
+ 'junho',
+ 'julho',
+ 'agosto',
+ 'setembro',
+ 'outubro',
+ 'novembro',
+ 'dezembro'
+ ],
+ monthShortNames: [
+ 'jan',
+ 'fev',
+ 'mar',
+ 'abr',
+ 'mai',
+ 'jun',
+ 'jul',
+ 'ago',
+ 'set',
+ 'out',
+ 'nov',
+ 'dez'
+ ],
+ };
+
+});
+
+}
diff --git a/ionic/components/item/item.ts b/ionic/components/item/item.ts
index f5d0568c98..e55ad09097 100644
--- a/ionic/components/item/item.ts
+++ b/ionic/components/item/item.ts
@@ -48,7 +48,7 @@ import {Label} from '../label/label';
'' +
' ' +
' ' +
- ' ' +
+ ' ' +
'' +
' ' +
'' +
diff --git a/ionic/components/picker/picker.ios.scss b/ionic/components/picker/picker.ios.scss
index 6e58e3811e..a254a7230b 100644
--- a/ionic/components/picker/picker.ios.scss
+++ b/ionic/components/picker/picker.ios.scss
@@ -15,11 +15,12 @@ $picker-ios-button-height: $picker-ios-toolbar-height !defau
$picker-ios-button-text-color: $link-ios-color !default;
$picker-ios-button-background-color: transparent !default;
-$picker-ios-column-padding: 0 12px !default;
+$picker-ios-column-padding: 0 4px !default;
+$picker-ios-column-perspective: 1000px !default;
-$picker-ios-option-padding: 0 10px !default;
+$picker-ios-option-padding: 0 !default;
$picker-ios-option-text-color: $list-ios-text-color !default;
-$picker-ios-option-font-size: 22px !default;
+$picker-ios-option-font-size: 20px !default;
$picker-ios-option-height: 42px !default;
$picker-ios-option-offset-y: (($picker-ios-height - $picker-ios-toolbar-height) / 2) - ($picker-ios-option-height / 2) - 10 !default;
@@ -74,7 +75,7 @@ $picker-highlight-opacity: .8 !default;
.picker-columns {
height: $picker-ios-height - $picker-ios-toolbar-height;
- perspective: 1800px;
+ perspective: $picker-ios-column-perspective;
}
.picker-col {
@@ -101,8 +102,6 @@ $picker-highlight-opacity: .8 !default;
margin: 0;
padding: $picker-ios-option-padding;
- width: calc(100% - 24px);
-
font-size: $picker-ios-option-font-size;
line-height: $picker-ios-option-height;
diff --git a/ionic/components/picker/picker.md.scss b/ionic/components/picker/picker.md.scss
index 825c72d3ca..8c10eb7335 100644
--- a/ionic/components/picker/picker.md.scss
+++ b/ionic/components/picker/picker.md.scss
@@ -15,15 +15,15 @@ $picker-md-button-height: $picker-md-toolbar-height !default
$picker-md-button-text-color: $link-md-color !default;
$picker-md-button-background-color: transparent !default;
-$picker-md-column-padding: 0 12px !default;
+$picker-md-column-padding: 0 8px !default;
-$picker-md-option-padding: 0 10px !default;
+$picker-md-option-padding: 0 !default;
$picker-md-option-text-color: $list-md-text-color !default;
$picker-md-option-font-size: 18px !default;
$picker-md-option-height: 42px !default;
$picker-md-option-offset-y: (($picker-md-height - $picker-md-toolbar-height) / 2) - ($picker-md-option-height / 2) - 10 !default;
-$picker-md-option-selected-font-size: 24px !default;
+$picker-md-option-selected-font-size: 22px !default;
$picker-md-option-selected-color: $link-md-color !default;
$picker-highlight-opacity: .8 !default;
@@ -98,8 +98,6 @@ $picker-highlight-opacity: .8 !default;
margin: 0;
padding: $picker-md-option-padding;
- width: calc(100% - 24px);
-
font-size: $picker-md-option-font-size;
line-height: $picker-md-option-height;
diff --git a/ionic/components/picker/picker.scss b/ionic/components/picker/picker.scss
index 792f3698fe..2eaf0cbcc4 100644
--- a/ionic/components/picker/picker.scss
+++ b/ionic/components/picker/picker.scss
@@ -99,10 +99,25 @@ ion-picker-cmp {
flex: 1;
width: 100%;
+}
+
+.picker-opt .button-inner {
+ display: block;
+
+ overflow: hidden;
- text-align: center;
text-overflow: ellipsis;
white-space: nowrap;
+
+ transition: opacity 150ms ease-in-out;
+}
+
+.picker-opt.picker-opt-disabled {
+ pointer-events: none;
+}
+
+.picker-opt-disabled .button-inner {
+ opacity: 0;
}
.picker-opts-left .button-inner {
diff --git a/ionic/components/picker/picker.ts b/ionic/components/picker/picker.ts
index 0c0f2c9758..0d58c58ac6 100644
--- a/ionic/components/picker/picker.ts
+++ b/ionic/components/picker/picker.ts
@@ -1,12 +1,12 @@
-import {Component, ElementRef, Input, ViewChild, Renderer, HostListener, ViewEncapsulation} from 'angular2/core';
+import {Component, ElementRef, Input, Output, EventEmitter, ViewChildren, QueryList, ViewChild, Renderer, HostListener, ViewEncapsulation} from 'angular2/core';
import {Animation} from '../../animations/animation';
import {Transition, TransitionOptions} from '../../transitions/transition';
import {Config} from '../../config/config';
-import {isPresent, isString, isNumber} from '../../util/util';
+import {isPresent, isString, isNumber, clamp} from '../../util/util';
import {NavParams} from '../nav/nav-params';
import {ViewController} from '../nav/view-controller';
-import {nativeRaf, cancelRaf, CSS, pointerCoord} from '../../util/dom';
+import {raf, cancelRaf, CSS, pointerCoord} from '../../util/dom';
/**
@@ -16,6 +16,8 @@ import {nativeRaf, cancelRaf, CSS, pointerCoord} from '../../util/dom';
*/
export class Picker extends ViewController {
+ @Output() change: EventEmitter;
+
constructor(opts: PickerOptions = {}) {
opts.columns = opts.columns || [];
opts.buttons = opts.buttons || [];
@@ -25,6 +27,8 @@ export class Picker extends ViewController {
this.viewType = 'picker';
this.isOverlay = true;
+ this.change = new EventEmitter();
+
// by default, pickers should not fire lifecycle events of other views
// for example, when an picker enters, the current active view should
// not fire its lifecycle events because it's not conceptually leaving
@@ -54,6 +58,14 @@ export class Picker extends ViewController {
this.data.columns.push(column);
}
+ getColumns(): PickerColumn[] {
+ return this.data.columns;
+ }
+
+ refresh() {
+ this.instance.refresh && this.instance.refresh();
+ }
+
/**
* @param {string} cssClass CSS class name to add to the picker's outer wrapper.
*/
@@ -76,7 +88,7 @@ export class Picker extends ViewController {
template:
'{{col.prefix}}
' +
'' +
- '' +
+ '' +
'{{o.text}}' +
' ' +
'
' +
@@ -91,7 +103,6 @@ export class Picker extends ViewController {
'(mousedown)': 'pointerStart($event)',
'(mousemove)': 'pointerMove($event)',
'(body:mouseup)': 'pointerEnd($event)',
- '(body:mouseout)': 'mouseOut($event)',
}
})
class PickerColumnCmp {
@@ -106,8 +117,11 @@ class PickerColumnCmp {
startY: number = null;
rafId: number;
bounceFrom: number;
+ minY: number;
maxY: number;
rotateFactor: number;
+ lastIndex: number;
+ @Output() change: EventEmitter = new EventEmitter();
constructor(config: Config) {
this.rotateFactor = config.getNumber('pickerRotateFactor', 0);
@@ -123,8 +137,7 @@ class PickerColumnCmp {
this.optHeight = (colEle.firstElementChild ? colEle.firstElementChild.clientHeight : 0);
// set the scroll position for the selected option
- let selectedIndex = this.col.options.indexOf(this.col.selected);
- this.setSelected(selectedIndex, 0);
+ this.setSelected(this.col.selectedIndex, 0);
}
pointerStart(ev) {
@@ -145,7 +158,24 @@ class PickerColumnCmp {
this.velocity = 0;
this.pos.length = 0;
this.pos.push(this.startY, Date.now());
- this.maxY = (this.optHeight * (this.col.options.length - 1)) * -1;
+
+ let minY = this.col.options.length - 1;
+ let maxY = 0;
+
+ for (var i = 0; i < this.col.options.length; i++) {
+ if (this.col.options[i].disabled) {
+ continue;
+ }
+ if (i < minY) {
+ minY = i;
+ }
+ if (i > maxY) {
+ maxY = i;
+ }
+ }
+
+ this.minY = (minY * this.optHeight * -1);
+ this.maxY = (maxY * this.optHeight * -1);
}
pointerMove(ev) {
@@ -163,21 +193,21 @@ class PickerColumnCmp {
// update the scroll position relative to pointer start position
var y = this.y + (currentY - this.startY);
- if (y > 0) {
+ if (y > this.minY) {
// scrolling up higher than scroll area
y = Math.pow(y, 0.8);
this.bounceFrom = y;
} else if (y < this.maxY) {
// scrolling down below scroll area
- y = y + Math.pow(this.maxY - y, 0.9);
+ y += Math.pow(this.maxY - y, 0.9);
this.bounceFrom = y;
} else {
this.bounceFrom = 0;
}
- this.update(y, 0, false);
+ this.update(y, 0, false, false);
}
}
@@ -190,11 +220,11 @@ class PickerColumnCmp {
if (this.bounceFrom > 0) {
// bounce back up
- this.update(0, 100, true);
+ this.update(this.minY, 100, true, true);
} else if (this.bounceFrom < 0) {
// bounce back down
- this.update(this.maxY, 100, true);
+ this.update(this.maxY, 100, true, true);
} else if (this.startY !== null) {
var endY = pointerCoord(ev).y;
@@ -226,7 +256,7 @@ class PickerColumnCmp {
ev.stopPropagation();
var y = this.y + (endY - this.startY);
- this.update(y, 0, true);
+ this.update(y, 0, true, true);
}
}
@@ -235,19 +265,13 @@ class PickerColumnCmp {
this.decelerate();
}
- mouseOut(ev) {
- if (ev.target.classList.contains('picker-col')) {
- this.pointerEnd(ev);
- }
- }
-
decelerate() {
- var y = 0;
+ let y = 0;
cancelRaf(this.rafId);
if (isNaN(this.y) || !this.optHeight) {
// fallback in case numbers get outta wack
- this.update(y, 0, true);
+ this.update(y, 0, true, true);
} else if (Math.abs(this.velocity) > 0) {
// still decelerating
@@ -258,9 +282,9 @@ class PickerColumnCmp {
y = Math.round(this.y - this.velocity);
- if (y > 0) {
+ if (y > this.minY) {
// whoops, it's trying to scroll up farther than the options we have!
- y = 0;
+ y = this.minY;
this.velocity = 0;
} else if (y < this.maxY) {
@@ -271,11 +295,13 @@ class PickerColumnCmp {
console.log(`decelerate y: ${y}, velocity: ${this.velocity}, optHeight: ${this.optHeight}`);
- this.update(y, 0, true);
+ var notLockedIn = (y % this.optHeight !== 0 || Math.abs(this.velocity) > 1);
- if (y % this.optHeight !== 0 || Math.abs(this.velocity) > 1) {
+ this.update(y, 0, true, !notLockedIn);
+
+ if (notLockedIn) {
// isn't locked in yet, keep decelerating until it is
- this.rafId = nativeRaf(this.decelerate.bind(this));
+ this.rafId = raf(this.decelerate.bind(this));
}
} else if (this.y % this.optHeight !== 0) {
@@ -307,22 +333,17 @@ class PickerColumnCmp {
this.velocity = 0;
// so what y position we're at
- this.update(y, duration, true);
+ this.update(y, duration, true, true);
}
- update(y: number, duration: number, saveY: boolean) {
+ update(y: number, duration: number, saveY: boolean, emitChange: boolean) {
// ensure we've got a good round number :)
y = Math.round(y);
- let selectedIndex = Math.abs(Math.round(y / this.optHeight));
+ this.col.selectedIndex = Math.max(Math.abs(Math.round(y / this.optHeight)), 0);
- this.col.selected = this.col.options[selectedIndex];
-
- let colEle: HTMLElement = this.colEle.nativeElement;
- let optElements: any = colEle.querySelectorAll('.picker-opt');
-
- for (var i = 0; i < optElements.length; i++) {
- var optEle: HTMLElement = optElements[i];
+ for (var i = 0; i < this.col.options.length; i++) {
+ var opt = this.col.options[i];
var optTop = (i * this.optHeight);
var optOffset = (optTop + y);
@@ -332,7 +353,7 @@ class PickerColumnCmp {
var translateZ = 0;
if (this.rotateFactor !== 0) {
- translateX = 10;
+ translateX = 0;
translateZ = 90;
if (rotateX > 90 || rotateX < -90) {
translateX = -9999;
@@ -343,17 +364,50 @@ class PickerColumnCmp {
translateY = optOffset;
}
- optEle.style[CSS.transform] = `rotateX(${rotateX}deg) translate3d(${translateX}px,${translateY}px,${translateZ}px)`;
-
- optEle.style[CSS.transitionDuration] = (duration > 0 ? duration + 'ms' : '');
-
- optEle.classList[i === selectedIndex ? 'add' : 'remove']('picker-opt-selected');
-
+ opt._trans = `rotateX(${rotateX}deg) translate3d(${translateX}px,${translateY}px,${translateZ}px)`;
+ opt._dur = (duration > 0 ? duration + 'ms' : '');
}
if (saveY) {
this.y = y;
}
+
+ if (emitChange) {
+ if (this.lastIndex === undefined) {
+ // have not set a last index yet
+ this.lastIndex = this.col.selectedIndex;
+
+ } else if (this.lastIndex !== this.col.selectedIndex) {
+ // new selected index has changed from the last index
+ // update the lastIndex and emit that it has changed
+ this.lastIndex = this.col.selectedIndex;
+ this.change.emit(this.col.options[this.col.selectedIndex]);
+ }
+ }
+ }
+
+ refresh() {
+ let min = this.col.options.length - 1;
+ let max = 0;
+
+ for (var i = 0; i < this.col.options.length; i++) {
+ var opt = this.col.options[i];
+ if (!opt.disabled) {
+ if (i < min) {
+ min = i;
+ }
+ if (i > max) {
+ max = i;
+ }
+ }
+ }
+
+ var selectedIndex = clamp(min, this.col.selectedIndex, max);
+
+ if (selectedIndex !== this.col.selectedIndex) {
+ var y = (selectedIndex * this.optHeight) * -1;
+ this.update(y, 150, true, true);
+ }
}
isPrevented(ev) {
@@ -390,7 +444,7 @@ class PickerColumnCmp {
'' +
'' +
'
' +
- '
' +
+ '
' +
'
' +
'
' +
'',
@@ -401,6 +455,7 @@ class PickerColumnCmp {
encapsulation: ViewEncapsulation.None,
})
class PickerDisplayCmp {
+ @ViewChildren(PickerColumnCmp) private _cols: QueryList;
private d: PickerOptions;
private created: number;
private lastClick: number;
@@ -452,12 +507,13 @@ class PickerDisplayCmp {
column.options = column.options.map(inputOpt => {
let opt: PickerColumnOption = {
text: '',
- value: ''
+ value: '',
+ disabled: inputOpt.disabled,
};
if (isPresent(inputOpt)) {
if (isString(inputOpt) || isNumber(inputOpt)) {
- opt.text = inputOpt;
+ opt.text = inputOpt.toString();
opt.value = inputOpt;
} else {
@@ -472,6 +528,18 @@ class PickerDisplayCmp {
});
}
+ refresh() {
+ this._cols.forEach(column => {
+ column.refresh();
+ });
+ }
+
+ private _colChange(selectedOption: PickerColumnOption) {
+ // one of the columns has changed its selected index
+ var picker = this._viewCtrl;
+ picker.change.emit(this.getSelected());
+ }
+
@HostListener('body:keyup', ['$event'])
private _keyUp(ev: KeyboardEvent) {
if (this.isEnabled() && this._viewCtrl.isLast()) {
@@ -518,7 +586,7 @@ class PickerDisplayCmp {
if (button.handler) {
// a handler has been provided, execute it
// pass the handler the values from the inputs
- if (button.handler(this.getValues()) === false) {
+ if (button.handler(this.getSelected()) === false) {
// if the return value of the handler is false then do not dismiss
shouldDismiss = false;
}
@@ -538,17 +606,20 @@ class PickerDisplayCmp {
}
dismiss(role): Promise {
- return this._viewCtrl.dismiss(this.getValues(), role);
+ return this._viewCtrl.dismiss(this.getSelected(), role);
}
- getValues() {
- // this is an alert with text inputs
- // return an object of all the values with the input name as the key
- let values = {};
- this.d.columns.forEach(col => {
- values[col.name] = col.selected ? col.selected.value : null;
+ getSelected(): any {
+ let selected = {};
+ this.d.columns.forEach((col, index) => {
+ let selectedColumn = col.options[col.selectedIndex];
+ selected[col.name] = {
+ text: selectedColumn ? selectedColumn.text : null,
+ value: selectedColumn ? selectedColumn.value : null,
+ columnIndex: index,
+ };
});
- return values;
+ return selected;
}
isEnabled() {
@@ -566,10 +637,10 @@ export interface PickerOptions {
export interface PickerColumn {
name?: string;
- selected?: PickerColumnOption;
+ selectedIndex?: number;
prefix?: string;
suffix?: string;
- options: PickerColumnOption[];
+ options?: PickerColumnOption[];
cssClass?: string;
columnWidth?: string;
prefixWidth?: string;
@@ -578,8 +649,9 @@ export interface PickerColumn {
}
export interface PickerColumnOption {
+ text?: string;
value?: any;
- text?: any;
+ disabled?: boolean;
}
diff --git a/ionic/components/picker/picker.wp.scss b/ionic/components/picker/picker.wp.scss
index 85fdb18cf4..a1603409bc 100644
--- a/ionic/components/picker/picker.wp.scss
+++ b/ionic/components/picker/picker.wp.scss
@@ -15,15 +15,15 @@ $picker-wp-button-height: $picker-wp-toolbar-height !default
$picker-wp-button-text-color: $link-wp-color !default;
$picker-wp-button-background-color: transparent !default;
-$picker-wp-column-padding: 0 12px !default;
+$picker-wp-column-padding: 0 4px !default;
-$picker-wp-option-padding: 0 10px !default;
+$picker-wp-option-padding: 0 !default;
$picker-wp-option-text-color: $list-wp-text-color !default;
$picker-wp-option-font-size: 18px !default;
$picker-wp-option-height: 42px !default;
$picker-wp-option-offset-y: (($picker-wp-height - $picker-wp-toolbar-height) / 2) - ($picker-wp-option-height / 2) - 10 !default;
-$picker-wp-option-selected-font-size: 24px !default;
+$picker-wp-option-selected-font-size: 22px !default;
$picker-wp-option-selected-color: $link-wp-color !default;
$picker-highlight-opacity: .8 !default;
@@ -110,8 +110,6 @@ $picker-highlight-opacity: .8 !default;
margin: 0;
padding: $picker-wp-option-padding;
- width: calc(100% - 24px);
-
font-size: $picker-wp-option-font-size;
line-height: $picker-wp-option-height;
diff --git a/ionic/components/select/select.ts b/ionic/components/select/select.ts
index 0daf458bd0..6a816e65bf 100644
--- a/ionic/components/select/select.ts
+++ b/ionic/components/select/select.ts
@@ -138,8 +138,8 @@ export class Select {
private _labelId: string;
private _multi: boolean = false;
private _options: QueryList;
- private _values: Array = [];
- private _texts: Array = [];
+ private _values: string[] = [];
+ private _texts: string[] = [];
private _text: string = '';
private _fn: Function;
private _isOpen: boolean = false;
@@ -282,7 +282,7 @@ export class Select {
// user cannot provide inputs from alertOptions
// alert inputs must be created by ionic from ion-options
- alertOptions.inputs = this._options.toArray().map(input => {
+ alertOptions.inputs = this._options.map(input => {
return {
type: (this._multi ? 'checkbox' : 'radio'),
label: input.text,
@@ -352,7 +352,7 @@ export class Select {
if (!this._values.length) {
// there are no values set at this point
// so check to see who should be checked
- this._values = val.toArray().filter(o => o.checked).map(o => o.value);
+ this._values = val.filter(o => o.checked).map(o => o.value);
}
this._updOpts();
@@ -365,7 +365,7 @@ export class Select {
this._texts = [];
if (this._options) {
- this._options.toArray().forEach(option => {
+ this._options.forEach(option => {
// check this option if the option's value is in the values array
option.checked = this._values.some(selectValue => {
return isCheckedProperty(selectValue, option.value);
diff --git a/ionic/config/directives.ts b/ionic/config/directives.ts
index 4ae7d5ff94..354dd0de82 100644
--- a/ionic/config/directives.ts
+++ b/ionic/config/directives.ts
@@ -27,6 +27,7 @@ import {Spinner} from '../components/spinner/spinner';
import {Checkbox} from '../components/checkbox/checkbox';
import {Select} from '../components/select/select';
import {Option} from '../components/option/option';
+import {DateTime} from '../components/datetime/datetime';
import {Toggle} from '../components/toggle/toggle';
import {TextInput, TextArea} from '../components/input/input';
import {Label} from '../components/label/label';
@@ -44,7 +45,7 @@ import {ShowWhen, HideWhen} from '../components/show-hide-when/show-hide-when';
* @name IONIC_DIRECTIVES
* @description
* The core Ionic directives as well as Angular's `CORE_DIRECTIVES` and `FORM_DIRECTIVES` are
- * avaialbe automatically when you bootstrap your app with the `@App` decorator. This means
+ * available automatically when you bootstrap your app with the `@App` decorator. This means
* if you are using custom components you no longer need to import `IONIC_DIRECTIVES` as they
* are part of the `@App`s default directives.
*
@@ -125,6 +126,7 @@ import {ShowWhen, HideWhen} from '../components/show-hide-when/show-hide-when';
* - RadioButton
* - Select
* - Option
+ * - DateTime
* - Toggle
* - TextArea
* - TextInput
@@ -197,6 +199,7 @@ export const IONIC_DIRECTIVES = [
RadioButton,
Select,
Option,
+ DateTime,
Toggle,
TextArea,
TextInput,
diff --git a/ionic/util.ts b/ionic/util.ts
index 008a46d8a4..f7cee16882 100644
--- a/ionic/util.ts
+++ b/ionic/util.ts
@@ -1,3 +1,4 @@
import * as domUtil from './util/dom';
export const dom = domUtil;
export * from './util/util';
+export * from './util/datetime-util';
diff --git a/ionic/util/datetime-util.ts b/ionic/util/datetime-util.ts
new file mode 100644
index 0000000000..bf3ade0bd2
--- /dev/null
+++ b/ionic/util/datetime-util.ts
@@ -0,0 +1,500 @@
+import {isBlank, isPresent, isString, isObject, assign} from './util';
+
+
+export function renderDateTime(template: string, value: DateTimeData, locale: LocaleData) {
+ if (isBlank(value)) {
+ return '';
+ }
+
+ let tokens = [];
+ let hasText = false;
+ FORMAT_KEYS.forEach((format, index) => {
+ if (template.indexOf(format.f) > -1) {
+ var token = '{' + index + '}';
+ var text = renderTextFormat(format.f, value[format.k], value, locale);
+
+ if (!hasText && text && isPresent(value[format.k])) {
+ hasText = true;
+ }
+
+ tokens.push(token, text);
+
+ template = template.replace(format.f, token);
+ }
+ });
+
+ if (!hasText) {
+ return '';
+ }
+
+ for (var i = 0; i < tokens.length; i += 2) {
+ template = template.replace(tokens[i], tokens[i + 1]);
+ }
+
+ return template;
+}
+
+
+export function renderTextFormat(format: string, value: any, date: DateTimeData, locale: LocaleData): string {
+
+ if (format === FORMAT_DDDD || format === FORMAT_DDD) {
+ try {
+ value = (new Date(date.year, date.month - 1, date.day)).getDay();
+
+ if (format === FORMAT_DDDD) {
+ return (isPresent(locale.dayShort) ? locale.dayShort : DAY_NAMES)[value];
+ }
+
+ return (isPresent(locale.dayShortNames) ? locale.dayShortNames : DAY_SHORT_NAMES)[value];
+
+ } catch (e) {}
+
+ return '';
+ }
+
+ if (format === FORMAT_A) {
+ return date ? date.hour < 12 ? 'AM' : 'PM' : isPresent(value) ? value.toUpperCase() : '';
+ }
+
+ if (format === FORMAT_a) {
+ return date ? date.hour < 12 ? 'am' : 'pm' : isPresent(value) ? value : '';
+ }
+
+ if (isBlank(value)) {
+ return '';
+ }
+
+ if (format === FORMAT_YY || format === FORMAT_MM ||
+ format === FORMAT_DD || format === FORMAT_HH ||
+ format === FORMAT_mm) {
+ return twoDigit(value);
+ }
+
+ if (format === FORMAT_YYYY) {
+ return fourDigit(value);
+ }
+
+ if (format === FORMAT_MMMM) {
+ return (isPresent(locale.monthNames) ? locale.monthNames : MONTH_NAMES)[value - 1];
+ }
+
+ if (format === FORMAT_MMM) {
+ return (isPresent(locale.monthShortNames) ? locale.monthShortNames : MONTH_SHORT_NAMES)[value - 1];
+ }
+
+ if (format === FORMAT_hh || format === FORMAT_h) {
+ if (value === 0) {
+ return '12';
+ }
+ if (value > 12) {
+ value -= 12;
+ }
+ if (format === FORMAT_hh && value < 10) {
+ return ('0' + value);
+ }
+ }
+
+ return value.toString();
+}
+
+
+export function dateValueRange(format: string, min: DateTimeData, max: DateTimeData): any[] {
+ let opts: any[] = [];
+ let i: number;
+
+ if (format === FORMAT_YYYY || format === FORMAT_YY) {
+ // year
+ i = max.year;
+ while (i >= min.year) {
+ opts.push(i--);
+ }
+
+ } else if (format === FORMAT_MMMM || format === FORMAT_MMM ||
+ format === FORMAT_MM || format === FORMAT_M ||
+ format === FORMAT_hh || format === FORMAT_h) {
+ // month or 12-hour
+ for (i = 1; i < 13; i++) {
+ opts.push(i);
+ }
+
+ } else if (format === FORMAT_DDDD || format === FORMAT_DDD ||
+ format === FORMAT_DD || format === FORMAT_D) {
+ // day
+ for (i = 1; i < 32; i++) {
+ opts.push(i);
+ }
+
+ } else if (format === FORMAT_HH || format === FORMAT_H) {
+ // 24-hour
+ for (i = 0; i < 24; i++) {
+ opts.push(i);
+ }
+
+ } else if (format === FORMAT_mm || format === FORMAT_m) {
+ // minutes
+ for (i = 0; i < 60; i++) {
+ opts.push(i);
+ }
+
+ } else if (format === FORMAT_A || format === FORMAT_a) {
+ // AM/PM
+ opts.push('am', 'pm');
+ }
+
+ return opts;
+}
+
+export function dateSortValue(year: number, month: number, day: number): number {
+ return parseInt(`1${fourDigit(year)}${twoDigit(month)}${twoDigit(day)}`, 10);
+}
+
+export function dateDataSortValue(data: DateTimeData): number {
+ if (data) {
+ return dateSortValue(data.year, data.month, data.day);
+ }
+ return -1;
+}
+
+export function daysInMonth(month: number, year: number): number {
+ return (month === 4 || month === 6 || month === 9 || month === 11) ? 30 : (month === 2) ? isLeapYear(year) ? 29 : 28 : 31;
+}
+
+export function isLeapYear(year: number): boolean {
+ return (year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0);
+}
+
+
+const ISO_8601_REGEXP = /^(\d{4}|[+\-]\d{6})(?:-(\d{2})(?:-(\d{2}))?)?(?:T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{3}))?)?(?:(Z)|([+\-])(\d{2})(?::(\d{2}))?)?)?$/;
+const TIME_REGEXP = /^((\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{3}))?)?(?:(Z)|([+\-])(\d{2})(?::(\d{2}))?)?)?$/;
+
+export function parseDate(val: any): DateTimeData {
+ // manually parse IS0 cuz Date.parse cannot be trusted
+ // ISO 8601 format: 1994-12-15T13:47:20Z
+ let parse: any[];
+
+ if (isPresent(val) && val !== '') {
+ // try parsing for just time first, HH:MM
+ parse = TIME_REGEXP.exec(val);
+ if (isPresent(parse)) {
+ // adjust the array so it fits nicely with the datetime parse
+ parse.unshift(undefined, undefined);
+ parse[2] = parse[3] = undefined;
+
+ } else {
+ // try parsing for full ISO datetime
+ parse = ISO_8601_REGEXP.exec(val);
+ }
+ }
+
+ if (isBlank(parse)) {
+ // wasn't able to parse the ISO datetime
+ return null;
+ }
+
+ // ensure all the parse values exist with at least 0
+ for (var i = 1; i < 8; i++) {
+ parse[i] = (parse[i] !== undefined ? parseInt(parse[i], 10) : null);
+ }
+
+ var tzOffset = 0;
+ if (isPresent(parse[9]) && isPresent(parse[10])) {
+ // hours
+ tzOffset = parseInt(parse[10], 10) * 60;
+ if (isPresent(parse[11])) {
+ // minutes
+ tzOffset += parseInt(parse[11], 10);
+ }
+ if (parse[9] === '-') {
+ // + or -
+ tzOffset *= -1;
+ }
+ }
+
+ return {
+ year: parse[1],
+ month: parse[2],
+ day: parse[3],
+ hour: parse[4],
+ minute: parse[5],
+ second: parse[6],
+ millisecond: parse[7],
+ tzOffset: tzOffset,
+ };
+}
+
+
+export function updateDate(existingData: DateTimeData, newData: any) {
+ if (isPresent(newData) && newData !== '') {
+
+ if (isString(newData)) {
+ // new date is a string, and hopefully in the ISO format
+ // convert it to our DateTimeData if a valid ISO
+ newData = parseDate(newData);
+ if (newData) {
+ // successfully parsed the ISO string to our DateTimeData
+ assign(existingData, newData);
+ return;
+ }
+
+ } else if ((isPresent(newData.year) || isPresent(newData.hour))) {
+ // newData is from of a datetime picker's selected values
+ // update the existing DateTimeData data with the new values
+
+ // do some magic for 12-hour values
+ if (isPresent(newData.ampm) && isPresent(newData.hour)) {
+ if (newData.ampm.value === 'pm') {
+ newData.hour.value = (newData.hour.value === 12 ? 12 : newData.hour.value + 12);
+
+ } else {
+ newData.hour.value = (newData.hour.value === 12 ? 0 : newData.hour.value);
+ }
+ }
+
+ // merge new values from the picker's selection
+ // to the existing DateTimeData values
+ for (var k in newData) {
+ existingData[k] = newData[k].value;
+ }
+
+ return;
+ }
+
+ // eww, invalid data
+ console.warn(`Error parsing date: "${newData}". Please provide a valid ISO 8601 datetime format: https://www.w3.org/TR/NOTE-datetime`);
+ }
+}
+
+
+export function parseTemplate(template: string): string[] {
+ var formats: string[] = [];
+
+ var foundFormats: {index: number; format: string}[] = [];
+ FORMAT_KEYS.forEach(format => {
+ var index = template.indexOf(format.f);
+ if (index > -1) {
+ template = template.replace(format.f, replacer(format.f));
+ foundFormats.push({
+ index: index,
+ format: format.f,
+ });
+ }
+ });
+
+ // sort the found formats back to their original order
+ foundFormats.sort((a, b) => (a.index > b.index) ? 1 : (a.index < b.index) ? -1 : 0);
+
+ return foundFormats.map(val => val.format);
+}
+
+
+function replacer(originalStr: string): string {
+ let r = '';
+ for (var i = 0; i < originalStr.length; i++) {
+ r += '^';
+ }
+ return r;
+}
+
+
+export function getValueFromFormat(date: DateTimeData, format: string) {
+ if (format === FORMAT_A || format === FORMAT_a) {
+ return (date.hour < 12 ? 'am' : 'pm');
+ }
+ if (format === FORMAT_hh || format === FORMAT_h) {
+ return (date.hour > 12 ? date.hour - 12 : date.hour);
+ }
+ return date[convertFormatToKey(format)];
+}
+
+
+export function convertFormatToKey(format: string): string {
+ for (var k in FORMAT_KEYS) {
+ if (FORMAT_KEYS[k].f === format) {
+ return FORMAT_KEYS[k].k;
+ }
+ }
+ return null;
+}
+
+
+export function convertDataToISO(data: DateTimeData): string {
+ // https://www.w3.org/TR/NOTE-datetime
+ let rtn = '';
+
+ if (isPresent(data)) {
+ if (isPresent(data.year)) {
+ // YYYY
+ rtn = fourDigit(data.year);
+
+ if (isPresent(data.month)) {
+ // YYYY-MM
+ rtn += '-' + twoDigit(data.month);
+
+ if (isPresent(data.day)) {
+ // YYYY-MM-DD
+ rtn += '-' + twoDigit(data.day);
+
+ if (isPresent(data.hour)) {
+ // YYYY-MM-DDTHH:mm:SS
+ rtn += `T${twoDigit(data.hour)}:${twoDigit(data.minute)}:${twoDigit(data.second)}`;
+
+ if (data.millisecond > 0) {
+ // YYYY-MM-DDTHH:mm:SS.SSS
+ rtn += '.' + threeDigit(data.millisecond);
+ }
+
+ if (data.tzOffset === 0) {
+ // YYYY-MM-DDTHH:mm:SSZ
+ rtn += 'Z';
+
+ } else {
+ // YYYY-MM-DDTHH:mm:SS+/-HH:mm
+ rtn += (data.tzOffset > 0 ? '+' : '-') + twoDigit(Math.floor(data.tzOffset / 60)) + ':' + twoDigit(data.tzOffset % 60);
+ }
+ }
+ }
+ }
+
+ } else if (isPresent(data.hour)) {
+ // HH:mm
+ rtn = twoDigit(data.hour) + ':' + twoDigit(data.minute);
+
+ if (isPresent(data.second)) {
+ // HH:mm:SS
+ rtn += ':' + twoDigit(data.second);
+
+ if (isPresent(data.millisecond)) {
+ // HH:mm:SS.SSS
+ rtn += '.' + threeDigit(data.millisecond);
+ }
+ }
+ }
+ }
+
+ return rtn;
+}
+
+function twoDigit(val: number): string {
+ return ('0' + (isPresent(val) ? val : '0')).slice(-2);
+}
+
+function threeDigit(val: number): string {
+ return ('00' + (isPresent(val) ? val : '0')).slice(-3);
+}
+
+function fourDigit(val: number): string {
+ return ('000' + (isPresent(val) ? val : '0')).slice(-4);
+}
+
+
+export interface DateTimeData {
+ year?: number;
+ month?: number;
+ day?: number;
+ hour?: number;
+ minute?: number;
+ second?: number;
+ millisecond?: number;
+ tzOffset?: number;
+}
+
+
+export interface LocaleData {
+ monthNames?: string[];
+ monthShortNames?: string[];
+ dayShort?: string[];
+ dayShortNames?: string[];
+}
+
+
+const FORMAT_YYYY = 'YYYY';
+const FORMAT_YY = 'YY';
+const FORMAT_MMMM = 'MMMM';
+const FORMAT_MMM = 'MMM';
+const FORMAT_MM = 'MM';
+const FORMAT_M = 'M';
+const FORMAT_DDDD = 'DDDD';
+const FORMAT_DDD = 'DDD';
+const FORMAT_DD = 'DD';
+const FORMAT_D = 'D';
+const FORMAT_HH = 'HH';
+const FORMAT_H = 'H';
+const FORMAT_hh = 'hh';
+const FORMAT_h = 'h';
+const FORMAT_mm = 'mm';
+const FORMAT_m = 'm';
+const FORMAT_A = 'A';
+const FORMAT_a = 'a';
+
+const FORMAT_KEYS = [
+ { f: FORMAT_YYYY, k: 'year' },
+ { f: FORMAT_MMMM, k: 'month' },
+ { f: FORMAT_DDDD, k: 'day' },
+ { f: FORMAT_MMM, k: 'month' },
+ { f: FORMAT_DDD, k: 'day' },
+ { f: FORMAT_YY, k: 'year' },
+ { f: FORMAT_MM, k: 'month' },
+ { f: FORMAT_DD, k: 'day' },
+ { f: FORMAT_HH, k: 'hour' },
+ { f: FORMAT_hh, k: 'hour' },
+ { f: FORMAT_mm, k: 'minute' },
+ { f: FORMAT_M, k: 'month' },
+ { f: FORMAT_D, k: 'day' },
+ { f: FORMAT_H, k: 'hour' },
+ { f: FORMAT_h, k: 'hour' },
+ { f: FORMAT_m, k: 'minute' },
+ { f: FORMAT_A, k: 'ampm' },
+ { f: FORMAT_a, k: 'ampm' },
+];
+
+const FORMAT_REGEX = /(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|DD?D?D?|ddd?d?|YYYY|YY|a|A|hh?|HH?|mm?|ss?|.)/g;
+
+const DAY_NAMES = [
+ 'Sunday',
+ 'Monday',
+ 'Tuesday',
+ 'Wednesday',
+ 'Thursday',
+ 'Friday',
+ 'Saturday',
+];
+
+const DAY_SHORT_NAMES = [
+ 'Sun',
+ 'Mon',
+ 'Tue',
+ 'Wed',
+ 'Thu',
+ 'Fri',
+ 'Sat',
+];
+
+const MONTH_NAMES = [
+ 'January',
+ 'February',
+ 'March',
+ 'April',
+ 'May',
+ 'June',
+ 'July',
+ 'August',
+ 'September',
+ 'October',
+ 'November',
+ 'December',
+];
+
+const MONTH_SHORT_NAMES = [
+ 'Jan',
+ 'Feb',
+ 'Mar',
+ 'Apr',
+ 'May',
+ 'Jun',
+ 'Jul',
+ 'Aug',
+ 'Sep',
+ 'Oct',
+ 'Nov',
+ 'Dec',
+];
diff --git a/ionic/util/test/datetime-util.spec.ts b/ionic/util/test/datetime-util.spec.ts
new file mode 100644
index 0000000000..c9b06d0f35
--- /dev/null
+++ b/ionic/util/test/datetime-util.spec.ts
@@ -0,0 +1,792 @@
+import * as datetime from '../../../ionic/util/datetime-util';
+
+export function run() {
+
+describe('convertDataToISO', () => {
+
+ it('should convert DateTimeData to datetime string, +330 tz offset', () => {
+ var data: datetime.DateTimeData = {
+ year: 1994,
+ month: 12,
+ day: 15,
+ hour: 13,
+ minute: 47,
+ second: 20,
+ millisecond: 789,
+ tzOffset: 330,
+ };
+
+ var str = datetime.convertDataToISO(data);
+ expect(str).toEqual('1994-12-15T13:47:20.789+05:30');
+ });
+
+ it('should convert DateTimeData to datetime string, Z timezone', () => {
+ var data: datetime.DateTimeData = {
+ year: 1994,
+ month: 12,
+ day: 15,
+ hour: 13,
+ minute: null,
+ second: null,
+ millisecond: null,
+ tzOffset: 0,
+ };
+
+ var str = datetime.convertDataToISO(data);
+ expect(str).toEqual('1994-12-15T13:00:00Z');
+ });
+
+ it('should convert DateTimeData to YYYY-MM-DD', () => {
+ var data: datetime.DateTimeData = {
+ year: 1994,
+ month: 1,
+ day: 1,
+ hour: null,
+ minute: null,
+ second: null,
+ millisecond: null,
+ tzOffset: 0,
+ };
+
+ var str = datetime.convertDataToISO(data);
+ expect(str).toEqual('1994-01-01');
+ });
+
+ it('should convert DateTimeData to YYYY-MM', () => {
+ var data: datetime.DateTimeData = {
+ year: 1994,
+ month: 1,
+ day: null,
+ hour: null,
+ minute: null,
+ second: null,
+ millisecond: null,
+ tzOffset: 0,
+ };
+
+ var str = datetime.convertDataToISO(data);
+ expect(str).toEqual('1994-01');
+ });
+
+ it('should convert DateTimeData to YYYY', () => {
+ var data: datetime.DateTimeData = {
+ year: 1994,
+ month: null,
+ day: null,
+ hour: null,
+ minute: null,
+ second: null,
+ millisecond: null,
+ tzOffset: 0,
+ };
+
+ var str = datetime.convertDataToISO(data);
+ expect(str).toEqual('1994');
+ });
+
+ it('should convert DateTimeData to HH:mm:SS.SSS', () => {
+ var data: datetime.DateTimeData = {
+ year: null,
+ month: null,
+ day: null,
+ hour: 13,
+ minute: 47,
+ second: 20,
+ millisecond: 789,
+ tzOffset: 0,
+ };
+
+ var str = datetime.convertDataToISO(data);
+ expect(str).toEqual('13:47:20.789');
+ });
+
+ it('should convert DateTimeData to HH:mm:SS string', () => {
+ var data: datetime.DateTimeData = {
+ year: null,
+ month: null,
+ day: null,
+ hour: 13,
+ minute: 47,
+ second: 20,
+ millisecond: null,
+ tzOffset: 0,
+ };
+
+ var str = datetime.convertDataToISO(data);
+ expect(str).toEqual('13:47:20');
+ });
+
+ it('should convert DateTimeData to HH:mm string', () => {
+ var data: datetime.DateTimeData = {
+ year: null,
+ month: null,
+ day: null,
+ hour: 13,
+ minute: 47,
+ second: null,
+ millisecond: null,
+ tzOffset: 0,
+ };
+
+ var str = datetime.convertDataToISO(data);
+ expect(str).toEqual('13:47');
+ });
+
+ it('should not convert DateTimeData with null data', () => {
+ var data: datetime.DateTimeData = {
+ year: null,
+ month: null,
+ day: null,
+ hour: null,
+ minute: null,
+ second: null,
+ millisecond: null,
+ tzOffset: 0,
+ };
+
+ var str = datetime.convertDataToISO(data);
+ expect(str).toEqual('');
+
+ var str = datetime.convertDataToISO({});
+ expect(str).toEqual('');
+ });
+
+});
+
+
+describe('convertFormatToKey', () => {
+
+ it('should convert year formats to their DateParse key', () => {
+ expect(datetime.convertFormatToKey('YYYY')).toEqual('year');
+ expect(datetime.convertFormatToKey('YY')).toEqual('year');
+ });
+
+ it('should convert month formats to their DateParse key', () => {
+ expect(datetime.convertFormatToKey('MMMM')).toEqual('month');
+ expect(datetime.convertFormatToKey('MMM')).toEqual('month');
+ expect(datetime.convertFormatToKey('MM')).toEqual('month');
+ expect(datetime.convertFormatToKey('M')).toEqual('month');
+ });
+
+ it('should convert day formats to their DateParse key', () => {
+ expect(datetime.convertFormatToKey('DDDD')).toEqual('day');
+ expect(datetime.convertFormatToKey('DDD')).toEqual('day');
+ expect(datetime.convertFormatToKey('DD')).toEqual('day');
+ expect(datetime.convertFormatToKey('D')).toEqual('day');
+ });
+
+ it('should convert hour formats to their DateParse key', () => {
+ expect(datetime.convertFormatToKey('HH')).toEqual('hour');
+ expect(datetime.convertFormatToKey('H')).toEqual('hour');
+ expect(datetime.convertFormatToKey('hh')).toEqual('hour');
+ expect(datetime.convertFormatToKey('h')).toEqual('hour');
+ });
+
+ it('should convert minute formats to their DateParse key', () => {
+ expect(datetime.convertFormatToKey('mm')).toEqual('minute');
+ expect(datetime.convertFormatToKey('m')).toEqual('minute');
+ });
+
+ it('should convert am/pm formats to their DateParse key', () => {
+ expect(datetime.convertFormatToKey('A')).toEqual('ampm');
+ expect(datetime.convertFormatToKey('a')).toEqual('ampm');
+ });
+
+});
+
+
+describe('getValueFromFormat', () => {
+
+ it('should convert 24 hour to am value', () => {
+ var d = datetime.parseDate('00:47');
+ expect(datetime.getValueFromFormat(d, 'hh')).toEqual(0);
+ expect(datetime.getValueFromFormat(d, 'h')).toEqual(0);
+
+ var d = datetime.parseDate('11:47');
+ expect(datetime.getValueFromFormat(d, 'hh')).toEqual(11);
+ expect(datetime.getValueFromFormat(d, 'h')).toEqual(11);
+ });
+
+ it('should convert 24 hour to pm value', () => {
+ var d = datetime.parseDate('12:47');
+ expect(datetime.getValueFromFormat(d, 'hh')).toEqual(12);
+ expect(datetime.getValueFromFormat(d, 'h')).toEqual(12);
+
+ var d = datetime.parseDate('13:47');
+ expect(datetime.getValueFromFormat(d, 'hh')).toEqual(1);
+ expect(datetime.getValueFromFormat(d, 'h')).toEqual(1);
+ });
+
+ it('should convert am hours to am value', () => {
+ var d = datetime.parseDate('00:47');
+ expect(datetime.getValueFromFormat(d, 'A')).toEqual('am');
+ expect(datetime.getValueFromFormat(d, 'a')).toEqual('am');
+
+ var d = datetime.parseDate('11:47');
+ expect(datetime.getValueFromFormat(d, 'A')).toEqual('am');
+ expect(datetime.getValueFromFormat(d, 'a')).toEqual('am');
+ });
+
+ it('should convert pm hours to pm value', () => {
+ var d = datetime.parseDate('12:47');
+ expect(datetime.getValueFromFormat(d, 'A')).toEqual('pm');
+ expect(datetime.getValueFromFormat(d, 'a')).toEqual('pm');
+
+ var d = datetime.parseDate('23:47');
+ expect(datetime.getValueFromFormat(d, 'A')).toEqual('pm');
+ expect(datetime.getValueFromFormat(d, 'a')).toEqual('pm');
+ });
+
+ it('should convert date formats to values', () => {
+ var d = datetime.parseDate('1994-12-15T13:47:20.789Z');
+ expect(datetime.getValueFromFormat(d, 'YYYY')).toEqual(1994);
+ expect(datetime.getValueFromFormat(d, 'M')).toEqual(12);
+ expect(datetime.getValueFromFormat(d, 'DDDD')).toEqual(15);
+ });
+
+});
+
+
+describe('parseTemplate', () => {
+
+ it('should get formats from template "a A m mm h hh H HH D DD DDD DDDD M MM MMM MMMM YY YYYY"', () => {
+ var formats = datetime.parseTemplate('a A m mm h hh H HH D DD DDD DDDD M MM MMM MMMM YY YYYY');
+ expect(formats[0]).toEqual('a');
+ expect(formats[1]).toEqual('A');
+ expect(formats[2]).toEqual('m');
+ expect(formats[3]).toEqual('mm');
+ expect(formats[4]).toEqual('h');
+ expect(formats[5]).toEqual('hh');
+ expect(formats[6]).toEqual('H');
+ expect(formats[7]).toEqual('HH');
+ expect(formats[8]).toEqual('D');
+ expect(formats[9]).toEqual('DD');
+ expect(formats[10]).toEqual('DDD');
+ expect(formats[11]).toEqual('DDDD');
+ expect(formats[12]).toEqual('M');
+ expect(formats[13]).toEqual('MM');
+ expect(formats[14]).toEqual('MMM');
+ expect(formats[15]).toEqual('MMMM');
+ expect(formats[16]).toEqual('YY');
+ expect(formats[17]).toEqual('YYYY');
+ });
+
+ it('should get formats from template YYMMMMDDHma', () => {
+ var formats = datetime.parseTemplate('YYMMMMDDHma');
+ expect(formats[0]).toEqual('YY');
+ expect(formats[1]).toEqual('MMMM');
+ expect(formats[2]).toEqual('DD');
+ expect(formats[3]).toEqual('H');
+ expect(formats[4]).toEqual('m');
+ expect(formats[5]).toEqual('a');
+ });
+
+ it('should get formats from template MM/DD/YYYY', () => {
+ var formats = datetime.parseTemplate('MM/DD/YYYY');
+ expect(formats[0]).toEqual('MM');
+ expect(formats[1]).toEqual('DD');
+ expect(formats[2]).toEqual('YYYY');
+ });
+
+});
+
+describe('renderDateTime', () => {
+
+ it('should show correct month and day name defaults', () => {
+ var d = datetime.parseDate('2016-05-12');
+ var r = datetime.renderDateTime('DDDD MMM D YYYY', d, {});
+ expect(r).toEqual('Thursday May 12 2016');
+ });
+
+ it('should format h:mm a, PM', () => {
+ var d = datetime.parseDate('1994-12-15T13:47:20.789Z');
+ expect(datetime.renderDateTime('h:mm a', d, {})).toEqual('1:47 pm');
+ });
+
+ it('should get empty text for format without data', () => {
+ var emptyObj = {};
+ expect(datetime.renderDateTime('MMMM D, YYYY h:mm a', emptyObj, {})).toEqual('');
+
+ var dataWithNulls: datetime.DateTimeData = {
+ year: null,
+ month: null,
+ day: null,
+ hour: null,
+ minute: null,
+ second: null,
+ millisecond: null,
+ tzOffset: 0,
+ };
+ expect(datetime.renderDateTime('MMMM D, YYYY h:mm a', dataWithNulls, {})).toEqual('');
+ });
+
+ it('should format h:mm a, AM', () => {
+ var d = datetime.parseDate('1994-12-15T00:47:20.789Z');
+ expect(datetime.renderDateTime('h:mm a', d, {})).toEqual('12:47 am');
+ });
+
+ it('should format HH:mm', () => {
+ var d = datetime.parseDate('1994-12-15T13:47:20.789Z');
+ expect(datetime.renderDateTime('HH:mm', d, {})).toEqual('13:47');
+ });
+
+ it('should format MMMM D, YYYY', () => {
+ var d = datetime.parseDate('1994-12-15T13:47:20.789Z');
+ expect(datetime.renderDateTime('MMMM D, YYYY', d, {})).toEqual('December 15, 1994');
+ });
+
+ it('should format MM/DD/YYYY', () => {
+ var d = datetime.parseDate('1994-12-15T13:47:20.789Z');
+ expect(datetime.renderDateTime('MM/DD/YYYY', d, {})).toEqual('12/15/1994');
+ });
+
+ it('should format DD-MM-YY', () => {
+ var d = datetime.parseDate('1994-12-15T13:47:20.789Z');
+ expect(datetime.renderDateTime('DD-MM-YY', d, {})).toEqual('15-12-94');
+ });
+
+ it('should format YYYY', () => {
+ var d = datetime.parseDate('1994-12-15T13:47:20.789Z');
+ expect(datetime.renderDateTime('DD-MM-YY', d, {})).toEqual('15-12-94');
+ });
+
+ it('should format YYYY$MM.DD*HH?mm', () => {
+ var d = datetime.parseDate('1994-12-15T13:47:20.789Z');
+ expect(datetime.renderDateTime('YYYY$MM.DD*HH?mm', d, {})).toEqual('1994$12.15*13?47');
+ });
+
+ it('should return empty when template invalid', () => {
+ var d = datetime.parseDate('1994-12-15T13:47:20.789Z');
+ expect(datetime.renderDateTime('', d, {})).toEqual('');
+ });
+
+ it('should return empty when date invalid', () => {
+ var d = datetime.parseDate(null);
+ expect(datetime.renderDateTime('YYYY', d, {})).toEqual('');
+ });
+
+});
+
+describe('renderTextFormat', () => {
+
+ it('should return a', () => {
+ var d = datetime.parseDate('00:47');
+ expect(datetime.renderTextFormat('a', 'am', d, {})).toEqual('am');
+ expect(datetime.renderTextFormat('a', 'am', null, {})).toEqual('am');
+
+ var d = datetime.parseDate('11:47');
+ expect(datetime.renderTextFormat('a', 'am', d, {})).toEqual('am');
+ expect(datetime.renderTextFormat('a', 'am', null, {})).toEqual('am');
+
+ var d = datetime.parseDate('12:47');
+ expect(datetime.renderTextFormat('a', 'pm', d, {})).toEqual('pm');
+ expect(datetime.renderTextFormat('a', 'pm', null, {})).toEqual('pm');
+ });
+
+ it('should return A', () => {
+ var d = datetime.parseDate('00:47');
+ expect(datetime.renderTextFormat('A', 'am', d, {})).toEqual('AM');
+ expect(datetime.renderTextFormat('A', 'am', null, {})).toEqual('AM');
+
+ var d = datetime.parseDate('11:47');
+ expect(datetime.renderTextFormat('A', 'am', d, {})).toEqual('AM');
+ expect(datetime.renderTextFormat('A', 'am', null, {})).toEqual('AM');
+
+ var d = datetime.parseDate('12:47');
+ expect(datetime.renderTextFormat('A', 'pm', d, {})).toEqual('PM');
+ expect(datetime.renderTextFormat('A', 'pm', null, {})).toEqual('PM');
+ });
+
+ it('should return m', () => {
+ expect(datetime.renderTextFormat('m', 1, null, {})).toEqual('1');
+ expect(datetime.renderTextFormat('m', 12, null, {})).toEqual('12');
+ });
+
+ it('should return mm', () => {
+ expect(datetime.renderTextFormat('mm', 1, null, {})).toEqual('01');
+ expect(datetime.renderTextFormat('mm', 12, null, {})).toEqual('12');
+ });
+
+ it('should return hh', () => {
+ expect(datetime.renderTextFormat('hh', 0, null, {})).toEqual('12');
+ expect(datetime.renderTextFormat('hh', 1, null, {})).toEqual('01');
+ expect(datetime.renderTextFormat('hh', 11, null, {})).toEqual('11');
+ expect(datetime.renderTextFormat('hh', 12, null, {})).toEqual('12');
+ expect(datetime.renderTextFormat('hh', 13, null, {})).toEqual('01');
+ expect(datetime.renderTextFormat('hh', 21, null, {})).toEqual('09');
+ expect(datetime.renderTextFormat('hh', 23, null, {})).toEqual('11');
+ });
+
+ it('should return h', () => {
+ expect(datetime.renderTextFormat('h', 0, null, {})).toEqual('12');
+ expect(datetime.renderTextFormat('h', 1, null, {})).toEqual('1');
+ expect(datetime.renderTextFormat('h', 11, null, {})).toEqual('11');
+ expect(datetime.renderTextFormat('h', 12, null, {})).toEqual('12');
+ expect(datetime.renderTextFormat('h', 13, null, {})).toEqual('1');
+ expect(datetime.renderTextFormat('h', 21, null, {})).toEqual('9');
+ expect(datetime.renderTextFormat('h', 23, null, {})).toEqual('11');
+ });
+
+ it('should return hh', () => {
+ expect(datetime.renderTextFormat('hh', 1, null, {})).toEqual('01');
+ expect(datetime.renderTextFormat('hh', 12, null, {})).toEqual('12');
+ });
+
+ it('should return H', () => {
+ expect(datetime.renderTextFormat('H', 1, null, {})).toEqual('1');
+ expect(datetime.renderTextFormat('H', 12, null, {})).toEqual('12');
+ });
+
+ it('should return HH', () => {
+ expect(datetime.renderTextFormat('HH', 1, null, {})).toEqual('01');
+ expect(datetime.renderTextFormat('HH', 12, null, {})).toEqual('12');
+ });
+
+ it('should return D', () => {
+ expect(datetime.renderTextFormat('D', 1, null, {})).toEqual('1');
+ expect(datetime.renderTextFormat('D', 12, null, {})).toEqual('12');
+ });
+
+ it('should return DD', () => {
+ expect(datetime.renderTextFormat('DD', 1, null, {})).toEqual('01');
+ expect(datetime.renderTextFormat('DD', 12, null, {})).toEqual('12');
+ });
+
+ it('should return DDD', () => {
+ var d: datetime.DateTimeData = {
+ year: 2016,
+ month: 5,
+ day: 12,
+ };
+
+ expect(datetime.renderTextFormat('DDD', null, d, {})).toEqual('Thu');
+ });
+
+ it('should return DDD with custom locale', () => {
+ var d: datetime.DateTimeData = {
+ year: 2016,
+ month: 5,
+ day: 12,
+ };
+
+ expect(datetime.renderTextFormat('DDD', null, d, customLocale)).toEqual('qui');
+ });
+
+ it('should return DDDD', () => {
+ var d: datetime.DateTimeData = {
+ year: 2016,
+ month: 5,
+ day: 12,
+ };
+
+ expect(datetime.renderTextFormat('DDDD', null, d, {})).toEqual('Thursday');
+ });
+
+ it('should return DDDD with custom locale', () => {
+ var d: datetime.DateTimeData = {
+ year: 2016,
+ month: 5,
+ day: 12,
+ };
+
+ expect(datetime.renderTextFormat('DDDD', null, d, customLocale)).toEqual('quinta-feira');
+ });
+
+ it('should return M', () => {
+ expect(datetime.renderTextFormat('M', 1, null, {})).toEqual('1');
+ expect(datetime.renderTextFormat('M', 12, null, {})).toEqual('12');
+ });
+
+ it('should return MM', () => {
+ expect(datetime.renderTextFormat('MM', 1, null, {})).toEqual('01');
+ expect(datetime.renderTextFormat('MM', 12, null, {})).toEqual('12');
+ });
+
+ it('should return MMM', () => {
+ expect(datetime.renderTextFormat('MMM', 1, null, {})).toEqual('Jan');
+ expect(datetime.renderTextFormat('MMM', 12, null, {})).toEqual('Dec');
+ });
+
+ it('should return MMM with custom locale', () => {
+ expect(datetime.renderTextFormat('MMM', 1, null, customLocale)).toEqual('jan');
+ });
+
+ it('should return MMMM', () => {
+ expect(datetime.renderTextFormat('MMMM', 1, null, {})).toEqual('January');
+ expect(datetime.renderTextFormat('MMMM', 12, null, {})).toEqual('December');
+ });
+
+ it('should return MMMM with custom locale', () => {
+ expect(datetime.renderTextFormat('MMMM', 1, null, customLocale)).toEqual('janeiro');
+ });
+
+ it('should return YY', () => {
+ expect(datetime.renderTextFormat('YY', 1994, null, {})).toEqual('94');
+ expect(datetime.renderTextFormat('YY', 94, null, {})).toEqual('94');
+ });
+
+ it('should return YYYY', () => {
+ expect(datetime.renderTextFormat('YYYY', 1994, null, {})).toEqual('1994');
+ expect(datetime.renderTextFormat('YYYY', 0, null, {})).toEqual('0000');
+ });
+
+ it('should return empty when blank', () => {
+ expect(datetime.renderTextFormat(null, null, null, {})).toEqual('');
+ expect(datetime.renderTextFormat(null, 1994, null, {})).toEqual('1994');
+ });
+
+});
+
+describe('parseISODate', () => {
+
+ it('should get HH:MM:SS.SSS+HH:MM', () => {
+ var parsed = datetime.parseDate('13:47:20.789+05:30');
+ expect(parsed.year).toEqual(null);
+ expect(parsed.month).toEqual(null);
+ expect(parsed.day).toEqual(null);
+ expect(parsed.hour).toEqual(13);
+ expect(parsed.minute).toEqual(47);
+ expect(parsed.second).toEqual(20);
+ expect(parsed.millisecond).toEqual(789);
+ expect(parsed.tzOffset).toEqual(330);
+ });
+
+ it('should get HH:MM:SS.SSS', () => {
+ var parsed = datetime.parseDate('13:47:20.789');
+ expect(parsed.year).toEqual(null);
+ expect(parsed.month).toEqual(null);
+ expect(parsed.day).toEqual(null);
+ expect(parsed.hour).toEqual(13);
+ expect(parsed.minute).toEqual(47);
+ expect(parsed.second).toEqual(20);
+ expect(parsed.millisecond).toEqual(789);
+ expect(parsed.tzOffset).toEqual(0);
+ });
+
+ it('should get HH:MM:SS', () => {
+ var parsed = datetime.parseDate('13:47:20');
+ expect(parsed.year).toEqual(null);
+ expect(parsed.month).toEqual(null);
+ expect(parsed.day).toEqual(null);
+ expect(parsed.hour).toEqual(13);
+ expect(parsed.minute).toEqual(47);
+ expect(parsed.second).toEqual(20);
+ expect(parsed.millisecond).toEqual(null);
+ expect(parsed.tzOffset).toEqual(0);
+ });
+
+ it('should get HH:MM', () => {
+ var parsed = datetime.parseDate('13:47');
+ expect(parsed.year).toEqual(null);
+ expect(parsed.month).toEqual(null);
+ expect(parsed.day).toEqual(null);
+ expect(parsed.hour).toEqual(13);
+ expect(parsed.minute).toEqual(47);
+ expect(parsed.second).toEqual(null);
+ expect(parsed.millisecond).toEqual(null);
+ expect(parsed.tzOffset).toEqual(0);
+ });
+
+ it('should get YYYY-MM-DDTHH:MM:SS.SSS+HH:MM', () => {
+ var parsed = datetime.parseDate('1994-12-15T13:47:20.789+05:30');
+ expect(parsed.year).toEqual(1994);
+ expect(parsed.month).toEqual(12);
+ expect(parsed.day).toEqual(15);
+ expect(parsed.hour).toEqual(13);
+ expect(parsed.minute).toEqual(47);
+ expect(parsed.second).toEqual(20);
+ expect(parsed.millisecond).toEqual(789);
+ expect(parsed.tzOffset).toEqual(330);
+ });
+
+ it('should get YYYY-MM-DDTHH:MM:SS.SSS-HH:MM', () => {
+ var parsed = datetime.parseDate('1994-12-15T13:47:20.789-11:45');
+ expect(parsed.year).toEqual(1994);
+ expect(parsed.month).toEqual(12);
+ expect(parsed.day).toEqual(15);
+ expect(parsed.hour).toEqual(13);
+ expect(parsed.minute).toEqual(47);
+ expect(parsed.second).toEqual(20);
+ expect(parsed.millisecond).toEqual(789);
+ expect(parsed.tzOffset).toEqual(-705);
+ });
+
+ it('should get YYYY-MM-DDTHH:MM:SS.SSS-HH', () => {
+ var parsed = datetime.parseDate('1994-12-15T13:47:20.789-02');
+ expect(parsed.year).toEqual(1994);
+ expect(parsed.month).toEqual(12);
+ expect(parsed.day).toEqual(15);
+ expect(parsed.hour).toEqual(13);
+ expect(parsed.minute).toEqual(47);
+ expect(parsed.second).toEqual(20);
+ expect(parsed.millisecond).toEqual(789);
+ expect(parsed.tzOffset).toEqual(-120);
+ });
+
+ it('should get YYYY-MM-DDTHH:MM:SS.SSSZ and set UTC offset', () => {
+ var parsed = datetime.parseDate('1994-12-15T13:47:20.789Z');
+ expect(parsed.year).toEqual(1994);
+ expect(parsed.month).toEqual(12);
+ expect(parsed.day).toEqual(15);
+ expect(parsed.hour).toEqual(13);
+ expect(parsed.minute).toEqual(47);
+ expect(parsed.second).toEqual(20);
+ expect(parsed.millisecond).toEqual(789);
+ expect(parsed.tzOffset).toEqual(0);
+ });
+
+ it('should get YYYY-MM-DDTHH:MM:SS', () => {
+ var parsed = datetime.parseDate('1994-12-15T13:47:20');
+ expect(parsed.year).toEqual(1994);
+ expect(parsed.month).toEqual(12);
+ expect(parsed.day).toEqual(15);
+ expect(parsed.hour).toEqual(13);
+ expect(parsed.minute).toEqual(47);
+ expect(parsed.second).toEqual(20);
+ expect(parsed.millisecond).toEqual(null);
+ expect(parsed.tzOffset).toEqual(0);
+ });
+
+ it('should get YYYY-MM-DDTHH:MM', () => {
+ var parsed = datetime.parseDate('1994-12-15T13:47');
+ expect(parsed.year).toEqual(1994);
+ expect(parsed.month).toEqual(12);
+ expect(parsed.day).toEqual(15);
+ expect(parsed.hour).toEqual(13);
+ expect(parsed.minute).toEqual(47);
+ expect(parsed.second).toEqual(null);
+ expect(parsed.millisecond).toEqual(null);
+ expect(parsed.tzOffset).toEqual(0);
+ });
+
+ it('should NOT work with YYYY-MM-DDTHH', () => {
+ var parsed = datetime.parseDate('1994-12-15T13');
+ expect(parsed).toEqual(null);
+ });
+
+ it('should get YYYY-MM-DD', () => {
+ var parsed = datetime.parseDate('1994-12-15');
+ expect(parsed.year).toEqual(1994);
+ expect(parsed.month).toEqual(12);
+ expect(parsed.day).toEqual(15);
+ expect(parsed.hour).toEqual(null);
+ expect(parsed.minute).toEqual(null);
+ expect(parsed.second).toEqual(null);
+ expect(parsed.millisecond).toEqual(null);
+ expect(parsed.tzOffset).toEqual(0);
+ });
+
+ it('should get YYYY-MM', () => {
+ var parsed = datetime.parseDate('1994-12');
+ expect(parsed.year).toEqual(1994);
+ expect(parsed.month).toEqual(12);
+ expect(parsed.day).toEqual(null);
+ expect(parsed.hour).toEqual(null);
+ expect(parsed.minute).toEqual(null);
+ expect(parsed.second).toEqual(null);
+ expect(parsed.millisecond).toEqual(null);
+ expect(parsed.tzOffset).toEqual(0);
+ });
+
+ it('should get YYYY', () => {
+ var parsed = datetime.parseDate('1994');
+ expect(parsed.year).toEqual(1994);
+ expect(parsed.month).toEqual(null);
+ expect(parsed.day).toEqual(null);
+ expect(parsed.hour).toEqual(null);
+ expect(parsed.minute).toEqual(null);
+ expect(parsed.second).toEqual(null);
+ expect(parsed.millisecond).toEqual(null);
+ expect(parsed.tzOffset).toEqual(0);
+ });
+
+ it('should handle bad date formats', () => {
+ var parsed = datetime.parseDate('12/15/1994');
+ expect(parsed).toEqual(null);
+
+ var parsed = datetime.parseDate('12-15-1994');
+ expect(parsed).toEqual(null);
+
+ var parsed = datetime.parseDate('1994-1994');
+ expect(parsed).toEqual(null);
+
+ var parsed = datetime.parseDate('1994 12 15');
+ expect(parsed).toEqual(null);
+
+ var parsed = datetime.parseDate('12.15.1994');
+ expect(parsed).toEqual(null);
+
+ var parsed = datetime.parseDate('12\\15\\1994');
+ expect(parsed).toEqual(null);
+
+ var parsed = datetime.parseDate('200');
+ expect(parsed).toEqual(null);
+
+ var parsed = datetime.parseDate('holla');
+ expect(parsed).toEqual(null);
+ });
+
+ it('should get nothing with null date', () => {
+ var parsed = datetime.parseDate(null);
+ expect(parsed).toEqual(null);
+
+ var parsed = datetime.parseDate(undefined);
+ expect(parsed).toEqual(null);
+
+ var parsed = datetime.parseDate('');
+ expect(parsed).toEqual(null);
+ });
+
+});
+
+// pt-br
+var customLocale: datetime.LocaleData = {
+ dayShort: [
+ 'domingo',
+ 'segunda-feira',
+ 'ter\u00e7a-feira',
+ 'quarta-feira',
+ 'quinta-feira',
+ 'sexta-feira',
+ 's\u00e1bado'
+ ],
+ dayShortNames: [
+ 'dom',
+ 'seg',
+ 'ter',
+ 'qua',
+ 'qui',
+ 'sex',
+ 's\u00e1b'
+ ],
+ monthNames: [
+ 'janeiro',
+ 'fevereiro',
+ 'mar\u00e7o',
+ 'abril',
+ 'maio',
+ 'junho',
+ 'julho',
+ 'agosto',
+ 'setembro',
+ 'outubro',
+ 'novembro',
+ 'dezembro'
+ ],
+ monthShortNames: [
+ 'jan',
+ 'fev',
+ 'mar',
+ 'abr',
+ 'mai',
+ 'jun',
+ 'jul',
+ 'ago',
+ 'set',
+ 'out',
+ 'nov',
+ 'dez'
+ ],
+};
+
+}
diff --git a/ionic/util/test/util.spec.ts b/ionic/util/test/util.spec.ts
index 92081e788a..2c22658d76 100644
--- a/ionic/util/test/util.spec.ts
+++ b/ionic/util/test/util.spec.ts
@@ -1,355 +1,356 @@
import * as util from '../../../ionic/util';
export function run() {
- describe('extend', function() {
- describe('isCheckedProperty', function() {
-
- it('should test a=undefined', () => {
- expect(util.isCheckedProperty(undefined, undefined)).toBe(true);
- expect(util.isCheckedProperty(undefined, null)).toBe(true);
- expect(util.isCheckedProperty(undefined, '')).toBe(true);
- expect(util.isCheckedProperty(undefined, 'string')).toBe(false);
- expect(util.isCheckedProperty(undefined, 0)).toBe(false);
- expect(util.isCheckedProperty(undefined, '0')).toBe(false);
- expect(util.isCheckedProperty(undefined, 1000)).toBe(false);
- expect(util.isCheckedProperty(undefined, '1000')).toBe(false);
- expect(util.isCheckedProperty(undefined, -1)).toBe(false);
- expect(util.isCheckedProperty(undefined, true)).toBe(false);
- expect(util.isCheckedProperty(undefined, false)).toBe(false);
- expect(util.isCheckedProperty(undefined, 'true')).toBe(false);
- expect(util.isCheckedProperty(undefined, 'false')).toBe(false);
- });
-
- it('should test a=null', () => {
- expect(util.isCheckedProperty(null, undefined)).toBe(true);
- expect(util.isCheckedProperty(null, null)).toBe(true);
- expect(util.isCheckedProperty(null, '')).toBe(true);
- expect(util.isCheckedProperty(null, 'string')).toBe(false);
- expect(util.isCheckedProperty(null, 0)).toBe(false);
- expect(util.isCheckedProperty(null, '0')).toBe(false);
- expect(util.isCheckedProperty(null, 1000)).toBe(false);
- expect(util.isCheckedProperty(null, '1000')).toBe(false);
- expect(util.isCheckedProperty(null, -1)).toBe(false);
- expect(util.isCheckedProperty(null, true)).toBe(false);
- expect(util.isCheckedProperty(null, false)).toBe(false);
- expect(util.isCheckedProperty(null, 'true')).toBe(false);
- expect(util.isCheckedProperty(null, 'false')).toBe(false);
- });
-
- it('should test a=""', () => {
- expect(util.isCheckedProperty('', undefined)).toBe(true);
- expect(util.isCheckedProperty('', null)).toBe(true);
- expect(util.isCheckedProperty('', '')).toBe(true);
- expect(util.isCheckedProperty('', 'string')).toBe(false);
- expect(util.isCheckedProperty('', 0)).toBe(false);
- expect(util.isCheckedProperty('', '0')).toBe(false);
- expect(util.isCheckedProperty('', 1000)).toBe(false);
- expect(util.isCheckedProperty('', '1000')).toBe(false);
- expect(util.isCheckedProperty('', -1)).toBe(false);
- expect(util.isCheckedProperty('', true)).toBe(false);
- expect(util.isCheckedProperty('', false)).toBe(false);
- expect(util.isCheckedProperty('', 'true')).toBe(false);
- expect(util.isCheckedProperty('', 'false')).toBe(false);
- });
-
- it('should test a=true', () => {
- expect(util.isCheckedProperty(true, undefined)).toBe(false);
- expect(util.isCheckedProperty(true, null)).toBe(false);
- expect(util.isCheckedProperty(true, '')).toBe(false);
- expect(util.isCheckedProperty(true, 'string')).toBe(false);
- expect(util.isCheckedProperty(true, 0)).toBe(false);
- expect(util.isCheckedProperty(true, '0')).toBe(false);
- expect(util.isCheckedProperty(true, 1000)).toBe(false);
- expect(util.isCheckedProperty(true, '1000')).toBe(false);
- expect(util.isCheckedProperty(true, -1)).toBe(false);
- expect(util.isCheckedProperty(true, true)).toBe(true);
- expect(util.isCheckedProperty(true, false)).toBe(false);
- expect(util.isCheckedProperty(true, 'true')).toBe(true);
- expect(util.isCheckedProperty(true, 'false')).toBe(false);
- });
-
- it('should test a="true"', () => {
- expect(util.isCheckedProperty('true', undefined)).toBe(false);
- expect(util.isCheckedProperty('true', null)).toBe(false);
- expect(util.isCheckedProperty('true', '')).toBe(false);
- expect(util.isCheckedProperty('true', 'string')).toBe(false);
- expect(util.isCheckedProperty('true', 0)).toBe(false);
- expect(util.isCheckedProperty('true', '0')).toBe(false);
- expect(util.isCheckedProperty('true', 1000)).toBe(false);
- expect(util.isCheckedProperty('true', '1000')).toBe(false);
- expect(util.isCheckedProperty('true', -1)).toBe(false);
- expect(util.isCheckedProperty('true', true)).toBe(true);
- expect(util.isCheckedProperty('true', false)).toBe(false);
- expect(util.isCheckedProperty('true', 'true')).toBe(true);
- expect(util.isCheckedProperty('true', 'false')).toBe(false);
- });
-
- it('should test a=false', () => {
- expect(util.isCheckedProperty(false, undefined)).toBe(false);
- expect(util.isCheckedProperty(false, null)).toBe(false);
- expect(util.isCheckedProperty(false, '')).toBe(false);
- expect(util.isCheckedProperty(false, 'string')).toBe(false);
- expect(util.isCheckedProperty(false, 0)).toBe(false);
- expect(util.isCheckedProperty(false, '0')).toBe(false);
- expect(util.isCheckedProperty(false, 1000)).toBe(false);
- expect(util.isCheckedProperty(false, '1000')).toBe(false);
- expect(util.isCheckedProperty(false, -1)).toBe(false);
- expect(util.isCheckedProperty(false, true)).toBe(false);
- expect(util.isCheckedProperty(false, false)).toBe(true);
- expect(util.isCheckedProperty(false, 'true')).toBe(false);
- expect(util.isCheckedProperty(false, 'false')).toBe(true);
- });
-
- it('should test a="false"', () => {
- expect(util.isCheckedProperty('false', undefined)).toBe(false);
- expect(util.isCheckedProperty('false', null)).toBe(false);
- expect(util.isCheckedProperty('false', '')).toBe(false);
- expect(util.isCheckedProperty('false', 'string')).toBe(false);
- expect(util.isCheckedProperty('false', 0)).toBe(false);
- expect(util.isCheckedProperty('false', '0')).toBe(false);
- expect(util.isCheckedProperty('false', 1000)).toBe(false);
- expect(util.isCheckedProperty('false', '1000')).toBe(false);
- expect(util.isCheckedProperty('false', -1)).toBe(false);
- expect(util.isCheckedProperty('false', true)).toBe(false);
- expect(util.isCheckedProperty('false', false)).toBe(true);
- expect(util.isCheckedProperty('false', 'true')).toBe(false);
- expect(util.isCheckedProperty('false', 'false')).toBe(true);
- });
-
- it('should test a=0', () => {
- expect(util.isCheckedProperty(0, undefined)).toBe(false);
- expect(util.isCheckedProperty(0, null)).toBe(false);
- expect(util.isCheckedProperty(0, '')).toBe(false);
- expect(util.isCheckedProperty(0, 'string')).toBe(false);
- expect(util.isCheckedProperty(0, 0)).toBe(true);
- expect(util.isCheckedProperty(0, '0')).toBe(true);
- expect(util.isCheckedProperty(0, 1000)).toBe(false);
- expect(util.isCheckedProperty(0, '1000')).toBe(false);
- expect(util.isCheckedProperty(0, -1)).toBe(false);
- expect(util.isCheckedProperty(0, true)).toBe(false);
- expect(util.isCheckedProperty(0, false)).toBe(false);
- expect(util.isCheckedProperty(0, 'true')).toBe(false);
- expect(util.isCheckedProperty(0, 'false')).toBe(false);
- });
-
- it('should test a="0"', () => {
- expect(util.isCheckedProperty('0', undefined)).toBe(false);
- expect(util.isCheckedProperty('0', null)).toBe(false);
- expect(util.isCheckedProperty('0', '')).toBe(false);
- expect(util.isCheckedProperty('0', 'string')).toBe(false);
- expect(util.isCheckedProperty('0', 0)).toBe(true);
- expect(util.isCheckedProperty('0', '0')).toBe(true);
- expect(util.isCheckedProperty('0', 1000)).toBe(false);
- expect(util.isCheckedProperty('0', '1000')).toBe(false);
- expect(util.isCheckedProperty('0', -1)).toBe(false);
- expect(util.isCheckedProperty('0', true)).toBe(false);
- expect(util.isCheckedProperty('0', false)).toBe(false);
- expect(util.isCheckedProperty('0', 'true')).toBe(false);
- expect(util.isCheckedProperty('0', 'false')).toBe(false);
- });
-
- it('should test a=1000', () => {
- expect(util.isCheckedProperty(1000, undefined)).toBe(false);
- expect(util.isCheckedProperty(1000, null)).toBe(false);
- expect(util.isCheckedProperty(1000, '')).toBe(false);
- expect(util.isCheckedProperty(1000, 'string')).toBe(false);
- expect(util.isCheckedProperty(1000, 0)).toBe(false);
- expect(util.isCheckedProperty(1000, '0')).toBe(false);
- expect(util.isCheckedProperty(1000, 1000)).toBe(true);
- expect(util.isCheckedProperty(1000, '1000')).toBe(true);
- expect(util.isCheckedProperty(1000, -1)).toBe(false);
- expect(util.isCheckedProperty(1000, true)).toBe(false);
- expect(util.isCheckedProperty(1000, false)).toBe(false);
- expect(util.isCheckedProperty(1000, 'true')).toBe(false);
- expect(util.isCheckedProperty(1000, 'false')).toBe(false);
- });
-
- it('should test a="1000"', () => {
- expect(util.isCheckedProperty('1000', undefined)).toBe(false);
- expect(util.isCheckedProperty('1000', null)).toBe(false);
- expect(util.isCheckedProperty('1000', '')).toBe(false);
- expect(util.isCheckedProperty('1000', 'string')).toBe(false);
- expect(util.isCheckedProperty('1000', 0)).toBe(false);
- expect(util.isCheckedProperty('1000', '0')).toBe(false);
- expect(util.isCheckedProperty('1000', 1000)).toBe(true);
- expect(util.isCheckedProperty('1000', '1000')).toBe(true);
- expect(util.isCheckedProperty('1000', -1)).toBe(false);
- expect(util.isCheckedProperty('1000', true)).toBe(false);
- expect(util.isCheckedProperty('1000', false)).toBe(false);
- expect(util.isCheckedProperty('1000', 'true')).toBe(false);
- expect(util.isCheckedProperty('1000', 'false')).toBe(false);
- });
-
- it('should test a=-1', () => {
- expect(util.isCheckedProperty(-1, undefined)).toBe(false);
- expect(util.isCheckedProperty(-1, null)).toBe(false);
- expect(util.isCheckedProperty(-1, '')).toBe(false);
- expect(util.isCheckedProperty(-1, 'string')).toBe(false);
- expect(util.isCheckedProperty(-1, 0)).toBe(false);
- expect(util.isCheckedProperty(-1, '0')).toBe(false);
- expect(util.isCheckedProperty(-1, 1000)).toBe(false);
- expect(util.isCheckedProperty(-1, '1000')).toBe(false);
- expect(util.isCheckedProperty(-1, -1)).toBe(true);
- expect(util.isCheckedProperty(-1, true)).toBe(false);
- expect(util.isCheckedProperty(-1, false)).toBe(false);
- expect(util.isCheckedProperty(-1, 'true')).toBe(false);
- expect(util.isCheckedProperty(-1, 'false')).toBe(false);
- });
-
- it('should test a="-1"', () => {
- expect(util.isCheckedProperty('-1', undefined)).toBe(false);
- expect(util.isCheckedProperty('-1', null)).toBe(false);
- expect(util.isCheckedProperty('-1', '')).toBe(false);
- expect(util.isCheckedProperty('-1', 'string')).toBe(false);
- expect(util.isCheckedProperty('-1', 0)).toBe(false);
- expect(util.isCheckedProperty('-1', '0')).toBe(false);
- expect(util.isCheckedProperty('-1', 1000)).toBe(false);
- expect(util.isCheckedProperty('-1', '1000')).toBe(false);
- expect(util.isCheckedProperty('-1', -1)).toBe(true);
- expect(util.isCheckedProperty('-1', true)).toBe(false);
- expect(util.isCheckedProperty('-1', false)).toBe(false);
- expect(util.isCheckedProperty('-1', 'true')).toBe(false);
- expect(util.isCheckedProperty('-1', 'false')).toBe(false);
- });
-
- it('should test a="string"', () => {
- expect(util.isCheckedProperty('string', undefined)).toBe(false);
- expect(util.isCheckedProperty('string', null)).toBe(false);
- expect(util.isCheckedProperty('string', '')).toBe(false);
- expect(util.isCheckedProperty('string', 'string')).toBe(true);
- expect(util.isCheckedProperty('string', 'otherstring')).toBe(false);
- expect(util.isCheckedProperty('string', 0)).toBe(false);
- expect(util.isCheckedProperty('string', '0')).toBe(false);
- expect(util.isCheckedProperty('string', 1000)).toBe(false);
- expect(util.isCheckedProperty('string', '1000')).toBe(false);
- expect(util.isCheckedProperty('string', -1)).toBe(false);
- expect(util.isCheckedProperty('string', true)).toBe(false);
- expect(util.isCheckedProperty('string', false)).toBe(false);
- expect(util.isCheckedProperty('string', 'true')).toBe(false);
- expect(util.isCheckedProperty('string', 'false')).toBe(false);
- });
+ describe('isCheckedProperty', () => {
+ it('should test a=undefined', () => {
+ expect(util.isCheckedProperty(undefined, undefined)).toBe(true);
+ expect(util.isCheckedProperty(undefined, null)).toBe(true);
+ expect(util.isCheckedProperty(undefined, '')).toBe(true);
+ expect(util.isCheckedProperty(undefined, 'string')).toBe(false);
+ expect(util.isCheckedProperty(undefined, 0)).toBe(false);
+ expect(util.isCheckedProperty(undefined, '0')).toBe(false);
+ expect(util.isCheckedProperty(undefined, 1000)).toBe(false);
+ expect(util.isCheckedProperty(undefined, '1000')).toBe(false);
+ expect(util.isCheckedProperty(undefined, -1)).toBe(false);
+ expect(util.isCheckedProperty(undefined, true)).toBe(false);
+ expect(util.isCheckedProperty(undefined, false)).toBe(false);
+ expect(util.isCheckedProperty(undefined, 'true')).toBe(false);
+ expect(util.isCheckedProperty(undefined, 'false')).toBe(false);
});
- describe('getQuerystring', function() {
- it('should have no entries for empty url', () => {
- expect(util.getQuerystring('')).toEqual({});
- expect(util.getQuerystring(null)).toEqual({});
- expect(util.getQuerystring(undefined)).toEqual({});
- });
-
- it('should have no entries when without ?', () => {
- expect(util.getQuerystring('http://localhost:1234/')).toEqual({});
- });
-
- it('should have no entries with only ?', () => {
- expect(util.getQuerystring('http://localhost:1234/?')).toEqual({});
- });
-
- it('should have no entries for key with no =', () => {
- expect(util.getQuerystring('http://localhost:1234/?key')).toEqual({});
- });
-
- it('should have no entries with only #?', () => {
- expect(util.getQuerystring('http://localhost:1234/#?')).toEqual({});
- });
-
- it('should have no entries with only #?=', () => {
- expect(util.getQuerystring('http://localhost:1234/#?=')).toEqual({});
- });
-
- it('should have no entries for url with no "?" character', () => {
- expect(util.getQuerystring('http://localhost:1234/#key1=1&key2=2')).toEqual({});
- });
-
- it('should contain key/value entries for all the parameters after "?" character', () => {
- expect(util.getQuerystring('http://localhost:1234/#key1=1&key2x=2x?key3=3&key4=4')).toEqual({
- key3: '3',
- key4: '4'
- });
- });
-
- it('should lowercase param keys', () => {
- expect(util.getQuerystring('http://localhost:1234/#?KEY1=1&kEy2=2')).toEqual({
- key1: '1',
- key2: '2'
- });
- });
-
- it('should not include any values when # comes after ?', () => {
- expect(util.getQuerystring('http://localhost:1234/?key1=1#key2=2')).toEqual({
- key1: '1'
- });
- });
-
- it('should ignore empty ?& and &&', () => {
- expect(util.getQuerystring('http://localhost:1234/#?&&')).toEqual({});
-
- expect(util.getQuerystring('http://localhost:1234/#?&&key1=1&key2=2&&')).toEqual({
- key1: '1',
- key2: '2'
- });
- });
-
- it('should get "" when key has no value', () => {
- expect(util.getQuerystring('http://localhost:1234/#?key=')).toEqual({
- key: ''
- });
- });
-
+ it('should test a=null', () => {
+ expect(util.isCheckedProperty(null, undefined)).toBe(true);
+ expect(util.isCheckedProperty(null, null)).toBe(true);
+ expect(util.isCheckedProperty(null, '')).toBe(true);
+ expect(util.isCheckedProperty(null, 'string')).toBe(false);
+ expect(util.isCheckedProperty(null, 0)).toBe(false);
+ expect(util.isCheckedProperty(null, '0')).toBe(false);
+ expect(util.isCheckedProperty(null, 1000)).toBe(false);
+ expect(util.isCheckedProperty(null, '1000')).toBe(false);
+ expect(util.isCheckedProperty(null, -1)).toBe(false);
+ expect(util.isCheckedProperty(null, true)).toBe(false);
+ expect(util.isCheckedProperty(null, false)).toBe(false);
+ expect(util.isCheckedProperty(null, 'true')).toBe(false);
+ expect(util.isCheckedProperty(null, 'false')).toBe(false);
});
- describe('isTrueProperty', function() {
-
- it('should be true from boolean true', () => {
- expect(util.isTrueProperty(true)).toBe(true);
- });
-
- it('should be true from string "true"', () => {
- expect(util.isTrueProperty('true')).toBe(true);
- expect(util.isTrueProperty('TRUE')).toBe(true);
- expect(util.isTrueProperty(' true ')).toBe(true);
- });
-
- it('should be true from string "on"', () => {
- expect(util.isTrueProperty(true)).toBe(true);
- });
-
- it('should be true from empty string ""', () => {
- expect(util.isTrueProperty('')).toBe(true);
- expect(util.isTrueProperty(' ')).toBe(true);
- });
-
- it('should be true from number greater than zero', () => {
- expect(util.isTrueProperty(1)).toBe(true);
- expect(util.isTrueProperty(999)).toBe(true);
- });
-
- it('should be false from boolean false', () => {
- expect(util.isTrueProperty(false)).toBe(false);
- });
-
- it('should be false from string "off"', () => {
- expect(util.isTrueProperty(true)).toBe(true);
- });
-
- it('should be false from null', () => {
- expect(util.isTrueProperty(null)).toBe(false);
- });
-
- it('should be false from undefined', () => {
- expect(util.isTrueProperty(undefined)).toBe(false);
- });
-
- it('should be false from string "false"', () => {
- expect(util.isTrueProperty('false')).toBe(false);
- expect(util.isTrueProperty(' FALSE ')).toBe(false);
- expect(util.isTrueProperty('doesnt actually matter')).toBe(false);
- });
-
+ it('should test a=""', () => {
+ expect(util.isCheckedProperty('', undefined)).toBe(true);
+ expect(util.isCheckedProperty('', null)).toBe(true);
+ expect(util.isCheckedProperty('', '')).toBe(true);
+ expect(util.isCheckedProperty('', 'string')).toBe(false);
+ expect(util.isCheckedProperty('', 0)).toBe(false);
+ expect(util.isCheckedProperty('', '0')).toBe(false);
+ expect(util.isCheckedProperty('', 1000)).toBe(false);
+ expect(util.isCheckedProperty('', '1000')).toBe(false);
+ expect(util.isCheckedProperty('', -1)).toBe(false);
+ expect(util.isCheckedProperty('', true)).toBe(false);
+ expect(util.isCheckedProperty('', false)).toBe(false);
+ expect(util.isCheckedProperty('', 'true')).toBe(false);
+ expect(util.isCheckedProperty('', 'false')).toBe(false);
});
+ it('should test a=true', () => {
+ expect(util.isCheckedProperty(true, undefined)).toBe(false);
+ expect(util.isCheckedProperty(true, null)).toBe(false);
+ expect(util.isCheckedProperty(true, '')).toBe(false);
+ expect(util.isCheckedProperty(true, 'string')).toBe(false);
+ expect(util.isCheckedProperty(true, 0)).toBe(false);
+ expect(util.isCheckedProperty(true, '0')).toBe(false);
+ expect(util.isCheckedProperty(true, 1000)).toBe(false);
+ expect(util.isCheckedProperty(true, '1000')).toBe(false);
+ expect(util.isCheckedProperty(true, -1)).toBe(false);
+ expect(util.isCheckedProperty(true, true)).toBe(true);
+ expect(util.isCheckedProperty(true, false)).toBe(false);
+ expect(util.isCheckedProperty(true, 'true')).toBe(true);
+ expect(util.isCheckedProperty(true, 'false')).toBe(false);
+ });
+
+ it('should test a="true"', () => {
+ expect(util.isCheckedProperty('true', undefined)).toBe(false);
+ expect(util.isCheckedProperty('true', null)).toBe(false);
+ expect(util.isCheckedProperty('true', '')).toBe(false);
+ expect(util.isCheckedProperty('true', 'string')).toBe(false);
+ expect(util.isCheckedProperty('true', 0)).toBe(false);
+ expect(util.isCheckedProperty('true', '0')).toBe(false);
+ expect(util.isCheckedProperty('true', 1000)).toBe(false);
+ expect(util.isCheckedProperty('true', '1000')).toBe(false);
+ expect(util.isCheckedProperty('true', -1)).toBe(false);
+ expect(util.isCheckedProperty('true', true)).toBe(true);
+ expect(util.isCheckedProperty('true', false)).toBe(false);
+ expect(util.isCheckedProperty('true', 'true')).toBe(true);
+ expect(util.isCheckedProperty('true', 'false')).toBe(false);
+ });
+
+ it('should test a=false', () => {
+ expect(util.isCheckedProperty(false, undefined)).toBe(false);
+ expect(util.isCheckedProperty(false, null)).toBe(false);
+ expect(util.isCheckedProperty(false, '')).toBe(false);
+ expect(util.isCheckedProperty(false, 'string')).toBe(false);
+ expect(util.isCheckedProperty(false, 0)).toBe(false);
+ expect(util.isCheckedProperty(false, '0')).toBe(false);
+ expect(util.isCheckedProperty(false, 1000)).toBe(false);
+ expect(util.isCheckedProperty(false, '1000')).toBe(false);
+ expect(util.isCheckedProperty(false, -1)).toBe(false);
+ expect(util.isCheckedProperty(false, true)).toBe(false);
+ expect(util.isCheckedProperty(false, false)).toBe(true);
+ expect(util.isCheckedProperty(false, 'true')).toBe(false);
+ expect(util.isCheckedProperty(false, 'false')).toBe(true);
+ });
+
+ it('should test a="false"', () => {
+ expect(util.isCheckedProperty('false', undefined)).toBe(false);
+ expect(util.isCheckedProperty('false', null)).toBe(false);
+ expect(util.isCheckedProperty('false', '')).toBe(false);
+ expect(util.isCheckedProperty('false', 'string')).toBe(false);
+ expect(util.isCheckedProperty('false', 0)).toBe(false);
+ expect(util.isCheckedProperty('false', '0')).toBe(false);
+ expect(util.isCheckedProperty('false', 1000)).toBe(false);
+ expect(util.isCheckedProperty('false', '1000')).toBe(false);
+ expect(util.isCheckedProperty('false', -1)).toBe(false);
+ expect(util.isCheckedProperty('false', true)).toBe(false);
+ expect(util.isCheckedProperty('false', false)).toBe(true);
+ expect(util.isCheckedProperty('false', 'true')).toBe(false);
+ expect(util.isCheckedProperty('false', 'false')).toBe(true);
+ });
+
+ it('should test a=0', () => {
+ expect(util.isCheckedProperty(0, undefined)).toBe(false);
+ expect(util.isCheckedProperty(0, null)).toBe(false);
+ expect(util.isCheckedProperty(0, '')).toBe(false);
+ expect(util.isCheckedProperty(0, 'string')).toBe(false);
+ expect(util.isCheckedProperty(0, 0)).toBe(true);
+ expect(util.isCheckedProperty(0, '0')).toBe(true);
+ expect(util.isCheckedProperty(0, 1000)).toBe(false);
+ expect(util.isCheckedProperty(0, '1000')).toBe(false);
+ expect(util.isCheckedProperty(0, -1)).toBe(false);
+ expect(util.isCheckedProperty(0, true)).toBe(false);
+ expect(util.isCheckedProperty(0, false)).toBe(false);
+ expect(util.isCheckedProperty(0, 'true')).toBe(false);
+ expect(util.isCheckedProperty(0, 'false')).toBe(false);
+ });
+
+ it('should test a="0"', () => {
+ expect(util.isCheckedProperty('0', undefined)).toBe(false);
+ expect(util.isCheckedProperty('0', null)).toBe(false);
+ expect(util.isCheckedProperty('0', '')).toBe(false);
+ expect(util.isCheckedProperty('0', 'string')).toBe(false);
+ expect(util.isCheckedProperty('0', 0)).toBe(true);
+ expect(util.isCheckedProperty('0', '0')).toBe(true);
+ expect(util.isCheckedProperty('0', 1000)).toBe(false);
+ expect(util.isCheckedProperty('0', '1000')).toBe(false);
+ expect(util.isCheckedProperty('0', -1)).toBe(false);
+ expect(util.isCheckedProperty('0', true)).toBe(false);
+ expect(util.isCheckedProperty('0', false)).toBe(false);
+ expect(util.isCheckedProperty('0', 'true')).toBe(false);
+ expect(util.isCheckedProperty('0', 'false')).toBe(false);
+ });
+
+ it('should test a=1000', () => {
+ expect(util.isCheckedProperty(1000, undefined)).toBe(false);
+ expect(util.isCheckedProperty(1000, null)).toBe(false);
+ expect(util.isCheckedProperty(1000, '')).toBe(false);
+ expect(util.isCheckedProperty(1000, 'string')).toBe(false);
+ expect(util.isCheckedProperty(1000, 0)).toBe(false);
+ expect(util.isCheckedProperty(1000, '0')).toBe(false);
+ expect(util.isCheckedProperty(1000, 1000)).toBe(true);
+ expect(util.isCheckedProperty(1000, '1000')).toBe(true);
+ expect(util.isCheckedProperty(1000, -1)).toBe(false);
+ expect(util.isCheckedProperty(1000, true)).toBe(false);
+ expect(util.isCheckedProperty(1000, false)).toBe(false);
+ expect(util.isCheckedProperty(1000, 'true')).toBe(false);
+ expect(util.isCheckedProperty(1000, 'false')).toBe(false);
+ });
+
+ it('should test a="1000"', () => {
+ expect(util.isCheckedProperty('1000', undefined)).toBe(false);
+ expect(util.isCheckedProperty('1000', null)).toBe(false);
+ expect(util.isCheckedProperty('1000', '')).toBe(false);
+ expect(util.isCheckedProperty('1000', 'string')).toBe(false);
+ expect(util.isCheckedProperty('1000', 0)).toBe(false);
+ expect(util.isCheckedProperty('1000', '0')).toBe(false);
+ expect(util.isCheckedProperty('1000', 1000)).toBe(true);
+ expect(util.isCheckedProperty('1000', '1000')).toBe(true);
+ expect(util.isCheckedProperty('1000', -1)).toBe(false);
+ expect(util.isCheckedProperty('1000', true)).toBe(false);
+ expect(util.isCheckedProperty('1000', false)).toBe(false);
+ expect(util.isCheckedProperty('1000', 'true')).toBe(false);
+ expect(util.isCheckedProperty('1000', 'false')).toBe(false);
+ });
+
+ it('should test a=-1', () => {
+ expect(util.isCheckedProperty(-1, undefined)).toBe(false);
+ expect(util.isCheckedProperty(-1, null)).toBe(false);
+ expect(util.isCheckedProperty(-1, '')).toBe(false);
+ expect(util.isCheckedProperty(-1, 'string')).toBe(false);
+ expect(util.isCheckedProperty(-1, 0)).toBe(false);
+ expect(util.isCheckedProperty(-1, '0')).toBe(false);
+ expect(util.isCheckedProperty(-1, 1000)).toBe(false);
+ expect(util.isCheckedProperty(-1, '1000')).toBe(false);
+ expect(util.isCheckedProperty(-1, -1)).toBe(true);
+ expect(util.isCheckedProperty(-1, true)).toBe(false);
+ expect(util.isCheckedProperty(-1, false)).toBe(false);
+ expect(util.isCheckedProperty(-1, 'true')).toBe(false);
+ expect(util.isCheckedProperty(-1, 'false')).toBe(false);
+ });
+
+ it('should test a="-1"', () => {
+ expect(util.isCheckedProperty('-1', undefined)).toBe(false);
+ expect(util.isCheckedProperty('-1', null)).toBe(false);
+ expect(util.isCheckedProperty('-1', '')).toBe(false);
+ expect(util.isCheckedProperty('-1', 'string')).toBe(false);
+ expect(util.isCheckedProperty('-1', 0)).toBe(false);
+ expect(util.isCheckedProperty('-1', '0')).toBe(false);
+ expect(util.isCheckedProperty('-1', 1000)).toBe(false);
+ expect(util.isCheckedProperty('-1', '1000')).toBe(false);
+ expect(util.isCheckedProperty('-1', -1)).toBe(true);
+ expect(util.isCheckedProperty('-1', true)).toBe(false);
+ expect(util.isCheckedProperty('-1', false)).toBe(false);
+ expect(util.isCheckedProperty('-1', 'true')).toBe(false);
+ expect(util.isCheckedProperty('-1', 'false')).toBe(false);
+ });
+
+ it('should test a="string"', () => {
+ expect(util.isCheckedProperty('string', undefined)).toBe(false);
+ expect(util.isCheckedProperty('string', null)).toBe(false);
+ expect(util.isCheckedProperty('string', '')).toBe(false);
+ expect(util.isCheckedProperty('string', 'string')).toBe(true);
+ expect(util.isCheckedProperty('string', 'otherstring')).toBe(false);
+ expect(util.isCheckedProperty('string', 0)).toBe(false);
+ expect(util.isCheckedProperty('string', '0')).toBe(false);
+ expect(util.isCheckedProperty('string', 1000)).toBe(false);
+ expect(util.isCheckedProperty('string', '1000')).toBe(false);
+ expect(util.isCheckedProperty('string', -1)).toBe(false);
+ expect(util.isCheckedProperty('string', true)).toBe(false);
+ expect(util.isCheckedProperty('string', false)).toBe(false);
+ expect(util.isCheckedProperty('string', 'true')).toBe(false);
+ expect(util.isCheckedProperty('string', 'false')).toBe(false);
+ });
+
+ });
+
+ describe('getQuerystring', () => {
+ it('should have no entries for empty url', () => {
+ expect(util.getQuerystring('')).toEqual({});
+ expect(util.getQuerystring(null)).toEqual({});
+ expect(util.getQuerystring(undefined)).toEqual({});
+ });
+
+ it('should have no entries when without ?', () => {
+ expect(util.getQuerystring('http://localhost:1234/')).toEqual({});
+ });
+
+ it('should have no entries with only ?', () => {
+ expect(util.getQuerystring('http://localhost:1234/?')).toEqual({});
+ });
+
+ it('should have no entries for key with no =', () => {
+ expect(util.getQuerystring('http://localhost:1234/?key')).toEqual({});
+ });
+
+ it('should have no entries with only #?', () => {
+ expect(util.getQuerystring('http://localhost:1234/#?')).toEqual({});
+ });
+
+ it('should have no entries with only #?=', () => {
+ expect(util.getQuerystring('http://localhost:1234/#?=')).toEqual({});
+ });
+
+ it('should have no entries for url with no "?" character', () => {
+ expect(util.getQuerystring('http://localhost:1234/#key1=1&key2=2')).toEqual({});
+ });
+
+ it('should contain key/value entries for all the parameters after "?" character', () => {
+ expect(util.getQuerystring('http://localhost:1234/#key1=1&key2x=2x?key3=3&key4=4')).toEqual({
+ key3: '3',
+ key4: '4'
+ });
+ });
+
+ it('should lowercase param keys', () => {
+ expect(util.getQuerystring('http://localhost:1234/#?KEY1=1&kEy2=2')).toEqual({
+ key1: '1',
+ key2: '2'
+ });
+ });
+
+ it('should not include any values when # comes after ?', () => {
+ expect(util.getQuerystring('http://localhost:1234/?key1=1#key2=2')).toEqual({
+ key1: '1'
+ });
+ });
+
+ it('should ignore empty ?& and &&', () => {
+ expect(util.getQuerystring('http://localhost:1234/#?&&')).toEqual({});
+
+ expect(util.getQuerystring('http://localhost:1234/#?&&key1=1&key2=2&&')).toEqual({
+ key1: '1',
+ key2: '2'
+ });
+ });
+
+ it('should get "" when key has no value', () => {
+ expect(util.getQuerystring('http://localhost:1234/#?key=')).toEqual({
+ key: ''
+ });
+ });
+
+ });
+
+ describe('isTrueProperty', () => {
+
+ it('should be true from boolean true', () => {
+ expect(util.isTrueProperty(true)).toBe(true);
+ });
+
+ it('should be true from string "true"', () => {
+ expect(util.isTrueProperty('true')).toBe(true);
+ expect(util.isTrueProperty('TRUE')).toBe(true);
+ expect(util.isTrueProperty(' true ')).toBe(true);
+ });
+
+ it('should be true from string "on"', () => {
+ expect(util.isTrueProperty(true)).toBe(true);
+ });
+
+ it('should be true from empty string ""', () => {
+ expect(util.isTrueProperty('')).toBe(true);
+ expect(util.isTrueProperty(' ')).toBe(true);
+ });
+
+ it('should be true from number greater than zero', () => {
+ expect(util.isTrueProperty(1)).toBe(true);
+ expect(util.isTrueProperty(999)).toBe(true);
+ });
+
+ it('should be false from boolean false', () => {
+ expect(util.isTrueProperty(false)).toBe(false);
+ });
+
+ it('should be false from string "off"', () => {
+ expect(util.isTrueProperty(true)).toBe(true);
+ });
+
+ it('should be false from null', () => {
+ expect(util.isTrueProperty(null)).toBe(false);
+ });
+
+ it('should be false from undefined', () => {
+ expect(util.isTrueProperty(undefined)).toBe(false);
+ });
+
+ it('should be false from string "false"', () => {
+ expect(util.isTrueProperty('false')).toBe(false);
+ expect(util.isTrueProperty(' FALSE ')).toBe(false);
+ expect(util.isTrueProperty('doesnt actually matter')).toBe(false);
+ });
+
+ });
+
+ describe('extend', () => {
+
it('should extend simple', () => {
var obj = { a: '0', c: '0' };
expect( util.assign(obj, { a: '1', b: '2' }) ).toBe(obj);
@@ -371,7 +372,7 @@ export function run() {
});
- describe('defaults', function() {
+ describe('defaults', () => {
it('should simple defaults', () => {
var obj = { a: '1' };