"Modular, composable web components. Modular, composable styles."
Gem.js is a view library - a set of extensible web components for building visual user interfaces and styling web applications
in pure-javascript.
The power of functions and variables is unparalleled, and yet languages like HTML and CSS, which don't have any ability to compose
structures together, are still the primary ways people build web applications. Framework after framework has come along to try and
solve the problem of making web pages dynamic. Angular, Backbone, Ember, jQuery, mooTools, Dojo, YUI, etc have all tried to be
everything to everyone. But what they fail at providing is simplicity and modularity. They end up with bloated libraries filled with
features you're not using.
Gem.js is here to change that. Finally, modern application development for the browser!
-
If anything in the documentation is unclear, or you want to see more examples, the unit tests give a comprehensive and exhaustive set of examples to look at.
Why use
The
The Inheriting from Gems with a class library other than
If you're building Gems with something other than

Gem
- [Static properties and methods](#static-properties-and-methods)
- [Instance properties and methods](#instance-properties-and-methods)
- [Event instance properties and methods](#event-instance-properties-and-methods)
- [`ifon`](#ifon)
- [`proxy`](#proxy)
- [Instance events](#instance-events)
- [Dom Events](#dom-events)
- Custom Gems
- [Releasing custom gems as separate modules](#releasing-custom-gems-as-separate-modules)
- [Inheriting from Gems with a class library other than `proto`](#inheriting-from-gems-with-a-class-library-other-than-proto)
- [Inheriting from Gems without a class library](#inheriting-from-gems-without-a-class-library)
- Standard Gems
- [Conventions](#conventions)
- [Button](#button)
- [Canvas](#canvas)
- [CheckBox](#checkbox)
- [Block](#block)
- [Image](#image)
- [List](#list)
- [Radio - Not a `Gem`](#radio---not-a-gem)
- [Select](#select)
- [Svg](#svg)
- [Table](#table)
- [Text](#text)
- [TextArea](#textarea)
- [TextField](#textfield)
- Style
objects
- [`Style` constructor](#style-constructor)
- [`<cssPropertyName>`](#csspropertyname)
- [`$setup` and `$kill`](#setup-and-kill)
- [`$state`](#state)
- [`<GemName>`](#gemname)
- [`$<label>`](#label)
- [`$$<pseudoclass>`](#pseudoclass)
- [`$inherit`](#inherit)
- [Combining them together](#combining-them-together)
- [`styleObject.mix`](#styleobjectmix)
- [`styleObject.copy()`](#styleobjectcopy)
- [`Style.addPseudoClass`](#styleaddpseudoclass)
- [`styleObject.toString`](#styleobjecttostring)
- [`Style.fromString`](#stylefromstring)
- [`styleObject.toObject`](#styleobjecttoobject)
- [Standard Pseudoclasses](#standard-pseudoclasses)
- [Built-in JS Rendered Pseudoclasses](#built-in-js-rendered-pseudoclasses)
- [Default style](#default-style)
- Tips and Recommendations
- [Don't Create a node until you need it](#dont-create-a-node-until-you-need-it)
- Other Gem Modules
Example
=======
```javascript
var Button = require("gem/Button")
var Style = require("gem/Style")
var Block = require("gem/Block")
var list = Block()
;1,2,3.forEach(function(n) {
var text = "Hi "+n
var toggleButton = Button(text) // create a button
// make it do stuff when you click on it
toggleButton.on('click', function() {
if(toggleButton.text !== "RAWRR!!!") {
toggleButton.text = "RAWRR!!!"
toggleButton.state.set('color', 'rgb(128, 0, 0)')
} else {
toggleButton.text = text
toggleButton.state.set('color', 'black')
}
})
// add the button to the list
list.add(toggleButton)
})
var greet = Button("send", "Send your Greetings") // labels like 'send' can differentiate
// otherwise identical types of gems
list.add(greet)
// create styles with style objects ..
list.style = Style({
border: '1px solid blue', // .. that use familiar css values,
marginRight: 34, // .. camelCase css properties and integers interpreted
// as "px" values when appropriate,
Button: { // .. sub-gem styles,
$$firstChild: { // .. pseudo-class styles,
color: 'rgb(0,100,240)',
},
$state: function(state) { // .. more sophisticated styling techniques
return Style({
color: state.color
})
}
},
$send: { // .. style based on an object's label, and ..
color: 'green'
}
})
// append the list of buttons to the document body (so it shows up)
list.attach()
// custom gems (use your favorite javascript class library - here proto is being used)
var NameInput = proto(Gem, function() { // inherit from Gem
this.name = 'NameInput'
this.build = function(LabelText) { // the `build` method initializes the custom Gem
this.nameField = TextField()
this.add(Text(LabelText), this.nameField)
this.nameField.on('change', function() {
list.children[0].text = "Hi "+this.val
})
}
})
var nameInput = NameInput("Your Name: ")
greet.on('click', function() {
var text = Text("Whats up, "+nameInput.nameField.val+'?')
text.style = Style({display:'block'})
text.attach()
})
nameInput.attach()
```

Gem.js
?
====================
- Makes your web application easier to develop with modular reusable structure objects (
Gem
objects) andStyle
objects
- No HTML Needed. With
Gem.js
, you write in 100% javascript. The only html requirement is adocument
body
. You can still add plain old HTML into your gems usinggem.domNode.innerHTML
tho if you so choose.
- No CSS Needed. While Gem.js uses css style properties, it rejects the cascading nature of css, allowing one style to be fully isolated from another. No more wondering which selector in which stylesheet botched your nice clean style.
- Works with your HTML and CSS.
Gems
can be added as a child to any standard dom object and they can be styled with standard css stylesheets if you so choose.
- Works with your current javascript.
Gems
give you direct access to theirdomNode
so you can use the dom manipulation libraries you're used to.
- Fully separate style from structure. By using
$state
,$setup
, and$kill
javascript in yourStyle
objects, you can include any javascript that is stylistic rather than structural.
- Import
Gem
modules with real APIs that anyone can release online. HTML snippets are so 1995.
- Lowers your risk of Cross-Site scripting. Data in Gem.js doesn't need to be escaped, and so you can cross that off your worry-list.
- Unlike HTML web components,
Gem.js
works in modern browsers without polyfills.
- Also unlike HTML web components, element name collision isn't a problem.
- Has a small footprint: 16.5KB minified and gzipped in umd format
Gem.umd.js
from the 'dist' folder in the repository
Usage
=====
```javascript
var Gem = require('gem') // node.js and webpack
define('Gem.umd.js', function(gem) { ... } // amd
```
Gem
------
All gems inherit from Gem
- the basic building-block of the system. Gems are EventEmitters, and emitting events is one of the primary ways gems should communicate.
Gem
is abstract and can't be instantiated on its own. See the section ''Custom Gems'' for details on how to create objects that inherit from Gem.
Static properties and methods
Gem.name
- The name of the Gem. Used both for naming dom elements for view in browser dev tools and for styling.
Gem.attach(listOfGems)
- Appends the passed gems to document.body
. IMPORTANT: only attach a gem to the dom via
this attach
function or a gem's attach
/attachBefore
method. Without this, styles won't be rendered. \
Gem.attach(domNode, listOfGems)
- Appends the passed gems to the passed domNode
. \
Gem.attachBefore(domNode, listOfGems)
- Appends the passed gems before the passed domNode
as a sibling.
Gem.detach(listOfGems)
- Removes the passed gems from their respective dom parents.
Gem.createBody(callback)
- Dynamically creates the body tag. Calls callback
when done.
Gem.dev
- (Default: false) - When set to true, certain debugging feature are enabled.
Instance properties and methods
gem.parent
- The Gem's parent (which will also be a Gem) \
gem.children
- An array of the Gem's children (which will all be Gems themselves). \
gem.domNode
- The Gem's standard dom node object. \
gem.label
- A string used for styling. Should be set once when the object is instantiated, and cannot change. See the section on Style
objects for details about how this is used. \
gem.excludeDomEvents
- A set of dom events to exclude from automatic registration. Will have the structure {eventName1:1, eventName2:1, ...}
. See the documentation for on
for more details. \
gem.state
- An observer object that can be listened on for changes. Can be used for any purpose, but is intended for being used to create dynamically changing styles. See the section on Style
objects for an example.
gem.add(gem, gem, ...)
- Appends gems as children to the calling gem. This causes the domNodes of the passed gems to be appended to the calling gem's dom node. \
gem.add(listOfGems)
- Same as above, but listOfGems
is an array of Gem
objects.
gem.addAt(index, gem, gem, ...)
- Adds gems as children to the calling gem at a particular index. \
gem.addAt(index, listOfGems)
- Same as above, but listOfGems
is an array of Gem
objects.
gem.addBefore(beforeChild, gem, gem, ...)
- Adds gems as children to the calling gem before a particular child. If beforeChild
is undefined, this will append the given nodes. \
gem.addBefore(beforeChild, listOfGems)
- Same as above, but listOfGems
is an array of Gem
objects.
gem.remove(gem, gem, ...)
- Removes the passed gems as children. \
gem.remove(listOfGems)
- Same as above, but listOfGems
is an array of Gem
objects. \
gem.remove(index, index, ...)
- Removes, as children, the gems at the given index
es in the children
list. \
gem.remove(listOfIndexes)
- Same as above, but listOfIndexes
is an array of indexes to remove.
gem.attach(domNode=document.body)
- Appends this Gem
's domNode to the passed domNode (default document.body
). IMPORTANT: only attach a gem to the dom via the attach
function or a gem's attach
/attachBefore
method. Without this, styles won't be rendered. \
gem.attachBefore(domNode=document.body)
- Appends this Gem
's domNode before the passed domNode (as a sibling).
gem.detach()
- Removes this Gem
's domNode from its dom parent.
gem.attr(attributeName)
- Return the value of the attribute named attributeName
on the Gem's domNode. \
gem.attr(attributeName, value)
- Sets the attribute to the passed value
. \
gem.attr(attributeObject)
- Sets the attributes in the attributeObject
, where attributeObject
looks like: {attribute1: value1, attribute2: value2, ...}
.
gem.style
- Holds the object's Style
object. Starts out undefined
, and can be set to undefined
to remove a Style
that has been set. Changing this property triggers style affects in the Gem's children. \
gem.visible
- Setting this variable to false hides the gem using "display: none;". Setting this variable to true unhides it. Accessing the variable will return its visibility state. \
gem.focus
- Setting this variable to true gives the gem focus on the page. Setting this variable to false blur
s it. Accessing the variable returns whether or not the gem is the focused element on the page. \
gem.quiet.focus
- Just like gem.focus
but won't cause a "focus" or "blur" event.
gem.selectionRange
- Returns an array representing the selection range in terms of visible character offsets. E.g. a value of [2,4]
means that the current element has 2 visible entities (usually characters) selected within it at offset 2 and 4 from the start. Note that if there are hidden characters like multiple spaces in a row, or newlines, or other non-visible characters (mostly only applies to contenteditable nodes), they are ignored. \
gem.selectionRange = [offsetStart, offsetEnd]
- Setting the selectionRange
property sets the selection inside the Gem's domNode based on the given offsets.
Example of selectionRange
:
```javascript
var x = Text("You're not my buddy, guy")
x.attach()
x.selectionRange = 0,6 // selects "You're"
```
Event instance properties and methods
Most of these are inherited fromEventEmitter
.
All methods and properties from EventEmitter
are inherited by Gem
. The important ones:
gem.emit(event, data, data2, ...)
- Emits an event that triggers handlers setup via the Gem's on
methods.
gem.on(event, callback)
- Registers a callback
that will be called when the passed event
is emit
ted by the Gem. \
event
- The string event name to listen for. If the passed event is one of the many standard dom events (e.g. 'click', 'mouseover', 'touchstart', etc), the passed handler will be registered as a dom event handler in one of three cases:
* the gem's `excludeDomEvents` object is undefined
* the event is `in` the gem's `excludeDomEvents` property
callback(data, data2, ...)
- the callback gets any arguments passed toemit
after the event name.
gem.onCapture(event, callback)
- Just like gem.on
but listens on the capture phase of native browser events. Note: this doesn't currently listen on events that aren't native browser events.
gem.once(event, callback)
- Like on
but the callback
will only be called the first time the event happens.
gem.off(event, callback)
- Removes a callback as an event handler (the callback
won't be called for that event again).
gem.removeListener(event,callback)
- Same as off
.
gem.removeAllListeners(event)
- Removes all the callbacks for the passed event
, except capture handlers.
gem.removeAllListeners()
- Removes all callbacks except capture handlers.
gem.offCapture(event, callback)
- Removes a capture handler.
ifon
The ifon
and related methods are useful primarily for performance reasons. They allow registering event listeners only when they're needed, so that the browser doesn't get overloaded with event handlers. Its recommended that ifon
is used whenever possible.
An example:
```javascript
var text = Text("CLICK ME")
var parent = Block(text)
var handler;
parent.ifon('someoneClickedTheThing', function() {
text.on('click', handler = function() {
parent.emit('someoneClickedTheThing', "I can't believe it")
})
})
parent.ifoff('someoneClickedTheThing', function() {
text.off('click', handler)
})
```
gem.ifon(event, callback)
- Registers a callback that will be called when a handler is registered for event
if it had no handler registered previously. If there is already a listener attached to that event, callback
is called immediately. \
gem.ifon(callback)
- Registers a callback that will be called when the first handler for any event is registered.
callback(event)
- The callback gets the newly registered event type as its argument.
gem.ifoff(event, callback)
- Registers a callback that will be called when the last handler for event
is unregistered. \
gem.ifoff(callback)
- Registers a callback that will be called when the last handler for any event is unregistered.
callback(event)
- The callback gets the unregistered event type as its argument.
gem.removeIfon()
- Removes all ifon
handlers. \
gem.removeIfon(event)
- Removes all ifon
handlers for the passed event
. \
gem.removeIfon(callback)
- Removes callback
as an "all" ifon
handler (a callback passed to ifon
without an event). \
gem.removeIfon(event, callback)
- Removes callback
as an ifon
handler for the passed event
.
gem.removeIfoff()
- Removes all ifoff
handlers. \
gem.removeIfoff(event)
- Removes all ifoff
handlers for the passed event
. \
gem.removeIfoff(callback)
- Removes callback
as an "all" ifoff
handler (a callback passed to ifoff
without an event). \
gem.removeIfoff(event, callback)
- Removes callback
as an ifoff
handler for the passed event
.
proxy
The proxy
method uses ifon
and ifoff
to minmize the number of event listeners that need to be attached in the system.
gem.proxy(emitter, options)
- Proxies event registration to emitter
. \
emitter
- The emitter (usually aGem
) to proxy handler binding to \
options
- An object that defines what events are proxied. Ifundefined
, all events are proxied. The object can have one of the following properties:
only
- An array of events to proxy.
except
- An array of events to not proxy. All other events are proxied.
Example of proxy
:
```javascript
var A = Text()
var B = Text()
A.proxy(B)
A.on("click", function(x) {
console.log("hey hey heyyy! "+x)
})
B.emit("click", "Ughh..") // console prints "hey hey heyyy! Ughh.."
```
Instance events
"attach"
- Emitted when the gem is attached to the document. \
"detach"
- Emitted when the gem is detached from the document. \
"newParent"
- Emitted when a Gem gets a new parent. Note: this event is used by Style
objects, so don't prevent these events. \
"parentRemoved"
- Emitted when a Gem is detached from its parent. Note: this event is used by Style
objects, so don't prevent these events. \
Dom Events
Gem
object will emit any standard dom event ("click"
, "mousedown"
, "keypress"
, etc) when listened on. Note that a Gem
doesn't add an event listener to the dom node until someone listens on
that event on the gem. This minimizes the number of event listeners that are registered on the page. To see the list of dom events this applies to (supposed to be all of them), see the top of src/nodemodules/Gem.js
Custom Gems
-------------
Gem.js is all about custom components. That's the point: your application should be built as a composition of custom gems on top of custom gems so that, instead of a million divs, you have semantically appropriate javascript web components.
In this documentation, we're going to be using the class library proto. The descriptions here apply to both inheriting from Gem
and inheriting from any of the standard gems. There are a couple special properties to create when making a custom Gem
:
name
- The name is a required property, should be named whatever your class is named, and should be a somewhat unique name in your system (tho it isn't required to be unique).
build()
- The "sub-constructor". The constructor calls this method, passing all arguments, to thebuild
method. The return value ofbuild
is ignored.
defaultStyle
- If set to aStyle
object, the style object will be the gem's default style. Unlike explicitly set Styles and inherited Styles, css properties indefaultStyle
do cascade line-by-line. Also, if a gem inherits from anotherGem
class that also has adefaultStyle
, the default styles mix together with the childGem
class style properties overriding the parentGem
class's default properties. So in the below example, ifgem
is given a style that definescolor: green
, it's fontWeight will still be 'bold'.
this.name = "CustomGem"
this.defaultStyle = Style({
color: 'red',
fontWeight: 'bold'
})
this.build = function(x) {
this.x = x
}
})
var gem = CustomGem(5) // gem.x is 5
```
Releasing custom gems as separate modules
If you'd like to release a customGem
or set of Gem
objects, there are a couple of important things to remember to do:
- If you're releasing on npm, do not add
Gem.js
as a normal "dependency". Instead, it should be added as a "peerDependency" or perhaps a "devDependency". It shouldn't be a normal "dependency" because otherwise bundlers may bundle multiple copies of Gem.js when using your custom gem module (even though bundlers like webpack dedupe files, if the versions of webpack being used are slightly different, they would still package together both versions of Gem.js)
- If you're releasing a module distribution intended to be loaded in a
<script>
tag, do not bundle Gem.js in your distribution bundle. It should assume thegems
global variable (e.g.gems.Gem
) is available.
Inheriting from Gems with a class library other than proto
If you're building Gems with something other than proto
(or are using a version of proto older than 1.0.17), note that Gem.js relies on the following properties:
- gem.constructor - must point to the Gem prototype class (in the proto example, the object returned by the call to proto). This is a standard property that all good class libraries should set.
- gem.constructor.parent - must point either to the parent of the gem's constructor, or undefined if there is no parent. Note that while
proto
sets this automatically, it is not a standard property and if you're using a different library from proto, you must set this manually.
- gem.constructor.name - the constructors must have the same name property that instances can access. Note that while
proto
sets this appropriately, most class libraries probably don't and it isn't simple to manually set. See here for details.
Gem
's constructor is called on new instances that inherit from Gem
.
Inheriting from Gems without a class library
Properly subclassing a prototype in javascript isn't the simplest thing to do, but if you want to do it, here's how: ```javascript var CustomGem = function() {Gem.init.call(this) // Gem's constructor must be called
}
CustomGem.parent = Gem // needed for correct Style rendering
var Intermediate = function(){}; Intermediate.prototype = Gem.prototype
CustomGem.prototype = new Intermediate()
CustomGem.prototype.name = 'CustomGem' // the name is a required property
CustomGem.prototype.constructor = CustomGem // required for correct Style rendering,
// and is a standard javascript convention
CustomGem.prototype.build = function(constructorArgument1, constructorArgument2, ...) {
// .. custom constructor code
}
CustomGem.prototype.customMethod = function() {
// ...
}
```
Standard Gems
---------------
The built-in standard gems all inherit from Gem
and so have all the methods and properties in the above documentation. For each build-in gem, its name
property will be the same as the name the documentation uses for it. For example Button
will have the name "Button"
.
To use these built in gems, access them via either require("gem/<GemName>")
or Gem.<GemName>
. For example:
```javascript
var Table = require("gem/Table") // webpack or browserify
// or
var Table = Gem.Table // if loading the umd bundle in a