UNPKG

@stratusjs/angularjs

Version:

This is the AngularJS package for StratusJS.

313 lines (295 loc) 11.3 kB
// Registry Service // ---------------- // Runtime import {forEach, get, kebabCase, isNumber, isObject, isUndefined, set, size, union} from 'lodash' import {auto, IInterpolateService, IScope} from 'angular' import {Stratus} from '@stratusjs/runtime/stratus' // Stratus Core import {sanitize} from '@stratusjs/core/conversion' import {flatten, isJSON, poll, ucfirst} from '@stratusjs/core/misc' import {cookie} from '@stratusjs/core/environment' // AngularJS Dependency Injector import {getInjector} from '../injector' // AngularJS Services import {Model, ModelOptionKeys, ModelOptions} from './model' import {Collection, CollectionOptionKeys, CollectionOptions} from './collection' import {EventManager} from '@stratusjs/core/events/eventManager' // Instantiate Injector let injector = getInjector() // Angular Services // let $interpolate: IInterpolateService = injector ? injector.get('$interpolate') : null let $interpolate: IInterpolateService // Interfaces export interface RegistryOptions extends CollectionOptions, ModelOptions { id?: number api?: string temp?: string decouple?: boolean fetch?: boolean } // Service Verification Function const serviceVerify = async () => { return new Promise(async (resolve, _reject) => { if ($interpolate) { resolve(true) return } if (!injector) { injector = getInjector() } if (injector) { $interpolate = injector.get('$interpolate') } if ($interpolate) { resolve(true) return } setTimeout(() => { if (cookie('env')) { console.log('wait for $interpolate service:', $interpolate) } serviceVerify().then(resolve) }, 250) }) } // TODO: Move this to the Backend Package export class Registry { constructor() { // Scope Binding // this.fetch = this.fetch.bind(this) } // TODO: Handle Version Routing through Angular // Maintain all models in Namespace // Inverse the parent and child objects the same way Doctrine does // TODO: PushState Handling like: #/media/p/2 fetch( $element: string|object|JQLite|any, $scope: object|IScope|any ): Promise<boolean|Collection|Model> { return new Promise(async (resolve, _reject) => { if (typeof $element === 'string') { $element = { target: $element } } const inputs: RegistryOptions = {} const baseInputs = [ 'id', 'api', 'temp', 'decouple', 'fetch' ] forEach( union(ModelOptionKeys, CollectionOptionKeys, baseInputs), (option: string) => set(inputs, option, 'data-' + kebabCase(option)) ) // FIXME: Sanitize function fails here in certain cases const options = forEach(inputs, (value: string, key: string, list: any) => { list[key] = $element.attr ? $element.attr(value) : $element[key] if (!isJSON(list[key])) { return } list[key] = JSON.parse(list[key]) }) options.api = sanitize(options.api) options.temp = sanitize(options.temp) // TODO: handle these sorts of shortcuts to the API that components are providing // $scope.api = isJSON(options.api) ? JSON.parse(options.api) : false // const request = { // api: { // options: options || {}, // limit: isJSON(options.limit) ? JSON.parse(options.limit) : 40 // } // } // if ($scope.api && isObject($scope.api)) { // request.api = extendDeep(request.api, $scope.api) // } let completed = 0 const verify = () => { if (!isNumber(completed) || completed !== size(options)) { return } resolve(this.build(options, $scope)) } if (!$interpolate) { await serviceVerify() } forEach(options, async (element, key) => { if (!element || typeof element !== 'string' || !$scope || !$scope.$parent) { completed++ verify() return } const interpreter = $interpolate(element, false, null, true) const initial = interpreter($scope.$parent) if (typeof initial !== 'undefined') { options[key] = initial completed++ verify() return } if (cookie('env')) { console.log(`poll (${key}): start`) } // TODO: Check if this ever hits a timeout let value: any try { value = await poll(() => interpreter($scope.$parent), 1500, 250) } catch (err) { if ( cookie('env') || err.name !== 'Timeout' ) { console.error(err) } } if (cookie('env')) { console.log(`poll (${key}):`, value) } options[key] = value completed++ verify() }) }) } build( options: RegistryOptions, $scope: object|IScope|any ): Collection | Model { let data: Collection | Model // TODO: Code golf this function to be only 1 level // Ensure we don't fetch if the data is already available if (options.payload || options.convoy) { options.fetch = false } // Lookup Reference based on Target (and id if present) if (options.target) { options.target = ucfirst(options.target) // Find or Create Reference if (options.manifest || options.id) { if (!Stratus.Catalog[options.target]) { Stratus.Catalog[options.target] = {} } const id = options.id || 'manifest' if (options.decouple || !Stratus.Catalog[options.target][id]) { const modelOptions: ModelOptions = { stagger: true } forEach(ModelOptionKeys, (element) => { const optionValue = get(options, element) if (isUndefined(optionValue)) { return } set(modelOptions, element, optionValue) }) data = new Model(modelOptions, { id: options.id }) if (!options.decouple) { Stratus.Catalog[options.target][id] = data } } else if (Stratus.Catalog[options.target][id]) { data = Stratus.Catalog[options.target][id] } } else { const registry = !options.direct ? 'Catalog' : 'Compendium' if (!Stratus[registry][options.target]) { Stratus[registry][options.target] = {} } if (options.decouple || !Stratus[registry][options.target].collection) { const collectionOptions: CollectionOptions = {} forEach(CollectionOptionKeys, (element) => { const optionValue = get(options, element) if (isUndefined(optionValue)) { return } set(collectionOptions, element, optionValue) }) data = new Collection(collectionOptions) if (!options.decouple) { Stratus[registry][options.target].collection = data } } else if (Stratus[registry][options.target].collection) { data = Stratus[registry][options.target].collection } } // Filter if Necessary if (options.api) { data.meta.set('api', isJSON(options.api) ? JSON.parse(options.api) : options.api) } // Add Temp Values if (options.temp && isObject(options.temp) && !data.completed) { forEach( flatten(options.temp), (v: any, k: string) => { console.log('setting temp:', `api.${k}`, v) data.meta.temp(`api.${k}`, v) } ) } // Handle Staggered if (data instanceof Model && data.stagger && typeof data.initialize === 'function') { data.initialize() } } // Evaluate if (typeof data === 'object' && data !== null) { if (typeof $scope !== 'undefined') { $scope.data = data // TODO: Add null values to ensure strict typing (disable scope inheritance) if (data instanceof Model) { $scope.model = data // $scope.collection = null } else if (data instanceof Collection) { // $scope.model = null $scope.collection = data } // bind changes to redraw if (data instanceof EventManager && typeof $scope.$applyAsync === 'function') { data.on('change', () => { // console.log('changed:', $scope) $scope.$applyAsync() }) data.on('error', () => { // console.log('errored:', $scope) $scope.$applyAsync() }) if (data.completed) { $scope.$applyAsync() } } } if (!data.pending && !data.completed && ( isUndefined(options.fetch) || options.fetch ) ) { data.fetch().then() } } return data } } // This Registry Service handles data binding for an element Stratus.Services.Registry = [ '$provide', ($provide: auto.IProvideService) => { $provide.factory('Registry', [ // '$interpolate', // 'Collection', // 'Model', ( // $i: IInterpolateService, // C: Collection, // M: Model ) => { // $interpolate = $i return new Registry() } ]) } ] Stratus.Data.Registry = Registry