/** * 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. **/ (function($) { var contextParse = function(v) { var parts = RED.utils.parseContextKey(v); return { option: parts.store, value: parts.key } } var contextExport = function(v,opt) { if (!opt) { return v; } var store = ((typeof opt === "string")?opt:opt.value) if (store !== RED.settings.context.default) { return "#:("+store+")::"+v; } else { return v; } } var mapDeprecatedIcon = function(icon) { if (/^red\/images\/typedInput\/.+\.png$/.test(icon)) { icon = icon.replace(/.png$/,".svg"); } return icon; } var allOptions = { msg: {value:"msg",label:"msg.",validate:RED.utils.validatePropertyExpression}, flow: {value:"flow",label:"flow.",hasValue:true, options:[], validate:RED.utils.validatePropertyExpression, parse: contextParse, export: contextExport }, global: {value:"global",label:"global.",hasValue:true, options:[], validate:RED.utils.validatePropertyExpression, parse: contextParse, export: contextExport }, str: {value:"str",label:"string",icon:"red/images/typedInput/az.svg"}, num: {value:"num",label:"number",icon:"red/images/typedInput/09.svg",validate:/^[+-]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?$/}, bool: {value:"bool",label:"boolean",icon:"red/images/typedInput/bool.svg",options:["true","false"]}, json: { value:"json", label:"JSON", icon:"red/images/typedInput/json.svg", validate: function(v) { try{JSON.parse(v);return true;}catch(e){return false;}}, expand: function() { var that = this; var value = this.value(); try { value = JSON.stringify(JSON.parse(value),null,4); } catch(err) { } RED.editor.editJSON({ value: value, complete: function(v) { var value = v; try { value = JSON.stringify(JSON.parse(v)); } catch(err) { } that.value(value); } }) } }, re: {value:"re",label:"regular expression",icon:"red/images/typedInput/re.svg"}, date: {value:"date",label:"timestamp",icon:"fa fa-clock-o",hasValue:false}, jsonata: { value: "jsonata", label: "expression", icon: "red/images/typedInput/expr.svg", validate: function(v) { try{jsonata(v);return true;}catch(e){return false;}}, expand:function() { var that = this; RED.editor.editExpression({ value: this.value().replace(/\t/g,"\n"), complete: function(v) { that.value(v.replace(/\n/g,"\t")); } }) } }, bin: { value: "bin", label: "buffer", icon: "red/images/typedInput/bin.svg", expand: function() { var that = this; RED.editor.editBuffer({ value: this.value(), complete: function(v) { that.value(v); } }) } }, env: { value: "env", label: "env variable", icon: "red/images/typedInput/env.svg" }, node: { value: "node", label: "node", icon: "red/images/typedInput/target.svg", valueLabel: function(container,value) { var node = RED.nodes.node(value); var nodeDiv = $('
',{class:"red-ui-search-result-node"}).css({ "margin-top": "2px", "margin-left": "3px" }).appendTo(container); var nodeLabel = $('').css({ "line-height": "32px", "margin-left": "6px" }).appendTo(container); if (node) { var colour = RED.utils.getNodeColor(node.type,node._def); var icon_url = RED.utils.getNodeIcon(node._def,node); if (node.type === 'tab') { colour = "#C0DEED"; } nodeDiv.css('backgroundColor',colour); var iconContainer = $('
',{class:"red-ui-palette-icon-container"}).appendTo(nodeDiv); RED.utils.createIconElement(icon_url, iconContainer, true); var l = RED.utils.getNodeLabel(node,node.id); nodeLabel.text(l); } else { nodeDiv.css({ 'backgroundColor': '#eee', 'border-style' : 'dashed' }); } }, expand: function() { var that = this; RED.tray.hide(); RED.view.selectNodes({ single: true, selected: [that.value()], onselect: function(selection) { that.value(selection.id); RED.tray.show(); }, oncancel: function() { RED.tray.show(); } }) } } }; var nlsd = false; $.widget( "nodered.typedInput", { _create: function() { try { if (!nlsd && RED && RED._) { for (var i in allOptions) { if (allOptions.hasOwnProperty(i)) { allOptions[i].label = RED._("typedInput.type."+i,{defaultValue:allOptions[i].label}); } } var contextStores = RED.settings.context.stores; var contextOptions = contextStores.map(function(store) { return {value:store,label: store, icon:''} }) if (contextOptions.length < 2) { allOptions.flow.options = []; allOptions.global.options = []; } else { allOptions.flow.options = contextOptions; allOptions.global.options = contextOptions; } } nlsd = true; var that = this; this.disarmClick = false; this.input = $(''); this.input.insertAfter(this.element); this.input.val(this.element.val()); this.element.addClass('red-ui-typedInput'); this.uiWidth = this.element.outerWidth(); this.elementDiv = this.input.wrap("
").parent().addClass('red-ui-typedInput-input-wrap'); this.uiSelect = this.elementDiv.wrap( "
" ).parent(); var attrStyle = this.element.attr('style'); var m; if ((m = /width\s*:\s*(calc\s*\(.*\)|\d+(%|px))/i.exec(attrStyle)) !== null) { this.input.css('width','100%'); this.uiSelect.width(m[1]); this.uiWidth = null; } else { this.uiSelect.width(this.uiWidth); } ["Right","Left"].forEach(function(d) { var m = that.element.css("margin"+d); that.uiSelect.css("margin"+d,m); that.input.css("margin"+d,0); }); ["type","placeholder","autocomplete","data-i18n"].forEach(function(d) { var m = that.element.attr(d); that.input.attr(d,m); }); this.uiSelect.addClass("red-ui-typedInput-container"); this.element.attr('type','hidden'); this.options.types = this.options.types||Object.keys(allOptions); this.selectTrigger = $('').prependTo(this.uiSelect); $('').toggle(this.options.types.length > 1).appendTo(this.selectTrigger); this.selectLabel = $('').appendTo(this.selectTrigger); this.valueLabelContainer = $('
').appendTo(this.uiSelect) this.types(this.options.types); if (this.options.typeField) { this.typeField = $(this.options.typeField).hide(); var t = this.typeField.val(); if (t && this.typeMap[t]) { this.options.default = t; } } else { this.typeField = $("",{type:'hidden'}).appendTo(this.uiSelect); } this.input.on('focus', function() { that.uiSelect.addClass('red-ui-typedInput-focus'); }); this.input.on('blur', function() { that.uiSelect.removeClass('red-ui-typedInput-focus'); }); this.input.on('change', function() { that.validate(); that.element.val(that.value()); that.element.trigger('change',that.propertyType,that.value()); }); this.input.on('keydown', function(evt) { if (evt.keyCode >= 37 && evt.keyCode <= 40) { evt.stopPropagation(); } }) this.selectTrigger.on("click", function(event) { event.preventDefault(); event.stopPropagation(); that._showTypeMenu(); }); this.selectTrigger.on('keydown',function(evt) { if (evt.keyCode === 40) { // Down that._showTypeMenu(); } evt.stopPropagation(); }).on('focus', function() { that.uiSelect.addClass('red-ui-typedInput-focus'); }) // explicitly set optionSelectTrigger display to inline-block otherwise jQ sets it to 'inline' this.optionSelectTrigger = $('').appendTo(this.uiSelect); this.optionSelectLabel = $('').prependTo(this.optionSelectTrigger); RED.popover.tooltip(this.optionSelectLabel,function() { return that.optionValue; }); this.optionSelectTrigger.on("click", function(event) { event.preventDefault(); event.stopPropagation(); that._showOptionSelectMenu(); }).on('keydown', function(evt) { if (evt.keyCode === 40) { // Down that._showOptionSelectMenu(); } evt.stopPropagation(); }).on('blur', function() { that.uiSelect.removeClass('red-ui-typedInput-focus'); }).on('focus', function() { that.uiSelect.addClass('red-ui-typedInput-focus'); }); this.optionExpandButton = $('').appendTo(this.uiSelect); this.optionExpandButtonIcon = $('').appendTo(this.optionExpandButton); this.type(this.options.default||this.typeList[0].value); }catch(err) { console.log(err.stack); } }, _showTypeMenu: function() { if (this.typeList.length > 1) { this._showMenu(this.menu,this.selectTrigger); var selected = this.menu.find("[value='"+this.propertyType+"']"); setTimeout(function() { selected.trigger("focus"); },120); } else { this.input.trigger("focus"); } }, _showOptionSelectMenu: function() { if (this.optionMenu) { this.optionMenu.css({ minWidth:this.optionSelectLabel.width() }); this._showMenu(this.optionMenu,this.optionSelectTrigger); var selectedOption = this.optionMenu.find("[value='"+this.optionValue+"']"); if (selectedOption.length === 0) { selectedOption = this.optionMenu.children(":first"); } selectedOption.trigger("focus"); } }, _hideMenu: function(menu) { $(document).off("mousedown.red-ui-typedInput-close-property-select"); menu.hide(); menu.css({ height: "auto" }); if (menu.opts.multiple) { var selected = []; menu.find('input[type="checkbox"]').each(function() { if ($(this).prop("checked")) { selected.push($(this).data('value')) } }) menu.callback(selected); } if (this.elementDiv.is(":visible")) { this.input.trigger("focus"); } else if (this.optionSelectTrigger.is(":visible")){ this.optionSelectTrigger.trigger("focus"); } else { this.selectTrigger.trigger("focus"); } }, _createMenu: function(menuOptions,opts,callback) { var that = this; var menu = $("
").addClass("red-ui-typedInput-options red-ui-editor-dialog"); menu.opts = opts; menu.callback = callback; menuOptions.forEach(function(opt) { if (typeof opt === 'string') { opt = {value:opt,label:opt}; } var op = $('').attr("value",opt.value).appendTo(menu); if (opt.label) { op.text(opt.label); } if (opt.icon) { if (opt.icon.indexOf("<") === 0) { $(opt.icon).prependTo(op); } else if (opt.icon.indexOf("/") !== -1) { $('',{src:mapDeprecatedIcon(opt.icon),style:"margin-right: 4px; height: 18px;"}).prependTo(op); } else { $('',{class:"red-ui-typedInput-icon "+opt.icon}).prependTo(op); } } else { op.css({paddingLeft: "18px"}); } if (!opt.icon && !opt.label) { op.text(opt.value); } var cb; if (opts.multiple) { cb = $('').css("pointer-events","none").data('value',opt.value).prependTo(op).on("mousedown", function(evt) { evt.preventDefault() }); } op.on("click", function(event) { event.preventDefault(); event.stopPropagation(); if (!opts.multiple) { callback(opt.value); that._hideMenu(menu); } else { cb.prop("checked",!cb.prop("checked")); } }); }); menu.css({ display: "none" }); menu.appendTo(document.body); menu.on('keydown', function(evt) { if (evt.keyCode === 40) { evt.preventDefault(); // DOWN $(this).children(":focus").next().trigger("focus"); } else if (evt.keyCode === 38) { evt.preventDefault(); // UP $(this).children(":focus").prev().trigger("focus"); } else if (evt.keyCode === 27) { // ESCAPE evt.preventDefault(); that._hideMenu(menu); } evt.stopPropagation(); }) return menu; }, _showMenu: function(menu,relativeTo) { if (this.disarmClick) { this.disarmClick = false; return } if (menu.opts.multiple) { var selected = {}; this.value().split(",").forEach(function(f) { selected[f] = true; }) menu.find('input[type="checkbox"]').each(function() { $(this).prop("checked",selected[$(this).data('value')]) }) } var that = this; var pos = relativeTo.offset(); var height = relativeTo.height(); var menuHeight = menu.height(); var top = (height+pos.top); if (top+menuHeight > $(window).height()) { top -= (top+menuHeight)-$(window).height()+5; } if (top < 0) { menu.height(menuHeight+top) top = 0; } menu.css({ top: top+"px", left: (pos.left)+"px", }); menu.slideDown(100); this._delay(function() { that.uiSelect.addClass('red-ui-typedInput-focus'); $(document).on("mousedown.red-ui-typedInput-close-property-select", function(event) { if(!$(event.target).closest(menu).length) { that._hideMenu(menu); } if ($(event.target).closest(relativeTo).length) { that.disarmClick = true; event.preventDefault(); } }) }); }, _getLabelWidth: function(label, done) { var labelWidth = label.outerWidth(); if (labelWidth === 0) { var wrapper = $('
').css({ position:"absolute", "white-space": "nowrap", top:-2000 }).appendTo(document.body); var container = $('
').appendTo(wrapper); var newTrigger = label.clone().appendTo(container); setTimeout(function() { labelWidth = newTrigger.outerWidth(); wrapper.remove(); done(labelWidth); },50) } else { done(labelWidth); } }, _resize: function() { var that = this; if (this.uiWidth !== null) { this.uiSelect.width(this.uiWidth); } var type = this.typeMap[this.propertyType]; if (type && type.hasValue === false) { this.selectTrigger.addClass("red-ui-typedInput-full-width"); } else { this.selectTrigger.removeClass("red-ui-typedInput-full-width"); this._getLabelWidth(this.selectTrigger, function(labelWidth) { that.elementDiv.css('left',labelWidth+"px"); that.valueLabelContainer.css('left',labelWidth+"px"); if (that.optionExpandButton.shown) { that.elementDiv.css('right',"22px"); that.valueLabelContainer.css('right',"22px"); } else { that.elementDiv.css('right','0'); that.valueLabelContainer.css('right','0'); that.input.css({ 'border-top-right-radius': '4px', 'border-bottom-right-radius': '4px' }); } if (that.optionSelectTrigger) { if (type && type.options && type.hasValue === true) { that.optionSelectLabel.css({'left':'auto'}) that._getLabelWidth(that.optionSelectLabel, function(lw) { that.optionSelectTrigger.css({'width':(23+lw)+"px"}); that.elementDiv.css('right',(23+lw)+"px"); that.input.css({ 'border-top-right-radius': 0, 'border-bottom-right-radius': 0 }); }); } else { that.optionSelectLabel.css({'left':'0'}) that.optionSelectTrigger.css({'width':'calc( 100% - '+labelWidth+'px )'}); if (!that.optionExpandButton.shown) { that.elementDiv.css({'right':0}); that.input.css({ 'border-top-right-radius': '4px', 'border-bottom-right-radius': '4px' }); } } } }); } }, _updateOptionSelectLabel: function(o) { var opt = this.typeMap[this.propertyType]; this.optionSelectLabel.empty(); if (this.typeMap[this.propertyType].valueLabel) { if (opt.multiple) { this.typeMap[this.propertyType].valueLabel.call(this,this.optionSelectLabel,o); } else { this.typeMap[this.propertyType].valueLabel.call(this,this.optionSelectLabel,o.value); } } else if (!opt.multiple) { if (o.icon) { if (o.icon.indexOf("<") === 0) { $(o.icon).prependTo(this.optionSelectLabel); } else if (o.icon.indexOf("/") !== -1) { // url $('',{src:mapDeprecatedIcon(o.icon),style:"height: 18px;"}).prependTo(this.optionSelectLabel); } else { // icon class $('',{class:"red-ui-typedInput-icon "+o.icon}).prependTo(this.optionSelectLabel); } } else if (o.label) { this.optionSelectLabel.text(o.label); } else { this.optionSelectLabel.text(o.value); } if (opt.hasValue) { this.optionValue = o.value; this._resize(); this.input.trigger('change',this.propertyType,this.value()); } } else { this.optionSelectLabel.text(o.length+" selected"); } }, _destroy: function() { if (this.optionMenu) { this.optionMenu.remove(); } this.menu.remove(); this.uiSelect.remove(); }, types: function(types) { var that = this; var currentType = this.type(); this.typeMap = {}; this.typeList = types.map(function(opt) { var result; if (typeof opt === 'string') { result = allOptions[opt]; } else { result = opt; } that.typeMap[result.value] = result; return result; }); this.selectTrigger.toggleClass("disabled", this.typeList.length === 1); this.selectTrigger.find(".fa-caret-down").toggle(this.typeList.length > 1) if (this.menu) { this.menu.remove(); } this.menu = this._createMenu(this.typeList,{},function(v) { that.type(v) }); if (currentType && !this.typeMap.hasOwnProperty(currentType)) { this.type(this.typeList[0].value); } else { this.propertyType = null; this.type(currentType); } setTimeout(function() {that._resize();},0); }, width: function(desiredWidth) { this.uiWidth = desiredWidth; this._resize(); }, value: function(value) { var that = this; var opt = this.typeMap[this.propertyType]; if (!arguments.length) { var v = this.input.val(); if (opt.export) { v = opt.export(v,this.optionValue) } return v; } else { var selectedOption = []; if (opt.options) { var checkValues = [value]; if (opt.multiple) { selectedOption = []; checkValues = value.split(","); } checkValues.forEach(function(value) { for (var i=0;i',{src:mapDeprecatedIcon(opt.icon),style:"margin-right: 4px;height: 18px;"}).prependTo(this.selectLabel); } else { $('',{class:"red-ui-typedInput-icon "+opt.icon}).prependTo(this.selectLabel); } } if (opt.hasValue === false || (opt.showLabel !== false && !opt.icon)) { this.selectLabel.text(opt.label); } if (this.optionMenu) { this.optionMenu.remove(); this.optionMenu = null; } if (opt.options) { if (this.optionExpandButton) { this.optionExpandButton.hide(); this.optionExpandButton.shown = false; } if (this.optionSelectTrigger) { this.optionSelectTrigger.show(); if (!opt.hasValue) { this.elementDiv.hide(); this.valueLabelContainer.hide(); } else { this.elementDiv.show(); this.valueLabelContainer.hide(); } this.activeOptions = {}; opt.options.forEach(function(o) { if (typeof o === 'string') { that.activeOptions[o] = {label:o,value:o}; } else { that.activeOptions[o.value] = o; } }); if (!that.activeOptions.hasOwnProperty(that.optionValue)) { that.optionValue = null; } var op; if (!opt.hasValue) { var validValue = false; var currentVal = this.input.val(); if (!opt.multiple) { for (var i=0;i'); var content = opt.expand.content.call(that,container); var panel = RED.popover.panel(container); panel.container.css({ width:that.valueLabelContainer.width() }); if (opt.expand.minWidth) { panel.container.css({ minWidth: opt.expand.minWidth+"px" }); } panel.show({ target:that.optionExpandButton, onclose:content.onclose, align: "right" }); } }) } else { this.optionExpandButton.shown = false; this.optionExpandButton.hide(); } } this._trigger("typechange",null,this.propertyType); this.input.trigger('change',this.propertyType,this.value()); } if (!image) { this._resize(); } } } }, validate: function() { var result; var value = this.value(); var type = this.type(); if (this.typeMap[type] && this.typeMap[type].validate) { var val = this.typeMap[type].validate; if (typeof val === 'function') { result = val(value); } else { result = val.test(value); } } else { result = true; } if (result) { this.uiSelect.removeClass('input-error'); } else { this.uiSelect.addClass('input-error'); } return result; }, show: function() { this.uiSelect.show(); this._resize(); }, hide: function() { this.uiSelect.hide(); } }); })(jQuery);