formstyler.js 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107
  1. /*
  2. * jQuery Form Styler v2.0.0
  3. * https://github.com/Dimox/jQueryFormStyler
  4. *
  5. * Copyright 2012-2017 Dimox (http://dimox.name/)
  6. * Released under the MIT license.
  7. *
  8. * Date: 2017.05.08
  9. *
  10. */
  11. ;(function(factory) {
  12. if (typeof define === 'function' && define.amd) {
  13. // AMD
  14. define(['jquery'], factory);
  15. } else if (typeof exports === 'object') {
  16. // CommonJS
  17. module.exports = factory($ || require('jquery'));
  18. } else {
  19. factory(jQuery);
  20. }
  21. }(function($) {
  22. 'use strict';
  23. var pluginName = 'styler',
  24. defaults = {
  25. idSuffix: '-styler',
  26. filePlaceholder: 'Файл не выбран',
  27. fileBrowse: 'Обзор...',
  28. fileNumber: 'Выбрано файлов: %s',
  29. selectPlaceholder: 'Выберите...',
  30. selectSearch: false,
  31. selectSearchLimit: 10,
  32. selectSearchNotFound: 'Совпадений не найдено',
  33. selectSearchPlaceholder: 'Поиск...',
  34. selectVisibleOptions: 0,
  35. selectSmartPositioning: true,
  36. locale: 'ru',
  37. locales: {
  38. 'en': {
  39. filePlaceholder: 'No file selected',
  40. fileBrowse: 'Browse...',
  41. fileNumber: 'Selected files: %s',
  42. selectPlaceholder: 'Select...',
  43. selectSearchNotFound: 'No matches found',
  44. selectSearchPlaceholder: 'Search...'
  45. }
  46. },
  47. onSelectOpened: function() {},
  48. onSelectClosed: function() {},
  49. onFormStyled: function() {}
  50. };
  51. function Plugin(element, options) {
  52. this.element = element;
  53. this.options = $.extend({}, defaults, options);
  54. var locale = this.options.locale;
  55. if (this.options.locales[locale] !== undefined) {
  56. $.extend(this.options, this.options.locales[locale]);
  57. }
  58. this.init();
  59. }
  60. Plugin.prototype = {
  61. // инициализация
  62. init: function() {
  63. var el = $(this.element);
  64. var opt = this.options;
  65. var iOS = (navigator.userAgent.match(/(iPad|iPhone|iPod)/i) && !navigator.userAgent.match(/(Windows\sPhone)/i)) ? true : false;
  66. var Android = (navigator.userAgent.match(/Android/i) && !navigator.userAgent.match(/(Windows\sPhone)/i)) ? true : false;
  67. function Attributes() {
  68. if (el.attr('id') !== undefined && el.attr('id') !== '') {
  69. this.id = el.attr('id') + opt.idSuffix;
  70. }
  71. this.title = el.attr('title');
  72. this.classes = el.attr('class');
  73. this.data = el.data();
  74. }
  75. // checkbox
  76. if (el.is(':checkbox')) {
  77. var checkboxOutput = function() {
  78. var att = new Attributes();
  79. var checkbox = $('<div class="jq-checkbox"><div class="jq-checkbox__div"></div></div>')
  80. .attr({
  81. id: att.id,
  82. title: att.title
  83. })
  84. .addClass(att.classes)
  85. .data(att.data)
  86. ;
  87. el.after(checkbox).prependTo(checkbox);
  88. if (el.is(':checked')) checkbox.addClass('checked');
  89. if (el.is(':disabled')) checkbox.addClass('disabled');
  90. // клик на псевдочекбокс
  91. checkbox.click(function(e) {
  92. e.preventDefault();
  93. el.triggerHandler('click');
  94. if (!checkbox.is('.disabled')) {
  95. if (el.is(':checked')) {
  96. el.prop('checked', false);
  97. checkbox.removeClass('checked');
  98. } else {
  99. el.prop('checked', true);
  100. checkbox.addClass('checked');
  101. }
  102. el.focus().change();
  103. }
  104. });
  105. // клик на label
  106. el.closest('label').add('label[for="' + el.attr('id') + '"]').on('click.styler', function(e) {
  107. if (!$(e.target).is('a') && !$(e.target).closest(checkbox).length) {
  108. checkbox.triggerHandler('click');
  109. e.preventDefault();
  110. }
  111. });
  112. // переключение по Space или Enter
  113. el.on('change.styler', function() {
  114. if (el.is(':checked')) checkbox.addClass('checked');
  115. else checkbox.removeClass('checked');
  116. })
  117. // чтобы переключался чекбокс, который находится в теге label
  118. .on('keydown.styler', function(e) {
  119. if (e.which == 32) checkbox.click();
  120. })
  121. .on('focus.styler', function() {
  122. if (!checkbox.is('.disabled')) checkbox.addClass('focused');
  123. })
  124. .on('blur.styler', function() {
  125. checkbox.removeClass('focused');
  126. });
  127. }; // end checkboxOutput()
  128. checkboxOutput();
  129. // обновление при динамическом изменении
  130. el.on('refresh', function() {
  131. el.closest('label').add('label[for="' + el.attr('id') + '"]').off('.styler');
  132. el.off('.styler').parent().before(el).remove();
  133. checkboxOutput();
  134. });
  135. // end checkbox
  136. // radio
  137. } else if (el.is(':radio')) {
  138. var radioOutput = function() {
  139. var att = new Attributes();
  140. var radio = $('<div class="jq-radio"><div class="jq-radio__div"></div></div>')
  141. .attr({
  142. id: att.id,
  143. title: att.title
  144. })
  145. .addClass(att.classes)
  146. .data(att.data)
  147. ;
  148. el.after(radio).prependTo(radio);
  149. if (el.is(':checked')) radio.addClass('checked');
  150. if (el.is(':disabled')) radio.addClass('disabled');
  151. // определяем общего родителя у радиокнопок с одинаковым name
  152. // http://stackoverflow.com/a/27733847
  153. $.fn.commonParents = function() {
  154. var cachedThis = this;
  155. return cachedThis.first().parents().filter(function() {
  156. return $(this).find(cachedThis).length === cachedThis.length;
  157. });
  158. };
  159. $.fn.commonParent = function() {
  160. return $(this).commonParents().first();
  161. };
  162. // клик на псевдорадиокнопке
  163. radio.click(function(e) {
  164. e.preventDefault();
  165. el.triggerHandler('click');
  166. if (!radio.is('.disabled')) {
  167. var inputName = $('input[name="' + el.attr('name') + '"]');
  168. inputName.commonParent().find(inputName).prop('checked', false).parent().removeClass('checked');
  169. el.prop('checked', true).parent().addClass('checked');
  170. el.focus().change();
  171. }
  172. });
  173. // клик на label
  174. el.closest('label').add('label[for="' + el.attr('id') + '"]').on('click.styler', function(e) {
  175. if (!$(e.target).is('a') && !$(e.target).closest(radio).length) {
  176. radio.triggerHandler('click');
  177. e.preventDefault();
  178. }
  179. });
  180. // переключение стрелками
  181. el.on('change.styler', function() {
  182. el.parent().addClass('checked');
  183. })
  184. .on('focus.styler', function() {
  185. if (!radio.is('.disabled')) radio.addClass('focused');
  186. })
  187. .on('blur.styler', function() {
  188. radio.removeClass('focused');
  189. });
  190. }; // end radioOutput()
  191. radioOutput();
  192. // обновление при динамическом изменении
  193. el.on('refresh', function() {
  194. el.closest('label').add('label[for="' + el.attr('id') + '"]').off('.styler');
  195. el.off('.styler').parent().before(el).remove();
  196. radioOutput();
  197. });
  198. // end radio
  199. // file
  200. } else if (el.is(':file')) {
  201. var fileOutput = function() {
  202. var att = new Attributes();
  203. var placeholder = el.data('placeholder');
  204. if (placeholder === undefined) placeholder = opt.filePlaceholder;
  205. var browse = el.data('browse');
  206. if (browse === undefined || browse === '') browse = opt.fileBrowse;
  207. var file =
  208. $('<div class="jq-file">' +
  209. '<div class="jq-file__name">' + placeholder + '</div>' +
  210. '<div class="jq-file__browse">' + browse + '</div>' +
  211. '</div>')
  212. .attr({
  213. id: att.id,
  214. title: att.title
  215. })
  216. .addClass(att.classes)
  217. .data(att.data)
  218. ;
  219. el.after(file).appendTo(file);
  220. if (el.is(':disabled')) file.addClass('disabled');
  221. var value = el.val();
  222. var name = $('div.jq-file__name', file);
  223. // чтобы при динамическом изменении имя файла не сбрасывалось
  224. if (value) name.text(value.replace(/.+[\\\/]/, ''));
  225. el.on('change.styler', function() {
  226. var value = el.val();
  227. if (el.is('[multiple]')) {
  228. value = '';
  229. var files = el[0].files.length;
  230. if (files > 0) {
  231. var number = el.data('number');
  232. if (number === undefined) number = opt.fileNumber;
  233. number = number.replace('%s', files);
  234. value = number;
  235. }
  236. }
  237. name.text(value.replace(/.+[\\\/]/, ''));
  238. if (value === '') {
  239. name.text(placeholder);
  240. file.removeClass('changed');
  241. } else {
  242. file.addClass('changed');
  243. }
  244. })
  245. .on('focus.styler', function() {
  246. file.addClass('focused');
  247. })
  248. .on('blur.styler', function() {
  249. file.removeClass('focused');
  250. })
  251. .on('click.styler', function() {
  252. file.removeClass('focused');
  253. });
  254. }; // end fileOutput()
  255. fileOutput();
  256. // обновление при динамическом изменении
  257. el.on('refresh', function() {
  258. el.off('.styler').parent().before(el).remove();
  259. fileOutput();
  260. });
  261. // end file
  262. } else if (el.is('input[type="number"]')) {
  263. var numberOutput = function() {
  264. var att = new Attributes();
  265. var number =
  266. $('<div class="jq-number">' +
  267. '<div class="jq-number__spin minus"></div>' +
  268. '<div class="jq-number__spin plus"></div>' +
  269. '</div>')
  270. .attr({
  271. id: att.id,
  272. title: att.title
  273. })
  274. .addClass(att.classes)
  275. .data(att.data)
  276. ;
  277. el.after(number).prependTo(number).wrap('<div class="jq-number__field"></div>');
  278. if (el.is(':disabled')) number.addClass('disabled');
  279. var min,
  280. max,
  281. step,
  282. timeout = null,
  283. interval = null;
  284. if (el.attr('min') !== undefined) min = el.attr('min');
  285. if (el.attr('max') !== undefined) max = el.attr('max');
  286. if (el.attr('step') !== undefined && $.isNumeric(el.attr('step')))
  287. step = Number(el.attr('step'));
  288. else
  289. step = Number(1);
  290. var changeValue = function(spin) {
  291. var value = el.val(),
  292. newValue;
  293. if (!$.isNumeric(value)) {
  294. value = 0;
  295. el.val('0');
  296. }
  297. if (spin.is('.minus')) {
  298. newValue = Number(value) - step;
  299. } else if (spin.is('.plus')) {
  300. newValue = Number(value) + step;
  301. }
  302. // определяем количество десятичных знаков после запятой в step
  303. var decimals = (step.toString().split('.')[1] || []).length;
  304. if (decimals > 0) {
  305. var multiplier = '1';
  306. while (multiplier.length <= decimals) multiplier = multiplier + '0';
  307. // избегаем появления лишних знаков после запятой
  308. newValue = Math.round(newValue * multiplier) / multiplier;
  309. }
  310. if ($.isNumeric(min) && $.isNumeric(max)) {
  311. if (newValue >= min && newValue <= max) el.val(newValue);
  312. } else if ($.isNumeric(min) && !$.isNumeric(max)) {
  313. if (newValue >= min) el.val(newValue);
  314. } else if (!$.isNumeric(min) && $.isNumeric(max)) {
  315. if (newValue <= max) el.val(newValue);
  316. } else {
  317. el.val(newValue);
  318. }
  319. };
  320. if (!number.is('.disabled')) {
  321. number.on('mousedown', 'div.jq-number__spin', function() {
  322. var spin = $(this);
  323. changeValue(spin);
  324. timeout = setTimeout(function(){
  325. interval = setInterval(function(){ changeValue(spin); }, 40);
  326. }, 350);
  327. }).on('mouseup mouseout', 'div.jq-number__spin', function() {
  328. clearTimeout(timeout);
  329. clearInterval(interval);
  330. }).on('mouseup', 'div.jq-number__spin', function() {
  331. el.change();
  332. });
  333. el.on('focus.styler', function() {
  334. number.addClass('focused');
  335. })
  336. .on('blur.styler', function() {
  337. number.removeClass('focused');
  338. });
  339. }
  340. }; // end numberOutput()
  341. numberOutput();
  342. // обновление при динамическом изменении
  343. el.on('refresh', function() {
  344. el.off('.styler').closest('.jq-number').before(el).remove();
  345. numberOutput();
  346. });
  347. // end number
  348. // select
  349. } else if (el.is('select')) {
  350. var selectboxOutput = function() {
  351. // запрещаем прокрутку страницы при прокрутке селекта
  352. function preventScrolling(selector) {
  353. var scrollDiff = selector.prop('scrollHeight') - selector.outerHeight(),
  354. wheelDelta = null,
  355. scrollTop = null;
  356. selector.off('mousewheel DOMMouseScroll').on('mousewheel DOMMouseScroll', function(e) {
  357. /**
  358. * нормализация направления прокрутки
  359. * (firefox < 0 || chrome etc... > 0)
  360. * (e.originalEvent.detail < 0 || e.originalEvent.wheelDelta > 0)
  361. */
  362. wheelDelta = (e.originalEvent.detail < 0 || e.originalEvent.wheelDelta > 0) ? 1 : -1; // направление прокрутки (-1 вниз, 1 вверх)
  363. scrollTop = selector.scrollTop(); // позиция скролла
  364. if ((scrollTop >= scrollDiff && wheelDelta < 0) || (scrollTop <= 0 && wheelDelta > 0)) {
  365. e.stopPropagation();
  366. e.preventDefault();
  367. }
  368. });
  369. }
  370. var option = $('option', el);
  371. var list = '';
  372. // формируем список селекта
  373. function makeList() {
  374. for (var i = 0; i < option.length; i++) {
  375. var op = option.eq(i);
  376. var li = '',
  377. liClass = '',
  378. liClasses = '',
  379. id = '',
  380. title = '',
  381. dataList = '',
  382. optionClass = '',
  383. optgroupClass = '',
  384. dataJqfsClass = '';
  385. var disabled = 'disabled';
  386. var selDis = 'selected sel disabled';
  387. if (op.prop('selected')) liClass = 'selected sel';
  388. if (op.is(':disabled')) liClass = disabled;
  389. if (op.is(':selected:disabled')) liClass = selDis;
  390. if (op.attr('id') !== undefined && op.attr('id') !== '') id = ' id="' + op.attr('id') + opt.idSuffix + '"';
  391. if (op.attr('title') !== undefined && option.attr('title') !== '') title = ' title="' + op.attr('title') + '"';
  392. if (op.attr('class') !== undefined) {
  393. optionClass = ' ' + op.attr('class');
  394. dataJqfsClass = ' data-jqfs-class="' + op.attr('class') + '"';
  395. }
  396. var data = op.data();
  397. for (var k in data) {
  398. if (data[k] !== '') dataList += ' data-' + k + '="' + data[k] + '"';
  399. }
  400. if ( (liClass + optionClass) !== '' ) liClasses = ' class="' + liClass + optionClass + '"';
  401. li = '<li' + dataJqfsClass + dataList + liClasses + title + id + '>'+ op.html() +'</li>';
  402. // если есть optgroup
  403. if (op.parent().is('optgroup')) {
  404. if (op.parent().attr('class') !== undefined) optgroupClass = ' ' + op.parent().attr('class');
  405. li = '<li' + dataJqfsClass + dataList + ' class="' + liClass + optionClass + ' option' + optgroupClass + '"' + title + id + '>'+ op.html() +'</li>';
  406. if (op.is(':first-child')) {
  407. li = '<li class="optgroup' + optgroupClass + '">' + op.parent().attr('label') + '</li>' + li;
  408. }
  409. }
  410. list += li;
  411. }
  412. } // end makeList()
  413. // одиночный селект
  414. function doSelect() {
  415. var att = new Attributes();
  416. var searchHTML = '';
  417. var selectPlaceholder = el.data('placeholder');
  418. var selectSearch = el.data('search');
  419. var selectSearchLimit = el.data('search-limit');
  420. var selectSearchNotFound = el.data('search-not-found');
  421. var selectSearchPlaceholder = el.data('search-placeholder');
  422. var selectSmartPositioning = el.data('smart-positioning');
  423. if (selectPlaceholder === undefined) selectPlaceholder = opt.selectPlaceholder;
  424. if (selectSearch === undefined || selectSearch === '') selectSearch = opt.selectSearch;
  425. if (selectSearchLimit === undefined || selectSearchLimit === '') selectSearchLimit = opt.selectSearchLimit;
  426. if (selectSearchNotFound === undefined || selectSearchNotFound === '') selectSearchNotFound = opt.selectSearchNotFound;
  427. if (selectSearchPlaceholder === undefined) selectSearchPlaceholder = opt.selectSearchPlaceholder;
  428. if (selectSmartPositioning === undefined || selectSmartPositioning === '') selectSmartPositioning = opt.selectSmartPositioning;
  429. var selectbox =
  430. $('<div class="jq-selectbox jqselect">' +
  431. '<div class="jq-selectbox__select">' +
  432. '<div class="jq-selectbox__select-text"></div>' +
  433. '<div class="jq-selectbox__trigger">' +
  434. '<div class="jq-selectbox__trigger-arrow"></div></div>' +
  435. '</div>' +
  436. '</div>')
  437. .attr({
  438. id: att.id,
  439. title: att.title
  440. })
  441. .addClass(att.classes)
  442. .data(att.data)
  443. ;
  444. el.after(selectbox).prependTo(selectbox);
  445. var selectzIndex = selectbox.css('z-index');
  446. selectzIndex = (selectzIndex > 0 ) ? selectzIndex : 1;
  447. var divSelect = $('div.jq-selectbox__select', selectbox);
  448. var divText = $('div.jq-selectbox__select-text', selectbox);
  449. var optionSelected = option.filter(':selected');
  450. makeList();
  451. if (selectSearch) searchHTML =
  452. '<div class="jq-selectbox__search"><input type="search" autocomplete="off" placeholder="' + selectSearchPlaceholder + '"></div>' +
  453. '<div class="jq-selectbox__not-found">' + selectSearchNotFound + '</div>';
  454. var dropdown =
  455. $('<div class="jq-selectbox__dropdown">' +
  456. searchHTML + '<ul>' + list + '</ul>' +
  457. '</div>');
  458. selectbox.append(dropdown);
  459. var ul = $('ul', dropdown);
  460. var li = $('li', dropdown);
  461. var search = $('input', dropdown);
  462. var notFound = $('div.jq-selectbox__not-found', dropdown).hide();
  463. if (li.length < selectSearchLimit) search.parent().hide();
  464. // показываем опцию по умолчанию
  465. // если у 1-й опции нет текста, она выбрана по умолчанию и параметр selectPlaceholder не false, то показываем плейсхолдер
  466. if (option.first().text() === '' && option.first().is(':selected') && selectPlaceholder !== false) {
  467. divText.text(selectPlaceholder).addClass('placeholder');
  468. } else {
  469. divText.text(optionSelected.text());
  470. }
  471. // определяем самый широкий пункт селекта
  472. var liWidthInner = 0,
  473. liWidth = 0;
  474. li.css({'display': 'inline-block'});
  475. li.each(function() {
  476. var l = $(this);
  477. if (l.innerWidth() > liWidthInner) {
  478. liWidthInner = l.innerWidth();
  479. liWidth = l.width();
  480. }
  481. });
  482. li.css({'display': ''});
  483. // подстраиваем ширину свернутого селекта в зависимости
  484. // от ширины плейсхолдера или самого широкого пункта
  485. if (divText.is('.placeholder') && (divText.width() > liWidthInner)) {
  486. divText.width(divText.width());
  487. } else {
  488. var selClone = selectbox.clone().appendTo('body').width('auto');
  489. var selCloneWidth = selClone.outerWidth();
  490. selClone.remove();
  491. if (selCloneWidth == selectbox.outerWidth()) {
  492. divText.width(liWidth);
  493. }
  494. }
  495. // подстраиваем ширину выпадающего списка в зависимости от самого широкого пункта
  496. if (liWidthInner > selectbox.width()) dropdown.width(liWidthInner);
  497. // прячем 1-ю пустую опцию, если она есть и если атрибут data-placeholder не пустой
  498. // если все же нужно, чтобы первая пустая опция отображалась, то указываем у селекта: data-placeholder=""
  499. if (option.first().text() === '' && el.data('placeholder') !== '') {
  500. li.first().hide();
  501. }
  502. var selectHeight = selectbox.outerHeight(true);
  503. var searchHeight = search.parent().outerHeight(true) || 0;
  504. var isMaxHeight = ul.css('max-height');
  505. var liSelected = li.filter('.selected');
  506. if (liSelected.length < 1) li.first().addClass('selected sel');
  507. if (li.data('li-height') === undefined) {
  508. var liOuterHeight = li.outerHeight();
  509. if (selectPlaceholder !== false) liOuterHeight = li.eq(1).outerHeight();
  510. li.data('li-height', liOuterHeight);
  511. }
  512. var position = dropdown.css('top');
  513. if (dropdown.css('left') == 'auto') dropdown.css({left: 0});
  514. if (dropdown.css('top') == 'auto') {
  515. dropdown.css({top: selectHeight});
  516. position = selectHeight;
  517. }
  518. dropdown.hide();
  519. // если выбран не дефолтный пункт
  520. if (liSelected.length) {
  521. // добавляем класс, показывающий изменение селекта
  522. if (option.first().text() != optionSelected.text()) {
  523. selectbox.addClass('changed');
  524. }
  525. // передаем селекту класс выбранного пункта
  526. selectbox.data('jqfs-class', liSelected.data('jqfs-class'));
  527. selectbox.addClass(liSelected.data('jqfs-class'));
  528. }
  529. // если селект неактивный
  530. if (el.is(':disabled')) {
  531. selectbox.addClass('disabled');
  532. return false;
  533. }
  534. // при клике на псевдоселекте
  535. divSelect.click(function() {
  536. // колбек при закрытии селекта
  537. if ($('div.jq-selectbox').filter('.opened').length) {
  538. opt.onSelectClosed.call($('div.jq-selectbox').filter('.opened'));
  539. }
  540. el.focus();
  541. // если iOS, то не показываем выпадающий список,
  542. // т.к. отображается нативный и неизвестно, как его спрятать
  543. if (iOS) return;
  544. // умное позиционирование
  545. var win = $(window);
  546. var liHeight = li.data('li-height');
  547. var topOffset = selectbox.offset().top;
  548. var bottomOffset = win.height() - selectHeight - (topOffset - win.scrollTop());
  549. var visible = el.data('visible-options');
  550. if (visible === undefined || visible === '') visible = opt.selectVisibleOptions;
  551. var minHeight = liHeight * 5;
  552. var newHeight = liHeight * visible;
  553. if (visible > 0 && visible < 6) minHeight = newHeight;
  554. if (visible === 0) newHeight = 'auto';
  555. var dropDown = function() {
  556. dropdown.height('auto').css({bottom: 'auto', top: position});
  557. var maxHeightBottom = function() {
  558. ul.css('max-height', Math.floor((bottomOffset - 20 - searchHeight) / liHeight) * liHeight);
  559. };
  560. maxHeightBottom();
  561. ul.css('max-height', newHeight);
  562. if (isMaxHeight != 'none') {
  563. ul.css('max-height', isMaxHeight);
  564. }
  565. if (bottomOffset < (dropdown.outerHeight() + 20)) {
  566. maxHeightBottom();
  567. }
  568. };
  569. var dropUp = function() {
  570. dropdown.height('auto').css({top: 'auto', bottom: position});
  571. var maxHeightTop = function() {
  572. ul.css('max-height', Math.floor((topOffset - win.scrollTop() - 20 - searchHeight) / liHeight) * liHeight);
  573. };
  574. maxHeightTop();
  575. ul.css('max-height', newHeight);
  576. if (isMaxHeight != 'none') {
  577. ul.css('max-height', isMaxHeight);
  578. }
  579. if ((topOffset - win.scrollTop() - 20) < (dropdown.outerHeight() + 20)) {
  580. maxHeightTop();
  581. }
  582. };
  583. if (selectSmartPositioning === true || selectSmartPositioning === 1) {
  584. // раскрытие вниз
  585. if (bottomOffset > (minHeight + searchHeight + 20)) {
  586. dropDown();
  587. selectbox.removeClass('dropup').addClass('dropdown');
  588. // раскрытие вверх
  589. } else {
  590. dropUp();
  591. selectbox.removeClass('dropdown').addClass('dropup');
  592. }
  593. } else if (selectSmartPositioning === false || selectSmartPositioning === 0) {
  594. // раскрытие вниз
  595. if (bottomOffset > (minHeight + searchHeight + 20)) {
  596. dropDown();
  597. selectbox.removeClass('dropup').addClass('dropdown');
  598. }
  599. } else {
  600. // если умное позиционирование отключено
  601. dropdown.height('auto').css({bottom: 'auto', top: position});
  602. ul.css('max-height', newHeight);
  603. if (isMaxHeight != 'none') {
  604. ul.css('max-height', isMaxHeight);
  605. }
  606. }
  607. // если выпадающий список выходит за правый край окна браузера,
  608. // то меняем позиционирование с левого на правое
  609. if (selectbox.offset().left + dropdown.outerWidth() > win.width()) {
  610. dropdown.css({left: 'auto', right: 0});
  611. }
  612. // конец умного позиционирования
  613. $('div.jqselect').css({zIndex: (selectzIndex - 1)}).removeClass('opened');
  614. selectbox.css({zIndex: selectzIndex});
  615. if (dropdown.is(':hidden')) {
  616. $('div.jq-selectbox__dropdown:visible').hide();
  617. dropdown.show();
  618. selectbox.addClass('opened focused');
  619. // колбек при открытии селекта
  620. opt.onSelectOpened.call(selectbox);
  621. } else {
  622. dropdown.hide();
  623. selectbox.removeClass('opened dropup dropdown');
  624. // колбек при закрытии селекта
  625. if ($('div.jq-selectbox').filter('.opened').length) {
  626. opt.onSelectClosed.call(selectbox);
  627. }
  628. }
  629. // поисковое поле
  630. if (search.length) {
  631. search.val('').keyup();
  632. notFound.hide();
  633. search.keyup(function() {
  634. var query = $(this).val();
  635. li.each(function() {
  636. if (!$(this).html().match(new RegExp('.*?' + query + '.*?', 'i'))) {
  637. $(this).hide();
  638. } else {
  639. $(this).show();
  640. }
  641. });
  642. // прячем 1-ю пустую опцию
  643. if (option.first().text() === '' && el.data('placeholder') !== '') {
  644. li.first().hide();
  645. }
  646. if (li.filter(':visible').length < 1) {
  647. notFound.show();
  648. } else {
  649. notFound.hide();
  650. }
  651. });
  652. }
  653. // прокручиваем до выбранного пункта при открытии списка
  654. if (li.filter('.selected').length) {
  655. if (el.val() === '') {
  656. ul.scrollTop(0);
  657. } else {
  658. // если нечетное количество видимых пунктов,
  659. // то высоту пункта делим пополам для последующего расчета
  660. if ( (ul.innerHeight() / liHeight) % 2 !== 0 ) liHeight = liHeight / 2;
  661. ul.scrollTop(ul.scrollTop() + li.filter('.selected').position().top - ul.innerHeight() / 2 + liHeight);
  662. }
  663. }
  664. preventScrolling(ul);
  665. }); // end divSelect.click()
  666. // при наведении курсора на пункт списка
  667. li.hover(function() {
  668. $(this).siblings().removeClass('selected');
  669. });
  670. var selectedText = li.filter('.selected').text();
  671. // при клике на пункт списка
  672. li.filter(':not(.disabled):not(.optgroup)').click(function() {
  673. el.focus();
  674. var t = $(this);
  675. var liText = t.text();
  676. if (!t.is('.selected')) {
  677. var index = t.index();
  678. index -= t.prevAll('.optgroup').length;
  679. t.addClass('selected sel').siblings().removeClass('selected sel');
  680. option.prop('selected', false).eq(index).prop('selected', true);
  681. selectedText = liText;
  682. divText.text(liText);
  683. // передаем селекту класс выбранного пункта
  684. if (selectbox.data('jqfs-class')) selectbox.removeClass(selectbox.data('jqfs-class'));
  685. selectbox.data('jqfs-class', t.data('jqfs-class'));
  686. selectbox.addClass(t.data('jqfs-class'));
  687. el.change();
  688. }
  689. dropdown.hide();
  690. selectbox.removeClass('opened dropup dropdown');
  691. // колбек при закрытии селекта
  692. opt.onSelectClosed.call(selectbox);
  693. });
  694. dropdown.mouseout(function() {
  695. $('li.sel', dropdown).addClass('selected');
  696. });
  697. // изменение селекта
  698. el.on('change.styler', function() {
  699. divText.text(option.filter(':selected').text()).removeClass('placeholder');
  700. li.removeClass('selected sel').not('.optgroup').eq(el[0].selectedIndex).addClass('selected sel');
  701. // добавляем класс, показывающий изменение селекта
  702. if (option.first().text() != li.filter('.selected').text()) {
  703. selectbox.addClass('changed');
  704. } else {
  705. selectbox.removeClass('changed');
  706. }
  707. })
  708. .on('focus.styler', function() {
  709. selectbox.addClass('focused');
  710. $('div.jqselect').not('.focused').removeClass('opened dropup dropdown').find('div.jq-selectbox__dropdown').hide();
  711. })
  712. .on('blur.styler', function() {
  713. selectbox.removeClass('focused');
  714. })
  715. // изменение селекта с клавиатуры
  716. .on('keydown.styler keyup.styler', function(e) {
  717. var liHeight = li.data('li-height');
  718. if (el.val() === '') {
  719. divText.text(selectPlaceholder).addClass('placeholder');
  720. } else {
  721. divText.text(option.filter(':selected').text());
  722. }
  723. li.removeClass('selected sel').not('.optgroup').eq(el[0].selectedIndex).addClass('selected sel');
  724. // вверх, влево, Page Up, Home
  725. if (e.which == 38 || e.which == 37 || e.which == 33 || e.which == 36) {
  726. if (el.val() === '') {
  727. ul.scrollTop(0);
  728. } else {
  729. ul.scrollTop(ul.scrollTop() + li.filter('.selected').position().top);
  730. }
  731. }
  732. // вниз, вправо, Page Down, End
  733. if (e.which == 40 || e.which == 39 || e.which == 34 || e.which == 35) {
  734. ul.scrollTop(ul.scrollTop() + li.filter('.selected').position().top - ul.innerHeight() + liHeight);
  735. }
  736. // закрываем выпадающий список при нажатии Enter
  737. if (e.which == 13) {
  738. e.preventDefault();
  739. dropdown.hide();
  740. selectbox.removeClass('opened dropup dropdown');
  741. // колбек при закрытии селекта
  742. opt.onSelectClosed.call(selectbox);
  743. }
  744. }).on('keydown.styler', function(e) {
  745. // открываем выпадающий список при нажатии Space
  746. if (e.which == 32) {
  747. e.preventDefault();
  748. divSelect.click();
  749. }
  750. });
  751. // прячем выпадающий список при клике за пределами селекта
  752. if (!onDocumentClick.registered) {
  753. $(document).on('click', onDocumentClick);
  754. onDocumentClick.registered = true;
  755. }
  756. } // end doSelect()
  757. // мультиселект
  758. function doMultipleSelect() {
  759. var att = new Attributes();
  760. var selectbox =
  761. $('<div class="jq-select-multiple jqselect"></div>')
  762. .attr({
  763. id: att.id,
  764. title: att.title
  765. })
  766. .addClass(att.classes)
  767. .data(att.data)
  768. ;
  769. el.after(selectbox);
  770. makeList();
  771. selectbox.append('<ul>' + list + '</ul>');
  772. var ul = $('ul', selectbox);
  773. var li = $('li', selectbox);
  774. var size = el.attr('size');
  775. var ulHeight = ul.outerHeight();
  776. var liHeight = li.outerHeight();
  777. if (size !== undefined && size > 0) {
  778. ul.css({'height': liHeight * size});
  779. } else {
  780. ul.css({'height': liHeight * 4});
  781. }
  782. if (ulHeight > selectbox.height()) {
  783. ul.css('overflowY', 'scroll');
  784. preventScrolling(ul);
  785. // прокручиваем до выбранного пункта
  786. if (li.filter('.selected').length) {
  787. ul.scrollTop(ul.scrollTop() + li.filter('.selected').position().top);
  788. }
  789. }
  790. // прячем оригинальный селект
  791. el.prependTo(selectbox);
  792. // если селект неактивный
  793. if (el.is(':disabled')) {
  794. selectbox.addClass('disabled');
  795. option.each(function() {
  796. if ($(this).is(':selected')) li.eq($(this).index()).addClass('selected');
  797. });
  798. // если селект активный
  799. } else {
  800. // при клике на пункт списка
  801. li.filter(':not(.disabled):not(.optgroup)').click(function(e) {
  802. el.focus();
  803. var clkd = $(this);
  804. if(!e.ctrlKey && !e.metaKey) clkd.addClass('selected');
  805. if(!e.shiftKey) clkd.addClass('first');
  806. if(!e.ctrlKey && !e.metaKey && !e.shiftKey) clkd.siblings().removeClass('selected first');
  807. // выделение пунктов при зажатом Ctrl
  808. if(e.ctrlKey || e.metaKey) {
  809. if (clkd.is('.selected')) clkd.removeClass('selected first');
  810. else clkd.addClass('selected first');
  811. clkd.siblings().removeClass('first');
  812. }
  813. // выделение пунктов при зажатом Shift
  814. if(e.shiftKey) {
  815. var prev = false,
  816. next = false;
  817. clkd.siblings().removeClass('selected').siblings('.first').addClass('selected');
  818. clkd.prevAll().each(function() {
  819. if ($(this).is('.first')) prev = true;
  820. });
  821. clkd.nextAll().each(function() {
  822. if ($(this).is('.first')) next = true;
  823. });
  824. if (prev) {
  825. clkd.prevAll().each(function() {
  826. if ($(this).is('.selected')) return false;
  827. else $(this).not('.disabled, .optgroup').addClass('selected');
  828. });
  829. }
  830. if (next) {
  831. clkd.nextAll().each(function() {
  832. if ($(this).is('.selected')) return false;
  833. else $(this).not('.disabled, .optgroup').addClass('selected');
  834. });
  835. }
  836. if (li.filter('.selected').length == 1) clkd.addClass('first');
  837. }
  838. // отмечаем выбранные мышью
  839. option.prop('selected', false);
  840. li.filter('.selected').each(function() {
  841. var t = $(this);
  842. var index = t.index();
  843. if (t.is('.option')) index -= t.prevAll('.optgroup').length;
  844. option.eq(index).prop('selected', true);
  845. });
  846. el.change();
  847. });
  848. // отмечаем выбранные с клавиатуры
  849. option.each(function(i) {
  850. $(this).data('optionIndex', i);
  851. });
  852. el.on('change.styler', function() {
  853. li.removeClass('selected');
  854. var arrIndexes = [];
  855. option.filter(':selected').each(function() {
  856. arrIndexes.push($(this).data('optionIndex'));
  857. });
  858. li.not('.optgroup').filter(function(i) {
  859. return $.inArray(i, arrIndexes) > -1;
  860. }).addClass('selected');
  861. })
  862. .on('focus.styler', function() {
  863. selectbox.addClass('focused');
  864. })
  865. .on('blur.styler', function() {
  866. selectbox.removeClass('focused');
  867. });
  868. // прокручиваем с клавиатуры
  869. if (ulHeight > selectbox.height()) {
  870. el.on('keydown.styler', function(e) {
  871. // вверх, влево, PageUp
  872. if (e.which == 38 || e.which == 37 || e.which == 33) {
  873. ul.scrollTop(ul.scrollTop() + li.filter('.selected').position().top - liHeight);
  874. }
  875. // вниз, вправо, PageDown
  876. if (e.which == 40 || e.which == 39 || e.which == 34) {
  877. ul.scrollTop(ul.scrollTop() + li.filter('.selected:last').position().top - ul.innerHeight() + liHeight * 2);
  878. }
  879. });
  880. }
  881. }
  882. } // end doMultipleSelect()
  883. if (el.is('[multiple]')) {
  884. // если Android или iOS, то мультиселект не стилизуем
  885. // причина для Android - в стилизованном селекте нет возможности выбрать несколько пунктов
  886. // причина для iOS - в стилизованном селекте неправильно отображаются выбранные пункты
  887. if (Android || iOS) return;
  888. doMultipleSelect();
  889. } else {
  890. doSelect();
  891. }
  892. }; // end selectboxOutput()
  893. selectboxOutput();
  894. // обновление при динамическом изменении
  895. el.on('refresh', function() {
  896. el.off('.styler').parent().before(el).remove();
  897. selectboxOutput();
  898. });
  899. // end select
  900. // reset
  901. } else if (el.is(':reset')) {
  902. el.on('click', function() {
  903. setTimeout(function() {
  904. el.closest('form').find('input, select').trigger('refresh');
  905. }, 1);
  906. });
  907. } // end reset
  908. }, // init: function()
  909. // деструктор
  910. destroy: function() {
  911. var el = $(this.element);
  912. if (el.is(':checkbox') || el.is(':radio')) {
  913. el.removeData('_' + pluginName).off('.styler refresh').removeAttr('style').parent().before(el).remove();
  914. el.closest('label').add('label[for="' + el.attr('id') + '"]').off('.styler');
  915. } else if (el.is('input[type="number"]')) {
  916. el.removeData('_' + pluginName).off('.styler refresh').closest('.jq-number').before(el).remove();
  917. } else if (el.is(':file') || el.is('select')) {
  918. el.removeData('_' + pluginName).off('.styler refresh').removeAttr('style').parent().before(el).remove();
  919. }
  920. } // destroy: function()
  921. }; // Plugin.prototype
  922. $.fn[pluginName] = function(options) {
  923. var args = arguments;
  924. if (options === undefined || typeof options === 'object') {
  925. this.each(function() {
  926. if (!$.data(this, '_' + pluginName)) {
  927. $.data(this, '_' + pluginName, new Plugin(this, options));
  928. }
  929. })
  930. // колбек после выполнения плагина
  931. .promise()
  932. .done(function() {
  933. var opt = $(this[0]).data('_' + pluginName);
  934. if (opt) opt.options.onFormStyled.call();
  935. });
  936. return this;
  937. } else if (typeof options === 'string' && options[0] !== '_' && options !== 'init') {
  938. var returns;
  939. this.each(function() {
  940. var instance = $.data(this, '_' + pluginName);
  941. if (instance instanceof Plugin && typeof instance[options] === 'function') {
  942. returns = instance[options].apply(instance, Array.prototype.slice.call(args, 1));
  943. }
  944. });
  945. return returns !== undefined ? returns : this;
  946. }
  947. };
  948. // прячем выпадающий список при клике за пределами селекта
  949. function onDocumentClick(e) {
  950. // e.target.nodeName != 'OPTION' - добавлено для обхода бага в Opera на движке Presto
  951. // (при изменении селекта с клавиатуры срабатывает событие onclick)
  952. if (!$(e.target).parents().hasClass('jq-selectbox') && e.target.nodeName != 'OPTION') {
  953. if ($('div.jq-selectbox.opened').length) {
  954. var selectbox = $('div.jq-selectbox.opened'),
  955. search = $('div.jq-selectbox__search input', selectbox),
  956. dropdown = $('div.jq-selectbox__dropdown', selectbox),
  957. opt = selectbox.find('select').data('_' + pluginName).options;
  958. // колбек при закрытии селекта
  959. opt.onSelectClosed.call(selectbox);
  960. if (search.length) search.val('').keyup();
  961. dropdown.hide().find('li.sel').addClass('selected');
  962. selectbox.removeClass('focused opened dropup dropdown');
  963. }
  964. }
  965. }
  966. onDocumentClick.registered = false;
  967. }));