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.

787 lines
24 KiB

10 years ago
  1. /*
  2. * # Semantic - Shape
  3. * http://github.com/semantic-org/semantic-ui/
  4. *
  5. *
  6. * Copyright 2014 Contributor
  7. * Released under the MIT license
  8. * http://opensource.org/licenses/MIT
  9. *
  10. */
  11. ;(function ( $, window, document, undefined ) {
  12. $.fn.shape = function(parameters) {
  13. var
  14. $allModules = $(this),
  15. $body = $('body'),
  16. time = new Date().getTime(),
  17. performance = [],
  18. query = arguments[0],
  19. methodInvoked = (typeof query == 'string'),
  20. queryArguments = [].slice.call(arguments, 1),
  21. returnedValue
  22. ;
  23. $allModules
  24. .each(function() {
  25. var
  26. moduleSelector = $allModules.selector || '',
  27. settings = $.extend(true, {}, $.fn.shape.settings, parameters),
  28. // internal aliases
  29. namespace = settings.namespace,
  30. selector = settings.selector,
  31. error = settings.error,
  32. className = settings.className,
  33. // define namespaces for modules
  34. eventNamespace = '.' + namespace,
  35. moduleNamespace = 'module-' + namespace,
  36. // selector cache
  37. $module = $(this),
  38. $sides = $module.find(selector.sides),
  39. $side = $module.find(selector.side),
  40. // private variables
  41. nextSelector = false,
  42. $activeSide,
  43. $nextSide,
  44. // standard module
  45. element = this,
  46. instance = $module.data(moduleNamespace),
  47. module
  48. ;
  49. module = {
  50. initialize: function() {
  51. module.verbose('Initializing module for', element);
  52. module.set.defaultSide();
  53. module.instantiate();
  54. },
  55. instantiate: function() {
  56. module.verbose('Storing instance of module', module);
  57. instance = module;
  58. $module
  59. .data(moduleNamespace, instance)
  60. ;
  61. },
  62. destroy: function() {
  63. module.verbose('Destroying previous module for', element);
  64. $module
  65. .removeData(moduleNamespace)
  66. .off(eventNamespace)
  67. ;
  68. },
  69. refresh: function() {
  70. module.verbose('Refreshing selector cache for', element);
  71. $module = $(element);
  72. $sides = $(this).find(selector.shape);
  73. $side = $(this).find(selector.side);
  74. },
  75. repaint: function() {
  76. module.verbose('Forcing repaint event');
  77. var
  78. shape = $sides.get(0) || document.createElement('div'),
  79. fakeAssignment = shape.offsetWidth
  80. ;
  81. },
  82. animate: function(propertyObject, callback) {
  83. module.verbose('Animating box with properties', propertyObject);
  84. callback = callback || function(event) {
  85. module.verbose('Executing animation callback');
  86. if(event !== undefined) {
  87. event.stopPropagation();
  88. }
  89. module.reset();
  90. module.set.active();
  91. };
  92. $.proxy(settings.beforeChange, $nextSide[0])();
  93. if(module.get.transitionEvent()) {
  94. module.verbose('Starting CSS animation');
  95. $module
  96. .addClass(className.animating)
  97. ;
  98. module.repaint();
  99. $module
  100. .addClass(className.animating)
  101. ;
  102. $activeSide
  103. .addClass(className.hidden)
  104. ;
  105. $sides
  106. .css(propertyObject)
  107. .one(module.get.transitionEvent(), callback)
  108. ;
  109. module.set.duration(settings.duration);
  110. }
  111. else {
  112. callback();
  113. }
  114. },
  115. queue: function(method) {
  116. module.debug('Queueing animation of', method);
  117. $sides
  118. .one(module.get.transitionEvent(), function() {
  119. module.debug('Executing queued animation');
  120. setTimeout(function(){
  121. $module.shape(method);
  122. }, 0);
  123. })
  124. ;
  125. },
  126. reset: function() {
  127. module.verbose('Animating states reset');
  128. $module
  129. .removeClass(className.animating)
  130. .attr('style', '')
  131. .removeAttr('style')
  132. ;
  133. // removeAttr style does not consistently work in safari
  134. $sides
  135. .attr('style', '')
  136. .removeAttr('style')
  137. ;
  138. $side
  139. .attr('style', '')
  140. .removeAttr('style')
  141. .removeClass(className.hidden)
  142. ;
  143. $nextSide
  144. .removeClass(className.animating)
  145. .attr('style', '')
  146. .removeAttr('style')
  147. ;
  148. },
  149. is: {
  150. animating: function() {
  151. return $module.hasClass(className.animating);
  152. }
  153. },
  154. set: {
  155. defaultSide: function() {
  156. $activeSide = $module.find('.' + settings.className.active);
  157. $nextSide = ( $activeSide.next(selector.side).size() > 0 )
  158. ? $activeSide.next(selector.side)
  159. : $module.find(selector.side).first()
  160. ;
  161. nextSelector = false;
  162. module.verbose('Active side set to', $activeSide);
  163. module.verbose('Next side set to', $nextSide);
  164. },
  165. duration: function(duration) {
  166. duration = duration || settings.duration;
  167. duration = (typeof duration == 'number')
  168. ? duration + 'ms'
  169. : duration
  170. ;
  171. module.verbose('Setting animation duration', duration);
  172. $sides.add($side)
  173. .css({
  174. '-webkit-transition-duration': duration,
  175. '-moz-transition-duration': duration,
  176. '-ms-transition-duration': duration,
  177. '-o-transition-duration': duration,
  178. 'transition-duration': duration
  179. })
  180. ;
  181. },
  182. stageSize: function() {
  183. var
  184. $clone = $module.clone().addClass(className.loading),
  185. $activeSide = $clone.find('.' + settings.className.active),
  186. $nextSide = (nextSelector)
  187. ? $clone.find(nextSelector)
  188. : ( $activeSide.next(selector.side).size() > 0 )
  189. ? $activeSide.next(selector.side)
  190. : $clone.find(selector.side).first(),
  191. newSize = {}
  192. ;
  193. $activeSide.removeClass(className.active);
  194. $nextSide.addClass(className.active);
  195. $clone.prependTo($body);
  196. newSize = {
  197. width : $nextSide.outerWidth(),
  198. height : $nextSide.outerHeight()
  199. };
  200. $clone.remove();
  201. $module
  202. .css(newSize)
  203. ;
  204. module.verbose('Resizing stage to fit new content', newSize);
  205. },
  206. nextSide: function(selector) {
  207. nextSelector = selector;
  208. $nextSide = $module.find(selector);
  209. if($nextSide.size() === 0) {
  210. module.error(error.side);
  211. }
  212. module.verbose('Next side manually set to', $nextSide);
  213. },
  214. active: function() {
  215. module.verbose('Setting new side to active', $nextSide);
  216. $side
  217. .removeClass(className.active)
  218. ;
  219. $nextSide
  220. .addClass(className.active)
  221. ;
  222. $.proxy(settings.onChange, $nextSide[0])();
  223. module.set.defaultSide();
  224. }
  225. },
  226. flip: {
  227. up: function() {
  228. module.debug('Flipping up', $nextSide);
  229. if( !module.is.animating() ) {
  230. module.set.stageSize();
  231. module.stage.above();
  232. module.animate( module.get.transform.up() );
  233. }
  234. else {
  235. module.queue('flip up');
  236. }
  237. },
  238. down: function() {
  239. module.debug('Flipping down', $nextSide);
  240. if( !module.is.animating() ) {
  241. module.set.stageSize();
  242. module.stage.below();
  243. module.animate( module.get.transform.down() );
  244. }
  245. else {
  246. module.queue('flip down');
  247. }
  248. },
  249. left: function() {
  250. module.debug('Flipping left', $nextSide);
  251. if( !module.is.animating() ) {
  252. module.set.stageSize();
  253. module.stage.left();
  254. module.animate(module.get.transform.left() );
  255. }
  256. else {
  257. module.queue('flip left');
  258. }
  259. },
  260. right: function() {
  261. module.debug('Flipping right', $nextSide);
  262. if( !module.is.animating() ) {
  263. module.set.stageSize();
  264. module.stage.right();
  265. module.animate(module.get.transform.right() );
  266. }
  267. else {
  268. module.queue('flip right');
  269. }
  270. },
  271. over: function() {
  272. module.debug('Flipping over', $nextSide);
  273. if( !module.is.animating() ) {
  274. module.set.stageSize();
  275. module.stage.behind();
  276. module.animate(module.get.transform.over() );
  277. }
  278. else {
  279. module.queue('flip over');
  280. }
  281. },
  282. back: function() {
  283. module.debug('Flipping back', $nextSide);
  284. if( !module.is.animating() ) {
  285. module.set.stageSize();
  286. module.stage.behind();
  287. module.animate(module.get.transform.back() );
  288. }
  289. else {
  290. module.queue('flip back');
  291. }
  292. }
  293. },
  294. get: {
  295. transform: {
  296. up: function() {
  297. var
  298. translate = {
  299. y: -(($activeSide.outerHeight() - $nextSide.outerHeight()) / 2),
  300. z: -($activeSide.outerHeight() / 2)
  301. }
  302. ;
  303. return {
  304. transform: 'translateY(' + translate.y + 'px) translateZ('+ translate.z + 'px) rotateX(-90deg)'
  305. };
  306. },
  307. down: function() {
  308. var
  309. translate = {
  310. y: -(($activeSide.outerHeight() - $nextSide.outerHeight()) / 2),
  311. z: -($activeSide.outerHeight() / 2)
  312. }
  313. ;
  314. return {
  315. transform: 'translateY(' + translate.y + 'px) translateZ('+ translate.z + 'px) rotateX(90deg)'
  316. };
  317. },
  318. left: function() {
  319. var
  320. translate = {
  321. x : -(($activeSide.outerWidth() - $nextSide.outerWidth()) / 2),
  322. z : -($activeSide.outerWidth() / 2)
  323. }
  324. ;
  325. return {
  326. transform: 'translateX(' + translate.x + 'px) translateZ(' + translate.z + 'px) rotateY(90deg)'
  327. };
  328. },
  329. right: function() {
  330. var
  331. translate = {
  332. x : -(($activeSide.outerWidth() - $nextSide.outerWidth()) / 2),
  333. z : -($activeSide.outerWidth() / 2)
  334. }
  335. ;
  336. return {
  337. transform: 'translateX(' + translate.x + 'px) translateZ(' + translate.z + 'px) rotateY(-90deg)'
  338. };
  339. },
  340. over: function() {
  341. var
  342. translate = {
  343. x : -(($activeSide.outerWidth() - $nextSide.outerWidth()) / 2)
  344. }
  345. ;
  346. return {
  347. transform: 'translateX(' + translate.x + 'px) rotateY(180deg)'
  348. };
  349. },
  350. back: function() {
  351. var
  352. translate = {
  353. x : -(($activeSide.outerWidth() - $nextSide.outerWidth()) / 2)
  354. }
  355. ;
  356. return {
  357. transform: 'translateX(' + translate.x + 'px) rotateY(-180deg)'
  358. };
  359. }
  360. },
  361. transitionEvent: function() {
  362. var
  363. element = document.createElement('element'),
  364. transitions = {
  365. 'transition' :'transitionend',
  366. 'OTransition' :'oTransitionEnd',
  367. 'MozTransition' :'transitionend',
  368. 'WebkitTransition' :'webkitTransitionEnd'
  369. },
  370. transition
  371. ;
  372. for(transition in transitions){
  373. if( element.style[transition] !== undefined ){
  374. return transitions[transition];
  375. }
  376. }
  377. },
  378. nextSide: function() {
  379. return ( $activeSide.next(selector.side).size() > 0 )
  380. ? $activeSide.next(selector.side)
  381. : $module.find(selector.side).first()
  382. ;
  383. }
  384. },
  385. stage: {
  386. above: function() {
  387. var
  388. box = {
  389. origin : (($activeSide.outerHeight() - $nextSide.outerHeight()) / 2),
  390. depth : {
  391. active : ($nextSide.outerHeight() / 2),
  392. next : ($activeSide.outerHeight() / 2)
  393. }
  394. }
  395. ;
  396. module.verbose('Setting the initial animation position as above', $nextSide, box);
  397. $activeSide
  398. .css({
  399. 'transform' : 'rotateY(0deg) translateZ(' + box.depth.active + 'px)'
  400. })
  401. ;
  402. $nextSide
  403. .addClass(className.animating)
  404. .css({
  405. 'display' : 'block',
  406. 'top' : box.origin + 'px',
  407. 'transform' : 'rotateX(90deg) translateZ(' + box.depth.next + 'px)'
  408. })
  409. ;
  410. },
  411. below: function() {
  412. var
  413. box = {
  414. origin : (($activeSide.outerHeight() - $nextSide.outerHeight()) / 2),
  415. depth : {
  416. active : ($nextSide.outerHeight() / 2),
  417. next : ($activeSide.outerHeight() / 2)
  418. }
  419. }
  420. ;
  421. module.verbose('Setting the initial animation position as below', $nextSide, box);
  422. $activeSide
  423. .css({
  424. 'transform' : 'rotateY(0deg) translateZ(' + box.depth.active + 'px)'
  425. })
  426. ;
  427. $nextSide
  428. .addClass(className.animating)
  429. .css({
  430. 'display' : 'block',
  431. 'top' : box.origin + 'px',
  432. 'transform' : 'rotateX(-90deg) translateZ(' + box.depth.next + 'px)'
  433. })
  434. ;
  435. },
  436. left: function() {
  437. var
  438. box = {
  439. origin : ( ( $activeSide.outerWidth() - $nextSide.outerWidth() ) / 2),
  440. depth : {
  441. active : ($nextSide.outerWidth() / 2),
  442. next : ($activeSide.outerWidth() / 2)
  443. }
  444. }
  445. ;
  446. module.verbose('Setting the initial animation position as left', $nextSide, box);
  447. $activeSide
  448. .css({
  449. 'transform' : 'rotateY(0deg) translateZ(' + box.depth.active + 'px)'
  450. })
  451. ;
  452. $nextSide
  453. .addClass(className.animating)
  454. .css({
  455. 'display' : 'block',
  456. 'left' : box.origin + 'px',
  457. 'transform' : 'rotateY(-90deg) translateZ(' + box.depth.next + 'px)'
  458. })
  459. ;
  460. },
  461. right: function() {
  462. var
  463. box = {
  464. origin : ( ( $activeSide.outerWidth() - $nextSide.outerWidth() ) / 2),
  465. depth : {
  466. active : ($nextSide.outerWidth() / 2),
  467. next : ($activeSide.outerWidth() / 2)
  468. }
  469. }
  470. ;
  471. module.verbose('Setting the initial animation position as left', $nextSide, box);
  472. $activeSide
  473. .css({
  474. 'transform' : 'rotateY(0deg) translateZ(' + box.depth.active + 'px)'
  475. })
  476. ;
  477. $nextSide
  478. .addClass(className.animating)
  479. .css({
  480. 'display' : 'block',
  481. 'left' : box.origin + 'px',
  482. 'transform' : 'rotateY(90deg) translateZ(' + box.depth.next + 'px)'
  483. })
  484. ;
  485. },
  486. behind: function() {
  487. var
  488. box = {
  489. origin : ( ( $activeSide.outerWidth() - $nextSide.outerWidth() ) / 2),
  490. depth : {
  491. active : ($nextSide.outerWidth() / 2),
  492. next : ($activeSide.outerWidth() / 2)
  493. }
  494. }
  495. ;
  496. module.verbose('Setting the initial animation position as behind', $nextSide, box);
  497. $activeSide
  498. .css({
  499. 'transform' : 'rotateY(0deg)'
  500. })
  501. ;
  502. $nextSide
  503. .addClass(className.animating)
  504. .css({
  505. 'display' : 'block',
  506. 'left' : box.origin + 'px',
  507. 'transform' : 'rotateY(-180deg)'
  508. })
  509. ;
  510. }
  511. },
  512. setting: function(name, value) {
  513. module.debug('Changing setting', name, value);
  514. if( $.isPlainObject(name) ) {
  515. $.extend(true, settings, name);
  516. }
  517. else if(value !== undefined) {
  518. settings[name] = value;
  519. }
  520. else {
  521. return settings[name];
  522. }
  523. },
  524. internal: function(name, value) {
  525. if( $.isPlainObject(name) ) {
  526. $.extend(true, module, name);
  527. }
  528. else if(value !== undefined) {
  529. module[name] = value;
  530. }
  531. else {
  532. return module[name];
  533. }
  534. },
  535. debug: function() {
  536. if(settings.debug) {
  537. if(settings.performance) {
  538. module.performance.log(arguments);
  539. }
  540. else {
  541. module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
  542. module.debug.apply(console, arguments);
  543. }
  544. }
  545. },
  546. verbose: function() {
  547. if(settings.verbose && settings.debug) {
  548. if(settings.performance) {
  549. module.performance.log(arguments);
  550. }
  551. else {
  552. module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
  553. module.verbose.apply(console, arguments);
  554. }
  555. }
  556. },
  557. error: function() {
  558. module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
  559. module.error.apply(console, arguments);
  560. },
  561. performance: {
  562. log: function(message) {
  563. var
  564. currentTime,
  565. executionTime,
  566. previousTime
  567. ;
  568. if(settings.performance) {
  569. currentTime = new Date().getTime();
  570. previousTime = time || currentTime;
  571. executionTime = currentTime - previousTime;
  572. time = currentTime;
  573. performance.push({
  574. 'Element' : element,
  575. 'Name' : message[0],
  576. 'Arguments' : [].slice.call(message, 1) || '',
  577. 'Execution Time' : executionTime
  578. });
  579. }
  580. clearTimeout(module.performance.timer);
  581. module.performance.timer = setTimeout(module.performance.display, 100);
  582. },
  583. display: function() {
  584. var
  585. title = settings.name + ':',
  586. totalTime = 0
  587. ;
  588. time = false;
  589. clearTimeout(module.performance.timer);
  590. $.each(performance, function(index, data) {
  591. totalTime += data['Execution Time'];
  592. });
  593. title += ' ' + totalTime + 'ms';
  594. if(moduleSelector) {
  595. title += ' \'' + moduleSelector + '\'';
  596. }
  597. if($allModules.size() > 1) {
  598. title += ' ' + '(' + $allModules.size() + ')';
  599. }
  600. if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
  601. console.groupCollapsed(title);
  602. if(console.table) {
  603. console.table(performance);
  604. }
  605. else {
  606. $.each(performance, function(index, data) {
  607. console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
  608. });
  609. }
  610. console.groupEnd();
  611. }
  612. performance = [];
  613. }
  614. },
  615. invoke: function(query, passedArguments, context) {
  616. var
  617. object = instance,
  618. maxDepth,
  619. found,
  620. response
  621. ;
  622. passedArguments = passedArguments || queryArguments;
  623. context = element || context;
  624. if(typeof query == 'string' && object !== undefined) {
  625. query = query.split(/[\. ]/);
  626. maxDepth = query.length - 1;
  627. $.each(query, function(depth, value) {
  628. var camelCaseValue = (depth != maxDepth)
  629. ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
  630. : query
  631. ;
  632. if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) {
  633. object = object[camelCaseValue];
  634. }
  635. else if( object[camelCaseValue] !== undefined ) {
  636. found = object[camelCaseValue];
  637. return false;
  638. }
  639. else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) {
  640. object = object[value];
  641. }
  642. else if( object[value] !== undefined ) {
  643. found = object[value];
  644. return false;
  645. }
  646. else {
  647. return false;
  648. }
  649. });
  650. }
  651. if ( $.isFunction( found ) ) {
  652. response = found.apply(context, passedArguments);
  653. }
  654. else if(found !== undefined) {
  655. response = found;
  656. }
  657. if($.isArray(returnedValue)) {
  658. returnedValue.push(response);
  659. }
  660. else if(returnedValue !== undefined) {
  661. returnedValue = [returnedValue, response];
  662. }
  663. else if(response !== undefined) {
  664. returnedValue = response;
  665. }
  666. return found;
  667. }
  668. };
  669. if(methodInvoked) {
  670. if(instance === undefined) {
  671. module.initialize();
  672. }
  673. module.invoke(query);
  674. }
  675. else {
  676. if(instance !== undefined) {
  677. module.destroy();
  678. }
  679. module.initialize();
  680. }
  681. })
  682. ;
  683. return (returnedValue !== undefined)
  684. ? returnedValue
  685. : this
  686. ;
  687. };
  688. $.fn.shape.settings = {
  689. // module info
  690. name : 'Shape',
  691. // debug content outputted to console
  692. debug : false,
  693. // verbose debug output
  694. verbose : true,
  695. // performance data output
  696. performance: true,
  697. // event namespace
  698. namespace : 'shape',
  699. // callback occurs on side change
  700. beforeChange : function() {},
  701. onChange : function() {},
  702. // animation duration
  703. duration : 700,
  704. // possible errors
  705. error: {
  706. side : 'You tried to switch to a side that does not exist.',
  707. method : 'The method you called is not defined'
  708. },
  709. // classnames used
  710. className : {
  711. animating : 'animating',
  712. hidden : 'hidden',
  713. loading : 'loading',
  714. active : 'active'
  715. },
  716. // selectors used
  717. selector : {
  718. sides : '.sides',
  719. side : '.side'
  720. }
  721. };
  722. })( jQuery, window , document );