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.

590 lines
17 KiB

  1. /* ******************************
  2. Semantic Module: Checkbox
  3. Author: Jack Lukic
  4. Notes: First Commit MAy 25, 2013
  5. Simple plug-in which maintains the state for ui dropdown
  6. ****************************** */
  7. ;(function ( $, window, document, undefined ) {
  8. $.fn.dropdown = function(parameters) {
  9. var
  10. $allModules = $(this),
  11. $document = $(document),
  12. settings = ( $.isPlainObject(parameters) )
  13. ? $.extend(true, {}, $.fn.dropdown.settings, parameters)
  14. : $.fn.dropdown.settings,
  15. eventNamespace = '.' + settings.namespace,
  16. moduleNamespace = 'module-' + settings.namespace,
  17. selector = $allModules.selector || '',
  18. time = new Date().getTime(),
  19. performance = [],
  20. query = arguments[0],
  21. methodInvoked = (typeof query == 'string'),
  22. queryArguments = [].slice.call(arguments, 1),
  23. invokedResponse,
  24. allModules
  25. ;
  26. $allModules
  27. .each(function() {
  28. var
  29. $module = $(this),
  30. $menu = $(this).find(settings.selector.menu),
  31. $item = $(this).find(settings.selector.item),
  32. $text = $(this).find(settings.selector.text),
  33. $input = $(this).find(settings.selector.input),
  34. isTouchDevice = ('ontouchstart' in document.documentElement),
  35. selector = $module.selector || '',
  36. element = this,
  37. instance = $module.data('module-' + settings.namespace),
  38. className = settings.className,
  39. metadata = settings.metadata,
  40. namespace = settings.namespace,
  41. animation = settings.animation,
  42. errors = settings.errors,
  43. module
  44. ;
  45. module = {
  46. initialize: function() {
  47. module.verbose('Initializing dropdown with bound events', $module);
  48. if(isTouchDevice) {
  49. $module
  50. .on('touchstart' + eventNamespace, module.event.test.toggle)
  51. ;
  52. }
  53. else if(settings.on == 'click') {
  54. $module
  55. .on('click' + eventNamespace, module.event.test.toggle)
  56. ;
  57. }
  58. else if(settings.on == 'hover') {
  59. $module
  60. .on('mouseenter' + eventNamespace, module.show)
  61. .on('mouseleave' + eventNamespace, module.delayedHide)
  62. ;
  63. }
  64. else {
  65. $module
  66. .on(settings.on + eventNamespace, module.toggle)
  67. ;
  68. }
  69. if(settings.action == 'form') {
  70. module.set.selected();
  71. }
  72. $item
  73. .on(module.get.selectEvent() + eventNamespace, module.event.item.click)
  74. ;
  75. $module
  76. .data(moduleNamespace, module)
  77. ;
  78. },
  79. destroy: function() {
  80. module.verbose('Destroying previous module for', $module);
  81. $module
  82. .off(namespace)
  83. ;
  84. },
  85. event: {
  86. stopPropagation: function(event) {
  87. event.stopPropagation();
  88. },
  89. test: {
  90. toggle: function(event) {
  91. module.intent.test(event, module.toggle);
  92. event.stopPropagation();
  93. },
  94. hide: function(event) {
  95. module.intent.test(event, module.hide);
  96. event.stopPropagation();
  97. }
  98. },
  99. item: {
  100. click: function (event) {
  101. var
  102. $choice = $(this),
  103. text = $choice.data(metadata.text) || $choice.text(),
  104. value = $choice.data(metadata.value) || text
  105. ;
  106. module.verbose('Adding active state to selected item');
  107. $item
  108. .removeClass(className.active)
  109. ;
  110. $choice
  111. .addClass(className.active)
  112. ;
  113. module.action.determine(text, value);
  114. $.proxy(settings.onChange, $menu.get())(text, value);
  115. event.stopPropagation();
  116. }
  117. }
  118. },
  119. intent: {
  120. test: function(event, callback) {
  121. module.debug('Determining whether event occurred in dropdown', event.target);
  122. callback = callback || function(){};
  123. if( $(event.target).closest($menu).size() === 0 ) {
  124. module.verbose('Triggering event', callback);
  125. callback();
  126. }
  127. else {
  128. module.verbose('Event occurred in dropdown, canceling callback');
  129. }
  130. },
  131. bind: function() {
  132. module.verbose('Binding hide intent event to document');
  133. $document
  134. .on(module.get.selectEvent(), module.event.test.hide)
  135. ;
  136. },
  137. unbind: function() {
  138. module.verbose('Removing hide intent event from document');
  139. $document
  140. .off(module.get.selectEvent())
  141. ;
  142. }
  143. },
  144. action: {
  145. determine: function(text, value) {
  146. if( $.isFunction( module.action[settings.action] ) ) {
  147. module.verbose('Triggering preset action', settings.action);
  148. module.action[ settings.action ](text, value);
  149. }
  150. else if( $.isFunction(settings.action) ) {
  151. module.verbose('Triggering user action', settings.action);
  152. settings.action(text, value);
  153. }
  154. else {
  155. module.error(errors.action);
  156. }
  157. },
  158. nothing: function() {},
  159. hide: function() {
  160. module.hide();
  161. },
  162. changeText: function(text, value) {
  163. module.set.text(text);
  164. module.hide();
  165. },
  166. form: function(text, value) {
  167. module.set.text(text);
  168. module.set.value(value);
  169. module.hide();
  170. }
  171. },
  172. get: {
  173. selectEvent: function() {
  174. return (isTouchDevice)
  175. ? 'touchstart'
  176. : 'click'
  177. ;
  178. },
  179. text: function() {
  180. return $text.text();
  181. },
  182. value: function() {
  183. return $input.val();
  184. },
  185. item: function(value) {
  186. var
  187. $selectedItem
  188. ;
  189. value = value || $input.val();
  190. $item
  191. .each(function() {
  192. if( $(this).data(metadata.value) == value ) {
  193. $selectedItem = $(this);
  194. }
  195. })
  196. ;
  197. return $selectedItem || false;
  198. }
  199. },
  200. set: {
  201. text: function(text) {
  202. module.debug('Changing text', text);
  203. $text.text(text);
  204. },
  205. value: function(value) {
  206. module.debug('Adding selected value to hidden input', value);
  207. $input.val(value);
  208. },
  209. selected: function(value) {
  210. var
  211. selectedValue = value || $input.val(),
  212. $selectedItem = module.get.item(value),
  213. selectedText
  214. ;
  215. if($selectedItem) {
  216. module.debug('Setting selected menu item to', $selectedItem);
  217. selectedText = $selectedItem.data(metadata.text) || $selectedItem.text();
  218. $item
  219. .removeClass(className.active)
  220. ;
  221. $selectedItem
  222. .addClass(className.active)
  223. ;
  224. module.set.text(selectedText);
  225. }
  226. }
  227. },
  228. is: {
  229. visible: function() {
  230. return $menu.is(':visible');
  231. },
  232. hidden: function() {
  233. return $menu.is(':not(:visible)');
  234. }
  235. },
  236. can: {
  237. click: function() {
  238. return (isTouchDevice || settings.on == 'click');
  239. },
  240. show: function() {
  241. return !$module.hasClass(className.disabled);
  242. }
  243. },
  244. animate: {
  245. show: function() {
  246. module.verbose('Doing menu showing animation');
  247. if(animation.show == 'show') {
  248. $menu
  249. .show()
  250. ;
  251. }
  252. else if(animation.show == 'slide') {
  253. $menu
  254. .clearQueue()
  255. .children()
  256. .clearQueue()
  257. .css('opacity', 0)
  258. .delay(100)
  259. .animate({
  260. opacity : 1
  261. }, 300, 'easeOutQuad')
  262. .end()
  263. .slideDown(200, 'easeOutQuad')
  264. ;
  265. }
  266. else {
  267. module.errors(error.animation);
  268. }
  269. },
  270. hide: function() {
  271. module.verbose('Doing menu hiding animation');
  272. if(animation.hide == 'hide') {
  273. $menu
  274. .hide()
  275. ;
  276. }
  277. else if(animation.hide == 'slide') {
  278. $menu
  279. .clearQueue()
  280. .children()
  281. .clearQueue()
  282. .css('opacity', 1)
  283. .animate({
  284. opacity : 0
  285. }, 300, 'easeOutQuad')
  286. .end()
  287. .delay(100)
  288. .slideUp(200, 'easeOutQuad')
  289. ;
  290. }
  291. else {
  292. module.errors(error.animation);
  293. }
  294. }
  295. },
  296. show: function() {
  297. module.debug('Checking if dropdown can show');
  298. clearTimeout(module.graceTimer);
  299. if( !module.is.visible() ) {
  300. module.hideOthers();
  301. $module
  302. .addClass(className.visible)
  303. ;
  304. module.animate.show();
  305. if( module.can.click() ) {
  306. module.intent.bind();
  307. }
  308. $.proxy(settings.onShow, $menu.get())();
  309. }
  310. },
  311. hide: function() {
  312. if( !module.is.hidden() ) {
  313. module.debug('Hiding dropdown');
  314. $module
  315. .removeClass(className.visible)
  316. ;
  317. if( module.can.click() ) {
  318. module.intent.unbind();
  319. }
  320. module.animate.hide();
  321. $.proxy(settings.onHide, $menu.get())();
  322. }
  323. },
  324. delayedHide: function() {
  325. module.verbose('User moused away setting timer to hide dropdown');
  326. module.graceTimer = setTimeout(module.hide, settings.gracePeriod);
  327. },
  328. hideOthers: function() {
  329. module.verbose('Finding other dropdowns to hide');
  330. $allModules
  331. .not($module)
  332. .has(settings.selector.menu + ':visible')
  333. .dropdown('hide')
  334. ;
  335. },
  336. toggle: function() {
  337. module.verbose('Toggling menu visibility');
  338. if( module.is.hidden() ) {
  339. module.show();
  340. }
  341. else {
  342. module.hide();
  343. }
  344. },
  345. setting: function(name, value) {
  346. if(value !== undefined) {
  347. if( $.isPlainObject(name) ) {
  348. $.extend(true, settings, name);
  349. }
  350. else {
  351. settings[name] = value;
  352. }
  353. }
  354. else {
  355. return settings[name];
  356. }
  357. },
  358. internal: function(name, value) {
  359. if(value !== undefined) {
  360. if( $.isPlainObject(name) ) {
  361. $.extend(true, module, name);
  362. }
  363. else {
  364. module[name] = value;
  365. }
  366. }
  367. else {
  368. return module[name];
  369. }
  370. },
  371. debug: function() {
  372. if(settings.debug) {
  373. if(settings.performance) {
  374. module.performance.log(arguments);
  375. }
  376. else {
  377. module.debug = Function.prototype.bind.call(console.info, console, settings.moduleName + ':');
  378. }
  379. }
  380. },
  381. verbose: function() {
  382. if(settings.verbose && settings.debug) {
  383. if(settings.performance) {
  384. module.performance.log(arguments);
  385. }
  386. else {
  387. module.verbose = Function.prototype.bind.call(console.info, console, settings.moduleName + ':');
  388. }
  389. }
  390. },
  391. error: function() {
  392. module.error = Function.prototype.bind.call(console.log, console, settings.moduleName + ':');
  393. },
  394. performance: {
  395. log: function(message) {
  396. var
  397. currentTime,
  398. executionTime,
  399. previousTime
  400. ;
  401. if(settings.performance) {
  402. currentTime = new Date().getTime();
  403. previousTime = time || currentTime,
  404. executionTime = currentTime - previousTime;
  405. time = currentTime;
  406. performance.push({
  407. 'Element' : element,
  408. 'Name' : message[0],
  409. 'Arguments' : message[1] || 'None',
  410. 'Execution Time' : executionTime
  411. });
  412. clearTimeout(module.performance.timer);
  413. module.performance.timer = setTimeout(module.performance.display, 100);
  414. }
  415. },
  416. display: function() {
  417. var
  418. title = settings.moduleName,
  419. caption = settings.moduleName + ': ' + selector + '(' + $allModules.size() + ' elements)',
  420. totalExecutionTime = 0
  421. ;
  422. if(selector) {
  423. title += ' Performance (' + selector + ')';
  424. }
  425. if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
  426. console.groupCollapsed(title);
  427. if(console.table) {
  428. $.each(performance, function(index, data) {
  429. totalExecutionTime += data['Execution Time'];
  430. });
  431. console.table(performance);
  432. }
  433. else {
  434. $.each(performance, function(index, data) {
  435. totalExecutionTime += data['Execution Time'];
  436. console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
  437. });
  438. }
  439. console.log('Total Execution Time:', totalExecutionTime +'ms');
  440. console.groupEnd();
  441. performance = [];
  442. time = false;
  443. }
  444. }
  445. },
  446. invoke: function(query, passedArguments, context) {
  447. var
  448. maxDepth,
  449. found
  450. ;
  451. passedArguments = passedArguments || queryArguments;
  452. context = element || context;
  453. if(typeof query == 'string' && instance !== undefined) {
  454. query = query.split('.');
  455. maxDepth = query.length - 1;
  456. $.each(query, function(depth, value) {
  457. if( $.isPlainObject( instance[value] ) && (depth != maxDepth) ) {
  458. instance = instance[value];
  459. return true;
  460. }
  461. else if( instance[value] !== undefined ) {
  462. found = instance[value];
  463. return true;
  464. }
  465. module.error(errors.method);
  466. return false;
  467. });
  468. }
  469. if ( $.isFunction( found ) ) {
  470. instance.verbose('Executing invoked function', found);
  471. return found.apply(context, passedArguments);
  472. }
  473. return found || false;
  474. }
  475. };
  476. if(methodInvoked) {
  477. if(instance === undefined) {
  478. module.initialize();
  479. }
  480. invokedResponse = module.invoke(query);
  481. }
  482. else {
  483. if(instance !== undefined) {
  484. module.destroy();
  485. }
  486. module.initialize();
  487. }
  488. })
  489. ;
  490. return (invokedResponse)
  491. ? invokedResponse
  492. : this
  493. ;
  494. };
  495. $.fn.dropdown.settings = {
  496. moduleName : 'Dropdown Module',
  497. namespace : 'dropdown',
  498. verbose : true,
  499. debug : true,
  500. performance : false,
  501. on : 'click',
  502. gracePeriod : 300,
  503. action : 'hide',
  504. animation : {
  505. show: 'slide',
  506. hide: 'slide'
  507. },
  508. onChange : function(){},
  509. onShow : function(){},
  510. onHide : function(){},
  511. errors : {
  512. action : 'You called a dropdown action that was not defined',
  513. method : 'The method you called is not defined.',
  514. animation : 'The requested animation was not found'
  515. },
  516. metadata: {
  517. text : 'text',
  518. value : 'value'
  519. },
  520. selector : {
  521. menu : '.menu',
  522. item : '.menu > .item',
  523. text : '> .text',
  524. input : '> input[type="hidden"]'
  525. },
  526. className : {
  527. active : 'active',
  528. disabled : 'disabled',
  529. visible : 'visible'
  530. }
  531. };
  532. })( jQuery, window , document );