@websy/websy-designs 0.0.128 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12,313 +12,870 @@
12
12
  WebsyTable
13
13
  WebsyChart
14
14
  WebsyChartTooltip
15
+ WebsyLegend
15
16
  WebsyMap
16
17
  WebsyKPI
17
18
  WebsyPDFButton
19
+ Switch
18
20
  WebsyTemplate
19
21
  APIService
22
+ ButtonGroup
20
23
  WebsyUtils
21
24
  */
22
25
 
23
- class WebsyPopupDialog {
26
+ /* global XMLHttpRequest fetch ENV */
27
+ class APIService {
28
+ constructor (baseUrl = '', options = {}) {
29
+ this.baseUrl = baseUrl
30
+ this.options = Object.assign({}, options)
31
+ }
32
+ add (entity, data, options = {}) {
33
+ const url = this.buildUrl(entity)
34
+ return this.run('POST', url, data, options)
35
+ }
36
+ buildUrl (entity, id, query) {
37
+ if (typeof query === 'undefined') {
38
+ query = []
39
+ }
40
+ if (id) {
41
+ query.push(`id:${id}`)
42
+ }
43
+ return `${this.baseUrl}/${entity}${query.length > 0 ? `${entity.indexOf('?') === -1 ? '?' : '&'}where=${query.join(';')}` : ''}`
44
+ }
45
+ delete (entity, id) {
46
+ const url = this.buildUrl(entity, id)
47
+ return this.run('DELETE', url)
48
+ }
49
+ get (entity, id, query) {
50
+ const url = this.buildUrl(entity, id, query)
51
+ return this.run('GET', url)
52
+ }
53
+ update (entity, id, data) {
54
+ const url = this.buildUrl(entity, id)
55
+ return this.run('PUT', url, data)
56
+ }
57
+ fetchData (method, url, data, options = {}) {
58
+ return fetch(url, {
59
+ method,
60
+ mode: 'cors', // no-cors, *cors, same-origin
61
+ cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
62
+ credentials: 'same-origin', // include, *same-origin, omit
63
+ headers: {
64
+ 'Content-Type': 'application/json'
65
+ // 'Content-Type': 'application/x-www-form-urlencoded',
66
+ },
67
+ redirect: 'follow', // manual, *follow, error
68
+ referrerPolicy: 'no-referrer', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
69
+ body: JSON.stringify(data) // body data type must match "Content-Type" header
70
+ }).then(response => {
71
+ return response.json()
72
+ })
73
+ }
74
+ run (method, url, data, options = {}, returnHeaders = false) {
75
+ return new Promise((resolve, reject) => {
76
+ const xhr = new XMLHttpRequest()
77
+ xhr.open(method, url)
78
+ xhr.setRequestHeader('Content-Type', 'application/json')
79
+ xhr.responseType = 'text'
80
+ if (options.responseType) {
81
+ xhr.responseType = options.responseType
82
+ }
83
+ if (options.headers) {
84
+ for (let key in options.headers) {
85
+ xhr.setRequestHeader(key, options.headers[key])
86
+ }
87
+ }
88
+ xhr.withCredentials = true
89
+ xhr.onload = () => {
90
+ if (xhr.status === 401 || xhr.status === 403) {
91
+ if (ENV && ENV.AUTH_REDIRECT) {
92
+ window.location = ENV.AUTH_REDIRECT
93
+ }
94
+ else {
95
+ window.location = '/login'
96
+ }
97
+ // reject('401 - Unauthorized')
98
+ return
99
+ }
100
+ let response = xhr.responseType === 'text' ? xhr.responseText : xhr.response
101
+ if (response !== '' && response !== 'null') {
102
+ try {
103
+ response = JSON.parse(response)
104
+ }
105
+ catch (e) {
106
+ // Either a bad Url or a string has been returned
107
+ }
108
+ }
109
+ else {
110
+ response = []
111
+ }
112
+ if (response.err) {
113
+ reject(JSON.stringify(response))
114
+ }
115
+ else {
116
+ if (returnHeaders === true) {
117
+ resolve([response, parseHeaders(xhr.getAllResponseHeaders())])
118
+ }
119
+ else {
120
+ resolve(response)
121
+ }
122
+ }
123
+ }
124
+ xhr.onerror = () => reject(xhr.statusText)
125
+ if (data) {
126
+ xhr.send(JSON.stringify(data))
127
+ }
128
+ else {
129
+ xhr.send()
130
+ }
131
+ })
132
+ function parseHeaders (headers) {
133
+ headers = headers.split('\r\n')
134
+ let ouput = {}
135
+ headers.forEach(h => {
136
+ h = h.split(':')
137
+ if (h.length === 2) {
138
+ ouput[h[0]] = h[1].trim()
139
+ }
140
+ })
141
+ return ouput
142
+ }
143
+ }
144
+ }
145
+
146
+ /* global */
147
+ class ButtonGroup {
148
+ constructor (elementId, options) {
149
+ this.elementId = elementId
150
+ const DEFAULTS = {
151
+ style: 'button',
152
+ subscribers: {},
153
+ activeItem: 0
154
+ }
155
+ this.options = Object.assign({}, DEFAULTS, options)
156
+ const el = document.getElementById(this.elementId)
157
+ if (el) {
158
+ el.addEventListener('click', this.handleClick.bind(this))
159
+ this.render()
160
+ }
161
+ }
162
+ handleClick (event) {
163
+ if (event.target.classList.contains('websy-button-group-item')) {
164
+ const index = +event.target.getAttribute('data-index')
165
+ if (this.options.activeItem !== index) {
166
+ if (this.options.onDeactivate) {
167
+ this.options.onDeactivate(this.options.items[this.options.activeItem], this.options.activeItem)
168
+ }
169
+ this.options.activeItem = index
170
+ if (this.options.onActivate) {
171
+ this.options.onActivate(this.options.items[index], index)
172
+ }
173
+ this.render()
174
+ }
175
+ }
176
+ }
177
+ on (event, fn) {
178
+ if (!this.options.subscribers[event]) {
179
+ this.options.subscribers[event] = []
180
+ }
181
+ this.options.subscribers[event].push(fn)
182
+ }
183
+ publish (event, params) {
184
+ this.options.subscribers[event].forEach((item) => {
185
+ item.apply(null, params)
186
+ })
187
+ }
188
+ render () {
189
+ const el = document.getElementById(this.elementId)
190
+ if (el && this.options.items) {
191
+ el.innerHTML = this.options.items.map((t, i) => `
192
+ <div ${(t.attributes || []).join(' ')} data-id="${t.id || t.label}" data-index="${i}" class="websy-button-group-item ${(t.classes || []).join(' ')} ${this.options.style}-style ${i === this.options.activeItem ? 'active' : ''}">${t.label}</div>
193
+ `).join('')
194
+ }
195
+ }
196
+ }
197
+
198
+ class WebsyDatePicker {
199
+ constructor (elementId, options) {
200
+ this.oneDay = 1000 * 60 * 60 * 24
201
+ this.currentselection = []
202
+ this.validDates = []
203
+ const DEFAULTS = {
204
+ defaultRange: 0,
205
+ minAllowedDate: this.floorDate(new Date(new Date((new Date().setFullYear(new Date().getFullYear() - 1))).setDate(1))),
206
+ maxAllowedDate: this.floorDate(new Date((new Date()))),
207
+ daysOfWeek: ['S', 'M', 'T', 'W', 'T', 'F', 'S'],
208
+ monthMap: {
209
+ 0: 'Jan',
210
+ 1: 'Feb',
211
+ 2: 'Mar',
212
+ 3: 'Apr',
213
+ 4: 'May',
214
+ 5: 'Jun',
215
+ 6: 'Jul',
216
+ 7: 'Aug',
217
+ 8: 'Sep',
218
+ 9: 'Oct',
219
+ 10: 'Nov',
220
+ 11: 'Dec'
221
+ },
222
+ ranges: []
223
+ }
224
+ DEFAULTS.ranges = [
225
+ {
226
+ label: 'All Dates',
227
+ range: [DEFAULTS.minAllowedDate, DEFAULTS.maxAllowedDate]
228
+ },
229
+ {
230
+ label: 'Today',
231
+ range: [this.floorDate(new Date())]
232
+ },
233
+ {
234
+ label: 'Yesterday',
235
+ range: [this.floorDate(new Date().setDate(new Date().getDate() - 1))]
236
+ },
237
+ {
238
+ label: 'Last 7 Days',
239
+ range: [this.floorDate(new Date().setDate(new Date().getDate() - 6)), this.floorDate(new Date())]
240
+ },
241
+ {
242
+ label: 'This Month',
243
+ range: [this.floorDate(new Date().setDate(1)), this.floorDate(new Date(new Date().setDate(1)).setMonth(new Date().getMonth() + 1) - this.oneDay)]
244
+ },
245
+ {
246
+ label: 'Last Month',
247
+ range: [this.floorDate(new Date(new Date().setDate(1)).setMonth(new Date().getMonth() - 1)), this.floorDate(new Date(new Date().setDate(1)).setMonth(new Date().getMonth()) - this.oneDay)]
248
+ },
249
+ {
250
+ label: 'This Year',
251
+ range: [this.floorDate(new Date(`1/1/${new Date().getFullYear()}`)), this.floorDate(new Date(`12/31/${new Date().getFullYear()}`))]
252
+ },
253
+ {
254
+ label: 'Last Year',
255
+ range: [this.floorDate(new Date(`1/1/${new Date().getFullYear() - 1}`)), this.floorDate(new Date(`12/31/${new Date().getFullYear() - 1}`))]
256
+ }
257
+ ]
258
+ this.options = Object.assign({}, DEFAULTS, options)
259
+ this.selectedRange = this.options.defaultRange || 0
260
+ this.selectedRangeDates = [...this.options.ranges[this.options.defaultRange || 0].range]
261
+ this.priorSelectedDates = null
262
+ if (!elementId) {
263
+ console.log('No element Id provided')
264
+ return
265
+ }
266
+ const el = document.getElementById(elementId)
267
+ if (el) {
268
+ this.elementId = elementId
269
+ el.addEventListener('click', this.handleClick.bind(this))
270
+ let html = `
271
+ <div class='websy-date-picker-container'>
272
+ <span class='websy-dropdown-header-label'>${this.options.label || 'Date'}</span>
273
+ <div class='websy-date-picker-header'>
274
+ <span id='${this.elementId}_selectedRange'>${this.options.ranges[this.selectedRange].label}</span>
275
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M23.677 18.52c.914 1.523-.183 3.472-1.967 3.472h-19.414c-1.784 0-2.881-1.949-1.967-3.472l9.709-16.18c.891-1.483 3.041-1.48 3.93 0l9.709 16.18z"/></svg>
276
+ </div>
277
+ <div id='${this.elementId}_mask' class='websy-date-picker-mask'></div>
278
+ <div id='${this.elementId}_content' class='websy-date-picker-content'>
279
+ <div class='websy-date-picker-ranges'>
280
+ <ul id='${this.elementId}_rangelist'>
281
+ ${this.renderRanges()}
282
+ </ul>
283
+ </div><!--
284
+ --><div id='${this.elementId}_datelist' class='websy-date-picker-custom'>${this.renderDates()}</div>
285
+ <div class='websy-dp-button-container'>
286
+ <button class='websy-btn websy-dp-cancel'>Cancel</button>
287
+ <button class='websy-btn websy-dp-confirm'>Confirm</button>
288
+ </div>
289
+ </div>
290
+ </div>
291
+ `
292
+ el.innerHTML = html
293
+ this.render()
294
+ }
295
+ else {
296
+ console.log('No element found with Id', elementId)
297
+ }
298
+ }
299
+ close (confirm) {
300
+ const maskEl = document.getElementById(`${this.elementId}_mask`)
301
+ const contentEl = document.getElementById(`${this.elementId}_content`)
302
+ maskEl.classList.remove('active')
303
+ contentEl.classList.remove('active')
304
+ if (confirm === true) {
305
+ if (this.options.onChange) {
306
+ this.options.onChange(this.selectedRangeDates)
307
+ }
308
+ this.updateRange()
309
+ }
310
+ else {
311
+ this.selectedRangeDates = [...this.priorSelectedDates]
312
+ this.selectedRange = this.priorSelectedRange
313
+ }
314
+ }
315
+ floorDate (d) {
316
+ if (typeof d === 'number') {
317
+ d = new Date(d)
318
+ }
319
+ return new Date(d.setHours(0, 0, 0, 0))
320
+ }
321
+ handleClick (event) {
322
+ if (event.target.classList.contains('websy-date-picker-header')) {
323
+ this.open()
324
+ }
325
+ else if (event.target.classList.contains('websy-date-picker-mask')) {
326
+ this.close()
327
+ }
328
+ else if (event.target.classList.contains('websy-date-picker-range')) {
329
+ if (event.target.classList.contains('websy-disabled-range')) {
330
+ return
331
+ }
332
+ const index = event.target.getAttribute('data-index')
333
+ this.selectRange(index)
334
+ this.updateRange(index)
335
+ }
336
+ else if (event.target.classList.contains('websy-dp-date')) {
337
+ if (event.target.classList.contains('websy-disabled-date')) {
338
+ return
339
+ }
340
+ const timestamp = event.target.id.split('_')[0]
341
+ this.selectDate(+timestamp)
342
+ }
343
+ else if (event.target.classList.contains('websy-dp-confirm')) {
344
+ this.close(true)
345
+ }
346
+ else if (event.target.classList.contains('websy-dp-cancel')) {
347
+ this.close()
348
+ }
349
+ }
350
+ highlightRange () {
351
+ const el = document.getElementById(`${this.elementId}_dateList`)
352
+ const dateEls = el.querySelectorAll('.websy-dp-date')
353
+ for (let i = 0; i < dateEls.length; i++) {
354
+ dateEls[i].classList.remove('selected')
355
+ dateEls[i].classList.remove('first')
356
+ dateEls[i].classList.remove('last')
357
+ }
358
+ if (this.selectedRange === 0) {
359
+ return
360
+ }
361
+ let daysDiff = Math.floor((this.selectedRangeDates[this.selectedRangeDates.length - 1].getTime() - this.selectedRangeDates[0].getTime()) / this.oneDay)
362
+ if (this.selectedRangeDates[0].getMonth() !== this.selectedRangeDates[this.selectedRangeDates.length - 1].getMonth()) {
363
+ daysDiff += 1
364
+ }
365
+ for (let i = 0; i < daysDiff + 1; i++) {
366
+ let d = this.floorDate(new Date(this.selectedRangeDates[0].getTime() + (i * this.oneDay)))
367
+ const dateEl = document.getElementById(`${d.getTime()}_date`)
368
+ if (dateEl) {
369
+ dateEl.classList.add('selected')
370
+ if (d.getTime() === this.selectedRangeDates[0].getTime()) {
371
+ dateEl.classList.add('first')
372
+ }
373
+ if (d.getTime() === this.selectedRangeDates[this.selectedRangeDates.length - 1].getTime()) {
374
+ dateEl.classList.add('last')
375
+ }
376
+ }
377
+ }
378
+ }
379
+ open (options, override = false) {
380
+ const maskEl = document.getElementById(`${this.elementId}_mask`)
381
+ const contentEl = document.getElementById(`${this.elementId}_content`)
382
+ maskEl.classList.add('active')
383
+ contentEl.classList.add('active')
384
+ this.priorSelectedDates = [...this.selectedRangeDates]
385
+ this.priorSelectedRange = this.selectedRange
386
+ this.scrollRangeIntoView()
387
+ }
388
+ render (disabledDates) {
389
+ if (!this.elementId) {
390
+ console.log('No element Id provided for Websy Loading Dialog')
391
+ return
392
+ }
393
+ const el = document.getElementById(`${this.elementId}_datelist`)
394
+ if (el && disabledDates) {
395
+ el.innerHTML = this.renderDates(disabledDates)
396
+ }
397
+ const rangeEl = document.getElementById(`${this.elementId}_rangelist`)
398
+ if (rangeEl && disabledDates) {
399
+ rangeEl.innerHTML = this.renderRanges()
400
+ }
401
+ this.highlightRange()
402
+ }
403
+ renderDates (disabledDates) {
404
+ let disabled = []
405
+ this.validDates = []
406
+ if (disabledDates) {
407
+ disabled = disabledDates.map(d => d.getTime())
408
+ }
409
+ // first disabled all of the ranges
410
+ this.options.ranges.forEach(r => (r.disabled = true))
411
+ let daysDiff = Math.ceil((this.options.maxAllowedDate.getTime() - this.options.minAllowedDate.getTime()) / this.oneDay) + 1
412
+ let months = {}
413
+ for (let i = 0; i < daysDiff; i++) {
414
+ let d = this.floorDate(new Date(this.options.minAllowedDate.getTime() + (i * this.oneDay)))
415
+ let monthYear = `${this.options.monthMap[d.getMonth()]} ${d.getFullYear()}`
416
+ if (!months[monthYear]) {
417
+ months[monthYear] = []
418
+ }
419
+ if (disabled.indexOf(d.getTime()) === -1) {
420
+ this.validDates.push(d.getTime())
421
+ }
422
+ months[monthYear].push({date: d, dayOfMonth: d.getDate(), dayOfWeek: d.getDay(), id: d.getTime(), disabled: disabled.indexOf(d.getTime()) !== -1})
423
+ }
424
+ // check each range to see if it can be enabled
425
+ for (let i = 0; i < this.options.ranges.length; i++) {
426
+ const r = this.options.ranges[i]
427
+ // check the first date
428
+ if (this.validDates.indexOf(r.range[0].getTime()) !== -1) {
429
+ r.disabled = false
430
+ }
431
+ else if (r.range[1]) {
432
+ // check the last date
433
+ if (this.validDates.indexOf(r.range[1].getTime()) !== -1) {
434
+ r.disabled = false
435
+ }
436
+ else {
437
+ // check the full range until a match is found
438
+ for (let i = r.range[0].getTime(); i <= r.range[1].getTime(); i += this.oneDay) {
439
+ let testDate = this.floorDate(new Date(i))
440
+ if (this.validDates.indexOf(testDate.getTime()) !== -1) {
441
+ r.disabled = false
442
+ break
443
+ }
444
+ }
445
+ }
446
+ }
447
+ }
448
+ let html = ''
449
+ html += `
450
+ <ul class='websy-dp-days-header'>
451
+ `
452
+ html += this.options.daysOfWeek.map(d => `<li>${d}</li>`).join('')
453
+ html += `
454
+ </ul>
455
+ <div id='${this.elementId}_dateList' class='websy-dp-date-list'>
456
+ `
457
+ for (let key in months) {
458
+ html += `
459
+ <div class='websy-dp-month-container'>
460
+ <span id='${key.replace(/\s/g, '_')}'>${key}</span>
461
+ <ul>
462
+ `
463
+ if (months[key][0].dayOfWeek > 0) {
464
+ let paddedDays = []
465
+ for (let i = 0; i < months[key][0].dayOfWeek; i++) {
466
+ paddedDays.push(`<li>&nbsp;</li>`)
467
+ }
468
+ html += paddedDays.join('')
469
+ }
470
+ html += months[key].map(d => `<li id='${d.id}_date' class='websy-dp-date ${d.disabled === true ? 'websy-disabled-date' : ''}'>${d.dayOfMonth}</li>`).join('')
471
+ html += `
472
+ </ul>
473
+ </div>
474
+ `
475
+ }
476
+ html += '</div>'
477
+ return html
478
+ }
479
+ renderRanges () {
480
+ return this.options.ranges.map((r, i) => `
481
+ <li data-index='${i}' class='websy-date-picker-range ${i === this.selectedRange ? 'active' : ''} ${r.disabled === true ? 'websy-disabled-range' : ''}'>${r.label}</li>
482
+ `).join('')
483
+ }
484
+ scrollRangeIntoView () {
485
+ if (this.selectedRangeDates[0]) {
486
+ const el = document.getElementById(`${this.selectedRangeDates[0].getTime()}_date`)
487
+ const parentEl = document.getElementById(`${this.elementId}_dateList`)
488
+ if (el && parentEl) {
489
+ parentEl.scrollTo(0, el.offsetTop)
490
+ }
491
+ }
492
+ }
493
+ selectDate (timestamp) {
494
+ if (this.currentselection.length === 0) {
495
+ this.currentselection.push(timestamp)
496
+ }
497
+ else {
498
+ if (timestamp > this.currentselection[0]) {
499
+ this.currentselection.push(timestamp)
500
+ }
501
+ else {
502
+ this.currentselection.splice(0, 0, timestamp)
503
+ }
504
+ }
505
+ this.selectedRangeDates = [new Date(this.currentselection[0]), new Date(this.currentselection[1] || this.currentselection[0])]
506
+ if (this.currentselection.length === 2) {
507
+ this.currentselection = []
508
+ }
509
+ this.selectedRange = -1
510
+ this.highlightRange()
511
+ }
512
+ selectRange (index) {
513
+ if (this.options.ranges[index]) {
514
+ this.selectedRangeDates = [...this.options.ranges[index].range]
515
+ this.selectedRange = +index
516
+ this.highlightRange()
517
+ this.close(true)
518
+ }
519
+ }
520
+ selectCustomRange (range) {
521
+ this.selectedRange = -1
522
+ this.selectedRangeDates = range
523
+ this.highlightRange()
524
+ this.updateRange()
525
+ }
526
+ setDateBounds (range) {
527
+ if (this.options.ranges[0].label === 'All Dates') {
528
+ this.options.ranges[0].range = [range[0], range[1] || range[0]]
529
+ }
530
+ this.options.minAllowedDate = range[0]
531
+ this.options.maxAllowedDate = range[1] || range[0]
532
+ }
533
+ updateRange () {
534
+ let range
535
+ if (this.selectedRange === -1) {
536
+ let start = this.selectedRangeDates[0].toLocaleDateString()
537
+ let end = ''
538
+ if (this.selectedRangeDates[1] && (this.selectedRangeDates[0].getTime() !== this.selectedRangeDates[1].getTime())) {
539
+ end = ` - ${this.selectedRangeDates[1].toLocaleDateString()}`
540
+ }
541
+ range = { label: `${start}${end}` }
542
+ }
543
+ else {
544
+ range = this.options.ranges[this.selectedRange]
545
+ }
546
+ const el = document.getElementById(this.elementId)
547
+ const labelEl = document.getElementById(`${this.elementId}_selectedRange`)
548
+ const rangeEls = el.querySelectorAll(`.websy-date-picker-range`)
549
+ for (let i = 0; i < rangeEls.length; i++) {
550
+ rangeEls[i].classList.remove('active')
551
+ if (i === this.selectedRange) {
552
+ rangeEls[i].classList.add('active')
553
+ }
554
+ }
555
+ if (labelEl) {
556
+ labelEl.innerHTML = range.label
557
+ }
558
+ }
559
+ }
560
+
561
+ Date.prototype.floor = function () {
562
+ return new Date(`${this.getMonth() + 1}/${this.getDate()}/${this.getFullYear()}`)
563
+ }
564
+
565
+ /* global WebsyUtils */
566
+ class WebsyDropdown {
24
567
  constructor (elementId, options) {
25
- this.DEFAULTS = {
26
- buttons: []
568
+ const DEFAULTS = {
569
+ multiSelect: false,
570
+ multiValueDelimiter: ',',
571
+ allowClear: true,
572
+ style: 'plain',
573
+ items: [],
574
+ label: '',
575
+ disabled: false,
576
+ minSearchCharacters: 2,
577
+ showCompleteSelectedList: false,
578
+ closeAfterSelection: true
27
579
  }
28
- this.options = Object.assign({}, this.DEFAULTS, options)
580
+ this.options = Object.assign({}, DEFAULTS, options)
581
+ this.tooltipTimeoutFn = null
582
+ this._originalData = []
583
+ this.selectedItems = this.options.selectedItems || []
29
584
  if (!elementId) {
30
- console.log('No element Id provided for Websy Popup')
585
+ console.log('No element Id provided')
31
586
  return
32
587
  }
33
- this.closeOnOutsideClick = true
34
588
  const el = document.getElementById(elementId)
35
- this.elementId = elementId
36
- el.addEventListener('click', this.handleClick.bind(this))
589
+ if (el) {
590
+ this.elementId = elementId
591
+ el.addEventListener('click', this.handleClick.bind(this))
592
+ el.addEventListener('keyup', this.handleKeyUp.bind(this))
593
+ el.addEventListener('mouseout', this.handleMouseOut.bind(this))
594
+ el.addEventListener('mousemove', this.handleMouseMove.bind(this))
595
+ this.render()
596
+ }
597
+ else {
598
+ console.log('No element found with Id', elementId)
599
+ }
37
600
  }
38
- hide () {
39
- const el = document.getElementById(this.elementId)
40
- el.innerHTML = ''
601
+ set selections (d) {
602
+ this.selectedItems = d || []
41
603
  }
42
- handleClick (event) {
43
- if (event.target.classList.contains('websy-btn')) {
44
- const buttonIndex = event.target.getAttribute('data-index')
45
- const buttonInfo = this.options.buttons[buttonIndex]
46
- if (buttonInfo && buttonInfo.fn) {
47
- if (typeof this.options.collectData !== 'undefined') {
48
- const collectEl = document.getElementById(`${this.elementId}_collect`)
49
- if (collectEl) {
50
- buttonInfo.collectedData = collectEl.value
51
- }
52
- }
53
- if (buttonInfo.preventClose !== true) {
54
- this.hide()
55
- }
56
- buttonInfo.fn(buttonInfo)
57
- }
58
- else if (buttonInfo && buttonInfo.preventClose !== true) {
59
- this.hide()
604
+ set data (d) {
605
+ this.options.items = d || []
606
+ const el = document.getElementById(`${this.elementId}_items`)
607
+ if (el.childElementCount === 0) {
608
+ this.render()
609
+ }
610
+ else {
611
+ if (this.options.items.length === 0) {
612
+ this.options.items = [{label: this.options.noItemsText || 'No Items'}]
60
613
  }
614
+ this.renderItems()
615
+ }
616
+ }
617
+ get data () {
618
+ return this.options.items
619
+ }
620
+ clearSelected () {
621
+ this.selectedItems = []
622
+ this.updateHeader()
623
+ if (this.options.onClearSelected) {
624
+ this.options.onClearSelected()
61
625
  }
62
- else if (this.closeOnOutsideClick === true) {
63
- this.hide()
626
+ }
627
+ close () {
628
+ const maskEl = document.getElementById(`${this.elementId}_mask`)
629
+ const contentEl = document.getElementById(`${this.elementId}_content`)
630
+ maskEl.classList.remove('active')
631
+ contentEl.classList.remove('active')
632
+ contentEl.classList.remove('on-top')
633
+ const searchEl = document.getElementById(`${this.elementId}_search`)
634
+ if (searchEl) {
635
+ if (searchEl.value.length > 0 && this.options.onCancelSearch) {
636
+ this.options.onCancelSearch('')
637
+ searchEl.value = ''
638
+ }
64
639
  }
65
640
  }
66
- render () {
67
- if (!this.elementId) {
68
- console.log('No element Id provided for Websy Popup')
641
+ handleClick (event) {
642
+ if (this.options.disabled === true) {
69
643
  return
70
644
  }
71
- const el = document.getElementById(this.elementId)
72
- let html = ''
73
- if (this.options.mask === true) {
74
- html += `<div class='websy-mask'></div>`
645
+ if (event.target.classList.contains('websy-dropdown-header')) {
646
+ this.open()
75
647
  }
76
- html += `
77
- <div class='websy-popup-dialog-container'>
78
- <div class='websy-popup-dialog'>
79
- `
80
- if (this.options.title) {
81
- html += `<h1>${this.options.title}</h1>`
648
+ else if (event.target.classList.contains('websy-dropdown-mask')) {
649
+ this.close()
82
650
  }
83
- if (this.options.message) {
84
- html += `<p>${this.options.message}</p>`
651
+ else if (event.target.classList.contains('websy-dropdown-item')) {
652
+ const index = event.target.getAttribute('data-index')
653
+ this.updateSelected(+index)
85
654
  }
86
- if (typeof this.options.collectData !== 'undefined') {
87
- html += `
88
- <div>
89
- <input id="${this.elementId}_collect" class="websy-input" value="${typeof this.options.collectData === 'boolean' ? '' : this.options.collectData}" placeholder="${this.options.collectPlaceholder || ''}">
90
- </div>
91
- `
655
+ else if (event.target.classList.contains('clear')) {
656
+ this.clearSelected()
92
657
  }
93
- this.closeOnOutsideClick = true
94
- if (this.options.buttons) {
95
- if (this.options.allowCloseOnOutsideClick !== true) {
96
- this.closeOnOutsideClick = false
97
- }
98
- html += `<div class='websy-popup-button-panel'>`
99
- for (let i = 0; i < this.options.buttons.length; i++) {
100
- html += `
101
- <button class='websy-btn ${(this.options.buttons[i].classes || []).join(' ')}' data-index='${i}'>
102
- ${this.options.buttons[i].label}
103
- </button>
104
- `
658
+ }
659
+ handleKeyUp (event) {
660
+ if (event.target.classList.contains('websy-dropdown-search')) {
661
+ if (this._originalData.length === 0) {
662
+ this._originalData = [...this.options.items]
663
+ }
664
+ if (event.target.value.length >= this.options.minSearchCharacters) {
665
+ if (event.key === 'Enter') {
666
+ if (this.options.onConfirmSearch) {
667
+ this.options.onConfirmSearch(event.target.value)
668
+ event.target.value = ''
669
+ }
670
+ }
671
+ else if (event.key === 'Escape') {
672
+ if (this.options.onCancelSearch) {
673
+ this.options.onCancelSearch(event.target.value)
674
+ event.target.value = ''
675
+ }
676
+ else {
677
+ this.data = this._originalData
678
+ this._originalData = []
679
+ }
680
+ }
681
+ else {
682
+ if (this.options.onSearch) {
683
+ this.options.onSearch(event.target.value)
684
+ }
685
+ else {
686
+ this.data = this._originalData.filter(d => d.label.toLowerCase().indexOf(event.target.value.toLowerCase()) !== -1)
687
+ }
688
+ }
689
+ }
690
+ else {
691
+ if (this.options.onCancelSearch) {
692
+ this.options.onCancelSearch(event.target.value)
693
+ }
105
694
  }
106
- html += `</div>`
107
695
  }
108
- html += `
109
- </div>
110
- </div>
111
- `
112
- el.innerHTML = html
113
696
  }
114
- show (options) {
115
- if (options) {
116
- this.options = Object.assign({}, this.DEFAULTS, options)
697
+ handleMouseMove (event) {
698
+ if (this.tooltipTimeoutFn) {
699
+ event.target.classList.remove('websy-delayed')
700
+ event.target.classList.remove('websy-delayed-info')
701
+ if (event.target.children[1]) {
702
+ event.target.children[1].classList.remove('websy-delayed-info')
703
+ }
704
+ clearTimeout(this.tooltipTimeoutFn)
705
+ }
706
+ if (event.target.tagName === 'LI') {
707
+ this.tooltipTimeoutFn = setTimeout(() => {
708
+ event.target.classList.add('websy-delayed')
709
+ }, 500)
710
+ }
711
+ if (event.target.classList.contains('websy-dropdown-header') && event.target.children[1]) {
712
+ this.tooltipTimeoutFn = setTimeout(() => {
713
+ event.target.children[1].classList.add('websy-delayed-info')
714
+ }, 500)
117
715
  }
118
- this.render()
119
716
  }
120
- }
121
-
122
- class WebsyLoadingDialog {
123
- constructor (elementId, options) {
124
- this.options = Object.assign({}, options)
125
- if (!elementId) {
126
- console.log('No element Id provided')
127
- return
717
+ handleMouseOut (event) {
718
+ if (this.tooltipTimeoutFn) {
719
+ event.target.classList.remove('websy-delayed')
720
+ event.target.classList.remove('websy-delayed-info')
721
+ if (event.target.children[1]) {
722
+ event.target.children[1].classList.remove('websy-delayed-info')
723
+ }
724
+ clearTimeout(this.tooltipTimeoutFn)
128
725
  }
129
- this.elementId = elementId
130
726
  }
131
- hide () {
132
- const el = document.getElementById(this.elementId)
133
- el.classList.remove('loading')
134
- el.innerHTML = ''
727
+ open (options, override = false) {
728
+ const maskEl = document.getElementById(`${this.elementId}_mask`)
729
+ const contentEl = document.getElementById(`${this.elementId}_content`)
730
+ maskEl.classList.add('active')
731
+ contentEl.classList.add('active')
732
+ if (WebsyUtils.getElementPos(contentEl).bottom > window.innerHeight) {
733
+ contentEl.classList.add('on-top')
734
+ }
735
+ if (this.options.disableSearch !== true) {
736
+ const searchEl = document.getElementById(`${this.elementId}_search`)
737
+ if (searchEl) {
738
+ searchEl.focus()
739
+ }
740
+ }
135
741
  }
136
742
  render () {
137
743
  if (!this.elementId) {
138
- console.log('No element Id provided for Websy Loading Dialog')
744
+ console.log('No element Id provided for Websy Dropdown')
139
745
  return
140
746
  }
141
747
  const el = document.getElementById(this.elementId)
748
+ const headerLabel = this.selectedItems.map(s => this.options.items[s].label || this.options.items[s].value).join(this.options.multiValueDelimiter)
749
+ const headerValue = this.selectedItems.map(s => this.options.items[s].value || this.options.items[s].label).join(this.options.multiValueDelimiter)
142
750
  let html = `
143
- <div class='websy-loading-container ${(this.options.classes || []).join(' ')}'>
144
- <div class='websy-ripple'>
145
- <div></div>
146
- <div></div>
147
- </div>
148
- <h4>${this.options.title || 'Loading...'}</h4>
149
- `
150
- if (this.options.messages) {
151
- for (let i = 0; i < this.options.messages.length; i++) {
152
- html += `<p>${this.options.messages[i]}</p>`
153
- }
154
- }
751
+ <div class='websy-dropdown-container ${this.options.disabled ? 'disabled' : ''} ${this.options.disableSearch !== true ? 'with-search' : ''}'>
752
+ <div id='${this.elementId}_header' class='websy-dropdown-header ${this.selectedItems.length === 1 ? 'one-selected' : ''} ${this.options.allowClear === true ? 'allow-clear' : ''}'>
753
+ <span id='${this.elementId}_headerLabel' class='websy-dropdown-header-label'>${this.options.label}</span>
754
+ <span data-info='${headerLabel}' class='websy-dropdown-header-value' id='${this.elementId}_selectedItems'>${headerLabel}</span>
755
+ <input class='dropdown-input' id='${this.elementId}_input' name='${this.options.field || this.options.label}' value='${headerValue}'>
756
+ <svg class='arrow' xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M23.677 18.52c.914 1.523-.183 3.472-1.967 3.472h-19.414c-1.784 0-2.881-1.949-1.967-3.472l9.709-16.18c.891-1.483 3.041-1.48 3.93 0l9.709 16.18z"/></svg>
757
+ `
758
+ if (this.options.allowClear === true) {
759
+ html += `
760
+ <svg class='clear' xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 512 512"><title>ionicons-v5-l</title><line x1="368" y1="368" x2="144" y2="144" style="fill:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:32px"/><line x1="368" y1="144" x2="144" y2="368" style="fill:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:32px"/></svg>
761
+ `
762
+ }
763
+ html += `
764
+ </div>
765
+ <div id='${this.elementId}_mask' class='websy-dropdown-mask'></div>
766
+ <div id='${this.elementId}_content' class='websy-dropdown-content'>
767
+ `
768
+ if (this.options.disableSearch !== true) {
769
+ html += `
770
+ <input id='${this.elementId}_search' class='websy-dropdown-search' placeholder='${this.options.searchPlaceholder || 'Search'}'>
771
+ `
772
+ }
155
773
  html += `
156
- </div>
774
+ <div class='websy-dropdown-items'>
775
+ <ul id='${this.elementId}_items'>
776
+ </ul>
777
+ </div><!--
778
+ --><div class='websy-dropdown-custom'></div>
779
+ </div>
780
+ </div>
157
781
  `
158
- el.classList.add('loading')
159
782
  el.innerHTML = html
160
- }
161
- show (options, override = false) {
162
- if (options) {
163
- if (override === true) {
164
- this.options = Object.assign({}, options)
165
- }
166
- else {
167
- this.options = Object.assign({}, this.options, options)
168
- }
169
- }
170
- this.render()
783
+ this.renderItems()
171
784
  }
172
- }
173
-
174
- class WebsyNavigationMenu {
175
- constructor (elementId, options) {
176
- this.options = Object.assign({}, {
177
- collapsable: false,
178
- orientation: 'horizontal',
179
- parentMap: {},
180
- childIndentation: 10
181
- }, options)
182
- if (!elementId) {
183
- console.log('No element Id provided for Websy Menu')
184
- return
185
- }
186
- const el = document.getElementById(elementId)
785
+ renderItems () {
786
+ let html = this.options.items.map((r, i) => `
787
+ <li data-index='${i}' class='websy-dropdown-item ${(r.classes || []).join(' ')} ${this.selectedItems.indexOf(i) !== -1 ? 'active' : ''}'>${r.label}</li>
788
+ `).join('')
789
+ const el = document.getElementById(`${this.elementId}_items`)
187
790
  if (el) {
188
- this.elementId = elementId
189
- el.classList.add(`websy-${this.options.orientation}-list-container`)
190
- el.classList.add('websy-menu')
191
- if (this.options.classes) {
192
- this.options.classes.forEach(c => el.classList.add(c))
193
- }
194
- el.addEventListener('click', this.handleClick.bind(this))
195
- this.render()
196
- }
197
- }
198
- handleClick (event) {
199
- if (event.target.classList.contains('websy-menu-icon') ||
200
- event.target.nodeName === 'svg' ||
201
- event.target.nodeName === 'rect') {
202
- this.toggleMobileMenu()
203
- }
204
- if (event.target.classList.contains('trigger-item') &&
205
- event.target.classList.contains('websy-menu-header')) {
206
- this.toggleMobileMenu('remove')
791
+ el.innerHTML = html
207
792
  }
208
- if (event.target.classList.contains('websy-menu-mask')) {
209
- this.toggleMobileMenu()
793
+ let item
794
+ if (this.selectedItems.length === 1) {
795
+ item = this.options.items[this.selectedItems[0]]
210
796
  }
797
+ this.updateHeader(item)
211
798
  }
212
- normaliseString (text) {
213
- return text.replace(/-/g, '').replace(/\s/g, '_')
214
- }
215
- render () {
799
+ updateHeader (item) {
216
800
  const el = document.getElementById(this.elementId)
217
- if (el) {
218
- let html = ``
219
- if (this.options.collapsable === true) {
220
- html += `
221
- <div id='${this.elementId}_menuIcon' class='websy-menu-icon'>
222
- <svg viewbox="0 0 40 40" width="30" height="40">
223
- <rect x="0" y="0" width="30" height="4" rx="2"></rect>
224
- <rect x="0" y="12" width="30" height="4" rx="2"></rect>
225
- <rect x="0" y="24" width="30" height="4" rx="2"></rect>
226
- </svg>
227
- </div>
228
- `
801
+ const headerEl = document.getElementById(`${this.elementId}_header`)
802
+ const headerLabelEl = document.getElementById(`${this.elementId}_headerLabel`)
803
+ const labelEl = document.getElementById(`${this.elementId}_selectedItems`)
804
+ const inputEl = document.getElementById(`${this.elementId}_input`)
805
+ const itemEls = el.querySelectorAll(`.websy-dropdown-item`)
806
+ for (let i = 0; i < itemEls.length; i++) {
807
+ itemEls[i].classList.remove('active')
808
+ if (this.selectedItems.indexOf(i) !== -1) {
809
+ itemEls[i].classList.add('active')
229
810
  }
230
- if (this.options.logo) {
231
- html += `
232
- <div
233
- class='logo ${(this.options.logo.classes && this.options.logo.classes.join(' ')) || ''}'
234
- ${this.options.logo.attributes && this.options.logo.attributes.join(' ')}
235
- >
236
- <img src='${this.options.logo.url}'></img>
237
- </div>
238
- <div id='${this.elementId}_mask' class='websy-menu-mask'></div>
239
- <div id="${this.elementId}_menuContainer" class="websy-menu-block-container">
240
- `
811
+ }
812
+ if (headerLabelEl) {
813
+ headerLabelEl.innerHTML = this.options.label
814
+ }
815
+ if (headerEl) {
816
+ headerEl.classList.remove('one-selected')
817
+ headerEl.classList.remove('multi-selected')
818
+ if (this.selectedItems.length === 1) {
819
+ headerEl.classList.add('one-selected')
241
820
  }
242
- html += this.renderBlock(this.options.items, 'main', 1)
243
- html += `</div>`
244
- el.innerHTML = html
245
- if (this.options.navigator) {
246
- this.options.navigator.registerElements(el)
821
+ else if (this.selectedItems.length > 1) {
822
+ if (this.options.showCompleteSelectedList === true) {
823
+ headerEl.classList.add('one-selected')
824
+ }
825
+ else {
826
+ headerEl.classList.add('multi-selected')
827
+ }
247
828
  }
248
829
  }
249
- }
250
- renderBlock (items, block, level) {
251
- let html = `
252
- <ul class='websy-${this.options.orientation}-list ${(block !== 'main' ? 'websy-menu-collapsed' : '')}' id='${this.elementId}_${block}_list'
253
- `
254
- if (block !== 'main') {
255
- html += ` data-collapsed='${(block !== 'main' ? 'true' : 'false')}'`
256
- }
257
- html += '>'
258
- for (let i = 0; i < items.length; i++) {
259
- // update the block to the current item
260
- let selected = '' // items[i].default === true ? 'selected' : ''
261
- let active = items[i].default === true ? 'active' : ''
262
- let currentBlock = this.normaliseString(items[i].text)
263
- let blockId = items[i].id || `${this.elementId}_${currentBlock}_label`
264
- html += `
265
- <li class='websy-${this.options.orientation}-list-item'>
266
- <div class='websy-menu-header ${items[i].classes && items[i].classes.join(' ')} ${selected} ${active}'
267
- id='${blockId}'
268
- data-id='${currentBlock}'
269
- data-menu-id='${this.elementId}_${currentBlock}_list'
270
- data-popout-id='${level > 1 ? block : currentBlock}'
271
- data-text='${items[i].text}'
272
- style='padding-left: ${level * this.options.childIndentation}px'
273
- ${items[i].attributes && items[i].attributes.join(' ')}
274
- >
275
- `
276
- if (this.options.orientation === 'horizontal') {
277
- html += items[i].text
830
+ if (labelEl) {
831
+ if (this.selectedItems.length === 1) {
832
+ labelEl.innerHTML = item.label
833
+ labelEl.setAttribute('data-info', item.label)
834
+ inputEl.value = item.value
278
835
  }
279
- html += `
280
- <span class='selected-bar'></span>
281
- <span class='active-square'></span>
282
- <span class='${items[i].items && items[i].items.length > 0 ? 'menu-carat' : ''}'></span>
283
- `
284
- if (this.options.orientation === 'vertical') {
285
- html += `
286
- &nbsp;
287
- `
288
- }
289
- html += `
290
- </div>
291
- `
292
- if (items[i].items) {
293
- html += this.renderBlock(items[i].items, currentBlock, level + 1)
836
+ else if (this.selectedItems.length > 1) {
837
+ if (this.options.showCompleteSelectedList === true) {
838
+ let selectedLabels = this.selectedItems.map(s => this.options.items[s].label || this.options.items[s].value).join(this.options.multiValueDelimiter)
839
+ let selectedValues = this.selectedItems.map(s => this.options.items[s].value || this.options.items[s].label).join(this.options.multiValueDelimiter)
840
+ labelEl.innerHTML = selectedLabels
841
+ labelEl.setAttribute('data-info', selectedLabels)
842
+ inputEl.value = selectedValues
843
+ }
844
+ else {
845
+ let selectedValues = this.selectedItems.map(s => this.options.items[s].value || this.options.items[s].label).join(this.options.multiValueDelimiter)
846
+ labelEl.innerHTML = `${this.selectedItems.length} selected`
847
+ labelEl.setAttribute('data-info', '')
848
+ inputEl.value = selectedValues
849
+ }
294
850
  }
295
- // map the item to it's parent
296
- if (block && block !== 'main') {
297
- if (!this.options.parentMap[currentBlock]) {
298
- this.options.parentMap[currentBlock] = block
299
- }
851
+ else {
852
+ labelEl.innerHTML = ''
853
+ labelEl.setAttribute('data-info', '')
854
+ inputEl.value = ''
300
855
  }
301
- html += `
302
- </li>
303
- `
304
856
  }
305
- html += `</ul>`
306
- return html
307
- }
308
- toggleOpen () {
309
-
310
857
  }
311
- toggleMobileMenu (method) {
312
- if (typeof method === 'undefined') {
313
- method = 'toggle'
858
+ updateSelected (index) {
859
+ if (typeof index !== 'undefined' && index !== null) {
860
+ let pos = this.selectedItems.indexOf(index)
861
+ if (pos !== -1) {
862
+ this.selectedItems.splice(pos, 1)
863
+ }
864
+ if (this.options.multiSelect === false) {
865
+ this.selectedItems = [index]
866
+ }
867
+ else {
868
+ this.selectedItems.push(index)
869
+ }
314
870
  }
315
- const el = document.getElementById(`${this.elementId}`)
316
- if (el) {
317
- el.classList[method]('open')
318
- }
319
- if (this.options.onToggle) {
320
- this.options.onToggle(method)
871
+ const item = this.options.items[index]
872
+ this.updateHeader(item)
873
+ if (item && this.options.onItemSelected) {
874
+ this.options.onItemSelected(item, this.selectedItems, this.options.items)
321
875
  }
876
+ if (this.options.closeAfterSelection === true) {
877
+ this.close()
878
+ }
322
879
  }
323
880
  }
324
881
 
@@ -564,691 +1121,535 @@ class WebsyForm {
564
1121
  }
565
1122
  }
566
1123
  validateRecaptcha (token) {
567
- this.recaptchaValue = token
568
- }
569
- }
570
-
571
- class WebsyDatePicker {
572
- constructor (elementId, options) {
573
- this.oneDay = 1000 * 60 * 60 * 24
574
- this.currentselection = []
575
- this.validDates = []
576
- const DEFAULTS = {
577
- defaultRange: 0,
578
- minAllowedDate: this.floorDate(new Date(new Date((new Date().setFullYear(new Date().getFullYear() - 1))).setDate(1))),
579
- maxAllowedDate: this.floorDate(new Date((new Date()))),
580
- daysOfWeek: ['S', 'M', 'T', 'W', 'T', 'F', 'S'],
581
- monthMap: {
582
- 0: 'Jan',
583
- 1: 'Feb',
584
- 2: 'Mar',
585
- 3: 'Apr',
586
- 4: 'May',
587
- 5: 'Jun',
588
- 6: 'Jul',
589
- 7: 'Aug',
590
- 8: 'Sep',
591
- 9: 'Oct',
592
- 10: 'Nov',
593
- 11: 'Dec'
594
- },
595
- ranges: []
596
- }
597
- DEFAULTS.ranges = [
598
- {
599
- label: 'All Dates',
600
- range: [DEFAULTS.minAllowedDate, DEFAULTS.maxAllowedDate]
601
- },
602
- {
603
- label: 'Today',
604
- range: [this.floorDate(new Date())]
605
- },
606
- {
607
- label: 'Yesterday',
608
- range: [this.floorDate(new Date().setDate(new Date().getDate() - 1))]
609
- },
610
- {
611
- label: 'Last 7 Days',
612
- range: [this.floorDate(new Date().setDate(new Date().getDate() - 6)), this.floorDate(new Date())]
613
- },
614
- {
615
- label: 'This Month',
616
- range: [this.floorDate(new Date().setDate(1)), this.floorDate(new Date(new Date().setDate(1)).setMonth(new Date().getMonth() + 1) - this.oneDay)]
617
- },
618
- {
619
- label: 'Last Month',
620
- range: [this.floorDate(new Date(new Date().setDate(1)).setMonth(new Date().getMonth() - 1)), this.floorDate(new Date(new Date().setDate(1)).setMonth(new Date().getMonth()) - this.oneDay)]
621
- },
622
- {
623
- label: 'This Year',
624
- range: [this.floorDate(new Date(`1/1/${new Date().getFullYear()}`)), this.floorDate(new Date(`12/31/${new Date().getFullYear()}`))]
625
- },
626
- {
627
- label: 'Last Year',
628
- range: [this.floorDate(new Date(`1/1/${new Date().getFullYear() - 1}`)), this.floorDate(new Date(`12/31/${new Date().getFullYear() - 1}`))]
629
- }
630
- ]
631
- this.options = Object.assign({}, DEFAULTS, options)
632
- this.selectedRange = this.options.defaultRange || 0
633
- this.selectedRangeDates = [...this.options.ranges[this.options.defaultRange || 0].range]
634
- this.priorSelectedDates = null
635
- if (!elementId) {
636
- console.log('No element Id provided')
637
- return
638
- }
639
- const el = document.getElementById(elementId)
640
- if (el) {
641
- this.elementId = elementId
642
- el.addEventListener('click', this.handleClick.bind(this))
643
- let html = `
644
- <div class='websy-date-picker-container'>
645
- <span class='websy-dropdown-header-label'>${this.options.label || 'Date'}</span>
646
- <div class='websy-date-picker-header'>
647
- <span id='${this.elementId}_selectedRange'>${this.options.ranges[this.selectedRange].label}</span>
648
- <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M23.677 18.52c.914 1.523-.183 3.472-1.967 3.472h-19.414c-1.784 0-2.881-1.949-1.967-3.472l9.709-16.18c.891-1.483 3.041-1.48 3.93 0l9.709 16.18z"/></svg>
649
- </div>
650
- <div id='${this.elementId}_mask' class='websy-date-picker-mask'></div>
651
- <div id='${this.elementId}_content' class='websy-date-picker-content'>
652
- <div class='websy-date-picker-ranges'>
653
- <ul id='${this.elementId}_rangelist'>
654
- ${this.renderRanges()}
655
- </ul>
656
- </div><!--
657
- --><div id='${this.elementId}_datelist' class='websy-date-picker-custom'>${this.renderDates()}</div>
658
- <div class='websy-dp-button-container'>
659
- <button class='websy-btn websy-dp-cancel'>Cancel</button>
660
- <button class='websy-btn websy-dp-confirm'>Confirm</button>
661
- </div>
662
- </div>
663
- </div>
664
- `
665
- el.innerHTML = html
666
- this.render()
667
- }
668
- else {
669
- console.log('No element found with Id', elementId)
670
- }
671
- }
672
- close (confirm) {
673
- const maskEl = document.getElementById(`${this.elementId}_mask`)
674
- const contentEl = document.getElementById(`${this.elementId}_content`)
675
- maskEl.classList.remove('active')
676
- contentEl.classList.remove('active')
677
- if (confirm === true) {
678
- if (this.options.onChange) {
679
- this.options.onChange(this.selectedRangeDates)
680
- }
681
- this.updateRange()
682
- }
683
- else {
684
- this.selectedRangeDates = [...this.priorSelectedDates]
685
- this.selectedRange = this.priorSelectedRange
686
- }
687
- }
688
- floorDate (d) {
689
- if (typeof d === 'number') {
690
- d = new Date(d)
691
- }
692
- return new Date(d.setHours(0, 0, 0, 0))
693
- }
694
- handleClick (event) {
695
- if (event.target.classList.contains('websy-date-picker-header')) {
696
- this.open()
697
- }
698
- else if (event.target.classList.contains('websy-date-picker-mask')) {
699
- this.close()
700
- }
701
- else if (event.target.classList.contains('websy-date-picker-range')) {
702
- if (event.target.classList.contains('websy-disabled-range')) {
703
- return
704
- }
705
- const index = event.target.getAttribute('data-index')
706
- this.selectRange(index)
707
- this.updateRange(index)
708
- }
709
- else if (event.target.classList.contains('websy-dp-date')) {
710
- if (event.target.classList.contains('websy-disabled-date')) {
711
- return
712
- }
713
- const timestamp = event.target.id.split('_')[0]
714
- this.selectDate(+timestamp)
715
- }
716
- else if (event.target.classList.contains('websy-dp-confirm')) {
717
- this.close(true)
718
- }
719
- else if (event.target.classList.contains('websy-dp-cancel')) {
720
- this.close()
721
- }
722
- }
723
- highlightRange () {
724
- const el = document.getElementById(`${this.elementId}_dateList`)
725
- const dateEls = el.querySelectorAll('.websy-dp-date')
726
- for (let i = 0; i < dateEls.length; i++) {
727
- dateEls[i].classList.remove('selected')
728
- dateEls[i].classList.remove('first')
729
- dateEls[i].classList.remove('last')
730
- }
731
- if (this.selectedRange === 0) {
732
- return
733
- }
734
- let daysDiff = Math.floor((this.selectedRangeDates[this.selectedRangeDates.length - 1].getTime() - this.selectedRangeDates[0].getTime()) / this.oneDay)
735
- if (this.selectedRangeDates[0].getMonth() !== this.selectedRangeDates[this.selectedRangeDates.length - 1].getMonth()) {
736
- daysDiff += 1
737
- }
738
- for (let i = 0; i < daysDiff + 1; i++) {
739
- let d = this.floorDate(new Date(this.selectedRangeDates[0].getTime() + (i * this.oneDay)))
740
- const dateEl = document.getElementById(`${d.getTime()}_date`)
741
- if (dateEl) {
742
- dateEl.classList.add('selected')
743
- if (d.getTime() === this.selectedRangeDates[0].getTime()) {
744
- dateEl.classList.add('first')
745
- }
746
- if (d.getTime() === this.selectedRangeDates[this.selectedRangeDates.length - 1].getTime()) {
747
- dateEl.classList.add('last')
748
- }
749
- }
1124
+ this.recaptchaValue = token
1125
+ }
1126
+ }
1127
+
1128
+ class WebsyLoadingDialog {
1129
+ constructor (elementId, options) {
1130
+ this.options = Object.assign({}, options)
1131
+ if (!elementId) {
1132
+ console.log('No element Id provided')
1133
+ return
750
1134
  }
1135
+ this.elementId = elementId
751
1136
  }
752
- open (options, override = false) {
753
- const maskEl = document.getElementById(`${this.elementId}_mask`)
754
- const contentEl = document.getElementById(`${this.elementId}_content`)
755
- maskEl.classList.add('active')
756
- contentEl.classList.add('active')
757
- this.priorSelectedDates = [...this.selectedRangeDates]
758
- this.priorSelectedRange = this.selectedRange
759
- this.scrollRangeIntoView()
1137
+ hide () {
1138
+ const el = document.getElementById(this.elementId)
1139
+ el.classList.remove('loading')
1140
+ el.innerHTML = ''
760
1141
  }
761
- render (disabledDates) {
1142
+ render () {
762
1143
  if (!this.elementId) {
763
1144
  console.log('No element Id provided for Websy Loading Dialog')
764
1145
  return
765
1146
  }
766
- const el = document.getElementById(`${this.elementId}_datelist`)
767
- if (el && disabledDates) {
768
- el.innerHTML = this.renderDates(disabledDates)
769
- }
770
- const rangeEl = document.getElementById(`${this.elementId}_rangelist`)
771
- if (rangeEl && disabledDates) {
772
- rangeEl.innerHTML = this.renderRanges()
1147
+ const el = document.getElementById(this.elementId)
1148
+ let html = `
1149
+ <div class='websy-loading-container ${(this.options.classes || []).join(' ')}'>
1150
+ <div class='websy-ripple'>
1151
+ <div></div>
1152
+ <div></div>
1153
+ </div>
1154
+ <h4>${this.options.title || 'Loading...'}</h4>
1155
+ `
1156
+ if (this.options.messages) {
1157
+ for (let i = 0; i < this.options.messages.length; i++) {
1158
+ html += `<p>${this.options.messages[i]}</p>`
1159
+ }
1160
+ }
1161
+ html += `
1162
+ </div>
1163
+ `
1164
+ el.classList.add('loading')
1165
+ el.innerHTML = html
1166
+ }
1167
+ show (options, override = false) {
1168
+ if (options) {
1169
+ if (override === true) {
1170
+ this.options = Object.assign({}, options)
1171
+ }
1172
+ else {
1173
+ this.options = Object.assign({}, this.options, options)
1174
+ }
773
1175
  }
774
- this.highlightRange()
1176
+ this.render()
775
1177
  }
776
- renderDates (disabledDates) {
777
- let disabled = []
778
- this.validDates = []
779
- if (disabledDates) {
780
- disabled = disabledDates.map(d => d.getTime())
781
- }
782
- // first disabled all of the ranges
783
- this.options.ranges.forEach(r => (r.disabled = true))
784
- let daysDiff = Math.ceil((this.options.maxAllowedDate.getTime() - this.options.minAllowedDate.getTime()) / this.oneDay) + 1
785
- let months = {}
786
- for (let i = 0; i < daysDiff; i++) {
787
- let d = this.floorDate(new Date(this.options.minAllowedDate.getTime() + (i * this.oneDay)))
788
- let monthYear = `${this.options.monthMap[d.getMonth()]} ${d.getFullYear()}`
789
- if (!months[monthYear]) {
790
- months[monthYear] = []
1178
+ }
1179
+
1180
+ /* global */
1181
+ class WebsyNavigationMenu {
1182
+ constructor (elementId, options) {
1183
+ this.options = Object.assign({}, {
1184
+ collapsible: false,
1185
+ orientation: 'horizontal',
1186
+ parentMap: {},
1187
+ childIndentation: 10,
1188
+ activeSymbol: 'none'
1189
+ }, options)
1190
+ if (!elementId) {
1191
+ console.log('No element Id provided for Websy Menu')
1192
+ return
1193
+ }
1194
+ const el = document.getElementById(elementId)
1195
+ if (el) {
1196
+ this.elementId = elementId
1197
+ this.lowestLevel = 0
1198
+ this.flatItems = []
1199
+ this.itemMap = {}
1200
+ this.flattenItems(0, this.options.items)
1201
+ console.log(this.flatItems)
1202
+ el.classList.add(`websy-${this.options.orientation}-list-container`)
1203
+ el.classList.add('websy-menu')
1204
+ if (this.options.align) {
1205
+ el.classList.add(`${this.options.align}-align`)
791
1206
  }
792
- if (disabled.indexOf(d.getTime()) === -1) {
793
- this.validDates.push(d.getTime())
1207
+ if (Array.isArray(this.options.classes)) {
1208
+ this.options.classes = this.options.classes.join(' ')
794
1209
  }
795
- months[monthYear].push({date: d, dayOfMonth: d.getDate(), dayOfWeek: d.getDay(), id: d.getTime(), disabled: disabled.indexOf(d.getTime()) !== -1})
796
- }
797
- // check each range to see if it can be enabled
798
- for (let i = 0; i < this.options.ranges.length; i++) {
799
- const r = this.options.ranges[i]
800
- // check the first date
801
- if (this.validDates.indexOf(r.range[0].getTime()) !== -1) {
802
- r.disabled = false
1210
+ if (this.options.classes) {
1211
+ this.options.classes.split(' ').forEach(c => el.classList.add(c))
803
1212
  }
804
- else if (r.range[1]) {
805
- // check the last date
806
- if (this.validDates.indexOf(r.range[1].getTime()) !== -1) {
807
- r.disabled = false
808
- }
809
- else {
810
- // check the full range until a match is found
811
- for (let i = r.range[0].getTime(); i <= r.range[1].getTime(); i += this.oneDay) {
812
- let testDate = this.floorDate(new Date(i))
813
- if (this.validDates.indexOf(testDate.getTime()) !== -1) {
814
- r.disabled = false
815
- break
816
- }
817
- }
818
- }
819
- }
1213
+ el.addEventListener('click', this.handleClick.bind(this))
1214
+ this.render()
820
1215
  }
821
- let html = ''
822
- html += `
823
- <ul class='websy-dp-days-header'>
824
- `
825
- html += this.options.daysOfWeek.map(d => `<li>${d}</li>`).join('')
826
- html += `
827
- </ul>
828
- <div id='${this.elementId}_dateList' class='websy-dp-date-list'>
829
- `
830
- for (let key in months) {
831
- html += `
832
- <div class='websy-dp-month-container'>
833
- <span id='${key.replace(/\s/g, '_')}'>${key}</span>
834
- <ul>
835
- `
836
- if (months[key][0].dayOfWeek > 0) {
837
- let paddedDays = []
838
- for (let i = 0; i < months[key][0].dayOfWeek; i++) {
839
- paddedDays.push(`<li>&nbsp;</li>`)
840
- }
841
- html += paddedDays.join('')
1216
+ }
1217
+ flattenItems (index, items, level = 0) {
1218
+ if (items[index]) {
1219
+ this.lowestLevel = Math.max(level, this.lowestLevel)
1220
+ items[index].id = items[index].id || `${this.elementId}_${this.normaliseString(items[index].text)}`
1221
+ this.itemMap[items[index].id] = items[index]
1222
+ items[index].level = level
1223
+ this.flatItems.push(items[index])
1224
+ if (items[index].items) {
1225
+ this.flattenItems(0, items[index].items, level + 1)
842
1226
  }
843
- html += months[key].map(d => `<li id='${d.id}_date' class='websy-dp-date ${d.disabled === true ? 'websy-disabled-date' : ''}'>${d.dayOfMonth}</li>`).join('')
844
- html += `
845
- </ul>
846
- </div>
847
- `
1227
+ this.flattenItems(++index, items, level)
1228
+ }
1229
+ }
1230
+ handleClick (event) {
1231
+ if (event.target.classList.contains('websy-menu-icon') ||
1232
+ event.target.nodeName === 'svg' ||
1233
+ event.target.nodeName === 'rect') {
1234
+ this.toggleMobileMenu()
1235
+ }
1236
+ if (event.target.classList.contains('websy-menu-header')) {
1237
+ let item = this.itemMap[event.target.id]
1238
+ if (event.target.classList.contains('trigger-item') && item.level === this.lowestLevel) {
1239
+ this.toggleMobileMenu('remove')
1240
+ }
1241
+ if (item.items) {
1242
+ event.target.classList.toggle('menu-open')
1243
+ this.toggleMenu(item.id)
1244
+ }
1245
+ }
1246
+ if (event.target.classList.contains('websy-menu-mask')) {
1247
+ this.toggleMobileMenu()
848
1248
  }
849
- html += '</div>'
850
- return html
851
1249
  }
852
- renderRanges () {
853
- return this.options.ranges.map((r, i) => `
854
- <li data-index='${i}' class='websy-date-picker-range ${i === this.selectedRange ? 'active' : ''} ${r.disabled === true ? 'websy-disabled-range' : ''}'>${r.label}</li>
855
- `).join('')
1250
+ normaliseString (text) {
1251
+ return text.replace(/-/g, '').replace(/\s/g, '_')
856
1252
  }
857
- scrollRangeIntoView () {
858
- if (this.selectedRangeDates[0]) {
859
- const el = document.getElementById(`${this.selectedRangeDates[0].getTime()}_date`)
860
- const parentEl = document.getElementById(`${this.elementId}_dateList`)
861
- if (el && parentEl) {
862
- parentEl.scrollTo(0, el.offsetTop)
863
- }
1253
+ render () {
1254
+ const el = document.getElementById(this.elementId)
1255
+ if (el) {
1256
+ let html = ``
1257
+ if (this.options.collapsible === true) {
1258
+ html += `
1259
+ <div id='${this.elementId}_menuIcon' class='websy-menu-icon'>
1260
+ <svg viewbox="0 0 40 40" width="30" height="40">
1261
+ <rect x="0" y="0" width="30" height="4" rx="2"></rect>
1262
+ <rect x="0" y="12" width="30" height="4" rx="2"></rect>
1263
+ <rect x="0" y="24" width="30" height="4" rx="2"></rect>
1264
+ </svg>
1265
+ </div>
1266
+ `
1267
+ }
1268
+ if (this.options.logo) {
1269
+ if (Array.isArray(this.options.logo.classes)) {
1270
+ this.options.logo.classes = this.options.logo.classes.join(' ')
1271
+ }
1272
+ html += `
1273
+ <div
1274
+ class='logo ${this.options.logo.classes || ''}'
1275
+ ${this.options.logo.attributes && this.options.logo.attributes.join(' ')}
1276
+ >
1277
+ <img src='${this.options.logo.url}'></img>
1278
+ </div>
1279
+ <div id='${this.elementId}_mask' class='websy-menu-mask'></div>
1280
+ <div id="${this.elementId}_menuContainer" class="websy-menu-block-container">
1281
+ `
1282
+ }
1283
+ html += this.renderBlock(this.options.items, 'main', 0)
1284
+ html += `</div>`
1285
+ el.innerHTML = html
1286
+ if (this.options.navigator) {
1287
+ this.options.navigator.registerElements(el)
1288
+ }
864
1289
  }
865
1290
  }
866
- selectDate (timestamp) {
867
- if (this.currentselection.length === 0) {
868
- this.currentselection.push(timestamp)
1291
+ renderBlock (items, block, level = 0) {
1292
+ let html = `
1293
+ <ul class='websy-${this.options.orientation}-list ${level > 0 ? 'websy-child-list' : ''} ${(block !== 'main' ? 'websy-menu-collapsed' : '')}' id='${this.elementId}_${block}_list'
1294
+ `
1295
+ if (block !== 'main') {
1296
+ html += ` data-collapsed='${(block !== 'main' ? 'true' : 'false')}'`
869
1297
  }
870
- else {
871
- if (timestamp > this.currentselection[0]) {
872
- this.currentselection.push(timestamp)
1298
+ html += '>'
1299
+ for (let i = 0; i < items.length; i++) {
1300
+ // update the block to the current item
1301
+ let selected = '' // items[i].default === true ? 'selected' : ''
1302
+ let active = items[i].default === true ? 'active' : ''
1303
+ let currentBlock = this.normaliseString(items[i].text)
1304
+ let blockId = items[i].id // || `${this.elementId}_${currentBlock}_label`
1305
+ if (Array.isArray(items[i].classes)) {
1306
+ items[i].classes = items[i].classes.join(' ')
1307
+ }
1308
+ html += `
1309
+ <li class='websy-${this.options.orientation}-list-item'>
1310
+ <div class='websy-menu-header ${items[i].classes || ''} ${selected} ${active}'
1311
+ id='${blockId}'
1312
+ data-id='${currentBlock}'
1313
+ data-menu-id='${this.elementId}_${currentBlock}_list'
1314
+ data-popout-id='${level > 1 ? block : currentBlock}'
1315
+ data-text='${items[i].text}'
1316
+ style='padding-left: ${level * this.options.childIndentation}px'
1317
+ ${(items[i].attributes && items[i].attributes.join(' ')) || ''}
1318
+ >
1319
+ `
1320
+ if (this.options.orientation === 'horizontal') {
1321
+ html += items[i].text
1322
+ }
1323
+ if (this.options.activeSymbol === 'line') {
1324
+ html += `
1325
+ <span class='selected-bar'></span>
1326
+ `
1327
+ }
1328
+ if (this.options.activeSymbol === 'triangle') {
1329
+ html += `
1330
+ <span class='active-square'></span>
1331
+ `
1332
+ }
1333
+ html += `
1334
+ <span class='${items[i].items && items[i].items.length > 0 ? 'menu-carat' : ''}'></span>
1335
+ `
1336
+ if (this.options.orientation === 'vertical') {
1337
+ html += `
1338
+ &nbsp;
1339
+ `
1340
+ }
1341
+ html += `
1342
+ </div>
1343
+ `
1344
+ if (items[i].items) {
1345
+ html += this.renderBlock(items[i].items, currentBlock, items[i].level + 1)
873
1346
  }
874
- else {
875
- this.currentselection.splice(0, 0, timestamp)
1347
+ // map the item to it's parent
1348
+ if (block && block !== 'main') {
1349
+ if (!this.options.parentMap[currentBlock]) {
1350
+ this.options.parentMap[currentBlock] = block
1351
+ }
876
1352
  }
1353
+ html += `
1354
+ </li>
1355
+ `
877
1356
  }
878
- this.selectedRangeDates = [new Date(this.currentselection[0]), new Date(this.currentselection[1] || this.currentselection[0])]
879
- if (this.currentselection.length === 2) {
880
- this.currentselection = []
881
- }
882
- this.selectedRange = -1
883
- this.highlightRange()
884
- }
885
- selectRange (index) {
886
- if (this.options.ranges[index]) {
887
- this.selectedRangeDates = [...this.options.ranges[index].range]
888
- this.selectedRange = +index
889
- this.highlightRange()
890
- this.close(true)
891
- }
892
- }
893
- selectCustomRange (range) {
894
- this.selectedRange = -1
895
- this.selectedRangeDates = range
896
- this.highlightRange()
897
- this.updateRange()
898
- }
899
- setDateBounds (range) {
900
- if (this.options.ranges[0].label === 'All Dates') {
901
- this.options.ranges[0].range = [range[0], range[1] || range[0]]
1357
+ html += `</ul>`
1358
+ return html
1359
+ }
1360
+ toggleMenu (id) {
1361
+ const el = document.getElementById(`${id}_list`)
1362
+ if (el) {
1363
+ el.classList.toggle('websy-menu-collapsed')
902
1364
  }
903
- this.options.minAllowedDate = range[0]
904
- this.options.maxAllowedDate = range[1] || range[0]
905
1365
  }
906
- updateRange () {
907
- let range
908
- if (this.selectedRange === -1) {
909
- let start = this.selectedRangeDates[0].toLocaleDateString()
910
- let end = ''
911
- if (this.selectedRangeDates[1] && (this.selectedRangeDates[0].getTime() !== this.selectedRangeDates[1].getTime())) {
912
- end = ` - ${this.selectedRangeDates[1].toLocaleDateString()}`
913
- }
914
- range = { label: `${start}${end}` }
915
- }
916
- else {
917
- range = this.options.ranges[this.selectedRange]
1366
+ toggleMobileMenu (method) {
1367
+ if (typeof method === 'undefined') {
1368
+ method = 'toggle'
918
1369
  }
919
- const el = document.getElementById(this.elementId)
920
- const labelEl = document.getElementById(`${this.elementId}_selectedRange`)
921
- const rangeEls = el.querySelectorAll(`.websy-date-picker-range`)
922
- for (let i = 0; i < rangeEls.length; i++) {
923
- rangeEls[i].classList.remove('active')
924
- if (i === this.selectedRange) {
925
- rangeEls[i].classList.add('active')
926
- }
1370
+ const el = document.getElementById(`${this.elementId}`)
1371
+ if (el) {
1372
+ el.classList[method]('open')
927
1373
  }
928
- if (labelEl) {
929
- labelEl.innerHTML = range.label
1374
+ if (this.options.onToggle) {
1375
+ this.options.onToggle(method)
930
1376
  }
931
1377
  }
932
1378
  }
933
1379
 
934
- Date.prototype.floor = function () {
935
- return new Date(`${this.getMonth() + 1}/${this.getDate()}/${this.getFullYear()}`)
936
- }
937
-
938
- /* global WebsyUtils */
939
- class WebsyDropdown {
1380
+ /* global WebsyDesigns Blob */
1381
+ class WebsyPDFButton {
940
1382
  constructor (elementId, options) {
941
1383
  const DEFAULTS = {
942
- multiSelect: false,
943
- multiValueDelimiter: ',',
944
- allowClear: true,
945
- style: 'plain',
946
- items: [],
947
- label: '',
948
- disabled: false,
949
- minSearchCharacters: 2,
950
- showCompleteSelectedList: false,
951
- closeAfterSelection: true
952
- }
953
- this.options = Object.assign({}, DEFAULTS, options)
954
- this.tooltipTimeoutFn = null
955
- this._originalData = []
956
- this.selectedItems = this.options.selectedItems || []
957
- if (!elementId) {
958
- console.log('No element Id provided')
959
- return
1384
+ classes: [],
1385
+ wait: 0,
1386
+ buttonText: 'Download',
1387
+ directDownload: false
960
1388
  }
961
- const el = document.getElementById(elementId)
1389
+ this.elementId = elementId
1390
+ this.options = Object.assign({}, DEFAULTS, options)
1391
+ this.service = new WebsyDesigns.APIService('/pdf')
1392
+ const el = document.getElementById(this.elementId)
962
1393
  if (el) {
963
- this.elementId = elementId
964
1394
  el.addEventListener('click', this.handleClick.bind(this))
965
- el.addEventListener('keyup', this.handleKeyUp.bind(this))
966
- el.addEventListener('mouseout', this.handleMouseOut.bind(this))
967
- el.addEventListener('mousemove', this.handleMouseMove.bind(this))
968
- this.render()
969
- }
970
- else {
971
- console.log('No element found with Id', elementId)
972
- }
973
- }
974
- set selections (d) {
975
- this.selectedItems = d || []
976
- }
977
- set data (d) {
978
- this.options.items = d || []
979
- const el = document.getElementById(`${this.elementId}_items`)
980
- if (el.childElementCount === 0) {
981
- this.render()
982
- }
983
- else {
984
- if (this.options.items.length === 0) {
985
- this.options.items = [{label: this.options.noItemsText || 'No Items'}]
1395
+ if (options.html) {
1396
+ el.innerHTML = options.html
1397
+ }
1398
+ else {
1399
+ el.innerHTML = `
1400
+ <!--<form style='display: none;' id='${this.elementId}_form' action='/pdf' method='POST'>
1401
+ <input id='${this.elementId}_pdfHeader' value='' name='header'>
1402
+ <input id='${this.elementId}_pdfHTML' value='' name='html'>
1403
+ <input id='${this.elementId}_pdfFooter' value='' name='footer'>
1404
+ </form>-->
1405
+ <button class='websy-btn websy-pdf-button ${this.options.classes.join(' ')}'>
1406
+ Create PDF
1407
+ <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
1408
+ viewBox="0 0 184.153 184.153" style="enable-background:new 0 0 184.153 184.153;" xml:space="preserve">
1409
+ <g>
1410
+ <g>
1411
+ <g>
1412
+ <path d="M129.318,0H26.06c-1.919,0-3.475,1.554-3.475,3.475v177.203c0,1.92,1.556,3.475,3.475,3.475h132.034
1413
+ c1.919,0,3.475-1.554,3.475-3.475V34.131C161.568,22.011,140.771,0,129.318,0z M154.62,177.203H29.535V6.949h99.784
1414
+ c7.803,0,25.301,18.798,25.301,27.182V177.203z"/>
1415
+ <path d="M71.23,76.441c15.327,0,27.797-12.47,27.797-27.797c0-15.327-12.47-27.797-27.797-27.797
1416
+ c-15.327,0-27.797,12.47-27.797,27.797C43.433,63.971,55.902,76.441,71.23,76.441z M71.229,27.797
1417
+ c11.497,0,20.848,9.351,20.848,20.847c0,0.888-0.074,1.758-0.183,2.617l-18.071-2.708L62.505,29.735
1418
+ C65.162,28.503,68.112,27.797,71.229,27.797z M56.761,33.668l11.951,19.869c0.534,0.889,1.437,1.49,2.462,1.646l18.669,2.799
1419
+ c-3.433,6.814-10.477,11.51-18.613,11.51c-11.496,0-20.847-9.351-20.847-20.847C50.381,42.767,52.836,37.461,56.761,33.668z"/>
1420
+ <rect x="46.907" y="90.339" width="73.058" height="6.949"/>
1421
+ <rect x="46.907" y="107.712" width="48.644" height="6.949"/>
1422
+ <rect x="46.907" y="125.085" width="62.542" height="6.949"/>
1423
+ </g>
1424
+ </g>
1425
+ </g>
1426
+ </svg>
1427
+ </button>
1428
+ <div id='${this.elementId}_loader'></div>
1429
+ <div id='${this.elementId}_popup'></div>
1430
+ `
1431
+ this.loader = new WebsyDesigns.WebsyLoadingDialog(`${this.elementId}_loader`, { classes: ['global-loader'] })
1432
+ this.popup = new WebsyDesigns.WebsyPopupDialog(`${this.elementId}_popup`)
1433
+ // const formEl = document.getElementById(`${this.elementId}_form`)
1434
+ // if (formEl) {
1435
+ // formEl.addEventListener('load', () => {
1436
+ // this.loader.hide()
1437
+ // })
1438
+ // }
986
1439
  }
987
- this.renderItems()
988
- }
989
- }
990
- get data () {
991
- return this.options.items
992
- }
993
- clearSelected () {
994
- this.selectedItems = []
995
- this.updateHeader()
996
- if (this.options.onClearSelected) {
997
- this.options.onClearSelected()
998
- }
999
- }
1000
- close () {
1001
- const maskEl = document.getElementById(`${this.elementId}_mask`)
1002
- const contentEl = document.getElementById(`${this.elementId}_content`)
1003
- maskEl.classList.remove('active')
1004
- contentEl.classList.remove('active')
1005
- contentEl.classList.remove('on-top')
1006
- const searchEl = document.getElementById(`${this.elementId}_search`)
1007
- if (searchEl) {
1008
- if (searchEl.value.length > 0 && this.options.onCancelSearch) {
1009
- this.options.onCancelSearch('')
1010
- searchEl.value = ''
1011
- }
1012
1440
  }
1013
1441
  }
1014
1442
  handleClick (event) {
1015
- if (this.options.disabled === true) {
1016
- return
1017
- }
1018
- if (event.target.classList.contains('websy-dropdown-header')) {
1019
- this.open()
1443
+ if (event.target.classList.contains('websy-pdf-button')) {
1444
+ this.loader.show()
1445
+ setTimeout(() => {
1446
+ if (this.options.targetId) {
1447
+ const el = document.getElementById(this.options.targetId)
1448
+ if (el) {
1449
+ const pdfData = { options: {} }
1450
+ if (this.options.pdfOptions) {
1451
+ pdfData.options = Object.assign({}, this.options.pdfOptions)
1452
+ }
1453
+ if (this.options.header) {
1454
+ if (this.options.header.elementId) {
1455
+ const headerEl = document.getElementById(this.options.header.elementId)
1456
+ if (headerEl) {
1457
+ pdfData.header = headerEl.outerHTML
1458
+ if (this.options.header.css) {
1459
+ pdfData.options.headerCSS = this.options.header.css
1460
+ }
1461
+ }
1462
+ }
1463
+ else if (this.options.header.html) {
1464
+ pdfData.header = this.options.header.html
1465
+ if (this.options.header.css) {
1466
+ pdfData.options.headerCSS = this.options.header.css
1467
+ }
1468
+ }
1469
+ else {
1470
+ pdfData.header = this.options.header
1471
+ }
1472
+ }
1473
+ if (this.options.footer) {
1474
+ if (this.options.footer.elementId) {
1475
+ const footerEl = document.getElementById(this.options.footer.elementId)
1476
+ if (footerEl) {
1477
+ pdfData.footer = footerEl.outerHTML
1478
+ if (this.options.footer.css) {
1479
+ pdfData.options.footerCSS = this.options.footer.css
1480
+ }
1481
+ }
1482
+ }
1483
+ else {
1484
+ pdfData.footer = this.options.footer
1485
+ }
1486
+ }
1487
+ pdfData.html = el.outerHTML
1488
+ // document.getElementById(`${this.elementId}_pdfHeader`).value = pdfData.header
1489
+ // document.getElementById(`${this.elementId}_pdfHTML`).value = pdfData.html
1490
+ // document.getElementById(`${this.elementId}_pdfFooter`).value = pdfData.footer
1491
+ // document.getElementById(`${this.elementId}_form`).submit()
1492
+ this.service.add('', pdfData, {responseType: 'blob'}).then(response => {
1493
+ this.loader.hide()
1494
+ const blob = new Blob([response], {type: 'application/pdf'})
1495
+ let msg = `
1496
+ <div class='text-center websy-pdf-download'>
1497
+ <div>Your file is ready to download</div>
1498
+ <a href='${URL.createObjectURL(blob)}' target='_blank'
1499
+ `
1500
+ if (this.options.directDownload === true) {
1501
+ msg += `download='${this.options.fileName || 'Export'}.pdf'`
1502
+ }
1503
+ msg += `
1504
+ >
1505
+ <button class='websy-btn download-pdf'>${this.options.buttonText}</button>
1506
+ </a>
1507
+ </div>
1508
+ `
1509
+ this.popup.show({
1510
+ message: msg,
1511
+ mask: true
1512
+ })
1513
+ }, err => {
1514
+ console.error(err)
1515
+ })
1516
+ }
1517
+ }
1518
+ }, this.options.wait)
1020
1519
  }
1021
- else if (event.target.classList.contains('websy-dropdown-mask')) {
1022
- this.close()
1520
+ else if (event.target.classList.contains('download-pdf')) {
1521
+ this.popup.hide()
1522
+ if (this.options.onClose) {
1523
+ this.options.onClose()
1524
+ }
1023
1525
  }
1024
- else if (event.target.classList.contains('websy-dropdown-item')) {
1025
- const index = event.target.getAttribute('data-index')
1026
- this.updateSelected(+index)
1526
+ }
1527
+ render () {
1528
+ //
1529
+ }
1530
+ }
1531
+
1532
+ class WebsyPopupDialog {
1533
+ constructor (elementId, options) {
1534
+ this.DEFAULTS = {
1535
+ buttons: []
1027
1536
  }
1028
- else if (event.target.classList.contains('clear')) {
1029
- this.clearSelected()
1537
+ this.options = Object.assign({}, this.DEFAULTS, options)
1538
+ if (!elementId) {
1539
+ console.log('No element Id provided for Websy Popup')
1540
+ return
1030
1541
  }
1542
+ this.closeOnOutsideClick = true
1543
+ const el = document.getElementById(elementId)
1544
+ this.elementId = elementId
1545
+ el.addEventListener('click', this.handleClick.bind(this))
1031
1546
  }
1032
- handleKeyUp (event) {
1033
- if (event.target.classList.contains('websy-dropdown-search')) {
1034
- if (this._originalData.length === 0) {
1035
- this._originalData = [...this.options.items]
1036
- }
1037
- if (event.target.value.length >= this.options.minSearchCharacters) {
1038
- if (event.key === 'Enter') {
1039
- if (this.options.onConfirmSearch) {
1040
- this.options.onConfirmSearch(event.target.value)
1041
- event.target.value = ''
1042
- }
1043
- }
1044
- else if (event.key === 'Escape') {
1045
- if (this.options.onCancelSearch) {
1046
- this.options.onCancelSearch(event.target.value)
1047
- event.target.value = ''
1048
- }
1049
- else {
1050
- this.data = this._originalData
1051
- this._originalData = []
1052
- }
1053
- }
1054
- else {
1055
- if (this.options.onSearch) {
1056
- this.options.onSearch(event.target.value)
1057
- }
1058
- else {
1059
- this.data = this._originalData.filter(d => d.label.toLowerCase().indexOf(event.target.value.toLowerCase()) !== -1)
1547
+ hide () {
1548
+ const el = document.getElementById(this.elementId)
1549
+ el.innerHTML = ''
1550
+ }
1551
+ handleClick (event) {
1552
+ if (event.target.classList.contains('websy-btn')) {
1553
+ const buttonIndex = event.target.getAttribute('data-index')
1554
+ const buttonInfo = this.options.buttons[buttonIndex]
1555
+ if (buttonInfo && buttonInfo.fn) {
1556
+ if (typeof this.options.collectData !== 'undefined') {
1557
+ const collectEl = document.getElementById(`${this.elementId}_collect`)
1558
+ if (collectEl) {
1559
+ buttonInfo.collectedData = collectEl.value
1060
1560
  }
1061
1561
  }
1062
- }
1063
- else {
1064
- if (this.options.onCancelSearch) {
1065
- this.options.onCancelSearch(event.target.value)
1562
+ if (buttonInfo.preventClose !== true) {
1563
+ this.hide()
1066
1564
  }
1565
+ buttonInfo.fn(buttonInfo)
1067
1566
  }
1068
- }
1069
- }
1070
- handleMouseMove (event) {
1071
- if (this.tooltipTimeoutFn) {
1072
- event.target.classList.remove('websy-delayed')
1073
- event.target.classList.remove('websy-delayed-info')
1074
- if (event.target.children[1]) {
1075
- event.target.children[1].classList.remove('websy-delayed-info')
1076
- }
1077
- clearTimeout(this.tooltipTimeoutFn)
1078
- }
1079
- if (event.target.tagName === 'LI') {
1080
- this.tooltipTimeoutFn = setTimeout(() => {
1081
- event.target.classList.add('websy-delayed')
1082
- }, 500)
1083
- }
1084
- if (event.target.classList.contains('websy-dropdown-header') && event.target.children[1]) {
1085
- this.tooltipTimeoutFn = setTimeout(() => {
1086
- event.target.children[1].classList.add('websy-delayed-info')
1087
- }, 500)
1088
- }
1089
- }
1090
- handleMouseOut (event) {
1091
- if (this.tooltipTimeoutFn) {
1092
- event.target.classList.remove('websy-delayed')
1093
- event.target.classList.remove('websy-delayed-info')
1094
- if (event.target.children[1]) {
1095
- event.target.children[1].classList.remove('websy-delayed-info')
1567
+ else if (buttonInfo && buttonInfo.preventClose !== true) {
1568
+ this.hide()
1096
1569
  }
1097
- clearTimeout(this.tooltipTimeoutFn)
1098
- }
1099
- }
1100
- open (options, override = false) {
1101
- const maskEl = document.getElementById(`${this.elementId}_mask`)
1102
- const contentEl = document.getElementById(`${this.elementId}_content`)
1103
- maskEl.classList.add('active')
1104
- contentEl.classList.add('active')
1105
- if (WebsyUtils.getElementPos(contentEl).bottom > window.innerHeight) {
1106
- contentEl.classList.add('on-top')
1107
1570
  }
1108
- if (this.options.disableSearch !== true) {
1109
- const searchEl = document.getElementById(`${this.elementId}_search`)
1110
- if (searchEl) {
1111
- searchEl.focus()
1112
- }
1571
+ else if (this.closeOnOutsideClick === true) {
1572
+ this.hide()
1113
1573
  }
1114
1574
  }
1115
1575
  render () {
1116
1576
  if (!this.elementId) {
1117
- console.log('No element Id provided for Websy Dropdown')
1577
+ console.log('No element Id provided for Websy Popup')
1118
1578
  return
1119
1579
  }
1120
1580
  const el = document.getElementById(this.elementId)
1121
- const headerLabel = this.selectedItems.map(s => this.options.items[s].label || this.options.items[s].value).join(this.options.multiValueDelimiter)
1122
- const headerValue = this.selectedItems.map(s => this.options.items[s].value || this.options.items[s].label).join(this.options.multiValueDelimiter)
1123
- let html = `
1124
- <div class='websy-dropdown-container ${this.options.disabled ? 'disabled' : ''} ${this.options.disableSearch !== true ? 'with-search' : ''}'>
1125
- <div id='${this.elementId}_header' class='websy-dropdown-header ${this.selectedItems.length === 1 ? 'one-selected' : ''} ${this.options.allowClear === true ? 'allow-clear' : ''}'>
1126
- <span id='${this.elementId}_headerLabel' class='websy-dropdown-header-label'>${this.options.label}</span>
1127
- <span data-info='${headerLabel}' class='websy-dropdown-header-value' id='${this.elementId}_selectedItems'>${headerLabel}</span>
1128
- <input class='dropdown-input' id='${this.elementId}_input' name='${this.options.field || this.options.label}' value='${headerValue}'>
1129
- <svg class='arrow' xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M23.677 18.52c.914 1.523-.183 3.472-1.967 3.472h-19.414c-1.784 0-2.881-1.949-1.967-3.472l9.709-16.18c.891-1.483 3.041-1.48 3.93 0l9.709 16.18z"/></svg>
1130
- `
1131
- if (this.options.allowClear === true) {
1132
- html += `
1133
- <svg class='clear' xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 512 512"><title>ionicons-v5-l</title><line x1="368" y1="368" x2="144" y2="144" style="fill:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:32px"/><line x1="368" y1="144" x2="144" y2="368" style="fill:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:32px"/></svg>
1134
- `
1135
- }
1136
- html += `
1137
- </div>
1138
- <div id='${this.elementId}_mask' class='websy-dropdown-mask'></div>
1139
- <div id='${this.elementId}_content' class='websy-dropdown-content'>
1140
- `
1141
- if (this.options.disableSearch !== true) {
1142
- html += `
1143
- <input id='${this.elementId}_search' class='websy-dropdown-search' placeholder='${this.options.searchPlaceholder || 'Search'}'>
1144
- `
1581
+ let html = ''
1582
+ if (this.options.mask === true) {
1583
+ html += `<div class='websy-mask'></div>`
1145
1584
  }
1146
1585
  html += `
1147
- <div class='websy-dropdown-items'>
1148
- <ul id='${this.elementId}_items'>
1149
- </ul>
1150
- </div><!--
1151
- --><div class='websy-dropdown-custom'></div>
1152
- </div>
1153
- </div>
1154
- `
1155
- el.innerHTML = html
1156
- this.renderItems()
1157
- }
1158
- renderItems () {
1159
- let html = this.options.items.map((r, i) => `
1160
- <li data-index='${i}' class='websy-dropdown-item ${this.selectedItems.indexOf(i) !== -1 ? 'active' : ''}'>${r.label}</li>
1161
- `).join('')
1162
- const el = document.getElementById(`${this.elementId}_items`)
1163
- if (el) {
1164
- el.innerHTML = html
1165
- }
1166
- let item
1167
- if (this.selectedItems.length === 1) {
1168
- item = this.options.items[this.selectedItems[0]]
1169
- }
1170
- this.updateHeader(item)
1171
- }
1172
- updateHeader (item) {
1173
- const el = document.getElementById(this.elementId)
1174
- const headerEl = document.getElementById(`${this.elementId}_header`)
1175
- const headerLabelEl = document.getElementById(`${this.elementId}_headerLabel`)
1176
- const labelEl = document.getElementById(`${this.elementId}_selectedItems`)
1177
- const inputEl = document.getElementById(`${this.elementId}_input`)
1178
- const itemEls = el.querySelectorAll(`.websy-dropdown-item`)
1179
- for (let i = 0; i < itemEls.length; i++) {
1180
- itemEls[i].classList.remove('active')
1181
- if (this.selectedItems.indexOf(i) !== -1) {
1182
- itemEls[i].classList.add('active')
1183
- }
1586
+ <div class='websy-popup-dialog-container'>
1587
+ <div class='websy-popup-dialog'>
1588
+ `
1589
+ if (this.options.title) {
1590
+ html += `<h1>${this.options.title}</h1>`
1184
1591
  }
1185
- if (headerLabelEl) {
1186
- headerLabelEl.innerHTML = this.options.label
1592
+ if (this.options.message) {
1593
+ html += `<p>${this.options.message}</p>`
1187
1594
  }
1188
- if (headerEl) {
1189
- headerEl.classList.remove('one-selected')
1190
- headerEl.classList.remove('multi-selected')
1191
- if (this.selectedItems.length === 1) {
1192
- headerEl.classList.add('one-selected')
1193
- }
1194
- else if (this.selectedItems.length > 1) {
1195
- if (this.options.showCompleteSelectedList === true) {
1196
- headerEl.classList.add('one-selected')
1197
- }
1198
- else {
1199
- headerEl.classList.add('multi-selected')
1200
- }
1201
- }
1595
+ if (typeof this.options.collectData !== 'undefined') {
1596
+ html += `
1597
+ <div>
1598
+ <input id="${this.elementId}_collect" class="websy-input" value="${typeof this.options.collectData === 'boolean' ? '' : this.options.collectData}" placeholder="${this.options.collectPlaceholder || ''}">
1599
+ </div>
1600
+ `
1202
1601
  }
1203
- if (labelEl) {
1204
- if (this.selectedItems.length === 1) {
1205
- labelEl.innerHTML = item.label
1206
- labelEl.setAttribute('data-info', item.label)
1207
- inputEl.value = item.value
1208
- }
1209
- else if (this.selectedItems.length > 1) {
1210
- if (this.options.showCompleteSelectedList === true) {
1211
- let selectedLabels = this.selectedItems.map(s => this.options.items[s].label || this.options.items[s].value).join(this.options.multiValueDelimiter)
1212
- let selectedValues = this.selectedItems.map(s => this.options.items[s].value || this.options.items[s].label).join(this.options.multiValueDelimiter)
1213
- labelEl.innerHTML = selectedLabels
1214
- labelEl.setAttribute('data-info', selectedLabels)
1215
- inputEl.value = selectedValues
1216
- }
1217
- else {
1218
- let selectedValues = this.selectedItems.map(s => this.options.items[s].value || this.options.items[s].label).join(this.options.multiValueDelimiter)
1219
- labelEl.innerHTML = `${this.selectedItems.length} selected`
1220
- labelEl.setAttribute('data-info', '')
1221
- inputEl.value = selectedValues
1222
- }
1223
- }
1224
- else {
1225
- labelEl.innerHTML = ''
1226
- labelEl.setAttribute('data-info', '')
1227
- inputEl.value = ''
1602
+ this.closeOnOutsideClick = true
1603
+ if (this.options.buttons) {
1604
+ if (this.options.allowCloseOnOutsideClick !== true) {
1605
+ this.closeOnOutsideClick = false
1606
+ }
1607
+ html += `<div class='websy-popup-button-panel'>`
1608
+ for (let i = 0; i < this.options.buttons.length; i++) {
1609
+ html += `
1610
+ <button class='websy-btn ${(this.options.buttons[i].classes || []).join(' ')}' data-index='${i}'>
1611
+ ${this.options.buttons[i].label}
1612
+ </button>
1613
+ `
1228
1614
  }
1615
+ html += `</div>`
1229
1616
  }
1617
+ html += `
1618
+ </div>
1619
+ </div>
1620
+ `
1621
+ el.innerHTML = html
1230
1622
  }
1231
- updateSelected (index) {
1232
- if (typeof index !== 'undefined' && index !== null) {
1233
- let pos = this.selectedItems.indexOf(index)
1234
- if (pos !== -1) {
1235
- this.selectedItems.splice(pos, 1)
1236
- }
1237
- if (this.options.multiSelect === false) {
1238
- this.selectedItems = [index]
1239
- }
1240
- else {
1241
- this.selectedItems.push(index)
1242
- }
1243
- }
1244
- const item = this.options.items[index]
1245
- this.updateHeader(item)
1246
- if (item && this.options.onItemSelected) {
1247
- this.options.onItemSelected(item, this.selectedItems, this.options.items)
1623
+ show (options) {
1624
+ if (options) {
1625
+ this.options = Object.assign({}, this.DEFAULTS, options)
1248
1626
  }
1249
- if (this.options.closeAfterSelection === true) {
1250
- this.close()
1251
- }
1627
+ this.render()
1628
+ }
1629
+ }
1630
+
1631
+ class WebsyPubSub {
1632
+ constructor (elementId, options) {
1633
+ this.options = Object.assign({}, options)
1634
+ if (!elementId) {
1635
+ console.log('No element Id provided')
1636
+ return
1637
+ }
1638
+ this.elementId = elementId
1639
+ this.subscriptions = {}
1640
+ }
1641
+ publish (method, data) {
1642
+ if (this.subscriptions[method]) {
1643
+ this.subscriptions[method].forEach(fn => {
1644
+ fn(data)
1645
+ })
1646
+ }
1647
+ }
1648
+ subscribe (method, fn) {
1649
+ if (!this.subscriptions[method]) {
1650
+ this.subscriptions[method] = []
1651
+ }
1652
+ this.subscriptions[method].push(fn)
1252
1653
  }
1253
1654
  }
1254
1655
 
@@ -1404,223 +1805,55 @@ class WebsyResultList {
1404
1805
  for (let i = 0; i < this.rows.length; i++) {
1405
1806
  if (this.rows[i].id === id) {
1406
1807
  return this.rows[i]
1407
- }
1408
- }
1409
- return null
1410
- }
1411
- handleClick (event) {
1412
- if (event.target.classList.contains('clickable')) {
1413
- let l = event.target.getAttribute('data-event')
1414
- if (l) {
1415
- l = l.split('(')
1416
- let params = []
1417
- const id = event.target.getAttribute('data-id')
1418
- if (l[1]) {
1419
- l[1] = l[1].replace(')', '')
1420
- params = l[1].split(',')
1421
- }
1422
- l = l[0]
1423
- params = params.map(p => {
1424
- if (typeof p !== 'string' && typeof p !== 'number') {
1425
- if (this.rows[+id]) {
1426
- p = this.rows[+id][p]
1427
- }
1428
- }
1429
- else if (typeof p === 'string') {
1430
- p = p.replace(/"/g, '').replace(/'/g, '')
1431
- }
1432
- return p
1433
- })
1434
- if (event.target.classList.contains('clickable') && this.options.listeners.click[l]) {
1435
- event.stopPropagation()
1436
- this.options.listeners.click[l].call(this, event, this.rows[+id], ...params)
1437
- }
1438
- }
1439
- }
1440
- }
1441
- render () {
1442
- if (this.options.entity) {
1443
- this.apiService.get(this.options.entity).then(results => {
1444
- this.rows = results.rows
1445
- this.resize()
1446
- })
1447
- }
1448
- else {
1449
- this.resize()
1450
- }
1451
- }
1452
- resize () {
1453
- const html = this.buildHTML(this.rows)
1454
- const el = document.getElementById(this.elementId)
1455
- el.innerHTML = html.replace(/\n/g, '')
1456
- }
1457
- }
1458
-
1459
- /* global WebsyDesigns */
1460
- class WebsyTemplate {
1461
- constructor (elementId, options) {
1462
- const DEFAULTS = {
1463
- listeners: {
1464
- click: {}
1465
- }
1466
- }
1467
- this.options = Object.assign({}, DEFAULTS, options)
1468
- this.elementId = elementId
1469
- this.templateService = new WebsyDesigns.APIService('')
1470
- if (!elementId) {
1471
- console.log('No element Id provided for Websy Template')
1472
- return
1473
- }
1474
- const el = document.getElementById(elementId)
1475
- if (el) {
1476
- el.addEventListener('click', this.handleClick.bind(this))
1477
- }
1478
- if (typeof options.template === 'object' && options.template.url) {
1479
- this.templateService.get(options.template.url).then(templateString => {
1480
- this.options.template = templateString
1481
- this.render()
1482
- })
1483
- }
1484
- else {
1485
- this.render()
1486
- }
1487
- }
1488
- buildHTML () {
1489
- let html = ``
1490
- if (this.options.template) {
1491
- let template = this.options.template
1492
- // find conditional elements
1493
- let ifMatches = [...template.matchAll(/<\s*if[^>]*>([\s\S]*?)<\s*\/\s*if>/g)]
1494
- ifMatches.forEach(m => {
1495
- // get the condition
1496
- if (m[0] && m.index > -1) {
1497
- let conditionMatch = m[0].match(/(\scondition=["|']\w.+)["|']/g)
1498
- if (conditionMatch && conditionMatch[0]) {
1499
- let c = conditionMatch[0].trim().replace('condition=', '')
1500
- if (c.split('')[0] === '"') {
1501
- c = c.replace(/"/g, '')
1502
- }
1503
- else if (c.split('')[0] === '\'') {
1504
- c = c.replace(/'/g, '')
1505
- }
1506
- let parts = []
1507
- let polarity = true
1508
- if (c.indexOf('===') !== -1) {
1509
- parts = c.split('===')
1510
- }
1511
- else if (c.indexOf('!==') !== -1) {
1512
- parts = c.split('!==')
1513
- polarity = false
1514
- }
1515
- else if (c.indexOf('==') !== -1) {
1516
- parts = c.split('==')
1517
- }
1518
- else if (c.indexOf('!=') !== -1) {
1519
- parts = c.split('!=')
1520
- polarity = false
1521
- }
1522
- let removeAll = true
1523
- if (parts.length === 2) {
1524
- if (!isNaN(parts[1])) {
1525
- parts[1] = +parts[1]
1526
- }
1527
- if (parts[1] === 'true') {
1528
- parts[1] = true
1529
- }
1530
- if (parts[1] === 'false') {
1531
- parts[1] = false
1532
- }
1533
- if (typeof parts[1] === 'string') {
1534
- if (parts[1].indexOf('"') !== -1) {
1535
- parts[1] = parts[1].replace(/"/g, '')
1536
- }
1537
- else if (parts[1].indexOf('\'') !== -1) {
1538
- parts[1] = parts[1].replace(/'/g, '')
1539
- }
1540
- }
1541
- if (polarity === true) {
1542
- if (typeof this.options.data[parts[0]] !== 'undefined' && this.options.data[parts[0]] === parts[1]) {
1543
- // remove the <if> tags
1544
- removeAll = false
1545
- }
1546
- else if (parts[0] === parts[1]) {
1547
- removeAll = false
1548
- }
1549
- }
1550
- else if (polarity === false) {
1551
- if (typeof this.options.data[parts[0]] !== 'undefined' && this.options.data[parts[0]] !== parts[1]) {
1552
- // remove the <if> tags
1553
- removeAll = false
1554
- }
1555
- }
1556
- }
1557
- if (removeAll === true) {
1558
- // remove the whole markup
1559
- template = template.replace(m[0], '')
1560
- }
1561
- else {
1562
- // remove the <if> tags
1563
- let newMarkup = m[0]
1564
- newMarkup = newMarkup.replace('</if>', '').replace(/<\s*if[^>]*>/g, '')
1565
- template = template.replace(m[0], newMarkup)
1566
- }
1567
- }
1568
- }
1569
- })
1570
- let tagMatches = [...template.matchAll(/(\sdata-event=["|']\w.+)["|']/g)]
1571
- tagMatches.forEach(m => {
1572
- if (m[0] && m.index > -1) {
1573
- template = template.replace(m[0], `${m[0]}`)
1574
- }
1575
- })
1576
- for (let key in this.options.data) {
1577
- let rg = new RegExp(`{${key}}`, 'gm')
1578
- if (rg) {
1579
- template = template.replace(rg, this.options.data[key])
1580
- }
1581
- }
1582
- html = template
1583
- }
1584
- return html
1585
- }
1586
- handleClick (event) {
1587
- //
1588
- }
1589
- render () {
1590
- this.resize()
1591
- }
1592
- resize () {
1593
- const html = this.buildHTML()
1594
- const el = document.getElementById(this.elementId)
1595
- el.innerHTML = html.replace(/\n/g, '')
1596
- if (this.options.readyCallbackFn) {
1597
- this.options.readyCallbackFn()
1808
+ }
1598
1809
  }
1810
+ return null
1599
1811
  }
1600
- }
1601
-
1602
- class WebsyPubSub {
1603
- constructor (elementId, options) {
1604
- this.options = Object.assign({}, options)
1605
- if (!elementId) {
1606
- console.log('No element Id provided')
1607
- return
1812
+ handleClick (event) {
1813
+ if (event.target.classList.contains('clickable')) {
1814
+ let l = event.target.getAttribute('data-event')
1815
+ if (l) {
1816
+ l = l.split('(')
1817
+ let params = []
1818
+ const id = event.target.getAttribute('data-id')
1819
+ if (l[1]) {
1820
+ l[1] = l[1].replace(')', '')
1821
+ params = l[1].split(',')
1822
+ }
1823
+ l = l[0]
1824
+ params = params.map(p => {
1825
+ if (typeof p !== 'string' && typeof p !== 'number') {
1826
+ if (this.rows[+id]) {
1827
+ p = this.rows[+id][p]
1828
+ }
1829
+ }
1830
+ else if (typeof p === 'string') {
1831
+ p = p.replace(/"/g, '').replace(/'/g, '')
1832
+ }
1833
+ return p
1834
+ })
1835
+ if (event.target.classList.contains('clickable') && this.options.listeners.click[l]) {
1836
+ event.stopPropagation()
1837
+ this.options.listeners.click[l].call(this, event, this.rows[+id], ...params)
1838
+ }
1839
+ }
1608
1840
  }
1609
- this.elementId = elementId
1610
- this.subscriptions = {}
1611
1841
  }
1612
- publish (method, data) {
1613
- if (this.subscriptions[method]) {
1614
- this.subscriptions[method].forEach(fn => {
1615
- fn(data)
1842
+ render () {
1843
+ if (this.options.entity) {
1844
+ this.apiService.get(this.options.entity).then(results => {
1845
+ this.rows = results.rows
1846
+ this.resize()
1616
1847
  })
1617
1848
  }
1618
- }
1619
- subscribe (method, fn) {
1620
- if (!this.subscriptions[method]) {
1621
- this.subscriptions[method] = []
1849
+ else {
1850
+ this.resize()
1622
1851
  }
1623
- this.subscriptions[method].push(fn)
1852
+ }
1853
+ resize () {
1854
+ const html = this.buildHTML(this.rows)
1855
+ const el = document.getElementById(this.elementId)
1856
+ el.innerHTML = html.replace(/\n/g, '')
1624
1857
  }
1625
1858
  }
1626
1859
 
@@ -1628,16 +1861,17 @@ class WebsyPubSub {
1628
1861
  class WebsyRouter {
1629
1862
  constructor (options) {
1630
1863
  const defaults = {
1631
- triggerClass: 'trigger-item',
1632
- triggerToggleClass: 'trigger-toggle',
1633
- viewClass: 'view',
1864
+ triggerClass: 'websy-trigger',
1865
+ triggerToggleClass: 'websy-trigger-toggle',
1866
+ viewClass: 'websy-view',
1634
1867
  activeClass: 'active',
1635
1868
  viewAttribute: 'data-view',
1636
1869
  groupAttribute: 'data-group',
1637
1870
  parentAttribute: 'data-parent',
1638
1871
  defaultView: '',
1639
1872
  defaultGroup: 'main',
1640
- subscribers: { show: [], hide: [] }
1873
+ subscribers: { show: [], hide: [] },
1874
+ persistentParameters: false
1641
1875
  }
1642
1876
  this.triggerIdList = []
1643
1877
  this.viewIdList = []
@@ -1653,27 +1887,40 @@ class WebsyRouter {
1653
1887
  window.addEventListener('keyup', this.handleKeyUp.bind(this))
1654
1888
  window.addEventListener('focus', this.handleFocus.bind(this))
1655
1889
  window.addEventListener('click', this.handleClick.bind(this))
1656
- this.options = Object.assign({}, defaults, options)
1657
- // add any necessary CSS if the viewClass has been changed
1658
- if (this.options.viewClass !== defaults.viewClass || this.options.activeClass !== defaults.activeClass) {
1659
- let style = `
1660
- <style>
1661
- .${this.options.viewClass}{ display: none; }
1662
- .${this.options.viewClass}.${this.options.activeClass}{ display: initial; }
1663
- .${this.options.triggerClass}{cursor: pointer;}
1664
- </style>
1665
- `
1666
- document.querySelector('head').innerHTML += style
1667
- }
1668
- // this.navigate(this.currentPath, this.options.defaultGroup)
1890
+ this.options = Object.assign({}, defaults, options)
1891
+ if (this.options.onShow) {
1892
+ this.on('show', this.options.onShow)
1893
+ }
1894
+ if (this.options.onHide) {
1895
+ this.on('hide', this.options.onHide)
1896
+ }
1897
+ this.init()
1669
1898
  }
1670
1899
  addGroup (group) {
1671
1900
  if (!this.groups[group]) {
1672
- this.groups[group] = {
1673
- activeView: ''
1674
- }
1901
+ const els = document.querySelectorAll(`.websy-view[data-group="${group}"]`)
1902
+ if (els) {
1903
+ console.log('els', els)
1904
+ this.getClosestParent(els[0], parent => {
1905
+ this.groups[group] = {
1906
+ activeView: '',
1907
+ views: [],
1908
+ parent: parent.getAttribute('data-view')
1909
+ }
1910
+ })
1911
+ }
1675
1912
  }
1676
1913
  }
1914
+ getClosestParent (el, callbackFn) {
1915
+ if (el && el.parentElement) {
1916
+ if (el.parentElement.attributes['data-view'] || el.tagName === 'BODY') {
1917
+ callbackFn(el.parentElement)
1918
+ }
1919
+ else {
1920
+ this.getClosestParent(el.parentElement, callbackFn)
1921
+ }
1922
+ }
1923
+ }
1677
1924
  addUrlParams (params) {
1678
1925
  if (typeof params === 'undefined') {
1679
1926
  return
@@ -1707,6 +1954,25 @@ class WebsyRouter {
1707
1954
  }
1708
1955
  return path.join('&')
1709
1956
  }
1957
+ checkChildGroups (parent) {
1958
+ if (!this.groups) {
1959
+ this.groups = {}
1960
+ }
1961
+ const parentEl = document.querySelector(`.websy-view[data-view="${parent}"]`)
1962
+ if (parentEl) {
1963
+ const els = parentEl.querySelectorAll(`.websy-view[data-group]`)
1964
+ for (let i = 0; i < els.length; i++) {
1965
+ const g = els[i].getAttribute('data-group')
1966
+ const v = els[i].getAttribute('data-view')
1967
+ if (!this.groups[g]) {
1968
+ this.addGroup(g)
1969
+ }
1970
+ if (this.groups[g].views.indexOf(v) === -1) {
1971
+ this.groups[g].views.push(v)
1972
+ }
1973
+ }
1974
+ }
1975
+ }
1710
1976
  formatParams (params) {
1711
1977
  const output = {
1712
1978
  path: params,
@@ -1734,11 +2000,15 @@ class WebsyRouter {
1734
2000
  return `${item}_${value.join('')}`
1735
2001
  }
1736
2002
  getActiveViewsFromParent (parent) {
1737
- let views = []
2003
+ let views = []
2004
+ this.checkChildGroups(parent)
1738
2005
  for (let g in this.groups) {
1739
2006
  if (this.groups[g].parent === parent) {
1740
2007
  if (this.groups[g].activeView) {
1741
- views.push(this.groups[g].activeView)
2008
+ views.push({view: this.groups[g].activeView, group: g})
2009
+ }
2010
+ else {
2011
+ views.push({view: this.groups[g].views[0], group: g})
1742
2012
  }
1743
2013
  }
1744
2014
  }
@@ -1753,7 +2023,7 @@ class WebsyRouter {
1753
2023
  }
1754
2024
  }
1755
2025
  init () {
1756
- this.registerElements(document)
2026
+ // this.registerElements(document)
1757
2027
  let view = ''
1758
2028
  let params = this.formatParams(this.queryParams)
1759
2029
  let url
@@ -1793,85 +2063,116 @@ class WebsyRouter {
1793
2063
  handleKeyUp (event) {
1794
2064
  this.controlPressed = false
1795
2065
  }
1796
- hideView (view, group) {
1797
- this.hideTriggerItems(view, group)
1798
- this.hideViewItems(view, group)
1799
- if (group === this.options.defaultGroup) {
1800
- let children = document.getElementsByClassName(`parent-${view}`)
1801
- if (children) {
1802
- for (let c = 0; c < children.length; c++) {
1803
- if (children[c].classList.contains(this.options.viewClass)) {
1804
- let viewAttr = children[c].attributes[this.options.viewAttribute]
1805
- let groupAttr = children[c].attributes[this.options.groupAttribute]
1806
- if (viewAttr && viewAttr.value !== '') {
1807
- this.hideView(viewAttr.value, groupAttr.value || this.options.defaultGroup)
1808
- }
1809
- }
1810
- }
1811
- }
2066
+ hideChildren (view, group) {
2067
+ let children = this.getActiveViewsFromParent(view)
2068
+ for (let c = 0; c < children.length; c++) {
2069
+ this.hideTriggerItems(children[c].view, group)
2070
+ this.hideViewItems(children[c].view, group)
2071
+ this.publish('hide', [children[c].view])
2072
+ }
2073
+ }
2074
+ hideView (view, group) {
2075
+ this.hideChildren(view, group)
2076
+ if (this.previousView !== this.currentView) {
2077
+ this.hideTriggerItems(view, group)
2078
+ this.hideViewItems(view, group)
2079
+ this.publish('hide', [view])
2080
+ }
2081
+ }
2082
+ // registerElements (root) {
2083
+ // if (root.nodeName === '#document') {
2084
+ // this.groups = {}
2085
+ // }
2086
+ // let triggerItems = root.getElementsByClassName(this.options.triggerClass)
2087
+ // for (let i = 0; i < triggerItems.length; i++) {
2088
+ // if (!triggerItems[i].id) {
2089
+ // triggerItems[i].id = this.generateId('trigger')
2090
+ // }
2091
+ // if (this.triggerIdList.indexOf(triggerItems[i].id) !== -1) {
2092
+ // continue
2093
+ // }
2094
+ // this.triggerIdList.push(triggerItems[i].id)
2095
+ // // get the view for each item
2096
+ // let viewAttr = triggerItems[i].attributes[this.options.viewAttribute]
2097
+ // if (viewAttr && viewAttr.value !== '') {
2098
+ // // check to see if the item belongs to a group
2099
+ // // use the group to add an additional class to the item
2100
+ // // this combines the triggerClass and groupAttr properties
2101
+ // let groupAttr = triggerItems[i].attributes[this.options.groupAttribute]
2102
+ // let group = this.options.defaultGroup
2103
+ // if (groupAttr && groupAttr.value !== '') {
2104
+ // // if no group is found, assign it to the default group
2105
+ // group = groupAttr.value
2106
+ // }
2107
+ // let parentAttr = triggerItems[i].attributes[this.options.parentAttribute]
2108
+ // if (parentAttr && parentAttr.value !== '') {
2109
+ // triggerItems[i].classList.add(`parent-${parentAttr.value}`)
2110
+ // }
2111
+ // triggerItems[i].classList.add(`${this.options.triggerClass}-${group}`)
2112
+ // }
2113
+ // }
2114
+ // // Assign group class to views
2115
+ // let viewItems = root.getElementsByClassName(this.options.viewClass)
2116
+ // for (let i = 0; i < viewItems.length; i++) {
2117
+ // let groupAttr = viewItems[i].attributes[this.options.groupAttribute]
2118
+ // let viewAttr = viewItems[i].attributes[this.options.viewAttribute]
2119
+ // if (!groupAttr || groupAttr.value === '') {
2120
+ // // if no group is found, assign it to the default group
2121
+ // viewItems[i].classList.add(`${this.options.viewClass}-${this.options.defaultGroup}`)
2122
+ // }
2123
+ // else {
2124
+ // this.addGroup(groupAttr.value)
2125
+ // if (viewItems[i].classList.contains(this.options.activeClass)) {
2126
+ // this.groups[groupAttr.value].activeView = viewAttr.value
2127
+ // }
2128
+ // viewItems[i].classList.add(`${this.options.viewClass}-${groupAttr.value}`)
2129
+ // }
2130
+ // let parentAttr = viewItems[i].attributes[this.options.parentAttribute]
2131
+ // if (parentAttr && parentAttr.value !== '') {
2132
+ // viewItems[i].classList.add(`parent-${parentAttr.value}`)
2133
+ // if (groupAttr && groupAttr.value !== '' && this.groups[groupAttr.value]) {
2134
+ // this.groups[groupAttr.value].parent = parentAttr.value
2135
+ // }
2136
+ // }
2137
+ // }
2138
+ // }
2139
+ prepComponent (elementId, options) {
2140
+ let el = document.getElementById(`${elementId}_content`)
2141
+ if (el) {
2142
+ return ''
1812
2143
  }
1813
- else {
1814
- if (this.groups[group] && this.groups[group].activeView === view) {
1815
- this.groups[group].activeView = null
1816
- }
2144
+ let html = `
2145
+ <article id='${elementId}_content' class='websy-content-article'></article>
2146
+ <div id='${elementId}_loading' class='websy-loading-container'><div class='websy-ripple'><div></div><div></div></div></div>
2147
+ `
2148
+ if (options.help && options.help !== '') {
2149
+ html += `
2150
+ <Help not yet supported>
2151
+ `
1817
2152
  }
1818
- this.publish('hide', [view])
1819
- }
1820
- registerElements (root) {
1821
- if (root.nodeName === '#document') {
1822
- this.groups = {}
1823
- }
1824
- let triggerItems = root.getElementsByClassName(this.options.triggerClass)
1825
- for (let i = 0; i < triggerItems.length; i++) {
1826
- if (!triggerItems[i].id) {
1827
- triggerItems[i].id = this.generateId('trigger')
1828
- }
1829
- if (this.triggerIdList.indexOf(triggerItems[i].id) !== -1) {
1830
- continue
1831
- }
1832
- this.triggerIdList.push(triggerItems[i].id)
1833
- // get the view for each item
1834
- let viewAttr = triggerItems[i].attributes[this.options.viewAttribute]
1835
- if (viewAttr && viewAttr.value !== '') {
1836
- // check to see if the item belongs to a group
1837
- // use the group to add an additional class to the item
1838
- // this combines the triggerClass and groupAttr properties
1839
- let groupAttr = triggerItems[i].attributes[this.options.groupAttribute]
1840
- let group = this.options.defaultGroup
1841
- if (groupAttr && groupAttr.value !== '') {
1842
- // if no group is found, assign it to the default group
1843
- group = groupAttr.value
1844
- }
1845
- let parentAttr = triggerItems[i].attributes[this.options.parentAttribute]
1846
- if (parentAttr && parentAttr.value !== '') {
1847
- triggerItems[i].classList.add(`parent-${parentAttr.value}`)
1848
- }
1849
- triggerItems[i].classList.add(`${this.options.triggerClass}-${group}`)
1850
- }
2153
+ if (options.tooltip && options.tooltip.value && options.tooltip.value !== '') {
2154
+ html += `
2155
+ <div class="websy-info ${this.options.tooltip.classes.join(' ') || ''}" data-info="${this.options.tooltip.value}">
2156
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 512 512"><title>ionicons-v5-e</title><path d="M256,56C145.72,56,56,145.72,56,256s89.72,200,200,200,200-89.72,200-200S366.28,56,256,56Zm0,82a26,26,0,1,1-26,26A26,26,0,0,1,256,138Zm48,226H216a16,16,0,0,1,0-32h28V244H228a16,16,0,0,1,0-32h32a16,16,0,0,1,16,16V332h28a16,16,0,0,1,0,32Z"/></svg>
2157
+ </div>
2158
+ `
1851
2159
  }
1852
- // Assign group class to views
1853
- let viewItems = root.getElementsByClassName(this.options.viewClass)
1854
- for (let i = 0; i < viewItems.length; i++) {
1855
- let groupAttr = viewItems[i].attributes[this.options.groupAttribute]
1856
- let viewAttr = viewItems[i].attributes[this.options.viewAttribute]
1857
- if (!groupAttr || groupAttr.value === '') {
1858
- // if no group is found, assign it to the default group
1859
- viewItems[i].classList.add(`${this.options.viewClass}-${this.options.defaultGroup}`)
1860
- }
1861
- else {
1862
- this.addGroup(groupAttr.value)
1863
- if (viewItems[i].classList.contains(this.options.activeClass)) {
1864
- this.groups[groupAttr.value].activeView = viewAttr.value
2160
+ el = document.getElementById(elementId)
2161
+ if (el) {
2162
+ el.innerHTML = html
2163
+ }
2164
+ }
2165
+ showComponents (view) {
2166
+ if (this.options.views && this.options.views[view] && this.options.views[view].components) {
2167
+ this.options.views[view].components.forEach(c => {
2168
+ if (typeof c.instance === 'undefined') {
2169
+ this.prepComponent(c.elementId, c.options)
2170
+ c.instance = new (c.Component)(c.elementId, c.options)
1865
2171
  }
1866
- viewItems[i].classList.add(`${this.options.viewClass}-${groupAttr.value}`)
1867
- }
1868
- let parentAttr = viewItems[i].attributes[this.options.parentAttribute]
1869
- if (parentAttr && parentAttr.value !== '') {
1870
- viewItems[i].classList.add(`parent-${parentAttr.value}`)
1871
- if (groupAttr && groupAttr.value !== '' && this.groups[groupAttr.value]) {
1872
- this.groups[groupAttr.value].parent = parentAttr.value
2172
+ else if (c.instance.render) {
2173
+ c.instance.render()
1873
2174
  }
1874
- }
2175
+ })
1875
2176
  }
1876
2177
  }
1877
2178
  showView (view, params) {
@@ -1879,21 +2180,26 @@ class WebsyRouter {
1879
2180
  this.activateItem(view, this.options.viewClass)
1880
2181
  let children = this.getActiveViewsFromParent(view)
1881
2182
  for (let c = 0; c < children.length; c++) {
1882
- this.activateItem(children[c], this.options.triggerClass)
1883
- this.activateItem(children[c], this.options.viewClass)
1884
- this.publish('show', [children[c]])
1885
- }
1886
- this.publish('show', [view, params])
2183
+ this.activateItem(children[c].view, this.options.triggerClass)
2184
+ this.activateItem(children[c].view, this.options.viewClass)
2185
+ this.showComponents(children[c].view)
2186
+ this.publish('show', [children[c].view])
2187
+ }
2188
+ if (this.previousView !== this.currentView) {
2189
+ this.showComponents(view)
2190
+ this.publish('show', [view, params])
2191
+ }
1887
2192
  }
1888
2193
  reloadCurrentView () {
1889
2194
  this.showView(this.currentView, this.currentParams)
1890
2195
  }
1891
- navigate (inputPath, group, event, popped) {
2196
+ navigate (inputPath, group = 'main', event, popped) {
1892
2197
  if (typeof popped === 'undefined') {
1893
2198
  popped = false
1894
2199
  }
1895
2200
  this.popped = popped
1896
2201
  let toggle = false
2202
+ let noInputParams = inputPath.indexOf('?') === -1
1897
2203
  let groupActiveView
1898
2204
  let params = {}
1899
2205
  let newPath = inputPath
@@ -1904,6 +2210,9 @@ class WebsyRouter {
1904
2210
  if (inputPath.indexOf('?') === -1 && this.queryParams) {
1905
2211
  inputPath += `?${this.queryParams}`
1906
2212
  }
2213
+ }
2214
+ else {
2215
+ this.currentParams = {}
1907
2216
  }
1908
2217
  if (this.usesHTMLSuffix === true) {
1909
2218
  if (inputPath.indexOf('?') === -1) {
@@ -1935,14 +2244,17 @@ class WebsyRouter {
1935
2244
  toggle = event
1936
2245
  }
1937
2246
  }
2247
+ if (!this.groups) {
2248
+ this.groups = {}
2249
+ }
2250
+ if (!this.groups[group]) {
2251
+ this.addGroup(group)
2252
+ }
1938
2253
  if (toggle === true && this.groups[group].activeView !== '') {
1939
2254
  newPath = ''
1940
2255
  }
1941
2256
  this.previousView = this.currentView
1942
- this.previousPath = this.currentPath
1943
- if (!this.groups) {
1944
- this.groups = {}
1945
- }
2257
+ this.previousPath = this.currentPath
1946
2258
  if (this.groups[group]) {
1947
2259
  if (toggle === false) {
1948
2260
  groupActiveView = this.groups[group].activeView
@@ -1954,7 +2266,7 @@ class WebsyRouter {
1954
2266
  this.hideView(this.previousPath, group)
1955
2267
  }
1956
2268
  }
1957
- else {
2269
+ else {
1958
2270
  this.hideView(this.previousView, group)
1959
2271
  }
1960
2272
  if (toggle === true && newPath === groupActiveView) {
@@ -1994,7 +2306,7 @@ class WebsyRouter {
1994
2306
  if (this.currentParams && this.currentParams.path) {
1995
2307
  historyUrl += `?${this.currentParams.path}`
1996
2308
  }
1997
- else if (this.queryParams) {
2309
+ else if (this.queryParams && this.options.persistentParameters === true) {
1998
2310
  historyUrl += `?${this.queryParams}`
1999
2311
  }
2000
2312
  history.pushState({
@@ -2006,6 +2318,9 @@ class WebsyRouter {
2006
2318
  }
2007
2319
  }
2008
2320
  }
2321
+ on (event, fn) {
2322
+ this.options.subscribers[event].push(fn)
2323
+ }
2009
2324
  onPopState (event) {
2010
2325
  if (event.state) {
2011
2326
  let url
@@ -2031,7 +2346,7 @@ class WebsyRouter {
2031
2346
  }
2032
2347
  subscribe (event, fn) {
2033
2348
  this.options.subscribers[event].push(fn)
2034
- }
2349
+ }
2035
2350
  get currentPath () {
2036
2351
  let path = window.location.pathname.split('/').pop()
2037
2352
  if (path.indexOf('.htm') !== -1) {
@@ -2073,277 +2388,383 @@ class WebsyRouter {
2073
2388
  if (els) {
2074
2389
  for (let i = 0; i < els.length; i++) {
2075
2390
  if (els[i].attributes[this.options.viewAttribute] && els[i].attributes[this.options.viewAttribute].value === path) {
2076
- els[i].classList.add(this.options.activeClass)
2077
- break
2078
- }
2079
- }
2080
- }
2081
- }
2082
- }
2083
-
2084
- /* global XMLHttpRequest fetch ENV */
2085
- class APIService {
2086
- constructor (baseUrl) {
2087
- this.baseUrl = baseUrl
2088
- }
2089
- add (entity, data, options = {}) {
2090
- const url = this.buildUrl(entity)
2091
- return this.run('POST', url, data, options)
2092
- }
2093
- buildUrl (entity, id, query) {
2094
- if (typeof query === 'undefined') {
2095
- query = []
2096
- }
2097
- if (id) {
2098
- query.push(`id:${id}`)
2099
- }
2100
- return `${this.baseUrl}/${entity}${query.length > 0 ? `${entity.indexOf('?') === -1 ? '?' : '&'}where=${query.join(';')}` : ''}`
2101
- }
2102
- delete (entity, id) {
2103
- const url = this.buildUrl(entity, id)
2104
- return this.run('DELETE', url)
2105
- }
2106
- get (entity, id, query) {
2107
- const url = this.buildUrl(entity, id, query)
2108
- return this.run('GET', url)
2109
- }
2110
- update (entity, id, data) {
2111
- const url = this.buildUrl(entity, id)
2112
- return this.run('PUT', url, data)
2113
- }
2114
- fetchData (method, url, data, options = {}) {
2115
- return fetch(url, {
2116
- method,
2117
- mode: 'cors', // no-cors, *cors, same-origin
2118
- cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
2119
- credentials: 'same-origin', // include, *same-origin, omit
2120
- headers: {
2121
- 'Content-Type': 'application/json'
2122
- // 'Content-Type': 'application/x-www-form-urlencoded',
2123
- },
2124
- redirect: 'follow', // manual, *follow, error
2125
- referrerPolicy: 'no-referrer', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
2126
- body: JSON.stringify(data) // body data type must match "Content-Type" header
2127
- }).then(response => {
2128
- return response.json()
2129
- })
2130
- }
2131
- run (method, url, data, options = {}, returnHeaders = false) {
2132
- return new Promise((resolve, reject) => {
2133
- const xhr = new XMLHttpRequest()
2134
- xhr.open(method, url)
2135
- xhr.setRequestHeader('Content-Type', 'application/json')
2136
- xhr.responseType = 'text'
2137
- if (options.responseType) {
2138
- xhr.responseType = options.responseType
2139
- }
2140
- if (options.headers) {
2141
- for (let key in options.headers) {
2142
- xhr.setRequestHeader(key, options.headers[key])
2143
- }
2144
- }
2145
- xhr.withCredentials = true
2146
- xhr.onload = () => {
2147
- if (xhr.status === 401 || xhr.status === 403) {
2148
- if (ENV && ENV.AUTH_REDIRECT) {
2149
- window.location = ENV.AUTH_REDIRECT
2150
- }
2151
- else {
2152
- window.location = '/login'
2153
- }
2154
- // reject('401 - Unauthorized')
2155
- return
2156
- }
2157
- let response = xhr.responseType === 'text' ? xhr.responseText : xhr.response
2158
- if (response !== '' && response !== 'null') {
2159
- try {
2160
- response = JSON.parse(response)
2161
- }
2162
- catch (e) {
2163
- // Either a bad Url or a string has been returned
2164
- }
2165
- }
2166
- else {
2167
- response = []
2168
- }
2169
- if (response.err) {
2170
- reject(JSON.stringify(response))
2171
- }
2172
- else {
2173
- if (returnHeaders === true) {
2174
- resolve([response, parseHeaders(xhr.getAllResponseHeaders())])
2175
- }
2176
- else {
2177
- resolve(response)
2178
- }
2179
- }
2180
- }
2181
- xhr.onerror = () => reject(xhr.statusText)
2182
- if (data) {
2183
- xhr.send(JSON.stringify(data))
2184
- }
2185
- else {
2186
- xhr.send()
2187
- }
2188
- })
2189
- function parseHeaders (headers) {
2190
- headers = headers.split('\r\n')
2191
- let ouput = {}
2192
- headers.forEach(h => {
2193
- h = h.split(':')
2194
- if (h.length === 2) {
2195
- ouput[h[0]] = h[1].trim()
2196
- }
2197
- })
2198
- return ouput
2391
+ els[i].classList.add(this.options.activeClass)
2392
+ break
2393
+ }
2394
+ }
2199
2395
  }
2200
- }
2396
+ }
2201
2397
  }
2202
2398
 
2203
- /* global WebsyDesigns Blob */
2204
- class WebsyPDFButton {
2399
+ /* global */
2400
+ class Switch {
2205
2401
  constructor (elementId, options) {
2206
- const DEFAULTS = {
2207
- classes: [],
2208
- wait: 0,
2209
- buttonText: 'Download'
2210
- }
2211
2402
  this.elementId = elementId
2403
+ const DEFAULTS = {
2404
+ enabled: false
2405
+ }
2212
2406
  this.options = Object.assign({}, DEFAULTS, options)
2213
- this.service = new WebsyDesigns.APIService('/pdf')
2214
2407
  const el = document.getElementById(this.elementId)
2215
2408
  if (el) {
2216
2409
  el.addEventListener('click', this.handleClick.bind(this))
2217
- if (options.html) {
2218
- el.innerHTML = options.html
2219
- }
2220
- else {
2221
- el.innerHTML = `
2222
- <!--<form style='display: none;' id='${this.elementId}_form' action='/pdf' method='POST'>
2223
- <input id='${this.elementId}_pdfHeader' value='' name='header'>
2224
- <input id='${this.elementId}_pdfHTML' value='' name='html'>
2225
- <input id='${this.elementId}_pdfFooter' value='' name='footer'>
2226
- </form>-->
2227
- <button class='websy-btn websy-pdf-button ${this.options.classes.join(' ')}'>
2228
- Create PDF
2229
- <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
2230
- viewBox="0 0 184.153 184.153" style="enable-background:new 0 0 184.153 184.153;" xml:space="preserve">
2231
- <g>
2232
- <g>
2233
- <g>
2234
- <path d="M129.318,0H26.06c-1.919,0-3.475,1.554-3.475,3.475v177.203c0,1.92,1.556,3.475,3.475,3.475h132.034
2235
- c1.919,0,3.475-1.554,3.475-3.475V34.131C161.568,22.011,140.771,0,129.318,0z M154.62,177.203H29.535V6.949h99.784
2236
- c7.803,0,25.301,18.798,25.301,27.182V177.203z"/>
2237
- <path d="M71.23,76.441c15.327,0,27.797-12.47,27.797-27.797c0-15.327-12.47-27.797-27.797-27.797
2238
- c-15.327,0-27.797,12.47-27.797,27.797C43.433,63.971,55.902,76.441,71.23,76.441z M71.229,27.797
2239
- c11.497,0,20.848,9.351,20.848,20.847c0,0.888-0.074,1.758-0.183,2.617l-18.071-2.708L62.505,29.735
2240
- C65.162,28.503,68.112,27.797,71.229,27.797z M56.761,33.668l11.951,19.869c0.534,0.889,1.437,1.49,2.462,1.646l18.669,2.799
2241
- c-3.433,6.814-10.477,11.51-18.613,11.51c-11.496,0-20.847-9.351-20.847-20.847C50.381,42.767,52.836,37.461,56.761,33.668z"/>
2242
- <rect x="46.907" y="90.339" width="73.058" height="6.949"/>
2243
- <rect x="46.907" y="107.712" width="48.644" height="6.949"/>
2244
- <rect x="46.907" y="125.085" width="62.542" height="6.949"/>
2245
- </g>
2246
- </g>
2247
- </g>
2248
- </svg>
2249
- </button>
2250
- <div id='${this.elementId}_loader'></div>
2251
- <div id='${this.elementId}_popup'></div>
2252
- `
2253
- this.loader = new WebsyDesigns.WebsyLoadingDialog(`${this.elementId}_loader`, { classes: ['global-loader'] })
2254
- this.popup = new WebsyDesigns.WebsyPopupDialog(`${this.elementId}_popup`)
2255
- // const formEl = document.getElementById(`${this.elementId}_form`)
2256
- // if (formEl) {
2257
- // formEl.addEventListener('load', () => {
2258
- // this.loader.hide()
2259
- // })
2260
- // }
2410
+ this.render()
2411
+ }
2412
+ }
2413
+ disabled () {
2414
+ this.options.enabled = false
2415
+ this.render()
2416
+ }
2417
+ enable () {
2418
+ this.options.enabled = true
2419
+ this.render()
2420
+ }
2421
+ handleClick (event) {
2422
+ this.options.enabled = !this.options.enabled
2423
+ let method = this.options.enabled === true ? 'add' : 'remove'
2424
+ const el = document.getElementById(`${this.elementId}_switch`)
2425
+ el.classList[method]('enabled')
2426
+ if (this.options.onToggle) {
2427
+ this.options.onToggle(this.options.enabled)
2428
+ }
2429
+ }
2430
+ on (event, fn) {
2431
+ if (!this.options.subscribers[event]) {
2432
+ this.options.subscribers[event] = []
2433
+ }
2434
+ this.options.subscribers[event].push(fn)
2435
+ }
2436
+ publish (event, params) {
2437
+ this.options.subscribers[event].forEach((item) => {
2438
+ item.apply(null, params)
2439
+ })
2440
+ }
2441
+ render () {
2442
+ const el = document.getElementById(this.elementId)
2443
+ if (el) {
2444
+ el.innerHTML = `
2445
+ <div class="websy-switch-container">
2446
+ <div class="websy-switch-label">${this.options.label || ''}</div>
2447
+ <div id="${this.elementId}_switch" class="websy-switch ${this.options.enabled === true ? 'enabled' : ''}"></div>
2448
+ </div>
2449
+ `
2450
+ }
2451
+ }
2452
+ }
2453
+
2454
+ /* global WebsyDesigns */
2455
+ class WebsyTemplate {
2456
+ constructor (elementId, options) {
2457
+ const DEFAULTS = {
2458
+ listeners: {
2459
+ click: {}
2261
2460
  }
2262
2461
  }
2462
+ this.options = Object.assign({}, DEFAULTS, options)
2463
+ this.elementId = elementId
2464
+ this.templateService = new WebsyDesigns.APIService('')
2465
+ if (!elementId) {
2466
+ console.log('No element Id provided for Websy Template')
2467
+ return
2468
+ }
2469
+ const el = document.getElementById(elementId)
2470
+ if (el) {
2471
+ el.addEventListener('click', this.handleClick.bind(this))
2472
+ }
2473
+ if (typeof options.template === 'object' && options.template.url) {
2474
+ this.templateService.get(options.template.url).then(templateString => {
2475
+ this.options.template = templateString
2476
+ this.render()
2477
+ })
2478
+ }
2479
+ else {
2480
+ this.render()
2481
+ }
2263
2482
  }
2264
- handleClick (event) {
2265
- if (event.target.classList.contains('websy-pdf-button')) {
2266
- this.loader.show()
2267
- setTimeout(() => {
2268
- if (this.options.targetId) {
2269
- const el = document.getElementById(this.options.targetId)
2270
- if (el) {
2271
- const pdfData = { options: {} }
2272
- if (this.options.pdfOptions) {
2273
- pdfData.options = Object.assign({}, this.options.pdfOptions)
2483
+ buildHTML () {
2484
+ let html = ``
2485
+ if (this.options.template) {
2486
+ let template = this.options.template
2487
+ // find conditional elements
2488
+ let ifMatches = [...template.matchAll(/<\s*if[^>]*>([\s\S]*?)<\s*\/\s*if>/g)]
2489
+ ifMatches.forEach(m => {
2490
+ // get the condition
2491
+ if (m[0] && m.index > -1) {
2492
+ let conditionMatch = m[0].match(/(\scondition=["|']\w.+)["|']/g)
2493
+ if (conditionMatch && conditionMatch[0]) {
2494
+ let c = conditionMatch[0].trim().replace('condition=', '')
2495
+ if (c.split('')[0] === '"') {
2496
+ c = c.replace(/"/g, '')
2274
2497
  }
2275
- if (this.options.header) {
2276
- if (this.options.header.elementId) {
2277
- const headerEl = document.getElementById(this.options.header.elementId)
2278
- if (headerEl) {
2279
- pdfData.header = headerEl.outerHTML
2280
- if (this.options.header.css) {
2281
- pdfData.options.headerCSS = this.options.header.css
2282
- }
2283
- }
2498
+ else if (c.split('')[0] === '\'') {
2499
+ c = c.replace(/'/g, '')
2500
+ }
2501
+ let parts = []
2502
+ let polarity = true
2503
+ if (c.indexOf('===') !== -1) {
2504
+ parts = c.split('===')
2505
+ }
2506
+ else if (c.indexOf('!==') !== -1) {
2507
+ parts = c.split('!==')
2508
+ polarity = false
2509
+ }
2510
+ else if (c.indexOf('==') !== -1) {
2511
+ parts = c.split('==')
2512
+ }
2513
+ else if (c.indexOf('!=') !== -1) {
2514
+ parts = c.split('!=')
2515
+ polarity = false
2516
+ }
2517
+ let removeAll = true
2518
+ if (parts.length === 2) {
2519
+ if (!isNaN(parts[1])) {
2520
+ parts[1] = +parts[1]
2284
2521
  }
2285
- else if (this.options.header.html) {
2286
- pdfData.header = this.options.header.html
2287
- if (this.options.header.css) {
2288
- pdfData.options.headerCSS = this.options.header.css
2289
- }
2522
+ if (parts[1] === 'true') {
2523
+ parts[1] = true
2290
2524
  }
2291
- else {
2292
- pdfData.header = this.options.header
2525
+ if (parts[1] === 'false') {
2526
+ parts[1] = false
2293
2527
  }
2294
- }
2295
- if (this.options.footer) {
2296
- if (this.options.footer.elementId) {
2297
- const footerEl = document.getElementById(this.options.footer.elementId)
2298
- if (footerEl) {
2299
- pdfData.footer = footerEl.outerHTML
2300
- if (this.options.footer.css) {
2301
- pdfData.options.footerCSS = this.options.footer.css
2302
- }
2528
+ if (typeof parts[1] === 'string') {
2529
+ if (parts[1].indexOf('"') !== -1) {
2530
+ parts[1] = parts[1].replace(/"/g, '')
2303
2531
  }
2532
+ else if (parts[1].indexOf('\'') !== -1) {
2533
+ parts[1] = parts[1].replace(/'/g, '')
2534
+ }
2304
2535
  }
2305
- else {
2306
- pdfData.footer = this.options.footer
2307
- }
2536
+ if (polarity === true) {
2537
+ if (typeof this.options.data[parts[0]] !== 'undefined' && this.options.data[parts[0]] === parts[1]) {
2538
+ // remove the <if> tags
2539
+ removeAll = false
2540
+ }
2541
+ else if (parts[0] === parts[1]) {
2542
+ removeAll = false
2543
+ }
2544
+ }
2545
+ else if (polarity === false) {
2546
+ if (typeof this.options.data[parts[0]] !== 'undefined' && this.options.data[parts[0]] !== parts[1]) {
2547
+ // remove the <if> tags
2548
+ removeAll = false
2549
+ }
2550
+ }
2551
+ }
2552
+ if (removeAll === true) {
2553
+ // remove the whole markup
2554
+ template = template.replace(m[0], '')
2555
+ }
2556
+ else {
2557
+ // remove the <if> tags
2558
+ let newMarkup = m[0]
2559
+ newMarkup = newMarkup.replace('</if>', '').replace(/<\s*if[^>]*>/g, '')
2560
+ template = template.replace(m[0], newMarkup)
2308
2561
  }
2309
- pdfData.html = el.outerHTML
2310
- // document.getElementById(`${this.elementId}_pdfHeader`).value = pdfData.header
2311
- // document.getElementById(`${this.elementId}_pdfHTML`).value = pdfData.html
2312
- // document.getElementById(`${this.elementId}_pdfFooter`).value = pdfData.footer
2313
- // document.getElementById(`${this.elementId}_form`).submit()
2314
- this.service.add('', pdfData, {responseType: 'blob'}).then(response => {
2315
- this.loader.hide()
2316
- const blob = new Blob([response], {type: 'application/pdf'})
2317
- this.popup.show({
2318
- message: `
2319
- <div class='text-center websy-pdf-download'>
2320
- <div>Your file is ready to download</div>
2321
- <a href='${URL.createObjectURL(blob)}' target='_blank'>
2322
- <button class='websy-btn download-pdf'>${this.options.buttonText}</button>
2323
- </a>
2324
- </div>
2325
- `,
2326
- mask: true
2327
- })
2328
- }, err => {
2329
- console.error(err)
2330
- })
2331
2562
  }
2563
+ }
2564
+ })
2565
+ let tagMatches = [...template.matchAll(/(\sdata-event=["|']\w.+)["|']/g)]
2566
+ tagMatches.forEach(m => {
2567
+ if (m[0] && m.index > -1) {
2568
+ template = template.replace(m[0], `${m[0]}`)
2569
+ }
2570
+ })
2571
+ for (let key in this.options.data) {
2572
+ let rg = new RegExp(`{${key}}`, 'gm')
2573
+ if (rg) {
2574
+ template = template.replace(rg, this.options.data[key])
2575
+ }
2576
+ }
2577
+ html = template
2578
+ }
2579
+ return html
2580
+ }
2581
+ handleClick (event) {
2582
+ //
2583
+ }
2584
+ render () {
2585
+ this.resize()
2586
+ }
2587
+ resize () {
2588
+ const html = this.buildHTML()
2589
+ const el = document.getElementById(this.elementId)
2590
+ el.innerHTML = html.replace(/\n/g, '')
2591
+ if (this.options.readyCallbackFn) {
2592
+ this.options.readyCallbackFn()
2593
+ }
2594
+ }
2595
+ }
2596
+
2597
+ const WebsyUtils = {
2598
+ createIdentity: (size = 6) => {
2599
+ let text = ''
2600
+ let possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
2601
+
2602
+ for (let i = 0; i < size; i++) {
2603
+ text += possible.charAt(Math.floor(Math.random() * possible.length))
2604
+ }
2605
+ return text
2606
+ },
2607
+ getElementPos: el => {
2608
+ const rect = el.getBoundingClientRect()
2609
+ const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft
2610
+ const scrollTop = window.pageYOffset || document.documentElement.scrollTop
2611
+ return {
2612
+ top: rect.top + scrollTop,
2613
+ left: rect.left + scrollLeft,
2614
+ bottom: rect.top + scrollTop + el.clientHeight,
2615
+ right: rect.left + scrollLeft + el.clientWidth
2616
+ }
2617
+ },
2618
+ getLightDark: (backgroundColor, darkColor = '#000000', lightColor = '#ffffff') => {
2619
+ let colorParts
2620
+ let red = 0
2621
+ let green = 0
2622
+ let blue = 0
2623
+ if (backgroundColor.indexOf('#') !== -1) {
2624
+ // hex color
2625
+ backgroundColor = backgroundColor.replace('#', '')
2626
+ colorParts = backgroundColor
2627
+ colorParts = colorParts.split('')
2628
+ red = parseInt(colorParts[0] + colorParts[1], 16)
2629
+ green = parseInt(colorParts[2] + colorParts[3], 16)
2630
+ blue = parseInt(colorParts[4] + colorParts[5], 16)
2631
+ }
2632
+ else if (backgroundColor.toLowerCase().indexOf('rgb') !== -1) {
2633
+ // rgb color
2634
+ colorParts = backgroundColor
2635
+ colorParts = colorParts.split(',')
2636
+ red = colorParts[0]
2637
+ green = colorParts[1]
2638
+ blue = colorParts[2]
2639
+ }
2640
+ return (red * 0.299 + green * 0.587 + blue * 0.114) > 186 ? darkColor : lightColor
2641
+ },
2642
+ parseUrlParams: () => {
2643
+ let queryString = window.location.search.replace('?', '')
2644
+ const params = {}
2645
+ let parts = queryString.split('&')
2646
+ for (let i = 0; i < parts.length; i++) {
2647
+ let keyValue = parts[i].split('=')
2648
+ params[keyValue[0]] = keyValue[1]
2649
+ }
2650
+ return params
2651
+ },
2652
+ buildUrlParams: (params) => {
2653
+ let out = []
2654
+ for (const key in params) {
2655
+ out.push(`${key}=${params[key]}`)
2656
+ }
2657
+ return out.join('&')
2658
+ },
2659
+ fromQlikDate: d => {
2660
+ let output = new Date(Math.round((d - 25569) * 86400000))
2661
+ output.setTime(output.getTime() + output.getTimezoneOffset() * 60000)
2662
+ return output
2663
+ },
2664
+ toReduced: (v, decimals = 0, isPercentage = false, test = false, control) => {
2665
+ let ranges = [{
2666
+ divider: 1e18,
2667
+ suffix: 'E'
2668
+ }, {
2669
+ divider: 1e15,
2670
+ suffix: 'P'
2671
+ }, {
2672
+ divider: 1e12,
2673
+ suffix: 'T'
2674
+ }, {
2675
+ divider: 1e9,
2676
+ suffix: 'G'
2677
+ }, {
2678
+ divider: 1e6,
2679
+ suffix: 'M'
2680
+ }, {
2681
+ divider: 1e3,
2682
+ suffix: 'K'
2683
+ }]
2684
+ let numOut
2685
+ let divider
2686
+ let suffix = ''
2687
+ if (control) {
2688
+ let settings = getDivider(control)
2689
+ divider = settings.divider
2690
+ suffix = settings.suffix
2691
+ }
2692
+ if (v === 0) {
2693
+ numOut = 0
2694
+ }
2695
+ else if (control) {
2696
+ numOut = (v / divider) // .toFixed(decimals).replace(/(\d)(?=(\d{3})+(?!\d))/g, '$100,')
2697
+ }
2698
+ else if (v < 1000 && v % 1 === 0) {
2699
+ numOut = v
2700
+ // decimals = 0
2701
+ }
2702
+ else {
2703
+ numOut = v
2704
+ for (let i = 0; i < ranges.length; i++) {
2705
+ if (v >= ranges[i].divider) {
2706
+ numOut = (v / ranges[i].divider) // .toFixed(decimals).replace(/(\d)(?=(\d{3})+(?!\d))/g, '$100,')
2707
+ suffix = ranges[i].suffix
2708
+ break
2332
2709
  }
2333
- }, this.options.wait)
2710
+ // else if (isPercentage === true) {
2711
+ // numOut = (this * 100).toFixed(decimals)
2712
+ // }
2713
+ // else {
2714
+ // numOut = (this).toFixed(decimals)
2715
+ // }
2716
+ }
2334
2717
  }
2335
- else if (event.target.classList.contains('download-pdf')) {
2336
- this.popup.hide()
2337
- if (this.options.onClose) {
2338
- this.options.onClose()
2718
+ if (isPercentage === true) {
2719
+ numOut = numOut * 100
2720
+ }
2721
+ if (numOut % 1 > 0) {
2722
+ decimals = 1
2723
+ }
2724
+ if (numOut < 1) {
2725
+ decimals = getZeroDecimals(numOut)
2726
+ }
2727
+ numOut = (+numOut).toFixed(decimals)
2728
+ if (test === true) {
2729
+ return numOut
2730
+ }
2731
+ if (numOut.replace) {
2732
+ numOut = numOut.replace(/\B(?=(\d{3})+(?!\d))/g, ',')
2733
+ }
2734
+ function getDivider (n) {
2735
+ let s = ''
2736
+ let d = 1
2737
+ // let out
2738
+ for (let i = 0; i < ranges.length; i++) {
2739
+ if (n >= ranges[i].divider) {
2740
+ d = ranges[i].divider
2741
+ s = ranges[i].suffix
2742
+ // out = (n / ranges[i].divider).toFixed(decimals).replace(/(\d)(?=(\d{3})+(?!\d))/g, '$100,')
2743
+ break
2744
+ }
2745
+ }
2746
+ return { divider: d, suffix: s }
2747
+ }
2748
+ function getZeroDecimals (n) {
2749
+ let d = 0
2750
+ n = Math.abs(n)
2751
+ if (n === 0) {
2752
+ return 0
2339
2753
  }
2754
+ while (n < 10) {
2755
+ d++
2756
+ n = n * 10
2757
+ }
2758
+ return d
2340
2759
  }
2341
- }
2342
- render () {
2343
- //
2760
+ return `${numOut}${suffix}${isPercentage === true ? '%' : ''}`
2761
+ },
2762
+ toQlikDateNum: d => {
2763
+ return Math.floor(d.getTime() / 86400000 + 25570)
2344
2764
  }
2345
2765
  }
2346
2766
 
2767
+ /* global WebsyDesigns */
2347
2768
  class WebsyTable {
2348
2769
  constructor (elementId, options) {
2349
2770
  const DEFAULTS = {
@@ -2370,6 +2791,13 @@ class WebsyTable {
2370
2791
  <tfoot id="${this.elementId}_foot">
2371
2792
  </tfoot>
2372
2793
  </table>
2794
+ <div id="${this.elementId}_errorContainer" class='websy-vis-error-container'>
2795
+ <div>
2796
+ <div id="${this.elementId}_errorTitle"></div>
2797
+ <div id="${this.elementId}_errorMessage"></div>
2798
+ </div>
2799
+ </div>
2800
+ <div id="${this.elementId}_loadingContainer"></div>
2373
2801
  </div>
2374
2802
  `
2375
2803
  el.addEventListener('click', this.handleClick.bind(this))
@@ -2377,6 +2805,7 @@ class WebsyTable {
2377
2805
  el.addEventListener('mousemove', this.handleMouseMove.bind(this))
2378
2806
  const scrollEl = document.getElementById(`${this.elementId}_tableContainer`)
2379
2807
  scrollEl.addEventListener('scroll', this.handleScroll.bind(this))
2808
+ this.loadingDialog = new WebsyDesigns.LoadingDialog(`${this.elementId}_loadingContainer`)
2380
2809
  this.render()
2381
2810
  }
2382
2811
  else {
@@ -2384,6 +2813,7 @@ class WebsyTable {
2384
2813
  }
2385
2814
  }
2386
2815
  appendRows (data) {
2816
+ this.hideError()
2387
2817
  let bodyHTML = ''
2388
2818
  if (data) {
2389
2819
  bodyHTML += data.map((r, rowIndex) => {
@@ -2410,12 +2840,13 @@ class WebsyTable {
2410
2840
  class='${this.options.columns[i].classes || ''}'
2411
2841
  style='${style}'
2412
2842
  colspan='${c.colspan || 1}'
2843
+ rowspan='${c.rowspan || 1}'
2413
2844
  >
2414
2845
  <a href='${c.value}' target='${this.options.columns[i].openInNewTab === true ? '_blank' : '_self'}'>${c.displayText || this.options.columns[i].linkText || c.value}</a>
2415
2846
  </td>
2416
2847
  `
2417
2848
  }
2418
- else if (this.options.columns[i].showAsNavigatorLink === true && c.value.trim() !== '') {
2849
+ else if ((this.options.columns[i].showAsNavigatorLink === true || this.options.columns[i].showAsRouterLink === true) && c.value.trim() !== '') {
2419
2850
  return `
2420
2851
  <td
2421
2852
  data-view='${c.value}'
@@ -2424,6 +2855,7 @@ class WebsyTable {
2424
2855
  class='trigger-item ${this.options.columns[i].clickable === true ? 'clickable' : ''} ${this.options.columns[i].classes || ''}'
2425
2856
  style='${style}'
2426
2857
  colspan='${c.colspan || 1}'
2858
+ rowspan='${c.rowspan || 1}'
2427
2859
  >${c.displayText || this.options.columns[i].linkText || c.value}</td>
2428
2860
  `
2429
2861
  }
@@ -2442,6 +2874,7 @@ class WebsyTable {
2442
2874
  class='${this.options.columns[i].classes || ''}'
2443
2875
  style='${style}'
2444
2876
  colspan='${c.colspan || 1}'
2877
+ rowspan='${c.rowspan || 1}'
2445
2878
  >${c.value}</td>
2446
2879
  `
2447
2880
  }
@@ -2533,6 +2966,15 @@ class WebsyTable {
2533
2966
  this.options.onScroll(event)
2534
2967
  }
2535
2968
  }
2969
+ hideError () {
2970
+ const containerEl = document.getElementById(`${this.elementId}_errorContainer`)
2971
+ if (containerEl) {
2972
+ containerEl.classList.remove('active')
2973
+ }
2974
+ }
2975
+ hideLoading () {
2976
+ this.loadingDialog.hide()
2977
+ }
2536
2978
  internalSort (column, colIndex) {
2537
2979
  this.options.columns.forEach((c, i) => {
2538
2980
  c.activeSort = i === colIndex
@@ -2573,11 +3015,13 @@ class WebsyTable {
2573
3015
  if (!this.options.columns) {
2574
3016
  return
2575
3017
  }
3018
+ this.hideError()
2576
3019
  this.data = []
2577
3020
  this.rowCount = 0
2578
3021
  const bodyEl = document.getElementById(`${this.elementId}_body`)
2579
3022
  bodyEl.innerHTML = ''
2580
3023
  if (this.options.allowDownload === true) {
3024
+ // doesn't do anything yet
2581
3025
  const el = document.getElementById(this.elementId)
2582
3026
  if (el) {
2583
3027
  el.classList.add('allow-download')
@@ -2622,25 +3066,60 @@ class WebsyTable {
2622
3066
  // this.data = this.data.concat(data)
2623
3067
  this.appendRows(data)
2624
3068
  }
2625
- }
3069
+ }
3070
+ showError (options) {
3071
+ const containerEl = document.getElementById(`${this.elementId}_errorContainer`)
3072
+ if (containerEl) {
3073
+ containerEl.classList.add('active')
3074
+ }
3075
+ if (options.title) {
3076
+ const titleEl = document.getElementById(`${this.elementId}_errorTitle`)
3077
+ if (titleEl) {
3078
+ titleEl.innerHTML = options.title
3079
+ }
3080
+ }
3081
+ if (options.message) {
3082
+ const messageEl = document.getElementById(`${this.elementId}_errorTitle`)
3083
+ if (messageEl) {
3084
+ messageEl.innerHTML = options.message
3085
+ }
3086
+ }
3087
+ }
3088
+ showLoading (options) {
3089
+ this.loadingDialog.show(options)
3090
+ }
2626
3091
  }
2627
3092
 
2628
- /* global d3 include */
3093
+ /* global d3 include WebsyDesigns */
2629
3094
  class WebsyChart {
2630
3095
  constructor (elementId, options) {
2631
3096
  const DEFAULTS = {
2632
- margin: { top: 3, left: 3, bottom: 3, right: 3, axisBottom: 0, axisLeft: 0, axisRight: 0, axisTop: 0 },
3097
+ margin: {
3098
+ top: 10,
3099
+ left: 3,
3100
+ bottom: 3,
3101
+ right: 3,
3102
+ axisBottom: 0,
3103
+ axisLeft: 0,
3104
+ axisRight: 0,
3105
+ axisTop: 0,
3106
+ legendBottom: 0,
3107
+ legendLeft: 0,
3108
+ legendRight: 0,
3109
+ legendTop: 0
3110
+ },
2633
3111
  orientation: 'vertical',
2634
- colors: d3.schemeCategory10,
3112
+ colors: ['#5e4fa2', '#3288bd', '#66c2a5', '#abdda4', '#e6f598', '#fee08b', '#fdae61', '#f46d43', '#d53e4f', '#9e0142'],
2635
3113
  transitionDuration: 650,
2636
3114
  curveStyle: 'curveLinear',
2637
3115
  lineWidth: 2,
2638
3116
  forceZero: true,
2639
3117
  fontSize: 14,
2640
- symbolSize: 20,
2641
- dateFormat: '%b/%m/%Y',
2642
- showTrackingLine: true,
3118
+ symbolSize: 20,
3119
+ showTrackingLine: true,
2643
3120
  showTooltip: true,
3121
+ showLegend: false,
3122
+ legendPosition: 'bottom',
2644
3123
  tooltipWidth: 200
2645
3124
  }
2646
3125
  this.elementId = elementId
@@ -2653,6 +3132,27 @@ class WebsyChart {
2653
3132
  console.log('No element Id provided for Websy Chart')
2654
3133
  return
2655
3134
  }
3135
+ this.invertOverride = (input, input2) => {
3136
+ let xAxis = 'bottomAxis'
3137
+ if (this.options.orientation === 'horizontal') {
3138
+ xAxis = 'leftAxis'
3139
+ }
3140
+ let width = this[xAxis].step()
3141
+ let output
3142
+ let domain = [...this[xAxis].domain()]
3143
+ if (this.options.orientation === 'horizontal') {
3144
+ domain = domain.reverse()
3145
+ }
3146
+ for (let j = 0; j < domain.length; j++) {
3147
+ let breakA = this[xAxis](domain[j]) - (width / 2)
3148
+ let breakB = breakA + width
3149
+ if (input > breakA && input <= breakB) {
3150
+ output = j
3151
+ break
3152
+ }
3153
+ }
3154
+ return output
3155
+ }
2656
3156
  const el = document.getElementById(this.elementId)
2657
3157
  if (el) {
2658
3158
  el.classList.add('websy-chart')
@@ -2662,6 +3162,10 @@ class WebsyChart {
2662
3162
  else {
2663
3163
  el.innerHTML = ''
2664
3164
  this.svg = d3.select(el).append('svg')
3165
+ this.legendArea = d3.select(el).append('div')
3166
+ .attr('id', `${this.elementId}_legend`)
3167
+ .attr('class', 'websy-chart-legend')
3168
+ this.legend = new WebsyDesigns.Legend(`${this.elementId}_legend`, {})
2665
3169
  this.prep()
2666
3170
  }
2667
3171
  }
@@ -2673,6 +3177,20 @@ class WebsyChart {
2673
3177
  this.options.data = d
2674
3178
  this.render()
2675
3179
  }
3180
+ close () {
3181
+ this.leftAxisLayer.selectAll('*').remove()
3182
+ this.rightAxisLayer.selectAll('*').remove()
3183
+ this.bottomAxisLayer.selectAll('*').remove()
3184
+ this.leftAxisLabel.selectAll('*').remove()
3185
+ this.rightAxisLabel.selectAll('*').remove()
3186
+ this.bottomAxisLabel.selectAll('*').remove()
3187
+ this.plotArea.selectAll('*').remove()
3188
+ this.areaLayer.selectAll('*').remove()
3189
+ this.lineLayer.selectAll('*').remove()
3190
+ this.barLayer.selectAll('*').remove()
3191
+ this.labelLayer.selectAll('*').remove()
3192
+ this.symbolLayer.selectAll('*').remove()
3193
+ }
2676
3194
  createDomain (side) {
2677
3195
  let domain = []
2678
3196
  /* global d3 side domain:writable */
@@ -2711,84 +3229,175 @@ if (this.options.data[side].scale === 'Time') {
2711
3229
  .attr('stroke-opacity', 0)
2712
3230
  this.tooltip.hide()
2713
3231
  }
2714
- handleEventMouseMove (event, d) {
2715
- // console.log('mouse move', event, d, d3.pointer(event))
3232
+ handleEventMouseMove (event, d) {
2716
3233
  let bisectDate = d3.bisector(d => {
2717
3234
  return this.parseX(d.x.value)
2718
3235
  }).left
2719
3236
  if (this.options.showTrackingLine === true && d3.pointer(event)) {
3237
+ let xAxis = 'bottomAxis'
3238
+ let xData = 'bottom'
2720
3239
  let x0 = d3.pointer(event)[0]
3240
+ if (this.options.orientation === 'horizontal') {
3241
+ xAxis = 'leftAxis'
3242
+ xData = 'left'
3243
+ x0 = d3.pointer(event)[1]
3244
+ }
2721
3245
  let xPoint
2722
3246
  let data
2723
3247
  let tooltipHTML = ''
2724
3248
  let tooltipTitle = ''
2725
- let tooltipData = []
2726
- if (this.bottomAxis.invert) {
2727
- x0 = this.bottomAxis.invert(x0)
2728
- this.options.data.series.forEach(s => {
2729
- let index = bisectDate(s.data, x0, 1)
3249
+ let tooltipData = []
3250
+ if (!this[xAxis].invert) {
3251
+ this[xAxis].invert = this.invertOverride
3252
+ }
3253
+ x0 = this[xAxis].invert(x0)
3254
+ let xDiff
3255
+ if (typeof x0 === 'undefined') {
3256
+ this.tooltip.hide()
3257
+ return
3258
+ }
3259
+ let xLabel = this[xAxis].domain()[x0]
3260
+ if (this.options.orientation === 'horizontal') {
3261
+ xLabel = [...this[xAxis].domain().reverse()][x0]
3262
+ }
3263
+ this.options.data.series.forEach(s => {
3264
+ if (this.options.data[xData].scale !== 'Time') {
3265
+ xPoint = this[xAxis](this.parseX(xLabel))
3266
+ s.data.forEach(d => {
3267
+ if (d.x.value === xLabel) {
3268
+ if (!d.y.color) {
3269
+ d.y.color = s.color
3270
+ }
3271
+ tooltipData.push(d.y)
3272
+ }
3273
+ })
3274
+ }
3275
+ else {
3276
+ let index = bisectDate(s.data, x0, 1)
2730
3277
  let pointA = s.data[index - 1]
2731
3278
  let pointB = s.data[index]
2732
- if (pointA) {
2733
- xPoint = this.bottomAxis(this.parseX(pointA.x.value))
3279
+ if (this.options.orientation === 'horizontal') {
3280
+ pointA = [...s.data].reverse()[index - 1]
3281
+ pointB = [...s.data].reverse()[index]
3282
+ }
3283
+ if (pointA && !pointB) {
3284
+ xPoint = this[xAxis](this.parseX(pointA.x.value))
2734
3285
  tooltipTitle = pointA.x.value
3286
+ if (!pointA.y.color) {
3287
+ pointA.y.color = s.color
3288
+ }
3289
+ tooltipData.push(pointA.y)
2735
3290
  if (typeof pointA.x.value.getTime !== 'undefined') {
2736
- tooltipTitle = d3.timeFormat(this.options.dateFormat)(pointA.x.value)
3291
+ tooltipTitle = d3.timeFormat(this.options.dateFormat || this.options.calculatedTimeFormatPattern)(pointA.x.value)
2737
3292
  }
2738
3293
  }
3294
+ if (pointB && !pointA) {
3295
+ xPoint = this[xAxis](this.parseX(pointB.x.value))
3296
+ tooltipTitle = pointB.x.value
3297
+ if (!pointB.y.color) {
3298
+ pointB.y.color = s.color
3299
+ }
3300
+ tooltipData.push(pointB.y)
3301
+ if (typeof pointB.x.value.getTime !== 'undefined') {
3302
+ tooltipTitle = d3.timeFormat(this.options.dateFormat || this.options.calculatedTimeFormatPattern)(pointB.x.value)
3303
+ }
3304
+ }
2739
3305
  if (pointA && pointB) {
2740
- let d0 = this.bottomAxis(this.parseX(pointA.x.value))
2741
- let d1 = this.bottomAxis(this.parseX(pointB.x.value))
3306
+ let d0 = this[xAxis](this.parseX(pointA.x.value))
3307
+ let d1 = this[xAxis](this.parseX(pointB.x.value))
2742
3308
  let mid = Math.abs(d0 - d1) / 2
2743
- if (d3.pointer(event)[0] - d0 >= mid) {
3309
+ if (d3.pointer(event)[0] - d0 >= mid) {
2744
3310
  xPoint = d1
2745
3311
  tooltipTitle = pointB.x.value
2746
3312
  if (typeof pointB.x.value.getTime !== 'undefined') {
2747
- tooltipTitle = d3.timeFormat(this.options.dateFormat)(pointB.x.value)
3313
+ tooltipTitle = d3.timeFormat(this.options.dateFormat || this.options.calculatedTimeFormatPattern)(pointB.x.value)
2748
3314
  }
3315
+ if (!pointB.y.color) {
3316
+ pointB.y.color = s.color
3317
+ }
2749
3318
  tooltipData.push(pointB.y)
2750
3319
  }
2751
3320
  else {
2752
- xPoint = d0
3321
+ xPoint = d0
3322
+ tooltipTitle = pointA.x.value
3323
+ if (typeof pointB.x.value.getTime !== 'undefined') {
3324
+ tooltipTitle = d3.timeFormat(this.options.dateFormat || this.options.calculatedTimeFormatPattern)(pointB.x.value)
3325
+ }
3326
+ if (!pointA.y.color) {
3327
+ pointA.y.color = s.color
3328
+ }
2753
3329
  tooltipData.push(pointA.y)
2754
3330
  }
2755
3331
  }
2756
- })
2757
- tooltipHTML = `
2758
- <ul>
2759
- `
2760
- tooltipHTML += tooltipData.map(d => `
2761
- <li>
2762
- <i style='background-color: ${d.color};'></i>
2763
- ${d.tooltipLabel || ''}<span>${d.tooltipValue || d.value}</span>
2764
- </li>
2765
- `).join('')
2766
- tooltipHTML += `</ul>`
2767
- let posOptions = {
3332
+ }
3333
+ })
3334
+ tooltipHTML = `
3335
+ <ul>
3336
+ `
3337
+ tooltipHTML += tooltipData.map(d => `
3338
+ <li>
3339
+ <i style='background-color: ${d.color};'></i>
3340
+ ${d.tooltipLabel || ''}<span> - ${d.tooltipValue || d.value}</span>
3341
+ </li>
3342
+ `).join('')
3343
+ tooltipHTML += `</ul>`
3344
+ let posOptions = {
3345
+ width: this.options.tooltipWidth,
3346
+ left: 0,
3347
+ top: 0,
3348
+ onLeft: xPoint > this.plotWidth / 2
3349
+ }
3350
+ if (xPoint > this.plotWidth / 2) {
3351
+ posOptions.left = xPoint - this.options.tooltipWidth - 15
3352
+ }
3353
+ else {
3354
+ posOptions.left = xPoint + this.options.margin.left + this.options.margin.axisLeft + 15
3355
+ }
3356
+ posOptions.top = this.options.margin.top + this.options.margin.axisTop
3357
+ if (this.options.orientation === 'horizontal') {
3358
+ delete posOptions.onLeft
3359
+ let adjuster = 0
3360
+ if (this.options.data[xData].scale !== 'Time') {
3361
+ adjuster = (this[xAxis].bandwidth() / 2) // - this.options.margin.top
3362
+ }
3363
+ posOptions = {
2768
3364
  width: this.options.tooltipWidth,
2769
- left: 0,
2770
- top: 0,
2771
- onLeft: xPoint > this.plotWidth / 2
3365
+ left: this.options.margin.left + this.options.margin.axisLeft + this.plotWidth - this.options.tooltipWidth,
3366
+ onTop: xPoint > this.plotHeight / 2,
3367
+ positioning: 'vertical'
2772
3368
  }
2773
- if (xPoint > this.plotWidth / 2) {
2774
- posOptions.left = xPoint - this.options.tooltipWidth - 15
3369
+ if (xPoint > this.plotHeight / 2) {
3370
+ posOptions.bottom = xPoint + this.options.margin.top + this.options.margin.axisTop
2775
3371
  }
2776
3372
  else {
2777
- posOptions.left = xPoint + this.options.margin.left + this.options.margin.axisLeft + 15
3373
+ posOptions.top = xPoint + this.options.margin.top + this.options.margin.axisTop + 15 + adjuster
2778
3374
  }
2779
- posOptions.top = this.options.margin.top + this.options.margin.axisTop
2780
- this.tooltip.show(tooltipTitle, tooltipHTML, posOptions)
2781
- // data = this.bottomAxis(data)
2782
- }
2783
- else {
2784
- xPoint = x0
2785
3375
  }
3376
+ this.tooltip.setHeight(this.plotHeight)
3377
+ this.tooltip.show(tooltipTitle, tooltipHTML, posOptions)
3378
+ // }
3379
+ // else {
3380
+ // xPoint = x0
3381
+ // }
3382
+ if (this.options.data[xData].scale !== 'Time') {
3383
+ xPoint += (this[xAxis].bandwidth() / 2) // - this.options.margin.top
3384
+ }
3385
+ let trackingXStart = xPoint
3386
+ let trackingXEnd = xPoint
3387
+ let trackingYStart = 0
3388
+ let trackingYEnd = this.plotHeight
3389
+ if (this.options.orientation === 'horizontal') {
3390
+ trackingXStart = 0
3391
+ trackingXEnd = this.plotWidth
3392
+ trackingYStart = xPoint
3393
+ trackingYEnd = xPoint
3394
+ }
2786
3395
  this.trackingLineLayer
2787
3396
  .select('.tracking-line')
2788
- .attr('x1', xPoint)
2789
- .attr('x2', xPoint)
2790
- .attr('y1', 0)
2791
- .attr('y2', this.plotHeight)
3397
+ .attr('x1', trackingXStart)
3398
+ .attr('x2', trackingXEnd)
3399
+ .attr('y1', trackingYStart)
3400
+ .attr('y2', trackingYEnd)
2792
3401
  .attr('stroke-width', 1)
2793
3402
  .attr('stroke-dasharray', '4 2')
2794
3403
  .attr('stroke', '#cccccc')
@@ -2797,22 +3406,22 @@ if (this.options.data[side].scale === 'Time') {
2797
3406
  }
2798
3407
  prep () {
2799
3408
  /* global d3 WebsyDesigns */
2800
- this.leftAxisLayer = this.svg.append('g')
2801
- this.rightAxisLayer = this.svg.append('g')
2802
- this.bottomAxisLayer = this.svg.append('g')
2803
- this.leftAxisLabel = this.svg.append('g')
2804
- this.rightAxisLabel = this.svg.append('g')
2805
- this.bottomAxisLabel = this.svg.append('g')
2806
- this.plotArea = this.svg.append('g')
2807
- this.areaLayer = this.svg.append('g')
2808
- this.lineLayer = this.svg.append('g')
2809
- this.barLayer = this.svg.append('g')
2810
- this.labelLayer = this.svg.append('g')
2811
- this.symbolLayer = this.svg.append('g')
2812
- this.trackingLineLayer = this.svg.append('g')
3409
+ this.leftAxisLayer = this.svg.append('g').attr('class', 'left-axis-layer')
3410
+ this.rightAxisLayer = this.svg.append('g').attr('class', 'right-axis-layer')
3411
+ this.bottomAxisLayer = this.svg.append('g').attr('class', 'bottom-axis-layer')
3412
+ this.leftAxisLabel = this.svg.append('g').attr('class', 'left-axis-label-layer')
3413
+ this.rightAxisLabel = this.svg.append('g').attr('class', 'right-axis-label-layer')
3414
+ this.bottomAxisLabel = this.svg.append('g').attr('class', 'bottom-axis-label-layer')
3415
+ this.plotArea = this.svg.append('g').attr('class', 'plot-layer')
3416
+ this.areaLayer = this.svg.append('g').attr('class', 'area-layer')
3417
+ this.lineLayer = this.svg.append('g').attr('class', 'line-layer')
3418
+ this.barLayer = this.svg.append('g').attr('class', 'bar-layer')
3419
+ this.labelLayer = this.svg.append('g').attr('class', 'label-layer')
3420
+ this.symbolLayer = this.svg.append('g').attr('class', 'symbol-layer')
3421
+ this.trackingLineLayer = this.svg.append('g').attr('class', 'tracking-line-layer')
2813
3422
  this.trackingLineLayer.append('line').attr('class', 'tracking-line')
2814
3423
  this.tooltip = new WebsyDesigns.WebsyChartTooltip(this.svg)
2815
- this.eventLayer = this.svg.append('g').append('rect')
3424
+ this.eventLayer = this.svg.append('g').attr('class', 'event-line').append('rect')
2816
3425
  this.eventLayer
2817
3426
  .on('mouseout', this.handleEventMouseOut.bind(this))
2818
3427
  .on('mousemove', this.handleEventMouseMove.bind(this))
@@ -2871,9 +3480,44 @@ else {
2871
3480
  if (el) {
2872
3481
  this.width = el.clientWidth
2873
3482
  this.height = el.clientHeight
3483
+ // establish the space and size for the legend
3484
+ // the legend gets rendered so that we can get its actual size
3485
+ if (this.options.showLegend === true) {
3486
+ let legendData = this.options.data.series.map((s, i) => ({value: s.label || s.key, color: s.color || this.options.colors[i % this.options.colors.length]}))
3487
+ if (this.options.legendPosition === 'top' || this.options.legendPosition === 'bottom') {
3488
+ this.legendArea.style('width', '100%')
3489
+ }
3490
+ if (this.options.legendPosition === 'left' || this.options.legendPosition === 'right') {
3491
+ this.legendArea.style('height', '100%')
3492
+ this.legendArea.style('width', this.legend.testWidth(d3.max(legendData.map(d => d.value))) + 'px')
3493
+ }
3494
+ this.legend.data = legendData
3495
+ let legendSize = this.legend.getSize()
3496
+ this.options.margin.legendTop = 0
3497
+ this.options.margin.legendBottom = 0
3498
+ this.options.margin.legendLeft = 0
3499
+ this.options.margin.legendRight = 0
3500
+ if (this.options.legendPosition === 'top') {
3501
+ this.options.margin.legendTop = legendSize.height
3502
+ this.legendArea.style('top', '0').style('bottom', 'unset')
3503
+ }
3504
+ if (this.options.legendPosition === 'bottom') {
3505
+ this.options.margin.legendBottom = legendSize.height
3506
+ this.legendArea.style('top', 'unset').style('bottom', '0')
3507
+ }
3508
+ if (this.options.legendPosition === 'left') {
3509
+ this.options.margin.legendLeft = legendSize.width
3510
+ this.legendArea.style('left', '0').style('right', 'unset').style('top', '0')
3511
+ }
3512
+ if (this.options.legendPosition === 'right') {
3513
+ this.options.margin.legendRight = legendSize.width
3514
+ this.legendArea.style('left', 'unset').style('right', '0').style('top', '0')
3515
+ }
3516
+ }
2874
3517
  this.svg
2875
- .attr('width', this.width)
2876
- .attr('height', this.height)
3518
+ .attr('width', this.width - this.options.margin.legendLeft - this.options.margin.legendRight)
3519
+ .attr('height', this.height - this.options.margin.legendTop - this.options.margin.legendBottom)
3520
+ .attr('transform', `translate(${this.options.margin.legendLeft}, ${this.options.margin.legendTop})`)
2877
3521
  this.longestLeft = 0
2878
3522
  this.longestRight = 0
2879
3523
  this.longestBottom = 0
@@ -2919,7 +3563,7 @@ else {
2919
3563
  this.options.margin.axisLeft = this.longestLeft * ((this.options.data.left && this.options.data.left.fontSize) || this.options.fontSize) * 0.7
2920
3564
  this.options.margin.axisRight = this.longestRight * ((this.options.data.right && this.options.data.right.fontSize) || this.options.fontSize) * 0.7
2921
3565
  this.options.margin.axisBottom = ((this.options.data.bottom && this.options.data.bottom.fontSize) || this.options.fontSize) + 10
2922
- this.options.margin.axisTop = 0
3566
+ this.options.margin.axisTop = 0
2923
3567
  // adjust axis margins based on title options
2924
3568
  if (this.options.data.left && this.options.data.left.showTitle === true) {
2925
3569
  if (this.options.data.left.titlePosition === 1) {
@@ -2960,15 +3604,18 @@ else {
2960
3604
  }
2961
3605
  }
2962
3606
  // Define the plot size
2963
- this.plotWidth = this.width - this.options.margin.left - this.options.margin.right - this.options.margin.axisLeft - this.options.margin.axisRight
2964
- this.plotHeight = this.height - this.options.margin.top - this.options.margin.bottom - this.options.margin.axisBottom - this.options.margin.axisTop
3607
+ this.plotWidth = this.width - this.options.margin.legendLeft - this.options.margin.legendRight - this.options.margin.left - this.options.margin.right - this.options.margin.axisLeft - this.options.margin.axisRight
3608
+ this.plotHeight = this.height - this.options.margin.legendTop - this.options.margin.legendBottom - this.options.margin.top - this.options.margin.bottom - this.options.margin.axisBottom - this.options.margin.axisTop
2965
3609
  // Translate the layers
2966
3610
  this.leftAxisLayer
2967
3611
  .attr('transform', `translate(${this.options.margin.left + this.options.margin.axisLeft}, ${this.options.margin.top + this.options.margin.axisTop})`)
3612
+ .style('font-size', (this.options.data.left && this.options.data.left.fontSize) || this.options.fontSize)
2968
3613
  this.rightAxisLayer
2969
3614
  .attr('transform', `translate(${this.options.margin.left + this.plotWidth + this.options.margin.axisLeft}, ${this.options.margin.top + this.options.margin.axisTop})`)
3615
+ .style('font-size', (this.options.data.right && this.options.data.right.fontSize) || this.options.fontSize)
2970
3616
  this.bottomAxisLayer
2971
3617
  .attr('transform', `translate(${this.options.margin.left + this.options.margin.axisLeft}, ${this.options.margin.top + this.options.margin.axisTop + this.plotHeight})`)
3618
+ .style('font-size', (this.options.data.bottom && this.options.data.bottom.fontSize) || this.options.fontSize)
2972
3619
  this.leftAxisLabel
2973
3620
  .attr('transform', `translate(${this.options.margin.left}, ${this.options.margin.top + this.options.margin.axisTop})`)
2974
3621
  this.rightAxisLabel
@@ -3011,28 +3658,55 @@ else {
3011
3658
  this.bottomAxis.padding(this.options.data.bottom.padding || 0)
3012
3659
  }
3013
3660
  if (this.options.margin.axisBottom > 0) {
3661
+ let timeFormatPattern = ''
3014
3662
  let tickDefinition
3015
3663
  if (this.options.data.bottom.data) {
3016
3664
  if (this.options.data.bottom.scale === 'Time') {
3017
- let diff = this.options.data.bottom.max.getTime() - this.options.data.bottom.min.getTime()
3665
+ let diff = this.options.data.bottom.max.getTime() - this.options.data.bottom.min.getTime()
3018
3666
  let oneDay = 1000 * 60 * 60 * 24
3019
- if (diff < 7 * oneDay) {
3667
+ if (diff < (oneDay / 24 / 6)) {
3668
+ tickDefinition = d3.timeSecond.every(15)
3669
+ timeFormatPattern = '%H:%M:%S'
3670
+ }
3671
+ else if (diff < (oneDay / 24)) {
3672
+ tickDefinition = d3.timeMinute.every(1)
3673
+ timeFormatPattern = '%H:%M'
3674
+ }
3675
+ else if (diff < (oneDay / 6)) {
3676
+ tickDefinition = d3.timeMinute.every(10)
3677
+ timeFormatPattern = '%H:%M'
3678
+ }
3679
+ else if (diff < (oneDay / 2)) {
3680
+ tickDefinition = d3.timeMinute.every(30)
3681
+ timeFormatPattern = '%H:%M'
3682
+ }
3683
+ else if (diff < oneDay) {
3684
+ tickDefinition = d3.timeHour.every(1)
3685
+ timeFormatPattern = '%H:%M'
3686
+ }
3687
+ else if (diff < 7 * oneDay) {
3020
3688
  tickDefinition = d3.timeDay.every(1)
3689
+ timeFormatPattern = '%d %b @ %H:%M'
3021
3690
  }
3022
3691
  else if (diff < 14 * oneDay) {
3023
3692
  tickDefinition = d3.timeDay.every(2)
3693
+ timeFormatPattern = '%d %b %Y'
3024
3694
  }
3025
3695
  else if (diff < 21 * oneDay) {
3026
3696
  tickDefinition = d3.timeDay.every(3)
3697
+ timeFormatPattern = '%d %b %Y'
3027
3698
  }
3028
3699
  else if (diff < 28 * oneDay) {
3029
3700
  tickDefinition = d3.timeDay.every(4)
3701
+ timeFormatPattern = '%d %b %Y'
3030
3702
  }
3031
3703
  else if (diff < 60 * oneDay) {
3032
3704
  tickDefinition = d3.timeDay.every(7)
3705
+ timeFormatPattern = '%d %b %Y'
3033
3706
  }
3034
3707
  else {
3035
3708
  tickDefinition = d3.timeMonth.every(1)
3709
+ timeFormatPattern = '%b %Y'
3036
3710
  }
3037
3711
  }
3038
3712
  else {
@@ -3041,14 +3715,18 @@ else {
3041
3715
  }
3042
3716
  else {
3043
3717
  tickDefinition = this.options.data.bottom.ticks || 5
3044
- }
3718
+ }
3719
+ this.options.calculatedTimeFormatPattern = timeFormatPattern
3045
3720
  let bAxisFunc = d3.axisBottom(this.bottomAxis)
3046
3721
  // .ticks(this.options.data.bottom.ticks || Math.min(this.options.data.bottom.data.length, 5))
3047
3722
  .ticks(tickDefinition)
3723
+ console.log('tickDefinition', tickDefinition)
3724
+ console.log(bAxisFunc)
3048
3725
  if (this.options.data.bottom.formatter) {
3049
3726
  bAxisFunc.tickFormat(d => this.options.data.bottom.formatter(d))
3050
3727
  }
3051
3728
  this.bottomAxisLayer.call(bAxisFunc)
3729
+ console.log(this.bottomAxisLayer.ticks)
3052
3730
  if (this.options.data.bottom.rotate) {
3053
3731
  this.bottomAxisLayer.selectAll('text')
3054
3732
  .attr('transform', `rotate(${this.options.data.bottom.rotate})`)
@@ -3126,7 +3804,8 @@ else {
3126
3804
  if (this.rightAxis.nice) {
3127
3805
  this.rightAxis.nice()
3128
3806
  }
3129
- if (this.options.margin.axisRight > 0) {
3807
+ console.log('axis right', this.options.margin.axisRight)
3808
+ if (this.options.margin.axisRight > 0 && (this.options.data.right.min !== 0 || this.options.data.right.max !== 0)) {
3130
3809
  this.rightAxisLayer.call(
3131
3810
  d3.axisRight(this.rightAxis)
3132
3811
  .ticks(this.options.data.left.ticks || 5)
@@ -3232,7 +3911,7 @@ areas.enter().append('path')
3232
3911
  // .style('fill-opacity', 0)
3233
3912
  .attr('stroke', 'transparent')
3234
3913
  // .transition(this.transition)
3235
- .style('fill-opacity', series.opacity || 1)
3914
+ .style('fill-opacity', series.opacity || 0.5)
3236
3915
 
3237
3916
  }
3238
3917
  renderbar (series, index) {
@@ -3240,12 +3919,16 @@ areas.enter().append('path')
3240
3919
  let xAxis = 'bottomAxis'
3241
3920
  let yAxis = 'leftAxis'
3242
3921
  let bars = this.barLayer.selectAll(`.bar_${series.key}`).data(series.data)
3922
+ let acummulativeY = new Array(this.options.data.series.length).fill(0)
3243
3923
  if (this.options.orientation === 'horizontal') {
3244
3924
  xAxis = 'leftAxis'
3245
3925
  yAxis = 'bottomAxis'
3246
3926
  }
3247
3927
  let barWidth = this[xAxis].bandwidth()
3248
- function getBarHeight (d) {
3928
+ if (this.options.data.series.length > 1 && this.options.grouping !== 'stacked') {
3929
+ barWidth = barWidth / this.options.data.series.length - 4
3930
+ }
3931
+ function getBarHeight (d, i) {
3249
3932
  if (this.options.orientation === 'horizontal') {
3250
3933
  return barWidth
3251
3934
  }
@@ -3253,28 +3936,50 @@ function getBarHeight (d) {
3253
3936
  return this[yAxis](d.y.value)
3254
3937
  }
3255
3938
  }
3256
- function getBarWidth (d) {
3939
+ function getBarWidth (d, i) {
3257
3940
  if (this.options.orientation === 'horizontal') {
3258
- return this[yAxis](d.y.value)
3941
+ let width = this[yAxis](d.y.value)
3942
+ acummulativeY[d.y.index] += width
3943
+ return width
3259
3944
  }
3260
3945
  else {
3261
3946
  return barWidth
3262
3947
  }
3263
3948
  }
3264
- function getBarX (d) {
3949
+ function getBarX (d, i) {
3265
3950
  if (this.options.orientation === 'horizontal') {
3266
- return 0
3951
+ if (this.options.grouping === 'stacked') {
3952
+ return this[yAxis](d.y.accumulative)
3953
+ }
3954
+ else {
3955
+ return 0
3956
+ }
3267
3957
  }
3268
3958
  else {
3269
- return this[xAxis](this.parseX(d.x.value))
3959
+ if (this.options.grouping === 'stacked') {
3960
+ return this[xAxis](this.parseX(d.x.value))
3961
+ }
3962
+ else {
3963
+ return this[xAxis](this.parseX(d.x.value)) + (i * barWidth)
3964
+ }
3270
3965
  }
3271
3966
  }
3272
- function getBarY (d) {
3967
+ function getBarY (d, i) {
3273
3968
  if (this.options.orientation === 'horizontal') {
3274
- return this[xAxis](this.parseX(d.x.value))
3969
+ if (this.options.grouping === 'stacked') {
3970
+ return this[xAxis](this.parseX(d.x.value))
3971
+ }
3972
+ else {
3973
+ return this[xAxis](this.parseX(d.x.value)) + ((d.y.index || i) * barWidth)
3974
+ }
3275
3975
  }
3276
3976
  else {
3277
- return this[yAxis](isNaN(d.y.value) ? 0 : d.y.value)
3977
+ if (this.options.grouping === 'stacked') {
3978
+ return this[yAxis](d.y.accumulative)
3979
+ }
3980
+ else {
3981
+ return 0
3982
+ }
3278
3983
  }
3279
3984
  }
3280
3985
  bars
@@ -3306,7 +4011,7 @@ bars
3306
4011
 
3307
4012
  }
3308
4013
  renderLabels (series, index) {
3309
- /* global series index d3 */
4014
+ /* global series index d3 WebsyDesigns */
3310
4015
  let xAxis = 'bottomAxis'
3311
4016
  let yAxis = 'leftAxis'
3312
4017
  let that = this
@@ -3329,6 +4034,7 @@ if (this.options.showLabels) {
3329
4034
  .attr('y', getLabelY.bind(this))
3330
4035
  .attr('class', `label_${series.key}`)
3331
4036
  .style('font-size', `${this.options.labelSize || this.options.fontSize}px`)
4037
+ .style('fill', this.options.labelColor || WebsyDesigns.WebsyUtils.getLightDark(series.color))
3332
4038
  .transition(this.transition)
3333
4039
  .text(d => d.y.label || d.y.value)
3334
4040
 
@@ -3341,6 +4047,7 @@ if (this.options.showLabels) {
3341
4047
  .attr('alignment-baseline', 'central')
3342
4048
  .attr('text-anchor', this.options.orientation === 'horizontal' ? 'left' : 'middle')
3343
4049
  .style('font-size', `${this.options.labelSize || this.options.fontSize}px`)
4050
+ .style('fill', this.options.labelColor || WebsyDesigns.WebsyUtils.getLightDark(series.color))
3344
4051
  .text(d => d.y.label || d.y.value)
3345
4052
  .each(function (d, i) {
3346
4053
  if (that.options.orientation === 'horizontal') {
@@ -3359,18 +4066,28 @@ if (this.options.showLabels) {
3359
4066
 
3360
4067
  function getLabelX (d) {
3361
4068
  if (this.options.orientation === 'horizontal') {
3362
- return this[yAxis](isNaN(d.y.value) ? 0 : d.y.value) + 4
4069
+ if (this.options.grouping === 'stacked') {
4070
+ return this[yAxis](d.y.accumulative) + (this[yAxis](d.y.value) / 2)
4071
+ }
4072
+ else {
4073
+ return this[yAxis](isNaN(d.y.value) ? 0 : d.y.value) + 4
4074
+ }
3363
4075
  }
3364
- else {
4076
+ else {
3365
4077
  return this[xAxis](this.parseX(d.x.value)) + (this[xAxis].bandwidth() / 2)
3366
4078
  }
3367
4079
  }
3368
4080
  function getLabelY (d) {
3369
- if (this.options.orientation === 'horizontal') {
4081
+ if (this.options.orientation === 'horizontal') {
3370
4082
  return this[xAxis](this.parseX(d.x.value)) + (this[xAxis].bandwidth() / 2)
3371
4083
  }
3372
4084
  else {
3373
- return this[yAxis](isNaN(d.y.value) ? 0 : d.y.value) - 4
4085
+ if (this.options.grouping === 'stacked') {
4086
+ //
4087
+ }
4088
+ else {
4089
+ return this[yAxis](isNaN(d.y.value) ? 0 : d.y.value) - 4
4090
+ }
3374
4091
  }
3375
4092
  }
3376
4093
 
@@ -3480,8 +4197,9 @@ if (el) {
3480
4197
  this.width = el.clientWidth
3481
4198
  this.height = el.clientHeight
3482
4199
  this.svg
3483
- .attr('width', this.width)
3484
- .attr('height', this.height)
4200
+ .attr('width', this.width - this.options.margin.legendLeft - this.options.margin.legendRight)
4201
+ .attr('height', this.height - this.options.margin.legendTop - this.options.margin.legendBottom)
4202
+ .attr('transform', `translate(${this.options.margin.legendLeft}, ${this.options.margin.legendTop})`)
3485
4203
  // Define the plot height
3486
4204
  // this.plotWidth = this.width - this.options.margin.left - this.options.margin.right - this.options.margin.axisLeft - this.options.margin.axisRight
3487
4205
  // this.plotHeight = this.height - this.options.margin.top - this.options.margin.bottom - this.options.margin.axisBottom
@@ -3569,6 +4287,99 @@ if (el) {
3569
4287
  }
3570
4288
  }
3571
4289
 
4290
+ class WebsyLegend {
4291
+ constructor (elementId, options) {
4292
+ const DEFAULTS = {
4293
+ align: 'center',
4294
+ direction: 'horizontal',
4295
+ style: 'circle',
4296
+ symbolSize: 16,
4297
+ hPadding: 20,
4298
+ vPadding: 10
4299
+ }
4300
+ this.elementId = elementId
4301
+ this.options = Object.assign({}, DEFAULTS, options)
4302
+ this._data = []
4303
+ if (!elementId) {
4304
+ console.log('No element Id provided for Websy Chart')
4305
+ return
4306
+ }
4307
+ const el = document.getElementById(this.elementId)
4308
+ if (el) {
4309
+ el.classList.add('websy-legend')
4310
+ this.render()
4311
+ }
4312
+ else {
4313
+ console.error(`No element found with ID ${this.elementId}`)
4314
+ }
4315
+ }
4316
+ getLegendItemHTML (d) {
4317
+ return `
4318
+ <div
4319
+ class='websy-legend-item ${this.options.direction}'
4320
+ style='margin: ${this.options.vPadding / 2}px ${this.options.hPadding / 2}px;'
4321
+ >
4322
+ <span
4323
+ class='symbol ${d.style || this.options.style}'
4324
+ style='
4325
+ background-color: ${d.color};
4326
+ width: ${this.options.symbolSize}px;
4327
+ height: ${this.options.style === 'line' ? 3 : this.options.symbolSize}px;
4328
+ '
4329
+ ></span>
4330
+ ${d.value}
4331
+ </div>
4332
+ `
4333
+ }
4334
+ getSize () {
4335
+ const el = document.getElementById(this.elementId)
4336
+ if (el) {
4337
+ return {
4338
+ width: el.clientWidth,
4339
+ height: el.clientHeight
4340
+ }
4341
+ }
4342
+ }
4343
+ set data (d) {
4344
+ this._data = d
4345
+ this.render()
4346
+ }
4347
+ render () {
4348
+ this.resize()
4349
+ }
4350
+ resize () {
4351
+ const el = document.getElementById(this.elementId)
4352
+ if (el) {
4353
+ // if (this.options.width) {
4354
+ // el.width = this.options.width
4355
+ // }
4356
+ // if (this.options.height) {
4357
+ // el.height = this.options.height
4358
+ // }
4359
+ let html = `
4360
+ <div class='text-${this.options.align}'>
4361
+ `
4362
+ html += this._data.map((d, i) => this.getLegendItemHTML(d)).join('')
4363
+ html += `
4364
+ <div>
4365
+ `
4366
+ el.innerHTML = html
4367
+ }
4368
+ }
4369
+ testWidth (v) {
4370
+ let html = this.getLegendItemHTML({value: v})
4371
+ const el = document.createElement('div')
4372
+ el.style.position = 'absolute'
4373
+ // el.style.width = '100vw'
4374
+ el.style.visibility = 'hidden'
4375
+ el.innerHTML = html
4376
+ document.body.appendChild(el)
4377
+ let w = el.clientWidth + 30 // for padding
4378
+ el.remove()
4379
+ return w
4380
+ }
4381
+ }
4382
+
3572
4383
  /* global */
3573
4384
  class WebsyKPI {
3574
4385
  constructor (elementId, options) {
@@ -3638,17 +4449,20 @@ class WebsyKPI {
3638
4449
  }
3639
4450
  }
3640
4451
 
3641
- /* global L */
4452
+ /* global d3 L WebsyDesigns */
3642
4453
  class WebsyMap {
3643
4454
  constructor (elementId, options) {
3644
4455
  const DEFAULTS = {
3645
- tileUrl: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
4456
+ tileUrl: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
3646
4457
  disablePan: false,
3647
4458
  disableZoom: false,
3648
4459
  markerSize: 10,
3649
4460
  useClustering: false,
3650
4461
  maxMarkerSize: 50,
3651
- minMarkerSize: 20
4462
+ minMarkerSize: 20,
4463
+ data: {},
4464
+ legendPosition: 'bottom',
4465
+ colors: ['#5e4fa2', '#3288bd', '#66c2a5', '#abdda4', '#e6f598', '#fee08b', '#fdae61', '#f46d43', '#d53e4f', '#9e0142']
3652
4466
  }
3653
4467
  this.elementId = elementId
3654
4468
  this.options = Object.assign({}, DEFAULTS, options)
@@ -3674,8 +4488,13 @@ class WebsyMap {
3674
4488
  if (typeof L === 'undefined') {
3675
4489
  console.error('Leaflet library has not been loaded')
3676
4490
  }
4491
+ el.innerHTML = `
4492
+ <div id="${this.elementId}_map"></div>
4493
+ <div id="${this.elementId}_legend" class="websy-map-legend"></div>
4494
+ `
3677
4495
  el.addEventListener('click', this.handleClick.bind(this))
3678
- this.map = L.map(this.elementId, mapOptions)
4496
+ this.legend = new WebsyDesigns.Legend(`${this.elementId}_legend`, {})
4497
+ this.map = L.map(`${this.elementId}_map`, mapOptions)
3679
4498
  this.render()
3680
4499
  }
3681
4500
  }
@@ -3686,14 +4505,60 @@ class WebsyMap {
3686
4505
 
3687
4506
  }
3688
4507
  render () {
3689
- const el = document.getElementById(`${this.options.elementId}_map`)
3690
-
4508
+ const mapEl = document.getElementById(`${this.elementId}_map`)
4509
+ const legendEl = document.getElementById(`${this.elementId}_map`)
4510
+ if (this.options.showLegend === true) {
4511
+ let legendData = this.options.data.polygons.map((s, i) => ({value: s.label || s.key, color: s.color || this.options.colors[i % this.options.colors.length]}))
4512
+ let longestValue = legendData.map(s => s.value).reduce((a, b) => a.length > b.length ? a : b)
4513
+ if (this.options.legendPosition === 'top' || this.options.legendPosition === 'bottom') {
4514
+ legendEl.style.width = '100%'
4515
+ }
4516
+ if (this.options.legendPosition === 'left' || this.options.legendPosition === 'right') {
4517
+ legendEl.style.height = '100%'
4518
+ legendEl.style.width = this.legend.testWidth(longestValue) + 'px'
4519
+ }
4520
+ this.legend.data = legendData
4521
+ let legendSize = this.legend.getSize()
4522
+ mapEl.style.position = 'relative'
4523
+ if (this.options.legendPosition === 'top') {
4524
+ legendEl.style.top = 0
4525
+ legendEl.style.bottom = 'unset'
4526
+ mapEl.style.top = legendSize.height
4527
+ mapEl.style.height = `calc(100% - ${legendSize.height}px)`
4528
+ }
4529
+ if (this.options.legendPosition === 'bottom') {
4530
+ legendEl.style.top = 'unset'
4531
+ legendEl.style.bottom = 0
4532
+ mapEl.style.height = `calc(100% - ${legendSize.height}px)`
4533
+ }
4534
+ if (this.options.legendPosition === 'left') {
4535
+ legendEl.style.left = 0
4536
+ legendEl.style.right = 'unset'
4537
+ legendEl.style.top = 0
4538
+ mapEl.style.left = `${legendSize.width}px`
4539
+ mapEl.style.width = `calc(100% - ${legendSize.width}px)`
4540
+ }
4541
+ if (this.options.legendPosition === 'right') {
4542
+ legendEl.style.left = 'unset'
4543
+ legendEl.style.right = 0
4544
+ legendEl.style.top = 0
4545
+ mapEl.style.width = `calc(100% - ${legendSize.width}px)`
4546
+ }
4547
+ }
4548
+ else {
4549
+ mapEl.style.width = '100%'
4550
+ mapEl.style.height = '100%'
4551
+ }
3691
4552
  const t = L.tileLayer(this.options.tileUrl, {
3692
4553
  attribution: 'Map data &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
3693
4554
  }).addTo(this.map)
3694
4555
  if (this.geo) {
3695
4556
  this.map.removeLayer(this.geo)
3696
4557
  }
4558
+ if (this.polygons) {
4559
+ this.polygons.forEach(p => this.map.removeLayer(p))
4560
+ }
4561
+ this.polygons = []
3697
4562
  if (this.options.geoJSON) {
3698
4563
  this.geo = L.geoJSON(this.options.geoJSON, {
3699
4564
  style: feature => {
@@ -3716,67 +4581,58 @@ class WebsyMap {
3716
4581
  }
3717
4582
  }).addTo(this.map)
3718
4583
  }
3719
- this.markers = []
3720
- if (this.cluster) {
3721
- this.map.removeLayer(this.cluster)
3722
- }
3723
- // this.cluster = L.markerClusterGroup({
3724
- // iconCreateFunction: cluster => {
3725
- // let markerSize = this.options.minMarkerSize + ((this.options.maxMarkerSize - this.options.minMarkerSize) * (cluster.getChildCount() / this.data.length))
3726
- // console.log(this.data.length, cluster.getChildCount(), markerSize)
3727
- // return L.divIcon({
3728
- // html: `
3729
- // <div
3730
- // class='simple-marker'
3731
- // style='
3732
- // height: ${markerSize}px;
3733
- // width: ${markerSize}px;
3734
- // margin-top: -${markerSize / 2}px;
3735
- // margin-left: -${markerSize / 2}px;
3736
- // text-align: center;
3737
- // line-height: ${markerSize}px;
3738
- // '>
3739
- // ${cluster.getChildCount()}
3740
- // </div>
3741
- // `
3742
- // })
4584
+ // this.markers = []
4585
+ // this.data = [] // this.data.filter(d => d.Latitude.qNum !== 0 && d.Longitude.qNum !== 0)
4586
+ // this.data.forEach(r => {
4587
+ // // console.log(r)
4588
+ // if (r.Latitude.qNum !== 0 && r.Longitude.qNum !== 0) {
4589
+ // const markerOptions = {}
4590
+ // if (this.options.simpleMarker === true) {
4591
+ // markerOptions.icon = L.divIcon({className: 'simple-marker'})
4592
+ // }
4593
+ // if (this.options.markerUrl) {
4594
+ // markerOptions.icon = L.icon({iconUrl: this.options.markerUrl})
4595
+ // }
4596
+ // markerOptions.data = r
4597
+ // let m = L.marker([r.Latitude.qText, r.Longitude.qText], markerOptions)
4598
+ // m.on('click', this.handleMapClick.bind(this))
4599
+ // if (this.options.useClustering === false) {
4600
+ // m.addTo(this.map)
4601
+ // }
4602
+ // this.markers.push(m)
4603
+ // if (this.options.useClustering === true) {
4604
+ // this.cluster.addLayer(m)
4605
+ // }
3743
4606
  // }
3744
4607
  // })
3745
- this.data = [] // this.data.filter(d => d.Latitude.qNum !== 0 && d.Longitude.qNum !== 0)
3746
- this.data.forEach(r => {
3747
- // console.log(r)
3748
- if (r.Latitude.qNum !== 0 && r.Longitude.qNum !== 0) {
3749
- const markerOptions = {}
3750
- if (this.options.simpleMarker === true) {
3751
- markerOptions.icon = L.divIcon({className: 'simple-marker'})
3752
- }
3753
- if (this.options.markerUrl) {
3754
- markerOptions.icon = L.icon({iconUrl: this.options.markerUrl})
4608
+ if (this.options.data.polygons) {
4609
+ this.options.data.polygons.forEach((p, i) => {
4610
+ if (!p.options) {
4611
+ p.options = {}
3755
4612
  }
3756
- markerOptions.data = r
3757
- let m = L.marker([r.Latitude.qText, r.Longitude.qText], markerOptions)
3758
- m.on('click', this.handleMapClick.bind(this))
3759
- if (this.options.useClustering === false) {
3760
- m.addTo(this.map)
4613
+ if (!p.options.color) {
4614
+ p.options.color = this.options.colors[i % this.options.colors.length]
3761
4615
  }
3762
- this.markers.push(m)
3763
- if (this.options.useClustering === true) {
3764
- this.cluster.addLayer(m)
3765
- }
3766
- }
3767
- })
3768
- if (this.data.length > 0) {
3769
- el.classList.remove('hidden')
3770
- if (this.options.useClustering === true) {
3771
- this.map.addLayer(this.cluster)
3772
- }
3773
- const g = L.featureGroup(this.markers)
3774
- this.map.fitBounds(g.getBounds())
3775
- this.map.invalidateSize()
4616
+ const pol = L.polygon(p.data.map(c => c.map(d => [d.Latitude, d.Longitude])), p.options).addTo(this.map)
4617
+ this.polygons.push(pol)
4618
+ this.map.fitBounds(pol.getBounds())
4619
+ })
3776
4620
  }
3777
- else if (this.geo) {
4621
+ // if (this.data.markers.length > 0) {
4622
+ // el.classList.remove('hidden')
4623
+ // if (this.options.useClustering === true) {
4624
+ // this.map.addLayer(this.cluster)
4625
+ // }
4626
+ // const g = L.featureGroup(this.markers)
4627
+ // this.map.fitBounds(g.getBounds())
4628
+ // this.map.invalidateSize()
4629
+ // }
4630
+ if (this.geo) {
3778
4631
  this.map.fitBounds(this.geo.getBounds())
3779
4632
  }
4633
+ else if (this.polygons) {
4634
+ // this.map.fitBounds(this.geo.getBounds())
4635
+ }
3780
4636
  else if (this.options.center) {
3781
4637
  this.map.setView(this.options.center, this.options.zoom || null)
3782
4638
  }
@@ -3810,24 +4666,36 @@ class WebsyChartTooltip {
3810
4666
  title,
3811
4667
  html,
3812
4668
  position = {
3813
- top: 0,
3814
- left: 0,
4669
+ top: 'unset',
4670
+ bottom: 'unset',
4671
+ left: 0,
3815
4672
  width: 0,
3816
4673
  height: 0,
3817
4674
  onLeft: false
3818
4675
  }
3819
4676
  ) {
4677
+ let classes = ['active']
4678
+ if (position.positioning === 'vertical') {
4679
+ classes.push('vertical')
4680
+ }
4681
+ if (position.onLeft === true) {
4682
+ classes.push('left')
4683
+ }
4684
+ if (position.onTop === true) {
4685
+ classes.push('top')
4686
+ }
4687
+ console.log(classes.join(' '))
3820
4688
  let fO = this.tooltipLayer
3821
4689
  .selectAll('foreignObject')
3822
4690
  .attr('width', `${position.width}px`)
3823
4691
  // .attr('height', `${position.height}px`)
3824
- .attr('y', `0px`)
3825
- .classed('left', position.onLeft)
4692
+ // .attr('y', `0px`)
4693
+ .attr('class', `websy-chart-tooltip ${classes.join(' ')}`)
3826
4694
  this.tooltipContent
3827
- .classed('active', true)
4695
+ .attr('class', `websy-chart-tooltip-content ${classes.join(' ')}`)
3828
4696
  .style('width', `${position.width}px`)
3829
4697
  // .style('left', '0px')
3830
- .style('top', `0px`)
4698
+ // .style('top', `0px`)
3831
4699
  .html(`<div class='title'>${title}</div>${html}`)
3832
4700
  if (
3833
4701
  navigator.userAgent.indexOf('Chrome') === -1 &&
@@ -3835,13 +4703,22 @@ class WebsyChartTooltip {
3835
4703
  ) {
3836
4704
  fO.attr('x', '0px')
3837
4705
  this.tooltipContent
3838
- .style('left', `${position.top}px`)
3839
- .style('top', `${position.top}px`)
4706
+ .style('left', position.positioning !== 'vertical' ? `${position.left}px` : 'unset')
4707
+ .style('top', position.onTop !== true ? `${position.top}px` : 'unset')
4708
+ .style('bottom', position.onTop === true ? `${position.bottom}px` : 'unset')
3840
4709
  // that.tooltipLayer.selectAll('foreignObject').transform(that.margin.left, that.margin.top)
3841
4710
  }
3842
4711
  else {
3843
- fO.attr('x', `${position.left}px`)
3844
- this.tooltipContent.style('left', '0px')
4712
+ if (position.positioning === 'vertical') {
4713
+ fO.attr('x', `${position.left}px`)
4714
+ fO.attr('y', `${position.onTop === true ? position.bottom - this.tooltipContent._groups[0][0].clientHeight : position.top}px`)
4715
+ }
4716
+ else {
4717
+ fO.attr('x', `${position.left}px`)
4718
+ fO.attr('y', `${position.top}px`)
4719
+ }
4720
+ this.tooltipContent.style('left', 'unset')
4721
+ this.tooltipContent.style('top', 'unset')
3845
4722
  }
3846
4723
  }
3847
4724
  transform (x, y) {
@@ -3849,52 +4726,51 @@ class WebsyChartTooltip {
3849
4726
  }
3850
4727
  }
3851
4728
 
3852
- const WebsyUtils = {
3853
- createIdentity: (size = 6) => {
3854
- let text = ''
3855
- let possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
3856
-
3857
- for (let i = 0; i < size; i++) {
3858
- text += possible.charAt(Math.floor(Math.random() * possible.length))
3859
- }
3860
- return text
3861
- },
3862
- getElementPos: el => {
3863
- const rect = el.getBoundingClientRect()
3864
- const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft
3865
- const scrollTop = window.pageYOffset || document.documentElement.scrollTop
3866
- return {
3867
- top: rect.top + scrollTop,
3868
- left: rect.left + scrollLeft,
3869
- bottom: rect.top + scrollTop + el.clientHeight,
3870
- right: rect.left + scrollLeft + el.clientWidth
3871
- }
3872
- }
3873
- }
3874
-
3875
4729
 
3876
4730
  const WebsyDesigns = {
3877
4731
  WebsyPopupDialog,
4732
+ PopupDialog: WebsyPopupDialog,
3878
4733
  WebsyLoadingDialog,
4734
+ LoadingDialog: WebsyLoadingDialog,
3879
4735
  WebsyNavigationMenu,
4736
+ NavigationMenu: WebsyNavigationMenu,
3880
4737
  WebsyForm,
4738
+ Form: WebsyForm,
3881
4739
  WebsyDatePicker,
4740
+ DatePicker: WebsyDatePicker,
3882
4741
  WebsyDropdown,
4742
+ Dropdown: WebsyDropdown,
3883
4743
  WebsyResultList,
4744
+ ResultList: WebsyResultList,
3884
4745
  WebsyTemplate,
4746
+ Template: WebsyTemplate,
3885
4747
  WebsyPubSub,
4748
+ PubSub: WebsyPubSub,
3886
4749
  WebsyRouter,
4750
+ Router: WebsyRouter,
3887
4751
  WebsyTable,
4752
+ Table: WebsyTable,
3888
4753
  WebsyChart,
4754
+ Chart: WebsyChart,
3889
4755
  WebsyChartTooltip,
4756
+ ChartTooltip: WebsyChartTooltip,
4757
+ Legend: WebsyLegend,
3890
4758
  WebsyMap,
4759
+ Map: WebsyMap,
3891
4760
  WebsyKPI,
3892
- WebsyPDFButton,
4761
+ KPI: WebsyKPI,
4762
+ WebsyPDFButton,
3893
4763
  PDFButton: WebsyPDFButton,
3894
4764
  APIService,
3895
- WebsyUtils
4765
+ WebsyUtils,
4766
+ Utils: WebsyUtils,
4767
+ ButtonGroup,
4768
+ WebsySwitch: Switch,
4769
+ Switch
3896
4770
  }
3897
4771
 
4772
+ WebsyDesigns.service = new WebsyDesigns.APIService('')
4773
+
3898
4774
  const GlobalPubSub = new WebsyPubSub('empty', {})
3899
4775
 
3900
4776
  function recaptchaReadyCallBack () {