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.

706 lines
21 KiB

  1. /*!
  2. * # Semantic UI 2.0.0 - Checkbox
  3. * http://github.com/semantic-org/semantic-ui/
  4. *
  5. *
  6. * Copyright 2015 Contributors
  7. * Released under the MIT license
  8. * http://opensource.org/licenses/MIT
  9. *
  10. */
  11. ;(function ( $, window, document, undefined ) {
  12. "use strict";
  13. $.fn.checkbox = function(parameters) {
  14. var
  15. $allModules = $(this),
  16. moduleSelector = $allModules.selector || '',
  17. time = new Date().getTime(),
  18. performance = [],
  19. query = arguments[0],
  20. methodInvoked = (typeof query == 'string'),
  21. queryArguments = [].slice.call(arguments, 1),
  22. returnedValue
  23. ;
  24. $allModules
  25. .each(function() {
  26. var
  27. settings = $.extend(true, {}, $.fn.checkbox.settings, parameters),
  28. className = settings.className,
  29. namespace = settings.namespace,
  30. selector = settings.selector,
  31. error = settings.error,
  32. eventNamespace = '.' + namespace,
  33. moduleNamespace = 'module-' + namespace,
  34. $module = $(this),
  35. $label = $(this).children(selector.label),
  36. $input = $(this).children(selector.input),
  37. instance = $module.data(moduleNamespace),
  38. observer,
  39. element = this,
  40. module
  41. ;
  42. module = {
  43. initialize: function() {
  44. module.verbose('Initializing checkbox', settings);
  45. module.create.label();
  46. module.bind.events();
  47. module.set.tabbable();
  48. module.hide.input();
  49. module.observeChanges();
  50. module.instantiate();
  51. module.setup();
  52. },
  53. instantiate: function() {
  54. module.verbose('Storing instance of module', module);
  55. instance = module;
  56. $module
  57. .data(moduleNamespace, module)
  58. ;
  59. },
  60. destroy: function() {
  61. module.verbose('Destroying module');
  62. module.unbind.events();
  63. module.show.input();
  64. $module.removeData(moduleNamespace);
  65. },
  66. fix: {
  67. reference: function() {
  68. if( $module.is(selector.input) ) {
  69. module.debug('Behavior called on <input> adjusting invoked element');
  70. $module = $module.closest(selector.checkbox);
  71. module.refresh();
  72. }
  73. }
  74. },
  75. setup: function() {
  76. if( module.is.indeterminate() ) {
  77. module.debug('Initial value is indeterminate');
  78. module.set.indeterminate();
  79. if(settings.fireOnInit) {
  80. settings.onIndeterminate.call($input[0]);
  81. settings.onChange.call($input[0]);
  82. }
  83. }
  84. else if( module.is.checked() ) {
  85. module.debug('Initial value is checked');
  86. module.set.checked();
  87. if(settings.fireOnInit) {
  88. settings.onChecked.call($input[0]);
  89. settings.onChange.call($input[0]);
  90. }
  91. }
  92. else {
  93. module.debug('Initial value is unchecked');
  94. module.set.unchecked();
  95. if(settings.fireOnInit) {
  96. settings.onUnchecked.call($input[0]);
  97. settings.onChange.call($input[0]);
  98. }
  99. }
  100. },
  101. refresh: function() {
  102. $label = $module.children(selector.label);
  103. $input = $module.children(selector.input);
  104. },
  105. hide: {
  106. input: function() {
  107. module.verbose('Modfying <input> z-index to be unselectable');
  108. $input.addClass(className.hidden);
  109. }
  110. },
  111. show: {
  112. input: function() {
  113. module.verbose('Modfying <input> z-index to be selectable');
  114. $input.removeClass(className.hidden);
  115. }
  116. },
  117. observeChanges: function() {
  118. if('MutationObserver' in window) {
  119. observer = new MutationObserver(function(mutations) {
  120. module.debug('DOM tree modified, updating selector cache');
  121. module.refresh();
  122. });
  123. observer.observe(element, {
  124. childList : true,
  125. subtree : true
  126. });
  127. module.debug('Setting up mutation observer', observer);
  128. }
  129. },
  130. attachEvents: function(selector, event) {
  131. var
  132. $element = $(selector)
  133. ;
  134. event = $.isFunction(module[event])
  135. ? module[event]
  136. : module.toggle
  137. ;
  138. if($element.length > 0) {
  139. module.debug('Attaching checkbox events to element', selector, event);
  140. $element
  141. .on('click' + eventNamespace, event)
  142. ;
  143. }
  144. else {
  145. module.error(error.notFound);
  146. }
  147. },
  148. event: {
  149. click: function(event) {
  150. if( $(event.target).is(selector.input) ) {
  151. module.verbose('Using default check action on initialized checkbox');
  152. return;
  153. }
  154. module.toggle();
  155. },
  156. keydown: function(event) {
  157. var
  158. key = event.which,
  159. keyCode = {
  160. enter : 13,
  161. space : 32,
  162. escape : 27
  163. }
  164. ;
  165. if(key == keyCode.escape) {
  166. module.verbose('Escape key pressed blurring field');
  167. $input.blur();
  168. event.preventDefault();
  169. }
  170. if(!event.ctrlKey && (key == keyCode.enter || key == keyCode.space)) {
  171. module.verbose('Enter key pressed, toggling checkbox');
  172. module.toggle();
  173. event.preventDefault();
  174. }
  175. }
  176. },
  177. check: function() {
  178. if( !module.is.indeterminate() && module.is.checked() ) {
  179. module.debug('Checkbox is already checked');
  180. return;
  181. }
  182. module.debug('Checking checkbox', $input);
  183. module.set.checked();
  184. settings.onChecked.call($input[0]);
  185. settings.onChange.call($input[0]);
  186. },
  187. uncheck: function() {
  188. if( !module.is.indeterminate() && module.is.unchecked() ) {
  189. module.debug('Checkbox is already unchecked');
  190. return;
  191. }
  192. module.debug('Unchecking checkbox');
  193. module.set.unchecked();
  194. settings.onUnchecked.call($input[0]);
  195. settings.onChange.call($input[0]);
  196. },
  197. indeterminate: function() {
  198. if( module.is.indeterminate() ) {
  199. module.debug('Checkbox is already indeterminate');
  200. return;
  201. }
  202. module.debug('Making checkbox indeterminate');
  203. module.set.indeterminate();
  204. settings.onIndeterminate.call($input[0]);
  205. settings.onChange.call($input[0]);
  206. },
  207. determinate: function() {
  208. if( module.is.determinate() ) {
  209. module.debug('Checkbox is already determinate');
  210. return;
  211. }
  212. module.debug('Making checkbox determinate');
  213. module.set.determinate();
  214. settings.onDeterminate.call($input[0]);
  215. settings.onChange.call($input[0]);
  216. },
  217. enable: function() {
  218. if( module.is.enabled() ) {
  219. module.debug('Checkbox is already enabled');
  220. return;
  221. }
  222. module.debug('Enabling checkbox');
  223. module.set.enabled();
  224. settings.onEnable.call($input[0]);
  225. },
  226. disable: function() {
  227. if( module.is.disabled() ) {
  228. module.debug('Checkbox is already disabled');
  229. return;
  230. }
  231. module.debug('Disabling checkbox');
  232. module.set.disabled();
  233. settings.onDisable.call($input[0]);
  234. },
  235. get: {
  236. radios: function() {
  237. var
  238. name = module.get.name()
  239. ;
  240. return $('input[name="' + name + '"]').closest(selector.checkbox);
  241. },
  242. name: function() {
  243. return $input.attr('name');
  244. }
  245. },
  246. is: {
  247. radio: function() {
  248. return ($input.hasClass(className.radio) || $input.attr('type') == 'radio');
  249. },
  250. indeterminate: function() {
  251. return $input.prop('indeterminate') !== undefined && $input.prop('indeterminate');
  252. },
  253. checked: function() {
  254. return $input.prop('checked') !== undefined && $input.prop('checked');
  255. },
  256. disabled: function() {
  257. return $input.prop('disabled') !== undefined && $input.prop('disabled');
  258. },
  259. enabled: function() {
  260. return !module.is.disabled();
  261. },
  262. determinate: function() {
  263. return !module.is.indeterminate();
  264. },
  265. unchecked: function() {
  266. return !module.is.checked();
  267. }
  268. },
  269. can: {
  270. change: function() {
  271. return !( $module.hasClass(className.disabled) || $module.hasClass(className.readOnly) || $input.prop('disabled') || $input.prop('readonly') );
  272. },
  273. uncheck: function() {
  274. return (typeof settings.uncheckable === 'boolean')
  275. ? settings.uncheckable
  276. : !module.is.radio()
  277. ;
  278. }
  279. },
  280. set: {
  281. checked: function() {
  282. if(!module.is.indeterminate() && module.is.checked()) {
  283. module.debug('Input is already checked');
  284. return;
  285. }
  286. module.verbose('Setting state to checked', $input[0]);
  287. if( module.is.radio() ) {
  288. module.uncheckOthers();
  289. }
  290. $input
  291. .prop('indeterminate', false)
  292. .prop('checked', true)
  293. ;
  294. $module
  295. .removeClass(className.indeterminate)
  296. .addClass(className.checked)
  297. ;
  298. module.trigger.change();
  299. },
  300. unchecked: function() {
  301. if(!module.is.indeterminate() && module.is.unchecked() ) {
  302. module.debug('Input is already unchecked');
  303. return;
  304. }
  305. module.debug('Setting state to unchecked');
  306. $input
  307. .prop('indeterminate', false)
  308. .prop('checked', false)
  309. ;
  310. $module
  311. .removeClass(className.indeterminate)
  312. .removeClass(className.checked)
  313. ;
  314. module.trigger.change();
  315. },
  316. indeterminate: function() {
  317. if( module.is.indeterminate() ) {
  318. module.debug('Input is already indeterminate');
  319. return;
  320. }
  321. module.debug('Setting state to indeterminate');
  322. $input
  323. .prop('indeterminate', true)
  324. ;
  325. $module
  326. .addClass(className.indeterminate)
  327. ;
  328. module.trigger.change();
  329. },
  330. determinate: function() {
  331. if( module.is.determinate() ) {
  332. module.debug('Input is already determinate');
  333. return;
  334. }
  335. module.debug('Setting state to determinate');
  336. $input
  337. .prop('indeterminate', false)
  338. ;
  339. $module
  340. .removeClass(className.indeterminate)
  341. ;
  342. },
  343. disabled: function() {
  344. if( module.is.disabled() ) {
  345. module.debug('Input is already disabled');
  346. return;
  347. }
  348. module.debug('Setting state to disabled');
  349. $input
  350. .prop('disabled', 'disabled')
  351. ;
  352. $module
  353. .addClass(className.disabled)
  354. ;
  355. module.trigger.change();
  356. },
  357. enabled: function() {
  358. if( module.is.enabled() ) {
  359. module.debug('Input is already enabled');
  360. return;
  361. }
  362. module.debug('Setting state to enabled');
  363. $input
  364. .prop('disabled', false)
  365. ;
  366. $module.removeClass(className.disabled);
  367. module.trigger.change();
  368. },
  369. tabbable: function() {
  370. module.verbose('Adding tabindex to checkbox');
  371. if( $input.attr('tabindex') === undefined) {
  372. $input.attr('tabindex', 0);
  373. }
  374. }
  375. },
  376. trigger: {
  377. change: function() {
  378. module.verbose('Triggering change event from programmatic change');
  379. $input
  380. .trigger('change')
  381. ;
  382. }
  383. },
  384. create: {
  385. label: function() {
  386. if($input.prevAll(selector.label).length > 0) {
  387. $input.prev(selector.label).detach().insertAfter($input);
  388. module.debug('Moving existing label', $label);
  389. }
  390. else if( !module.has.label() ) {
  391. $label = $('<label>').insertAfter($input);
  392. module.debug('Creating label', $label);
  393. }
  394. }
  395. },
  396. has: {
  397. label: function() {
  398. return ($label.length > 0);
  399. }
  400. },
  401. bind: {
  402. events: function() {
  403. module.verbose('Attaching checkbox events');
  404. $module
  405. .on('click' + eventNamespace, module.event.click)
  406. .on('keydown' + eventNamespace, selector.input, module.event.keydown)
  407. ;
  408. }
  409. },
  410. unbind: {
  411. events: function() {
  412. module.debug('Removing events');
  413. $module
  414. .off(eventNamespace)
  415. ;
  416. }
  417. },
  418. uncheckOthers: function() {
  419. var
  420. $radios = module.get.radios()
  421. ;
  422. module.debug('Unchecking other radios', $radios);
  423. $radios.removeClass(className.checked);
  424. },
  425. toggle: function() {
  426. if( !module.can.change() ) {
  427. if(!module.is.radio()) {
  428. module.debug('Checkbox is read-only or disabled, ignoring toggle');
  429. }
  430. return;
  431. }
  432. if( module.is.indeterminate() || module.is.unchecked() ) {
  433. module.debug('Currently unchecked');
  434. module.check();
  435. }
  436. else if( module.is.checked() && module.can.uncheck() ) {
  437. module.debug('Currently checked');
  438. module.uncheck();
  439. }
  440. },
  441. setting: function(name, value) {
  442. module.debug('Changing setting', name, value);
  443. if( $.isPlainObject(name) ) {
  444. $.extend(true, settings, name);
  445. }
  446. else if(value !== undefined) {
  447. settings[name] = value;
  448. }
  449. else {
  450. return settings[name];
  451. }
  452. },
  453. internal: function(name, value) {
  454. if( $.isPlainObject(name) ) {
  455. $.extend(true, module, name);
  456. }
  457. else if(value !== undefined) {
  458. module[name] = value;
  459. }
  460. else {
  461. return module[name];
  462. }
  463. },
  464. debug: function() {
  465. if(settings.debug) {
  466. if(settings.performance) {
  467. module.performance.log(arguments);
  468. }
  469. else {
  470. module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
  471. module.debug.apply(console, arguments);
  472. }
  473. }
  474. },
  475. verbose: function() {
  476. if(settings.verbose && settings.debug) {
  477. if(settings.performance) {
  478. module.performance.log(arguments);
  479. }
  480. else {
  481. module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
  482. module.verbose.apply(console, arguments);
  483. }
  484. }
  485. },
  486. error: function() {
  487. module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
  488. module.error.apply(console, arguments);
  489. },
  490. performance: {
  491. log: function(message) {
  492. var
  493. currentTime,
  494. executionTime,
  495. previousTime
  496. ;
  497. if(settings.performance) {
  498. currentTime = new Date().getTime();
  499. previousTime = time || currentTime;
  500. executionTime = currentTime - previousTime;
  501. time = currentTime;
  502. performance.push({
  503. 'Name' : message[0],
  504. 'Arguments' : [].slice.call(message, 1) || '',
  505. 'Element' : element,
  506. 'Execution Time' : executionTime
  507. });
  508. }
  509. clearTimeout(module.performance.timer);
  510. module.performance.timer = setTimeout(module.performance.display, 500);
  511. },
  512. display: function() {
  513. var
  514. title = settings.name + ':',
  515. totalTime = 0
  516. ;
  517. time = false;
  518. clearTimeout(module.performance.timer);
  519. $.each(performance, function(index, data) {
  520. totalTime += data['Execution Time'];
  521. });
  522. title += ' ' + totalTime + 'ms';
  523. if(moduleSelector) {
  524. title += ' \'' + moduleSelector + '\'';
  525. }
  526. if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
  527. console.groupCollapsed(title);
  528. if(console.table) {
  529. console.table(performance);
  530. }
  531. else {
  532. $.each(performance, function(index, data) {
  533. console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
  534. });
  535. }
  536. console.groupEnd();
  537. }
  538. performance = [];
  539. }
  540. },
  541. invoke: function(query, passedArguments, context) {
  542. var
  543. object = instance,
  544. maxDepth,
  545. found,
  546. response
  547. ;
  548. passedArguments = passedArguments || queryArguments;
  549. context = element || context;
  550. if(typeof query == 'string' && object !== undefined) {
  551. query = query.split(/[\. ]/);
  552. maxDepth = query.length - 1;
  553. $.each(query, function(depth, value) {
  554. var camelCaseValue = (depth != maxDepth)
  555. ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
  556. : query
  557. ;
  558. if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) {
  559. object = object[camelCaseValue];
  560. }
  561. else if( object[camelCaseValue] !== undefined ) {
  562. found = object[camelCaseValue];
  563. return false;
  564. }
  565. else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) {
  566. object = object[value];
  567. }
  568. else if( object[value] !== undefined ) {
  569. found = object[value];
  570. return false;
  571. }
  572. else {
  573. module.error(error.method, query);
  574. return false;
  575. }
  576. });
  577. }
  578. if ( $.isFunction( found ) ) {
  579. response = found.apply(context, passedArguments);
  580. }
  581. else if(found !== undefined) {
  582. response = found;
  583. }
  584. if($.isArray(returnedValue)) {
  585. returnedValue.push(response);
  586. }
  587. else if(returnedValue !== undefined) {
  588. returnedValue = [returnedValue, response];
  589. }
  590. else if(response !== undefined) {
  591. returnedValue = response;
  592. }
  593. return found;
  594. }
  595. };
  596. if(methodInvoked) {
  597. if(instance === undefined) {
  598. module.initialize();
  599. }
  600. module.invoke(query);
  601. }
  602. else {
  603. if(instance !== undefined) {
  604. instance.invoke('destroy');
  605. }
  606. module.initialize();
  607. }
  608. })
  609. ;
  610. return (returnedValue !== undefined)
  611. ? returnedValue
  612. : this
  613. ;
  614. };
  615. $.fn.checkbox.settings = {
  616. name : 'Checkbox',
  617. namespace : 'checkbox',
  618. debug : false,
  619. verbose : true,
  620. performance : true,
  621. // delegated event context
  622. uncheckable : 'auto',
  623. fireOnInit : false,
  624. onChange : function(){},
  625. onChecked : function(){},
  626. onUnchecked : function(){},
  627. onDeterminate : function() {},
  628. onIndeterminate : function() {},
  629. onEnabled : function(){},
  630. onDisabled : function(){},
  631. className : {
  632. checked : 'checked',
  633. indeterminate : 'indeterminate',
  634. disabled : 'disabled',
  635. hidden : 'hidden',
  636. radio : 'radio',
  637. readOnly : 'read-only'
  638. },
  639. error : {
  640. method : 'The method you called is not defined'
  641. },
  642. selector : {
  643. checkbox : '.ui.checkbox',
  644. label : 'label, .box',
  645. input : 'input[type="checkbox"], input[type="radio"]',
  646. }
  647. };
  648. })( jQuery, window , document );