UNPKG

transactional

Version:

Reactive objects with transactional updates and automatic serialization

188 lines (147 loc) 5.83 kB
import { setAttribute, Record, Attribute, Transform, ChangeHandler, AttributeDescriptor } from './transaction.ts' import { notEqual, assign } from '../tools.ts' import { Owner, Transactional, TransactionOptions, Constructor } from '../types.ts' type GetHook = ( value : any, key : string ) => any; export type ChangeAttrHandler = ( ( value : any, attr : string ) => void ) | string; declare global { interface Function { _attribute : typeof GenericAttribute } } // TODO: interface differs from options, do something obout it export class GenericAttribute implements Attribute { // Factory method to create attribute from options static create( options : AttributeDescriptor, name : string ) : GenericAttribute { const type = options.type, AttributeCtor = type ? type._attribute : GenericAttribute; return new AttributeCtor( name, options ); } /** * Update pipeline functions * ========================= * * Stage 0. canBeUpdated( value ) * - presence of this function implies attribute's ability to update in place. */ canBeUpdated( prev, next ) : boolean { return false; } /** * Stage 1. Transform stage */ transform( value, options, prev, model ) { return value; } // convert attribute type to `this.type` convert( value, options, model ) { return value; } /** * Stage 2. Check if attr value is changed */ isChanged( a, b ) { return notEqual( a, b ); } /** * Stage 3. Handle attribute change */ handleChange( next, prev, model ) {} /** * End update pipeline definitions. */ // create empty object passing backbone options to constructor... create() { return new ( <any>this.type )(); } // generic clone function for typeless attributes // Must be overriden in sublass clone( value, options : { deep? : boolean } = {} ) { if( value && typeof value === 'object' ) { // delegate to object's clone(), if it exist... if( value.clone ) { return value.clone( options ); } if( options.deep ){ const proto = Object.getPrototypeOf( value ); // attempt to deep copy raw objects, assuming they are JSON if( proto === Object.prototype || proto === Array.prototype ){ return JSON.parse( JSON.stringify( value ) ); } } } return value; } validate( record : Record, value : any, key : string ){} toJSON( value, key ) { return value && value.toJSON ? value.toJSON() : value; } createPropertyDescriptor() : PropertyDescriptor | void { const { name, getHook } = this; if( name !== 'id' ){ return { // call to optimized set function for single argument. set( value ){ setAttribute( <any>this, name, value ); }, // attach get hook to the getter function, if present get : getHook ? function() { return getHook.call( this, this.attributes[ name ], name ); } : function() { return this.attributes[ name ]; } } } } value : any type : Constructor parse : ( value, key : string ) => any initialize( name : string, options ){} constructor( public name : string, public options : AttributeDescriptor ) { const { value, type, parse, toJSON, getHooks = [], transforms = [], changeHandlers = [] } = options; this.value = value; this.type = type; this.parse = parse; this.toJSON = toJSON === void 0 ? this.toJSON : toJSON; /** * Assemble pipelines... */ // `convert` is default transform, which is always present... this.transform = this.convert; // No change handler by default this.handleChange = null; // Get hook from the attribute will be used first... this.getHook = this.get || null; // let subclasses configure the pipeline... this.initialize.apply( this, arguments ); // let attribute spec configure the pipeline... getHooks.forEach( gh => this.addGetHook( gh ) ); transforms.forEach( t => this.addTransform( t ) ); changeHandlers.forEach( ch => this.addChangeHandler( ch ) ); } getHook : ( value, key : string ) => any get : ( value, key : string ) => any addGetHook( next : GetHook ) : void { const prev = this.getHook; this.getHook = prev ? function( value, name ) { const next = prev.call( value, name ); return next.call( next, name ); } : next; } addTransform( next : Transform ) : void { const prev = this.transform; this.transform = function( value, options, prev, model ) { const next = prev.call( this, value, options, prev, model ); return next.call( this, next, options, prev, model ); } } addChangeHandler( next : ChangeHandler ) : void { const prev = this.handleChange; this.handleChange = prev ? function( next, prev, model ) { prev.call( this, next, prev, model ); next.call( this, next, prev, model ); } : next; } }