Source: xui/Dialog.js

  1. /**
  2. * The base class of dialog boxes.
  3. * <p>Part of the XUI module which, for now, has an undocumented API.
  4. */
  5. export class Dialog {
  6. constructor(options) {
  7. this._testMode = false;
  8. let opts = Object.assign({
  9. type: "modal", movable: false, resizable: false,
  10. classes: "xui-control xui-dialog xui-dlg",
  11. title: null, closeable: false,
  12. template: null,
  13. buttons: null
  14. }, options);
  15. this._type = opts.type;
  16. this._classes = opts.classes;
  17. this._form = document.createElement("div");
  18. this._form.classList.add("xui-dlg-container");
  19. if (opts.resizable) {
  20. this._form.classList.add("xui-dlg-resizable");
  21. }
  22. this._form.appendChild(Dialog.TEMPLATE.content.cloneNode(true));
  23. this._titlePane = this._form.firstElementChild;
  24. this._contentPane = this._titlePane.nextElementSibling;
  25. this._buttonPane = this._form.lastElementChild;
  26. this._titleText = this._titlePane.firstElementChild;
  27. this._closeButton = this._titlePane.lastElementChild;
  28. if (opts.title === null && !opts.closeable) {
  29. this._titleText.style.display = "none";
  30. this._closeButton.style.display = "none";
  31. } else {
  32. if (opts.title !== null) {
  33. this._titleText.textContent = opts.title;
  34. }
  35. if (opts.closeable) {
  36. this._closeButton.textContent = StockIcon["cancel"];
  37. this._closeButton.setAttribute("title", "Close");
  38. this._closeButton.addEventListener("click", (e) => {
  39. Util.consumeEvent(e);
  40. this.close(null);
  41. });
  42. }
  43. }
  44. if (opts.template !== null) {
  45. this._contentPane.appendChild(opts.template.content.cloneNode(true));
  46. }
  47. this._buttons = [];
  48. if (opts.buttons !== null) {
  49. let buttonSpecs = opts.buttons;
  50. if (!Array.isArray(buttonSpecs)) {
  51. buttonSpecs = [ buttonSpecs ];
  52. }
  53. let containsSepar = false;
  54. let defaultAction = null;
  55. for (let buttonSpec of buttonSpecs) {
  56. if (Object.keys(buttonSpec).length === 0) {
  57. // Separator ---
  58. let separ = document.createElement("span");
  59. separ.setAttribute("class", "xui-dlg-button-separ");
  60. this._buttonPane.appendChild(separ);
  61. containsSepar = true;
  62. } else {
  63. // Button ---
  64. let action = (typeof buttonSpec["action"] === "string")?
  65. buttonSpec["action"] : null;
  66. let label = (typeof buttonSpec["label"] === "string")?
  67. buttonSpec["label"] : "???";
  68. let isDefault = (buttonSpec["default"] === true);
  69. let button = document.createElement("button");
  70. button.setAttribute("type", "button");
  71. button.classList.add("xui-control", "xui-dlg-button");
  72. if (isDefault) {
  73. button.classList.add("xui-dlg-default-button");
  74. if (action !== null) {
  75. defaultAction = this[action].bind(this);
  76. }
  77. }
  78. button.textContent = label;
  79. this._buttonPane.appendChild(button);
  80. this._buttons.push(button);
  81. if (action !== null) {
  82. const buttonAction = this[action].bind(this);
  83. button.addEventListener("click", (e) => {
  84. Util.consumeEvent(e);
  85. buttonAction();
  86. });
  87. }
  88. }
  89. }
  90. if (defaultAction !== null) {
  91. const onPressEnter = (e) => {
  92. if (e.key === "Enter") {
  93. Util.consumeEvent(e);
  94. defaultAction();
  95. }
  96. };
  97. const inputs = this._contentPane.querySelectorAll(
  98. "input[type=text],input:not([type]),select,[tabindex]");
  99. for (let input of inputs) {
  100. input.addEventListener("keydown", onPressEnter);
  101. }
  102. }
  103. if (!containsSepar) {
  104. this._buttonPane.classList.add("xui-dlg-button-default-layout");
  105. }
  106. }
  107. if (opts.movable && opts.title !== null) {
  108. this._titleText.addEventListener("mousedown",
  109. this.onDragStart.bind(this));
  110. // When the mousemove handler is set on this._titleText, dragging
  111. // stops when the mouse is moved quickly (because the mouse leaves
  112. // this._titleText. That's why the mousemove handler must be set
  113. // on the document.
  114. this._onDrag = this.onDrag.bind(this);
  115. this._titleText.addEventListener("mouseup",
  116. this.onDragEnd.bind(this));
  117. this._dragOffsetX = this._dragOffsetY = -1;
  118. this._dragging = false;
  119. }
  120. }
  121. get testMode() {
  122. return this._testMode;
  123. }
  124. set testMode(testing) {
  125. this._testMode = testing;
  126. }
  127. get form() {
  128. return this._form;
  129. }
  130. get titlePane() {
  131. return this._titlePane;
  132. }
  133. get contentPane() {
  134. return this._contentPane;
  135. }
  136. get buttonPane() {
  137. return this._buttonPane;
  138. }
  139. get titleText() {
  140. return this._titleText;
  141. }
  142. get closeButton() {
  143. return this._closeButton;
  144. }
  145. get buttons() {
  146. return this._buttons;
  147. }
  148. open(position="center", reference=null) {
  149. this._dialog = Dialogs.open({
  150. type: this._type, classes: this._classes,
  151. form: this._form,
  152. position: position, reference: reference
  153. });
  154. this._dialog.addEventListener("dialogclosed", (e) => {
  155. this.dialogClosed(e.xuiDialogResult);
  156. });
  157. return this._dialog;
  158. }
  159. close(result=null) {
  160. Dialogs.close(this._dialog, result);
  161. this._dialog = null;
  162. }
  163. dialogClosed(result) {
  164. // Default implementation does nothing at all.
  165. }
  166. onDragStart(event) {
  167. if (event.buttons === 1) { // Primary button pressed.
  168. Util.consumeEvent(event);
  169. this._dragging = true;
  170. // Coordinates of the top/left of the dialog relative to this
  171. // initial mouse down.
  172. let dialogRect = this._dialog.getBoundingClientRect();
  173. this._dragOffsetX = event.clientX - dialogRect.left;
  174. this._dragOffsetY = event.clientY - dialogRect.top;
  175. this._titleText.style.cursor = "move";
  176. document.addEventListener("mousemove", this._onDrag);
  177. }
  178. }
  179. onDrag(event) {
  180. if (this._dragging) {
  181. Util.consumeEvent(event);
  182. this._dialog.style.left =
  183. String(event.pageX - this._dragOffsetX) + "px";
  184. this._dialog.style.top =
  185. String(event.pageY - this._dragOffsetY) + "px";
  186. }
  187. }
  188. onDragEnd(event) {
  189. if (this._dragging) {
  190. Util.consumeEvent(event);
  191. document.removeEventListener("mousemove", this._onDrag);
  192. this._titleText.style.cursor = null;
  193. this._dragOffsetX = this._dragOffsetY = -1;
  194. this._dragging = false;
  195. }
  196. }
  197. }
  198. Dialog.TEMPLATE = document.createElement("template");
  199. Dialog.TEMPLATE.innerHTML = `
  200. <div class="xui-dlg-title-pane">
  201. <span draggable="false" class="xui-dlg-title"></span>
  202. <span class="xui-small-icon xui-dlg-close-button"></span>
  203. </div>
  204. <div class="xui-dlg-content-pane"></div>
  205. <div class="xui-dlg-button-pane"></div>
  206. `;