SkillAgentSearch skills...

Primish

Tiny OOP JavaScript Class constructor sugar in the style of MooTools

Install / Use

/learn @DimitarChristoff/Primish
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

primish

A prime derivative that went beyond repair. Initially a fork of MooTools prime, now with a lot of sugar.

Why fork prime in the first place? Although prime is very good, it has been two years in the making and is not officially released yet. It's also written for CommonJS and it needs a fair amount of packaging, bundling of plugins and behaviours in order to make it work in a browser (via browserify or wrapup). It also tries to provide its own utilities methods and helpers, something that a lot of people have solved via lodash or underscore. It also does not try to keep the MooTools Class API / features. Primish does not only fix the module packaging (UMD!), there are considerable changes in code to make it more Classy and it is bower-ready and available on cdnjs.com for immediate use. At little over 4K in size, you get a lot of mileage for your money if you like classic OOP style code in your JavaScript.

NPM version

Differences

prime changes

  • forked from before the new prime types and object mixins.
  • .parent()
  • .implement() and implement mutator, like mootools. not mixin
  • extend, not inherits
  • prime.merge() shallow Object merging
  • object keys of constructor object are NOT de-referenced / cloned
  • only options objects are automatically de-referenced like in MooTools, other objects will point to prototype and will be mutable
  • extras from prime like utils, shell, type, etc have all been removed, recommended util library is lodash.
  • primish classes can have IDs for reflection like in AMD.

emitter changes

  • .emit is actually .trigger, so it's not an emitter as such :)
  • no support for defered async events (see this)
  • support for event stacking like .on('foo bar baz', function(){});
  • support for event pseudos like .on('foo:once', function(){});
  • emitter.definePseudo() to allow custom pseudo events

options

  • .setOptions() - shallow merging of object with this.options
  • support for emitter events via onEventname -> this.on('eventname') like in MooTools 1.x

Browser support

The main driving force behind primish is to change prime to work in a browser out of the box as well as under nodejs. This fork changes the code to work via an UMD wrap w/o any dependencies, so it supports AMD (eg. RequireJS, Almond) as well as simple browser exports to globals. If you don't have an AMD loader and not under NodeJS / browserify, it will export window.primish, window.emitter and window.options, so be careful. Another goal has been to bring as much MooTools 1.x sugar into classes as possible.

### Size and download

The minified packaged version weighs just 4.2K without gzipping, so a tiny footprint in any codebase.

<a class="btn btn-large btn-primary" rel="download" target="_blank" href="./js/primish/primish-min.js">primish-min.js (4.2k)</a>

Changelog

  • 0.3.9 safer hasOwnProperty checks
  • 0.3.8 performance optimisations for primish/emitter
  • 0.3.7 tweaks for bower consumption
  • 0.3.6 perf fixes for emitter.trigger, tiny doc changes, jsdoc notation
  • 0.3.5 dereferencing this.options from protos, export of primish.clone
  • 0.3.4 fixed AMD module IDs for build to work better with bundles
  • 0.3.3 requirejs 2.1.10 compatible bundles support via module ids
  • 0.3.2 requirejs uglify2 build

Testimonials

kentaromiura, mootools-core and mootools-prime developer said:

I guess that when you said I'll go and make my own version of prime with, blackjack and hookers you really meant it

our own

Creating a Class

To create a new Class, you simply need to do:

// under AMD
require(['primish/primish'], function(primish){

	var Human = primish({
		setName: function(name){
			this.name = name;
		},
		getName: function(){
			return this.name;
		}
	});

	var Bob = new Human();
	Bob.setName('Bob');
	console.log(Bob.getName()); // 'Bob'

});

You can also add a constructor method on your config object to run automatically:

require(['primish/primish'], function(primish){

	var Human = primish({
		constructor: function(name){
			name && this.setName(name);
		},
		setName: function(name){
			this.name = name;
		},
		getName: function(){
			return this.name;
		}
	});

	var Bob = new Human('Bob');
	console.log(Bob.getName()); // 'Bob'

});

For node / CommonJS:

var primish = require('primish'),
	options = require('primish/options');

var Human = primish({
	implement: [options],
	constructor: function(options){
		this.setOptions(options);
	}
});

var Bob = new Human({name: 'Bob'});
console.log(Bob.options.name); // 'Bob'

Here is an example that will make the name property readonly and example private variables

