Creating Plugins for SnackJS

By  on  

My SnackJS: A Tiny-But-Tasty JavaScript Framework post exposed you to Ryan Florence's new JavaScript framework called SnackJS.  SnackJS' motto is "Because sometimes, all you need is a snack" , and for good reason:  this 3KB micro-framework provides the functionality you need for most websites without the huge overhead of jQuery or other frameworks.  As SnackJS is in its infancy and its aim is to be tiny, the framework does not include some functionalities found in other frameworks;  in short, SnackJS assumes you know how to accomplish some tasks (like modifying element styles, etc.)  using basic JavaScript.

There's no denying that even if you know JavaScript, shortcut methods are hugely helpful -- if they weren't, smart JavaScript developers wouldn't spend time creating JavaScript frameworks.  One shortcut functionality that SnackJS doesn't have is DOM node creation...and by extension, node attribute modification and placements.  Since I believe in the SnackJS project and the functionality mentioned is ideal for my projects, I've created create, attr, and place plugins which I'll share with the community.

snack.attr and snack.wrap.attr

Modifying node attributes was the most difficult of the three tasks because I couldn't simply use the node's setAttribute method.  The most obvious of those is style because that node property is a CSSStyleDeclaration object containing the complete list of node styles.  For that purpose, as well as the purposes of innerHTML, I've created a few special definitions to accommodate for setting those properties as well:

// The "attr" plugin
!function(snack){
	
	// Will hold special attributes, privately
	var props = {};
	
	// The main method
	snack.attr = function(node,attr,value) {
		// Does the actual setting
		var doSet = function(val,key) {
			props[key] && props[key].set ? props[key].set(node,val) : node.setAttribute(key,val);
		};

		// Setter
		var isObj = typeof attr == "object";
		if(value != undefined || isObj) {
			isObj ? snack.each(attr,doSet) : doSet(value,attr);
		}
		else { // Getter
			return props[attr] ? props[attr].get(node) : node.getAttribute(attr);
		}
		// Return the node
		return node;
	};
	
	//  Creates a method by which one can define special node attributes
	snack.attr.define = function(name, obj){
		if (typeof name === 'string'){
			props[name] = obj;
			return;
		}
		// takes an object of key:values
		for (i in name) {
			if (name.hasOwnProperty(i)) {
				snack.attr.define(i, name[i]);
			}
		}
	};
	
	// Define the special attributes now
	snack.attr.define({
		html: {
			set: function(node,value) { node.innerHTML = value; },
			get: function(node) { return node.innerHTML; }
		},
		style: {
			set: function(node,value) { node.setAttribute("style",value); },
			get: function(node) { return node.getAttribute("style"); }
		}
	});
	
	// Extend to the "wrap" method
	snack.wrap.define('attr', function(attr, value){
		this.each(function(node){
			snack.attr(node, attr, value);
		});
	});

}(snack);

The attr property is used for both getting and setting attributes.  Providing three arguments always acts as a setter, providing a key/value object as the second argument is a setter, otherwise it acts as a getter.  Here are sample uses of attr:

// Retrieve the title attribute of a node
var title = snack.attr(node,"title");

// Then retrieve the node's innerHTML
var html = snack.attr(node,"html");

// Set a node's "style" attribute
snack.attr(node,"style","background-color:green;color:#fff;padding:20px;");

// Set multiple attributes at once
snack.arr(node,{
	tabindex: 1,
	value: "davidwalsh",
	placeholder: "username"
});

The attr method even allows you to create custom setters and getters:

// Define the special attributes now
snack.attr.define({
	"class": {
		set: function(node,value) { node.className = value; },
		get: function(node) { return node.className; }
	}
});

I plan of reworking attr just a bit in the future, but what's presented is the current state.

snack.place and snack.wrap.place

The place method inserts nodes in specific locations within the DOM.  In evaluating how many of the larger JavaScript frameworks place nodes, I found Dojo's to be the most concise for the number of positions it allows you to inject nodes.  My method is largely based on the Dojo Tookit's:

// Fun this function immediately after creation
!function(snack) {

	// Places a node at a given position
	snack.place = function(node,domReference,position) {
		// Create functions for insertion
		var before = function(node,domReference) {
			var parent = domReference.parentNode;
			if(parent){
				parent.insertBefore(node, domReference);
			}
		};
		var after = function(node,domReference) {
			//	summary:
			//		Try to insert node after ref
			var parent = domReference.parentNode;
			if(parent){
				if(parent.lastChild == domReference){
					parent.appendChild(node);
				}else{
					parent.insertBefore(node, domReference.nextSibling);
				}
			}
		};
	
	
		if(typeof position == "number"){ // inline'd type check
			var cn = domReference.childNodes;
			if(!cn.length || cn.length <= position){
				domReference.appendChild(node);
			}else{
				before(node, cn[position < 0 ? 0 : position]);
			}
		}else{
			switch(position){
				case "before":
					before(node, domReference);
					break;
				case "after":
					after(node, domReference);
					break;
				case "replace":
					domReference.parentNode.replaceChild(node, domReference);
					break;
				case "first":
					if(domReference.firstChild){
						before(node, domReference.firstChild);
						break;
					}
					// else fallthrough...
				default: // aka: last
					domReference.appendChild(node);
			}
		}
		return node;
	};
	
	// Extend to the "wrap" method
	snack.wrap.define("place", function(domRef,pos){
		this.each(function(node){
			snack.place(node,domRef,pos);
		});
	});

}(snack);

