@iamproperty/components 5.1.0-beta → 5.1.0-beta3

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.
Files changed (53) hide show
  1. package/assets/css/components/component.reset.css +1 -1
  2. package/assets/css/components/component.reset.css.map +1 -1
  3. package/assets/css/components/nav.global.css +1 -1
  4. package/assets/css/components/nav.global.css.map +1 -1
  5. package/assets/css/core.min.css +1 -1
  6. package/assets/css/core.min.css.map +1 -1
  7. package/assets/css/style.min.css +1 -1
  8. package/assets/css/style.min.css.map +1 -1
  9. package/assets/js/components/accordion/accordion.component.min.js +1 -1
  10. package/assets/js/components/actionbar/actionbar.component.min.js +1 -1
  11. package/assets/js/components/address-lookup/address-lookup.component.js +17 -3
  12. package/assets/js/components/address-lookup/address-lookup.component.min.js +4 -4
  13. package/assets/js/components/address-lookup/address-lookup.component.min.js.map +1 -1
  14. package/assets/js/components/applied-filters/applied-filters.component.min.js +1 -1
  15. package/assets/js/components/card/card.component.min.js +1 -1
  16. package/assets/js/components/collapsible-side/collapsible-side.component.min.js +1 -1
  17. package/assets/js/components/fileupload/fileupload.component.min.js +1 -1
  18. package/assets/js/components/filterlist/filterlist.component.min.js +1 -1
  19. package/assets/js/components/header/header.component.min.js +1 -1
  20. package/assets/js/components/nav/nav.component.js +4 -36
  21. package/assets/js/components/nav/nav.component.min.js +9 -12
  22. package/assets/js/components/nav/nav.component.min.js.map +1 -1
  23. package/assets/js/components/notification/notification.component.min.js +1 -1
  24. package/assets/js/components/pagination/pagination.component.min.js +1 -1
  25. package/assets/js/components/search/search.component.js +148 -0
  26. package/assets/js/components/search/search.component.min.js +14 -0
  27. package/assets/js/components/search/search.component.min.js.map +1 -0
  28. package/assets/js/components/table/table.component.min.js +5 -5
  29. package/assets/js/components/table/table.component.min.js.map +1 -1
  30. package/assets/js/components/tabs/tabs.component.min.js +1 -1
  31. package/assets/js/dynamic.min.js +1 -1
  32. package/assets/js/dynamic.min.js.map +1 -1
  33. package/assets/js/modules/dynamicEvents.js +28 -13
  34. package/assets/js/modules/helpers.js +3 -0
  35. package/assets/js/modules/table.js +1 -2
  36. package/assets/js/scripts.bundle.js +15 -15
  37. package/assets/js/scripts.bundle.js.map +1 -1
  38. package/assets/js/scripts.bundle.min.js +2 -2
  39. package/assets/js/scripts.bundle.min.js.map +1 -1
  40. package/assets/sass/components/nav.global.scss +1 -1
  41. package/assets/sass/elements/forms.scss +1 -1
  42. package/assets/ts/components/address-lookup/address-lookup.component.ts +23 -4
  43. package/assets/ts/components/nav/README.md +2 -13
  44. package/assets/ts/components/nav/nav.component.ts +4 -47
  45. package/assets/ts/components/search/search.component.ts +177 -0
  46. package/assets/ts/modules/dynamicEvents.ts +44 -24
  47. package/assets/ts/modules/helpers.ts +7 -1
  48. package/assets/ts/modules/table.ts +1 -3
  49. package/dist/components.es.js +490 -491
  50. package/dist/components.umd.js +62 -68
  51. package/package.json +1 -1
  52. package/src/components/Nav/README.md +1 -12
  53. package/src/components/Search/Search.vue +25 -0
