Skip to content

Instantly share code, notes, and snippets.

@FrikkieSnyman
Last active June 24, 2023 08:06
Show Gist options
  • Save FrikkieSnyman/b2328ffa29cbf80748de0ee5e0b010f0 to your computer and use it in GitHub Desktop.
Save FrikkieSnyman/b2328ffa29cbf80748de0ee5e0b010f0 to your computer and use it in GitHub Desktop.
GrapesJS - attributes, classes, and scripts

GrapesJS - attributes, classes, and scripts

Attributes

In order to add custom attributes to an HTML element that is generated by GrapesJS, one can simply make use of traits. For example, if you wish to produce an HTML element which contains the tag data-noout, a trait with the name data-noout can be added to a custom component.

    var domComps = editor.DomComponents;
    var dType = domComps.getType('default');
    var dModel = dType.model;
    var dView = dType.view;

    domComps.addType('input', {
      model: dModel.extend({
        defaults: Object.assign({}, dModel.prototype.defaults, {
          traits: [
            {
              type: 'checkbox',
              label: 'Data No Output',
              name: 'data-noout'
            }
          ]
        }),
      }, {
          isComponent: function (el) {
            if (el.tagName == 'INPUT') {
              return { type: 'input' };
            }
          },
        }),

      view: dView,
    });

The component above will produce an input component.

alt text

The component can now be dragged onto the canvas. By default, this will produce the following HTML:

<input class="input"/>

To add the custom attribute data-noout, click on the input box. In the component's settings, tick the checkbox labelled Data No Output

alt_text

Exporting the HTML now results in:

<input class="input" data-noout="true"/>

It is important to note that when the checkbox is now unchecked, the attribute is not removed from the HTML tag, but rather updated to false.

<input class="input" data-noout="false"/>

Scripts

GrapesJS makes use of an iframe in which it displays the formcreator. As such, none of the global scripts and packages are available to GrapesJS. To get around this, one must set the allowScripts property in the editor init, and then include the script in the scripts array.

    var editor = grapesjs.init({
      allowScripts: 1,
      canvas: {
        scripts: ['https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js']
      },
      components: `
      <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
      `

The above snippet will make jQuery available to the form builder, as well as bootstrap. Notice that bootstrap, or any CSS lib for that matter, is included in the components.

Classes

It is useful to be able to update the classes of a component if you are using a custom CSS library such as Bootstrap.

First, create a custom trait. This example is based on a select input:

    editor.TraitManager.addType('my-trait', {
      onValueChange() {
        var parentModel = this.target.collection.parent.sm;
        var component = this.target;
        const sm = parentModel.get('SelectorManager');
        var traitModel = this.model;
        var selectedComponent = this.target;
        var label = traitModel.get('value');
        var compCls = component.get('classes');
        compCls.forEach(element => {
          if (element.id == this.model._previousAttributes.value) {
            compCls.remove(element);
          }
        });
        if (label) {
          var classModel = sm.add({label});
          compCls.add(classModel);
          parentModel.trigger('targetClassAdded');
        }
      },

      getInputEl() {
        if(!this.$input) {
          var md = this.model;
          var opts = md.get('options') || [];
          var input = '<select>';
    
          if (opts.length) {
            _.each(opts, el => {
              var name, value, style;
              var attrs = '';
              if(typeof el === 'string'){
                name = el;
                value = el;
              }else{
                name = el.name ? el.name : el.value;
                value = el.value.replace(/"/g,'&quot;');
                style = el.style ? el.style.replace(/"/g,'&quot;') : '';
                attrs += style ? 'style="' + style + '"' : '';
              }
              input += '<option value="' + value + '" ' + attrs + '>' + name + '</option>';
            });
          }
    
          input += '</select>';
          this.input = input;
          this.$input = $(this.input);
    
          var target = this.target;
          var name = md.get('name');
          var val = md.get('value');
    
          if (md.get('changeProp')) {
            val = val || target.get(name);
          } else {
            var attrs = target.get('attributes');
            val = attrs[name];
          }
    
          if(val)
            this.$input.val(val);
        }
    
        return this.$input.get(0);
      },
    });

The main focus here is in the onValueChange() method. The getInputEl() method is just a copy-paste from the default Input trait of GrapesJS, found in TraitSelectView.js.

The onValueChange() removes the previous class added by the trait. It then adds the new class as the value specified for the label.

Once again, creating a custom Input field:

domComps.addType('input', {
      model: dModel.extend({
        defaults: Object.assign({}, dModel.prototype.defaults, {
          traits: [
            {
              type: 'my-trait',
              label: 'Width',
              name: 'class',
              options: [
                {value: '', name: 'None'},
                {value: 'col-lg-1', name: 'Small'},
                {value: 'col-lg-3', name: 'Medium'},
                {value: 'col-lg-6', name: 'Large'},
                {value: 'col-lg-12', name: 'Massive'}
              ]
            }
          ]
        }),
      }, {
          isComponent: function (el) {
            if (el.tagName == 'INPUT') {
              return { type: 'input' };
            }
          },
        }),

      view: dView,
    });

This results in the following dropdown list available to the user:

alt text

and it generates the following HTML:

<input class="input col-lg-3"/>

Inheriting from custom base component

It would be useful to have a set of default traits on custom components. It is actually quite simple to achieve this. Firstly, create a new default type, which contains the traits that you want on all of its children components:

    var domComps = editor.DomComponents;
    var dType = domComps.getType('default');
    var dModel = dType.model;
    var dView = dType.view;
    
    domComps.addType('default', {
      model: dModel.extend({
        defaults: Object.assign({}, dModel.prototype.defaults, {
          traits: [
            // strings are automatically converted to text types
            'id',
            'title',
            'name',
            'placeholder',
            {
              type: 'select',
              label: 'Type',
              name: 'type',
              options: [
                { value: 'text', name: 'Text' },
                { value: 'email', name: 'Email' },
                { value: 'password', name: 'Password' },
                { value: 'number', name: 'Number' },
              ]
            }, {
              type: 'checkbox',
              label: 'Required',
              name: 'required',
            },
            {
              type: 'checkbox',
              label: 'Data No Output',
              name: 'data-nooutput'
            },
            {
              type: 'my-trait',
              label: 'Width',
              name: 'class',
              options: [
                { value: '', name: 'None' },
                { value: 'col-lg-1', name: 'Small' },
                { value: 'col-lg-3', name: 'Medium' },
                { value: 'col-lg-6', name: 'Large' },
                { value: 'col-lg-12', name: 'Massive' }
              ]
            }
          ]
        }),
      }),
      view: dView,
    });

Then, overriding the input component as follows:

    dType = domComps.getType('default');
    dModel = dType.model;
    dView = dType.view;

    domComps.addType('input', {
      model: dModel.extend({
        defaults: Object.assign({}, dModel.prototype.defaults, {
          traits: dModel.prototype.defaults.traits.concat([
            'test'
          ])
        })
      },
      {
        isComponent: function (el) {
          if (el.tagName == 'INPUT') {
            return { type: 'input' };
          }
        },
      }),
      view: dView
    });

This results in the input component with all of the following traits:

alt text

If you do not wish to have all the traits, simply do not concat with the dModel.prototype.defaults.traits array.

@frookypoon
Copy link

This looks good. Thanks Frikkie.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment