Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
- Remove outdated pre IE9 browser compatibility polyfill `core/compat`.
- Remove unused `lib/htmlparser`.
- Remove obsolete library `prefixfree`.
- pat date picker: Remove ``format`` argument and just use the ISO 8601 standard "YYYY-MM-DD", like the specification of date inputs defines it.
Format would have submitted a formatted value where the ISO standard is expected.
This also allows for removing the dependency of ``pat-date-picker`` on MomentJS.

### Features

Expand All @@ -40,6 +43,8 @@
- core dom: Add ``querySelectorAllAndMe`` to do a querySelectorAll including the starter element.
- core dom: Add ``wrap`` wrap an element with a wrapper element.
- core dom: Add ``hide`` and ``show`` for DOM elements which retain the original display value.
- pat date picker: Support updating a date if it is before another dependent date.


### Technical

Expand Down
84 changes: 52 additions & 32 deletions src/pat/date-picker/date-picker.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
/* pat-date-picker - Polyfill for input type=date */
import "regenerator-runtime/runtime"; // needed for ``await`` support
import $ from "jquery";
import Parser from "../../core/parser";
import Base from "../../core/base";
import Parser from "../../core/parser";
import utils from "../../core/utils";

// Lazy loading modules.
let Pikaday;
let Moment;

var parser = new Parser("date-picker");
const parser = new Parser("date-picker");
parser.addArgument("behavior", "styled", ["native", "styled"]);
parser.addArgument("format", "YYYY-MM-DD");
parser.addArgument("week-numbers", [], ["show", "hide"]);
parser.addArgument("i18n"); // URL pointing to JSON resource with i18n values
parser.addArgument("first-day", 0);
parser.addArgument("after");
parser.addArgument("offset-days", 0);

parser.addAlias("behaviour", "behavior");

/* JSON format for i18n
* { "previousMonth": "Previous Month",
Expand All @@ -23,73 +25,91 @@ parser.addArgument("first-day", 0);
* "weekdays" : ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],
* "weekdaysShort": ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"]
* } */
parser.addAlias("behaviour", "behavior");

