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.

478 lines
14 KiB

  1. /*!
  2. * iCheck v1.0.1, http://git.io/arlzeA
  3. * =================================
  4. * Powerful jQuery and Zepto plugin for checkboxes and radio buttons customization
  5. *
  6. * (c) 2013 Damir Sultanov, http://fronteed.com
  7. * MIT Licensed
  8. */
  9. (function($) {
  10. // Cached vars
  11. var _iCheck = 'iCheck',
  12. _iCheckHelper = _iCheck + '-helper',
  13. _checkbox = 'checkbox',
  14. _radio = 'radio',
  15. _checked = 'checked',
  16. _unchecked = 'un' + _checked,
  17. _disabled = 'disabled',
  18. _determinate = 'determinate',
  19. _indeterminate = 'in' + _determinate,
  20. _update = 'update',
  21. _type = 'type',
  22. _click = 'click',
  23. _touch = 'touchbegin.i touchend.i',
  24. _add = 'addClass',
  25. _remove = 'removeClass',
  26. _callback = 'trigger',
  27. _label = 'label',
  28. _cursor = 'cursor',
  29. _mobile = /ipad|iphone|ipod|android|blackberry|windows phone|opera mini|silk/i.test(navigator.userAgent);
  30. // Plugin init
  31. $.fn[_iCheck] = function(options, fire) {
  32. // Walker
  33. var handle = 'input[type="' + _checkbox + '"], input[type="' + _radio + '"]',
  34. stack = $(),
  35. walker = function(object) {
  36. object.each(function() {
  37. var self = $(this);
  38. if (self.is(handle)) {
  39. stack = stack.add(self);
  40. } else {
  41. stack = stack.add(self.find(handle));
  42. }
  43. });
  44. };
  45. // Check if we should operate with some method
  46. if (/^(check|uncheck|toggle|indeterminate|determinate|disable|enable|update|destroy)$/i.test(options)) {
  47. // Normalize method's name
  48. options = options.toLowerCase();
  49. // Find checkboxes and radio buttons
  50. walker(this);
  51. return stack.each(function() {
  52. var self = $(this);
  53. if (options == 'destroy') {
  54. tidy(self, 'ifDestroyed');
  55. } else {
  56. operate(self, true, options);
  57. }
  58. // Fire method's callback
  59. if ($.isFunction(fire)) {
  60. fire();
  61. }
  62. });
  63. // Customization
  64. } else if (typeof options == 'object' || !options) {
  65. // Check if any options were passed
  66. var settings = $.extend({
  67. checkedClass: _checked,
  68. disabledClass: _disabled,
  69. indeterminateClass: _indeterminate,
  70. labelHover: true,
  71. aria: false
  72. }, options),
  73. selector = settings.handle,
  74. hoverClass = settings.hoverClass || 'hover',
  75. focusClass = settings.focusClass || 'focus',
  76. activeClass = settings.activeClass || 'active',
  77. labelHover = !!settings.labelHover,
  78. labelHoverClass = settings.labelHoverClass || 'hover',
  79. // Setup clickable area
  80. area = ('' + settings.increaseArea).replace('%', '') | 0;
  81. // Selector limit
  82. if (selector == _checkbox || selector == _radio) {
  83. handle = 'input[type="' + selector + '"]';
  84. }
  85. // Clickable area limit
  86. if (area < -50) {
  87. area = -50;
  88. }
  89. // Walk around the selector
  90. walker(this);
  91. return stack.each(function() {
  92. var self = $(this);
  93. // If already customized
  94. tidy(self);
  95. var node = this,
  96. id = node.id,
  97. // Layer styles
  98. offset = -area + '%',
  99. size = 100 + (area * 2) + '%',
  100. layer = {
  101. position: 'absolute',
  102. top: offset,
  103. left: offset,
  104. display: 'block',
  105. width: size,
  106. height: size,
  107. margin: 0,
  108. padding: 0,
  109. background: '#fff',
  110. border: 0,
  111. opacity: 0
  112. },
  113. // Choose how to hide input
  114. hide = _mobile ? {
  115. position: 'absolute',
  116. visibility: 'hidden'
  117. } : area ? layer : {
  118. position: 'absolute',
  119. opacity: 0
  120. },
  121. // Get proper class
  122. className = node[_type] == _checkbox ? settings.checkboxClass || 'i' + _checkbox : settings.radioClass || 'i' + _radio,
  123. // Find assigned labels
  124. label = $(_label + '[for="' + id + '"]').add(self.closest(_label)),
  125. // Check ARIA option
  126. aria = !!settings.aria,
  127. // Set ARIA placeholder
  128. ariaID = _iCheck + '-' + Math.random().toString(36).replace('0.', ''),
  129. // Parent & helper
  130. parent = '<div class="' + className + '" ' + (aria ? 'role="' + node[_type] + '" ' : ''),
  131. helper;
  132. // Set ARIA "labelledby"
  133. if (label.length && aria) {
  134. label.each(function() {
  135. parent += 'aria-labelledby="';
  136. if (this.id) {
  137. parent += this.id;
  138. } else {
  139. this.id = ariaID;
  140. parent += ariaID;
  141. }
  142. parent += '"';
  143. });
  144. }
  145. // Wrap input
  146. parent = self.wrap(parent + '/>')[_callback]('ifCreated').parent().append(settings.insert);
  147. // Layer addition
  148. helper = $('<ins class="' + _iCheckHelper + '"/>').css(layer).appendTo(parent);
  149. // Finalize customization
  150. self.data(_iCheck, {o: settings, s: self.attr('style')}).css(hide);
  151. !!settings.inheritClass && parent[_add](node.className || '');
  152. !!settings.inheritID && id && parent.attr('id', _iCheck + '-' + id);
  153. parent.css('position') == 'static' && parent.css('position', 'relative');
  154. operate(self, true, _update);
  155. // Label events
  156. if (label.length) {
  157. label.on(_click + '.i mouseover.i mouseout.i ' + _touch, function(event) {
  158. var type = event[_type],
  159. item = $(this);
  160. // Do nothing if input is disabled
  161. if (!node[_disabled]) {
  162. // Click
  163. if (type == _click) {
  164. if ($(event.target).is('a')) {
  165. return;
  166. }
  167. operate(self, false, true);
  168. // Hover state
  169. } else if (labelHover) {
  170. // mouseout|touchend
  171. if (/ut|nd/.test(type)) {
  172. parent[_remove](hoverClass);
  173. item[_remove](labelHoverClass);
  174. } else {
  175. parent[_add](hoverClass);
  176. item[_add](labelHoverClass);
  177. }
  178. }
  179. if (_mobile) {
  180. event.stopPropagation();
  181. } else {
  182. return false;
  183. }
  184. }
  185. });
  186. }
  187. // Input events
  188. self.on(_click + '.i focus.i blur.i keyup.i keydown.i keypress.i', function(event) {
  189. var type = event[_type],
  190. key = event.keyCode;
  191. // Click
  192. if (type == _click) {
  193. return false;
  194. // Keydown
  195. } else if (type == 'keydown' && key == 32) {
  196. if (!(node[_type] == _radio && node[_checked])) {
  197. if (node[_checked]) {
  198. off(self, _checked);
  199. } else {
  200. on(self, _checked);
  201. }
  202. }
  203. return false;
  204. // Keyup
  205. } else if (type == 'keyup' && node[_type] == _radio) {
  206. !node[_checked] && on(self, _checked);
  207. // Focus/blur
  208. } else if (/us|ur/.test(type)) {
  209. parent[type == 'blur' ? _remove : _add](focusClass);
  210. }
  211. });
  212. // Helper events
  213. helper.on(_click + ' mousedown mouseup mouseover mouseout ' + _touch, function(event) {
  214. var type = event[_type],
  215. // mousedown|mouseup
  216. toggle = /wn|up/.test(type) ? activeClass : hoverClass;
  217. // Do nothing if input is disabled
  218. if (!node[_disabled]) {
  219. // Click
  220. if (type == _click) {
  221. operate(self, false, true);
  222. // Active and hover states
  223. } else {
  224. // State is on
  225. if (/wn|er|in/.test(type)) {
  226. // mousedown|mouseover|touchbegin
  227. parent[_add](toggle);
  228. // State is off
  229. } else {
  230. parent[_remove](toggle + ' ' + activeClass);
  231. }
  232. // Label hover
  233. if (label.length && labelHover && toggle == hoverClass) {
  234. // mouseout|touchend
  235. label[/ut|nd/.test(type) ? _remove : _add](labelHoverClass);
  236. }
  237. }
  238. if (_mobile) {
  239. event.stopPropagation();
  240. } else {
  241. return false;
  242. }
  243. }
  244. });
  245. });
  246. } else {
  247. return this;
  248. }
  249. };
  250. // Do something with inputs
  251. function operate(input, direct, method) {
  252. var node = input[0],
  253. state = /er/.test(method) ? _indeterminate : /bl/.test(method) ? _disabled : _checked,
  254. active = method == _update ? {
  255. checked: node[_checked],
  256. disabled: node[_disabled],
  257. indeterminate: input.attr(_indeterminate) == 'true' || input.attr(_determinate) == 'false'
  258. } : node[state];
  259. // Check, disable or indeterminate
  260. if (/^(ch|di|in)/.test(method) && !active) {
  261. on(input, state);
  262. // Uncheck, enable or determinate
  263. } else if (/^(un|en|de)/.test(method) && active) {
  264. off(input, state);
  265. // Update
  266. } else if (method == _update) {
  267. // Handle states
  268. for (var state in active) {
  269. if (active[state]) {
  270. on(input, state, true);
  271. } else {
  272. off(input, state, true);
  273. }
  274. }
  275. } else if (!direct || method == 'toggle') {
  276. // Helper or label was clicked
  277. if (!direct) {
  278. input[_callback]('ifClicked');
  279. }
  280. // Toggle checked state
  281. if (active) {
  282. if (node[_type] !== _radio) {
  283. off(input, state);
  284. }
  285. } else {
  286. on(input, state);
  287. }
  288. }
  289. }
  290. // Add checked, disabled or indeterminate state
  291. function on(input, state, keep) {
  292. var node = input[0],
  293. parent = input.parent(),
  294. checked = state == _checked,
  295. indeterminate = state == _indeterminate,
  296. disabled = state == _disabled,
  297. callback = indeterminate ? _determinate : checked ? _unchecked : 'enabled',
  298. regular = option(input, callback + capitalize(node[_type])),
  299. specific = option(input, state + capitalize(node[_type]));
  300. // Prevent unnecessary actions
  301. if (node[state] !== true) {
  302. // Toggle assigned radio buttons
  303. if (!keep && state == _checked && node[_type] == _radio && node.name) {
  304. var form = input.closest('form'),
  305. inputs = 'input[name="' + node.name + '"]';
  306. inputs = form.length ? form.find(inputs) : $(inputs);
  307. inputs.each(function() {
  308. if (this !== node && $(this).data(_iCheck)) {
  309. off($(this), state);
  310. }
  311. });
  312. }
  313. // Indeterminate state
  314. if (indeterminate) {
  315. // Add indeterminate state
  316. node[state] = true;
  317. // Remove checked state
  318. if (node[_checked]) {
  319. off(input, _checked, 'force');
  320. }
  321. // Checked or disabled state
  322. } else {
  323. // Add checked or disabled state
  324. if (!keep) {
  325. node[state] = true;
  326. }
  327. // Remove indeterminate state
  328. if (checked && node[_indeterminate]) {
  329. off(input, _indeterminate, false);
  330. }
  331. }
  332. // Trigger callbacks
  333. callbacks(input, checked, state, keep);
  334. }
  335. // Add proper cursor
  336. if (node[_disabled] && !!option(input, _cursor, true)) {
  337. parent.find('.' + _iCheckHelper).css(_cursor, 'default');
  338. }
  339. // Add state class
  340. parent[_add](specific || option(input, state) || '');
  341. // Set ARIA attribute
  342. disabled ? parent.attr('aria-disabled', 'true') : parent.attr('aria-checked', indeterminate ? 'mixed' : 'true');
  343. // Remove regular state class
  344. parent[_remove](regular || option(input, callback) || '');
  345. }
  346. // Remove checked, disabled or indeterminate state
  347. function off(input, state, keep) {
  348. var node = input[0],
  349. parent = input.parent(),
  350. checked = state == _checked,
  351. indeterminate = state == _indeterminate,
  352. disabled = state == _disabled,
  353. callback = indeterminate ? _determinate : checked ? _unchecked : 'enabled',
  354. regular = option(input, callback + capitalize(node[_type])),
  355. specific = option(input, state + capitalize(node[_type]));
  356. // Prevent unnecessary actions
  357. if (node[state] !== false) {
  358. // Toggle state
  359. if (indeterminate || !keep || keep == 'force') {
  360. node[state] = false;
  361. }
  362. // Trigger callbacks
  363. callbacks(input, checked, callback, keep);
  364. }
  365. // Add proper cursor
  366. if (!node[_disabled] && !!option(input, _cursor, true)) {
  367. parent.find('.' + _iCheckHelper).css(_cursor, 'pointer');
  368. }
  369. // Remove state class
  370. parent[_remove](specific || option(input, state) || '');
  371. // Set ARIA attribute
  372. disabled ? parent.attr('aria-disabled', 'false') : parent.attr('aria-checked', 'false');
  373. // Add regular state class
  374. parent[_add](regular || option(input, callback) || '');
  375. }
  376. // Remove all traces
  377. function tidy(input, callback) {
  378. if (input.data(_iCheck)) {
  379. // Remove everything except input
  380. input.parent().html(input.attr('style', input.data(_iCheck).s || ''));
  381. // Callback
  382. if (callback) {
  383. input[_callback](callback);
  384. }
  385. // Unbind events
  386. input.off('.i').unwrap();
  387. $(_label + '[for="' + input[0].id + '"]').add(input.closest(_label)).off('.i');
  388. }
  389. }
  390. // Get some option
  391. function option(input, state, regular) {
  392. if (input.data(_iCheck)) {
  393. return input.data(_iCheck).o[state + (regular ? '' : 'Class')];
  394. }
  395. }
  396. // Capitalize some string
  397. function capitalize(string) {
  398. return string.charAt(0).toUpperCase() + string.slice(1);
  399. }
  400. // Executable handlers
  401. function callbacks(input, checked, callback, keep) {
  402. if (!keep) {
  403. if (checked) {
  404. input[_callback]('ifToggled');
  405. }
  406. input[_callback]('ifChanged')[_callback]('if' + capitalize(callback));
  407. }
  408. }
  409. })(window.jQuery || window.Zepto);