', startRange, endRange;
115 | if( !isEmpty (dateStates) ) {
116 | html += '
';
117 | html += ' ';
118 | if (dateStates.rangeStart) {
119 | startRange = createMoment(dateStates.rangeStart).format(DateFormat);
120 | endRange = (dateStates.rangeEnd && (dateStates.rangeEnd !== dateStates.rangeStart)) ? createMoment(dateStates.rangeEnd).format(DateFormat) : null;
121 | if( !sortAscending ) {
122 | html += startRange;
123 | if (endRange !== null) {
124 | html += encoder.encodeForHTML(props.separator) + endRange;
125 | }
126 | } else {
127 | if( endRange!== null) {
128 | html += endRange + encoder.encodeForHTML(props.separator) + startRange;
129 | } else {
130 | html += startRange;
131 | }
132 | }
133 | } else {
134 | html += encoder.encodeForHTML(props.defaultText);
135 | }
136 | html += ' ';
137 | html += '
';
138 | } else {
139 | html += '
';
140 | html += 'Add Date Field' + '';
141 | }
142 | html += '
';
143 | return html;
144 | }
145 | function getPosition( element ) {
146 | if (element.offset().left < 600) {
147 | return "right";
148 | }
149 | else if (element.offset().right < 600) {
150 | return "left";
151 | }
152 | else {
153 | "left";
154 | }
155 | }
156 | function getTopPosition( element ) {
157 | if ($(window).height() - element.offset().top > 310) {
158 | return "down";
159 | }
160 | else {
161 | return "up";
162 | }
163 | }
164 | return {
165 | methods: { //for testability
166 | createDate: createDate,
167 | createMoment: createMoment,
168 | createRanges: createRanges,
169 | createDateStates: createDateStates,
170 | createHtml:createHtml
171 | },
172 | initialProperties: {
173 | version: 1.0,
174 | qListObjectDef: {
175 | qDef: {
176 | autoSort: false,
177 | qSortCriterias: [
178 | {qSortByNumeric: -1},
179 | {qSortByState: 1},
180 | ],
181 | },
182 | qShowAlternatives: true,
183 | qFrequencyMode: "V",
184 | qInitialDataFetch: [{
185 | qWidth: 1,
186 | qHeight: 10000
187 | }]
188 | },
189 | advanced: false
190 | },
191 | // Prevent conversion from and to this object
192 | exportProperties: null,
193 | importProperties: null,
194 | definition: CalendarSettings,
195 | support: {
196 | snapshot: false,
197 | export: false,
198 | exportData: false
199 | },
200 | paint: function ($element, layout) {
201 | var self = this;
202 | var interactionState = this._interactionState;
203 | var noSelections = this.options.noSelections === true;
204 |
205 | // old sort order was ascending, check to see if the object was created before the change
206 | // to calcuate the range start and end dates in the createDateStates
207 | var sortAscending = layout && layout.qListObject && layout.qListObject.qSortCriterias &&
208 | layout.qListObject.qSortCriterias.qSortByNumeric == "1";
209 |
210 | function canInteract() {
211 | return interactionState === 1;
212 | }
213 |
214 | this.dateStates = createDateStates(layout.qListObject.qDataPages);
215 | if (!self.app) {
216 | self.app = qlik.currApp(this);
217 | }
218 |
219 | var qlikDateFormat = layout.qListObject.qDimensionInfo.qNumFormat.qFmt
220 | || self.app.model.layout.qLocaleInfo.qDateFmt;
221 | var outDateFormat = layout.props.format || qlikDateFormat;
222 | var minDate, maxDate, startDate, endDate;
223 | moment.locale(layout.props.locale);
224 | if (qlikDateFormat.indexOf('#') === -1) {
225 | minDate = moment.utc(moment(layout.props.minDate).toDate());
226 | maxDate = moment.utc(moment(layout.props.maxDate).toDate());
227 | startDate = moment.utc(moment(layout.props.startDate).toDate());
228 | endDate = moment.utc(moment(layout.props.endDate).toDate());
229 | } else {
230 | minDate = createMoment(layout.props.minDate, qlikDateFormat);
231 | maxDate = createMoment(layout.props.maxDate, qlikDateFormat);
232 | startDate = createMoment(layout.props.startDate, qlikDateFormat);
233 | endDate = createMoment(layout.props.endDate, qlikDateFormat);
234 | }
235 | $('#dropDown_' + layout.qInfo.qId).remove();
236 |
237 | $element.html(createHtml(this.dateStates, outDateFormat, layout.props, sortAscending));
238 |
239 | var config = {
240 | singleDatePicker: layout.props.isSingleDate,
241 | preventSelections: noSelections,
242 | "locale": {
243 | "format": outDateFormat,
244 | "separator": layout.props.separator
245 | },
246 | "parentEl": "#grid",
247 | "autoUpdateInput": false,
248 | "autoApply": true,
249 | "opens": getPosition($element),
250 | "top": getTopPosition($element),
251 | "id": layout.qInfo.qId,
252 | getClass: function (date) {
253 | var d = date.format('YYYYMMDD');
254 | if (self.dateStates[d]) {
255 | return 'state' + self.dateStates[d];
256 | }
257 | return 'nodata';
258 | }
259 | };
260 |
261 | if (minDate.isValid()) {
262 | config.minDate = minDate;
263 | }
264 |
265 | if (maxDate.isValid()) {
266 | config.maxDate = maxDate;
267 | }
268 |
269 | if (startDate.isValid()) {
270 | config.startDate = startDate;
271 | } else {
272 | config.startDate = config.minDate;
273 | }
274 |
275 | if (endDate.isValid()) {
276 | config.endDate = endDate;
277 | } else {
278 | config.endDate = config.maxDate;
279 | }
280 |
281 | if (layout.props.CustomRangesEnabled) {
282 | config.locale.customRangeLabel = layout.props.customRangeLabel;
283 | config.ranges = createRanges(layout.props);
284 | }
285 | if (canInteract()) {
286 | $element.find('.show-range').qlikdaterangepicker(config, function (pickStart, pickEnd, label) {
287 | if (!noSelections && pickStart.isValid() && pickEnd.isValid()) {
288 |
289 | var lastIndex, lowIndex, highIndex, qElemNumbers;
290 | // The conversion to UTC below doesn't work correctly for Timestamp,
291 | // so checking the format which is '###0' for timestamps and doing different conversion formats.
292 | if (qlikDateFormat.indexOf('#') === -1) {
293 | //To support various time zones, converting dates to a UTC format so they can be compared correctly.
294 | pickStart = moment.utc(moment(pickStart).toDate());
295 | pickEnd = moment.utc(moment(pickEnd).toDate());
296 | } else {
297 | // Handling timestamp case separately
298 | pickStart = moment.utc(pickStart.format("YYYYMMDD").toString(), "YYYYMMDD"),
299 | pickEnd = moment.utc(pickEnd.format("YYYYMMDD").toString(), "YYYYMMDD");
300 | }
301 | var dateBinarySearch = function(seachDate, lowIndex, highIndex) {
302 | if (lowIndex === highIndex) {
303 | return lowIndex;
304 | }
305 | var middleIndex = lowIndex + Math.ceil((highIndex - lowIndex) / 2);
306 | var middleDate = qlikDateFormat.indexOf('#') === -1 ? moment.utc(moment(
307 | layout.qListObject.qDataPages[0].qMatrix[middleIndex][0].qText,
308 | qlikDateFormat).toDate()): createMoment(layout.qListObject.qDataPages[0].qMatrix[middleIndex][0].qText, qlikDateFormat);
309 | // If the date object is created prior to September 2019, the order
310 | //of dates shall be ascending and needs to be handled separately
311 | if (sortAscending) {
312 | if (seachDate.isBefore(middleDate)) {
313 | return dateBinarySearch(seachDate, lowIndex, middleIndex - 1);
314 | }
315 | }
316 | // From September 2019, The matrix stores the dates from latest to earliest, so if the
317 | // sought date is after the middle date, pick the lower index span
318 | else {
319 | if (seachDate.isAfter(middleDate)) {
320 | return dateBinarySearch(seachDate, lowIndex, middleIndex - 1);
321 | }
322 | }
323 | return dateBinarySearch(seachDate, middleIndex, highIndex);
324 | };
325 | if (sortAscending) {
326 | lastIndex = layout.qListObject.qDataPages[0].qMatrix.length - 1;
327 | // Elements are stored in ascending order, so pick out index of start first
328 | lowIndex = dateBinarySearch(pickStart, 0, lastIndex);
329 | // Index of end is guaranteed to be >= index of start
330 | highIndex = dateBinarySearch(pickEnd, lowIndex, lastIndex);
331 | qElemNumbers = layout.qListObject.qDataPages[0].qMatrix
332 | .slice(lowIndex, highIndex + 1).map(function (fieldValue) {
333 | return fieldValue[0].qElemNumber;
334 | });
335 | } else {
336 | lastIndex = layout.qListObject.qDataPages[0].qMatrix.length - 1;
337 | // Elements are stored in reverse order, so pick out index of end first
338 | lowIndex = dateBinarySearch(pickEnd, 0, lastIndex);
339 | // Index of start is guaranteed to be >= index of end
340 | highIndex = dateBinarySearch(pickStart, lowIndex, lastIndex);
341 | qElemNumbers = layout.qListObject.qDataPages[0].qMatrix
342 | .slice(lowIndex, highIndex + 1).map(function (fieldValue) {
343 | var date;
344 | if (qlikDateFormat.indexOf('#') === -1) {
345 | date = moment.utc(moment(fieldValue[0].qText, qlikDateFormat).toDate());
346 | } else {
347 | date = createMoment(fieldValue[0].qText, qlikDateFormat);
348 | }
349 | if(date.isSame(pickEnd) || date.isSame(pickStart)) {
350 | return fieldValue[0].qElemNumber;
351 | } else if(date.isBefore(pickEnd) && date.isAfter(pickStart)) {
352 | return fieldValue[0].qElemNumber;
353 | } else {
354 | return -1;
355 | }
356 | });
357 | }
358 | self.backendApi.selectValues(0, qElemNumbers, false);
359 | }
360 | });
361 | }
362 | }
363 | };
364 | });
--------------------------------------------------------------------------------
/src/calendar-settings.js:
--------------------------------------------------------------------------------
1 | define(["qlik"], function (qlik) {
2 | var fieldList, fieldListPromise, CalendarSettings;
3 |
4 | function getPromise() {
5 | fieldListPromise = qlik.currApp().createGenericObject({
6 | qFieldListDef: {
7 | qType: 'variable'
8 | }
9 | }).then(function (reply) {
10 | fieldList = reply.layout.qFieldList.qItems.filter(function (item) {
11 | return item.qTags && item.qTags.indexOf('$date') > -1;
12 | }).map(function (item) {
13 | return {
14 | value: item.qName,
15 | label: item.qName
16 | };
17 | });
18 | return fieldList;
19 | });
20 | return fieldListPromise;
21 | }
22 | function isQlikCloud(){
23 | const qlikCloudRegEx = /\.(qlik-stage|qlikcloud)\.com/;
24 | const matcher = window.location.hostname.match(qlikCloudRegEx) || [];
25 | return matcher.length;
26 | }
27 |
28 | var dimension = {
29 | type: "items",
30 | label: "Field",
31 | min: 1,
32 | max: 1,
33 | items: {
34 | field: {
35 | type: "string",
36 | ref: "qListObjectDef.qDef.qFieldDefs.0",
37 | label: "Date field",
38 | component: 'dropdown',
39 | options: function () {
40 | return getPromise();
41 | },
42 | show: function (data) {
43 | return !data.advanced;
44 | },
45 | change: function (data) {
46 | var field = data.qListObjectDef.qDef.qFieldDefs[0];
47 | data.props.minDate = { qStringExpression: '=Min( {1} [' + field + '])' };
48 | data.props.maxDate = { qStringExpression: '=Max( {1} [' + field + '])' };
49 | data.props.startDate = { qStringExpression: '=Min([' + field + '])' };
50 | data.props.endDate = { qStringExpression: '=Max([' + field + '])' };
51 | }
52 | },
53 | fieldAdvanced: {
54 | type: "string",
55 | ref: "qListObjectDef.qDef.qFieldDefs.0",
56 | label: "Date field",
57 | expression: "always",
58 | expressionType: "dimension",
59 | show: function (data) {
60 | return data.advanced;
61 | }
62 | },
63 | SingleDateSwitch: {
64 | type: "boolean",
65 | component: "switch",
66 | label: "Single date / interval",
67 | ref: "props.isSingleDate",
68 | options: [{
69 | value: true,
70 | label: "Single date"
71 | }, {
72 | value: false,
73 | label: "Date interval"
74 | }],
75 | defaultValue: false
76 | },
77 | advanced: {
78 | type: "boolean",
79 | component: "switch",
80 | label: "Advanced setup",
81 | ref: "advanced",
82 | options: [{
83 | value: true,
84 | translation: "properties.on"
85 | }, {
86 | value: false,
87 | translation: "properties.off"
88 | }],
89 | defaultValue: false,
90 | show: function (data) {
91 | return data.qListObjectDef.qDef.qFieldDefs.length > 0 &&
92 | data.qListObjectDef.qDef.qFieldDefs[0].length > 0;
93 | }
94 | },
95 | minDate: {
96 | ref: "props.minDate",
97 | label: "Min date",
98 | type: "string",
99 | expression: "optional",
100 | show: function (data) {
101 | return data.advanced;
102 | }
103 | },
104 | maxDate: {
105 | ref: "props.maxDate",
106 | label: "Max date",
107 | type: "string",
108 | expression: "optional",
109 | show: function (data) {
110 | return data.advanced;
111 | }
112 | },
113 | startDate: {
114 | ref: "props.startDate",
115 | label: "Start date",
116 | type: "string",
117 | expression: "optional",
118 | show: function (data) {
119 | return data.advanced;
120 | }
121 | },
122 | endDate: {
123 | ref: "props.endDate",
124 | label: "End date",
125 | type: "string",
126 | expression: "optional",
127 | show: function (data) {
128 | return data.advanced;
129 | }
130 | }
131 | }
132 | };
133 | // This creates a divider in the property panel without showing any warnings
134 | var divider = {
135 | type: 'items',
136 | grouped: true,
137 | items: { divider: { type: 'items' } },
138 | };
139 | if( !isQlikCloud() ) {
140 | CalendarSettings = {
141 | component: "expandable-items",
142 | label: "Calendar Settings",
143 | items: {
144 | ranges: {
145 | type: "items",
146 | label: "Predefined ranges",
147 | grouped: true,
148 | items: {
149 | showPredefinedRanges: {
150 | type: 'items',
151 | items: {
152 | CustomRangesSwitch: {
153 | type: "boolean",
154 | component: "switch",
155 | label: "Show predefined ranges",
156 | ref: "props.CustomRangesEnabled",
157 | options: [{
158 | value: true,
159 | translation: "properties.on"
160 | }, {
161 | value: false,
162 | translation: "properties.off"
163 | }],
164 | defaultValue: true
165 | },
166 | CustomRange: {
167 | type: "string",
168 | ref: "props.customRangeLabel",
169 | label: "Custom Range",
170 | defaultValue: "Range",
171 | expression: "optional",
172 | show: function (data) {
173 | return data.props.CustomRangesEnabled;
174 | }
175 | },
176 | Today: {
177 | type: "string",
178 | ref: "props.today",
179 | label: "Today",
180 | defaultValue: "Today",
181 | expression: "optional",
182 | show: function (data) {
183 | return data.props.CustomRangesEnabled;
184 | }
185 | },
186 | Yesterday: {
187 | type: "string",
188 | ref: "props.yesterday",
189 | label: "Yesterday",
190 | defaultValue: "Yesterday",
191 | expression: "optional",
192 | show: function (data) {
193 | return data.props.CustomRangesEnabled;
194 | }
195 | },
196 | LastDays: {
197 | type: "string",
198 | ref: "props.lastXDays",
199 | label: "Last $ days",
200 | defaultValue: "Last $ days",
201 | expression: "optional",
202 | show: function (data) {
203 | return data.props.CustomRangesEnabled;
204 | }
205 | },
206 | },
207 | },
208 | showCustomRangeThis: {
209 | type: 'items',
210 | items: {
211 | ThisMonthDropDown: {
212 | type: "string",
213 | ref: "props.this",
214 | label: "This",
215 | component: "dropdown",
216 | defaultValue: 'm',
217 | show: function (data) {
218 | return data.props.CustomRangesEnabled;
219 | },
220 | change: function (data) {
221 | if (data.props.this === 'd') {
222 | data.props.thisLabel = "This Day";
223 | } else if(data.props.this === 'm') {
224 | data.props.thisLabel = "This Month";
225 | } else if(data.props.this === 'q') {
226 | data.props.thisLabel = "This Quarter";
227 | } else if(data.props.this === 'y') {
228 | data.props.thisLabel = "This Year";
229 | } else if(data.props.this === 'n') {
230 | data.props.thisLabel = "";
231 | }
232 | },
233 | options: [{
234 | value: 'd',
235 | label: 'Day'
236 | }, {
237 | value: 'm',
238 | label: 'Month'
239 | }, {
240 | value: 'q',
241 | label: 'Quarter'
242 | }, {
243 | value: 'y',
244 | label: 'Year'
245 | },{
246 | value: 'n',
247 | label: 'None'
248 | }],
249 | },
250 | ThisMonth: {
251 | type: "string",
252 | ref: "props.thisLabel",
253 | defaultValue: "This Month",
254 | expression: 'optional',
255 | show: function (data) {
256 | return data.props.CustomRangesEnabled;
257 | },
258 | change: function (data) {
259 | if (data.props.this === 'd' && data.props.thisLabel === '') {
260 | data.props.thisLabel = "This Day";
261 | } else if(data.props.this === 'm' && data.props.thisLabel === '') {
262 | data.props.thisLabel = "This Month";
263 | } else if(data.props.this === 'q' && data.props.thisLabel === '') {
264 | data.props.thisLabel = "This Quarter";
265 | } else if(data.props.this === 'y' && data.props.thisLabel === '') {
266 | data.props.thisLabel = "This Year";
267 | } else if(data.props.this === 'n') {
268 | data.props.thisLabel = "";
269 | }
270 | },
271 | },
272 | },
273 | },
274 | showCustomRangeLast: {
275 | type: 'items',
276 | items: {
277 | LastMonthDropDown: {
278 | type: "string",
279 | ref: "props.last",
280 | label: "Last",
281 | component: "dropdown",
282 | defaultValue: 'm',
283 | show: function (data) {
284 | return data.props.CustomRangesEnabled;
285 | },
286 | change: function (data) {
287 | if (data.props.last === 'd') {
288 | data.props.lastLabel = "Last Day";
289 | } else if(data.props.last === 'm') {
290 | data.props.lastLabel = "Last Month";
291 | } else if(data.props.last === 'q') {
292 | data.props.lastLabel = "Last Quarter";
293 | } else if(data.props.last === 'y') {
294 | data.props.lastLabel = "Last Year";
295 | } else if(data.props.last === 'n') {
296 | data.props.lastLabel = "";
297 | }
298 | },
299 | options: [{
300 | value: 'd',
301 | label: 'Day'
302 | }, {
303 | value: 'm',
304 | label: 'Month'
305 | }, {
306 | value: 'q',
307 | label: 'Quarter'
308 | }, {
309 | value: 'y',
310 | label: 'Year'
311 | },{
312 | value: 'n',
313 | label: 'None'
314 | }],
315 | },
316 | LastMonth: {
317 | type: "string",
318 | ref: "props.lastLabel",
319 | defaultValue: "Last Month",
320 | expression: 'optional',
321 | show: function (data) {
322 | return data.props.CustomRangesEnabled;
323 | },
324 | change: function (data) {
325 | if (data.props.last === 'd' && data.props.lastLabel === '') {
326 | data.props.lastLabel = "Last Day";
327 | } else if(data.props.last === 'm' && data.props.lastLabel === '') {
328 | data.props.lastLabel = "Last Month";
329 | } else if(data.props.last === 'q' && data.props.lastLabel === '') {
330 | data.props.lastLabel = "Last Quarter";
331 | } else if(data.props.last === 'y' && data.props.lastLabel === '') {
332 | data.props.lastLabel = "Last Year";
333 | } else if(data.props.last === 'n') {
334 | data.props.lastLabel = "";
335 | }
336 | },
337 | },
338 | numberOf: {
339 | type: 'number',
340 | ref: 'props.numberOf',
341 | label: 'Last number of',
342 | defaultValue: 1,
343 | show: function (data) {
344 | return data.props.CustomRangesEnabled && ['d','m','q','y'].indexOf(data.props.last) > -1;
345 | }
346 | },
347 | previousOrLastValues: {
348 | ref: 'props.previousOrLast',
349 | type: 'boolean',
350 | label: 'Include current',
351 | component: 'checkbox',
352 | defaultValue: false,
353 | show: function (data) {
354 | return data.props.CustomRangesEnabled && ['d','m','q','y'].indexOf(data.props.last) > -1;
355 | }
356 | },
357 | },
358 | },
359 | },
360 | },
361 | header1: {
362 | type: "items",
363 | label: "Language and labels",
364 | items: {
365 | Language: {
366 | type: "string",
367 | ref: "props.locale",
368 | label: "Locale",
369 | defaultValue: "en",
370 | expression: "optional"
371 | },
372 | Format: {
373 | type: "string",
374 | ref: "props.format",
375 | label: "Format",
376 | defaultValue: "YYYY-MM-DD",
377 | expression: "optional"
378 | },
379 | Separator: {
380 | type: "string",
381 | ref: "props.separator",
382 | label: "Separator",
383 | defaultValue: " - ",
384 | expression: "optional"
385 | },
386 | defaultText: {
387 | type: "string",
388 | ref: "props.defaultText",
389 | label: "Default Text",
390 | expression: "optional",
391 | defaultValue: "Select date range"
392 | }
393 | }
394 | }
395 | }
396 | };
397 | } else {
398 | CalendarSettings = {
399 | component: "expandable-items",
400 | label: "Calendar Settings",
401 | items: {
402 | ranges: {
403 | type: "items",
404 | label: "Predefined ranges",
405 | items: {
406 | CustomRangesSwitch: {
407 | type: "boolean",
408 | component: "switch",
409 | label: "Show predefined ranges",
410 | ref: "props.CustomRangesEnabled",
411 | options: [{
412 | value: true,
413 | translation: "properties.on"
414 | }, {
415 | value: false,
416 | translation: "properties.off"
417 | }],
418 | defaultValue: true
419 | },
420 | CustomRange: {
421 | type: "string",
422 | ref: "props.customRangeLabel",
423 | label: "Custom Range",
424 | defaultValue: "Range",
425 | expression: "optional",
426 | show: function (data) {
427 | return data.props.CustomRangesEnabled;
428 | }
429 | },
430 | Today: {
431 | type: "string",
432 | ref: "props.today",
433 | label: "Today",
434 | defaultValue: "Today",
435 | expression: "optional",
436 | show: function (data) {
437 | return data.props.CustomRangesEnabled;
438 | }
439 | },
440 | Yesterday: {
441 | type: "string",
442 | ref: "props.yesterday",
443 | label: "Yesterday",
444 | defaultValue: "Yesterday",
445 | expression: "optional",
446 | show: function (data) {
447 | return data.props.CustomRangesEnabled;
448 | }
449 | },
450 | LastDays: {
451 | type: "string",
452 | ref: "props.lastXDays",
453 | label: "Last $ days",
454 | defaultValue: "Last $ days",
455 | expression: "optional",
456 | show: function (data) {
457 | return data.props.CustomRangesEnabled;
458 | }
459 | },
460 | ThisMonth: {
461 | type: "string",
462 | ref: "props.thisMonth",
463 | label: "This Month",
464 | defaultValue: "This Month",
465 | expression: "optional",
466 | show: function (data) {
467 | return data.props.CustomRangesEnabled;
468 | }
469 | },
470 | LastMonth: {
471 | type: "string",
472 | ref: "props.lastMonth",
473 | label: "Last Month",
474 | defaultValue: "Last Month",
475 | expression: "optional",
476 | show: function (data) {
477 | return data.props.CustomRangesEnabled;
478 | }
479 | }
480 | }
481 | },
482 | header1: {
483 | type: "items",
484 | label: "Language and labels",
485 | items: {
486 | Language: {
487 | type: "string",
488 | ref: "props.locale",
489 | label: "Locale",
490 | defaultValue: "en",
491 | expression: "optional"
492 | },
493 | Format: {
494 | type: "string",
495 | ref: "props.format",
496 | label: "Format",
497 | defaultValue: "YYYY-MM-DD",
498 | expression: "optional"
499 | },
500 | Separator: {
501 | type: "string",
502 | ref: "props.separator",
503 | label: "Separator",
504 | defaultValue: " - ",
505 | expression: "optional"
506 | },
507 | defaultText: {
508 | type: "string",
509 | ref: "props.defaultText",
510 | label: "Default Text",
511 | expression: "optional",
512 | defaultValue: "Select date range"
513 | }
514 | }
515 | }
516 | }
517 | };
518 | }
519 | var about = {
520 | label: "About",
521 | component: "items",
522 | items: {
523 | header: {
524 | label: 'Date picker',
525 | style: 'header',
526 | component: 'text'
527 | },
528 | paragraph1: {
529 | label: 'A calendar object that allows a user to make selections in a date field.',
530 | component: 'text'
531 | },
532 | paragraph2: {
533 | label: 'Date picker is based upon an extension created by Nodier Torres.',
534 | component: 'text'
535 | }
536 | }
537 | };
538 | var appearance = {
539 | uses: "settings",
540 | items: {
541 | general: {
542 | items: {
543 | details: {
544 | show: false
545 | }
546 | }
547 | },
548 | }
549 | };
550 | return {
551 | type: "items",
552 | component: "accordion",
553 | items: {
554 | dimension: dimension,
555 | settings: appearance,
556 | CalSettings: CalendarSettings,
557 | about: about
558 | }
559 | }
560 | })
--------------------------------------------------------------------------------
/src/lib/daterangepicker.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @version: 2.1.13
3 | * @author: Dan Grossman http://www.dangrossman.info/
4 | * @copyright: Copyright (c) 2012-2015 Dan Grossman. All rights reserved.
5 | * @license: Licensed under the MIT license. See http://www.opensource.org/licenses/mit-license.php
6 | * @website: https://www.improvely.com/
7 | ***
8 | *Sense Extension
9 | *Nodier Torres
10 | *Added a few changes to the html template
11 | **Added if (this.endDate?this.endDate._isValid:false) at udpdateFormInputs. when clear selections endDate will be set to null to make default calendar without range selected
12 | ***
13 | */
14 |
15 | (function(root, factory) {
16 |
17 | if (typeof define === 'function' && define.amd ) {
18 | define(['./moment.min', 'jquery', 'exports'], function(momentjs, $, exports) {
19 | root.qlikdaterangepicker = factory(root, exports, momentjs, $);
20 | });
21 |
22 | } else if (typeof exports !== 'undefined') {
23 | var momentjs = require('moment');
24 | var jQuery = (typeof window != 'undefined') ? window.jQuery : undefined; //isomorphic issue
25 | if (!jQuery) {
26 | try {
27 | jQuery = require('jquery');
28 | if (!jQuery.fn) jQuery.fn = {}; //isomorphic issue
29 | } catch (err) {
30 | if (!jQuery) throw new Error('jQuery dependency not found');
31 | }
32 | }
33 |
34 | factory(root, exports, momentjs, jQuery);
35 |
36 | // Finally, as a browser global.
37 | } else {
38 | root.qlikdaterangepicker = factory(root, {}, root.moment || moment, (root.jQuery || root.Zepto || root.ender || root.$));
39 | }
40 |
41 | }(this || {}, function(root, qlikdaterangepicker, moment, $) { // 'this' doesn't exist on a server
42 |
43 | var DateRangePicker = function(element, options, cb) {
44 |
45 | //default settings for options
46 | this.parentEl = 'body';
47 | this.element = $(element);
48 | this.startDate = moment().startOf('day');
49 | this.endDate = moment().endOf('day');
50 | this.minDate = false;
51 | this.maxDate = false;
52 | this.dateLimit = false;
53 | this.autoApply = false;
54 | this.singleDatePicker = false;
55 | this.showDropdowns = false;
56 | this.showWeekNumbers = false;
57 | this.timePicker = false;
58 | this.timePicker24Hour = false;
59 | this.timePickerIncrement = 1;
60 | this.timePickerSeconds = false;
61 | this.linkedCalendars = true;
62 | this.autoUpdateInput = true;
63 | this.ranges = {};
64 |
65 | this.opens = 'right';
66 | if (this.element.hasClass('pull-right'))
67 | this.opens = 'left';
68 | this.drops = options.top;
69 | if (this.element.hasClass('dropup'))
70 | this.drops = 'up';
71 |
72 | this.buttonClasses = 'btn btn-sm';
73 | this.applyClass = 'btn-success';
74 | this.cancelClass = 'btn-default';
75 |
76 | this.locale = {
77 | format: 'MM/DD/YYYY',
78 | separator: ' - ',
79 | applyLabel: 'Apply',
80 | cancelLabel: 'Cancel',
81 | weekLabel: 'W',
82 | customRangeLabel: 'Custom Range',
83 | daysOfWeek: moment.weekdaysMin(),
84 | monthNames: moment.monthsShort(),
85 | firstDay: moment.localeData().firstDayOfWeek()
86 | };
87 |
88 | this.callback = function() { };
89 |
90 | //some state information
91 | this.isShowing = false;
92 | this.leftCalendar = {};
93 | this.rightCalendar = {};
94 |
95 | this.preventSelections = false;
96 | //custom options from user
97 | if (typeof options !== 'object' || options === null)
98 | options = {};
99 |
100 | //allow setting options with data attributes
101 | //data-api options will be overwritten with custom javascript options
102 | options = $.extend(this.element.data(), options);
103 | error_nodata = "No data available for the range selected. Please select again."
104 |
105 | //html template for the picker UI
106 | if (typeof options.template !== 'string')
107 | options.template = '';
138 |
139 | this.parentEl = (options.parentEl && $(options.parentEl).length) ? $(options.parentEl) : $(this.parentEl);
140 |
141 | this.container = $(options.template).appendTo(this.parentEl);
142 |
143 | //
144 | // handle all the possible options overriding defaults
145 | //
146 |
147 | if (typeof options.locale === 'object') {
148 |
149 | if (typeof options.locale.format === 'string')
150 | this.locale.format = options.locale.format;
151 |
152 | if (typeof options.locale.separator === 'string')
153 | this.locale.separator = options.locale.separator;
154 |
155 | if (typeof options.locale.daysOfWeek === 'object')
156 | this.locale.daysOfWeek = options.locale.daysOfWeek.slice();
157 |
158 | if (typeof options.locale.monthNames === 'object')
159 | this.locale.monthNames = options.locale.monthNames.slice();
160 |
161 | if (typeof options.locale.firstDay === 'number')
162 | this.locale.firstDay = options.locale.firstDay;
163 |
164 | if (typeof options.locale.applyLabel === 'string')
165 | this.locale.applyLabel = options.locale.applyLabel;
166 |
167 | if (typeof options.locale.cancelLabel === 'string')
168 | this.locale.cancelLabel = options.locale.cancelLabel;
169 |
170 | if (typeof options.locale.weekLabel === 'string')
171 | this.locale.weekLabel = options.locale.weekLabel;
172 |
173 | if (typeof options.locale.customRangeLabel === 'string')
174 | this.locale.customRangeLabel = options.locale.customRangeLabel;
175 |
176 | }
177 |
178 | if (typeof options.startDate === 'string')
179 | this.startDate = moment(options.startDate, this.locale.format);
180 |
181 | if (typeof options.endDate === 'string')
182 | this.endDate = moment(options.endDate, this.locale.format);
183 |
184 | if (typeof options.minDate === 'string')
185 | this.minDate = moment(options.minDate, this.locale.format);
186 |
187 | if (typeof options.maxDate === 'string')
188 | this.maxDate = moment(options.maxDate, this.locale.format);
189 |
190 | if (typeof options.startDate === 'object')
191 | this.startDate = moment(options.startDate);
192 |
193 | if (typeof options.endDate === 'object')
194 | this.endDate = moment(options.endDate);
195 |
196 | if (typeof options.minDate === 'object')
197 | this.minDate = moment(options.minDate);
198 |
199 | if (typeof options.maxDate === 'object')
200 | this.maxDate = moment(options.maxDate);
201 |
202 | // sanity check for bad options
203 | if (this.minDate && this.startDate.isBefore(this.minDate))
204 | this.startDate = this.minDate.clone();
205 |
206 | // sanity check for bad options
207 | if (this.maxDate && this.endDate.isAfter(this.maxDate))
208 | this.endDate = this.maxDate.clone();
209 |
210 | if (typeof options.applyClass === 'string')
211 | this.applyClass = options.applyClass;
212 |
213 | if (typeof options.cancelClass === 'string')
214 | this.cancelClass = options.cancelClass;
215 |
216 | if (typeof options.dateLimit === 'object')
217 | this.dateLimit = options.dateLimit;
218 |
219 | if (typeof options.opens === 'string')
220 | this.opens = options.opens;
221 |
222 | if (typeof options.top === 'string')
223 | this.top = options.top;
224 |
225 | if (typeof options.drops === 'string')
226 | this.drops = options.drops;
227 |
228 | if (typeof options.showWeekNumbers === 'boolean')
229 | this.showWeekNumbers = options.showWeekNumbers;
230 |
231 | if (typeof options.buttonClasses === 'string')
232 | this.buttonClasses = options.buttonClasses;
233 |
234 | if (typeof options.buttonClasses === 'object')
235 | this.buttonClasses = options.buttonClasses.join(' ');
236 |
237 | if (typeof options.showDropdowns === 'boolean')
238 | this.showDropdowns = options.showDropdowns;
239 |
240 | if (typeof options.singleDatePicker === 'boolean') {
241 | this.singleDatePicker = options.singleDatePicker;
242 | if (this.singleDatePicker)
243 | this.endDate = this.startDate.clone();
244 | }
245 |
246 | if (typeof options.timePicker === 'boolean')
247 | this.timePicker = options.timePicker;
248 |
249 | if (typeof options.timePickerSeconds === 'boolean')
250 | this.timePickerSeconds = options.timePickerSeconds;
251 |
252 | if (typeof options.timePickerIncrement === 'number')
253 | this.timePickerIncrement = options.timePickerIncrement;
254 |
255 | if (typeof options.timePicker24Hour === 'boolean')
256 | this.timePicker24Hour = options.timePicker24Hour;
257 |
258 | if (typeof options.autoApply === 'boolean')
259 | this.autoApply = options.autoApply;
260 |
261 | if (typeof options.autoUpdateInput === 'boolean')
262 | this.autoUpdateInput = options.autoUpdateInput;
263 |
264 | if (typeof options.linkedCalendars === 'boolean')
265 | this.linkedCalendars = options.linkedCalendars;
266 |
267 | if (typeof options.isInvalidDate === 'function')
268 | this.isInvalidDate = options.isInvalidDate;
269 |
270 | if (typeof options.getClass === 'function')
271 | this.getClass = options.getClass;
272 |
273 | if (typeof options.preventSelections === 'boolean')
274 | this.preventSelections = options.preventSelections;
275 |
276 | // update day names order to firstDay
277 | if (this.locale.firstDay != 0) {
278 | var iterator = this.locale.firstDay;
279 | while (iterator > 0) {
280 | this.locale.daysOfWeek.push(this.locale.daysOfWeek.shift());
281 | iterator--;
282 | }
283 | }
284 |
285 | var start, end, range;
286 |
287 | //if no start/end dates set, check if an input element contains initial values
288 | if (typeof options.startDate === 'undefined' && typeof options.endDate === 'undefined') {
289 | if ($(this.element).is('input[type=text]')) {
290 | var val = $(this.element).val(),
291 | split = val.split(this.locale.separator);
292 |
293 | start = end = null;
294 |
295 | if (split.length == 2) {
296 | start = moment(split[0], this.locale.format);
297 | end = moment(split[1], this.locale.format);
298 | } else if (this.singleDatePicker && val !== "") {
299 | start = moment(val, this.locale.format);
300 | end = moment(val, this.locale.format);
301 | }
302 | if (start !== null && end !== null) {
303 | this.setStartDate(start);
304 | this.setEndDate(end);
305 | }
306 | }
307 | }
308 |
309 | if (typeof options.ranges === 'object') {
310 | for (range in options.ranges) {
311 |
312 | if (typeof options.ranges[range][0] === 'string')
313 | start = moment(options.ranges[range][0], this.locale.format);
314 | else
315 | start = moment(options.ranges[range][0]);
316 |
317 | if (typeof options.ranges[range][1] === 'string')
318 | end = moment(options.ranges[range][1], this.locale.format);
319 | else
320 | end = moment(options.ranges[range][1]);
321 |
322 | // If the start or end date exceed those allowed by the minDate or dateLimit
323 | // options, shorten the range to the allowable period.
324 | if (this.minDate && start.isBefore(this.minDate))
325 | start = this.minDate.clone();
326 |
327 | var maxDate = this.maxDate;
328 | if (this.dateLimit && start.clone().add(this.dateLimit).isAfter(maxDate))
329 | maxDate = start.clone().add(this.dateLimit);
330 | if (maxDate && end.isAfter(maxDate))
331 | end = maxDate.clone();
332 |
333 | // If the end of the range is before the minimum or the start of the range is
334 | // after the maximum, disable this range option .
335 | var disabled = (this.minDate && end.isBefore(this.minDate)) || (maxDate && start.isAfter(maxDate));
336 |
337 | var elem = document.createElement('textarea');
338 | elem.innerHTML = range;
339 | var rangeHtml = elem.value;
340 |
341 | this.ranges[rangeHtml] = [start, end, disabled];
342 | }
343 |
344 | var list = '