form.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. /**
  2. * form.js - form element for blessed
  3. * Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License).
  4. * https://github.com/chjj/blessed
  5. */
  6. /**
  7. * Modules
  8. */
  9. var Node = require('./node');
  10. var Box = require('./box');
  11. /**
  12. * Form
  13. */
  14. function Form(options) {
  15. var self = this;
  16. if (!(this instanceof Node)) {
  17. return new Form(options);
  18. }
  19. options = options || {};
  20. options.ignoreKeys = true;
  21. Box.call(this, options);
  22. if (options.keys) {
  23. this.screen._listenKeys(this);
  24. this.on('element keypress', function(el, ch, key) {
  25. if ((key.name === 'tab' && !key.shift)
  26. || (el.type === 'textbox' && options.autoNext && key.name === 'enter')
  27. || key.name === 'down'
  28. || (options.vi && key.name === 'j')) {
  29. if (el.type === 'textbox' || el.type === 'textarea') {
  30. if (key.name === 'j') return;
  31. if (key.name === 'tab') {
  32. // Workaround, since we can't stop the tab from being added.
  33. el.emit('keypress', null, { name: 'backspace' });
  34. }
  35. el.emit('keypress', '\x1b', { name: 'escape' });
  36. }
  37. self.focusNext();
  38. return;
  39. }
  40. if ((key.name === 'tab' && key.shift)
  41. || key.name === 'up'
  42. || (options.vi && key.name === 'k')) {
  43. if (el.type === 'textbox' || el.type === 'textarea') {
  44. if (key.name === 'k') return;
  45. el.emit('keypress', '\x1b', { name: 'escape' });
  46. }
  47. self.focusPrevious();
  48. return;
  49. }
  50. if (key.name === 'escape') {
  51. self.focus();
  52. return;
  53. }
  54. });
  55. }
  56. }
  57. Form.prototype.__proto__ = Box.prototype;
  58. Form.prototype.type = 'form';
  59. Form.prototype._refresh = function() {
  60. // XXX Possibly remove this if statement and refresh on every focus.
  61. // Also potentially only include *visible* focusable elements.
  62. // This would remove the need to check for _selected.visible in previous()
  63. // and next().
  64. if (!this._children) {
  65. var out = [];
  66. this.children.forEach(function fn(el) {
  67. if (el.keyable) out.push(el);
  68. el.children.forEach(fn);
  69. });
  70. this._children = out;
  71. }
  72. };
  73. Form.prototype._visible = function() {
  74. return !!this._children.filter(function(el) {
  75. return el.visible;
  76. }).length;
  77. };
  78. Form.prototype.next = function() {
  79. this._refresh();
  80. if (!this._visible()) return;
  81. if (!this._selected) {
  82. this._selected = this._children[0];
  83. if (!this._selected.visible) return this.next();
  84. if (this.screen.focused !== this._selected) return this._selected;
  85. }
  86. var i = this._children.indexOf(this._selected);
  87. if (!~i || !this._children[i + 1]) {
  88. this._selected = this._children[0];
  89. if (!this._selected.visible) return this.next();
  90. return this._selected;
  91. }
  92. this._selected = this._children[i + 1];
  93. if (!this._selected.visible) return this.next();
  94. return this._selected;
  95. };
  96. Form.prototype.previous = function() {
  97. this._refresh();
  98. if (!this._visible()) return;
  99. if (!this._selected) {
  100. this._selected = this._children[this._children.length - 1];
  101. if (!this._selected.visible) return this.previous();
  102. if (this.screen.focused !== this._selected) return this._selected;
  103. }
  104. var i = this._children.indexOf(this._selected);
  105. if (!~i || !this._children[i - 1]) {
  106. this._selected = this._children[this._children.length - 1];
  107. if (!this._selected.visible) return this.previous();
  108. return this._selected;
  109. }
  110. this._selected = this._children[i - 1];
  111. if (!this._selected.visible) return this.previous();
  112. return this._selected;
  113. };
  114. Form.prototype.focusNext = function() {
  115. var next = this.next();
  116. if (next) next.focus();
  117. };
  118. Form.prototype.focusPrevious = function() {
  119. var previous = this.previous();
  120. if (previous) previous.focus();
  121. };
  122. Form.prototype.resetSelected = function() {
  123. this._selected = null;
  124. };
  125. Form.prototype.focusFirst = function() {
  126. this.resetSelected();
  127. this.focusNext();
  128. };
  129. Form.prototype.focusLast = function() {
  130. this.resetSelected();
  131. this.focusPrevious();
  132. };
  133. Form.prototype.submit = function() {
  134. var out = {};
  135. this.children.forEach(function fn(el) {
  136. if (el.value != null) {
  137. var name = el.name || el.type;
  138. if (Array.isArray(out[name])) {
  139. out[name].push(el.value);
  140. } else if (out[name]) {
  141. out[name] = [out[name], el.value];
  142. } else {
  143. out[name] = el.value;
  144. }
  145. }
  146. el.children.forEach(fn);
  147. });
  148. this.emit('submit', out);
  149. return this.submission = out;
  150. };
  151. Form.prototype.cancel = function() {
  152. this.emit('cancel');
  153. };
  154. Form.prototype.reset = function() {
  155. this.children.forEach(function fn(el) {
  156. switch (el.type) {
  157. case 'screen':
  158. break;
  159. case 'box':
  160. break;
  161. case 'text':
  162. break;
  163. case 'line':
  164. break;
  165. case 'scrollable-box':
  166. break;
  167. case 'list':
  168. el.select(0);
  169. return;
  170. case 'form':
  171. break;
  172. case 'input':
  173. break;
  174. case 'textbox':
  175. el.clearInput();
  176. return;
  177. case 'textarea':
  178. el.clearInput();
  179. return;
  180. case 'button':
  181. delete el.value;
  182. break;
  183. case 'progress-bar':
  184. el.setProgress(0);
  185. break;
  186. case 'file-manager':
  187. el.refresh(el.options.cwd);
  188. return;
  189. case 'checkbox':
  190. el.uncheck();
  191. return;
  192. case 'radio-set':
  193. break;
  194. case 'radio-button':
  195. el.uncheck();
  196. return;
  197. case 'prompt':
  198. break;
  199. case 'question':
  200. break;
  201. case 'message':
  202. break;
  203. case 'info':
  204. break;
  205. case 'loading':
  206. break;
  207. case 'list-bar':
  208. //el.select(0);
  209. break;
  210. case 'dir-manager':
  211. el.refresh(el.options.cwd);
  212. return;
  213. case 'terminal':
  214. el.write('');
  215. return;
  216. case 'image':
  217. //el.clearImage();
  218. return;
  219. }
  220. el.children.forEach(fn);
  221. });
  222. this.emit('reset');
  223. };
  224. /**
  225. * Expose
  226. */
  227. module.exports = Form;