dropdown.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  1. // Generated by CoffeeScript 2.0.0-beta4
  2. (function() {
  3. // ------------------------------------------------------------------------
  4. // 變數與常數設置
  5. // ------------------------------------------------------------------------
  6. // 模組名稱。
  7. var Attribute, ClassName, EVENT_NAMESPACE, Error, Event, Key, MODULE_NAMESPACE, NAME, Selector, Settings, ZIndex;
  8. NAME = 'dropdown';
  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. onChange: (value, text, element) => {},
  23. // 當多選選單多選了一個值所會呼叫的回呼函式。
  24. onAdd: (addedValue, addedText, addedElement) => {},
  25. // 當多選選單有一個值被移除時所會呼叫的回呼函式。
  26. onRemove: (removedValue, removedText, removedElement) => {},
  27. // 當使用者在多選選單自創了一個新的值所會呼叫的回呼函式。
  28. onLabelCreate: function(value, text) {
  29. return this;
  30. },
  31. // 當上述函式回傳下列物件時就修改元素。
  32. // {
  33. // image : ''
  34. // icon : ''
  35. // emphasis: ''
  36. // class : ''
  37. // element : @
  38. // }
  39. // 當使用者再多選選單移除了一個值所會呼叫的回呼函式。
  40. onLabelRemove: (value, text) => {},
  41. // 當使用者選取或按壓多選選單其中一個標籤時所會呼叫的回呼函式。
  42. onLabelSelect: (value, text, element) => {},
  43. // 當使用者在選單中輸入文字時所呼叫的回呼函式。
  44. onInput: () => {},
  45. // 當沒有相符搜尋內容所會呼叫的回呼函式。
  46. onNoResults: () => {},
  47. // 當選單展開時所呼叫的回呼函式,回傳 `false` 會避免選單顯示。
  48. onShow: () => {
  49. return true;
  50. },
  51. // 當選單隱藏時所呼叫的回呼函式,回傳 `false` 會避免選單顯示。
  52. onHide: () => {
  53. return true;
  54. },
  55. // 當使用者按下選單中其中一個選項時所呼叫的回呼函式。
  56. onSelect: (value, element) => {},
  57. // 用以初始化選單內容的選項,當這個選項是 `false` 而不是陣列的時候會從 HTML 架構初始化。
  58. values: false,
  59. // 是否允許重新選取,當設為 `true` 時,就算使用者選取了正在選取的值,仍會呼叫 `onChange`。
  60. allowReselection: false,
  61. // 是否允許使用者擅自新增選單值。
  62. allowAdditions: false,
  63. // 當使用者新增了值並移除後,是否要在選單中隱藏這個值。
  64. hideAdditions: true,
  65. // 搜尋的底限字數,超過此字數才會開始搜尋。
  66. minCharacters: 1,
  67. // 搜尋時的依據,可用:`both` 符合文字或值、`value` 符合值、`text` 符合文字。
  68. match: 'both',
  69. // 是否要進行全文搜尋,若為 `true` 只要搜尋的值符合選項文字其中即可;`false` 則會強迫搜尋的值必須和選項文字開頭相符。
  70. fullTextSearch: false,
  71. // 是否要使用標籤而非純計數文字。
  72. useLabels: true,
  73. // 多選選單是否使用標籤?若設置為 `false` 會以「已選擇 x 個」純文字替代標籤。
  74. useLabels: true,
  75. // 多選選單最多可以選擇幾個項目?設置為 `0` 表示無限。
  76. maxSelections: 0
  77. };
  78. // 事件名稱。
  79. Event = {
  80. CHANGE: `change${EVENT_NAMESPACE}`,
  81. ADD: `add${EVENT_NAMESPACE}`,
  82. REMOVE: `remove${EVENT_NAMESPACE}`,
  83. LABEL_CREATE: `labelcreate${EVENT_NAMESPACE}`,
  84. LABEL_REMOVE: `labelremove${EVENT_NAMESPACE}`,
  85. LABEL_SELECT: `labelselect${EVENT_NAMESPACE}`,
  86. INPUT: `input${EVENT_NAMESPACE}`,
  87. NO_RESULT: `noresult${EVENT_NAMESPACE}`,
  88. SHOW: `show${EVENT_NAMESPACE}`,
  89. HIDE: `hide${EVENT_NAMESPACE}`,
  90. SELECT: `select${EVENT_NAMESPACE}`,
  91. CLICK: `click${EVENT_NAMESPACE}`,
  92. ANIMATIONEND: `animationend${EVENT_NAMESPACE}`
  93. };
  94. // 樣式名稱。
  95. ClassName = {
  96. VISIBLE: 'visible',
  97. HIDDEN: 'hidden',
  98. ANIMATING: 'animating',
  99. DROPDOWN: 'dropdown',
  100. TEXT: 'text',
  101. ICON: 'icon',
  102. IMAGE: 'image',
  103. ITEM: 'item',
  104. MENU: 'menu',
  105. UPWARD: 'upward',
  106. DOWNWARD: 'downward',
  107. LEFTWARD: 'leftward',
  108. RIGHTWARD: 'rightward',
  109. SELECTION: 'selection',
  110. MULTIPLE: 'multiple',
  111. LABELS: 'labels',
  112. CLOSE: 'close',
  113. DEFAULT_LABEL: 'ts compact label',
  114. DEFAULT_BUTTON: 'ts tiny close button',
  115. FILTERED: 'filtered',
  116. PLACEHOLDER: 'placeholder',
  117. SELECTED: 'selected',
  118. ACTIVE: 'active',
  119. ADDITION_ITEM: 'addition item'
  120. };
  121. // 選擇器名稱。
  122. Selector = {
  123. OPTION: 'option',
  124. DROPDOWN: '.ts.dropdown',
  125. VISIBLE_DROPDOWN: '.ts.visible.dropdown',
  126. DEFAULT_BUTTON: '.ts.tiny.close.button',
  127. LABELS: '.labels',
  128. SELF_LABELS: ':scope > .labels',
  129. SEARCH: 'input.search',
  130. ITEM: '.item:not(.addition)',
  131. ACTIVE_ITEM: '.active.item',
  132. UNSELECTED_ITEM: '.item:not(.selected):not(.addition)',
  133. ACTIVE_ITEM_UNFILTERED: '.active.item:not(.filtered):not(.addition)',
  134. ITEM_NOT_FILTERED: '.item:not(.filtered):not(.addition)',
  135. FILTERED: '.filtered',
  136. TEXT: '.text',
  137. LABEL: '.labels .label',
  138. MESSAGE: '.message',
  139. MENU: '.menu',
  140. ADDITION_ITEM: '.addition.item',
  141. SELECT: 'select',
  142. SELECTED_ITEM: '.item.selected',
  143. BODY: 'body',
  144. SELECTABLE_OPTION: 'select option:not([selected]):not([value=""])',
  145. SPECIFIED_OPTION: (v) => {
  146. return `select option[value='${v}']`;
  147. },
  148. SPECIFIED_ITEM: (v) => {
  149. return `.menu .item[data-value='${v}']`;
  150. },
  151. SPECIFIED_LABEL: (v) => {
  152. return `.labels .label[data-value='${v}']`;
  153. }
  154. };
  155. // 鍵盤按鍵代號。
  156. Key = {
  157. ENTER: 13,
  158. BACKSPACE: 8,
  159. UP: 38,
  160. DOWN: 40,
  161. LEFT: 37,
  162. RIGHT: 39
  163. };
  164. // Z 索引
  165. ZIndex = {
  166. MENU: 9,
  167. ACTIVE: 10,
  168. HOVERED: 11
  169. };
  170. // 元素資訊標籤。
  171. Attribute = {
  172. VALUE: 'data-value'
  173. };
  174. // 錯誤訊息。
  175. Error = {};
  176. // ------------------------------------------------------------------------
  177. // 模組註冊
  178. // ------------------------------------------------------------------------
  179. ts.register({NAME, MODULE_NAMESPACE, Error, Settings}, ({$allModules, $this, element, debug, settings}) => {
  180. var $body, duration, id, module;
  181. // ------------------------------------------------------------------------
  182. // 區域變數
  183. // ------------------------------------------------------------------------
  184. $body = ts(Selector.BODY);
  185. id = $this.uniqueID();
  186. duration = 300;
  187. // ------------------------------------------------------------------------
  188. // 模組定義
  189. // ------------------------------------------------------------------------
  190. return module = {
  191. setup: {
  192. menu: (values) => {}
  193. },
  194. change: {
  195. values: (values) => {}
  196. },
  197. refresh: () => {},
  198. toggle: () => {
  199. if (module.is.visible()) {
  200. return module.hide();
  201. } else {
  202. return module.show();
  203. }
  204. },
  205. animate: {
  206. expand: (callback) => {
  207. module.set.zIndex(ZIndex.ACTIVE);
  208. return $this.removeClass(ClassName.HIDDEN).addClass(`${ClassName.VISIBLE} ${ClassName.ANIMATING}`).off(Event.ANIMATIONEND).one(Event.ANIMATIONEND, () => {
  209. if (callback != null) {
  210. return callback.call();
  211. }
  212. }).emulate(Event.ANIMATIONEND, duration);
  213. },
  214. contract: (callback) => {
  215. module.set.zIndex(ZIndex.MENU);
  216. return $this.removeClass(ClassName.VISIBLE).addClass(`${ClassName.HIDDEN} ${ClassName.ANIMATING}`).off(Event.ANIMATIONEND).one(Event.ANIMATIONEND, () => {
  217. if (callback != null) {
  218. return callback.call();
  219. }
  220. }).emulate(Event.ANIMATIONEND, duration);
  221. }
  222. },
  223. show: () => {
  224. if (module.is.visible()) {
  225. return;
  226. }
  227. if (module.trigger.show() === false) {
  228. return;
  229. }
  230. module.set.direction();
  231. return module.animate.expand(() => {
  232. if (module.is.visible()) {
  233. return $this.removeClass(ClassName.ANIMATING);
  234. }
  235. });
  236. },
  237. hide: () => {
  238. if (module.is.hidden()) {
  239. return;
  240. }
  241. if (module.trigger.hide() === false) {
  242. return;
  243. }
  244. module.set.direction();
  245. return module.animate.contract(() => {
  246. if (!module.is.hidden()) {
  247. return;
  248. }
  249. return $this.removeClass(`${ClassName.ANIMATING} ${ClassName.HIDDEN} ${ClassName.UPWARD} ${ClassName.DOWNWARD} ${ClassName.LEFTWARD} ${ClassName.RIGHTWARD}`);
  250. });
  251. },
  252. clear: () => {},
  253. hideOthers: () => {},
  254. restore: {
  255. defaults: () => {},
  256. default: {
  257. text: () => {},
  258. value: () => {}
  259. },
  260. placeholder: {
  261. text: () => {}
  262. }
  263. },
  264. save: {
  265. defaults: () => {}
  266. },
  267. set: {
  268. selected: (value) => {},
  269. exactly: (value) => {},
  270. text: (text) => {},
  271. value: (value) => {},
  272. active: () => {},
  273. visible: () => {},
  274. zIndex: (z) => {
  275. return $this.css('z-index', z);
  276. },
  277. direction: () => {
  278. var height, heightHalf, position, width, widthHalf;
  279. position = $this.rect();
  280. width = window.innerWidth;
  281. widthHalf = width / 2;
  282. height = window.innerHeight;
  283. heightHalf = height / 2;
  284. if (position.left < widthHalf && position.top < heightHalf) {
  285. return $this.addClass(`${ClassName.DOWNWARD} ${ClassName.RIGHTWARD}`);
  286. } else if (position.left < widthHalf && position.top > heightHalf) {
  287. return $this.addClass(`${ClassName.UPWARD} ${ClassName.RIGHTWARD}`);
  288. } else if (position.left > widthHalf && position.top > heightHalf) {
  289. return $this.addClass(`${ClassName.UPWARD} ${ClassName.LEFTWARD}`);
  290. } else if (position.left > widthHalf && position.top < heightHalf) {
  291. return $this.addClass(`${ClassName.DOWNWARD} ${ClassName.LEFTWARD}`);
  292. }
  293. }
  294. },
  295. remove: {
  296. selected: (value) => {}
  297. },
  298. get: {
  299. text: () => {},
  300. value: () => {},
  301. item: (value) => {},
  302. default: {
  303. text: () => {}
  304. },
  305. placeholder: {
  306. text: () => {}
  307. }
  308. },
  309. determine: {
  310. intent: () => {},
  311. select: {
  312. action: (text, value) => {}
  313. }
  314. },
  315. remove: {
  316. active: () => {},
  317. visible: () => {}
  318. },
  319. is: {
  320. selection: () => {},
  321. animated: () => {},
  322. visible: () => {
  323. return $this.hasClass(ClassName.VISIBLE);
  324. },
  325. hidden: () => {
  326. return !module.is.visible();
  327. }
  328. },
  329. listener: {
  330. body: (target) => {
  331. var isSelfChild;
  332. // Multiple Tag delete button
  333. // if $target.hasClass('close')
  334. // return
  335. isSelfChild = $this.is(target) || $this.contains(target);
  336. if (!isSelfChild) {
  337. return module.hide();
  338. }
  339. }
  340. },
  341. trigger: {
  342. select: (event) => {
  343. var item, value;
  344. value = ts(event.target).attr(Attribute.VALUE);
  345. item = event.target;
  346. debug('發生 SELECT 事件', value);
  347. return settings.onSelect.call(element, value, item);
  348. },
  349. show: () => {
  350. debug('發生 SHOW 事件', element);
  351. return settings.onShow.call(element);
  352. },
  353. hide: () => {
  354. debug('發生 HIDE 事件', element);
  355. return settings.onHide.call(element);
  356. }
  357. },
  358. bind: {
  359. events: () => {
  360. $body.on(`${Event.CLICK}${id}`, (event) => {
  361. //debug '發生 CLICK 事件', element
  362. return module.listener.body(event.target);
  363. });
  364. $this.on(Event.CLICK, (event) => {
  365. //debug "發生 CLICK 事件", element
  366. return module.toggle();
  367. });
  368. return $this.on(Event.CLICK, Selector.ITEM, (event) => {
  369. //debug "發生 CLICK 事件", element
  370. return module.trigger.select(event);
  371. });
  372. }
  373. },
  374. // $this.on Event.CHANGE, (event, context) =>
  375. // debug "發生 CHANGE 事件", context
  376. // settings.onChange.call context, event
  377. // ------------------------------------------------------------------------
  378. // 基礎方法
  379. // ------------------------------------------------------------------------
  380. initialize: () => {
  381. debug('初始化下拉式選單', element);
  382. return module.bind.events();
  383. },
  384. instantiate: () => {
  385. return debug('實例化下拉式選單', element);
  386. },
  387. refresh: () => {
  388. return $allModules;
  389. },
  390. destroy: () => {
  391. debug('摧毀下拉式選單', element);
  392. $body.off(`${Event.CLICK}${id}`);
  393. $this.removeData(MODULE_NAMESPACE).off(EVENT_NAMESPACE);
  394. return $allModules;
  395. }
  396. };
  397. });
  398. }).call(this);