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.

777 lines
23 KiB

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