popup.js 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967
  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, Position, Selector, Settings, duration;
  8. NAME = 'popup';
  9. // 模組事件鍵名。
  10. EVENT_NAMESPACE = `.${NAME}`;
  11. // 模組命名空間。
  12. MODULE_NAMESPACE = `module-${NAME}`;
  13. // 模組設定。
  14. Settings = {
  15. // 消音所有提示,甚至是錯誤訊息。
  16. silent: true,
  17. // 顯示除錯訊息。
  18. debug: true,
  19. // 監聽 DOM 結構異動並自動重整快取。
  20. observeChanges: true,
  21. // 欲使用的彈出式訊息元素選擇器(如果已經有先建立好的話),`false` 的話則是即時產生。
  22. popup: false,
  23. // 同時是否僅能有一個彈出式訊息出現在螢幕上。
  24. exclusive: true,
  25. // 彈出式訊息的邊界元素,彈出式訊息會試圖不要超過這個元素。
  26. boundary: 'body',
  27. // 此彈出式訊息偵測畫面是否有捲動的元素選擇器,如果指定元素有捲動事件則會自動隱藏此彈出式訊息。
  28. scrollContext: window,
  29. // 當彈出式訊息無法符合邊界所會往上找的最大層數,設為 `0` 即表示當不符合邊界時直接失敗。
  30. maxSearchDepth: 10,
  31. // 偏好的彈出式訊息出現位置。
  32. position: 'top',
  33. // 欲觸發彈出式訊息的事件,如:`click`、`hover`、`focus`、`manul`。
  34. on: 'hover',
  35. // 觸發的延遲毫秒數。
  36. delay: {
  37. // 欲顯示彈出式訊息前所需的毫秒數。
  38. show: 50,
  39. // 隱藏彈出式訊息前所等待的毫秒數。
  40. hide: 50
  41. },
  42. // 過場動畫。
  43. transition: 'auto',
  44. // 過場動畫的演繹毫秒時間。
  45. duration: 'auto',
  46. // 游標是否能在彈出式訊息遊走,如:導覽式彈出選單。
  47. hoverable: false,
  48. // 是否能在點擊彈出式訊息以外的地方自動關閉。
  49. closable: true,
  50. // 是否要在指定捲動時自動隱藏此彈出式訊息。
  51. hideOnScroll: 'auto',
  52. // 是否帶有指標外觀。
  53. pointing: true,
  54. // 是否為反色外觀。
  55. inverted: false,
  56. // 目標元素選擇器,彈出式訊息會以這個元素為主。
  57. target: false,
  58. // 欲套用的樣式名稱,以空白分隔。
  59. variation: false,
  60. // 內容純文字。
  61. content: false,
  62. // 標題純文字。
  63. title: false,
  64. // 彈出式訊息的 HTML 內容,如果採用此屬性則會忽略 `content` 與 `title`。
  65. html: false,
  66. // 當彈出式訊息被建立時所會呼叫的回呼函式。
  67. onCreate: (element) => {},
  68. // 當彈出式訊息不再被需要且從頁面上移除時所會呼叫的回呼函式。
  69. onRemove: (element) => {},
  70. // 當彈出式訊息開始顯示時所會呼叫的回呼函式,回傳 `false` 將會停止顯示。
  71. onShow: (element) => {
  72. return true;
  73. },
  74. // 當彈出式訊息動畫結束並顯示在畫面上時所會呼叫的回呼函式。
  75. onVisible: (element) => {},
  76. // 當彈出式訊息開始消失時所會呼叫的回呼函式,回傳 `false` 將會避免消失。
  77. onHide: (element) => {
  78. return true;
  79. },
  80. // 當彈出式訊息已經完全消失時所會呼叫的回呼函式。
  81. onHidden: (element) => {},
  82. // 當彈出式訊息無法在畫面上產生或放置(不合適尺寸)時所會呼叫的回呼函式。
  83. onUnplaceable: (element) => {}
  84. };
  85. // 事件名稱。
  86. Event = {
  87. CREATE: `create${EVENT_NAMESPACE}`,
  88. REMOVE: `remove${EVENT_NAMESPACE}`,
  89. SHOW: `show${EVENT_NAMESPACE}`,
  90. VISIBLE: `visible${EVENT_NAMESPACE}`,
  91. HIDE: `hide${EVENT_NAMESPACE}`,
  92. HIDDEN: `hidden${EVENT_NAMESPACE}`,
  93. UNPLACEABLE: `unplaceable${EVENT_NAMESPACE}`,
  94. CLICK: `click${EVENT_NAMESPACE}`,
  95. FOCUS: `focus${EVENT_NAMESPACE}`,
  96. FOCUSOUT: `focusout${EVENT_NAMESPACE}`,
  97. BLUR: `blur${EVENT_NAMESPACE}`,
  98. SCROLL: `scroll${EVENT_NAMESPACE}`,
  99. MOUSEMOVE: `mousemove${EVENT_NAMESPACE}`,
  100. MOUSEENTER: `mouseenter${EVENT_NAMESPACE}`,
  101. MOUSELEAVE: `mouseleave${EVENT_NAMESPACE}`,
  102. MOUSEOUT: `mouseout${EVENT_NAMESPACE}`,
  103. ANIMATIONEND: 'animationend'
  104. };
  105. // 樣式名稱。
  106. ClassName = {
  107. TOP: 'top',
  108. LEFT: 'left',
  109. RIGHT: 'right',
  110. BOTTOM: 'bottom',
  111. CENTER: 'center',
  112. VISIBLE: 'visible',
  113. ANIMATING: 'animating',
  114. HIDDEN: 'hidden',
  115. CUSTOM: 'custom',
  116. POPUP: 'ts popup',
  117. TITLE: 'title',
  118. CONTENT: 'content',
  119. LOADING: 'loading',
  120. ARROW: 'arrow',
  121. HOVERABLE: 'hoverable',
  122. MEDIUM: 'medium',
  123. INVERTED: 'inverted',
  124. POINTING: 'pointing',
  125. SIZES: 'mini tiny small medium large big huge massive'
  126. };
  127. // 位置。
  128. Position = {
  129. TOP: 'top',
  130. BOTTOM: 'bottom',
  131. LEFT: 'left',
  132. RIGHT: 'right'
  133. };
  134. // 中繼資料。
  135. Metadata = {
  136. SHOW_TIMER: 'showTimer',
  137. HIDE_TIMER: 'hideTimer',
  138. ACTIVATOR: 'activator'
  139. };
  140. // 選擇器名稱。
  141. Selector = {
  142. BODY: 'body',
  143. DIV: '<div>',
  144. ARROW: '.arrow',
  145. VISIBLE: '.visible',
  146. POPUP: '.ts.popup',
  147. HTML: 'html'
  148. };
  149. // 元素標籤。
  150. Attribute = {
  151. CONTENT: 'data-content',
  152. HTML: 'data-html',
  153. TITLE: 'data-title',
  154. VARIATION: 'data-variation',
  155. TRANSITION: 'data-popup-transition',
  156. POSITION: 'data-position',
  157. STYLE: 'style'
  158. };
  159. // 錯誤訊息。
  160. Error = {};
  161. // 過場動畫毫秒。
  162. duration = 200;
  163. // ------------------------------------------------------------------------
  164. // 模組註冊
  165. // ------------------------------------------------------------------------
  166. ts.register({NAME, MODULE_NAMESPACE, Error, Settings}, ({$allModules, $this, element, debug, settings}) => {
  167. var $arrow, $body, $boundary, $popup, $scrollContext, arrowBorderSize, arrowSize, boundary, boundaryRect, module, offset, padding, popupRect, rect, scrollContext;
  168. // ------------------------------------------------------------------------
  169. // 區域變數
  170. // ------------------------------------------------------------------------
  171. $body = ts(Selector.BODY);
  172. $popup = ts();
  173. $boundary = ts();
  174. popupRect = {};
  175. rect = {};
  176. boundaryRect = {};
  177. boundary = null;
  178. $scrollContext = ts();
  179. $arrow = ts();
  180. scrollContext = null;
  181. offset = 20;
  182. padding = 10;
  183. arrowBorderSize = 2;
  184. arrowSize = 8;
  185. // ------------------------------------------------------------------------
  186. // 模組定義
  187. // ------------------------------------------------------------------------
  188. return module = {
  189. show: (callback) => {
  190. module.remove.timers();
  191. //if module.is.animating()
  192. // return
  193. if (module.is.visible()) {
  194. return;
  195. }
  196. module.reposition();
  197. if (module.trigger.show() === false) {
  198. return;
  199. }
  200. module.animate.show(() => {
  201. module.set.animating(false);
  202. if (callback != null) {
  203. callback.call();
  204. }
  205. return module.trigger.visible();
  206. });
  207. return $allModules;
  208. },
  209. hide: (callback) => {
  210. module.remove.timers();
  211. //if module.is.animating()
  212. // return
  213. if (module.is.hidden()) {
  214. return;
  215. }
  216. if (module.trigger.hide() === false) {
  217. return;
  218. }
  219. module.animate.hide(() => {
  220. module.set.animating(false);
  221. if (callback != null) {
  222. callback.call();
  223. }
  224. return module.trigger.hidden();
  225. });
  226. return $allModules;
  227. },
  228. hideAll: () => {
  229. ts(Selector.POPUP).filter(Selector.VISIBLE).each((el) => {
  230. return ts(el).data(Metadata.ACTIVATOR).popup('hide');
  231. });
  232. return $allModules;
  233. },
  234. hideOthers: () => {
  235. ts(Selector.POPUP).filter(Selector.VISIBLE).not($popup.get()).each((el) => {
  236. var $activator;
  237. $activator = ts(el).data(Metadata.ACTIVATOR);
  238. if ($activator !== void 0) {
  239. return $activator.popup('hide');
  240. }
  241. });
  242. return $allModules;
  243. },
  244. animate: {
  245. show: (callback) => {
  246. return $popup.addClass(`${ClassName.VISIBLE} ${ClassName.ANIMATING}`).off(Event.ANIMATIONEND).one(Event.ANIMATIONEND, () => {
  247. if (callback != null) {
  248. return callback.call();
  249. }
  250. }).emulate(Event.ANIMATIONEND, duration);
  251. },
  252. hide: (callback) => {
  253. return $popup.removeClass(ClassName.VISIBLE).addClass(ClassName.ANIMATING).one(Event.ANIMATIONEND, () => {
  254. if (callback != null) {
  255. return callback.call();
  256. }
  257. }).emulate(Event.ANIMATIONEND, duration);
  258. }
  259. },
  260. remove: {
  261. timers: () => {
  262. module.remove.show.timer();
  263. return module.remove.hide.timer();
  264. },
  265. show: {
  266. timer: () => {
  267. return $this.removeTimer(Metadata.SHOW_TIMER);
  268. }
  269. },
  270. hide: {
  271. timer: () => {
  272. return $this.removeTimer(Metadata.HIDE_TIMER);
  273. }
  274. },
  275. popup: () => {
  276. $popup.remove();
  277. return $allModules;
  278. }
  279. },
  280. set: {
  281. loading: (value) => {
  282. if (value) {
  283. $popup.addClass(ClassName.LOADING);
  284. } else {
  285. $popup.removeClass(ClassName.LOADING);
  286. }
  287. return module.reposition();
  288. },
  289. position: (position, modify = false) => {
  290. $popup.attr(Attribute.POSITION, position);
  291. if (modify) {
  292. return settings.position = position;
  293. }
  294. },
  295. transition: (transition) => {
  296. settings.transition = transition;
  297. $popup.attr(Attribute.TRANSITION, transition);
  298. if (settings.duration === 'auto') {
  299. switch (settings.transition) {
  300. case 'fade':
  301. duration = 300;
  302. }
  303. return;
  304. }
  305. duration = settings.duration;
  306. return $popup.css('animation-duration', settings.duration);
  307. },
  308. pointing: (value) => {
  309. settings.pointing = value;
  310. if (value) {
  311. return $popup.addClass(ClassName.POINTING);
  312. } else {
  313. return $popup.removeClass(ClassName.POINTING);
  314. }
  315. },
  316. inverted: (value) => {
  317. settings.inverted = value;
  318. if (value) {
  319. return $popup.addClass(ClassName.INVERTED);
  320. } else {
  321. return $popup.removeClass(ClassName.INVERTED);
  322. }
  323. },
  324. hoverable: (value) => {
  325. settings.hoverable = value;
  326. if (value) {
  327. return $popup.addClass(ClassName.HOVERABLE);
  328. } else {
  329. return $popup.removeClass(ClassName.HOVERABLE);
  330. }
  331. },
  332. boundary: (selector) => {
  333. $boundary = $this.closest(selector);
  334. return boundary = $boundary.get();
  335. },
  336. scrollContext: (selector) => {
  337. $scrollContext = ts(selector);
  338. return scrollContext = $scrollContext.get();
  339. },
  340. coordinate: (x, y) => {
  341. return $popup.css({
  342. top: x,
  343. left: y
  344. });
  345. },
  346. width: (width) => {
  347. return $popup.css({
  348. width: width
  349. });
  350. },
  351. animating: (value) => {
  352. if (value) {
  353. return $popup.addClass(ClassName.ANIMATING);
  354. } else {
  355. return $popup.removeClass(ClassName.ANIMATING);
  356. }
  357. },
  358. variation: (value) => {
  359. return $popup.addClass(value);
  360. },
  361. activator: () => {
  362. return $popup.data(Metadata.ACTIVATOR, $this);
  363. },
  364. show: {
  365. timer: () => {
  366. return $this.setTimer({
  367. name: Metadata.SHOW_TIMER,
  368. callback: module.show,
  369. interval: settings.delay.show,
  370. looping: false,
  371. visible: true
  372. });
  373. }
  374. },
  375. hide: {
  376. timer: () => {
  377. return $this.setTimer({
  378. name: Metadata.HIDE_TIMER,
  379. callback: module.hide,
  380. interval: settings.delay.hide,
  381. looping: false,
  382. visible: true
  383. });
  384. }
  385. }
  386. },
  387. init: {
  388. popup: () => {
  389. var $next;
  390. if (settings.popup) {
  391. $popup = ts(settings.popup);
  392. }
  393. if ($popup.exists()) {
  394. module.create.arrow();
  395. return $popup;
  396. }
  397. $next = $this.next();
  398. if ($next.is(Selector.POPUP)) {
  399. $popup = $next;
  400. } else {
  401. module.create.popup();
  402. }
  403. module.set.activator();
  404. module.create.arrow();
  405. return $popup;
  406. }
  407. },
  408. get: {
  409. distance: () => {
  410. var bRect, offsetLeft, offsetTop;
  411. bRect = boundaryRect;
  412. if ($boundary.is(Selector.BODY)) {
  413. bRect.top = 0;
  414. bRect.left = 0;
  415. bRect.bottom = 0;
  416. bRect.right = 0;
  417. bRect.width = document.body.clientWidth;
  418. bRect.height = document.body.clientHeight;
  419. }
  420. offsetTop = element.offsetTop;
  421. offsetLeft = element.offsetLeft;
  422. if ($this.parents().length !== $popup.parents().length) {
  423. $this.parents(settings.boundary).each((el, i) => {
  424. offsetTop += el.offsetTop;
  425. return offsetLeft += el.offsetLeft;
  426. });
  427. }
  428. return {
  429. top: offsetTop,
  430. left: offsetLeft,
  431. right: boundary.clientWidth - (offsetLeft + rect.width),
  432. bottom: boundary.scrollHeight - (offsetTop + rect.height),
  433. viewport: {
  434. top: rect.top - bRect.top,
  435. left: rect.left - bRect.left,
  436. right: bRect.width - ((rect.left - bRect.left) + rect.width),
  437. bottom: bRect.height - ((rect.top - bRect.top) + rect.height)
  438. }
  439. };
  440. },
  441. position: () => {
  442. return $popup.attr(Attribute.POSITION);
  443. },
  444. popup: () => {
  445. return $popup.get();
  446. }
  447. },
  448. repositionArrow: () => {
  449. return setTimeout(() => {
  450. module.refresh();
  451. switch (module.get.position()) {
  452. case Position.TOP:
  453. return $arrow.css({
  454. left: (rect.left + rect.width / 2) - popupRect.left - arrowSize - arrowBorderSize,
  455. top: popupRect.height - arrowBorderSize
  456. });
  457. case Position.RIGHT:
  458. return $arrow.css({
  459. left: `-${arrowSize * 2}`,
  460. top: (rect.top + rect.height / 2) - popupRect.top - arrowSize - arrowBorderSize
  461. });
  462. case Position.BOTTOM:
  463. return $arrow.css({
  464. left: (rect.left + rect.width / 2) - popupRect.left - arrowSize - arrowBorderSize,
  465. top: `-${arrowSize * 2}`
  466. });
  467. case Position.LEFT:
  468. return $arrow.css({
  469. left: popupRect.width - arrowBorderSize,
  470. top: (rect.top + rect.height / 2) - popupRect.top - arrowSize - arrowBorderSize
  471. });
  472. }
  473. }, 0);
  474. },
  475. calculate: {
  476. direction: (viewport, level, $parent) => {
  477. var bottomOK, direction, isParentHTML, isReachedDepth, leftOK, noParent, parent, parentRect, rightOK, topOK;
  478. topOK = viewport.top > popupRect.height + arrowSize;
  479. rightOK = viewport.right > popupRect.width + arrowSize;
  480. bottomOK = viewport.bottom > popupRect.height + arrowSize;
  481. leftOK = viewport.left > popupRect.width + arrowSize;
  482. direction = null;
  483. switch (settings.position) {
  484. case Position.TOP:
  485. if (topOK) {
  486. return Position.TOP;
  487. }
  488. break;
  489. case Position.RIGHT:
  490. if (rightOK) {
  491. return Position.RIGHT;
  492. }
  493. break;
  494. case Position.BOTTOM:
  495. if (bottomOK) {
  496. return Position.BOTTOM;
  497. }
  498. break;
  499. case Position.LEFT:
  500. if (leftOK) {
  501. return Position.LEFT;
  502. }
  503. }
  504. switch (false) {
  505. case !topOK:
  506. return Position.TOP;
  507. case !bottomOK:
  508. return Position.BOTTOM;
  509. case !rightOK:
  510. return Position.RIGHT;
  511. case !leftOK:
  512. return Position.LEFT;
  513. }
  514. $parent = $parent.parent();
  515. parent = $parent.get();
  516. parentRect = $parent.rect();
  517. isParentHTML = $parent.is(Selector.HTML);
  518. noParent = !$parent.exists();
  519. isReachedDepth = level >= settings.maxSearchDepth;
  520. if (isReachedDepth || noParent || isParentHTML) {
  521. return null;
  522. }
  523. viewport = {
  524. top: rect.top - parentRect.top,
  525. right: parent.clientWidth - rect.left + rect.width,
  526. bottom: parent.clientHeight - rect.top + rect.height,
  527. left: rect.left - parentRect.left
  528. };
  529. if ($parent.is(Selector.BODY)) {
  530. viewport.top = rect.top;
  531. viewport.left = rect.left;
  532. viewport.bottom = parent.clientHeight - rect.bottom;
  533. viewport.right = parent.clientWidth - rect.right;
  534. }
  535. return module.calculate.direction(viewport, level + 1, $parent);
  536. },
  537. popup: {
  538. position: () => {
  539. var direction, distance, position;
  540. module.refresh();
  541. distance = module.get.distance();
  542. direction = module.calculate.direction(distance.viewport, 0, $boundary);
  543. position = '';
  544. console.log(distance);
  545. if (direction === null) {
  546. module.trigger.unplaceable();
  547. return;
  548. }
  549. switch (direction) {
  550. case Position.TOP:
  551. if (!module.is.pointing()) {
  552. $popup.css({
  553. top: distance.top - popupRect.height + arrowSize
  554. });
  555. } else {
  556. $popup.css({
  557. top: distance.top - popupRect.height
  558. });
  559. }
  560. break;
  561. case Position.RIGHT:
  562. if (!module.is.pointing()) {
  563. $popup.css({
  564. left: distance.left + rect.width - arrowSize
  565. });
  566. } else {
  567. $popup.css({
  568. left: distance.left + rect.width
  569. });
  570. }
  571. break;
  572. case Position.BOTTOM:
  573. if (!module.is.pointing()) {
  574. $popup.css({
  575. top: distance.top + rect.height - arrowSize
  576. });
  577. } else {
  578. $popup.css({
  579. top: distance.top + rect.height
  580. });
  581. }
  582. break;
  583. case Position.LEFT:
  584. if (!module.is.pointing()) {
  585. $popup.css({
  586. left: distance.left - popupRect.width + arrowSize
  587. });
  588. } else {
  589. $popup.css({
  590. left: distance.left - popupRect.width
  591. });
  592. }
  593. }
  594. switch (direction) {
  595. case Position.TOP:
  596. case Position.BOTTOM:
  597. // 如果彈出式訊息寬度剛好全滿,那麼就直接置左。
  598. if (popupRect.width + 2 >= boundaryRect.width) {
  599. $popup.css({
  600. left: 0
  601. });
  602. // 如果左右各有空間,那麼就置中彈出式訊息。
  603. } else if (distance.viewport.left > popupRect.width / 2 && distance.viewport.right > popupRect.width / 2) {
  604. $popup.css({
  605. left: (distance.left + rect.width / 2) - popupRect.width / 2
  606. });
  607. // 如果預計會超出邊界的話。
  608. } else if ((distance.left + popupRect.width) - boundaryRect.width > 0 || (distance.right + popupRect.width) - boundaryRect.width > 0) {
  609. // 要是觸發元素在左半邊。
  610. if (distance.left < boundaryRect.width / 2) {
  611. // 就讓彈出式訊息靠齊左側。
  612. $popup.css({
  613. left: 0 + padding
  614. });
  615. } else {
  616. // 就讓彈出式訊息靠右側。
  617. // 不然觸發元素在右半邊的話。
  618. $popup.css({
  619. left: distance.left + rect.width - popupRect.width + distance.right - padding
  620. });
  621. }
  622. } else {
  623. $popup.css({
  624. left: (distance.left + rect.width / 2) - popupRect.width / 2
  625. });
  626. }
  627. break;
  628. case Position.LEFT:
  629. case Position.RIGHT:
  630. if (distance.viewport.top > popupRect.height / 2 && distance.viewport.bottom > popupRect.height / 2) {
  631. $popup.css({
  632. top: (distance.top + rect.height / 2) - popupRect.height / 2
  633. });
  634. } else {
  635. if (distance.viewport.top > distance.viewport.bottom) {
  636. $popup.css({
  637. top: distance.top + rect.height - popupRect.height + distance.viewport.bottom - padding
  638. });
  639. } else {
  640. $popup.css({
  641. top: distance.top - distance.viewport.top + padding
  642. });
  643. }
  644. }
  645. }
  646. module.set.position(direction);
  647. return module.repositionArrow();
  648. }
  649. }
  650. },
  651. toggle: () => {
  652. if (module.is.hidden()) {
  653. module.show();
  654. } else {
  655. module.hide();
  656. }
  657. return $allModules;
  658. },
  659. change: {
  660. title: (title) => {
  661. $popup.find(Selector.TITLE).html(title);
  662. return $allModules;
  663. },
  664. content: (content) => {
  665. var $content;
  666. $content = $popup.find(Selector.CONTENT);
  667. if ($content.exists()) {
  668. $content.html(content);
  669. } else {
  670. $popup.html(content);
  671. module.create.arrow();
  672. }
  673. module.reposition();
  674. return $allModules;
  675. },
  676. html: (html) => {
  677. $popup.html(html);
  678. return $allModules;
  679. }
  680. },
  681. is: {
  682. visible: () => {
  683. return $popup.hasClass(ClassName.VISIBLE);
  684. },
  685. hidden: () => {
  686. return !module.is.visible();
  687. },
  688. showing: () => {
  689. return $this.hasTimer(Metadata.SHOW_TIMER);
  690. },
  691. hiding: () => {
  692. return $this.hasTimer(Metadata.HIDE_TIMER);
  693. },
  694. animating: () => {
  695. return $popup.hasClass(ClassName.ANIMATING);
  696. },
  697. hoverable: () => {
  698. return settings.hoverable === true;
  699. },
  700. pointing: () => {
  701. return settings.pointing === true;
  702. },
  703. arrow: {
  704. bounding: (x, y) => {
  705. switch (module.get.position()) {
  706. case Position.TOP:
  707. if (y > popupRect.bottom && y < rect.top && x < popupRect.right && x > popupRect.left) {
  708. return true;
  709. }
  710. break;
  711. case Position.RIGHT:
  712. if (y < popupRect.bottom && y > popupRect.top && x < popupRect.left && x > rect.right) {
  713. return true;
  714. }
  715. break;
  716. case Position.BOTTOM:
  717. if (y > rect.bottom && y < popupRect.top && x < popupRect.right && x > popupRect.left) {
  718. return true;
  719. }
  720. break;
  721. case Position.LEFT:
  722. if (y < popupRect.bottom && y > popupRect.top && x < rect.left && x > popupRect.right) {
  723. return true;
  724. }
  725. }
  726. return false;
  727. }
  728. }
  729. },
  730. create: {
  731. arrow: () => {
  732. return $arrow = ts(Selector.DIV).addClass(ClassName.ARROW).appendTo($popup);
  733. },
  734. popup: () => {
  735. var $content, $title, attr, content, html, title, variation;
  736. variation = settings.variation || '';
  737. content = settings.content || '';
  738. html = settings.html || '';
  739. title = settings.title || '';
  740. attr = {
  741. variation: $this.attr(Attribute.VARIATION),
  742. content: $this.attr(Attribute.CONTENT),
  743. title: $this.attr(Attribute.TITLE),
  744. html: $this.attr(Attribute.HTML)
  745. };
  746. if (attr.variation !== null) {
  747. variation = attr.variation;
  748. }
  749. if (attr.content !== null) {
  750. content = attr.content;
  751. }
  752. if (attr.title !== null) {
  753. title = attr.title;
  754. }
  755. if (attr.html !== null) {
  756. html = attr.html;
  757. }
  758. $popup = ts(Selector.DIV).addClass(`${ClassName.POPUP} ${variation}`);
  759. if (html !== '') {
  760. $popup.html(html);
  761. }
  762. if (content !== '') {
  763. $content = ts(Selector.DIV).addClass(ClassName.CONTENT).html(content);
  764. $title = ts(Selector.DIV).addClass(ClassName.TITLE).html(title);
  765. if (title !== '') {
  766. $popup.append($title);
  767. }
  768. $popup.append($content);
  769. }
  770. return $popup.insertAfter($this);
  771. }
  772. },
  773. exists: () => {
  774. return $popup.exists();
  775. },
  776. reposition: () => {
  777. module.calculate.popup.position();
  778. return $allModules;
  779. },
  780. event: {
  781. start: () => {
  782. if (!$popup.exists()) {
  783. return;
  784. }
  785. if (settings.exclusive === true) {
  786. module.hideOthers();
  787. }
  788. module.remove.timers();
  789. return module.set.show.timer();
  790. },
  791. end: () => {
  792. if (!$popup.exists()) {
  793. return;
  794. }
  795. module.remove.timers();
  796. return module.set.hide.timer();
  797. }
  798. },
  799. click: {
  800. handler: (event) => {
  801. var isPointingPopup, isPointingSelf, pointElement;
  802. pointElement = ts.fromPoint(event.clientX, event.clientY).get();
  803. isPointingSelf = $this.is(pointElement);
  804. isPointingPopup = $popup.contains(pointElement);
  805. if (isPointingSelf) {
  806. module.toggle();
  807. return;
  808. }
  809. if (!isPointingPopup && settings.closable) {
  810. return module.hide();
  811. }
  812. }
  813. },
  814. focus: {
  815. handler: (event) => {
  816. return module.show();
  817. }
  818. },
  819. blur: {
  820. handler: (event) => {
  821. return module.hide();
  822. }
  823. },
  824. scroll: {
  825. handler: () => {
  826. return module.hide();
  827. }
  828. },
  829. trigger: {
  830. create: () => {
  831. return $this.trigger(Event.CREATE);
  832. },
  833. remove: () => {
  834. return $this.trigger(Event.REMOVE);
  835. },
  836. show: () => {
  837. debug('發生 SHOW 事件', element);
  838. return settings.onShow.call(module.get.popup(), element);
  839. },
  840. visible: () => {
  841. return $this.trigger(Event.VISIBLE);
  842. },
  843. hide: () => {
  844. debug('發生 HIDE 事件', element);
  845. return settings.onHide.call(module.get.popup(), element);
  846. },
  847. hidden: () => {
  848. return $this.trigger(Event.HIDDEN);
  849. },
  850. unplaceable: () => {
  851. return $this.trigger(Event.UNPLACEABLE);
  852. }
  853. },
  854. bind: {
  855. hover: () => {
  856. $this.on(Event.MOUSEENTER, module.event.start);
  857. $this.on(Event.MOUSELEAVE, module.event.end);
  858. $popup.on(Event.MOUSEENTER, module.event.start);
  859. return $popup.on(Event.MOUSELEAVE, module.event.end);
  860. },
  861. click: () => {
  862. return $body.on(Event.CLICK, module.click.handler);
  863. },
  864. focus: () => {
  865. $this.on(Event.FOCUS, module.focus.handler);
  866. return $this.on(Event.FOCUSOUT, module.blur.handler);
  867. },
  868. scroll: () => {
  869. return $scrollContext.on(Event.SCROLL, module.scroll.handler);
  870. },
  871. events: () => {
  872. switch (settings.on) {
  873. case 'hover':
  874. module.bind.hover();
  875. break;
  876. case 'click':
  877. module.bind.click();
  878. break;
  879. case 'focus':
  880. module.bind.focus();
  881. }
  882. if (settings.hideOnScroll === 'auto') {
  883. if (settings.on === 'hover') {
  884. module.bind.scroll();
  885. }
  886. }
  887. if (settings.hideOnScroll === true) {
  888. module.bind.scroll();
  889. }
  890. $this.on(Event.CREATE, () => {
  891. debug('發生 CREATE 事件', element);
  892. return settings.onCreate.call(module.get.popup(), element);
  893. });
  894. $this.on(Event.REMOVE, () => {
  895. debug('發生 REMOVE 事件', element);
  896. return settings.onRemove.call(module.get.popup(), element);
  897. });
  898. $this.on(Event.VISIBLE, () => {
  899. debug('發生 VISIBLE 事件', element);
  900. return settings.onVisible.call(module.get.popup(), element);
  901. });
  902. $this.on(Event.HIDDEN, () => {
  903. debug('發生 HIDDEN 事件', element);
  904. return settings.onHidden.call(module.get.popup(), element);
  905. });
  906. return $this.on(Event.UNPLACEABLE, () => {
  907. debug('發生 UNPLACEABLE 事件', element);
  908. return settings.onUnplaceable.call(module.get.popup(), element);
  909. });
  910. }
  911. },
  912. // ------------------------------------------------------------------------
  913. // 基礎方法
  914. // ------------------------------------------------------------------------
  915. initialize: () => {
  916. var position, variation;
  917. debug('初始化彈出式訊息', element);
  918. position = $this.attr(Attribute.POSITION);
  919. if (position !== null) {
  920. module.set.position(position, true);
  921. }
  922. module.init.popup();
  923. module.trigger.create();
  924. module.set.inverted(settings.inverted);
  925. module.set.pointing(settings.pointing);
  926. variation = $this.attr(Attribute.VARIATION);
  927. if (variation !== null) {
  928. module.set.variation(variation);
  929. }
  930. module.set.hoverable(settings.hoverable);
  931. module.set.transition(settings.transition);
  932. module.set.boundary(settings.boundary);
  933. module.set.scrollContext(settings.scrollContext);
  934. return module.bind.events();
  935. },
  936. instantiate: () => {
  937. return debug('實例化彈出式訊息', element);
  938. },
  939. refresh: () => {
  940. rect = $this.rect();
  941. popupRect = $popup.rect();
  942. boundaryRect = $boundary.rect();
  943. return $allModules;
  944. },
  945. destroy: () => {
  946. debug('摧毀彈出式訊息', element);
  947. $this.removeData(MODULE_NAMESPACE).off(EVENT_NAMESPACE);
  948. return $allModules;
  949. }
  950. };
  951. });
  952. }).call(this);