Node-Red configuration
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ui_dropdown.js 9.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. module.exports = function(RED) {
  2. var ui = require('../ui')(RED);
  3. function DropdownNode(config) {
  4. RED.nodes.createNode(this, config);
  5. this.pt = config.passthru;
  6. this.multiple = config.multiple || false;
  7. this.state = [" "," "];
  8. var node = this;
  9. node.status({});
  10. var group = RED.nodes.getNode(config.group);
  11. if (!group) { return; }
  12. var tab = RED.nodes.getNode(group.config.tab);
  13. if (!tab) { return; }
  14. var control = {
  15. type: 'dropdown',
  16. multiple: config.multiple,
  17. label: config.label,
  18. tooltip: config.tooltip,
  19. place: config.place,
  20. order: config.order,
  21. value: config.payload || node.id,
  22. width: config.width || group.config.width || 6,
  23. height: config.height || 1,
  24. className: config.className || '',
  25. };
  26. for (var o=0; o<config.options.length; o++) {
  27. config.options[o].label = config.options[o].label || config.options[o].value;
  28. }
  29. control.options = config.options;
  30. var emitOptions = { value:undefined };
  31. node.on("input", function(msg) {
  32. node.topi = msg.topic;
  33. });
  34. var done = ui.add({
  35. node: node,
  36. tab: tab,
  37. group: group,
  38. forwardInputMessages: config.passthru,
  39. control: control,
  40. convert: function (payload, oldValue, msg) {
  41. // convert msg
  42. // as of now, only allow a full replacement of options
  43. // beforeEmit is only called when a node linked to us sends a msg
  44. // we are expecting to receive an "update options" msg
  45. // which we expect to be an array of new options
  46. // for convenience, we pass an indication to the node connected to this dropdown
  47. // that this is an "update options" message coming from the input sender
  48. // 'beforeEmit' is called before 'beforeSend', so we may pass in that info
  49. // otherwise that convenience info would not be sent (would not cause any problems)...
  50. emitOptions = {isOptionsValid:false, value:undefined, newOptions:undefined};
  51. do {
  52. if (!msg.options) { break; }
  53. if (typeof msg.options === "string" ) { msg.options = [ msg.options ]; }
  54. if (!Array.isArray(msg.options)) { break; }
  55. emitOptions.newOptions = [];
  56. if (msg.options.length === 0) {
  57. emitOptions.isOptionsValid = true;
  58. break;
  59. }
  60. // could check whether or not all members have same type
  61. for (var i = 0; i < msg.options.length; i++) {
  62. var opt = msg.options[i];
  63. if (opt === undefined || opt === null) { continue; }
  64. switch (typeof opt) {
  65. case 'number': {
  66. opt = "" + opt;
  67. emitOptions.newOptions.push({label:opt, value:opt, type:"number"});
  68. break;
  69. }
  70. case 'string': {
  71. emitOptions.newOptions.push({label:opt, value:opt, type:"string"});
  72. break;
  73. }
  74. case 'object': {
  75. // assuming object of {label:value}
  76. for (var m in opt) {
  77. if (opt.hasOwnProperty(m)) {
  78. emitOptions.newOptions.push({label:m, value:opt[m], type:typeof(opt[m])});
  79. }
  80. }
  81. break;
  82. }
  83. default:
  84. // do nothing, just continue with next option
  85. }
  86. }
  87. // send null object on change of menu list
  88. if (emitOptions.newOptions.length > 0) { emitOptions.value = null; }
  89. // or send the preselected value
  90. if (msg.payload) { emitOptions.value = msg.payload; }
  91. emitOptions.isOptionsValid = true;
  92. } while (false);
  93. // finally adjust msg to reflect the input
  94. msg._dontSend = true;
  95. if (emitOptions.isOptionsValid) {
  96. control.options = emitOptions.newOptions;
  97. control.value = emitOptions.value;
  98. }
  99. else {
  100. if (msg.options) {
  101. node.error("ERR: Invalid Options", msg);
  102. }
  103. }
  104. if (msg.hasOwnProperty("payload")) {
  105. if (node.multiple) {
  106. if (typeof msg.payload === "string") {
  107. msg.payload = msg.payload.split(',');
  108. }
  109. }
  110. emitOptions.value = msg.payload;
  111. control.value = emitOptions.value;
  112. delete msg._dontSend;
  113. return emitOptions;
  114. }
  115. // we do not overide payload here due to 'opt.emitOnlyNewValues' in ui.js
  116. // when undefined is returned, msg will not be forwarded
  117. return emitOptions.isOptionsValid ? emitOptions : undefined; // always pass entire object (newValue == oldValue)
  118. },
  119. beforeEmit: function (msg, newValue) {
  120. if (msg.socketid) { emitOptions.socketid = msg.socketid; }
  121. return emitOptions;
  122. },
  123. convertBack: function (msg) {
  124. var val = node.multiple ? [] : "";
  125. var m = RED.util.cloneMessage(msg);
  126. var mm = (m.hasOwnProperty("id") && m.hasOwnProperty("value")) ? m.value : m;
  127. for (var i=0; i<control.options.length; i++) {
  128. if (!node.multiple) {
  129. delete m["$$mdSelectId"];
  130. if (JSON.stringify(control.options[i].value) == JSON.stringify(mm)) {
  131. val = control.options[i].value;
  132. if (typeof val === "string" && control.options[i].type.indexOf("str") !== 0) {
  133. try { val = JSON.parse(val); }
  134. catch(e) {}
  135. }
  136. break;
  137. }
  138. }
  139. else if (node.multiple && mm !== null) {
  140. if (!Array.isArray(mm)) {
  141. if (mm.hasOwnProperty("value")) { mm = mm.value; }
  142. // if (typeof m === "string") { m = [ m ]; }
  143. if (mm == null) { mm = []; }
  144. else { mm = [ mm ]; }
  145. }
  146. mm.map(x => delete x["$$mdSelectId"])
  147. for (var j = 0; j < mm.length; j++) {
  148. if (JSON.stringify(control.options[i].value) === JSON.stringify(mm[j])) {
  149. var v = control.options[i].value;
  150. if (typeof v === "string" && control.options[i].type !== "string") {
  151. try { v = JSON.parse(v); }
  152. catch(e) {}
  153. }
  154. val.push(v);
  155. break;
  156. }
  157. }
  158. }
  159. }
  160. return val;
  161. },
  162. beforeSend: function (msg) {
  163. if (msg.payload === undefined) { msg.payload = []; }
  164. if (msg.payload === "") { msg._dontSend = true; }
  165. if (msg._dontSend) {
  166. delete msg.options;
  167. msg.payload = emitOptions.value;
  168. }
  169. var t = RED.util.evaluateNodeProperty(config.topic,config.topicType || "str",node,msg) || node.topi;
  170. if (t) { msg.topic = t; }
  171. if (msg.payload === null || msg._dontSend) { node.status({}); }
  172. else {
  173. var stat = "";
  174. if (Array.isArray(msg.payload)) { stat = msg.payload.length + " items"; }
  175. else {
  176. if (typeof msg.payload === "object") { stat = JSON.stringify(msg.payload); }
  177. else { stat = msg.payload.toString(); }
  178. if (stat.length > 32) { stat = stat.substr(0,31)+"..."; }
  179. }
  180. if (node.pt) {
  181. node.status({shape:"dot",fill:"grey",text:stat});
  182. }
  183. else {
  184. node.state[1] = stat;
  185. node.status({shape:"dot",fill:"grey",text:node.state[1] + " | " + node.state[1]});
  186. }
  187. }
  188. }
  189. });
  190. if (!node.pt) {
  191. node.on("input", function(msg) {
  192. node.state[0] = msg.payload;
  193. node.status({shape:"dot",fill:"grey",text:node.state[0] + " | " + node.state[1]});
  194. });
  195. }
  196. node.on("close", done);
  197. }
  198. RED.nodes.registerType("ui_dropdown", DropdownNode);
  199. };