Armed with the place method, you can place nodes in several positions:

// Place the node into the BODY
snack.place(node,document.body);

// Place the node above the another node
snack.place(node,otherNode,"before");

// Replace one node with another
snack.place(node,otherNode,"replace");

You can also use snack.wrap.place to move multiple nodes at a time:

// Create a UL
var list = snack.create("ul");

// Place the element above the node
snack.place(list,node,"before");
var arr = [];
for(x = 0; x <= 4; x++) {
	arr.push(snack.create("li",{ html: "List item " + (x + 1) }));
}
snack.wrap(arr).place(list);

Like I said, I've borrowed quite heavily from Dojo.  My reason for doing so is that Dojo's been proven for years and offers the most flexibility.  Hooray for not reinventing the wheel!

snack.create

The create method was the easiest of the three, even employing attr and place when available.  Simply provide the tag, optional properties, and optional placement:

!function(snack) {
	// If not already created...
	snack.create = function(nodeType,props,nodeRef,where) {
		// Create the node
		var node = document.createElement(nodeType);
		// Add properties
		if(props && snack.attr) {
			// Set properties
			snack.attr(node,props);
		}
		// Inject into parent
		if(nodeRef && snack.place) {
			snack.place(node,nodeRef,where);
		}
		// Return the node
		return node;
	};
}(snack);

snack.create would be used as follows:

// Create a UL
var list = snack.create("ul");

// Add an LI to the list
snack.create("li",{ html: "List item " + (x + 1) },list);

If attr and place plugins aren't loaded, snack.create simply acts as a document.createElement wrapper.

Creating SnackJS Plugins

Creating Snack.js plugins is extremely simple.  Simply add your method to the snack object or use SnackJS' define method:

!function(snack) {

	snack.pluginName = function(arg1,arg2/*, etc.*/) {
	
	};

}(snack);

Voila -- your custom SnackJS plugin is now available.  And if you want your plugin to work with snack.wrap, that's as easy as a snack.each loop:

!function(snack) {

	snack.pluginName = function(arg1,arg2/*, etc.*/) {
	
	};
	
	// Extend to the "wrap" method
	snack.wrap.define("pluginName", function(arg1,arg2/*, etc.*/){
		this.each(function(arg){
			snack.pluginName(arg,arg1,arg2/*, etc.*/);
		});
	});

}(snack);

Adding wrap support is useful in many cases but there are always exceptions.  For example, adding snack.wrap.create doesn't make sense.

Remember that one of SnackJS' goals is to be small and concise, so write your plugins that way.

Contributing SnackJS Plugins

SnackJS is a new, growing framework so your contributions are always welcome.  While they may not make the SnackJS core, there's no downside to creating simple but useful JavaScript plugins for SnackJS.  The steps to creating SnackJS plugins are much like any other framework:

  1. Research your plugin;  look at how other JS frameworks accomplish the same goal and decide which you think is best
  2. Code your plugin
  3. Test.  Test.  Test.
  4. Fork the official SnackJS repo and commit the code.  Feel free to send a pull request to Ryan.

Please, please consider contributing to SnackJS.  It's a project with great intentions, coded beautifully by Ryan, which could vastly improve the speed of the web.  It seems that jQuery is the default JavaScript frameworks for most websites at this point;  it shouldn't be. Don't make more of a meal about your JavaScript framework when all you need is a snack.

Recent Features

Incredible Demos

Discussion

  1. Bartek

    Nice to read about another JS library.
    Could you eplain why you’ve put ! before function? Just for fun?

    • Great question Bartek. Adding “!” before the function declaration instructs the parser to execute the function immediately. It’s very much like:

      (function() { /* do stuff here */ })();
      

      Using “!” saves characters.

    • ! forces a function expression which can then be immediately called.

      function (){ ... }() is a syntax error whereas:
      !function (){ ... }() is not

  2. rajkamal

    ! in javascript is similar to !important in css :-)

  3. Mat

    Hi David,
    why didn’t you send the code as pull request to Snack repository?

    It would be helpful to manage and further update the code.

  4. Mat

    Maybe you could at least post the code in one piece? Or as separate github repository? :) It would be helpful for developers trying to abandon jQuery in favor of something lighter.

    Thanks.

Wrap your code in <pre class="{language}"></pre> tags, link to a GitHub gist, JSFiddle fiddle, or CodePen pen to embed!