@@ -554,7 +554,7 @@ iam-nav details {
554
554
 
555
555
  &.search-open {
556
556
 
557
- > *:not([slot="logo"]):not([slot="secondary"]) {
557
+ > *:not([slot="logo"]):not([slot="secondary"]):not([slot="search"]) {
558
558
  display: none!important;
559
559
  }
560
560
  }
@@ -858,7 +858,7 @@ iam-fileupload {
858
858
  width: 100%;
859
859
  }
860
860
 
861
- :is(.suffix,.prefix) {
861
+ :is(.suffix,.prefix):not(.mt-0) {
862
862
  margin: rem(8) 0 0 0!important;
863
863
  }
864
864
  }
@@ -34,7 +34,7 @@ class iamAddressLookup extends HTMLElement {
34
34
  <div>
35
35
  <label class="mb-2">Search <span class="title text-lowercase"></span> <span class="optional">(Optional)</span>
36
36
  <span>
37
- <input type="text" name="postcode" list="address-lookup__addressess" autoComplete="new-password" aria-autocomplete="none" placeholder="Postcode" />
37
+ <input type="text" name="postcode" list="address-lookup__addressess" autocomplete="off" aria-autocomplete="none" placeholder="Postcode" />
38
38
  <span class="suffix fa-regular fa-search"></span>
39
39
  </span>
40
40
  <span class="invalid-feedback">Required Adddress fields missing</span>
@@ -251,9 +251,28 @@ class iamAddressLookup extends HTMLElement {
251
251
  let listString = '';
252
252
  response.forEach((address, index) => {
253
253
 
254
-
255
- let values = JSON.stringify(address.value);
256
- listString += `<option value="${address['label']}, ${postcode}" data-values='${values}'></option>`;
254
+ // Deal with agent platform response
255
+ if(typeof address.value == "object"){
256
+ let values = JSON.stringify(address.value);
257
+ listString += `<option value="${address['label']}, ${postcode}" data-values='${values}'></option>`;
258
+ }
259
+ else {
260
+ let values = JSON.stringify(address);
261
+
262
+ let itemString = '';
263
+
264
+ for (const [key, value] of Object.entries(address)) {
265
+
266
+ if(key == "address_number_name")
267
+ itemString += `${value} `;
268
+ else if(key != "postcode" && key != "address_title")
269
+ itemString += `${value}, `;
270
+ }
271
+
272
+ listString += `<option value="${itemString}${postcode}" data-values='${values}'></option>`;
273
+ }
274
+
275
+
257
276
  });
258
277
  list.innerHTML = listString;
259
278
 
@@ -37,9 +37,6 @@ import('../node_modules/@iamproperty/components/assets/js/components/nav/nav.com
37
37
 
38
38
  | Option | Type | Default Value | Description |
39
39
  | ------ | ---- | ------------- | ----------- |
40
- | data-search | String | - | Optional, displays a search button and form. The value passed through is used for the forms action attribute. |
41
- | data-list | String | - | Optional string with an ID of a datalist, note this list needs to be added as an element in the component |
42
- | data-prevent-search | String | - | Flag that prevents the search form submitting allowing it to used purely with JavaScript |
43
40
  | data-searcd-open | String | - | Flag that opens the search bar on desktop on page load. |
44
41
 
45
42
  **Slots**
@@ -51,18 +48,10 @@ import('../node_modules/@iamproperty/components/assets/js/components/nav/nav.com
51
48
  | secondary | - | Moves the link upto the top of the navbar on desktop |
52
49
  | actions | - | A place to add buttons |
53
50
  | dual | - | Plave the link or list to the right of the nav, forcing the default slot to the left. |
51
+ | search | - | A place to include a form with search functionality |
54
52
 
55
53
  **Class modifiers**
56
54
 
57
55
  - Adding a class of **.bg-primary** will change the background of the navbar without chaning the menu background.
58
56
  - Adding a class of **.nav--sticky** will add etxra styling to make the navbar stick to the top of the page
59
- - Adding a class of **.nav--xs-sticky** will add etxra styling to make the navbar stick to the top of the page BUT only on the mobile view.
60
-
61
- **Dispatched events**
62
-
63
- | Event | Dispatched when | Details passed|
64
- | ------ | ------------- | ----------- |
65
- | search-keydown | When a user uses the search input field and triggers the keydown event. | { search: $inputValue } |
66
- | search-keyup | When a user uses the search input field and triggers the keyup event. | { search: $inputValue } |
67
- | search-change | When a user uses the search input field and triggers the change event. | { search: $inputValue } |
68
- | search-submit | When a user uses the search form and triggers the submit event. | { search: $inputValue } |
57
+ - Adding a class of **.nav--xs-sticky** will add etxra styling to make the navbar stick to the top of the page BUT only on the mobile view.
@@ -48,7 +48,6 @@ class iamNav extends HTMLElement {
48
48
  <slot name="menus"></slot>
49
49
  </div>
50
50
  </div>
51
- <div class="lists"></div>
52
51
  <div class="backdrop" part="backdrop"></div>
53
52
  `;
54
53
 
@@ -285,7 +284,7 @@ class iamNav extends HTMLElement {
285
284
  });
286
285
 
287
286
  // Search
288
- if(this.hasAttribute('data-search')){
287
+ if(this.querySelector('[slot="search"]')){
289
288
  menu.classList.add('has-search');
290
289
  let searchWrapper = this.shadowRoot.querySelector('#search-wrapper');
291
290
 
@@ -293,24 +292,20 @@ class iamNav extends HTMLElement {
293
292
  searchWrapper.insertAdjacentHTML('afterbegin',`<button class="btn btn-secondary btn-compact fa-search me-0 mb-0" id="search-button" aria-controls="search-dialog">Open Search field</button>
294
293
  <dialog id="search-dialog">
295
294
  <div class="container">
296
- <form action="${this.hasAttribute('data-search') ? this.getAttribute('data-search') : ''}" class="row" id="search-form">
295
+ <div class="row">
297
296
  <div class="col mb-0 ms-auto col-md-7">
298
- <label for="search" class="visually-hidden">Search</label>
299
- <button class="suffix me-0 mb-0"><i class="fa-regular fa-search"></i></button>
300
- <input type="search" class="" id="search" name="search" required="" autocomplete="off" data-list="${this.hasAttribute('data-list') ? this.getAttribute('data-list') : ''}" />
297
+ <slot name="search"></slot>
301
298
  </div>
302
299
  <div class="col d-none d-md-block mw-fit-content ms-3">
303
300
  <button class="btn btn-compact btn-secondary fa-xmark-large m-0 mb-0" type="button" id="search-close">Close search field</button>
304
301
  </div>
305
- </form>
302
+ </div>
306
303
  </div>
307
304
  </dialog>`);
308
305
 
309
306
  let searchButton = this.shadowRoot.querySelector('#search-button');
310
307
  let searchClose = this.shadowRoot.querySelector('#search-close');
311
308
  let searchDialog = this.shadowRoot.querySelector('#search-dialog');
312
- let searchInput = this.shadowRoot.querySelector('#search');
313
- let searchForm = this.shadowRoot.querySelector('#search-form');
314
309
 
315
310
  if(this.hasAttribute('data-search-open')){
316
311
 
@@ -336,44 +331,6 @@ class iamNav extends HTMLElement {
336
331
  searchButton.removeAttribute('aria-expanded');
337
332
  });
338
333
 
339
- // Search events
340
- searchInput.addEventListener('keydown', (event) => {
341
-
342
- const keyupEvent = new CustomEvent("search-keydown", { detail: { search: searchInput.value } });
343
- this.dispatchEvent(keyupEvent);
344
- });
345
-
346
- searchInput.addEventListener('keyup', (event) => {
347
-
348
- if (searchInput.value.length >= 3 && searchInput.hasAttribute('data-list'))
349
- searchInput.setAttribute("list", searchInput.getAttribute('data-list'));
350
- else
351
- searchInput.removeAttribute("list");
352
-
353
- const keyupEvent = new CustomEvent("search-keyup", { detail: { search: searchInput.value } });
354
- this.dispatchEvent(keyupEvent);
355
- });
356
-
357
- searchInput.addEventListener('change', (event) => {
358
-
359
- const changeEvent = new CustomEvent("search-change", { detail: { search: searchInput.value } });
360
- this.dispatchEvent(changeEvent);
361
- });
362
-
363
- searchForm.addEventListener('submit', (event) => {
364
-
365
- if(this.hasAttribute('data-prevent-search'))
366
- event.preventDefault();
367
-
368
- const submitEvent = new CustomEvent("search-submit", { detail: { search: searchInput.value } });
369
- this.dispatchEvent(submitEvent);
370
- });
371
-
372
- // Make sure any child lists are available to the search input
373
- this.querySelectorAll('datalist').forEach((list) => {
374
-
375
- iamNav.shadowRoot.querySelector('.lists').insertAdjacentElement('beforeend',list);
376
- });
377
334
  }
378
335
  }
379
336
  }
@@ -0,0 +1,177 @@
1
+ // @ts-nocheck
2
+ import Cookies from 'js-cookie';
3
+ import { safeID, resolvePath, isTraversable } from '../../modules/helpers'
4
+
5
+ // Data layer Web component created
6
+ window.dataLayer = window.dataLayer || [];
7
+ window.dataLayer.push({
8
+ "event": "customElementRegistered",
9
+ "element": "Search"
10
+ });
11
+
12
+ class iamSearch extends HTMLElement {
13
+
14
+ constructor(){
15
+ super();
16
+ this.attachShadow({ mode: 'open'});
17
+
18
+ const assetLocation = document.body.hasAttribute('data-assets-location') ? document.body.getAttribute('data-assets-location') : '/assets'
19
+ const coreCSS = document.body.hasAttribute('data-core-css') ? document.body.getAttribute('data-core-css') : `${assetLocation}/css/core.min.css`;
20
+
21
+ const template = document.createElement('template');
22
+ template.innerHTML = `
23
+ <style>
24
+ @import "${coreCSS}";
25
+
26
+ </style>
27
+ <link rel="stylesheet" href="https://kit.fontawesome.com/26fdbf0179.css" crossorigin="anonymous" />
28
+ <slot></slot>
29
+ `;
30
+ this.shadowRoot.appendChild(template.content.cloneNode(true));
31
+ }
32
+
33
+ async connectedCallback() {
34
+
35
+ const searchWrapper = this;
36
+ const inputField = this.querySelector('input');
37
+ const valueSchema = this.hasAttribute('data-value-schema') ? this.getAttribute('data-value-schema') : 'value';
38
+ const displaySchema = this.hasAttribute('data-display-schema') ? this.getAttribute('data-display-schema') : 'label';
39
+ const loopSchema = this.hasAttribute('data-schema') ? this.getAttribute('data-schema') : '';
40
+ let datalist = this.querySelector('datalist');
41
+ let searched = [];
42
+
43
+ // Clone original input field, re-name and use for display purposes
44
+ const displayInputField = inputField.cloneNode();
45
+ inputField.setAttribute('name',`${inputField.getAttribute}-alt`);
46
+ inputField.removeAttribute('data-change-events');
47
+ displayInputField.removeAttribute('id');
48
+
49
+ inputField.after(displayInputField);
50
+
51
+ // Hide original input field
52
+ inputField.setAttribute('type','hidden');
53
+
54
+ // if data list does not exist then create one and append
55
+ if(!datalist){
56
+
57
+ datalist = document.createElement("datalist");
58
+ let listID = safeID('list');
59
+ datalist.setAttribute('id',listID);
60
+ searchWrapper.appendChild(datalist);
61
+
62
+ displayInputField.setAttribute('list',listID);
63
+ }
64
+
65
+ // on change update oringinal field with the actual value and use displayed input for the nice display text
66
+ displayInputField.addEventListener('change', (event) => {
67
+
68
+ let match = datalist.querySelector(`option[value="${displayInputField.value}"]`);
69
+
70
+ if(match){
71
+ inputField.value = match.getAttribute('data-value');
72
+ }
73
+ });
74
+
75
+ // Search the endpoint when 3 characters has been added
76
+ if(searchWrapper.hasAttribute('data-url')){
77
+
78
+ displayInputField.addEventListener('keyup', (event) => {
79
+
80
+ if(displayInputField.value.length == 3 && !searched.includes(displayInputField.value)){
81
+ search(displayInputField.value);
82
+ searched.push(displayInputField.value);
83
+ }
84
+ });
85
+
86
+ displayInputField.addEventListener('change', (event) => {
87
+
88
+ if(displayInputField.value.length >= 3 && !searched.includes(displayInputField.value.substring(0,3))){
89
+ search(displayInputField.value.substring(0,3));
90
+ searched.push(displayInputField.value.substring(0,3));
91
+ }
92
+ });
93
+ }
94
+
95
+ const search = async (searchterm) => {
96
+
97
+ let ajaxURL = searchWrapper.getAttribute('data-url');
98
+ ajaxURL += `${encodeURI(searchterm)}`;
99
+
100
+ // Setup controller vars if not already set
101
+ if(!window.controller)
102
+ window.controller = [];
103
+
104
+ // Abort if controller already present for this url
105
+ if(window.controller[ajaxURL])
106
+ window.controller[ajaxURL].abort();
107
+
108
+ // Create a new controller so it can be aborted if new fetch made
109
+ window.controller[ajaxURL] = new AbortController();
110
+ const { signal } = controller[ajaxURL];
111
+
112
+ try {
113
+ await fetch(ajaxURL, {
114
+ signal: signal,
115
+ method: 'get',
116
+ credentials: 'same-origin',
117
+ headers: new Headers({
118
+ 'Content-Type': 'application/json',
119
+ Accept: 'application/json',
120
+ 'X-Requested-With': 'XMLHttpRequest',
121
+ 'X-XSRF-TOKEN': Cookies.get('XSRF-TOKEN')
122
+ })
123
+ })
124
+ .then((response) => response.json()).then((response) => {
125
+
126
+ // populate datalist
127
+ let listString = '';
128
+ let loopValues = resolvePath(response, loopSchema, '');
129
+
130
+ if(isTraversable(loopValues) && typeof loopValues.forEach == 'function') {
131
+ loopValues.forEach((item, index) => {
132
+ let actualValue = resolvePath(item, valueSchema, '');
133
+ let displayValue = resolvePath(item, displaySchema, '').replace('\n',', ');
134
+
135
+ if(!datalist.querySelector(`option[data-value="${actualValue}"]`))
136
+ listString += `<option value="${displayValue}" data-value='${actualValue}'></option>`;
137
+ });
138
+ }
139
+ else if (typeof loopValues == 'object'){
140
+
141
+ for (const [key, value] of Object.entries(loopValues)) {
142
+
143
+ if(isTraversable(value) && typeof value.forEach == 'function') {
144
+ value.forEach((item, index) => {
145
+ let actualValue = resolvePath(item, valueSchema, '');
146
+ let displayValue = resolvePath(item, displaySchema, '').replace('\n',', ');
147
+
148
+ if(!datalist.querySelector(`option[data-value="${actualValue}"]`))
149
+ listString += `<option value="${key}: ${displayValue}" data-value='${actualValue}'></option>`;
150
+ });
151
+ }
152
+ }
153
+ }
154
+
155
+ datalist.innerHTML += listString;
156
+
157
+ return response;
158
+ });
159
+
160
+ } catch (error) {
161
+ console.log(error);
162
+ }
163
+ }
164
+
165
+ if(searchWrapper.hasAttribute('data-prevent-submit')){
166
+
167
+ const form = searchWrapper.closest('form');
168
+
169
+ form.addEventListener('submit', (e) => {
170
+
171
+ e.preventDefault();
172
+ });
173
+ }
174
+ }
175
+ }
176
+
177
+ export default iamSearch;
@@ -77,43 +77,63 @@ const runEvent = (element,event,eventType) => {
77
77
  switch (event[eventType]){
78
78
  case "hide":
79
79
 
80
- let hideElement = document.querySelector(event['target'])
81
- hideElement.classList.add('js-hide');
82
-
83
- Array.from(hideElement.querySelectorAll('[data-required]')).forEach((input, index) => {
84
-
85
- input.removeAttribute('required');
86
- });
80
+ if(document.querySelector(event['target'])){
81
+
82
+ let hideElement = document.querySelector(event['target']);
83
+ hideElement.classList.add('js-hide');
84
+
85
+ Array.from(hideElement.querySelectorAll('[data-required]')).forEach((input, index) => {
86
+
87
+ input.removeAttribute('required');
88
+ });
89
+ }
87
90
  break;
88
91
  case "show":
89
92
 
90
- let showElement = document.querySelector(event['target'])
91
- showElement.classList.remove('js-hide');
92
-
93
- Array.from(showElement.querySelectorAll('[data-required]')).forEach((input, index) => {
94
-
95
- if(!input.closest('.js-hide'))
96
- input.setAttribute('required','true');
97
- });
93
+ if(document.querySelector(event['target'])){
94
+
95
+ let showElement = document.querySelector(event['target']);
96
+ showElement.classList.remove('js-hide');
97
+
98
+ Array.from(showElement.querySelectorAll('[data-required]')).forEach((input, index) => {
99
+
100
+ if(!input.closest('.js-hide'))
101
+ input.setAttribute('required','true');
102
+ });
103
+ }
104
+ break;
105
+ case "populate-form":
106
+ populateForm(element,event);
107
+ break;
108
+ case "dispatchEvent":
109
+ let theEvent = new Event(event['value']);
110
+ document.querySelector(`${event['target']}`).dispatchEvent(theEvent);
98
111
  break;
99
- case "populate-form":
100
- populateForm(element,event);
101
- break;
102
- case "dispatchEvent":
103
- let theEvent = new Event(event['value']);
104
- document.querySelector(`${event['target']}`).dispatchEvent(theEvent);
105
- break;
106
112
  case "setAttribute":
107
- document.querySelector(`${event['target']}`).setAttribute(event['attribute'],event['value']);
113
+
114
+ Array.from(form.querySelectorAll(`${event['target']}`)).forEach(function(element,index){
115
+ element.setAttribute(event['attribute'],event['value']);
116
+ });
108
117
  break;
109
118
  case "removeAttribute":
110
- document.querySelector(`${event['target']}`).removeAttribute(event['attribute']);
119
+ Array.from(form.querySelectorAll(`${event['target']}`)).forEach(function(element,index){
120
+ element.removeAttribute(event['attribute']);
121
+ });
111
122
  break;
112
123
  case "updateValue":
113
124
  document.querySelector(`${event['target']}`).value = event['value'] ? event['value'] : "";
114
125
 
115
126
  let changeEvent = new Event('change');
116
127
  document.querySelector(`${event['target']}`).dispatchEvent(changeEvent);
128
+ break;
129
+ case "submitForm":
130
+ document.querySelector(`${event['target']}`).submit();
131
+ break;
132
+ case "openLink":
133
+
134
+ if(document.querySelector(`${event['target']}`).value)
135
+ window.location.href = document.querySelector(`${event['target']}`).value;
136
+
117
137
  break;
118
138
  default:
119
139
  break;
@@ -97,4 +97,10 @@ export const safeID = function(str){
97
97
  str = str.replace(/\W/g,'');
98
98
 
99
99
  return str;
100
- }
100
+ }
101
+
102
+ // Used to get values from nested json objects
103
+ export const resolvePath = (object, path, defaultValue) => path.split(/[\.\[\]\'\"]/).filter(p => p).reduce((o, p) => o ? o[p] : defaultValue, object);
104
+
105
+
106
+ export const isTraversable = o => Array.isArray(o) || o !== null && ['function', 'object'].includes(typeof o);
@@ -1,5 +1,5 @@
1
1
  // @ts-nocheck
2
- import { zeroPad, isNumeric, ucfirst } from "./helpers";
2
+ import { zeroPad, isNumeric, ucfirst, resolvePath } from "./helpers";
3
3
 
4
4
  // Basic functionality needed
5
5
  export const addDataAttributes = (table) => {
@@ -903,8 +903,6 @@ const filterFilters = function(form){
903
903
 
904
904
  export const loadAjaxTable = async function (table, form, pagination, wrapper){
905
905
 
906
- const resolvePath = (object, path, defaultValue) => path.split(/[\.\[\]\'\"]/).filter(p => p).reduce((o, p) => o ? o[p] : defaultValue, object);
907
-
908
906
  let formData = new FormData(form);
909
907
  let queryString = new URLSearchParams(formData).toString();
910
908
  let columns = table.querySelectorAll('thead tr th');