/** * Copyright JS Foundation and other contributors, http://js.foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. **/ /** * @namespace RED.editor */ RED.editor = (function() { var editStack = []; var editing_node = null; var editing_config_node = null; var subflowEditor; var customEditTypes = {}; var editTrayWidthCache = {}; function getCredentialsURL(nodeType, nodeID) { var dashedType = nodeType.replace(/\s+/g, '-'); return 'credentials/' + dashedType + "/" + nodeID; } /** * Validate a node * @param node - the node being validated * @returns {boolean} whether the node is valid. Sets node.dirty if needed */ function validateNode(node) { var oldValue = node.valid; var oldChanged = node.changed; node.valid = true; var subflow; var isValid; var validationErrors; var hasChanged; if (node.type.indexOf("subflow:")===0) { subflow = RED.nodes.subflow(node.type.substring(8)); isValid = subflow.valid; hasChanged = subflow.changed; if (isValid === undefined) { isValid = validateNode(subflow); hasChanged = subflow.changed; } validationErrors = validateNodeProperties(node, node._def.defaults, node); node.valid = isValid && validationErrors.length === 0; node.changed = node.changed || hasChanged; node.validationErrors = validationErrors; } else if (node._def) { validationErrors = validateNodeProperties(node, node._def.defaults, node); if (node._def._creds) { validationErrors = validationErrors.concat(validateNodeProperties(node, node._def.credentials, node._def._creds)) } node.valid = (validationErrors.length === 0); node.validationErrors = validationErrors; } else if (node.type == "subflow") { var subflowNodes = RED.nodes.filterNodes({z:node.id}); for (var i=0;i 0) { var value = input.val(); if (defaults[property].hasOwnProperty("format") && defaults[property].format !== "" && input[0].nodeName === "DIV") { value = input.text(); } if (!validateNodeProperty(node, defaults, property,value)) { input.addClass("input-error"); } else { input.removeClass("input-error"); } } } /** * Called when the node's properties have changed. * Marks the node as dirty and needing a size check. * Removes any links to non-existant outputs. * @param node - the node that has been updated * @param outputMap - (optional) a map of old->new port numbers if wires should be moved * @returns {array} the links that were removed due to this update */ function updateNodeProperties(node, outputMap) { node.resize = true; node.dirty = true; node.dirtyStatus = true; var removedLinks = []; if (outputMap) { RED.nodes.eachLink(function(l) { if (l.source === node) { if (outputMap.hasOwnProperty(l.sourcePort)) { if (outputMap[l.sourcePort] === "-1") { removedLinks.push(l); } else { l.sourcePort = outputMap[l.sourcePort]; } } } }); } if (node.hasOwnProperty("__outputs")) { if (node.outputs < node.__outputs) { RED.nodes.eachLink(function(l) { if (l.source === node && l.sourcePort >= node.outputs && removedLinks.indexOf(l) === -1) { removedLinks.push(l); } }); } delete node.__outputs; } node.inputs = Math.min(1,Math.max(0,parseInt(node.inputs))); if (isNaN(node.inputs)) { node.inputs = 0; } if (node.inputs === 0) { removedLinks = removedLinks.concat(RED.nodes.filterLinks({target:node})); } for (var l=0;l").css({display:'inline-block',position:'relative'}); var selectWrap = $("
").css({position:'absolute',left:0,right:'40px'}).appendTo(outerWrap); var select = $('').appendTo(selectWrap); outerWrap.width(newWidth).height(input.height()); if (outerWrap.width() === 0) { outerWrap.width("70%"); } input.replaceWith(outerWrap); // set the style attr directly - using width() on FF causes a value of 114%... select.attr('style',"width:100%"); updateConfigNodeSelect(property,type,node[property],prefix); $('') .css({position:'absolute',right:0,top:0}) .appendTo(outerWrap); $('#'+prefix+'-lookup-'+property).on("click", function(e) { showEditConfigNodeDialog(property,type,select.find(":selected").val(),prefix); e.preventDefault(); }); var label = ""; var configNode = RED.nodes.node(node[property]); var node_def = RED.nodes.getType(type); if (configNode) { label = RED.utils.getNodeLabel(configNode,configNode.id); } input.val(label); } /** * Create a config-node button for this property * @param node - the node being edited * @param property - the name of the field * @param type - the type of the config-node */ function prepareConfigNodeButton(node,property,type,prefix) { var input = $("#"+prefix+"-"+property); input.val(node[property]); input.attr("type","hidden"); var button = $("",{id:prefix+"-edit-"+property, class:"red-ui-button"}); input.after(button); if (node[property]) { button.text(RED._("editor.configEdit")); } else { button.text(RED._("editor.configAdd")); } button.on("click", function(e) { showEditConfigNodeDialog(property,type,input.val()||"_ADD_",prefix); e.preventDefault(); }); } /** * Populate the editor dialog input field for this property * @param node - the node being edited * @param property - the name of the field * @param prefix - the prefix to use in the input element ids (node-input|node-config-input) * @param definition - the definition of the field */ function preparePropertyEditor(node,property,prefix,definition) { var input = $("#"+prefix+"-"+property); if (input.length === 0) { return; } if (input.attr('type') === "checkbox") { input.prop('checked',node[property]); } else { var val = node[property]; if (val == null) { val = ""; } if (definition !== undefined && definition[property].hasOwnProperty("format") && definition[property].format !== "" && input[0].nodeName === "DIV") { input.html(RED.text.format.getHtml(val, definition[property].format, {}, false, "en")); RED.text.format.attach(input[0], definition[property].format, {}, false, "en"); } else { input.val(val); if (input[0].nodeName === 'INPUT' || input[0].nodeName === 'TEXTAREA') { RED.text.bidi.prepareInput(input); } } } } /** * Add an on-change handler to revalidate a node field * @param node - the node being edited * @param definition - the definition of the node * @param property - the name of the field * @param prefix - the prefix to use in the input element ids (node-input|node-config-input) */ function attachPropertyChangeHandler(node,definition,property,prefix) { var input = $("#"+prefix+"-"+property); if (definition !== undefined && "format" in definition[property] && definition[property].format !== "" && input[0].nodeName === "DIV") { $("#"+prefix+"-"+property).on('change keyup', function(event) { if (!$(this).attr("skipValidation")) { validateNodeEditor(node,prefix); } }); } else { $("#"+prefix+"-"+property).on("change", function(event) { if (!$(this).attr("skipValidation")) { validateNodeEditor(node,prefix); } }); } } /** * Assign the value to each credential field * @param node * @param credDef * @param credData * @param prefix */ function populateCredentialsInputs(node, credDef, credData, prefix) { var cred; for (cred in credDef) { if (credDef.hasOwnProperty(cred)) { if (credDef[cred].type == 'password') { if (credData[cred]) { $('#' + prefix + '-' + cred).val(credData[cred]); } else if (credData['has_' + cred]) { $('#' + prefix + '-' + cred).val('__PWRD__'); } else { $('#' + prefix + '-' + cred).val(''); } } else { preparePropertyEditor(credData, cred, prefix, credDef); } attachPropertyChangeHandler(node, credDef, cred, prefix); } } } /** * Update the node credentials from the edit form * @param node - the node containing the credentials * @param credDefinition - definition of the credentials * @param prefix - prefix of the input fields * @return {boolean} whether anything has changed */ function updateNodeCredentials(node, credDefinition, prefix) { var changed = false; if (!node.credentials) { node.credentials = {_:{}}; } else if (!node.credentials._) { node.credentials._ = {}; } for (var cred in credDefinition) { if (credDefinition.hasOwnProperty(cred)) { var input = $("#" + prefix + '-' + cred); if (input.length > 0) { var value = input.val(); if (credDefinition[cred].type == 'password') { node.credentials['has_' + cred] = (value !== ""); if (value == '__PWRD__') { continue; } changed = true; } node.credentials[cred] = value; if (value != node.credentials._[cred]) { changed = true; } } } } return changed; } /** * Prepare all of the editor dialog fields * @param node - the node being edited * @param definition - the node definition * @param prefix - the prefix to use in the input element ids (node-input|node-config-input) */ function prepareEditDialog(node,definition,prefix,done) { for (var d in definition.defaults) { if (definition.defaults.hasOwnProperty(d)) { if (definition.defaults[d].type) { if (!definition.defaults[d]._type.array) { var configTypeDef = RED.nodes.getType(definition.defaults[d].type); if (configTypeDef && configTypeDef.category === 'config') { if (configTypeDef.exclusive) { prepareConfigNodeButton(node,d,definition.defaults[d].type,prefix); } else { prepareConfigNodeSelect(node,d,definition.defaults[d].type,prefix); } } else { console.log("Unknown type:", definition.defaults[d].type); preparePropertyEditor(node,d,prefix,definition.defaults); } } } else { preparePropertyEditor(node,d,prefix,definition.defaults); } attachPropertyChangeHandler(node,definition.defaults,d,prefix); } } var completePrepare = function() { if (definition.oneditprepare) { try { definition.oneditprepare.call(node); } catch(err) { console.log("oneditprepare",node.id,node.type,err.toString()); console.log(err.stack); } } // Now invoke any change handlers added to the fields - passing true // to prevent full node validation from being triggered each time for (var d in definition.defaults) { if (definition.defaults.hasOwnProperty(d)) { var el = $("#"+prefix+"-"+d); el.attr("skipValidation", true); if (el.data("noderedTypedInput") !== undefined) { el.trigger("change",[el.typedInput('type'),el.typedInput('value')]); } else { el.trigger("change"); } el.removeAttr("skipValidation"); } } if (definition.credentials) { for (d in definition.credentials) { if (definition.credentials.hasOwnProperty(d)) { var el = $("#"+prefix+"-"+d); el.attr("skipValidation", true); if (el.data("noderedTypedInput") !== undefined) { el.trigger("change",[el.typedInput('type'),el.typedInput('value')]); } else { el.trigger("change"); } el.removeAttr("skipValidation"); } } } validateNodeEditor(node,prefix); if (done) { done(); } } if (definition.credentials || /^subflow:/.test(definition.type)) { if (node.credentials) { populateCredentialsInputs(node, definition.credentials, node.credentials, prefix); completePrepare(); } else { getNodeCredentials(node.type, node.id, function(data) { if (data) { node.credentials = data; node.credentials._ = $.extend(true,{},data); if (!/^subflow:/.test(definition.type)) { populateCredentialsInputs(node, definition.credentials, node.credentials, prefix); } } completePrepare(); }); } } else { completePrepare(); } } function getEditStackTitle() { var label; for (var i=editStack.length-1;i').appendTo(container); dialogForm.html($("script[data-template-name='"+type+"']").html()); ns = ns||"node-red"; dialogForm.find('[data-i18n]').each(function() { var current = $(this).attr("data-i18n"); var keys = current.split(";"); for (var i=0;i