# Components & JS

In this guide you'll see how to attach component related scripts and deal with external JavaScript libraries (eg. counters, galleries, slideshows, etc.)

WARNING

This guide is referring to GrapesJS v0.16.34 or higher.

To get a better understanding of the content in this guide, we recommend reading Components and Traits first

# Basic scripts

Let's see how to create a component with scripts.

// This is our custom script (avoid using arrow functions)
const script = function() {
  alert('Hi');
  // `this` is bound to the component element
  console.log('the element', this);
};

// Define a new custom component
editor.Components.addType('comp-with-js', {
  model: {
    defaults: {
      script,
      // Add some style, just to make the component visible
      style: {
        width: '100px',
        height: '100px',
        background: 'red',
      }
    }
  }
});

// Create a block for the component, so we can drop it easily
editor.Blocks.add('test-block', {
  label: 'Test block',
  attributes: { class: 'fa fa-text' },
  content: { type: 'comp-with-js' },
});

Now if you drag the new block inside the canvas you'll see an alert and the message in console, as you might expect. One thing worth noting is that this context is bound to the component's element, so, for example, if you need to change its property, you'd do this.innerHTML = 'inner content'.

One thing you should take into account is how the script is bound to component once rendered in the canvas or in your final template. If now you check the generated HTML code in the editor (via Export button or editor.getHtml()), you might see something like this:

<div id="c764"></div>
<script>
  var items = document.querySelectorAll('#c764');
  for (var i = 0, len = items.length; i < len; i++) {
    (function(){
      // START component code
      alert('Hi');
      console.log('the element', this)
      // END component code
    }.bind(items[i]))();
  }
</script>

As you see the editor attaches a unique ID to all components with scripts and retrieves them via querySelectorAll. Dragging another test-block will generate this:

<div id="c764"></div>
<div id="c765"></div>
<script>
  var items = document.querySelectorAll('#c764, #c765');
  for (var i = 0, len = items.length; i < len; i++) {
    (function(){
      // START component code
      alert('Hi');
      console.log('the element', this)
      // END component code
    }.bind(items[i]))();
  }
</script>

# Important caveat

WARNING

Read carefully

Keep in mind that all component scripts are executed inside the iframe of the canvas (isolated, just like your final template), and therefore are NOT part of the current document. All your external libraries (eg. those you're loading along with the editor) are not there (you'll see later how to manage components with dependencies).

That means you can't use stuff outside of the function scope. Take a look at this scenario:

const myVar = 'John';

const script = function() {
  alert('Hi ' + myVar);
  console.log('the element', this);
};

This won't work. You'll get an error of the undefined myVar. The final HTML shows the reason more clearly

<div id="c764"></div>
<script>
  var items = document.querySelectorAll('#c764');
  for (var i = 0, len = items.length; i < len; i++) {
    (function(){
      alert('Hi ' + myVar); // <- ERROR: undefined myVar
      console.log('the element', this);
    }.bind(items[i]))();
  }
</script>

# Passing properties to scripts

Let's say you need to make the script behave differently, based on some component property, maybe also changable via Traits (eg. you want to initiliaze some library with different options). You can do it by using the script-props property on your component.

// The `props` argument will contain only the properties you have declared in `script-props`
const script = function(props) {
  const myLibOpts = {
    prop1: props.myprop1,
    prop2: props.myprop2,
  };
  alert('My lib options: ' + JSON.stringify(myLibOpts));
};

editor.Components.addType('comp-with-js', {
  model: {
    defaults: {
      script,
      // Define default values for your custom properties
      myprop1: 'value1',
      myprop2: '10',
      // Define traits, in order to change your properties
      traits: [
        {
          type: 'select',
          name: 'myprop1',
          changeProp: true,
          options: [
            { value: 'value1', name: 'Value 1' },
            { value: 'value2', name: 'Value 2' },
          ],
        }, {
          type: 'number',
          name: 'myprop2',
          changeProp: true,
        }
      ],
      // Define which properties to pass (this will also reset your script on their changes)
      'script-props': ['myprop1', 'myprop2'],
      // ...
    }
  }
});

Now, if you try to change traits, you'll also see how the script will be triggered with the new updated properties.

# Dependencies

As we mentioned above, scripts are executed independently inside the canvas, without any dependencies, exactly as the final HTML generated by the editor. If you want to make use of external libraries you have two approaches: component-related and template-related.

The component related approach is definitely the best one, as the dependencies will be loaded dynamically and printed in the final HTML only when the component exists in the canvas. All you have to do is to load your dependencies before executing your initialization script.

const script = function(props) {
  const initLib = function() {
    const el = this;
    const myLibOpts = {
      prop1: props.myprop1,
      prop2: props.myprop2,
    };
    someExtLib(el, myLibOpts);
  };

  if (typeof someExtLib == 'undefined') {
    const script = document.createElement('script');
    script.onload = initLib;
    script.src = 'https://.../somelib.min.js';
    document.body.appendChild(script);
  } else {
    initLib();
  }
};

A dependency might be used along all your components (eg. JQuery) so instead requiring it inside each script you might want to inject it directly inside the canvas:

const editor = grapesjs.init({
  ...
  canvas: {
    scripts: ['https://.../somelib.min.js'],
    // The same would be for external styles
    styles: ['https://.../ext-style.min.css'],
  }
});

Keep in mind that the editor won't render those dependencies in the exported HTML (eg. via editor.getHtml()), so it's up to you how to include them in the final page where the final HTML is rendered.