blainehansen

Old Meteor Toolbelt Package

Before the simple-schema and autoform packages appeared in the Meteor ecosystem, I cobbled together a package to automate form validation, among other things.

published: November 1, 2015 - last updated: May 13, 2024

Form validation is one of the most important but also most tedious and uninspiring of all web development tasks. It's absolutely essential to ensure a good user experience and data integrity, but oftentimes it simply involves a lot of repetitive boilerplate and incredibly dry (but not DRY) jquery. The simple-schema (opens new window), collection2 (opens new window), and autoform (opens new window) trio of Meteor packages make the process absolutely seamless, you define a data schema once, attach it to your database collection, and all validation, even on the client side, is completely handled. And there's the added option of using forms automatically generated with the schema as a guide.

However, before these packages hit the scene, I still wanted to make form validation automated to protect my sanity, and this incomplete package is what I put together before I heard the news of those packages' arrival.

It doesn't only handle form validation. I was building it to be a general-purpose home of many common tasks, essentially all of which were eventually replaced by other packages in the ecosystem or by different patterns I adopted when new features appeared.

This package is a perfect example of the unfortunately overlapping work done by members of a bleeding edge development community, and it especially shows how the less experienced members of that community, especially ones that are pursuing it more casually rather than with business interests in mind, often do work that quickly becomes obsolete.

I'm reasonably proud of the work I did here, since even though it did have some notable flaws and fail to notice more appropriate patterns and abstractions, it shows my continual dedication to staying DRY and abstracting away the tedious and uncreative work that can prevent projects from reaching their full potential.

The package had one exported Belt symbol, a container for all of it's different sub-modules. I made this choice because I didn't want to pollute the global context and namespace with too many symbols, but in the advent of the new import architecture (opens new window) of Meteor projects, this definitely becomes the wrong choice. If I rebuilt this package today, I would export several different symbols representing each module of functionality.

The sub-modules were:

§ General purpose functions defined on Belt itself.

These mostly included three functions related to dynamically looking up a Meteor Collection by name, and the tag function which allowed me to store state associated with individual DOM elements by giving them a unique tag that could represent them in the Session variable. I stopped using this pattern after the ability to store ReactiveVars on Template instances became a much better substitute.

Belt = {
	tag: function (token, obj, attr) {
		attr = attr || '_id'
		return token + '' + obj[attr];
	},
	collectionList: {'users': Meteor.users},
	addCollection: function (name, collection) {
		this.collectionList[name] = collection;
	},
	collection: function (name) {
		return this.collectionList[name];
	}
};

This system has an important flaw, in that it requires the programmer to register their collections with the Belt.addCollection function. It's a task that only requires one line per collection, but it's still not desirable.

Now the ability to look up Collections by name is handled by the dburles:collection-instances (opens new window) package, without the need to register your collections, so I stopped using Belt for that.

I'm not terribly satisfied with that dburles package though, since it isn't aware of the programmatic names assigned to the collections by the programmer, instead only relying on the Mongo name. For example, MyBooks = new Mongo.Collection('books'); creates the possibility to look up that collection both as MyBooks and books, but the dburles package only works with books. Looking on the global window object is one possibility for finding those global symbols, as I did with my smartstarter project's editable templates: window[t.data.contextCollection]. It's possible dburles made that decision intentionally to make the API more stable and prevent programs from randomly reaching into the global namespace. I ought to reach out and ask.

§ Session variable shorthands.

Since I was using the aforementioned Belt.tag to track state attributes of many DOM elements and Templates in the Session variable, I needed shorthands to make those all easier to deal with. As I've already mentioned, this was a poor way of handling state for these elements, but it was also fairly unavoidable at the time.

