Няма описание
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

selectcomponent.js 6.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. class SearchableMulti extends HTMLElement {
  2. static get observedAttributes() {
  3. return ['placeholder'];
  4. }
  5. constructor() {
  6. super();
  7. this._values = [];
  8. this._placeholder = 'Search...';
  9. }
  10. connectedCallback() {
  11. if(!this._rendered) {
  12. this._rendered = true;
  13. this.attachShadow({ mode: 'open' });
  14. this.shadowRoot.appendChild(this._template());
  15. this._refresh();
  16. }
  17. this._nonSelected.addEventListener('click', this);
  18. this._selected.addEventListener('click', this);
  19. this._search.addEventListener('keyup', this);
  20. }
  21. disconnectedCallback() {
  22. this._nonSelected.removeEventListener('click', this);
  23. this._selected.removeEventListener('click', this);
  24. this._search.removeEventListener('keyup', this);
  25. }
  26. attributeChangedCallback(name, oldVal, newVal) {
  27. if(name === 'placeholder') {
  28. this.placeholder = newVal;
  29. }
  30. }
  31. get value() {
  32. return this._values;
  33. }
  34. get placeholder() {
  35. return this._placeholder;
  36. }
  37. set placeholder(val) {
  38. this._placeholder = val;
  39. if(this._rendered) {
  40. this.shadowRoot.querySelector('input').placeholder = val;
  41. }
  42. }
  43. handleEvent(ev) {
  44. var el = ev.target;
  45. switch(ev.type) {
  46. case 'click':
  47. if(el.className === 'item') {
  48. if(el.parentNode.className === 'non-selected-wrapper') {
  49. this._nonSelectedClick(el);
  50. } else {
  51. this._selectedClick(el);
  52. }
  53. }
  54. break;
  55. case 'keyup':
  56. if(ev.keyCode === 32 || ev.keyCode === 13) {
  57. if(el.className === 'item') {
  58. if(el.parentNode.className === 'non-selected-wrapper') {
  59. this._nonSelectedClick(el);
  60. } else {
  61. this._selectedClick(el);
  62. }
  63. ev.preventDefault();
  64. }
  65. } else {
  66. this._onSearch();
  67. }
  68. break;
  69. }
  70. }
  71. _nonSelectedClick(el) {
  72. // Not already selected
  73. if(!el._selected) {
  74. this._setSelected(el);
  75. this.dispatchEvent(new Event('change'));
  76. }
  77. }
  78. _setSelected(el) {
  79. el._option.selected = true;
  80. var clone = el._selected = el.cloneNode(true);
  81. clone._nonSelected = el;
  82. this._selected.appendChild(clone);
  83. this._values.push(el.dataset.value);
  84. }
  85. _selectedClick(el) {
  86. var nonSelected = el._nonSelected;
  87. var option = nonSelected._option;
  88. nonSelected._selected = undefined;
  89. el.parentNode.removeChild(el);
  90. // Deselect the option
  91. option.selected = false;
  92. // Remove from values
  93. var idx = this._values.indexOf(el.dataset.value);
  94. if(idx !== -1) {
  95. this._values.splice(idx, 1);
  96. this.dispatchEvent(new Event('change'));
  97. }
  98. }
  99. _onSearch() {
  100. var term = this._search.value.toLowerCase();
  101. function includes(str) {
  102. return str.toLowerCase().indexOf(term) !== -1;
  103. }
  104. var nonSelected, d;
  105. for(var i = 0, len = this._nonSelected.children.length; i < len; i++) {
  106. nonSelected = this._nonSelected.children[i];
  107. if(term && !includes(nonSelected.dataset.value) &&
  108. !includes(nonSelected.textContent)) {
  109. d = 'none';
  110. } else {
  111. d = '';
  112. }
  113. nonSelected.style.display = d;
  114. if(nonSelected._selected) {
  115. nonSelected._selected.style.display = d;
  116. }
  117. }
  118. }
  119. _template() {
  120. var doc = this.ownerDocument;
  121. var wrapper = doc.createElement('div');
  122. wrapper.className = 'wrapper';
  123. var style = doc.createElement('style');
  124. style.textContent = this._styles();
  125. var input = this._search = doc.createElement('input');
  126. input.type = 'text';
  127. input.className = 'search-input';
  128. input.placeholder = this.placeholder;
  129. var nonSelected = this._nonSelected = doc.createElement('div');
  130. nonSelected.className = 'non-selected-wrapper';
  131. var selected = this._selected = doc.createElement('div');
  132. selected.className = 'selected-wrapper';
  133. wrapper.appendChild(style);
  134. wrapper.appendChild(input);
  135. wrapper.appendChild(nonSelected);
  136. wrapper.appendChild(selected);
  137. return wrapper;
  138. }
  139. _styles() {
  140. return `
  141. :host {
  142. display: block;
  143. }
  144. .wrapper {
  145. border: 1px solid #ccc;
  146. border-radius: 3px;
  147. overflow: hidden;
  148. width: 100%;
  149. }
  150. .non-selected-wrapper,
  151. .selected-wrapper {
  152. box-sizing: border-box;
  153. display: inline-block;
  154. height: 200px;
  155. overflow-y: scroll;;
  156. padding: 10px;
  157. vertical-align: top;
  158. width: 50%;
  159. }
  160. .non-selected-wrapper {
  161. background: #fafafa;
  162. border-right: 1px solid #ccc;
  163. }
  164. .selected-wrapper {
  165. background: #fff;
  166. }
  167. .item {
  168. cursor: pointer;
  169. display: block;
  170. padding: 5px 10px;
  171. }
  172. .item:hover {
  173. background: #ececec;
  174. border-radius: 2px;
  175. }
  176. .search-input {
  177. border: 0;
  178. border-bottom: 1px solid #ccc;
  179. border-radius: 0;
  180. display: block;
  181. font-size: 1em;
  182. margin: 0;
  183. outline: 0;
  184. padding: 10px 20px;
  185. width: 100%;
  186. }
  187. .non-selected-wrapper .item.selected {
  188. opacity: 0.5;
  189. }
  190. .non-selected-wrapper .row.selected:hover {
  191. background: inherit;
  192. cursor: inherit;
  193. }
  194. `;
  195. }
  196. _refresh() {
  197. this._selected.innerHTML = this._nonSelected.innerHTML = '';
  198. var term = this._search.value;
  199. var options = [].slice.call(this.querySelectorAll('option'));
  200. var doc = this.ownerDocument;
  201. options.forEach(function(option){
  202. var row = doc.createElement('a');
  203. row.setAttribute('tabindex', "0");
  204. row.setAttribute('role', 'button');
  205. row.textContent = option.textContent;
  206. row.dataset.value = option.value;
  207. row.className = 'item';
  208. row._option = option;
  209. this._nonSelected.appendChild(row);
  210. if(option.selected) {
  211. this._setSelected(row);
  212. }
  213. }.bind(this));
  214. }
  215. }
  216. customElements.define('searchable-multi', SearchableMulti);