Node library conflict variables on html file

Hi!
I reelected the following problem in Node-Red 3.0.2 Node v18,

I developed three similar libraries (circumstances made it necessary for them to be three libraries)

we detected in these libraries upon installation of at least two of them in a Node-Red impanto that some nodes were adding to overwrite themselves,

I verified:

  • the registration are different

  • the assignment in the “packege.json” are different

  • the file names are different

after a few hours of debugging I found the cause of the problem it seems that the nodes in their html file encoded in the script part of the variables with the same name,

immagine

once replaced in all of them there was no more conflict/overwriting in installation,

i think it is deeply related to the operation of JQuery in loading the node html

since it is very likely to happen in the future.

is it possible to foresee a bug fix at core level?

Hi @APXc

Can you share some more details? Is the source code available anywhere for us to have a look? If not, can you try recreating it with a simple example?

At this point, we don't have enough details to understand the issue.

Nick

Hi @knolleary,

this is a original code on html file before fix

the name variables it's:

  • servicesName
  • servicesData

library 1

<script type="text/javascript">
  let servicesName = [];
  let servicesData = {};
    $.getJSON('services', (data, status) => {
      for(service in data) {
        servicesData = data;
        servicesName.push(service);
      }
    });
  RED.nodes.registerType('serviceSap',{
    category: 'Sap',
    color: '#FFC300',
    defaults: {
      name: {value: ''},
      serviceName: {value: ''},
      service: {value: ''},
      // entity: {value: ''},
      // udo: {value: ''},
      // udt: {value: ''},
      // entityId: {value: ''},
      // docEntry: {value: ''},
      // code: {value: ''},
      headers: {value: ''},
      bodyPost: {value: ''}
    },
    inputs:1,
    outputs:1,
    icon: 'font-awesome/fa-gears',
    label: function() {
      return this.name||"Sap service";
    },
    oneditprepare: function() {
      // $("#node-input-entityId").typedInput({
      //   type:"msg",
      //   types:["msg"],
      //   typeField: "#node-input-entityId-type",
      //   value: 'entityId'
      // });

      // $("#node-input-docEntry").typedInput({
      //   type:"msg",
      //   types:["msg"],
      //   typeField: "#node-input-docEntry-type",
      //   value: 'docEntry'
      // });

      // $("#node-input-code").typedInput({
      //   type:"msg",
      //   types:["msg"],
      //   typeField: "#node-input-code-type",
      //   value: 'code'
      // });

      $("#node-input-headers").typedInput({
        type:"msg",
        types:["msg"],
        typeField: "#node-input-headers-type",
        value: 'headers'
      });

      $("#node-input-bodyPost").typedInput({
        type:"msg",
        types:["msg"],
        typeField: "#node-input-bodyPost-type",
        value: 'bodyPost'
      });

      // jQuery("#node-input-entity").change(function() {
      //   jQuery('#container-udo').hide();
      //   jQuery('#container-docEntry').hide();
      //   jQuery('#container-udt').hide();
      //   jQuery('#container-code').hide();
      //   jQuery('#container-entityId').hide();

      //   if (jQuery(this).val() === 'UDO'){ 
      //     jQuery('#container-udo').show();
      //     jQuery('#container-docEntry').show();
      //   } 
      //   if (jQuery(this).val() === 'UDT'){ 
      //     jQuery('#container-udt').show();
      //     jQuery('#container-code').show();
      //   } 
      //   if(jQuery(this).val() !== 'UDO' && jQuery(this).val() !== 'UDT') {
      //     jQuery('#container-entityId').show();
      //   }
      // });

      servicesName.forEach((service) => {
        $('#node-input-serviceName')
          .append($("<option></option>")
                      .attr("value", service)
                      .text(service)); 
      });

      // set the previous value
      if(this.serviceName) {
        $('#node-input-serviceName').val(this.serviceName);
      }
      

      $("#node-input-serviceName").change((event) => {
        const service = $("#node-input-serviceName").val();

        $('#node-input-service').empty();

        if(servicesData[service]) {
          servicesData[service].forEach((endpoint) => {
          $('#node-input-service')
            .append($("<option></option>")
                        .attr("value", endpoint)
                        .text(endpoint)); 
          });
          // trick check if change with click or not
          if(!event.originalEvent){
            $('#node-input-service').val(this.service);
          }
        }
      });

    }
  });
</script>
  