export default Base.extend({
name: "date-picker",
trigger: ".pat-date-picker",

async init() {
this.options = $.extend(parser.parse(this.$el), this.options);
this.polyfill = this.options.behavior === "native";
if (this.polyfill && utils.checkInputSupport("date", "invalid date")) {
const el = this.el;
//TODO: make parser with options extend missing options.
//this.options = parser.parse(el, opts);
this.options = $.extend(parser.parse(el), this.options);

if (this.options.after) {
// Set the date depending on another date which must be ``offset-days``
// BEFORE this date. Only set it, if the other date is AFTER this
// date.
const befores = document.querySelectorAll(this.options.after);
for (const b_el of befores) {
b_el.addEventListener("change", (e) => {
let b_date = e.target.value; // the "before-date"
b_date = b_date ? new Date(b_date) : null;
if (!b_date) {
return;
}
let a_date = this.el.value; // the "after-date"
a_date = a_date ? new Date(a_date) : null;
if (!a_date || a_date < b_date) {
const offset = this.options.offsetDays || 0;
b_date.setDate(b_date.getDate() + offset);
this.el.value = b_date.toISOString().substring(0, 10);
}
});
}
}

if (
this.options.behavior === "native" &&
utils.checkInputSupport("date", "invalid date")
) {
return;
}

Pikaday = await import("pikaday");
Pikaday = Pikaday.default;
Moment = await import("moment");
Moment = Moment.default;

if (this.$el.attr("type") === "date") {
this.$el.attr("type", "text");
if (el.getAttribute("type") === "date") {
el.setAttribute("type", "text");
}

var config = {
field: this.$el[0],
format: this.options.format,
const config = {
field: el,
format: "YYYY-MM-DD",
firstDay: this.options.firstDay,
showWeekNumber: this.options.weekNumbers === "show",
toString: function (date, format) {
return Moment(date).format(format);
},
onSelect: function () {
onSelect() {
$(this._o.field).closest("form").trigger("input-change");
/* Also trigger input change on date field to support pat-autosubmit. */
$(this._o.field).trigger("input-change");
},
};

if (this.$el.attr("min")) {
config.minDate = Moment(this.$el.attr("min")).toDate();
if (el.getAttribute("min")) {
config.minDate = new Date(el.getAttribute("min"));
}
if (this.$el.attr("max")) {
config.maxDate = Moment(this.$el.attr("max")).toDate();
if (el.getAttribute("max")) {
config.maxDate = new Date(el.getAttribute("max"));
}

if (this.options.i18n) {
$.getJSON(this.options.i18n)
.done(function (data) {
.done((data) => {
config.i18n = data;
})
.fail(
$.proxy(function () {
$.proxy(() => {
console.error(
"date-picker could not load i18n: " +
this.options.i18n
);
}, this)
)
.always(function () {
.always(() => {
new Pikaday(config);
});
} else {
new Pikaday(config);
}
return this.$el;
},

isodate: function () {
var now = new Date();
return now.toISOString().substr(0, 10);
},
});
146 changes: 146 additions & 0 deletions src/pat/date-picker/date-picker.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,4 +176,150 @@ describe("pat-date-picker", function () {
});
});
});

describe("Update one input depending on the other.", function () {
it("Updates with default offset-days", async (done) => {
const wrapper = document.createElement("div");
wrapper.innerHTML = `
<input name="start" type="date" class="pat-date-picker" />
<input name="end" type="date" class="pat-date-picker" data-pat-date-picker="after:input[name=start]" />
`;
document.body.appendChild(wrapper);
const start = wrapper.querySelector("input[name=start]");
const end = wrapper.querySelector("input[name=end]");

pattern.init(start);
pattern.init(end);
await utils.timeout(1); // wait a tick for async to settle.

const cal1 = document.querySelectorAll(".pika-single")[0];
const cal2 = document.querySelectorAll(".pika-single")[1];

// Check initial values
expect(start.value).toBeFalsy();
expect(end.value).toBeFalsy();

// Set start value
start.click();

let btn = cal1.querySelectorAll(".pika-table button")[0];
btn.dispatchEvent(new Event("mousedown"));

let start1 = start.value;
let end1 = end.value;

expect(start.value).toBeTruthy();
expect(end.value).toBeTruthy();
expect(start.value).toBe(end.value);

// Setting it again to a date after the end date will change the end
// date again.
start.click();

btn = cal1.querySelectorAll(".pika-table button")[10];
btn.dispatchEvent(new Event("mousedown"));

let start2 = start.value;
let end2 = end.value;

expect(start.value).toBeTruthy();
expect(start.value).not.toBe(start1);
expect(end.value).toBeTruthy();
expect(end.value).not.toBe(end1);
expect(start.value).toBe(end.value);

// Setting it to an earlier value will not change the end date again.
start.click();

btn = cal1.querySelectorAll(".pika-table button")[5];
btn.dispatchEvent(new Event("mousedown"));

let start3 = start.value;
let end3 = end.value;

expect(start.value).toBeTruthy();
expect(start.value).not.toBe(start1);
expect(start.value).not.toBe(start2);
expect(end.value).toBeTruthy();
expect(end.value).not.toBe(end1);
expect(end.value).toBe(end2);
expect(start.value).not.toBe(end.value);

// Setting end to an earlier value than start is possible.
// Use pat-validation to prevent submitting it.
end.click();

btn = cal2.querySelectorAll(".pika-table button")[0];
btn.dispatchEvent(new Event("mousedown"));

expect(start.value).toBeTruthy();
expect(start.value).not.toBe(start1);
expect(start.value).not.toBe(start2);
expect(start.value).toBe(start3);
expect(end.value).toBeTruthy();
expect(end.value).toBe(end1);
expect(end.value).not.toBe(end2);
expect(end.value).not.toBe(end3);
expect(start.value).not.toBe(end.value);

done();
});

it("Updates with offset-days 2", async (done) => {
const diff_days = (val1, val2) => {
// diff is in milliseconds
const diff = new Date(val1) - new Date(val2);
return diff / 1000 / 60 / 60 / 24;
};

const wrapper = document.createElement("div");
wrapper.innerHTML = `
<input name="start" type="date" class="pat-date-picker" />
<input name="end" type="date" class="pat-date-picker" data-pat-date-picker="after:input[name=start]; offset-days: 2" />
`;
document.body.appendChild(wrapper);
const start = wrapper.querySelector("input[name=start]");
const end = wrapper.querySelector("input[name=end]");

pattern.init(start);
pattern.init(end);
await utils.timeout(1); // wait a tick for async to settle.

const cal1 = document.querySelectorAll(".pika-single")[0];

// Check initial values
expect(start.value).toBeFalsy();
expect(end.value).toBeFalsy();

// Set start value
start.click();
let btn = cal1.querySelectorAll(".pika-table button")[0];
btn.dispatchEvent(new Event("mousedown"));
// end date is set +2 days in advance of start date.
expect(diff_days(start.value, end.value)).toBe(-2);

// Change start value +1day
start.click();
btn = cal1.querySelectorAll(".pika-table button")[1];
btn.dispatchEvent(new Event("mousedown"));
// end date doesn't change.
expect(diff_days(start.value, end.value)).toBe(-1);

// Change start value +1day = same day
start.click();
btn = cal1.querySelectorAll(".pika-table button")[2];
btn.dispatchEvent(new Event("mousedown"));
// end date doesn't change.
expect(diff_days(start.value, end.value)).toBe(0);

// Change start value +1day = 1 day after end date
start.click();
btn = cal1.querySelectorAll(".pika-table button")[3];
btn.dispatchEvent(new Event("mousedown"));
// end is set 2 days after start date
expect(diff_days(start.value, end.value)).toBe(-2);

done();
});
});
});
2 changes: 2 additions & 0 deletions src/pat/date-picker/documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,5 @@ In addition, the following options can be passed to `data-pat-date-picker`:
| **week-numbers** | string | hide | show, hide | "show" will show the weeks' numbers in a leftmost column. |
| **i18n** | URL | | | Provide a URL to a JSON resource which gives the i18n values. |
| **first-day** | Integer | 0 | | Set the first day of the week (0 -> Sunday, 1-> Monday, ...). |
| **after** | string | | | CSS selector of another date input. If this date is before the other, it will be updated to the oder date plus offset-days. |
| **offset-days** | Integer | 0 | | Number of days added to the **after** reference date which will be used to update this date value. |
11 changes: 11 additions & 0 deletions src/pat/date-picker/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,17 @@
type="date"
/>
</label>

<label>Start
<input name="start" class="pat-date-picker" type="date"/>
</label>
<label>End after start, automatically updated.
<input name="end" class="pat-date-picker" type="date" data-pat-date-picker="after: input[name=start]; offset-days: 2;"/>
</label>

<br />


</fieldset>
</form>
</body>
Expand Down
Loading