tab.js 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. // Generated by CoffeeScript 2.0.0-beta4
  2. (function() {
  3. // ------------------------------------------------------------------------
  4. // 變數與常數設置
  5. // ------------------------------------------------------------------------
  6. // 模組名稱。
  7. var Attribute, ClassName, EVENT_NAMESPACE, Error, Event, MODULE_NAMESPACE, Metadata, NAME, Selector, Settings;
  8. NAME = 'tab';
  9. // 模組事件鍵名。
  10. EVENT_NAMESPACE = `.${NAME}`;
  11. // 模組命名空間。
  12. MODULE_NAMESPACE = `module-${NAME}`;
  13. // 模組設定。
  14. Settings = {
  15. // 消音所有提示,甚至是錯誤訊息。
  16. silent: false,
  17. // 顯示除錯訊息。
  18. debug: true,
  19. // 監聽 DOM 結構異動並自動重整快取。
  20. observeChanges: true,
  21. // 當分頁第一次開啟時所會呼叫的回呼函式。
  22. onFirstLoad: (tabName) => {},
  23. // 當分頁被開啟時所會呼叫的回呼函式。
  24. onLoad: (tabName) => {},
  25. // 是否要紀錄分頁籤的開關歷程至瀏覽器的上下頁歷程中。
  26. history: true
  27. };
  28. // 中繼資料名稱。
  29. Metadata = {
  30. LOADED: 'loaded'
  31. };
  32. // 事件名稱。
  33. Event = {
  34. FIRSTLOAD: `firstload${EVENT_NAMESPACE}`,
  35. LOAD: `load${EVENT_NAMESPACE}`,
  36. CLICK: `click${EVENT_NAMESPACE}`,
  37. HASHCHANGE: `hashchange${EVENT_NAMESPACE}`,
  38. POPSTATE: `popstate${EVENT_NAMESPACE}`
  39. };
  40. // 標籤名稱。
  41. Attribute = {
  42. GROUP: 'data-tab-group',
  43. TAB: 'data-tab'
  44. };
  45. // 樣式名稱。
  46. ClassName = {
  47. ACTIVE: 'active',
  48. TAB: 'tab'
  49. };
  50. // 選擇器名稱。
  51. Selector = {
  52. TAB: (name) => {
  53. return `.tab[${Attribute.TAB}='${name}']`;
  54. },
  55. ANY_TAB: '.tab[data-tab]',
  56. ACTIVE_TAB: '.active.tab[data-tab]',
  57. HIDDEN_TAB: '.tab[data-tab]:not(.active)',
  58. MENU: '.menu',
  59. MENU_ITEM: (name) => {
  60. return `.menu .item[${Attribute.TAB}='${name}']`;
  61. },
  62. ITEM: '.item'
  63. };
  64. // 錯誤訊息。
  65. Error = {};
  66. // ------------------------------------------------------------------------
  67. // 模組註冊
  68. // ------------------------------------------------------------------------
  69. ts.register({NAME, MODULE_NAMESPACE, Error, Settings}, ({$allModules, $this, element, debug, settings, index}) => {
  70. var module, separator;
  71. // ------------------------------------------------------------------------
  72. // 區域變數
  73. // ------------------------------------------------------------------------
  74. separator = ',';
  75. // ------------------------------------------------------------------------
  76. // 模組定義
  77. // ------------------------------------------------------------------------
  78. return module = {
  79. change: {
  80. tab: (name, recursive = true, update = true) => {
  81. var $item, $tab, i, len, path, paths;
  82. name = module.decode(name);
  83. paths = recursive ? ts(Selector.TAB(name)).tab('get paths') : [name];
  84. for (i = 0, len = paths.length; i < len; i++) {
  85. path = paths[i];
  86. $item = ts(Selector.MENU_ITEM(path));
  87. $tab = ts(Selector.TAB(path));
  88. $tab.tab('set active');
  89. $item.tab('set active').tab('hide others');
  90. if ($item.tab('not loaded')) {
  91. $item.trigger(Event.FIRSTLOAD, $tab.get(), path).tab('set loaded', true);
  92. }
  93. $item.trigger(Event.LOAD, $tab.get(), path);
  94. }
  95. if (update) {
  96. module.update.hash();
  97. }
  98. return $allModules;
  99. }
  100. },
  101. hide: {
  102. others: () => {
  103. var $items;
  104. $items = module.get.relative.$items();
  105. $items.tab('set hidden');
  106. return $items.each(function() {
  107. return ts(this).tab('get $tab').tab('set hidden');
  108. });
  109. }
  110. },
  111. get: {
  112. name: () => {
  113. return $this.attr(Attribute.TAB);
  114. },
  115. paths: () => {
  116. var $parent, paths;
  117. paths = [];
  118. $parent = $this;
  119. while ($parent.length !== 0) {
  120. paths.push($parent.tab('get name'));
  121. $parent = $parent.tab('get parent $tab');
  122. }
  123. return paths;
  124. },
  125. active: {
  126. $tab: () => {
  127. return ts(Selector.ACTIVE_TAB);
  128. }
  129. },
  130. relative: {
  131. $items: () => {
  132. return $this.closest(Selector.MENU).find(Selector.ITEM).not($this);
  133. }
  134. },
  135. parent: {
  136. $tab: $this.parent().closest(Selector.ANY_TAB)
  137. },
  138. tab: () => {
  139. return module.get.$tab().get();
  140. },
  141. $tab: () => {
  142. return ts(Selector.TAB(module.get.name()));
  143. },
  144. path: () => {
  145. return module.get.paths().join(separator);
  146. },
  147. hash: () => {
  148. var hash;
  149. hash = window.location.hash;
  150. if (hash) {
  151. return module.decode(hash.slice(1));
  152. } else {
  153. return '';
  154. }
  155. }
  156. },
  157. decode: (uri) => {
  158. return decodeURIComponent(uri);
  159. },
  160. has: {
  161. hash: () => {
  162. return !!window.location.hash;
  163. },
  164. parent: {
  165. tab: () => {
  166. return $this.parent().closest(Selector.ANY_TAB).length !== 0;
  167. }
  168. },
  169. hidden: {
  170. parent: () => {
  171. return $this.closest(Selector.HIDDEN_TAB).length !== 0;
  172. }
  173. },
  174. active: {
  175. children: () => {
  176. return $this.find(Selector.ACTIVE_TAB).length !== 0;
  177. }
  178. }
  179. },
  180. set: {
  181. loaded: (bool) => {
  182. return $this.data(Metadata.LOADED, bool);
  183. },
  184. hidden: () => {
  185. return $this.removeClass(ClassName.ACTIVE);
  186. },
  187. active: () => {
  188. return $this.addClass(ClassName.ACTIVE);
  189. }
  190. },
  191. is: {
  192. active: () => {
  193. return $this.hasClass(ClassName.ACTIVE);
  194. },
  195. tab: () => {
  196. return $this.hasClass(ClassName.TAB);
  197. },
  198. loaded: () => {
  199. return $this.data(Metadata.LOADED) === true;
  200. }
  201. },
  202. not: {
  203. loaded: () => {
  204. return !module.is.loaded();
  205. }
  206. },
  207. apply: {
  208. hash: () => {
  209. return setTimeout(function() {
  210. var hash;
  211. if (!module.has.hash()) {
  212. return;
  213. }
  214. hash = module.get.hash();
  215. if (module.same.hash(hash)) {
  216. return;
  217. }
  218. return hash.split(separator).forEach((value) => {
  219. return module.change.tab(value, true, false);
  220. });
  221. }, 0);
  222. }
  223. },
  224. update: {
  225. hash: () => {
  226. var hash;
  227. hash = [];
  228. module.get.active.$tab().each(function() {
  229. var $tab;
  230. $tab = ts(this);
  231. if ($tab.tab('has hidden parent')) {
  232. return;
  233. }
  234. if ($tab.tab('has active children')) {
  235. return;
  236. }
  237. return hash.push($tab.tab('get name'));
  238. });
  239. hash = `#${hash.join(separator)}`;
  240. if (settings.history) {
  241. return history.pushState(null, null, hash);
  242. } else {
  243. return history.replaceState(null, null, hash);
  244. }
  245. }
  246. },
  247. same: {
  248. hash: (hash) => {
  249. var same;
  250. same = true;
  251. hash.split(separator).forEach((value) => {
  252. if (!same) {
  253. return;
  254. }
  255. if (ts(Selector.TAB(value)).tab('has hidden parent')) {
  256. return same = false;
  257. }
  258. });
  259. return same;
  260. }
  261. },
  262. bind: {
  263. events: () => {
  264. $this.on(Event.CLICK, () => {
  265. if (module.is.active()) {
  266. return;
  267. }
  268. return module.change.tab(module.get.name(), false);
  269. });
  270. $this.on(Event.FIRSTLOAD, (event, context, name) => {
  271. debug('發生 FIRSTLOAD 事件', context, name);
  272. return settings.onFirstLoad.call(context, event, name);
  273. });
  274. $this.on(Event.LOAD, (event, context, name) => {
  275. debug('發生 LOAD 事件', context, name);
  276. return settings.onLoad.call(context, event, name);
  277. });
  278. $this.attr('href', 'javascript:void(0)');
  279. if (!settings.history) {
  280. return;
  281. }
  282. return ts(window).off(Event.POPSTATE).on(Event.POPSTATE, () => {
  283. debug('發生 POPSTATE 事件', window);
  284. return module.apply.hash();
  285. });
  286. }
  287. },
  288. // ------------------------------------------------------------------------
  289. // 基礎方法
  290. // ------------------------------------------------------------------------
  291. initialize: () => {
  292. debug('初始化分頁籤', element);
  293. if (module.is.tab()) {
  294. return;
  295. }
  296. module.bind.events();
  297. return module.apply.hash();
  298. },
  299. instantiate: () => {
  300. return debug('實例化分頁籤', element);
  301. },
  302. refresh: () => {
  303. return $allModules;
  304. },
  305. destroy: () => {
  306. debug('摧毀分頁籤', element);
  307. $this.removeData(MODULE_NAMESPACE).off(EVENT_NAMESPACE);
  308. return $allModules;
  309. }
  310. };
  311. });
  312. }).call(this);