<script type="text/html" data-template-name="serviceSap">
  <div class="form-row">
    <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
    <input type="text" id="node-input-name" placeholder="Name">
  </div>

 <div class="form-row">
    <label for="node-input-type"><i class="fa fa-gears"></i> Service</label>
    <select name="node-input-serviceName" id="node-input-serviceName">
      <option></option>
    </select>
  </div>

  <div class="form-row">
    <label for="node-input-type"><i class="fa fa-gears"></i> Endpoint</label>
    <select name="node-input-service" id="node-input-service">
    </select>
  </div>

  <div class="form-row">
    <label for="node-input-type"><i class="fa fa-cog"></i> Headers</label>
    <input type="text" id="node-input-headers">
    <input type="hidden" id="node-input-headers-type">
  </div>
  
  <div class="form-row">
    <label for="node-input-type"><i class="fa fa-cog"></i> BodyPost</label>
    <input type="text" id="node-input-bodyPost">
    <input type="hidden" id="node-input-bodyPost-type">
  </div>
</script>
  
<!-- Documentation -->
<script type="text/html" data-help-name="serviceSap">
  <p>Post action</p>
  
  <h3>Inputs</h3>
      <dl class="message-properties">
          <dt>Name
            <span class="property-type">string</span>
          </dt>
          <dd> the node's name </dd>
          <dt>Entity
            <span class="property-type">string</span>
          </dt>
          <dd> the entity name of SAP </dd>
          <dt>entityId
            <span class="property-type">number | string</span>
          </dt>
          <dd> the id of the entity of SAP </dd>
          <dt>bodyPost
            <span class="property-type">object</span>
          </dt>
          <dd> data to update to the entity </dd>
      </dl>
  
   <h3>Outputs</h3>
       <ol class="node-ports">
           <li>Standard output
               <dl class="message-properties">
                   <dt>payload <span class="property-type">string</span></dt>
                   <dd>the standard output of the command.</dd>
               </dl>
           </li>
       </ol>
  
  <h3>Details</h3>
      <p>this node is used to update the entity of SAP.
        See the examples to understand how to use it.
      </p>
      <!-- <p><code>msg.payload</code> is used as the payload of the published message.
      If it contains an Object it will be converted to a JSON string before being sent.
      If it contains a binary Buffer the message will be published as-is.</p>
      <p>The topic used can be configured in the node or, if left blank, can be set
      by <code>msg.topic</code>.</p>
      <p>Likewise the QoS and retain values can be configured in the node or, if left
      blank, set by <code>msg.qos</code> and <code>msg.retain</code> respectively.</p> -->
</script>

library 2

<script type="text/javascript">
  let servicesName = [];
  let servicesData = {};
    $.getJSON('services', (data, status) => {
      for(service in data) {
        servicesData = data;
        servicesName.push(service);
      }
    });
  RED.nodes.registerType('serviceLayerOneSL',{
    category: 'LayerOne SL',
    color: '#139ce9',
    defaults: {
      name: {value: ''},
      serviceName: {value: ''},
      service: {value: ''},
      // entity: {value: ''},
      // udo: {value: ''},
      // udt: {value: ''},
      // entityId: {value: ''},
      // docEntry: {value: ''},
      // code: {value: ''},
      headers: {value: ''},
      bodyPost: {value: ''}
    },
    inputs:1,
    outputs:1,
    icon: 'font-awesome/fa-gears',
    label: function() {
      return this.name||"Sap service";
    },
    oneditprepare: function() {
      // $("#node-input-entityId").typedInput({
      //   type:"msg",
      //   types:["msg"],
      //   typeField: "#node-input-entityId-type",
      //   value: 'entityId'
      // });

      // $("#node-input-docEntry").typedInput({
      //   type:"msg",
      //   types:["msg"],
      //   typeField: "#node-input-docEntry-type",
      //   value: 'docEntry'
      // });

      // $("#node-input-code").typedInput({
      //   type:"msg",
      //   types:["msg"],
      //   typeField: "#node-input-code-type",
      //   value: 'code'
      // });

      $("#node-input-headers").typedInput({
        type:"msg",
        types:["msg"],
        typeField: "#node-input-headers-type",
        value: 'headers'
      });

      $("#node-input-bodyPost").typedInput({
        type:"msg",
        types:["msg"],
        typeField: "#node-input-bodyPost-type",
        value: 'bodyPost'
      });

      // jQuery("#node-input-entity").change(function() {
      //   jQuery('#container-udo').hide();
      //   jQuery('#container-docEntry').hide();
      //   jQuery('#container-udt').hide();
      //   jQuery('#container-code').hide();
      //   jQuery('#container-entityId').hide();

      //   if (jQuery(this).val() === 'UDO'){ 
      //     jQuery('#container-udo').show();
      //     jQuery('#container-docEntry').show();
      //   } 
      //   if (jQuery(this).val() === 'UDT'){ 
      //     jQuery('#container-udt').show();
      //     jQuery('#container-code').show();
      //   } 
      //   if(jQuery(this).val() !== 'UDO' && jQuery(this).val() !== 'UDT') {
      //     jQuery('#container-entityId').show();
      //   }
      // });

      servicesName.forEach((service) => {
        $('#node-input-serviceName')
          .append($("<option></option>")
                      .attr("value", service)
                      .text(service)); 
      });

      // set the previous value
      if(this.serviceName) {
        $('#node-input-serviceName').val(this.serviceName);
      }
      

      $("#node-input-serviceName").change((event) => {
        const service = $("#node-input-serviceName").val();

        $('#node-input-service').empty();

        if(servicesData[service]) {
          servicesData[service].forEach((endpoint) => {
          $('#node-input-service')
            .append($("<option></option>")
                        .attr("value", endpoint)
                        .text(endpoint)); 
          });
          // trick check if change with click or not
          if(!event.originalEvent){
            $('#node-input-service').val(this.service);
          }
        }
      });

    }
  });
