0

I have been writing a reusable script, let's call it a plugin although it's not jQuery, that can be initialised in a declarative way from the HTML. I have extremely simplified it to explain my question so let's say that if a user inserts a tag like:

<span data-color="green"></span>

the script will fire because the attribute data-color is found, changing the color accordingly.

This approach proved very handy because it avoids anyone using the plugin having to initialise it imperatively in their own scripts with something like:

var elem = document.getElementsByTagName('span')[0];
myPlugin.init(elem);

Moreover by going the declarative way I could get away without defining any global (in this case myPlugin), which seemed to be a nice side effect. I simplified this situation in an example fiddle here, and as you can see a user can avoid writing any js, leaving the configuration to the HTML.

Current situation

The plugin is wrapped in a closure like so:

;(function(){

  var changeColor = {
    init : function(elem){
       var bg = elem.getAttribute('data-color');            
       elem.style.background = bg;
    }
  };

  // the plugin itslef looks for appropriate HTML elements 
  var elem = document.querySelectorAll('[data-color]')[0];

  // it inits itself as soon as it is evaluated at page load
  changeColor.init(elem);

})();

The page loads and the span gets the correct colour, so everything is fine.

The problem

What has come up lately, though, is the need to let the user re-evaluate/re-init the plugin when he needs to. Let's say that in the first example the HTML is changed dynamically after the page is loaded, becoming:

<span data-color="purple"></span>

With the first fiddle there's no way to re-init the plugin, so I am now testing some solutions.

Possible solutions

Exposing a global

The most obvious is exposing a global. If we go this route the fiddle becomes

http://jsfiddle.net/gleezer/089om9z5/4/

where the only real difference is removing the selection of the element, leaving it to the user:

// we remove this line
// var elem = document.querySelectorAll('[data-color]')[0];

and adding something like (again, i am simplifying for the sake of the question):

 window.changeColor = changeColor;

to the above code in order to expose the init method to be called from anywhere. Although this works I am not satisfied with it. I am really looking for an alternative solution, as I don't want to lose the ease of use of the original approach and I don't want to force anyone using the script adding a new global to their projects.

Events

One solution I have found is leveraging events. By putting something like this in the plugin body:

elem.addEventListener('init', function() {
  changeColor.init(elem);
}, false);   

anybody will be able to just create an event an fire it accordingly. An example in this case:

var event = new CustomEvent('init', {});
span.dispatchEvent(event);

This would re-init the plugin whenever needed. A working fiddle is to be found here: http://jsfiddle.net/gleezer/tgztjdzL/1/

The question (finally)

My question is: is there a cleaner/better way of handling this? How can i let people using this plugin without the need of a global or having to initialise the script themselves the first time? Is event the best way or am I missing some more obvious/better solutions?

Aurelio
  • 24,702
  • 9
  • 60
  • 63
  • You will want to have a look at [Web Components](http://www.html5rocks.com/en/tutorials/webcomponents/customelements/) and [Mutation Observers](http://addyosmani.com/blog/mutation-observers/) – Bergi Apr 27 '15 at 18:14
  • Thanks @Bergi, I know both of them, but in this case I am happy with letting the user decide when to update the dom so I think that a very simple solution can be found without using Mutation observers or listening to any particular change on DOM level. – Aurelio Apr 27 '15 at 18:24

1 Answers1

0

You can override Element.setAttribute to trigger your plugin:

var oldSetAttribute = Element.prototype.setAttribute;
Element.prototype.setAttribute = function(name, value) {
  oldSetAttribute.call(this, name, value);
  if (name === 'data-color') {
    changeColor.init(this);
  }
}

Pros:

  • User does not have to explicitly re-initialize the plugin. It will happen automatically when required.

Cons:

  • This will, of course, only work if the user changes data-color attributes using setAttribute, and not if they create new DOM elements using innerHTML or via some other approach.

  • Modifying host object prototypes is considered bad practice by many, and for good reasons. Use at your own risk.

Community
  • 1
  • 1
radiaph
  • 4,001
  • 1
  • 16
  • 19
  • I would guess that *most* of the typical DOM manipulations do not use `setAttribute`… – Bergi Apr 27 '15 at 20:36