No-framework JavaScript

Recently I’ve been stripping out jQuery and Vue from a page I’d been working on. Frameworks can be really useful, but when not utilised fully it’s worth asking whether importing an entire library or framework is necessary. In this post, I’ll share some basic ideas using native vanilla JavaScript.

var getJsonpResponse = function(url, params, callback, appender = document.querySelector('head')) {

// Create <script> object
var script = document.createElement('script');

// build the url with params
script.src = (function(url, params = {}) {

// e.g. params.callback=jsonpcallback_hjhz7
if (typeof params.callback === "undefined") params.callback = "jsonpcallback_" + Math.random().toString(36).substring(7);

// generate param string e.g. n1=v1&n2=v2
var paramString = Object.keys(params).map((key) => {
return key + '=' + encodeURIComponent(params[key]);
}).join('&');

// affix urlParams string on the end of url
if (paramString) {
if (url.indexOf("?") > 0) { // contains ?
let reversed = url.split("").reverse().join("");
if (url.indexOf("?") === 0 || url.indexOf("&") === 0) {
url += paramString;
} else {
url += "&" + paramString;
}
} else {
url += "?" + paramString;
}
}

return url;

})(url, params);

// handler callback
window[params.callback] = function(data) {

// developer defined callback
callback(data);

// tidy up!
// allow the function to be garbage collected, tidy up window, and
// remove the script from the dom
window[params.callback] = null && delete window[params.callback];
script.parentNode.removeChild(script);
};

// append the script to run it
appender.appendChild(script);

};

Making JSONP requests

I find JSONP really useful when querying APIs to get around CORS issues. CORS provides important protection against Cross-Site Request Forgery attacks and depending on how sensitive your data is, you might not want to permit cross domain access to API resources. However, there are times when fetching non-sensitive data that it can be a pain.

Below is a function I wrote to handle JSONP API requests:

var getJsonpResponse = function(url, params, callback, appender = document.querySelector('head')) {

// Create <script> object
var script = document.createElement('script');

// build the url with params
script.src = (function(url, params = {}) {

// e.g. params.callback=jsonpcallback_hjhz7
if (typeof params.callback === "undefined") params.callback = "jsonpcallback_" + Math.random().toString(36).substring(7);

// generate param string e.g. n1=v1&n2=v2
var paramString = Object.keys(params).map((key) => {
return key + '=' + encodeURIComponent(params[key]);
}).join('&');

// affix urlParams string on the end of url
if (paramString) {
if (url.indexOf("?") > 0) { // contains ?
let reversed = url.split("").reverse().join("");
if (url.indexOf("?") === 0 || url.indexOf("&") === 0) {
url += paramString;
} else {
url += "&" + paramString;
}
} else {
url += "?" + paramString;
}
}

return url;

})(url, params);

// handler callback
window[params.callback] = function(data) {

// developer defined callback
callback(data);

// tidy up!
// allow the function to be garbage collected, tidy up window, and
// remove the script from the dom
window[params.callback] = null && delete window[params.callback];
script.parentNode.removeChild(script);
};

// append the script to run it
appender.appendChild(script);

};

State management and updating the DOM on change

Frameworks such as React and Vue will maintain state of an app and, when state updates, makes updates to the DOM and such. Essentially this is an observer pattern where “observers” are notified when the subject or state is altered.

class StateManager {

    constructor() {

        this.stateChangeHandlers = [];

        // when we update state, we notify observers (e.g. templates)
        this.state = {};

    }

    /**
     * This is to update state and automatically call the state change handler
     * @param values {object}
     */
    setState(values = {}) {

        // update state
        this.state = Object.assign(this.state, values);

        // notify handlers
        this.stateChangeHandlers.forEach(handler => {
            handler(this.state);
        });

    }

    /**
     * Register a handler for when state changes
     * @param mixed {args,...} (key, handler) or (handler)
     */
    onStateChange(handler) {

        this.stateChangeHandlers.push(handler);

    }
}


var state = new StateManager();

// here is where we set handler to fire when state changes 
state.onStateChange(function(state) {
    document.getElementById('app').innerHTML = "State changed: " + JSON.stringify(state);
});

state.setState({
    name: "Martyn"
});

Letting go of jQuery

jQuery was a blessing when it arrived especially when browser incompatibility was rife (IE6, ugh!). Standards have come along way and there are so many native methods to do things such as DOM selection, looping, etc than before. By all means, continue to use jQuery if you want as there is tons of useful functionality there. For me though, I’d rather not use it unless it’s really neccessary and ave manage to remove it from a few sites and not be so dependent on it.

I won’t repeat what’s already out there, but I’ll leave this link which I found useful when dropping jQuery from a site:

http://youmightnotneedjquery.com/