</script>
  
<script type="text/html" data-template-name="serviceLayerOneSL">
  <div class="form-row">
    <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
    <input type="text" id="node-input-name" placeholder="Name">
  </div>

 <div class="form-row">
    <label for="node-input-type"><i class="fa fa-gears"></i> Service</label>
    <select name="node-input-serviceName" id="node-input-serviceName">
      <option></option>
    </select>
  </div>

  <div class="form-row">
    <label for="node-input-type"><i class="fa fa-gears"></i> Endpoint</label>
    <select name="node-input-service" id="node-input-service">
    </select>
  </div>

  <div class="form-row">
    <label for="node-input-type"><i class="fa fa-cog"></i> Headers</label>
    <input type="text" id="node-input-headers">
    <input type="hidden" id="node-input-headers-type">
  </div>
  
  <div class="form-row">
    <label for="node-input-type"><i class="fa fa-cog"></i> BodyPost</label>
    <input type="text" id="node-input-bodyPost">
    <input type="hidden" id="node-input-bodyPost-type">
  </div>
</script>
  
<!-- Documentation -->
<script type="text/html" data-help-name="serviceLayerOneSL">
  <p>Post action</p>
  
  <h3>Inputs</h3>
      <dl class="message-properties">
          <dt>Name
            <span class="property-type">string</span>
          </dt>
          <dd> the node's name </dd>
          <dt>Entity
            <span class="property-type">string</span>
          </dt>
          <dd> the entity name of SAP </dd>
          <dt>entityId
            <span class="property-type">number | string</span>
          </dt>
          <dd> the id of the entity of SAP </dd>
          <dt>bodyPost
            <span class="property-type">object</span>
          </dt>
          <dd> data to update to the entity </dd>
      </dl>
  
   <h3>Outputs</h3>
       <ol class="node-ports">
           <li>Standard output
               <dl class="message-properties">
                   <dt>payload <span class="property-type">string</span></dt>
                   <dd>the standard output of the command.</dd>
               </dl>
           </li>
       </ol>
  
  <h3>Details</h3>
      <p>this node is used to update the entity of SAP.
        See the examples to understand how to use it.
      </p>
      <!-- <p><code>msg.payload</code> is used as the payload of the published message.
      If it contains an Object it will be converted to a JSON string before being sent.
      If it contains a binary Buffer the message will be published as-is.</p>
      <p>The topic used can be configured in the node or, if left blank, can be set
      by <code>msg.topic</code>.</p>
      <p>Likewise the QoS and retain values can be configured in the node or, if left
      blank, set by <code>msg.qos</code> and <code>msg.retain</code> respectively.</p> -->
</script>

library 3