Belt.session = {
	erase: function (variables) {
		_setSession(variables, undefined);
	},
	tagErase: function (token, obj, attr) {
		_tagSetSession(token, obj, attr, undefined);
	},
	on: function (variables) {
		_setSession(variables, true);
	},
	// ...
	pluck: function (variables) {
		var obj = {};
		variables.forEach(function (item) {
			obj[item] = Session.get(item);
		});
		return obj;
	},
	reap: function (variables) {
		var arr = [];
		variables.forEach(function (item) {
			arr.push(Session.get(item));
		});
		return arr;
	},
	// ...
	act: function (variables, action) {
		variables.forEach(function (item) {
			// action should have two parameters, key and value.
			action(item, Session.get(item));
		});
	},
	every: function (variables, predicate) {
		var result;
		if (predicate === undefined) {
			result = _.every(variables, function (item) {
				return Session.equals(item, true);
			});
		}
		else if (typeof predicate !== 'function') {
			// Add support for array or object predicates.
			result = _.every(variables, function (item) {
				return Session.equals(item, predicate);
			});
		}
		else {
			var values = [];
			variables.forEach(function (item) {
				values.push(Session.get(item));
			});
			result = _.every(values, predicate);
		}
		return result;
	}
};

§ "Running the Gauntlet" and Formatting.

The package had two sub-modules named gauntlet and format that were both concerned with validating form inputs. gauntlet was largely for internal usage, and had a series of functions focused on validating a certain quality of an input. Here are three of them along with the run function used to call a succession of them:

Belt.gauntlet = {
	run: function (tests, input) {
		var give = {value: input};

		give.passed = tests.every(function (tester) {
			try {
				give.value = tester(give.value);
				return true;
			}
			catch (error) {
				give.message = error;
				return false;
			}
		});

		return give;
	},
	_existence: function (input) {
		input = input.trim();
		if (input == '' || input == undefined || input == null) throw "Doesn't Exist";
		return input;
	},
	_hasNumber: function (input) {
		input = parseFloat(input)
		if (isNaN(input)) throw "Isn't a Number";
		return input;
	},
	_pureNumber: function (input) {
		input = input.trim();
		var originalLength = input.length;
		input = parseFloat(input);
		if (originalLength != input.length) throw "Isn't Purely a Number";
		return input;
	},
	// ...
};

format was simply used for shorthands of several gauntlet checks together, such as this:

Belt.format = {
	// ...
	realPrice: function (input) {
		var g = Belt.gauntlet;
		return g.run([g._existence, g._hasNumber, g._realPrice], input);
	},
	// ...
};

Overall fairly simple. The addition of Meteor's check library (opens new window) introduces a more natural way of doing this, and simple-schema of course uses that library to great effect. I was apparently stuck in underscore.js (opens new window) thinking, and wanted to pass anonymous functions to do everything, rather than simply providing objects to be matched against.

§ Functions to quickly gather and check Template values.

Belt.template provided every(), same(), and equals() to be used to quickly check if a list of Template attributes were all true, were all the same, or equal to each other, respectively.

§ Functions to perform automatically validated database interactions.

Belt.base gave update(), insert(), and method() functions that were wrappers of the same Meteor functions, but that used Belt.gauntlet tests to validate inputs before doing so.

Again, the collection2 package accomplishes this nicely when combined with simple-schema.

Two other functions were included. reap() was essentially a wrapper for Collection.find() but that provided a falsey value if the results were empty (that behavior has since become the default in Meteor). And reduce() was a function that reduced the results of a query to a single value, which is actually still behavior not directly supported by Meteor, though it is by MongoDb.

§ Functions to more quickly deal with Templates.

Three functions used to add created, rendered, and destroyed callbacks to an array of Templates are provided, which are made unnecessary by the advent of the aldeed:template-extension (opens new window) that allows for inheritance between Templates, among other things.

Then a state() method generalizes the ReactiveVar-style state maintenance pattern I began using. This is also made unnecessary by the template-extension package, since it allows Templates to inherit from one another.

§ Summary

This package became completely obsolete very quickly, and was replaced by an assortment of packages that all did the tasks better than it did. But I look back at this fondly, since it shows that I had my head in the right place, and was seeing the same problems and needed improvements that other smart people in the Meteor community were seeing. Almost all of the ideas I had were implemented in some way by someone else, and those packages became very popular, even essential. This proves that the problems I was envisioning were important and needed to be solved well. I just wasn't serious enough or experienced enough at the time to actually get there first. And it didn't help that I was trying to solve all of those problems at the same time with a single package.

All in all though, this package is something I'm proud of. As always, I was trying to be the barrel not the bullet (opens new window), and that habit has continued to pay off.

Want to hear from me in the future?

Usual fine print, I won't spam you or sell your info.
Thank you! Come again.