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.

709 lines
20 KiB

9 years ago
8 years ago
9 years ago
9 years ago
9 years ago
  1. /*!
  2. * # Semantic UI 2.2.0 - State
  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. window = (typeof window != 'undefined' && window.Math == Math)
  14. ? window
  15. : (typeof self != 'undefined' && self.Math == Math)
  16. ? self
  17. : Function('return this')()
  18. ;
  19. $.fn.state = function(parameters) {
  20. var
  21. $allModules = $(this),
  22. moduleSelector = $allModules.selector || '',
  23. hasTouch = ('ontouchstart' in document.documentElement),
  24. time = new Date().getTime(),
  25. performance = [],
  26. query = arguments[0],
  27. methodInvoked = (typeof query == 'string'),
  28. queryArguments = [].slice.call(arguments, 1),
  29. returnedValue
  30. ;
  31. $allModules
  32. .each(function() {
  33. var
  34. settings = ( $.isPlainObject(parameters) )
  35. ? $.extend(true, {}, $.fn.state.settings, parameters)
  36. : $.extend({}, $.fn.state.settings),
  37. error = settings.error,
  38. metadata = settings.metadata,
  39. className = settings.className,
  40. namespace = settings.namespace,
  41. states = settings.states,
  42. text = settings.text,
  43. eventNamespace = '.' + namespace,
  44. moduleNamespace = namespace + '-module',
  45. $module = $(this),
  46. element = this,
  47. instance = $module.data(moduleNamespace),
  48. module
  49. ;
  50. module = {
  51. initialize: function() {
  52. module.verbose('Initializing module');
  53. // allow module to guess desired state based on element
  54. if(settings.automatic) {
  55. module.add.defaults();
  56. }
  57. // bind events with delegated events
  58. if(settings.context && moduleSelector !== '') {
  59. $(settings.context)
  60. .on(moduleSelector, 'mouseenter' + eventNamespace, module.change.text)
  61. .on(moduleSelector, 'mouseleave' + eventNamespace, module.reset.text)
  62. .on(moduleSelector, 'click' + eventNamespace, module.toggle.state)
  63. ;
  64. }
  65. else {
  66. $module
  67. .on('mouseenter' + eventNamespace, module.change.text)
  68. .on('mouseleave' + eventNamespace, module.reset.text)
  69. .on('click' + eventNamespace, module.toggle.state)
  70. ;
  71. }
  72. module.instantiate();
  73. },
  74. instantiate: function() {
  75. module.verbose('Storing instance of module', module);
  76. instance = module;
  77. $module
  78. .data(moduleNamespace, module)
  79. ;
  80. },
  81. destroy: function() {
  82. module.verbose('Destroying previous module', instance);
  83. $module
  84. .off(eventNamespace)
  85. .removeData(moduleNamespace)
  86. ;
  87. },
  88. refresh: function() {
  89. module.verbose('Refreshing selector cache');
  90. $module = $(element);
  91. },
  92. add: {
  93. defaults: function() {
  94. var
  95. userStates = parameters && $.isPlainObject(parameters.states)
  96. ? parameters.states
  97. : {}
  98. ;
  99. $.each(settings.defaults, function(type, typeStates) {
  100. if( module.is[type] !== undefined && module.is[type]() ) {
  101. module.verbose('Adding default states', type, element);
  102. $.extend(settings.states, typeStates, userStates);
  103. }
  104. });
  105. }
  106. },
  107. is: {
  108. active: function() {
  109. return $module.hasClass(className.active);
  110. },
  111. loading: function() {
  112. return $module.hasClass(className.loading);
  113. },
  114. inactive: function() {
  115. return !( $module.hasClass(className.active) );
  116. },
  117. state: function(state) {
  118. if(className[state] === undefined) {
  119. return false;
  120. }
  121. return $module.hasClass( className[state] );
  122. },
  123. enabled: function() {
  124. return !( $module.is(settings.filter.active) );
  125. },
  126. disabled: function() {
  127. return ( $module.is(settings.filter.active) );
  128. },
  129. textEnabled: function() {
  130. return !( $module.is(settings.filter.text) );
  131. },
  132. // definitions for automatic type detection
  133. button: function() {
  134. return $module.is('.button:not(a, .submit)');
  135. },
  136. input: function() {
  137. return $module.is('input');
  138. },
  139. progress: function() {
  140. return $module.is('.ui.progress');
  141. }
  142. },
  143. allow: function(state) {
  144. module.debug('Now allowing state', state);
  145. states[state] = true;
  146. },
  147. disallow: function(state) {
  148. module.debug('No longer allowing', state);
  149. states[state] = false;
  150. },
  151. allows: function(state) {
  152. return states[state] || false;
  153. },
  154. enable: function() {
  155. $module.removeClass(className.disabled);
  156. },
  157. disable: function() {
  158. $module.addClass(className.disabled);
  159. },
  160. setState: function(state) {
  161. if(module.allows(state)) {
  162. $module.addClass( className[state] );
  163. }
  164. },
  165. removeState: function(state) {
  166. if(module.allows(state)) {
  167. $module.removeClass( className[state] );
  168. }
  169. },
  170. toggle: {
  171. state: function() {
  172. var
  173. apiRequest,
  174. requestCancelled
  175. ;
  176. if( module.allows('active') && module.is.enabled() ) {
  177. module.refresh();
  178. if($.fn.api !== undefined) {
  179. apiRequest = $module.api('get request');
  180. requestCancelled = $module.api('was cancelled');
  181. if( requestCancelled ) {
  182. module.debug('API Request cancelled by beforesend');
  183. settings.activateTest = function(){ return false; };
  184. settings.deactivateTest = function(){ return false; };
  185. }
  186. else if(apiRequest) {
  187. module.listenTo(apiRequest);
  188. return;
  189. }
  190. }
  191. module.change.state();
  192. }
  193. }
  194. },
  195. listenTo: function(apiRequest) {
  196. module.debug('API request detected, waiting for state signal', apiRequest);
  197. if(apiRequest) {
  198. if(text.loading) {
  199. module.update.text(text.loading);
  200. }
  201. $.when(apiRequest)
  202. .then(function() {
  203. if(apiRequest.state() == 'resolved') {
  204. module.debug('API request succeeded');
  205. settings.activateTest = function(){ return true; };
  206. settings.deactivateTest = function(){ return true; };
  207. }
  208. else {
  209. module.debug('API request failed');
  210. settings.activateTest = function(){ return false; };
  211. settings.deactivateTest = function(){ return false; };
  212. }
  213. module.change.state();
  214. })
  215. ;
  216. }
  217. },
  218. // checks whether active/inactive state can be given
  219. change: {
  220. state: function() {
  221. module.debug('Determining state change direction');
  222. // inactive to active change
  223. if( module.is.inactive() ) {
  224. module.activate();
  225. }
  226. else {
  227. module.deactivate();
  228. }
  229. if(settings.sync) {
  230. module.sync();
  231. }
  232. settings.onChange.call(element);
  233. },
  234. text: function() {
  235. if( module.is.textEnabled() ) {
  236. if(module.is.disabled() ) {
  237. module.verbose('Changing text to disabled text', text.hover);
  238. module.update.text(text.disabled);
  239. }
  240. else if( module.is.active() ) {
  241. if(text.hover) {
  242. module.verbose('Changing text to hover text', text.hover);
  243. module.update.text(text.hover);
  244. }
  245. else if(text.deactivate) {
  246. module.verbose('Changing text to deactivating text', text.deactivate);
  247. module.update.text(text.deactivate);
  248. }
  249. }
  250. else {
  251. if(text.hover) {
  252. module.verbose('Changing text to hover text', text.hover);
  253. module.update.text(text.hover);
  254. }
  255. else if(text.activate){
  256. module.verbose('Changing text to activating text', text.activate);
  257. module.update.text(text.activate);
  258. }
  259. }
  260. }
  261. }
  262. },
  263. activate: function() {
  264. if( settings.activateTest.call(element) ) {
  265. module.debug('Setting state to active');
  266. $module
  267. .addClass(className.active)
  268. ;
  269. module.update.text(text.active);
  270. settings.onActivate.call(element);
  271. }
  272. },
  273. deactivate: function() {
  274. if( settings.deactivateTest.call(element) ) {
  275. module.debug('Setting state to inactive');
  276. $module
  277. .removeClass(className.active)
  278. ;
  279. module.update.text(text.inactive);
  280. settings.onDeactivate.call(element);
  281. }
  282. },
  283. sync: function() {
  284. module.verbose('Syncing other buttons to current state');
  285. if( module.is.active() ) {
  286. $allModules
  287. .not($module)
  288. .state('activate');
  289. }
  290. else {
  291. $allModules
  292. .not($module)
  293. .state('deactivate')
  294. ;
  295. }
  296. },
  297. get: {
  298. text: function() {
  299. return (settings.selector.text)
  300. ? $module.find(settings.selector.text).text()
  301. : $module.html()
  302. ;
  303. },
  304. textFor: function(state) {
  305. return text[state] || false;
  306. }
  307. },
  308. flash: {
  309. text: function(text, duration, callback) {
  310. var
  311. previousText = module.get.text()
  312. ;
  313. module.debug('Flashing text message', text, duration);
  314. text = text || settings.text.flash;
  315. duration = duration || settings.flashDuration;
  316. callback = callback || function() {};
  317. module.update.text(text);
  318. setTimeout(function(){
  319. module.update.text(previousText);
  320. callback.call(element);
  321. }, duration);
  322. }
  323. },
  324. reset: {
  325. // on mouseout sets text to previous value
  326. text: function() {
  327. var
  328. activeText = text.active || $module.data(metadata.storedText),
  329. inactiveText = text.inactive || $module.data(metadata.storedText)
  330. ;
  331. if( module.is.textEnabled() ) {
  332. if( module.is.active() && activeText) {
  333. module.verbose('Resetting active text', activeText);
  334. module.update.text(activeText);
  335. }
  336. else if(inactiveText) {
  337. module.verbose('Resetting inactive text', activeText);
  338. module.update.text(inactiveText);
  339. }
  340. }
  341. }
  342. },
  343. update: {
  344. text: function(text) {
  345. var
  346. currentText = module.get.text()
  347. ;
  348. if(text && text !== currentText) {
  349. module.debug('Updating text', text);
  350. if(settings.selector.text) {
  351. $module
  352. .data(metadata.storedText, text)
  353. .find(settings.selector.text)
  354. .text(text)
  355. ;
  356. }
  357. else {
  358. $module
  359. .data(metadata.storedText, text)
  360. .html(text)
  361. ;
  362. }
  363. }
  364. else {
  365. module.debug('Text is already set, ignoring update', text);
  366. }
  367. }
  368. },
  369. setting: function(name, value) {
  370. module.debug('Changing setting', name, value);
  371. if( $.isPlainObject(name) ) {
  372. $.extend(true, settings, name);
  373. }
  374. else if(value !== undefined) {
  375. if($.isPlainObject(settings[name])) {
  376. $.extend(true, settings[name], value);
  377. }
  378. else {
  379. settings[name] = value;
  380. }
  381. }
  382. else {
  383. return settings[name];
  384. }
  385. },
  386. internal: function(name, value) {
  387. if( $.isPlainObject(name) ) {
  388. $.extend(true, module, name);
  389. }
  390. else if(value !== undefined) {
  391. module[name] = value;
  392. }
  393. else {
  394. return module[name];
  395. }
  396. },
  397. debug: function() {
  398. if(!settings.silent && settings.debug) {
  399. if(settings.performance) {
  400. module.performance.log(arguments);
  401. }
  402. else {
  403. module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
  404. module.debug.apply(console, arguments);
  405. }
  406. }
  407. },
  408. verbose: function() {
  409. if(!settings.silent && settings.verbose && settings.debug) {
  410. if(settings.performance) {
  411. module.performance.log(arguments);
  412. }
  413. else {
  414. module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
  415. module.verbose.apply(console, arguments);
  416. }
  417. }
  418. },
  419. error: function() {
  420. if(!settings.silent) {
  421. module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
  422. module.error.apply(console, arguments);
  423. }
  424. },
  425. performance: {
  426. log: function(message) {
  427. var
  428. currentTime,
  429. executionTime,
  430. previousTime
  431. ;
  432. if(settings.performance) {
  433. currentTime = new Date().getTime();
  434. previousTime = time || currentTime;
  435. executionTime = currentTime - previousTime;
  436. time = currentTime;
  437. performance.push({
  438. 'Name' : message[0],
  439. 'Arguments' : [].slice.call(message, 1) || '',
  440. 'Element' : element,
  441. 'Execution Time' : executionTime
  442. });
  443. }
  444. clearTimeout(module.performance.timer);
  445. module.performance.timer = setTimeout(module.performance.display, 500);
  446. },
  447. display: function() {
  448. var
  449. title = settings.name + ':',
  450. totalTime = 0
  451. ;
  452. time = false;
  453. clearTimeout(module.performance.timer);
  454. $.each(performance, function(index, data) {
  455. totalTime += data['Execution Time'];
  456. });
  457. title += ' ' + totalTime + 'ms';
  458. if(moduleSelector) {
  459. title += ' \'' + moduleSelector + '\'';
  460. }
  461. if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
  462. console.groupCollapsed(title);
  463. if(console.table) {
  464. console.table(performance);
  465. }
  466. else {
  467. $.each(performance, function(index, data) {
  468. console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
  469. });
  470. }
  471. console.groupEnd();
  472. }
  473. performance = [];
  474. }
  475. },
  476. invoke: function(query, passedArguments, context) {
  477. var
  478. object = instance,
  479. maxDepth,
  480. found,
  481. response
  482. ;
  483. passedArguments = passedArguments || queryArguments;
  484. context = element || context;
  485. if(typeof query == 'string' && object !== undefined) {
  486. query = query.split(/[\. ]/);
  487. maxDepth = query.length - 1;
  488. $.each(query, function(depth, value) {
  489. var camelCaseValue = (depth != maxDepth)
  490. ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
  491. : query
  492. ;
  493. if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) {
  494. object = object[camelCaseValue];
  495. }
  496. else if( object[camelCaseValue] !== undefined ) {
  497. found = object[camelCaseValue];
  498. return false;
  499. }
  500. else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) {
  501. object = object[value];
  502. }
  503. else if( object[value] !== undefined ) {
  504. found = object[value];
  505. return false;
  506. }
  507. else {
  508. module.error(error.method, query);
  509. return false;
  510. }
  511. });
  512. }
  513. if ( $.isFunction( found ) ) {
  514. response = found.apply(context, passedArguments);
  515. }
  516. else if(found !== undefined) {
  517. response = found;
  518. }
  519. if($.isArray(returnedValue)) {
  520. returnedValue.push(response);
  521. }
  522. else if(returnedValue !== undefined) {
  523. returnedValue = [returnedValue, response];
  524. }
  525. else if(response !== undefined) {
  526. returnedValue = response;
  527. }
  528. return found;
  529. }
  530. };
  531. if(methodInvoked) {
  532. if(instance === undefined) {
  533. module.initialize();
  534. }
  535. module.invoke(query);
  536. }
  537. else {
  538. if(instance !== undefined) {
  539. instance.invoke('destroy');
  540. }
  541. module.initialize();
  542. }
  543. })
  544. ;
  545. return (returnedValue !== undefined)
  546. ? returnedValue
  547. : this
  548. ;
  549. };
  550. $.fn.state.settings = {
  551. // module info
  552. name : 'State',
  553. // debug output
  554. debug : false,
  555. // verbose debug output
  556. verbose : false,
  557. // namespace for events
  558. namespace : 'state',
  559. // debug data includes performance
  560. performance : true,
  561. // callback occurs on state change
  562. onActivate : function() {},
  563. onDeactivate : function() {},
  564. onChange : function() {},
  565. // state test functions
  566. activateTest : function() { return true; },
  567. deactivateTest : function() { return true; },
  568. // whether to automatically map default states
  569. automatic : true,
  570. // activate / deactivate changes all elements instantiated at same time
  571. sync : false,
  572. // default flash text duration, used for temporarily changing text of an element
  573. flashDuration : 1000,
  574. // selector filter
  575. filter : {
  576. text : '.loading, .disabled',
  577. active : '.disabled'
  578. },
  579. context : false,
  580. // error
  581. error: {
  582. beforeSend : 'The before send function has cancelled state change',
  583. method : 'The method you called is not defined.'
  584. },
  585. // metadata
  586. metadata: {
  587. promise : 'promise',
  588. storedText : 'stored-text'
  589. },
  590. // change class on state
  591. className: {
  592. active : 'active',
  593. disabled : 'disabled',
  594. error : 'error',
  595. loading : 'loading',
  596. success : 'success',
  597. warning : 'warning'
  598. },
  599. selector: {
  600. // selector for text node
  601. text: false
  602. },
  603. defaults : {
  604. input: {
  605. disabled : true,
  606. loading : true,
  607. active : true
  608. },
  609. button: {
  610. disabled : true,
  611. loading : true,
  612. active : true,
  613. },
  614. progress: {
  615. active : true,
  616. success : true,
  617. warning : true,
  618. error : true
  619. }
  620. },
  621. states : {
  622. active : true,
  623. disabled : true,
  624. error : true,
  625. loading : true,
  626. success : true,
  627. warning : true
  628. },
  629. text : {
  630. disabled : false,
  631. flash : false,
  632. hover : false,
  633. active : false,
  634. inactive : false,
  635. activate : false,
  636. deactivate : false
  637. }
  638. };
  639. })( jQuery, window, document );