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
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. maxDepth,
  617. found,
  618. response
  619. ;
  620. passedArguments = passedArguments || queryArguments;
  621. context = element || context;
  622. if(typeof query == 'string' && instance !== undefined) {
  623. query = query.split(/[\. ]/);
  624. maxDepth = query.length - 1;
  625. $.each(query, function(depth, value) {
  626. var camelCaseValue = (depth != maxDepth)
  627. ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
  628. : query
  629. ;
  630. if( $.isPlainObject( instance[camelCaseValue] ) && (depth != maxDepth) ) {
  631. instance = instance[camelCaseValue];
  632. }
  633. else if( instance[camelCaseValue] !== undefined ) {
  634. found = instance[camelCaseValue];
  635. return false;
  636. }
  637. else if( $.isPlainObject( instance[value] ) && (depth != maxDepth) ) {
  638. instance = instance[value];
  639. }
  640. else if( instance[value] !== undefined ) {
  641. found = instance[value];
  642. return false;
  643. }
  644. else {
  645. module.error(error.method, query);
  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 : true,
  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 );