<script type="text/javascript">
  let servicesName = [];
  let servicesData = {};
    $.getJSON('services', (data, status) => {
      for(service in data) {
        servicesData = data;
        servicesName.push(service);
      }
    });
  RED.nodes.registerType('you-layerone2-sl-service',{
    category: 'LayerOne2SL',
    color:"#139ce9",
    defaults: {
      name: {value: ''},
      serviceName: {value: ''},
      service: {value: ''},
      headers: {value: ''},
      bodyPost: {value: ''}
    },
    inputs:1,
    outputs:1,
    icon: 'font-awesome/fa-gears',
    paletteLabel: "Service",
    label: function() {
      return this.name||"Sap service";
    },
    oneditprepare: function() {
      $("#node-input-headers").typedInput({
        type:"msg",
        types:["msg"],
        typeField: "#node-input-headers-type",
        value: 'headers'
      });

      $("#node-input-bodyPost").typedInput({
        type:"msg",
        types:["msg"],
        typeField: "#node-input-bodyPost-type",
        value: 'bodyPost'
      });
      servicesName.forEach((service) => {
        $('#node-input-serviceName')
          .append($("<option></option>")
                      .attr("value", service)
                      .text(service)); 
      });

      // set the previous value
      if(this.serviceName) {
        $('#node-input-serviceName').val(this.serviceName);
      }
      

      $("#node-input-serviceName").change((event) => {
        const service = $("#node-input-serviceName").val();

        $('#node-input-service').empty();

        if(servicesData[service]) {
          servicesData[service].forEach((endpoint) => {
          $('#node-input-service')
            .append($("<option></option>")
                        .attr("value", endpoint)
                        .text(endpoint)); 
          });
          // trick check if change with click or not
          if(!event.originalEvent){
            $('#node-input-service').val(this.service);
          }
        }
      });

    }
  });
</script>
  
<script type="text/html" data-template-name="you-layerone2-sl-service">
  <div class="form-row">
    <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
    <input type="text" id="node-input-name" placeholder="Name">
  </div>

 <div class="form-row">
    <label for="node-input-type"><i class="fa fa-gears"></i> Service</label>
    <select name="node-input-serviceName" id="node-input-serviceName">
      <option></option>
    </select>
  </div>

  <div class="form-row">
    <label for="node-input-type"><i class="fa fa-gears"></i> Endpoint</label>
    <select name="node-input-service" id="node-input-service">
    </select>
  </div>

  <div class="form-row">
    <label for="node-input-type"><i class="fa fa-cog"></i> Headers</label>
    <input type="text" id="node-input-headers">
    <input type="hidden" id="node-input-headers-type">
  </div>
  
  <div class="form-row">
    <label for="node-input-type"><i class="fa fa-cog"></i> BodyPost</label>
    <input type="text" id="node-input-bodyPost">
    <input type="hidden" id="node-input-bodyPost-type">
  </div>
</script>
  
<!-- Documentation -->
<script type="text/html" data-help-name="you-layerone2-sl-service">
  <p>Post action</p>
  
  <h3>Inputs</h3>
      <dl class="message-properties">
          <dt>Name
            <span class="property-type">string</span>
          </dt>
          <dd> the node's name </dd>
          <dt>Entity
            <span class="property-type">string</span>
          </dt>
          <dd> the entity name of SAP </dd>
          <dt>entityId
            <span class="property-type">number | string</span>
          </dt>
          <dd> the id of the entity of SAP </dd>
          <dt>bodyPost
            <span class="property-type">object</span>
          </dt>
          <dd> data to update to the entity </dd>
      </dl>
  
   <h3>Outputs</h3>
       <ol class="node-ports">
           <li>Standard output
               <dl class="message-properties">
                   <dt>payload <span class="property-type">string</span></dt>
                   <dd>the standard output of the command.</dd>
               </dl>
           </li>
       </ol>
  
  <h3>Details</h3>
      <p>this node is used to update the entity of SAP.
        See the examples to understand how to use it.
      </p>
      <!-- <p><code>msg.payload</code> is used as the payload of the published message.
      If it contains an Object it will be converted to a JSON string before being sent.
      If it contains a binary Buffer the message will be published as-is.</p>
      <p>The topic used can be configured in the node or, if left blank, can be set
      by <code>msg.topic</code>.</p>
      <p>Likewise the QoS and retain values can be configured in the node or, if left
      blank, set by <code>msg.qos</code> and <code>msg.retain</code> respectively.</p> -->
</script>

All three of those are adding servicesName and servicesData to the global scope - so they will overwrite each other.

One solution is to wrap your code in a function:

;(function () {
   let servicesName = [];
   let servicesData = {};
   $.getJSON('services', (data, status) => {
     for(service in data) {
       servicesData = data;
       servicesName.push(service);
     }
   });
   ...
})()

This will keep those variables scoped to your node.

2 Likes

Yes, this is something I discovered early on and is easily overlooked by new entrants to node development.

Code isolation is quite a tricky concept in relation to Node-RED due to it loading so many different modules.

1 Like