jquery-selective.es.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610
  1. /**
  2. * jQuery Selective v0.3.5
  3. * https://github.com/amazingSurge/jquery-selective
  4. *
  5. * Copyright (c) amazingSurge
  6. * Released under the LGPL-3.0 license
  7. */
  8. import $$1 from 'jquery';
  9. /*eslint no-empty-function: "off"*/
  10. var DEFAULTS = {
  11. namespace: 'selective',
  12. buildFromHtml: true,
  13. closeOnSelect: false,
  14. local: null,
  15. selected: null,
  16. withSearch: false,
  17. searchType: null, //'change' or 'keyup'
  18. ajax: {
  19. work: false,
  20. url: null,
  21. quietMills: null,
  22. loadMore: false,
  23. pageSize: null
  24. },
  25. query: function() {}, //function(api, search_text, page) {},
  26. tpl: {
  27. frame: function() {
  28. return `<div class="${this.namespace}"><div class="${this.namespace}-trigger">${this.options.tpl.triggerButton.call(this)}<div class="${this.namespace}-trigger-dropdown"><div class="${this.namespace}-list-wrap">${this.options.tpl.list.call(this)}</div></div></div>${this.options.tpl.items.call(this)}</div>`;
  29. },
  30. search: function() {
  31. return `<input class="${this.namespace}-search" type="text" placeholder="Search...">`;
  32. },
  33. select: function() {
  34. return `<select class="${this.namespace}-select" name="${this.namespace}" multiple="multiple"></select>`;
  35. },
  36. optionValue: function(data) {
  37. if('name' in data) {
  38. return data.name;
  39. }
  40. return data;
  41. },
  42. option: function(content) {
  43. return `<option value="${this.options.tpl.optionValue.call(this)}">${content}</option>`;
  44. },
  45. items: function() {
  46. return `<ul class="${this.namespace}-items"></ul>`;
  47. },
  48. item: function(content) {
  49. return `<li class="${this.namespace}-item">${content}${this.options.tpl.itemRemove.call(this)}</li>`;
  50. },
  51. itemRemove: function() {
  52. return `<span class="${this.namespace}-remove">x</span>`;
  53. },
  54. triggerButton: function() {
  55. return `<div class="${this.namespace}-trigger-button">Add</div>`;
  56. },
  57. list: function() {
  58. return `<ul class="${this.namespace}-list"></ul>`;
  59. },
  60. listItem: function(content) {
  61. return `<li class="${this.namespace}-list-item">${content}</li>`;
  62. }
  63. },
  64. onBeforeShow: null,
  65. onAfterShow: null,
  66. onBeforeHide: null,
  67. onAfterHide: null,
  68. onBeforeSearch: null,
  69. onAfterSearch: null,
  70. onBeforeSelected: null,
  71. onAfterSelected: null,
  72. onBeforeUnselect: null,
  73. onAfterUnselect: null,
  74. onBeforeItemRemove: null,
  75. onAfterItemRemove: null,
  76. onBeforeItemAdd: null,
  77. onAfterItemAdd: null
  78. };
  79. class Options {
  80. constructor(instance) {
  81. this.instance = instance;
  82. }
  83. getOptions() {
  84. this.instance.$options = this.instance.$select.find('option');
  85. return this.instance.$options;
  86. }
  87. select(opt) {
  88. $(opt).prop('selected', true);
  89. return this.instance;
  90. }
  91. unselect(opt) {
  92. $(opt).prop('selected', false);
  93. return this.instance;
  94. }
  95. add(data) {
  96. /*eslint consistent-return: "off"*/
  97. if (this.instance.options.buildFromHtml === false &&
  98. this.instance.getItem('option', this.instance.$select, this.instance.options.tpl.optionValue(data)) === undefined) {
  99. const $option = $(this.instance.options.tpl.option.call(this.instance, data));
  100. this.instance.setIndex($option, data);
  101. this.instance.$select.append($option);
  102. return $option;
  103. }
  104. }
  105. remove(opt) {
  106. $(opt).remove();
  107. return this.instance;
  108. }
  109. }
  110. class List {
  111. constructor(instance) {
  112. this.instance = instance;
  113. }
  114. build(data) {
  115. const $list = $('<ul></ul>');
  116. const $options = this.instance._options.getOptions();
  117. if (this.instance.options.buildFromHtml === true) {
  118. if ($options.length !== 0) {
  119. $.each($options, (i, n) => {
  120. const $li = $(this.instance.options.tpl.listItem.call(this.instance, n.text));
  121. const $n = $(n);
  122. this.instance.setIndex($li, $n);
  123. if ($n.attr('selected') !== undefined) {
  124. this.instance.select($li);
  125. }
  126. $list.append($li);
  127. });
  128. }
  129. } else if (data !== null) {
  130. $.each(data, i => {
  131. const $li = $(this.instance.options.tpl.listItem.call(this.instance, data[i]));
  132. this.instance.setIndex($li, data[i]);
  133. $list.append($li);
  134. });
  135. if ($options.length !== 0) {
  136. $.each($options, (i, n) => {
  137. const $n = $(n);
  138. const li = this.instance.getItem('li', $list, this.instance.options.tpl.optionValue($n.data('selective_index')));
  139. if (li !== undefined) {
  140. this.instance._list.select(li);
  141. }
  142. });
  143. }
  144. }
  145. this.instance.$list.append($list.children('li'));
  146. return this.instance;
  147. }
  148. buildSearch() {
  149. if (this.instance.options.withSearch === true) {
  150. this.instance.$triggerDropdown.prepend(this.instance.options.tpl.search.call(this.instance));
  151. this.instance.$search = this.instance.$triggerDropdown.find(`.${this.instance.namespace}-search`);
  152. }
  153. return this.instance;
  154. }
  155. select(obj) {
  156. this.instance._trigger("beforeSelected");
  157. $(obj).addClass(`${this.instance.namespace}-selected`);
  158. this.instance._trigger("afterSelected");
  159. return this.instance;
  160. }
  161. unselect(obj) {
  162. this.instance._trigger("beforeUnselected");
  163. $(obj).removeClass(`${this.instance.namespace}-selected`);
  164. this.instance._trigger("afterUnselected");
  165. return this.instance;
  166. }
  167. click() {
  168. const that = this;
  169. this.instance.$list.on('click', 'li', function() {
  170. const $this = $(this);
  171. if (!$this.hasClass(`${that.instance.namespace}-selected`)) {
  172. that.instance.select($this);
  173. }
  174. });
  175. }
  176. filter(val) {
  177. $.expr[':'].Contains = (a, i, m) => jQuery(a).text().toUpperCase().includes(m[3].toUpperCase());
  178. if (val) {
  179. this.instance.$list.find(`li:not(:Contains(${val}))`).slideUp();
  180. this.instance.$list.find(`li:Contains(${val})`).slideDown();
  181. } else {
  182. this.instance.$list.children('li').slideDown();
  183. }
  184. return this.instance;
  185. }
  186. loadMore() {
  187. const pageMax = this.instance.options.ajax.pageSize || 9999;
  188. this.instance.$listWrap.on('scroll.selective', () => {
  189. if (pageMax > this.instance.page) {
  190. const listHeight = this.instance.$list.outerHeight(true);
  191. const wrapHeight = this.instance.$listWrap.outerHeight();
  192. const wrapScrollTop = this.instance.$listWrap.scrollTop();
  193. const below = listHeight - wrapHeight - wrapScrollTop;
  194. if (below === 0) {
  195. this.instance.options.query(this.instance, this.instance.$search.val(), ++this.instance.page);
  196. }
  197. }
  198. });
  199. return this.instance;
  200. }
  201. loadMoreRemove() {
  202. this.instance.$listWrap.off('scroll.selective');
  203. return this.instance;
  204. }
  205. }
  206. class Search {
  207. constructor(instance) {
  208. this.instance = instance;
  209. }
  210. change() {
  211. this.instance.$search.change(() => {
  212. this.instance._trigger("beforeSearch");
  213. if (this.instance.options.buildFromHtml === true) {
  214. this.instance._list.filter(this.instance.$search.val());
  215. } else if (this.instance.$search.val() !== '') {
  216. this.instance.page = 1;
  217. this.instance.options.query(this.instance, this.instance.$search.val(), this.instance.page);
  218. } else {
  219. this.instance.update(this.instance.options.local);
  220. }
  221. this.instance._trigger("afterSearch");
  222. });
  223. }
  224. keyup() {
  225. const quietMills = this.instance.options.ajax.quietMills || 1000;
  226. let oldValue = '';
  227. let currentValue = '';
  228. let timeout;
  229. this.instance.$search.on('keyup', e => {
  230. this.instance._trigger("beforeSearch");
  231. currentValue = this.instance.$search.val();
  232. if (this.instance.options.buildFromHtml === true) {
  233. if (currentValue !== oldValue) {
  234. this.instance._list.filter(currentValue);
  235. }
  236. } else if (currentValue !== oldValue || e.keyCode === 13) {
  237. window.clearTimeout(timeout);
  238. timeout = window.setTimeout(() => {
  239. if (currentValue !== '') {
  240. this.instance.page = 1;
  241. this.instance.options.query(this.instance, currentValue, this.instance.page);
  242. } else {
  243. this.instance.update(this.instance.options.local);
  244. }
  245. }, quietMills);
  246. }
  247. oldValue = currentValue;
  248. this.instance._trigger("afterSearch");
  249. });
  250. }
  251. bind(type) {
  252. if (type === 'change') {
  253. this.change();
  254. } else if (type === 'keyup') {
  255. this.keyup();
  256. }
  257. }
  258. }
  259. class Items {
  260. constructor(instance) {
  261. this.instance = instance;
  262. }
  263. withDefaults(data) {
  264. if (data !== null) {
  265. $.each(data, i => {
  266. this.instance._options.add(data[i]);
  267. this.instance._options.select(this.instance.getItem('option', this.instance.$select, this.instance.options.tpl.optionValue(data[i])));
  268. this.instance._items.add(data[i]);
  269. });
  270. }
  271. }
  272. add(data, content) {
  273. let $item;
  274. let fill;
  275. if (this.instance.options.buildFromHtml === true) {
  276. fill = content;
  277. } else {
  278. fill = data;
  279. }
  280. $item = $(this.instance.options.tpl.item.call(this.instance, fill));
  281. this.instance.setIndex($item, data);
  282. this.instance.$items.append($item);
  283. }
  284. remove(obj) {
  285. obj = $(obj);
  286. let $li;
  287. let $option;
  288. if (this.instance.options.buildFromHtml === true) {
  289. this.instance._list.unselect(obj.data('selective_index'));
  290. this.instance._options.unselect(obj.data('selective_index').data('selective_index'));
  291. } else {
  292. $li = this.instance.getItem('li', this.instance.$list, this.instance.options.tpl.optionValue(obj.data('selective_index')));
  293. if ($li !== undefined) {
  294. this.instance._list.unselect($li);
  295. }
  296. $option = this.instance.getItem('option', this.instance.$select, this.instance.options.tpl.optionValue(obj.data('selective_index')));
  297. this.instance._options.unselect($option)._options.remove($option);
  298. }
  299. obj.remove();
  300. return this.instance;
  301. }
  302. click() {
  303. const that = this;
  304. this.instance.$items.on('click', `.${this.instance.namespace}-remove`, function() {
  305. const $this = $(this);
  306. const $item = $this.parents('li');
  307. that.instance.itemRemove($item);
  308. });
  309. }
  310. }
  311. const NAMESPACE$1 = 'selective';
  312. /**
  313. * Plugin constructor
  314. **/
  315. class Selective {
  316. constructor(element, options = {}) {
  317. this.element = element;
  318. this.$element = $$1(element).hide() || $$1('<select></select>');
  319. this.options = $$1.extend(true, {}, DEFAULTS, options);
  320. this.namespace = this.options.namespace;
  321. const $frame = $$1(this.options.tpl.frame.call(this));
  322. //get the select
  323. const _build = () => {
  324. this.$element.html(this.options.tpl.select.call(this));
  325. return this.$element.children('select');
  326. };
  327. this.$select = this.$element.is('select') === true ? this.$element : _build();
  328. this.$element.after($frame);
  329. this.init();
  330. this.opened = false;
  331. }
  332. init() {
  333. this.$selective = this.$element.next(`.${this.namespace}`);
  334. this.$items = this.$selective.find(`.${this.namespace}-items`);
  335. this.$trigger = this.$selective.find(`.${this.namespace}-trigger`);
  336. this.$triggerButton = this.$selective.find(`.${this.namespace}-trigger-button`);
  337. this.$triggerDropdown = this.$selective.find(`.${this.namespace}-trigger-dropdown`);
  338. this.$listWrap = this.$selective.find(`.${this.namespace}-list-wrap`);
  339. this.$list = this.$selective.find(`.${this.namespace}-list`);
  340. this._list = new List(this);
  341. this._options = new Options(this);
  342. this._search = new Search(this);
  343. this._items = new Items(this);
  344. this._items.withDefaults(this.options.selected);
  345. this.update(this.options.local)._list.buildSearch();
  346. this.$triggerButton.on('click', () => {
  347. if (this.opened === false) {
  348. this.show();
  349. } else {
  350. this.hide();
  351. }
  352. });
  353. this._list.click(this);
  354. this._items.click(this);
  355. if (this.options.withSearch === true) {
  356. this._search.bind(this.options.searchType);
  357. }
  358. this._trigger('ready');
  359. }
  360. _trigger(eventType, ...params) {
  361. let data = [this].concat(params);
  362. // event
  363. this.$element.trigger(`${NAMESPACE$1}::${eventType}`, data);
  364. // callback
  365. eventType = eventType.replace(/\b\w+\b/g, (word) => {
  366. return word.substring(0, 1).toUpperCase() + word.substring(1);
  367. });
  368. let onFunction = `on${eventType}`;
  369. if (typeof this.options[onFunction] === 'function') {
  370. this.options[onFunction].apply(this, params);
  371. }
  372. }
  373. _show() {
  374. $$1(document).on('click.selective', e => {
  375. if (this.options.closeOnSelect === true) {
  376. if ($$1(e.target).closest(this.$triggerButton).length === 0 &&
  377. $$1(e.target).closest(this.$search).length === 0) {
  378. this._hide();
  379. }
  380. } else if ($$1(e.target).closest(this.$trigger).length === 0) {
  381. this._hide();
  382. }
  383. });
  384. this.$trigger.addClass(`${this.namespace}-active`);
  385. this.opened = true;
  386. if (this.options.ajax.loadMore === true) {
  387. this._list.loadMore();
  388. }
  389. return this;
  390. }
  391. _hide() {
  392. $$1(document).off('click.selective');
  393. this.$trigger.removeClass(`${this.namespace}-active`);
  394. this.opened = false;
  395. if (this.options.ajax.loadMore === true) {
  396. this._list.loadMoreRemove();
  397. }
  398. return this;
  399. }
  400. show() {
  401. this._trigger("beforeShow");
  402. this._show();
  403. this._trigger("afterShow");
  404. return this;
  405. }
  406. hide() {
  407. this._trigger("beforeHide");
  408. this._hide();
  409. this._trigger("afterHide");
  410. return this;
  411. }
  412. select($li) {
  413. this._list.select($li);
  414. const data = $li.data('selective_index');
  415. if (this.options.buildFromHtml === true) {
  416. this._options.select(data);
  417. this.itemAdd($li, data.text());
  418. } else {
  419. this._options.add(data);
  420. this._options.select(this.getItem('option', this.$select, this.options.tpl.optionValue(data)));
  421. this.itemAdd(data);
  422. }
  423. return this;
  424. }
  425. unselect($li) {
  426. this._list.unselect($li);
  427. return this;
  428. }
  429. setIndex(obj, index) {
  430. obj.data('selective_index', index);
  431. return this;
  432. }
  433. getItem(type, $list, index) {
  434. const $items = $list.children(type);
  435. let position = '';
  436. for (let i = 0; i < $items.length; i++) {
  437. if (this.options.tpl.optionValue($items.eq(i).data('selective_index')) === index) {
  438. position = i;
  439. }
  440. }
  441. return position === '' ? undefined : $items.eq(position);
  442. }
  443. itemAdd(data, content) {
  444. this._trigger("beforeItemAdd");
  445. this._items.add(data, content);
  446. this._trigger("afterItemAdd");
  447. return this;
  448. }
  449. itemRemove($li) {
  450. this._trigger("beforeItemRemove");
  451. this._items.remove($li);
  452. this._trigger("afterItemRemove");
  453. return this;
  454. }
  455. optionAdd(data) {
  456. this._options.add(data);
  457. return this;
  458. }
  459. optionRemove(opt) {
  460. this._options.remove(opt);
  461. return this;
  462. }
  463. update(data) {
  464. this.$list.empty();
  465. this.page = 1;
  466. if (data !== null) {
  467. this._list.build(data);
  468. } else {
  469. this._list.build();
  470. }
  471. return this;
  472. }
  473. destroy() {
  474. this.$selective.remove();
  475. this.$element.show();
  476. $$1(document).off('click.selective');
  477. this._trigger('destroy');
  478. }
  479. static setDefaults(options) {
  480. $$1.extend(true, DEFAULTS, $$1.isPlainObject(options) && options);
  481. }
  482. }
  483. var info = {
  484. version:'0.3.5'
  485. };
  486. const NAMESPACE = 'selective';
  487. const OtherSelective = $$1.fn.selective;
  488. const jQuerySelective = function(options, ...args) {
  489. if (typeof options === 'string') {
  490. const method = options;
  491. if (/^_/.test(method)) {
  492. return false;
  493. } else if ((/^(get)/.test(method))) {
  494. const instance = this.first().data(NAMESPACE);
  495. if (instance && typeof instance[method] === 'function') {
  496. return instance[method](...args);
  497. }
  498. } else {
  499. return this.each(function() {
  500. const instance = $$1.data(this, NAMESPACE);
  501. if (instance && typeof instance[method] === 'function') {
  502. instance[method](...args);
  503. }
  504. });
  505. }
  506. }
  507. return this.each(function() {
  508. if (!$$1(this).data(NAMESPACE)) {
  509. $$1(this).data(NAMESPACE, new Selective(this, options));
  510. }
  511. });
  512. };
  513. $$1.fn.selective = jQuerySelective;
  514. $$1.selective = $$1.extend({
  515. setDefaults: Selective.setDefaults,
  516. noConflict: function() {
  517. $$1.fn.selective = OtherSelective;
  518. return jQuerySelective;
  519. }
  520. }, info);