require(['primish/primish'], function(primish){

    var Human = (function(){
        var storage = {},
            hid = 0;

        var Human = primish({
            constructor: function(name){
                this.$hid = hid++;
                storage[this.$hid] = {};
                // disallow changes to human id
                primish.define(this, '$hid', {
                    writable: false,
                    enumerable: false
                });

                primish.define(this, 'name', {
                    configurable: false,
                    get: function(){
                        return this.getName();
                    }
                });

                name && this.setName(name);
            },
            setName: function(name){
                storage[this.$hid].name = name;
            },
            getName: function(){
                return storage[this.$hid].name;
            }
        });

        return Human;
    }());

    var Bob = new Human('Bob'),
        Greg = new Human('Greg');

    console.log(Bob);
    console.log(Bob.getName()); // 'Bob'
    console.log(Bob.name); // 'Bob'
    Bob.name = 'Robert'; // nope, should not change.
    console.log(Bob.name); // 'Bob'
    Bob.$uid = Greg.$uid; // try to puncture Greg's storage
    console.log(Bob.name); // 'Bob'

});

What happens behind the scenes? prime accepts a single argument as a config object. The object is a simple JavaScript Object - with special keys (also referred to mutator keys).

A mutator key is a key:value pair that has a special meaning and is used differently by the Class constructor. The following keys in your config object are considered mutator:

constructor

The constructor method in your config object is what becomes the prime constructor. It runs automatically when you instantiate and can accept any number of arguments, named or otherwise.

require(['primish/primish'], function(primish){
	// have an element
	var div = document.createElement('div');
	div.setAttribute('id', 'myWidget');
	document.body.appendChild(div);

	var Widget = primish({
		options: {
			title: 'My Widget'
		},
		constructor: function(el, options){
			this.element = document.getElementById(el);
			if (options && Object(options) === options){
				this.options = options;
			}
			this.element.innerHTML = this.options.title;
		}
	});

	var instance = new Widget('myWidget', {
		title: 'Cool Widget',
		height: 300
	});

	console.log(instance.options.title); // 'Cool Widget'
	console.log(instance.element.innerHTML); // 'Cool Widget'
});

class IDs

Primish also supports Class IDs (for 'reflection') - similar to AMD's module IDs. The first argument can be an optional string ID, which can then be accessed via instance._id. When possible, these are added via Object.defineProperty and are not enumerable.

require(['primish/primish'], function(primish){

	var User = primish('Admin.User', {
		constructor: function(){
			console.log(this._id);
		}
	});

	var instance = new User();
	console.log('It looks like the instance is ' + instance._id);
});

Caveat: if your super Class has an ID but your subclass does not, it will still resolve this via the prototype chain and may incorrectly identify your instance as the parent. Make sure you use IDs recursively if you need them.

extend

The special key extend defines what SuperClass your new Class will inherit from. It only accepts a single argument, pointing to another Class. The resulting new Class definition will have its prototype set to the SuperClass and inherit any of its static properties and methods via the scope chain.

This allows you to abstract differences between Classes without having to repeat a lot of code.

require(['primish/primish'], function(primish){
	var Rectangle = primish({

		constructor: function(width, height){
			return this.setWidth(width).setHeight(height);
		},

		setWidth: function(width){
			this.width = width;
			return this; // allow chaining
		},

		setHeight: function(height){
			this.height = height;
			return this;
		},

		squareRoot: function(){
			return this.height * this.width;
		}

	});

	var Square = primish({

		// subclass of Rectangle
		extend: Rectangle,

		constructor: function(side){
			return this.setSide(side);
		},

		setSide: function(side){
			// both sides are the same
			this.width = this.height = side;
			return this;
		},

		setWidth: function(width){
			return this.setSide(width);
		},

		setHeight: function(height){
			return this.setSide(height);
		}

	});

	var square = new Square(30);
	square.setWidth(5); // local
	console.log(square.height); // 5
	console.log(square.squareRoot()); // from parent proto of Rectangle, 25
});

Changes to the parent Class are also reflected in the child Class by inheritance (unless the child has a local implementation). This differs from when you use the [implement]

Related Skills

View on GitHub
GitHub Stars19
CategoryDevelopment
Updated4y ago
Forks2

Languages

JavaScript

Security Score

75/100

Audited on Sep 12, 2021

No findings