0.0.3 • Published 5 years ago

chainify-api v0.0.3

Weekly downloads
3
License
ISC
Repository
github
Last release
5 years ago

chainify.js WIP

A simple utility library that converts old-style imperative API into functional/chainable style API. A functional/chainable style goes well with the latest features of EcmaScript.

If you are forced to work with an imperative API and crave a functional/chainable style, this is the library for you. It creates a very light-weight wrapper (less than 150 lines unminified, has no dependencies) over an imperative API.

Sample usage on Three.js's imperative API.

Original imperative API
var raycaster = new THREE.Raycaster()
raycaster.setFromCamera( mouse, camera )
var intersects = raycaster.intersectObjects( scene.children )

for ( var i = 0; i < intersects.length; i++ ) {
	intersects[ i ].object.material.color.set( 0xff0000 )
}
Chainified API
const { Raycaster } = chainify(THREE)	// do this once at the top of the file

new Raycaster()
	.setFromCamera( mouse, camera )
	.intersectObjects( scene.children )
	.forEach(i => i.object.material.color.set( 0xff0000 ))

Sample usage on DOM's imperative API.

Original imperative API
window.addEventListener( 'mousemove', updateMouse, false )
window.requestAnimationFrame(render)
Chainified API
chainify(window)
  .addEventListener("mousemove", updateMouse, false)
  .renderAnimationFrame(render)

Use cases - Features supported

1. Converting unchainable member functions into chainable

As an example, consider the THREE.WebGLRenderer constructor provide by the library Three.js, used to create a WebGL-based renderer. This is how the original constrcutor is meant to be used.

Original imperative API
var renderer = new THREE.WebGLRenderer()

renderer.setClearColor(new Color(0xEEEEEE, 1.0))
renderer.setSize( window.innerWidth, window.innerHeight )
renderer.shadowMap.enabled = true

renderer.render(scene, camera)

var domEl = renderer.domElement

If you pass THREE to chainify, you can use the resulting WebGLRenderer constructor like shown below.

Chainified API
const { WebGLRenderer } = chainify(THREE) 	// do this once at the top of the file

const domEl = new WebGLRenderer()
		    .setClearColor(new Color(0xEEEEEE, 1.0))
		    .setSize(window.innerWidth, window.innerHeight)
		    .manage(r => r.shadowMap.enabled = true)
		    .render(scene, camera)
		    .domElement

The original setClearColor method returns undefined, so it is unchainable. But the setClearColor method in the chainified code example reutrns an instance of WebGLRenderer, so now setSize method can be directly chained to it. If setClearColor had returned a value of type number or boolean, the chain could not be continued.

Note that if setClearColor had returns an object, array, map or string, then ONLY that object's, array's, map's or string's methods could come next in chain. But since, setClearColor returns undefined, chainify ensures that it return the instance of WebGLRenderer, so WebGLRenderer's setSize method could be used in the chain.

As another example below, new TextureLoader() returns a TextureLoader instance, but since .load("media/pano.jpg") returns a Texture instance, the next function in the chain has to be a member function of Texture (and not TextureLoader).

Now updateMatirx() returns null, so the manage function receives the last context (return value of load viz. Texture instance). Thus, t argument of manage is an instance of Texture.

const { TextureLoader } = chainify(THREE)

background = new TextureLoader()
	      .load("media/pano.jpg")
	      .updateMatrix()
	      .manage(t => {
		t.wrapS = RepeatWrapping
		t.repeat.x = -1
	      })

2. manage function

Chainifying an object adds manage function to it. manage can be used to operate on the object without breaking the function chain.

In the previous two code examples, manage is used 1. to enable the shadowMap on the renderer in the middle of the chain 2. update texture properties at the end of the chain.

So, manage itself can be further chained if required because manage always returns its context (this).

3. map function

Chainifying an object adds map function to it. While manage always returns its context (object on which it was called), map returns the value returned by the function passed as argument to map. Thus, while manage maintains the context of the chain, map can be used to change the context of the chain without breaking it.

An example using map is given below.

Original imperative API
const video = document.getElementById("video")
video.pause()
const metadata = { width: video.videoWidth, height: video.videoHeight }
Chainified API
const cdocument = chainify(document) // do this once at the top of the file, then use cdocument everywhere

const metadata = cdocument.getElementById("video")
			  .pause()
			  .map(v => ({ width: v.videoWidth, height: v.videoheight }))

Nore that .pause() could be placed in the chain exactly because it returns undefined. Had it returned anything other than undefined or null, then the map would get the return value of pause() (and not the video DOM node) as v.

TODO

4. Array-like objects returned by one function are treated as Arrays in the next function in the chain

5. Add support for conditional chaining (functional replacement for imperative if-else)

0.0.3

5 years ago

0.0.2

5 years ago

1.0.1

5 years ago

1.0.0

5 years ago