checkbox.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. /**
  2. * `list` type prompt
  3. */
  4. var _ = require("lodash");
  5. var util = require("util");
  6. var chalk = require("chalk");
  7. var cliCursor = require("cli-cursor");
  8. var figures = require("figures");
  9. var Base = require("./base");
  10. var observe = require("../utils/events");
  11. var utils = require("../utils/readline");
  12. var Paginator = require("../utils/paginator");
  13. /**
  14. * Module exports
  15. */
  16. module.exports = Prompt;
  17. /**
  18. * Constructor
  19. */
  20. function Prompt() {
  21. Base.apply( this, arguments );
  22. if (!this.opt.choices) {
  23. this.throwParamError("choices");
  24. }
  25. if ( _.isArray(this.opt.default) ) {
  26. this.opt.choices.forEach(function( choice ) {
  27. if ( this.opt.default.indexOf(choice.value) >= 0 ) {
  28. choice.checked = true;
  29. }
  30. }, this);
  31. }
  32. this.firstRender = true;
  33. this.pointer = 0;
  34. // Make sure no default is set (so it won't be printed)
  35. this.opt.default = null;
  36. this.paginator = new Paginator();
  37. }
  38. util.inherits( Prompt, Base );
  39. /**
  40. * Start the Inquiry session
  41. * @param {Function} cb Callback when prompt is done
  42. * @return {this}
  43. */
  44. Prompt.prototype._run = function( cb ) {
  45. this.done = cb;
  46. var events = observe(this.rl);
  47. var validation = this.handleSubmitEvents( events.line );
  48. validation.success.forEach( this.onEnd.bind(this) );
  49. validation.error.forEach( this.onError.bind(this) );
  50. events.normalizedUpKey.takeUntil( validation.success ).forEach( this.onUpKey.bind(this) );
  51. events.normalizedDownKey.takeUntil( validation.success ).forEach( this.onDownKey.bind(this) );
  52. events.numberKey.takeUntil( validation.success ).forEach( this.onNumberKey.bind(this) );
  53. events.spaceKey.takeUntil( validation.success ).forEach( this.onSpaceKey.bind(this) );
  54. // Init the prompt
  55. cliCursor.hide();
  56. this.render();
  57. return this;
  58. };
  59. /**
  60. * Render the prompt to screen
  61. * @return {Prompt} self
  62. */
  63. Prompt.prototype.render = function (error) {
  64. // Render question
  65. var cursor = 0;
  66. var message = this.getQuestion();
  67. if ( this.firstRender ) {
  68. message += "(Press <space> to select)";
  69. }
  70. // Render choices or answer depending on the state
  71. if ( this.status === "answered" ) {
  72. message += chalk.cyan( this.selection.join(", ") );
  73. } else {
  74. var choicesStr = renderChoices(this.opt.choices, this.pointer);
  75. message += "\n" + this.paginator.paginate(choicesStr, this.pointer);
  76. }
  77. if (error) {
  78. message += '\n' + chalk.red('>> ') + error;
  79. cursor++;
  80. }
  81. this.firstRender = false;
  82. this.screen.render(message, { cursor: cursor });
  83. };
  84. /**
  85. * When user press `enter` key
  86. */
  87. Prompt.prototype.onEnd = function( state ) {
  88. this.status = "answered";
  89. // Rerender prompt (and clean subline error)
  90. this.render();
  91. this.screen.done();
  92. cliCursor.show();
  93. this.done( state.value );
  94. };
  95. Prompt.prototype.onError = function ( state ) {
  96. this.render(state.isValid);
  97. };
  98. Prompt.prototype.getCurrentValue = function () {
  99. var choices = this.opt.choices.filter(function( choice ) {
  100. return !!choice.checked && !choice.disabled;
  101. });
  102. this.selection = _.pluck(choices, "name");
  103. return _.pluck(choices, "value");
  104. };
  105. Prompt.prototype.onUpKey = function() {
  106. var len = this.opt.choices.realLength;
  107. this.pointer = (this.pointer > 0) ? this.pointer - 1 : len - 1;
  108. this.render();
  109. };
  110. Prompt.prototype.onDownKey = function() {
  111. var len = this.opt.choices.realLength;
  112. this.pointer = (this.pointer < len - 1) ? this.pointer + 1 : 0;
  113. this.render();
  114. };
  115. Prompt.prototype.onNumberKey = function( input ) {
  116. if ( input <= this.opt.choices.realLength ) {
  117. this.pointer = input - 1;
  118. this.toggleChoice( this.pointer );
  119. }
  120. this.render();
  121. };
  122. Prompt.prototype.onSpaceKey = function( input ) {
  123. this.toggleChoice(this.pointer);
  124. this.render();
  125. };
  126. Prompt.prototype.toggleChoice = function( index ) {
  127. var checked = this.opt.choices.getChoice(index).checked;
  128. this.opt.choices.getChoice(index).checked = !checked;
  129. };
  130. /**
  131. * Function for rendering checkbox choices
  132. * @param {Number} pointer Position of the pointer
  133. * @return {String} Rendered content
  134. */
  135. function renderChoices(choices, pointer) {
  136. var output = '';
  137. var separatorOffset = 0;
  138. choices.forEach(function (choice, i) {
  139. if (choice.type === 'separator') {
  140. separatorOffset++;
  141. output += ' ' + choice + '\n';
  142. return;
  143. }
  144. if (choice.disabled) {
  145. separatorOffset++;
  146. output += ' - ' + choice.name;
  147. output += ' (' + (_.isString(choice.disabled) ? choice.disabled : 'Disabled') + ')';
  148. } else {
  149. var isSelected = (i - separatorOffset === pointer);
  150. output += isSelected ? chalk.cyan(figures.pointer) : ' ';
  151. output += getCheckbox(choice.checked) + ' ' + choice.name;
  152. }
  153. output += '\n';
  154. });
  155. return output.replace(/\n$/, '');
  156. }
  157. /**
  158. * Get the checkbox
  159. * @param {Boolean} checked - add a X or not to the checkbox
  160. * @return {String} Composited checkbox string
  161. */
  162. function getCheckbox(checked) {
  163. return checked ? chalk.green(figures.radioOn) : figures.radioOff;
  164. }