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.

725 lines
21 KiB

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