mobiscroll.select.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621
  1. (function ($, undefined) {
  2. var ms = $.mobiscroll,
  3. util = ms.util,
  4. isString = util.isString,
  5. defaults = {
  6. batch: 40,
  7. inputClass: '',
  8. invalid: [],
  9. rtl: false,
  10. showInput: true,
  11. groupLabel: 'Groups',
  12. checkIcon: 'checkmark',
  13. dataText: 'text',
  14. dataValue: 'value',
  15. dataGroup: 'group',
  16. dataDisabled: 'disabled'
  17. };
  18. ms.presetShort('select');
  19. ms.presets.scroller.select = function (inst) {
  20. var change,
  21. group,
  22. groupArray,
  23. groupChanged,
  24. groupTap,
  25. groupWheelIdx,
  26. i,
  27. input,
  28. optionArray,
  29. optionWheelIdx,
  30. option,
  31. origValues,
  32. prevGroup,
  33. timer,
  34. batchChanged = {},
  35. batchStart = {},
  36. batchEnd = {},
  37. tempBatchStart = {},
  38. tempBatchEnd = {},
  39. orig = $.extend({}, inst.settings),
  40. s = $.extend(inst.settings, defaults, orig),
  41. batch = s.batch,
  42. layout = s.layout || (/top|bottom/.test(s.display) ? 'liquid' : ''),
  43. isLiquid = layout == 'liquid',
  44. elm = $(this),
  45. multiple = s.multiple || elm.prop('multiple'),
  46. id = this.id + '_dummy',
  47. lbl = $('label[for="' + this.id + '"]').attr('for', id),
  48. label = s.label !== undefined ? s.label : (lbl.length ? lbl.text() : elm.attr('name')),
  49. selectedClass = 'dw-msel mbsc-ic mbsc-ic-' + s.checkIcon,
  50. origReadOnly = s.readonly,
  51. data = s.data,
  52. hasData = !!data,
  53. hasGroups = hasData ? !!s.group : $('optgroup', elm).length,
  54. defaultValue = hasData ? (data[0] ? data[0][s.dataValue] : null) : $('option', elm).attr('value'),
  55. groupSetup = s.group,
  56. groupWheel = hasGroups && groupSetup && groupSetup.groupWheel !== false,
  57. groupSep = hasGroups && groupSetup && groupWheel && groupSetup.clustered === true,
  58. groupHdr = hasGroups && (!groupSetup || (groupSetup.header !== false && !groupSep)),
  59. values = elm.val() || [],
  60. invalid = [],
  61. selectedValues = {},
  62. options = {},
  63. groups = {};
  64. function prepareData() {
  65. var gr,
  66. lbl,
  67. opt,
  68. txt,
  69. val,
  70. l = 0,
  71. c = 0,
  72. groupIndexes = {};
  73. optionArray = [];
  74. groupArray = [];
  75. if (hasData) {
  76. $.each(s.data, function (i, v) {
  77. txt = v[s.dataText];
  78. val = v[s.dataValue];
  79. lbl = v[s.dataGroup];
  80. opt = {
  81. value: val,
  82. text: txt,
  83. index: i
  84. };
  85. options[val] = opt;
  86. optionArray.push(opt);
  87. if (hasGroups) {
  88. if (groupIndexes[lbl] === undefined) {
  89. gr = { text: lbl, value: c, options: [], index: c };
  90. groups[c] = gr;
  91. groupIndexes[lbl] = c;
  92. groupArray.push(gr);
  93. c++;
  94. } else {
  95. gr = groups[groupIndexes[lbl]];
  96. }
  97. if (groupSep) {
  98. opt.index = gr.options.length;
  99. }
  100. opt.group = groupIndexes[lbl];
  101. gr.options.push(opt);
  102. }
  103. if (v[s.dataDisabled]) {
  104. invalid.push(val);
  105. }
  106. });
  107. } else {
  108. if (hasGroups) {
  109. $('optgroup', elm).each(function (i) {
  110. groups[i] = { text: this.label, value: i, options: [], index: i };
  111. groupArray.push(groups[i]);
  112. $('option', this).each(function (j) {
  113. opt = {
  114. value: this.value,
  115. text: this.text,
  116. index: groupSep ? j : l++,
  117. group: i
  118. };
  119. options[this.value] = opt;
  120. optionArray.push(opt);
  121. groups[i].options.push(opt);
  122. if (this.disabled) {
  123. invalid.push(this.value);
  124. }
  125. });
  126. });
  127. } else {
  128. $('option', elm).each(function (i) {
  129. opt = {
  130. value: this.value,
  131. text: this.text,
  132. index: i
  133. };
  134. options[this.value] = opt;
  135. optionArray.push(opt);
  136. if (this.disabled) {
  137. invalid.push(this.value);
  138. }
  139. });
  140. }
  141. }
  142. if (groupHdr) {
  143. optionArray = [];
  144. l = 0;
  145. $.each(groups, function (i, gr) {
  146. val = '__group' + i;
  147. opt = {
  148. text: gr.text,
  149. value: val,
  150. group: i,
  151. index: l++
  152. };
  153. options[val] = opt;
  154. optionArray.push(opt);
  155. invalid.push(opt.value);
  156. $.each(gr.options, function (j, opt) {
  157. opt.index = l++;
  158. optionArray.push(opt);
  159. });
  160. });
  161. }
  162. }
  163. function genValues(w, data, dataMap, value, index, multiple, label) {
  164. var i,
  165. wheel,
  166. keys = [],
  167. values = [],
  168. selectedIndex = dataMap[value] !== undefined ? dataMap[value].index : 0,
  169. start = Math.max(0, selectedIndex - batch),
  170. end = Math.min(data.length - 1, start + batch * 2);
  171. if (batchStart[index] !== start || batchEnd[index] !== end) {
  172. for (i = start; i <= end; i++) {
  173. values.push(data[i].text);
  174. keys.push(data[i].value);
  175. }
  176. batchChanged[index] = true;
  177. tempBatchStart[index] = start;
  178. tempBatchEnd[index] = end;
  179. wheel = {
  180. multiple: multiple,
  181. values: values,
  182. keys: keys,
  183. label: label
  184. };
  185. if (isLiquid) {
  186. w[0][index] = wheel;
  187. } else {
  188. w[index] = [wheel];
  189. }
  190. } else {
  191. batchChanged[index] = false;
  192. }
  193. }
  194. function genGroupWheel(w) {
  195. genValues(w, groupArray, groups, group, groupWheelIdx, false, s.groupLabel);
  196. }
  197. function genOptWheel(w) {
  198. genValues(w, groupSep ? groups[group].options : optionArray, options, option, optionWheelIdx, multiple, label);
  199. }
  200. function genWheels() {
  201. var w = [[]];
  202. if (groupWheel) {
  203. genGroupWheel(w);
  204. }
  205. genOptWheel(w);
  206. return w;
  207. }
  208. function getOption(v) {
  209. if (multiple) {
  210. if (v && isString(v)) {
  211. v = v.split(',');
  212. }
  213. if ($.isArray(v)) {
  214. v = v[0];
  215. }
  216. }
  217. option = v === undefined || v === null || v === '' ? defaultValue : v;
  218. if (groupWheel) {
  219. group = options[option].group;
  220. prevGroup = group;
  221. }
  222. }
  223. function getVal(temp, group) {
  224. var val = temp ? inst._tempWheelArray : (inst._hasValue ? inst._wheelArray : null);
  225. return val ? (s.group && group ? val : val[optionWheelIdx]) : null;
  226. }
  227. function onFill() {
  228. var txt,
  229. val,
  230. sel = [],
  231. i = 0;
  232. if (multiple) {
  233. val = [];
  234. for (i in selectedValues) {
  235. sel.push(options[i] ? options[i].text : '');
  236. val.push(i);
  237. }
  238. txt = sel.join(', ');
  239. } else {
  240. val = option;
  241. txt = options[option] ? options[option].text : '';
  242. }
  243. inst._tempValue = val;
  244. input.val(txt);
  245. elm.val(val);
  246. }
  247. function onTap(li) {
  248. var val = li.attr('data-val'),
  249. selected = li.hasClass('dw-msel');
  250. if (multiple && li.closest('.dwwl').hasClass('dwwms')) {
  251. if (li.hasClass('dw-v')) {
  252. if (selected) {
  253. li.removeClass(selectedClass).removeAttr('aria-selected');
  254. delete selectedValues[val];
  255. } else {
  256. li.addClass(selectedClass).attr('aria-selected', 'true');
  257. selectedValues[val] = val;
  258. }
  259. }
  260. return false;
  261. } else if (li.hasClass('dw-w-gr')) {
  262. groupTap = li.attr('data-val');
  263. }
  264. }
  265. if (!s.invalid.length) {
  266. s.invalid = invalid;
  267. }
  268. if (groupWheel) {
  269. groupWheelIdx = 0;
  270. optionWheelIdx = 1;
  271. } else {
  272. groupWheelIdx = -1;
  273. optionWheelIdx = 0;
  274. }
  275. if (multiple) {
  276. elm.prop('multiple', true);
  277. if (values && isString(values)) {
  278. values = values.split(',');
  279. }
  280. for (i = 0; i < values.length; i++) {
  281. selectedValues[values[i]] = values[i];
  282. }
  283. }
  284. prepareData();
  285. getOption(elm.val());
  286. $('#' + id).remove();
  287. input = $('<input type="text" id="' + id + '" class="' + s.inputClass + '" placeholder="' + (s.placeholder || '') + '" readonly />');
  288. if (s.showInput) {
  289. input.insertBefore(elm);
  290. }
  291. inst.attachShow(input);
  292. elm.addClass('dw-hsel').attr('tabindex', -1).closest('.ui-field-contain').trigger('create');
  293. onFill();
  294. // Extended methods
  295. // ---
  296. inst.setVal = function (val, fill, change, temp, time) {
  297. if (multiple) {
  298. if (val && isString(val)) {
  299. val = val.split(',');
  300. }
  301. selectedValues = util.arrayToObject(val);
  302. val = val ? val[0] : null;
  303. }
  304. inst._setVal(val, fill, change, temp, time);
  305. };
  306. inst.getVal = function (temp, group) {
  307. if (multiple) {
  308. return util.objectToArray(selectedValues);
  309. }
  310. return getVal(temp, group);
  311. };
  312. inst.refresh = function () {
  313. prepareData();
  314. batchStart = {};
  315. batchEnd = {};
  316. s.wheels = genWheels();
  317. batchStart[groupWheelIdx] = tempBatchStart[groupWheelIdx];
  318. batchEnd[groupWheelIdx] = tempBatchEnd[groupWheelIdx];
  319. batchStart[optionWheelIdx] = tempBatchStart[optionWheelIdx];
  320. batchEnd[optionWheelIdx] = tempBatchEnd[optionWheelIdx];
  321. // Prevent wheel generation on initial validation
  322. change = true;
  323. if (inst._isVisible) {
  324. inst.changeWheel(groupWheel ? [groupWheelIdx, optionWheelIdx] : [optionWheelIdx]);
  325. }
  326. };
  327. // @deprecated since 2.14.0, backward compatibility code
  328. // ---
  329. inst.getValues = inst.getVal;
  330. inst.getValue = getVal;
  331. // ---
  332. // ---
  333. return {
  334. width: 50,
  335. layout: layout,
  336. headerText: false,
  337. anchor: input,
  338. confirmOnTap: groupWheel ? [false, true] : true,
  339. formatValue: function (d) {
  340. var i,
  341. opt,
  342. sel = [];
  343. if (multiple) {
  344. for (i in selectedValues) {
  345. sel.push(options[i] ? options[i].text : '');
  346. }
  347. return sel.join(', ');
  348. }
  349. opt = d[optionWheelIdx];
  350. return options[opt] ? options[opt].text : '';
  351. },
  352. parseValue: function (val) {
  353. getOption(val === undefined ? elm.val() : val);
  354. return groupWheel ? [group, option] : [option];
  355. },
  356. onValueTap: onTap,
  357. onValueFill: onFill,
  358. onBeforeShow: function () {
  359. if (multiple && s.counter) {
  360. s.headerText = function () {
  361. var length = 0;
  362. $.each(selectedValues, function () {
  363. length++;
  364. });
  365. return length + ' ' + s.selectedText;
  366. };
  367. }
  368. getOption(elm.val());
  369. if (groupWheel) {
  370. inst._tempWheelArray = [group, option];
  371. }
  372. inst.refresh();
  373. },
  374. onMarkupReady: function (dw) {
  375. dw.addClass('dw-select');
  376. $('.dwwl' + groupWheelIdx, dw).on('mousedown touchstart', function () {
  377. clearTimeout(timer);
  378. });
  379. $('.dwwl' + optionWheelIdx, dw).on('mousedown touchstart', function () {
  380. if (!groupChanged) {
  381. clearTimeout(timer);
  382. }
  383. });
  384. if (groupHdr) {
  385. $('.dwwl' + optionWheelIdx, dw).addClass('dw-select-gr');
  386. }
  387. if (multiple) {
  388. dw.addClass('dwms');
  389. $('.dwwl', dw).on('keydown', function (e) {
  390. if (e.keyCode == 32) { // Space
  391. e.preventDefault();
  392. e.stopPropagation();
  393. onTap($('.dw-sel', this));
  394. }
  395. }).eq(optionWheelIdx).addClass('dwwms').attr('aria-multiselectable', 'true');
  396. origValues = $.extend({}, selectedValues);
  397. }
  398. },
  399. validate: function (dw, i, time, dir) {
  400. var j,
  401. v,
  402. changes = [],
  403. temp = inst.getArrayVal(true),
  404. tempGr = temp[groupWheelIdx],
  405. tempOpt = temp[optionWheelIdx],
  406. t1 = $('.dw-ul', dw).eq(groupWheelIdx),
  407. t2 = $('.dw-ul', dw).eq(optionWheelIdx);
  408. if (batchStart[groupWheelIdx] > 1) {
  409. $('.dw-li', t1).slice(0, 2).removeClass('dw-v').addClass('dw-fv');
  410. }
  411. if (batchEnd[groupWheelIdx] < groupArray.length - 2) {
  412. $('.dw-li', t1).slice(-2).removeClass('dw-v').addClass('dw-fv');
  413. }
  414. if (batchStart[optionWheelIdx] > 1) {
  415. $('.dw-li', t2).slice(0, 2).removeClass('dw-v').addClass('dw-fv');
  416. }
  417. if (batchEnd[optionWheelIdx] < (groupSep ? groups[tempGr].options : optionArray).length - 2) {
  418. $('.dw-li', t2).slice(-2).removeClass('dw-v').addClass('dw-fv');
  419. }
  420. if (!change) {
  421. option = tempOpt;
  422. if (groupWheel) {
  423. group = options[option].group;
  424. // If group changed, load group options
  425. if (i === undefined || i === groupWheelIdx) {
  426. group = +temp[groupWheelIdx];
  427. groupChanged = false;
  428. if (group !== prevGroup) {
  429. option = groups[group].options[0].value;
  430. batchStart[optionWheelIdx] = null;
  431. batchEnd[optionWheelIdx] = null;
  432. groupChanged = true;
  433. s.readonly = [false, true];
  434. } else {
  435. s.readonly = origReadOnly;
  436. }
  437. }
  438. }
  439. // Adjust value to the first group option if group header was selected
  440. if (hasGroups && (/__group/.test(option) || groupTap)) {
  441. option = groups[options[groupTap || option].group].options[0].value;
  442. tempOpt = option;
  443. groupTap = false;
  444. }
  445. // Update values if changed
  446. // Don't set the new option yet (if group changed), because it's not on the wheel yet
  447. inst._tempWheelArray = groupWheel ? [tempGr, tempOpt] : [tempOpt];
  448. // Generate new wheel batches
  449. if (groupWheel) {
  450. genGroupWheel(s.wheels);
  451. if (batchChanged[groupWheelIdx]) {
  452. changes.push(groupWheelIdx);
  453. }
  454. }
  455. genOptWheel(s.wheels);
  456. if (batchChanged[optionWheelIdx]) {
  457. changes.push(optionWheelIdx);
  458. }
  459. clearTimeout(timer);
  460. timer = setTimeout(function () {
  461. if (changes.length) {
  462. change = true;
  463. groupChanged = false;
  464. prevGroup = group;
  465. // Save current batch boundaries
  466. batchStart[groupWheelIdx] = tempBatchStart[groupWheelIdx];
  467. batchEnd[groupWheelIdx] = tempBatchEnd[groupWheelIdx];
  468. batchStart[optionWheelIdx] = tempBatchStart[optionWheelIdx];
  469. batchEnd[optionWheelIdx] = tempBatchEnd[optionWheelIdx];
  470. // Set the updated values
  471. inst._tempWheelArray = groupWheel ? [tempGr, option] : [option];
  472. // Change the wheels
  473. inst.changeWheel(changes, 0, i !== undefined);
  474. }
  475. if (groupWheel) {
  476. if (i === optionWheelIdx) {
  477. inst.scroll(t1, groupWheelIdx, inst.getValidCell(group, t1, dir, false, true).v, 0.1);
  478. }
  479. inst._tempWheelArray[groupWheelIdx] = group;
  480. }
  481. // Restore readonly status
  482. s.readonly = origReadOnly;
  483. }, i === undefined ? 100 : time * 1000);
  484. if (changes.length) {
  485. return groupChanged ? false : true;
  486. }
  487. }
  488. // Add selected styling to selected elements in case of multiselect
  489. if (i === undefined && multiple) {
  490. v = selectedValues;
  491. j = 0;
  492. $('.dwwl' + optionWheelIdx + ' .dw-li', dw).removeClass(selectedClass).removeAttr('aria-selected');
  493. for (j in v) {
  494. $('.dwwl' + optionWheelIdx + ' .dw-li[data-val="' + v[j] + '"]', dw).addClass(selectedClass).attr('aria-selected', 'true');
  495. }
  496. }
  497. // Add styling to group headers
  498. if (groupHdr) {
  499. $('.dw-li[data-val^="__group"]', dw).addClass('dw-w-gr');
  500. }
  501. // Disable invalid options
  502. $.each(s.invalid, function (i, v) {
  503. $('.dw-li[data-val="' + v + '"]', t2).removeClass('dw-v dw-fv');
  504. });
  505. change = false;
  506. },
  507. onClear: function (dw) {
  508. selectedValues = {};
  509. input.val('');
  510. $('.dwwl' + optionWheelIdx + ' .dw-li', dw).removeClass(selectedClass).removeAttr('aria-selected');
  511. },
  512. onCancel: function () {
  513. if (!inst.live && multiple) {
  514. selectedValues = $.extend({}, origValues);
  515. }
  516. },
  517. onDestroy: function () {
  518. input.remove();
  519. elm.removeClass('dw-hsel').removeAttr('tabindex');
  520. }
  521. };
  522. };
  523. })(jQuery);