input.ts 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. import {Command, Session} from "../session";
  2. import {body_scroll_to, LRUMap, make_set} from "../utils";
  3. import {InputItem} from "../models/input/base"
  4. import {state} from '../state'
  5. import {all_input_items} from "../models/input"
  6. import {CommandHandler} from "./base"
  7. /*
  8. * 整个输入区域的控制类
  9. * 管理当前活跃和非活跃的表单
  10. * */
  11. export class InputHandler implements CommandHandler{
  12. accept_command: string[] = ['input', 'input_group', 'update_input', 'destroy_form'];
  13. session: Session;
  14. private form_ctrls: LRUMap;
  15. private readonly container_elem: JQuery;
  16. constructor(session: Session, container_elem: JQuery) {
  17. this.session = session;
  18. this.container_elem = container_elem;
  19. this.form_ctrls = new LRUMap(); // task_id -> stack of FormGroupController
  20. }
  21. private _after_show_form() {
  22. if (!state.AutoScrollBottom)
  23. return;
  24. if (this.container_elem.height() > $(window).height())
  25. body_scroll_to(this.container_elem, 'top', () => {
  26. $('[auto_focus="true"]').focus();
  27. });
  28. else
  29. body_scroll_to(this.container_elem, 'bottom', () => {
  30. $('[auto_focus="true"]').focus();
  31. });
  32. };
  33. // hide old_ctrls显示的表单,激活 task_id 对应的表单
  34. // 需要保证 task_id 对应有表单
  35. private _activate_form(task_id: string, old_ctrl: InputItem) {
  36. let ctrls = this.form_ctrls.get_value(task_id);
  37. let ctrl = ctrls[ctrls.length - 1];
  38. if (ctrl === old_ctrl || old_ctrl === undefined) {
  39. return ctrl.element.show(state.ShowDuration, () => {
  40. this._after_show_form()
  41. });
  42. }
  43. this.form_ctrls.move_to_top(task_id);
  44. old_ctrl.element.hide(100, () => {
  45. // ctrl.element.show(100);
  46. // 需要在回调中重新获取当前前置表单元素,因为100ms内可能有变化
  47. let t = this.form_ctrls.get_top();
  48. if (t) t[t.length - 1].element.show(state.ShowDuration, () => {
  49. this._after_show_form()
  50. });
  51. });
  52. };
  53. /*
  54. * 每次函数调用返回后,this.form_ctrls.get_top()的栈顶对应的表单为当前活跃表单
  55. * */
  56. handle_message(msg: Command) {
  57. let old_ctrls = this.form_ctrls.get_top();
  58. let old_ctrl = old_ctrls && old_ctrls[old_ctrls.length - 1];
  59. let target_ctrls = this.form_ctrls.get_value(msg.task_id);
  60. if (target_ctrls === undefined) {
  61. this.form_ctrls.push(msg.task_id, []);
  62. target_ctrls = this.form_ctrls.get_value(msg.task_id);
  63. }
  64. // 创建表单
  65. if (msg.command in make_set(['input', 'input_group'])) {
  66. let ctrl = new FormController(this.session, msg.task_id, msg.spec);
  67. target_ctrls.push(ctrl);
  68. this.container_elem.append(ctrl.create_element());
  69. this._activate_form(msg.task_id, old_ctrl);
  70. } else if (msg.command in make_set(['update_input'])) {
  71. // 更新表单
  72. if (target_ctrls.length === 0) {
  73. return console.error('No form to current message. task_id:%s', msg.task_id);
  74. }
  75. target_ctrls[target_ctrls.length - 1].dispatch_ctrl_message(msg.spec);
  76. // 表单前置 removed
  77. // this._activate_form(msg.task_id, old_ctrl);
  78. } else if (msg.command === 'destroy_form') {
  79. if (target_ctrls.length === 0) {
  80. return console.error('No form to current message. task_id:%s', msg.task_id);
  81. }
  82. let deleted = target_ctrls.pop() as FormController;
  83. if (target_ctrls.length === 0)
  84. this.form_ctrls.remove(msg.task_id);
  85. // 销毁的是当前显示的form
  86. if (old_ctrls === target_ctrls) {
  87. deleted.element.hide(100, () => {
  88. deleted.element.remove();
  89. let t = this.form_ctrls.get_top();
  90. if (t) t[t.length - 1].element.show(state.ShowDuration, () => {
  91. this._after_show_form()
  92. });
  93. });
  94. } else {
  95. deleted.element.remove();
  96. }
  97. }
  98. }
  99. }
  100. /*
  101. * 表单控制器
  102. * */
  103. class FormController {
  104. static input_items: { [input_type: string]: typeof InputItem } = {};
  105. session: Session;
  106. element: JQuery = null;
  107. private task_id: string;
  108. private spec: any;
  109. // name -> input_controller
  110. private name2input: { [i: string]: InputItem } = {};
  111. public static register_inputitem(cls: typeof InputItem) {
  112. for (let type of cls.accept_input_types) {
  113. if (type in this.input_items)
  114. throw new Error(`duplicated accept_input_types:[${type}] in ${cls} and ${this.input_items[type]}`);
  115. this.input_items[type] = cls;
  116. }
  117. }
  118. constructor(session: Session, task_id: string, spec: any) {
  119. this.session = session;
  120. this.task_id = task_id;
  121. this.spec = spec;
  122. // this.create_element();
  123. }
  124. create_element(): JQuery {
  125. let tpl = `
  126. <div class="card" style="display: none">
  127. <h5 class="card-header">{{label}}</h5>
  128. <div class="card-body">
  129. <form>
  130. <div class="input-container"></div>
  131. <div class="ws-form-submit-btns">
  132. <button type="submit" class="btn btn-primary">提交</button>
  133. <button type="reset" class="btn btn-warning">重置</button>
  134. {{#cancelable}}<button type="button" class="pywebio_cancel_btn btn btn-danger">取消</button>{{/cancelable}}
  135. </div>
  136. </form>
  137. </div>
  138. </div>`;
  139. let that = this;
  140. const html = Mustache.render(tpl, {label: this.spec.label, cancelable: this.spec.cancelable});
  141. let element = $(html);
  142. element.find('.pywebio_cancel_btn').on('click', function (e) {
  143. that.session.send_message({
  144. event: "from_cancel",
  145. task_id: that.task_id,
  146. data: null
  147. });
  148. });
  149. // 如果表单最后一个输入元素为actions组件,则隐藏默认的"提交"/"重置"按钮
  150. if (this.spec.inputs.length && this.spec.inputs[this.spec.inputs.length - 1].type === 'actions')
  151. element.find('.ws-form-submit-btns').hide();
  152. // 输入控件创建
  153. let body = element.find('.input-container');
  154. for (let idx in this.spec.inputs) {
  155. let input_spec = this.spec.inputs[idx];
  156. if (!(input_spec.type in FormController.input_items))
  157. throw new Error(`Unknown input type '${input_spec.type}'`);
  158. let item_class = FormController.input_items[input_spec.type];
  159. let item = new item_class(this.session, this.task_id, input_spec);
  160. this.name2input[input_spec.name] = item;
  161. body.append(item.create_element());
  162. }
  163. // 事件绑定
  164. element.on('submit', 'form', function (e) {
  165. e.preventDefault(); // avoid to execute the actual submit of the form.
  166. let data: { [i: string]: any } = {};
  167. $.each(that.name2input, (name, ctrl) => {
  168. data[name] = ctrl.get_value();
  169. });
  170. that.session.send_message({
  171. event: "from_submit",
  172. task_id: that.task_id,
  173. data: data
  174. });
  175. });
  176. this.element = element;
  177. return element;
  178. };
  179. dispatch_ctrl_message(spec: any) {
  180. if (!(spec.target_name in this.name2input)) {
  181. return console.error('Can\'t find input[name=%s] element in curr form!', spec.target_name);
  182. }
  183. this.name2input[spec.target_name].update_input(spec);
  184. };
  185. }
  186. for (let item of all_input_items)
  187. FormController.register_inputitem(item);