Showing posts with label JavaScript. Show all posts
Showing posts with label JavaScript. Show all posts

Thursday, August 27, 2015

Simplistic JavaScript dependency injection with ES6 destructuring

Recently I got a bit tired with Angular's quirks and intricacies. To freshen up, I'm playing with framework-less JavaScript (Vanilla JS). I'm also getting more and more used to ES6 features. One of the outcomes by now is the idea for the Dependency Injection approach that stays simplistic, decoupled from any framework and still convenient to consume.

Destructuring

One of the features I like most in ES6 is destructuring. It introduces a convenient syntax for getting multiple values from arrays or objects in a single step, i.e. do the following:

let [lat, lng] = [54.4049, 18.5763];
console.log(lat); // 54.4049
console.log(lng); // 18.5763

or like this:

let source = { first: 1, second: 2 };
let { first, second } = source;
console.log(first, second); // 1, 2

What is even nicer, it works fine in a function definition, too, making it a great replacement for the config object pattern, where instead of providing the large number of parameters, some of them potentially optional, we provide a single plain configuration object and read all the relevant options from the inside the object provided. So, with ES6 destructuring (+default parameters support), instead of this:

function configurable(config) {
    var option1 = config.option1 || 123;
    var option2 = config.option2 || 'abc';
    // the actual code starts here...
}

we can move all that read-config-and-apply-default-if-needed stuff directly as a parameter:

function configurable({ option1 = 123, option2 = 'abc' }) {
    // the actual code from the very beginning...
}

The code is equivalent and the change doesn't require any changes at the caller side.

Injecting

We can use destructuring to provide Angular-like experience for receiving the dependencies by a class or a function that is even more cruft-free as it's minification-safe and thus doesn't require tricks like ngAnnotate does.

Here is how it can look from the dependencies consumer side:

function iHaveDependencies({ dependency1, dependency2 }) {
    // use dependency1 & dependency2
}

Whenever we invoke the iHaveDependencies function, we need to pass it a single parameter containing the object with dependency1 and dependency2 keys, but possibly also with others. Nothing prevents us from passing the object with all the possible dependencies there (a container).

So the last thing is to ensure we have one available whenever we create the objects (or invoke the functions):

// possibly create it once and keep it for a long time
let container = { 
  dependency1: createDependency1(),
  dependency2: createDependency2(),
  dependency3: createDependency3(),
  otherDependency: createOtherDependency() 
};

// use our "container" to resolve dependencies
iHaveDependencies(container);

That's all. The destructuring mechanism will take care of populating dependency1 and dependency2 variables within our function seamlessly.

We can easily build a dependency injection "framework" on top of that. The implementation would vary depending on how our application creates and accesses the objects, but the general idea will hold true. Isn't that neat?

PS. Because the lack of direct ES6 support in the browsers, running that in a browser right now requires transpiling it down to ES5 beforehand. Babel works great for that.

This post was originally posted on my company blog.

Wednesday, November 5, 2014

Attaching ShareJS to <select> element

One thing that I found missing in ShareJS library was the possibility to attach live concurrent editing to HTML <select> element. Out of the box it works only with text fields - <input> and <textarea> using doc.attachTextarea(elem) function.

Working around that deficiency wasn't so trivial. ShareJS works with Operational Transformations that extracts each logical change to the text (addition or removal) and sends only the change information over the wire. It is great for textual elements, but for <select>, whose value is replaced always in one shot, it makes a little sense.

Unfortunately, there is no "replace" operation we could use on <select> value change - the modus operandi we have to live with is constrained to insertions and removals. It means we have to mimic "replace" operation with removal and insertion. The problem with this approach is that when the operations get reversed - so that the client receives new value insertion first and then removal of the previous value - the intermittent value in-between is not a valid <option>. It is a concatenation of old value and new value. DOM API doesn't like that and rejects that change, setting the <select> value to empty one. The removal operation that comes next is then unable to fix the value as it tries to remove something from already empty string in DOM.

I have worked around that wrapping my DOM element with a tiny wrapper that keeps the raw value and exposes it for ShareJS transformations while still trying to update the original element's DOM:

var rawValue = innerElem.value;
var elem = {
    get value () {
        return rawValue;
    },
    set value (v) {
        rawValue = v;
        innerElem.value = v;
    }
};

ShareJS also doesn't attach itself to change event, typical for <select> element - it specializes in keyboard events. So I have to attach on my own and rely the event to the underlying ShareJS implementation, faking the event of type that is handled by the library - I've chosen the mysterious textInput event.

Here is the full code as Gist: ShareJS attachSelect. It adds a new function to the Doc prototype, allowing calling it in the same way we're calling ShareJS native attachTextarea:

if (elem.tagName.toLowerCase() === 'select') {
    doc.attachSelect(elem);
} else {
    doc.attachTextarea(elem);
}

Feel free to use the code, I hope someone finds that useful.

This post was originally posted on my company blog.