Skip to content

Instantly share code, notes, and snippets.

@fhrbek
Last active April 18, 2016 10:57
Show Gist options
  • Save fhrbek/2dfe0d347d1e98083cadb5ad397637c7 to your computer and use it in GitHub Desktop.
Save fhrbek/2dfe0d347d1e98083cadb5ad397637c7 to your computer and use it in GitHub Desktop.
Datetime Range Picker with Chrono
import Ember from 'ember';
import DatetimeObject from '../datetime-input/datetime-object';
import DatetimeRangeObject from '../datetime-range-input/datetime-range-object';
export default Ember.Controller.extend({
appName: 'PE Datetime Range Selector Demo',
datetimeRange: DatetimeRangeObject.create(),
init () {
this.reset();
},
reset () {
var date = moment().utc(),
from, to;
to = date.format('YYYY-MM-DD [00:00]');
date.subtract(1, 'day');
from = date.format('YYYY-MM-DD [00:00]');
// This does not work well - if we only change inner attributes,
// the datetime-input component does not recognize any change in
// parameters and does not call didReceiveAttrs callback;
// see the uncommented code below which creates brand new objects
this.setProperties({
'datetimeRange.from.timestamp': from,
'datetimeRange.to.timestamp': to
});
/*
this.setProperties({
'datetimeRange.from': DatetimeObject.create({'timestamp': from}),
'datetimeRange.to': DatetimeObject.create({'timestamp': to})
});
*/
},
updateObject: function (obj, diff) {
var properties = Object.keys(diff);
properties.forEach((property) => {
var value = diff[property],
orig = obj.get(property);
if (orig instanceof Ember.Object) {
this.updateObject(orig, value)
} else {
obj.set(property, value);
}
});
},
actions: {
valueChanged: function (diff) {
this.updateObject(this.get('datetimeRange'), diff);
},
reset: function () {
this.reset();
}
}
});
import Ember from 'ember';
import DatetimeObject from './datetime-object';
export default Ember.Component.extend({
tagName: 'span',
datetimeObject: DatetimeObject.create(),
didReceiveAttrs () {
this._super(...arguments);
this.set('workDatetimeObject', DatetimeObject.create({'timestamp': this.get('datetimeObject.timestamp')}));
},
didUpdateAttrs () {
console.log('DID UPDATE ATTRS CALLED');
},
classNames: ['datetime-input'],
classNameBindings: ['workDatetimeObject.isValid::invalid'],
didInsertElement: function () {
this.$('input').blur(() => {
// This assignment looks superfluous to me but without is, the work timestamp is not updated
// as didReceiveAttrs is not called if a nested property only (on the datetimeObject) is changed. Why???
this.set('workDatetimeObject.timestamp', this.get('workDatetimeObject.normalizedTimestamp'));
this.sendAction('valueChangedAction', {'timestamp': this.get('workDatetimeObject.timestamp')});
});
},
actions: {
workValueChanged: function (timestamp) {
this.set('workDatetimeObject.timestamp', timestamp);
}
}
});
import Ember from 'ember';
export default Ember.Object.extend({
timestamp: '',
normalFormat: 'YYYY-MM-DD HH:mm',
acceptedFormats: [
'YYYY',
'YYYYMM',
'YYYY-MM',
'YYYYMMDD',
'YYYY-MM-DD',
'YYYYMMDDHH',
'YYYY-MM-DD HH',
'YYYYMMDDHHmm',
'YYYY-MM-DD HH:mm',
'H',
'HA',
'H:m',
'H:mA'
],
parsedTimestamp: Ember.computed('timestamp', 'acceptedFormats', 'acceptedFormats.[]', function () {
var tstr = this.get('timestamp');
if (Ember.isBlank(tstr)) {
return null;
}
var result;
result = moment(tstr, this.get('acceptedFormats'), true);
if (!result.isValid()) {
var chronoResult = chrono.parse(tstr)[0];
if (chronoResult) {
result = moment(chronoResult.start.date());
}
}
return result.isValid() ? result : false;
}),
normalizedTimestamp: Ember.computed('parsedTimestamp', 'normalFormat', function () {
var ts = this.get('parsedTimestamp');
if (ts !== false) {
if (ts === null) {
return null;
} else {
return ts.format(this.get('normalFormat'));
}
}
return this.get('timestamp');
}),
isValid: Ember.computed('timestamp', function () {
return this.get('parsedTimestamp') !== false;
})
});
<input value={{workDatetimeObject.timestamp}} oninput={{action "workValueChanged" value="target.value"}}>
import Ember from 'ember';
import DatetimeRangeObject from './datetime-range-object';
export default Ember.Component.extend({
tagName: 'span',
datetimeRangeObject: DatetimeRangeObject.create(),
classNames: ['datetime-range-input'],
classNameBindings: ['datetimeRangeObject.isValid::invalid'],
actions: {
fromChanged: function (diff) {
this.sendAction('valueChangedAction', {'from': diff});
},
toChanged: function (diff) {
this.sendAction('valueChangedAction', {'to': diff});
}
}
});
import Ember from 'ember';
import DatetimeObject from '../datetime-input/datetime-object';
export default Ember.Object.extend({
from: DatetimeObject.create(),
to: DatetimeObject.create(),
isValid: Ember.computed('from.timestamp', 'to.timestamp', function () {
var from = this.get('from'),
to = this.get('to');
return from.get('isValid') && to.get('isValid') &&
(from.get('parsedTimestamp') === null || to.get('parsedTimestamp') === null ||
from.get('parsedTimestamp').isSameOrBefore(to.get('parsedTimestamp')));
}),
err: Ember.computed('from.timestamp', 'to.timestamp', 'isValid', function () {
var from = this.get('from'),
to = this.get('to'),
err = '';
if (!from.get('isValid')) {
err += 'Timestamp from is not valid;';
}
if (!to.get('isValid')) {
err += 'Timestamp to is not valid;';
}
if (from.get('isValid') && to.get('isValid') && !this.get('isValid')) {
err = 'Individual timestamps are valid but the range is invalid';
}
return err;
})
});
{{datetime-input datetimeObject=datetimeRangeObject.from valueChangedAction="fromChanged"}}
<span class="delimiter">-</span>
{{datetime-input datetimeObject=datetimeRangeObject.to valueChangedAction="toChanged"}}
body {
margin: 12px 16px;
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-size: 12pt;
}
button {
margin-top: 1em;
}
label {
margin-right: 1em;
}
.datetime-range-input.invalid input,
.datetime-input.invalid input {
background-color: #ffaaaa;
}
.datetime-range-input .delimiter {
margin-left: .5em;
margin-right: .5em;
}
.info {
color: #bbb;
}
.error {
color: #f55;
}
<h1>{{appName}}</h1>
<br>
<br>
<label>Datetime range:</label>
{{datetime-range-input datetimeRangeObject=datetimeRange valueChangedAction="valueChanged"}}
<br>
<br>
{{#if datetimeRange.isValid}}
<div class="info">User wants data from <span class="fromDatetime">{{datetimeRange.from.normalizedTimestamp}}</span> to <span class="toDatetime">{{datetimeRange.to.normalizedTimestamp}}</span></div>
{{else}}
<div class="error">The selection is invalid: {{datetimeRange.err}}</div>
{{/if}}
<button {{action "reset"}}>Reset</button>
{
"version": "0.7.2",
"EmberENV": {
"FEATURES": {}
},
"options": {
"use_pods": true,
"enable-testing": false
},
"dependencies": {
"jquery": "https://cdnjs.cloudflare.com/ajax/libs/jquery/1.11.3/jquery.js",
"ember": "https://cdnjs.cloudflare.com/ajax/libs/ember.js/2.4.4/ember.debug.js",
"ember-data": "https://cdnjs.cloudflare.com/ajax/libs/ember-data.js/2.4.3/ember-data.js",
"ember-template-compiler": "https://cdnjs.cloudflare.com/ajax/libs/ember.js/2.4.4/ember-template-compiler.js",
"moment": "https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.12.0/moment.js",
"chrono": "https://cdn.jsdelivr.net/chrono/1.2.0/chrono.js"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment