Simple two-way binding without a JavaScript framework

I love JavaScript. I hate JavaScript tooling and frameworks. I hate the fact that everything changes in a matter of months.

Don’t get me wrong. Big frameworks are needed, of course, to bring standardization and uniformity to big projects. I can’t imagine how more complex a project like Oxygen Builder would be if it was built without Angular or any other framework.

But I can’t stand when people use powerful frameworks to build minimal projects when all they needed is plain vanilla JavaScript.

Also, I don’t hate things. I dislike them.

My face turns red of dislike 🙂 when seeing a single-page personal site built with Next.js. But who am I to criticize? I’m the guy using a full-blown blogging suite and dev platform just to write an occasional paragraph.

Perhaps what people really need are just one o two of the framework features. In my case, what I normally miss from big frameworks when working on a simple site/page/feature are either routing, templating, or view data binding. Routing and templating it is stupidly simple to implement with standard JavaScript and current browser APIs. Data binding too, but somehow people I’ve talked to about see this as black magic, so I’m writing a quick sample to illustrate how easy it is.

Once, there was a promising watch method in JavaScript similar to the $watch in AngularJS, but never became standard and no browser implemented it. With that, object-to-view bindind would be ridiculously easy, but still, there is a super-easy way to accomplish the same using getters and setters:

(function () {
	var elements = document.querySelectorAll("[data-bind]");
	window.scope = {};
	// Bind scope.prop to element
	function addScopeProp(prop) {
		// No duplicated properties
		if (!scope.hasOwnProperty(prop)) {
			// Value to populate with newvalue and persist in this object
			var value;
			Object.defineProperty(scope, prop, {
				set: function (newValue) {
					value = newValue;
					elements.forEach(function (element) {
						// Change value to binded elements
						if (element.getAttribute("data-bind") === prop) {
							if (
								element.type &&
								(element.type === "text" ||
									element.type === "textarea")
							) {
								element.value = newValue;
							}
						}
					});
				},
				get: function () {
					return value;
				},
				enumerable: true,
			});
		}
	}

	// Init
	elements.forEach(function (element) {
		// Only text and textarea input elements
		if (element.type === "text" || element.type === "textarea") {
			// Bind element > scope
			var prop = element.getAttribute("data-bind");
			element.onkeyup = function () {
				scope[prop] = element.value;
			};
			// Bind scope > element
			addScopeProp(prop);
		}
	});
})();

Place the above code on any HTML page and you’ll be ready to bind any Text or Textarea fields to properties of the “scope” global object.

Sample usage (see it live here):

<textarea cols="80" rows="25" data-bind="message"></textarea>
<script type="text/javascript">
	scope.message = "This is a test.";
	setTimeout(function(){
		scope.message += " This message will appear after 4 seconds...";
	},4000);
	setTimeout(function(){
		scope.message += "and then this last message will appear.";
	},10000);
</script>

This simple code is good for basic stuff, but quickly more advanced needs will arise, requiring, for example, binding arrays to views via for-loops or binding other element properties like its visibility or classlist. For those cases, the solution I always chose is tinybind. Tinybind is basically all you need to stop thinking about using a full-blown framework. It’s what I’m using for the next generation OxyPowerPack 3.0 settings pages. It allows you to create mid-to-high complexity projects and still feel you own 100% of the code.