3
\$\begingroup\$

Based on the feedback and suggestions I received in my previous post (Lightweight shorthand JS library), I've updated and optimized Mozart JS.

Again, the goal is to have some jQuery-like functionality in a minimal size. Thoughts on this updated version?

Full library on GitHub.


Mozart JS

Element Retrieval

Shorthand for document.querySelectorAll or document.querySelector. My decision to include both is just personal preference.

function $get(input) {
    return document.querySelectorAll(input);
}
function $grab(input) {
    return document.querySelector(input);
}

Example:

var foo = $grab('#bar'); //returns element with the Id of "bar"
var zed = $get('.cop'); //returns collection with the classname "cop"   

Element Creation

Shorthand for document.createElement and Element.setAttribute. Though text is not an attribute, I chose to allow users to set textContent as well, similar to jQuery.

function $make(tag, params) {
    var obj = document.createElement(tag);
    for (var key in params) {
        if (key === 'text') {
            obj.textContent = params[key];
        } else {
            obj.setAttribute(key, params[key]);
        }
    }
    return obj;
}

Example:

var g = $make('button', {
    style:'float: left',
    class:'myClass',
    text:'Some text here',
    value:'myValue'
});

//Equivalent to:

var g = document.createElement('button');
g.setAttribute('style','float: left');
g.setAttribute('class','myClass');
g.textContent = 'Some text here';
g.setAttribute('value','myValue');

Element Processing

This function is not meant to be called outside the library. It ensures that data is in an iterable format that can be processed by the insertion and removal methods.

//turn something into a set
function $set(input) {
    var set;
    if (typeof input === 'string' || input instanceof String) {
        set = $get(input);
    } else if (input.nodeType === 1 || input.nodeType === 3) {
        set = [input];
    } else if (input[Symbol.iterator] !== undefined) {
        set = input;
    } else {
        throw new TypeError('Cannot convert ' + typeof input + ' to iterable');
    }
    return set;
}   

Element Sibling Insertion

Designed to mimic most of jQuery's insertBefore() method. I chose to allow only one element inserted at a time, but that element can be inserted multiple times. I also chose to clone the element for all but the first insertion so that event listeners would be preserved for the first one. There may be a better way to do this, though, so I'd appreciate suggestions.

//equivalent function $addAfter() not shown here
function $addBefore(input, target) {
    var newElm = $set(input)[0];
    var oldElms = $set(target);
    if (oldElms[0]) {
        var firstTarget = oldElms[0];
        firstTarget.parentNode.insertBefore(newElm, firstTarget);
        for (var i = 1; i < oldElms.length; i++) {
            var oldElm = oldElms[i];
            var clone = newElm.cloneNode(true);
            oldElm.parentNode.insertBefore(clone, oldElm);
        }
    }
}

Example:

var foo = $make('div', {'text': 'This is a test'});
$addBefore(foo, '.myClass'); //adds an instance of foo before each myClass

Element Removal

Removes an element or collection of elements.

//element removal
function $remove(input) {
    var rem = $set(input);
    for (var i = 0; i < rem.length; i++) {
        var elm = rem[i];
        elm.parentElement.removeChild(elm);
    }
}

Example:

$remove('#myId');
$remove('.myClass');

//Equivalent to:

var rem = document.querySelectorAll('#myId');       
rem.parentNode.remove(rem); 

var myClassColl = document.querySelectorAll('.myClass');
for (var i = 0; i < myClassColl.length; i++) {
    var elm = myClassColl[i];
    elm.parentNode.remove(elm);
}
\$\endgroup\$
1
\$\begingroup\$
function $get(input) {
    return document.querySelectorAll(input);
}
function $grab(input) {
    return document.querySelector(input);
}

If I would like to get DOM elements from the page, its overhead to think about having 2 ways to get them and having 2 possible values to deal with. Best if you just deal with sets. If you just need one, it's as simple as the grabbing the set, and get one. No extra code needed.

var justOneDiv = $get('div')[0];

Speaking of sets and array-like objects, querySelectorAll returns a NodeList. It's like an array, but without the array methods. What you could do is to convert the NodeList into a real array. That way, you have instant access to array methods. This could be done with array.slice.

function toArray(arrayLike){
  return Array.prototype.slice.call(arrayLike);
}

function $get(selector){
  return toArray(document.querySelectorAll(selector));
}

// We have instant access to forEach
$get('div').forEach(function(div){
  div.style.color = 'red';  
})

// We can use filter
$get('div').filter(function(div){
  return ~toArray(div.classList).indexOf('bold');
}).forEach(function(div){
  div.style.fontWeight= 'bold';  
});
.red{
  color: red;
}
<div>Red</div>
<div class="bold">Red</div>
<div>Red</div>

Now we're almost like jQuery but jQuery takes it a bit further. It has the concept of a "jQuery object" which is a custom object that holds the set we're interested in, and has a prototype containing all the methods that operate on that set.

function $(selector){
  if(!(this instanceof $)) return new $(selector);
  this.set = this.find(selector);
}

$.prototype.find = function(selector){/* code that finds elements */};
$.prototype.insert = function(something){/* code that inserts something */};
$.prototype.each = function(callback){ this.set.forEach(callback} };

var div = $('div').each(function(div){/* do something */});

The above is a contrived example of how jQuery works. Adding methods is as easy as adding to the prototype (jQuery exposes it to the world as $.fn). This is how jQuery is so extensible in addition to just exposing one object to the global namespace.

var g = $make('button', {
    style:'float: left',
    class:'myClass',
    text:'Some text here',
    value:'myValue'
});

Still, text is the odd one here. Text are separate nodes, totally different from the element itself. They're essentially "children" of the element and not part of the element. I'd suggest you do something similar to jQuery's text() method. value is different because it is a legit property of some inputs.

Also, all your functions reside in the global scope. You risk clobbering other methods of the same name, or risk yours getting overridden. I suggest you take up just one name in the global scope and nest your methods under it. Mozart would be a good name.

var Mozart = {
  $get: function(){...},
  $remove: function(){...},
  ...
};

var divs = Mozart.get('div');
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.