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.

786 lines
24 KiB

11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
  1. /*
  2. * # Semantic - Shape
  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.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. if( $.isPlainObject(name) ) {
  514. $.extend(true, settings, name);
  515. }
  516. else if(value !== undefined) {
  517. settings[name] = value;
  518. }
  519. else {
  520. return settings[name];
  521. }
  522. },
  523. internal: function(name, value) {
  524. if( $.isPlainObject(name) ) {
  525. $.extend(true, module, name);
  526. }
  527. else if(value !== undefined) {
  528. module[name] = value;
  529. }
  530. else {
  531. return module[name];
  532. }
  533. },
  534. debug: function() {
  535. if(settings.debug) {
  536. if(settings.performance) {
  537. module.performance.log(arguments);
  538. }
  539. else {
  540. module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
  541. module.debug.apply(console, arguments);
  542. }
  543. }
  544. },
  545. verbose: function() {
  546. if(settings.verbose && settings.debug) {
  547. if(settings.performance) {
  548. module.performance.log(arguments);
  549. }
  550. else {
  551. module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
  552. module.verbose.apply(console, arguments);
  553. }
  554. }
  555. },
  556. error: function() {
  557. module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
  558. module.error.apply(console, arguments);
  559. },
  560. performance: {
  561. log: function(message) {
  562. var
  563. currentTime,
  564. executionTime,
  565. previousTime
  566. ;
  567. if(settings.performance) {
  568. currentTime = new Date().getTime();
  569. previousTime = time || currentTime;
  570. executionTime = currentTime - previousTime;
  571. time = currentTime;
  572. performance.push({
  573. 'Element' : element,
  574. 'Name' : message[0],
  575. 'Arguments' : [].slice.call(message, 1) || '',
  576. 'Execution Time' : executionTime
  577. });
  578. }
  579. clearTimeout(module.performance.timer);
  580. module.performance.timer = setTimeout(module.performance.display, 100);
  581. },
  582. display: function() {
  583. var
  584. title = settings.name + ':',
  585. totalTime = 0
  586. ;
  587. time = false;
  588. clearTimeout(module.performance.timer);
  589. $.each(performance, function(index, data) {
  590. totalTime += data['Execution Time'];
  591. });
  592. title += ' ' + totalTime + 'ms';
  593. if(moduleSelector) {
  594. title += ' \'' + moduleSelector + '\'';
  595. }
  596. if($allModules.size() > 1) {
  597. title += ' ' + '(' + $allModules.size() + ')';
  598. }
  599. if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
  600. console.groupCollapsed(title);
  601. if(console.table) {
  602. console.table(performance);
  603. }
  604. else {
  605. $.each(performance, function(index, data) {
  606. console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
  607. });
  608. }
  609. console.groupEnd();
  610. }
  611. performance = [];
  612. }
  613. },
  614. invoke: function(query, passedArguments, context) {
  615. var
  616. object = instance,
  617. maxDepth,
  618. found,
  619. response
  620. ;
  621. passedArguments = passedArguments || queryArguments;
  622. context = element || context;
  623. if(typeof query == 'string' && object !== undefined) {
  624. query = query.split(/[\. ]/);
  625. maxDepth = query.length - 1;
  626. $.each(query, function(depth, value) {
  627. var camelCaseValue = (depth != maxDepth)
  628. ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
  629. : query
  630. ;
  631. if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) {
  632. object = object[camelCaseValue];
  633. }
  634. else if( object[camelCaseValue] !== undefined ) {
  635. found = object[camelCaseValue];
  636. return false;
  637. }
  638. else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) {
  639. object = object[value];
  640. }
  641. else if( object[value] !== undefined ) {
  642. found = object[value];
  643. return false;
  644. }
  645. else {
  646. return false;
  647. }
  648. });
  649. }
  650. if ( $.isFunction( found ) ) {
  651. response = found.apply(context, passedArguments);
  652. }
  653. else if(found !== undefined) {
  654. response = found;
  655. }
  656. if($.isArray(returnedValue)) {
  657. returnedValue.push(response);
  658. }
  659. else if(returnedValue !== undefined) {
  660. returnedValue = [returnedValue, response];
  661. }
  662. else if(response !== undefined) {
  663. returnedValue = response;
  664. }
  665. return found;
  666. }
  667. };
  668. if(methodInvoked) {
  669. if(instance === undefined) {
  670. module.initialize();
  671. }
  672. module.invoke(query);
  673. }
  674. else {
  675. if(instance !== undefined) {
  676. module.destroy();
  677. }
  678. module.initialize();
  679. }
  680. })
  681. ;
  682. return (returnedValue !== undefined)
  683. ? returnedValue
  684. : this
  685. ;
  686. };
  687. $.fn.shape.settings = {
  688. // module info
  689. name : 'Shape',
  690. // debug content outputted to console
  691. debug : false,
  692. // verbose debug output
  693. verbose : true,
  694. // performance data output
  695. performance: true,
  696. // event namespace
  697. namespace : 'shape',
  698. // callback occurs on side change
  699. beforeChange : function() {},
  700. onChange : function() {},
  701. // animation duration
  702. duration : 700,
  703. // possible errors
  704. error: {
  705. side : 'You tried to switch to a side that does not exist.',
  706. method : 'The method you called is not defined'
  707. },
  708. // classnames used
  709. className : {
  710. animating : 'animating',
  711. hidden : 'hidden',
  712. loading : 'loading',
  713. active : 'active'
  714. },
  715. // selectors used
  716. selector : {
  717. sides : '.sides',
  718. side : '.side'
  719. }
  720. };
  721. })( jQuery, window , document );