Menu toggle
Fork me on GitHub

agave.js

Cleaner, simpler JavaScript for ES8

Build Status

Agave.js safely extends native JavaScript objects with useful things you'll use every day.

What does Agave provide?

Remember, all methods are available under the prefix you enabled agave with (if any).

kind

Tells you the closest prototype in the inheritance tree or, for always-primitive types like 'null' and 'undefined', the primitive name. Think of it like a typeof() that does what you expect.

So you can access it easily, kind() is a global (of course, it's prefixed like everything else in Agave).

Numbers

				kind(37) === 'Number'
				kind(3.14) === 'Number'
				kind(Math.LN2) === 'Number'
				kind(Infinity) === 'Number'
				kind(Number(1)) === 'Number'
			
NaN
kind(NaN) === 'NaN'
Strings

				kind('') === 'String'
				kind('bla') === 'String'
				kind(String("abc")) === 'String'
				kind(new String("abc")) === 'String'
			
Booleans

				kind(true) === 'Boolean'
				kind(false) === 'Boolean'
				kind(new Boolean(true)) === 'Boolean'
			
Arrays

				kind([1, 2, 4]) === 'Array'
				kind(new Array(1, 2, 3)) === 'Array'
			
Objects

				kind({a:1}) === 'Object'
				kind(new Object()) === 'Object'
			
Dates
kind(new Date()) === 'Date'
Functions
kind(function(){}) === 'Function'
	kind(new Function("console.log(arguments)")) === 'Function'
	kind(Math.sin) === 'Function'
undefined
kind(undefined) === 'undefined'
null
kind(null) === 'null'

Object methods

The following examples are based on this sample object:


				var mockObject = {
					foo: 'bar',
					baz: {
						bam:'boo',
						zar:{
							zog:'something useful'
						}
					}
				}
			
.getKeys()

Returns an array of the object's keys.

mockObject.getKeys()
			

Returns:

['foo','baz']
			
.getSize()

Returns the number of properties in the object.

mockObject.getSize()
			

Returns:

2
			
.getPath(path)

Provided with either a '/' separated path, or an array of keys, get the value of the nested keys in the object. If any of the keys along the way are missing, return undefined. This is very useful for useful for checking JSON API responses where something useful may be buried deep inside an object, but you don't want to error out if the path doesn't exist. Eg, the following code:

mockObject.getPath('/baz/zar/zog')
			

or, alternatively:

mockObject.getPath(['baz','zar','zog'])
			

will return:

'something useful'

However, checking for a path that doesn't exist:

mockObject.getPath(['baz','gur','yak'])

will simply return:

undefined

Keys, of course, could be strings, array indices, or anything else.

.clone()

Returns a deep clone of the object, so that modifications to the new object will not affect the original.

.forEach(function)

Iterates over the object's keys and values, using the function provided.

.extend(newProperties)

Applies the properties from newProperties object to the existing object.


				mockObject.avextend({
					'gnar':{
						shub:'zoo'
					},
					'gert':{
						yaz:'frub'
					}
				});
			

will return:


				{
					"foo":"bar",
					"baz":{
					"bam":"boo",
					"zar":{
						"zog":"victory"}
					},
					"null":{
						"yarr":{
						"parrot":"ahoy"
						}
					},
					"gnar":{
					"shub":"zoo"
					},
					"gert":{
					"yaz":"frub"
					}
				}
			

Array methods

.extend(newarray)

Adds the items from newarray to the end of this array.

.clone()

Returns a shallow clone of the object.

.remove(member)

Removes member from the array, returning true if the member was found, false otherwise.

.first()

Returns the first item of the array.

.last()

Returns the last item of the array.

.forEachAsync()

An async version of `forEach()`.


				await productNames.forEachAsync(function(productName){
					console.log(`Making product '${productName}'`)
					stripe.products.create({
						name: productName,
						type: 'service',				
						statement_descriptor: productName 
					});
				})	
			

Function methods

.throttle(wait)

Run a function after it hasn't been invoked for 'wait' ms. Commonly used to stop repeated calls to a function overlapping each other (sometimes called 'bouncing').

For example, window.resize() events normally fire repeatedly as a user resizes a window, causing functions that are waiting for the 'resize' event to run at the same time, which can cause problems.


				var updateLayout = function(event) {
					console.log('Updating layout!')
				};

				window.addEventListener("resize", updateLayout.throttle(500));
			

Will wait until the user has stopped dragging the window for 500ms before printing:

Updating layout!
			

on the console.

.repeat(arguments, interval, leadingEdge)

Run the function repeatedly at the interval (in ms). If leadingEdge is true, run the function immediately too.


				var doThing = function(first, second){
					console.log('first', first, 'second', second)
				};

				var interval = doThing.repeat(['red', 'pandas'], 1000, true)
			

Will run doThing() immediately, then every second afterwards.

String methods

.strip(characters)

returns the string, with the specified characters removed from the beginning and end.

'Hello world'.strip('Hld')
			

Returns:

'ello wor'
			
.leftStrip(characters)

returns the string, with the specified characters removed from the beginning.

.rightStrip(characters)

returns the string, with the specified characters removed from the end.

.forEach(iterationFunction)

Runs iterationFunction over each character in the String. Just like ES5's inbuilt Array.forEach().

.reverse()

Returns a backwards copy of the string.

Number properties and methods

Note: use brackets or two dots to use properties and methods on Numbers. See below for examples!

.seconds, .minutes(), .hours(), .days(), and .weeks()

Converts a number into the amount of milliseconds for a timespan. For example:

(5).days
			
or
5..days
			

Returns:

432000000
			

Since 5 days is 432000000 milliseconds.

.before(), .after()

Turns a number (assumed to be an amount of milliseconds) into the Date in the past (using .before) or the future (using .after). You'd typically combine this with .seconds, .hours, .days, and .weeks to easily get a date a certain amount of units in the past or the future. For example:

2..days().before()
			

Returns a Date for 2 days ago, eg:

Tue Jun 04 2013 22:16:50 GMT+0100 (BST)
			

You can also specify a date to be before/after. For example:

var joinedCompanyDate = new Date('Tue Jun 04 2013 1:00:00 GMT+0100 (BST)')
	(3).weeks().after(joinedCompanyDate)
			

Returns a Date for 3 weeks after that date, eg:

Thu Jun 27 2013 22:44:05 GMT+0100 (BST)
			
.round(), .ceil(), .floor(), .abs()

Returns the value corresponding to Math.round(number) etc.


				4.2..round(); // 4
				4.2..ceil(); // 5
				4.2..floor(); // 4

				-4.2..abs(); // 4.2
				-4.2..abs().ceil(); // 5
			
.pow(exponent)

Returns the number multiplied by itself exponent times.

5..pow(3); // 125
			

Date methods

.isOnWeekend()

Returns `true` or `false` depending if the day is a Saturday or Sunday

.daysAgo() or .daysUntil()

Returns the amount of days left, or until, the date


				var future = new Date('2022-03-12')
				future.daysUntil()
			

Return the days until March 12, 2022.

.daysUntil()

Returns the amount of days left, or until, the date


				var future = new Date('2022-03-12')
				future.daysUntil()
			

Return the days until March 12, 2022.

.withoutTime()

Returns the date with the time stripped off - so the time is midnight on that day


				var now = new Date()
				today = now.withoutTime()
			
.clone()

Returns a full copy of the date, so that modification to the original will not affect the clone

Why would I want to use Agave?

Agave will make your code shorter and more readable.

How Does Agave Compare to Underscore.js and Lodash?

How Does Agave Compare to Sugar.js?

Sugar.js is an excellent project and was the inspiration for Agave. Like Sugar, Agave provides useful additional methods on native objects. Here are the the differences:

Concerns

I read that adding methods to prototypes is bad

Agave addresses a number of concerns people have raised over the years since Prototype.JS first began extending built ins. Andrew Dupont's talk at JSConf provides an excellent overview on how the JS community has approached this topic over time.

Q. Will Agave methods appear when iterating over objects?

A. No. Methods will never appear when iterating over objects.

Adding methods to inbuilt objects was bad, back on ES3 browsers like IE8 and Firefox 3 and older. ES3 didn't provide a way for developers to add their own non-enumerable properties to inbuilt objects.

Let's see the problem: open your browser console right now and add a method, the traditional way:

Object.prototype.oldStyleMethod = function oldStyleMethod (){}
		

And make an object:

var myobject = {};
		

Watch what happens when we iterate over the object:

for (var key in myobject) { console.log(key) };
		

You can see the problem: 'oldStyleMethod' shows up as one of myobject's keys. This will break things and is indeed bad.

But wait a sec: Objects already have some methods out of the box. Like toString():

console.log(Object.prototype.toString)
function toString() { [native code] }

console.log(Object.prototype.oldStyleMethod)
function oldStyleMethod(){}
		

Why are only our add-on methods showing up as keys? Why don't the native, inbuilt methods appear in our 'for' loop?

The answer is that inbuilt methods in JavaScript have always been non-enumerable. But in ES3, you never had the ability to make your own non-enumerable methods.

ES5 - the version of JavaScript created in 2009 that Chrome, Firefox, and IE9-11, as well as node.js use - specifically allows for the addition of new non-enumerable properties via Object.defineProperty().

So open a new tab. Let's try again, ES5-style:

Object.defineProperty( Object.prototype, "newStyleMethod", {value: function newStyleMethod(){}, enumerable: false});

for (var key in myobject) { console.log(key) };
		

Hrm, it seems newStyleMethod(), just like toString(), doesn't interfere with our loops.

This is exactly what Agave uses. As a result, Agave's methods will never show up in for loops.

So if you're OK with Agave's requirements - ie, you support current generation browsers and node - you can use Agave.

Q. Future ES versions or other libraries might use the same method names to do different stuff

A. That's why Agave uses prefixes

Another concern may be naming or implementation conflicts - ie, another library or perhaps a new version of ES includes some code that uses the same method name to do something differently. This is why Agave makes you to prefix every method it provides. Just start it with:

agave.enable('av');
		

or any prefix of your choice to have all the methods prefixed with whatever string you like.

Using a prefix is the preferred mechanism for publicly distributed libraries that use Agave.

The prefix can be as short or as long as you like. In my own experience I've found two letters has been enough to avoid conflicts in the last year I've been using Agave, prior to its public release. If you're think this might not be enough to satisfy the need for uniqueness, use a longer prefix. If you're feeling adventurous (and you strongly control the libraries used in your projects) you can use no prefix at all.

Q. There are new methods on my window object!

A. Yes, window is an object. This is how JS works.

Everything's an object in JS, so everything has has object methods. We mentioned object.toString() earlier - there's a window.toString() in your browser, and a global.toString() in Node that JS provides because window and global are objects.

When running agave, the additional methods added to Object.prototype will appear on window and global just like the inbuilt ones. You might find this odd, but it's expected behavior.

You may find this useful - for example, if you wanted to find out whether some deeply nested set of keys exists underneath window, then .getPath() is awfully handy.

It would make things nicer if a future version of JS allowed us to isolate prototypes between modules. But it certainly won't kill us in the meantime if we're using prefixed, non-enumerable methods.

Using Agave

In the browser, on the server, or both:

Just run:


			yarn install agave
		

Then in your code:


			var agave = require('agave');
			// Start Agave, providing a prefix of your choice.
			agave('yourPrefix');
		

All the methods here are now available.

I've got stuff to add!

Awesome. Fork the repo, add your code, add your tests to tests.js and send me a pull request.

Tests

Install node.js, and run:

yarn test

Inside the folder you downloaded Agave to.

What browsers does Agave support?

Any ES8 compatible environment. This includes Chrome, Firefox, Safari, Edge and node.js LTS.

If you still support IE, you can also add support for IE9-11 by using an ES8 polyfill or transpilation.

What about IE8 and Firefox 3 support?

Sorry, but this isn't possible. ES3 browsers like IE8 and Firefox 3 don't support Object.defineProperty() and it cannot be emulated via shims.

License

MIT license.

Author

Mike MacCana (mike.maccana@gmail.com)