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.

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