UNPKG

pict-application

Version:

Application base class for a pict view-based application

1,100 lines (1,054 loc) 52.7 kB
"use strict"; function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; } function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } (function (f) { if (typeof exports === "object" && typeof module !== "undefined") { module.exports = f(); } else if (typeof define === "function" && define.amd) { define([], f); } else { var g; if (typeof window !== "undefined") { g = window; } else if (typeof global !== "undefined") { g = global; } else if (typeof self !== "undefined") { g = self; } else { g = this; } g.PictApplication = f(); } })(function () { var define, module, exports; return function () { function r(e, n, t) { function o(i, f) { if (!n[i]) { if (!e[i]) { var c = "function" == typeof require && require; if (!f && c) return c(i, !0); if (u) return u(i, !0); var a = new Error("Cannot find module '" + i + "'"); throw a.code = "MODULE_NOT_FOUND", a; } var p = n[i] = { exports: {} }; e[i][0].call(p.exports, function (r) { var n = e[i][1][r]; return o(n || r); }, p, p.exports, r, e, n, t); } return n[i].exports; } for (var u = "function" == typeof require && require, i = 0; i < t.length; i++) o(t[i]); return o; } return r; }()({ 1: [function (require, module, exports) { module.exports = { "name": "fable-serviceproviderbase", "version": "3.0.15", "description": "Simple base classes for fable services.", "main": "source/Fable-ServiceProviderBase.js", "scripts": { "start": "node source/Fable-ServiceProviderBase.js", "test": "npx mocha -u tdd -R spec", "tests": "npx mocha -u tdd --exit -R spec --grep", "coverage": "npx nyc --reporter=lcov --reporter=text-lcov npx mocha -- -u tdd -R spec", "build": "npx quack build" }, "mocha": { "diff": true, "extension": ["js"], "package": "./package.json", "reporter": "spec", "slow": "75", "timeout": "5000", "ui": "tdd", "watch-files": ["source/**/*.js", "test/**/*.js"], "watch-ignore": ["lib/vendor"] }, "repository": { "type": "git", "url": "https://github.com/stevenvelozo/fable-serviceproviderbase.git" }, "keywords": ["entity", "behavior"], "author": "Steven Velozo <steven@velozo.com> (http://velozo.com/)", "license": "MIT", "bugs": { "url": "https://github.com/stevenvelozo/fable-serviceproviderbase/issues" }, "homepage": "https://github.com/stevenvelozo/fable-serviceproviderbase", "devDependencies": { "fable": "^3.0.143", "quackage": "^1.0.33" } }; }, {}], 2: [function (require, module, exports) { /** * Fable Service Base * @author <steven@velozo.com> */ const libPackage = require('../package.json'); class FableServiceProviderBase { // The constructor can be used in two ways: // 1) With a fable, options object and service hash (the options object and service hash are optional) // 2) With an object or nothing as the first parameter, where it will be treated as the options object constructor(pFable, pOptions, pServiceHash) { // Check if a fable was passed in; connect it if so if (typeof pFable === 'object' && pFable.isFable) { this.connectFable(pFable); } else { this.fable = false; } // Initialize the services map if it wasn't passed in /** @type {Object} */ this._PackageFableServiceProvider = libPackage; // initialize options and UUID based on whether the fable was passed in or not. if (this.fable) { this.UUID = pFable.getUUID(); this.options = typeof pOptions === 'object' ? pOptions : {}; } else { // With no fable, check to see if there was an object passed into either of the first two // Parameters, and if so, treat it as the options object this.options = typeof pFable === 'object' && !pFable.isFable ? pFable : typeof pOptions === 'object' ? pOptions : {}; this.UUID = `CORE-SVC-${Math.floor(Math.random() * (99999 - 10000) + 10000)}`; } // It's expected that the deriving class will set this this.serviceType = `Unknown-${this.UUID}`; // The service hash is used to identify the specific instantiation of the service in the services map this.Hash = typeof pServiceHash === 'string' ? pServiceHash : !this.fable && typeof pOptions === 'string' ? pOptions : `${this.UUID}`; } connectFable(pFable) { if (typeof pFable !== 'object' || !pFable.isFable) { let tmpErrorMessage = `Fable Service Provider Base: Cannot connect to Fable, invalid Fable object passed in. The pFable parameter was a [${typeof pFable}].}`; console.log(tmpErrorMessage); return new Error(tmpErrorMessage); } if (!this.fable) { this.fable = pFable; } if (!this.log) { this.log = this.fable.Logging; } if (!this.services) { this.services = this.fable.services; } if (!this.servicesMap) { this.servicesMap = this.fable.servicesMap; } return true; } } _defineProperty(FableServiceProviderBase, "isFableService", true); module.exports = FableServiceProviderBase; // This is left here in case we want to go back to having different code/base class for "core" services module.exports.CoreServiceProviderBase = FableServiceProviderBase; }, { "../package.json": 1 }], 3: [function (require, module, exports) { module.exports = { "name": "pict-application", "version": "1.0.24", "description": "Application base class for a pict view-based application", "main": "source/Pict-Application.js", "scripts": { "test": "npx mocha -u tdd -R spec", "start": "node source/Pict-Application.js", "coverage": "npx nyc --reporter=lcov --reporter=text-lcov npx mocha -- -u tdd -R spec", "build": "npx quack build", "docker-dev-build": "docker build ./ -f Dockerfile_LUXURYCode -t pict-application-image:local", "docker-dev-run": "docker run -it -d --name pict-application-dev -p 30001:8080 -p 38086:8086 -v \"$PWD/.config:/home/coder/.config\" -v \"$PWD:/home/coder/pict-application\" -u \"$(id -u):$(id -g)\" -e \"DOCKER_USER=$USER\" pict-application-image:local", "docker-dev-shell": "docker exec -it pict-application-dev /bin/bash", "tests": "npx mocha -u tdd --exit -R spec --grep", "lint": "eslint source/**", "types": "tsc -p ." }, "types": "types/source/Pict-Application.d.ts", "repository": { "type": "git", "url": "git+https://github.com/stevenvelozo/pict-application.git" }, "author": "steven velozo <steven@velozo.com>", "license": "MIT", "bugs": { "url": "https://github.com/stevenvelozo/pict-application/issues" }, "homepage": "https://github.com/stevenvelozo/pict-application#readme", "devDependencies": { "@eslint/js": "^9.17.0", "browser-env": "^3.3.0", "eslint": "^9.17.0", "pict": "^1.0.226", "pict-view": "^1.0.55", "quackage": "^1.0.36" }, "mocha": { "diff": true, "extension": ["js"], "package": "./package.json", "reporter": "spec", "slow": "75", "timeout": "5000", "ui": "tdd", "watch-files": ["source/**/*.js", "test/**/*.js"], "watch-ignore": ["lib/vendor"] }, "dependencies": { "fable-serviceproviderbase": "^3.0.15" } }; }, {}], 4: [function (require, module, exports) { const libFableServiceBase = require('fable-serviceproviderbase'); const libPackage = require('../package.json'); const defaultPictSettings = { Name: 'DefaultPictApplication', // The main "viewport" is the view that is used to host our application MainViewportViewIdentifier: 'Default-View', MainViewportRenderableHash: false, MainViewportDestinationAddress: false, MainViewportDefaultDataAddress: false, // Whether or not we should automatically render the main viewport and other autorender views after we initialize the pict application AutoSolveAfterInitialize: true, AutoRenderMainViewportViewAfterInitialize: true, AutoRenderViewsAfterInitialize: false, ConfigurationOnlyViews: [], Manifests: {}, // The prefix to prepend on all template destination hashes IdentifierAddressPrefix: 'PICT-' }; /** * Base class for pict applications. */ class PictApplication extends libFableServiceBase { /** * @param {import('fable')} pFable * @param {any} [pOptions] * @param {string} [pServiceHash] */ constructor(pFable, pOptions, pServiceHash) { let tmpOptions = Object.assign({}, JSON.parse(JSON.stringify(defaultPictSettings)), pOptions); super(pFable, tmpOptions, pServiceHash); /** @type {any} */ this.options; /** @type {any} */ this.log; /** @type {import('pict') & import('fable')} */ this.fable; /** @type {string} */ this.UUID; /** @type {string} */ this.Hash; /** * @type {{ [key: string]: any }} */ this.servicesMap; this.serviceType = 'PictApplication'; /** @type {Object} */ this._Package = libPackage; // Convenience and consistency naming this.pict = this.fable; // Wire in the essential Pict state this.AppData = this.fable.AppData; /** @type {number} */ this.initializeTimestamp; /** @type {number} */ this.lastSolvedTimestamp; /** @type {number} */ this.lastMarshalFromViewsTimestamp; /** @type {number} */ this.lastMarshalToViewsTimestamp; /** @type {number} */ this.lastAutoRenderTimestamp; // Load all the manifests for the application let tmpManifestKeys = Object.keys(this.options.Manifests); if (tmpManifestKeys.length > 0) { for (let i = 0; i < tmpManifestKeys.length; i++) { // Load each manifest let tmpManifestKey = tmpManifestKeys[i]; this.fable.instantiateServiceProvider('Manifest', this.options.Manifests[tmpManifestKey], tmpManifestKey); } } } /* -------------------------------------------------------------------------- */ /* Code Section: Solve All Views */ /* -------------------------------------------------------------------------- */ /** * @return {boolean} */ onPreSolve() { if (this.pict.LogNoisiness > 3) { this.log.trace(`PictApp [${this.UUID}]::[${this.Hash}] ${this.options.Name} onPreSolve:`); } return true; } /** * @param {(error?: Error) => void} fCallback */ onPreSolveAsync(fCallback) { this.onPreSolve(); return fCallback(); } /** * @return {boolean} */ onBeforeSolve() { if (this.pict.LogNoisiness > 3) { this.log.trace(`PictApp [${this.UUID}]::[${this.Hash}] ${this.options.Name} onBeforeSolve:`); } return true; } /** * @param {(error?: Error) => void} fCallback */ onBeforeSolveAsync(fCallback) { this.onBeforeSolve(); return fCallback(); } /** * @return {boolean} */ onSolve() { if (this.pict.LogNoisiness > 3) { this.log.trace(`PictApp [${this.UUID}]::[${this.Hash}] ${this.options.Name} onSolve:`); } return true; } /** * @param {(error?: Error) => void} fCallback */ onSolveAsync(fCallback) { this.onSolve(); return fCallback(); } /** * @return {boolean} */ solve() { if (this.pict.LogNoisiness > 2) { this.log.trace(`PictApp [${this.UUID}]::[${this.Hash}] ${this.options.Name} executing solve() function...`); } // Walk through any loaded providers and solve them as well. let tmpLoadedProviders = Object.keys(this.pict.providers); let tmpProvidersToSolve = []; for (let i = 0; i < tmpLoadedProviders.length; i++) { let tmpProvider = this.pict.providers[tmpLoadedProviders[i]]; if (tmpProvider.options.AutoSolveWithApp) { tmpProvidersToSolve.push(tmpProvider); } } // Sort the views by their priority (if they are all priority 0, it will end up being add order due to JSON Object Property Key order stuff) tmpProvidersToSolve.sort((a, b) => { return a.options.AutoSolveOrdinal - b.options.AutoSolveOrdinal; }); for (let i = 0; i < tmpProvidersToSolve.length; i++) { tmpProvidersToSolve[i].solve(tmpProvidersToSolve[i]); } this.onBeforeSolve(); // Now walk through any loaded views and initialize them as well. let tmpLoadedViews = Object.keys(this.pict.views); let tmpViewsToSolve = []; for (let i = 0; i < tmpLoadedViews.length; i++) { let tmpView = this.pict.views[tmpLoadedViews[i]]; if (tmpView.options.AutoInitialize) { tmpViewsToSolve.push(tmpView); } } // Sort the views by their priority (if they are all priority 0, it will end up being add order due to JSON Object Property Key order stuff) tmpViewsToSolve.sort((a, b) => { return a.options.AutoInitializeOrdinal - b.options.AutoInitializeOrdinal; }); for (let i = 0; i < tmpViewsToSolve.length; i++) { tmpViewsToSolve[i].solve(); } this.onSolve(); this.onAfterSolve(); this.lastSolvedTimestamp = this.fable.log.getTimeStamp(); return true; } /** * @param {(error?: Error) => void} fCallback */ solveAsync(fCallback) { let tmpAnticipate = this.fable.instantiateServiceProviderWithoutRegistration('Anticipate'); tmpAnticipate.anticipate(this.onBeforeSolveAsync.bind(this)); // Allow the callback to be passed in as the last parameter no matter what let tmpCallback = typeof fCallback === 'function' ? fCallback : false; if (!tmpCallback) { this.log.warn(`PictApp [${this.UUID}]::[${this.Hash}] ${this.options.Name} solveAsync was called without a valid callback. A callback will be generated but this could lead to race conditions.`); tmpCallback = pError => { if (pError) { this.log.error(`PictApp [${this.UUID}]::[${this.Hash}] ${this.options.Name} solveAsync Auto Callback Error: ${pError}`, pError); } }; } // Walk through any loaded providers and solve them as well. let tmpLoadedProviders = Object.keys(this.pict.providers); let tmpProvidersToSolve = []; for (let i = 0; i < tmpLoadedProviders.length; i++) { let tmpProvider = this.pict.providers[tmpLoadedProviders[i]]; if (tmpProvider.options.AutoSolveWithApp) { tmpProvidersToSolve.push(tmpProvider); } } // Sort the views by their priority (if they are all priority 0, it will end up being add order due to JSON Object Property Key order stuff) tmpProvidersToSolve.sort((a, b) => { return a.options.AutoSolveOrdinal - b.options.AutoSolveOrdinal; }); for (let i = 0; i < tmpProvidersToSolve.length; i++) { tmpAnticipate.anticipate(tmpProvidersToSolve[i].solveAsync.bind(tmpProvidersToSolve[i])); } // Walk through any loaded views and solve them as well. let tmpLoadedViews = Object.keys(this.pict.views); let tmpViewsToSolve = []; for (let i = 0; i < tmpLoadedViews.length; i++) { let tmpView = this.pict.views[tmpLoadedViews[i]]; if (tmpView.options.AutoSolveWithApp) { tmpViewsToSolve.push(tmpView); } } // Sort the views by their priority (if they are all priority 0, it will end up being add order due to JSON Object Property Key order stuff) tmpViewsToSolve.sort((a, b) => { return a.options.AutoSolveOrdinal - b.options.AutoSolveOrdinal; }); for (let i = 0; i < tmpViewsToSolve.length; i++) { tmpAnticipate.anticipate(tmpViewsToSolve[i].solveAsync.bind(tmpViewsToSolve[i])); } tmpAnticipate.anticipate(this.onSolveAsync.bind(this)); tmpAnticipate.anticipate(this.onAfterSolveAsync.bind(this)); tmpAnticipate.wait(pError => { if (this.pict.LogNoisiness > 2) { this.log.trace(`PictApp [${this.UUID}]::[${this.Hash}] ${this.options.Name} solveAsync() complete.`); } this.lastSolvedTimestamp = this.fable.log.getTimeStamp(); return tmpCallback(pError); }); } /** * @return {boolean} */ onAfterSolve() { if (this.pict.LogNoisiness > 3) { this.log.trace(`PictApp [${this.UUID}]::[${this.Hash}] ${this.options.Name} onAfterSolve:`); } return true; } /** * @param {(error?: Error) => void} fCallback */ onAfterSolveAsync(fCallback) { this.onAfterSolve(); return fCallback(); } /* -------------------------------------------------------------------------- */ /* Code Section: Initialize Application */ /* -------------------------------------------------------------------------- */ /** * @return {boolean} */ onBeforeInitialize() { if (this.pict.LogNoisiness > 3) { this.log.trace(`PictApp [${this.UUID}]::[${this.Hash}] ${this.options.Name} onBeforeInitialize:`); } return true; } /** * @param {(error?: Error) => void} fCallback */ onBeforeInitializeAsync(fCallback) { this.onBeforeInitialize(); return fCallback(); } /** * @return {boolean} */ onInitialize() { if (this.pict.LogNoisiness > 3) { this.log.trace(`PictApp [${this.UUID}]::[${this.Hash}] ${this.options.Name} onInitialize:`); } return true; } /** * @param {(error?: Error) => void} fCallback */ onInitializeAsync(fCallback) { this.onInitialize(); return fCallback(); } /** * @return {boolean} */ initialize() { if (this.pict.LogControlFlow) { this.log.trace(`PICT-ControlFlow APPLICATION [${this.UUID}]::[${this.Hash}] ${this.options.Name} initialize:`); } if (!this.initializeTimestamp) { this.onBeforeInitialize(); if ('ConfigurationOnlyViews' in this.options) { // Load all the configuration only views for (let i = 0; i < this.options.ConfigurationOnlyViews.length; i++) { let tmpViewIdentifier = typeof this.options.ConfigurationOnlyViews[i].ViewIdentifier === 'undefined' ? `AutoView-${this.fable.getUUID()}` : this.options.ConfigurationOnlyViews[i].ViewIdentifier; this.log.info(`PictApp [${this.UUID}]::[${this.Hash}] ${this.options.Name} adding configuration only view: ${tmpViewIdentifier}`); this.pict.addView(tmpViewIdentifier, this.options.ConfigurationOnlyViews[i]); } } this.onInitialize(); // Walk through any loaded providers and initialize them as well. let tmpLoadedProviders = Object.keys(this.pict.providers); let tmpProvidersToInitialize = []; for (let i = 0; i < tmpLoadedProviders.length; i++) { let tmpProvider = this.pict.providers[tmpLoadedProviders[i]]; if (tmpProvider.options.AutoInitialize) { tmpProvidersToInitialize.push(tmpProvider); } } // Sort the views by their priority (if they are all priority 0, it will end up being add order due to JSON Object Property Key order stuff) tmpProvidersToInitialize.sort((a, b) => { return a.options.AutoInitializeOrdinal - b.options.AutoInitializeOrdinal; }); for (let i = 0; i < tmpProvidersToInitialize.length; i++) { tmpProvidersToInitialize[i].initialize(); } // Now walk through any loaded views and initialize them as well. let tmpLoadedViews = Object.keys(this.pict.views); let tmpViewsToInitialize = []; for (let i = 0; i < tmpLoadedViews.length; i++) { let tmpView = this.pict.views[tmpLoadedViews[i]]; if (tmpView.options.AutoInitialize) { tmpViewsToInitialize.push(tmpView); } } // Sort the views by their priority (if they are all priority 0, it will end up being add order due to JSON Object Property Key order stuff) tmpViewsToInitialize.sort((a, b) => { return a.options.AutoInitializeOrdinal - b.options.AutoInitializeOrdinal; }); for (let i = 0; i < tmpViewsToInitialize.length; i++) { tmpViewsToInitialize[i].initialize(); } this.onAfterInitialize(); if (this.options.AutoSolveAfterInitialize) { if (this.pict.LogNoisiness > 1) { this.log.trace(`PictApp [${this.UUID}]::[${this.Hash}] ${this.options.Name} auto solving after initialization...`); } // Solve the template synchronously this.solve(); } // Now check and see if we should automatically render as well if (this.options.AutoRenderMainViewportViewAfterInitialize) { if (this.pict.LogNoisiness > 1) { this.log.trace(`PictApp [${this.UUID}]::[${this.Hash}] ${this.options.Name} auto rendering after initialization...`); } // Render the template synchronously this.render(); } this.initializeTimestamp = this.fable.log.getTimeStamp(); return true; } else { this.log.warn(`PictApp [${this.UUID}]::[${this.Hash}] ${this.options.Name} initialize called but initialization is already completed. Aborting.`); return false; } } /** * @param {(error?: Error) => void} fCallback */ initializeAsync(fCallback) { if (this.pict.LogControlFlow) { this.log.trace(`PICT-ControlFlow APPLICATION [${this.UUID}]::[${this.Hash}] ${this.options.Name} initializeAsync:`); } // Allow the callback to be passed in as the last parameter no matter what let tmpCallback = typeof fCallback === 'function' ? fCallback : false; if (!tmpCallback) { this.log.warn(`PictApp [${this.UUID}]::[${this.Hash}] ${this.options.Name} initializeAsync was called without a valid callback. A callback will be generated but this could lead to race conditions.`); tmpCallback = pError => { if (pError) { this.log.error(`PictApp [${this.UUID}]::[${this.Hash}] ${this.options.Name} initializeAsync Auto Callback Error: ${pError}`, pError); } }; } if (!this.initializeTimestamp) { let tmpAnticipate = this.fable.instantiateServiceProviderWithoutRegistration('Anticipate'); if (this.pict.LogNoisiness > 3) { this.log.trace(`PictApp [${this.UUID}]::[${this.Hash}] ${this.options.Name} beginning initialization...`); } if ('ConfigurationOnlyViews' in this.options) { // Load all the configuration only views for (let i = 0; i < this.options.ConfigurationOnlyViews.length; i++) { let tmpViewIdentifier = typeof this.options.ConfigurationOnlyViews[i].ViewIdentifier === 'undefined' ? `AutoView-${this.fable.getUUID()}` : this.options.ConfigurationOnlyViews[i].ViewIdentifier; this.log.info(`PictApp [${this.UUID}]::[${this.Hash}] ${this.options.Name} adding configuration only view: ${tmpViewIdentifier}`); this.pict.addView(tmpViewIdentifier, this.options.ConfigurationOnlyViews[i]); } } tmpAnticipate.anticipate(this.onBeforeInitializeAsync.bind(this)); tmpAnticipate.anticipate(this.onInitializeAsync.bind(this)); // Walk through any loaded providers and solve them as well. let tmpLoadedProviders = Object.keys(this.pict.providers); let tmpProvidersToInitialize = []; for (let i = 0; i < tmpLoadedProviders.length; i++) { let tmpProvider = this.pict.providers[tmpLoadedProviders[i]]; if (tmpProvider.options.AutoInitialize) { tmpProvidersToInitialize.push(tmpProvider); } } // Sort the views by their priority (if they are all priority 0, it will end up being add order due to JSON Object Property Key order stuff) tmpProvidersToInitialize.sort((a, b) => { return a.options.AutoInitializeOrdinal - b.options.AutoInitializeOrdinal; }); for (let i = 0; i < tmpProvidersToInitialize.length; i++) { tmpAnticipate.anticipate(tmpProvidersToInitialize[i].initializeAsync.bind(tmpProvidersToInitialize[i])); } // Now walk through any loaded views and initialize them as well. // TODO: Some optimization cleverness could be gained by grouping them into a parallelized async operation, by ordinal. let tmpLoadedViews = Object.keys(this.pict.views); let tmpViewsToInitialize = []; for (let i = 0; i < tmpLoadedViews.length; i++) { let tmpView = this.pict.views[tmpLoadedViews[i]]; if (tmpView.options.AutoInitialize) { tmpViewsToInitialize.push(tmpView); } } // Sort the views by their priority // If they are all the default priority 0, it will end up being add order due to JSON Object Property Key order stuff tmpViewsToInitialize.sort((a, b) => { return a.options.AutoInitializeOrdinal - b.options.AutoInitializeOrdinal; }); for (let i = 0; i < tmpViewsToInitialize.length; i++) { let tmpView = tmpViewsToInitialize[i]; tmpAnticipate.anticipate(tmpView.initializeAsync.bind(tmpView)); } tmpAnticipate.anticipate(this.onAfterInitializeAsync.bind(this)); if (this.options.AutoSolveAfterInitialize) { if (this.pict.LogNoisiness > 1) { this.log.trace(`PictApp [${this.UUID}]::[${this.Hash}] ${this.options.Name} auto solving (asynchronously) after initialization...`); } tmpAnticipate.anticipate(this.solveAsync.bind(this)); } if (this.options.AutoRenderMainViewportViewAfterInitialize) { if (this.pict.LogNoisiness > 1) { this.log.trace(`PictApp [${this.UUID}]::[${this.Hash}] ${this.options.Name} auto rendering (asynchronously) after initialization...`); } tmpAnticipate.anticipate(this.renderMainViewportAsync.bind(this)); } tmpAnticipate.wait(pError => { if (pError) { this.log.error(`PictApp [${this.UUID}]::[${this.Hash}] ${this.options.Name} initializeAsync Error: ${pError.message || pError}`, { stack: pError.stack }); } this.initializeTimestamp = this.fable.log.getTimeStamp(); if (this.pict.LogNoisiness > 2) { this.log.trace(`PictApp [${this.UUID}]::[${this.Hash}] ${this.options.Name} initialization complete.`); } return tmpCallback(); }); } else { this.log.warn(`PictApp [${this.UUID}]::[${this.Hash}] ${this.options.Name} async initialize called but initialization is already completed. Aborting.`); // TODO: Should this be an error? return tmpCallback(); } } /** * @return {boolean} */ onAfterInitialize() { if (this.pict.LogNoisiness > 3) { this.log.trace(`PictApp [${this.UUID}]::[${this.Hash}] ${this.options.Name} onAfterInitialize:`); } return true; } /** * @param {(error?: Error) => void} fCallback */ onAfterInitializeAsync(fCallback) { this.onAfterInitialize(); return fCallback(); } /* -------------------------------------------------------------------------- */ /* Code Section: Marshal Data From All Views */ /* -------------------------------------------------------------------------- */ /** * @return {boolean} */ onBeforeMarshalFromViews() { if (this.pict.LogNoisiness > 3) { this.log.trace(`PictApp [${this.UUID}]::[${this.Hash}] ${this.options.Name} onBeforeMarshalFromViews:`); } return true; } /** * @param {(error?: Error) => void} fCallback */ onBeforeMarshalFromViewsAsync(fCallback) { this.onBeforeMarshalFromViews(); return fCallback(); } /** * @return {boolean} */ onMarshalFromViews() { if (this.pict.LogNoisiness > 3) { this.log.trace(`PictApp [${this.UUID}]::[${this.Hash}] ${this.options.Name} onMarshalFromViews:`); } return true; } /** * @param {(error?: Error) => void} fCallback */ onMarshalFromViewsAsync(fCallback) { this.onMarshalFromViews(); return fCallback(); } /** * @return {boolean} */ marshalFromViews() { if (this.pict.LogNoisiness > 2) { this.log.trace(`PictApp [${this.UUID}]::[${this.Hash}] ${this.options.Name} executing marshalFromViews() function...`); } this.onBeforeMarshalFromViews(); // Now walk through any loaded views and initialize them as well. let tmpLoadedViews = Object.keys(this.pict.views); let tmpViewsToMarshalFromViews = []; for (let i = 0; i < tmpLoadedViews.length; i++) { let tmpView = this.pict.views[tmpLoadedViews[i]]; tmpViewsToMarshalFromViews.push(tmpView); } for (let i = 0; i < tmpViewsToMarshalFromViews.length; i++) { tmpViewsToMarshalFromViews[i].marshalFromView(); } this.onMarshalFromViews(); this.onAfterMarshalFromViews(); this.lastMarshalFromViewsTimestamp = this.fable.log.getTimeStamp(); return true; } /** * @param {(error?: Error) => void} fCallback */ marshalFromViewsAsync(fCallback) { let tmpAnticipate = this.fable.instantiateServiceProviderWithoutRegistration('Anticipate'); // Allow the callback to be passed in as the last parameter no matter what let tmpCallback = typeof fCallback === 'function' ? fCallback : false; if (!tmpCallback) { this.log.warn(`PictApp [${this.UUID}]::[${this.Hash}] ${this.options.Name} marshalFromViewsAsync was called without a valid callback. A callback will be generated but this could lead to race conditions.`); tmpCallback = pError => { if (pError) { this.log.error(`PictApp [${this.UUID}]::[${this.Hash}] ${this.options.Name} marshalFromViewsAsync Auto Callback Error: ${pError}`, pError); } }; } tmpAnticipate.anticipate(this.onBeforeMarshalFromViewsAsync.bind(this)); // Walk through any loaded views and marshalFromViews them as well. let tmpLoadedViews = Object.keys(this.pict.views); let tmpViewsToMarshalFromViews = []; for (let i = 0; i < tmpLoadedViews.length; i++) { let tmpView = this.pict.views[tmpLoadedViews[i]]; tmpViewsToMarshalFromViews.push(tmpView); } for (let i = 0; i < tmpViewsToMarshalFromViews.length; i++) { tmpAnticipate.anticipate(tmpViewsToMarshalFromViews[i].marshalFromViewAsync.bind(tmpViewsToMarshalFromViews[i])); } tmpAnticipate.anticipate(this.onMarshalFromViewsAsync.bind(this)); tmpAnticipate.anticipate(this.onAfterMarshalFromViewsAsync.bind(this)); tmpAnticipate.wait(pError => { if (this.pict.LogNoisiness > 2) { this.log.trace(`PictApp [${this.UUID}]::[${this.Hash}] ${this.options.Name} marshalFromViewsAsync() complete.`); } this.lastMarshalFromViewsTimestamp = this.fable.log.getTimeStamp(); return tmpCallback(pError); }); } /** * @return {boolean} */ onAfterMarshalFromViews() { if (this.pict.LogNoisiness > 3) { this.log.trace(`PictApp [${this.UUID}]::[${this.Hash}] ${this.options.Name} onAfterMarshalFromViews:`); } return true; } /** * @param {(error?: Error) => void} fCallback */ onAfterMarshalFromViewsAsync(fCallback) { this.onAfterMarshalFromViews(); return fCallback(); } /* -------------------------------------------------------------------------- */ /* Code Section: Marshal Data To All Views */ /* -------------------------------------------------------------------------- */ /** * @return {boolean} */ onBeforeMarshalToViews() { if (this.pict.LogNoisiness > 3) { this.log.trace(`PictApp [${this.UUID}]::[${this.Hash}] ${this.options.Name} onBeforeMarshalToViews:`); } return true; } /** * @param {(error?: Error) => void} fCallback */ onBeforeMarshalToViewsAsync(fCallback) { this.onBeforeMarshalToViews(); return fCallback(); } /** * @return {boolean} */ onMarshalToViews() { if (this.pict.LogNoisiness > 3) { this.log.trace(`PictApp [${this.UUID}]::[${this.Hash}] ${this.options.Name} onMarshalToViews:`); } return true; } /** * @param {(error?: Error) => void} fCallback */ onMarshalToViewsAsync(fCallback) { this.onMarshalToViews(); return fCallback(); } /** * @return {boolean} */ marshalToViews() { if (this.pict.LogNoisiness > 2) { this.log.trace(`PictApp [${this.UUID}]::[${this.Hash}] ${this.options.Name} executing marshalToViews() function...`); } this.onBeforeMarshalToViews(); // Now walk through any loaded views and initialize them as well. let tmpLoadedViews = Object.keys(this.pict.views); let tmpViewsToMarshalToViews = []; for (let i = 0; i < tmpLoadedViews.length; i++) { let tmpView = this.pict.views[tmpLoadedViews[i]]; tmpViewsToMarshalToViews.push(tmpView); } for (let i = 0; i < tmpViewsToMarshalToViews.length; i++) { tmpViewsToMarshalToViews[i].marshalToView(); } this.onMarshalToViews(); this.onAfterMarshalToViews(); this.lastMarshalToViewsTimestamp = this.fable.log.getTimeStamp(); return true; } /** * @param {(error?: Error) => void} fCallback */ marshalToViewsAsync(fCallback) { let tmpAnticipate = this.fable.instantiateServiceProviderWithoutRegistration('Anticipate'); // Allow the callback to be passed in as the last parameter no matter what let tmpCallback = typeof fCallback === 'function' ? fCallback : false; if (!tmpCallback) { this.log.warn(`PictApp [${this.UUID}]::[${this.Hash}] ${this.options.Name} marshalToViewsAsync was called without a valid callback. A callback will be generated but this could lead to race conditions.`); tmpCallback = pError => { if (pError) { this.log.error(`PictApp [${this.UUID}]::[${this.Hash}] ${this.options.Name} marshalToViewsAsync Auto Callback Error: ${pError}`, pError); } }; } tmpAnticipate.anticipate(this.onBeforeMarshalToViewsAsync.bind(this)); // Walk through any loaded views and marshalToViews them as well. let tmpLoadedViews = Object.keys(this.pict.views); let tmpViewsToMarshalToViews = []; for (let i = 0; i < tmpLoadedViews.length; i++) { let tmpView = this.pict.views[tmpLoadedViews[i]]; tmpViewsToMarshalToViews.push(tmpView); } for (let i = 0; i < tmpViewsToMarshalToViews.length; i++) { tmpAnticipate.anticipate(tmpViewsToMarshalToViews[i].marshalToViewAsync.bind(tmpViewsToMarshalToViews[i])); } tmpAnticipate.anticipate(this.onMarshalToViewsAsync.bind(this)); tmpAnticipate.anticipate(this.onAfterMarshalToViewsAsync.bind(this)); tmpAnticipate.wait(pError => { if (this.pict.LogNoisiness > 2) { this.log.trace(`PictApp [${this.UUID}]::[${this.Hash}] ${this.options.Name} marshalToViewsAsync() complete.`); } this.lastMarshalToViewsTimestamp = this.fable.log.getTimeStamp(); return tmpCallback(pError); }); } /** * @return {boolean} */ onAfterMarshalToViews() { if (this.pict.LogNoisiness > 3) { this.log.trace(`PictApp [${this.UUID}]::[${this.Hash}] ${this.options.Name} onAfterMarshalToViews:`); } return true; } /** * @param {(error?: Error) => void} fCallback */ onAfterMarshalToViewsAsync(fCallback) { this.onAfterMarshalToViews(); return fCallback(); } /* -------------------------------------------------------------------------- */ /* Code Section: Render View */ /* -------------------------------------------------------------------------- */ /** * @return {boolean} */ onBeforeRender() { if (this.pict.LogNoisiness > 3) { this.log.trace(`PictApp [${this.UUID}]::[${this.Hash}] ${this.options.Name} onBeforeRender:`); } return true; } /** * @param {(error?: Error) => void} fCallback */ onBeforeRenderAsync(fCallback) { this.onBeforeRender(); return fCallback(); } /** * @param {string} [pViewIdentifier] - The hash of the view to render. By default, the main viewport view is rendered. * @param {string} [pRenderableHash] - The hash of the renderable to render. * @param {string} [pRenderDestinationAddress] - The address where the renderable will be rendered. * @param {string} [pTemplateDataAddress] - The address where the data for the template is stored. * * TODO: Should we support objects for pTemplateDataAddress for parity with pict-view? */ render(pViewIdentifier, pRenderableHash, pRenderDestinationAddress, pTemplateDataAddress) { let tmpViewIdentifier = typeof pViewIdentifier !== 'string' ? this.options.MainViewportViewIdentifier : pViewIdentifier; let tmpRenderableHash = typeof pRenderableHash !== 'string' ? this.options.MainViewportRenderableHash : pRenderableHash; let tmpRenderDestinationAddress = typeof pRenderDestinationAddress !== 'string' ? this.options.MainViewportDestinationAddress : pRenderDestinationAddress; let tmpTemplateDataAddress = typeof pTemplateDataAddress !== 'string' ? this.options.MainViewportDefaultDataAddress : pTemplateDataAddress; if (this.pict.LogControlFlow) { this.log.trace(`PICT-ControlFlow APPLICATION [${this.UUID}]::[${this.Hash}] ${this.options.Name} VIEW Renderable[${tmpRenderableHash}] Destination[${tmpRenderDestinationAddress}] TemplateDataAddress[${tmpTemplateDataAddress}] render:`); } this.onBeforeRender(); // Now get the view (by hash) from the loaded views let tmpView = typeof tmpViewIdentifier === 'string' ? this.servicesMap.PictView[tmpViewIdentifier] : false; if (!tmpView) { this.log.error(`PictApp [${this.UUID}]::[${this.Hash}] ${this.options.Name} could not render from View ${tmpViewIdentifier} because it is not a valid view.`); return false; } this.onRender(); tmpView.render(tmpRenderableHash, tmpRenderDestinationAddress, tmpTemplateDataAddress); this.onAfterRender(); return true; } /** * @return {boolean} */ onRender() { if (this.pict.LogNoisiness > 3) { this.log.trace(`PictApp [${this.UUID}]::[${this.Hash}] ${this.options.Name} onRender:`); } return true; } /** * @param {(error?: Error) => void} fCallback */ onRenderAsync(fCallback) { this.onRender(); return fCallback(); } /** * @param {string|((error?: Error) => void)} pViewIdentifier - The hash of the view to render. By default, the main viewport view is rendered. (or the callback) * @param {string|((error?: Error) => void)} [pRenderableHash] - The hash of the renderable to render. (or the callback) * @param {string|((error?: Error) => void)} [pRenderDestinationAddress] - The address where the renderable will be rendered. (or the callback) * @param {string|((error?: Error) => void)} [pTemplateDataAddress] - The address where the data for the template is stored. (or the callback) * @param {(error?: Error) => void} [fCallback] - The callback, if all other parameters are provided. * * TODO: Should we support objects for pTemplateDataAddress for parity with pict-view? */ renderAsync(pViewIdentifier, pRenderableHash, pRenderDestinationAddress, pTemplateDataAddress, fCallback) { let tmpViewIdentifier = typeof pViewIdentifier !== 'string' ? this.options.MainViewportViewIdentifier : pViewIdentifier; let tmpRenderableHash = typeof pRenderableHash !== 'string' ? this.options.MainViewportRenderableHash : pRenderableHash; let tmpRenderDestinationAddress = typeof pRenderDestinationAddress !== 'string' ? this.options.MainViewportDestinationAddress : pRenderDestinationAddress; let tmpTemplateDataAddress = typeof pTemplateDataAddress !== 'string' ? this.options.MainViewportDefaultDataAddress : pTemplateDataAddress; // Allow the callback to be passed in as the last parameter no matter what let tmpCallback = typeof fCallback === 'function' ? fCallback : typeof pTemplateDataAddress === 'function' ? pTemplateDataAddress : typeof pRenderDestinationAddress === 'function' ? pRenderDestinationAddress : typeof pRenderableHash === 'function' ? pRenderableHash : typeof pViewIdentifier === 'function' ? pViewIdentifier : false; if (!tmpCallback) { this.log.warn(`PictApp [${this.UUID}]::[${this.Hash}] ${this.options.Name} renderAsync was called without a valid callback. A callback will be generated but this could lead to race conditions.`); tmpCallback = pError => { if (pError) { this.log.error(`PictApp [${this.UUID}]::[${this.Hash}] ${this.options.Name} renderAsync Auto Callback Error: ${pError}`, pError); } }; } if (this.pict.LogControlFlow) { this.log.trace(`PICT-ControlFlow APPLICATION [${this.UUID}]::[${this.Hash}] ${this.options.Name} VIEW Renderable[${tmpRenderableHash}] Destination[${tmpRenderDestinationAddress}] TemplateDataAddress[${tmpTemplateDataAddress}] renderAsync:`); } let tmpRenderAnticipate = this.fable.newAnticipate(); tmpRenderAnticipate.anticipate(this.onBeforeRenderAsync.bind(this)); let tmpView = typeof tmpViewIdentifier === 'string' ? this.servicesMap.PictView[tmpViewIdentifier] : false; if (!tmpView) { let tmpErrorMessage = `PictApp [${this.UUID}]::[${this.Hash}] ${this.options.Name} could not asynchronously render from View ${tmpViewIdentifier} because it is not a valid view.`; if (this.pict.LogNoisiness > 3) { this.log.error(tmpErrorMessage); } return tmpCallback(new Error(tmpErrorMessage)); } tmpRenderAnticipate.anticipate(this.onRenderAsync.bind(this)); tmpRenderAnticipate.anticipate(fNext => { tmpView.renderAsync.call(tmpView, tmpRenderableHash, tmpRenderDestinationAddress, tmpTemplateDataAddress, fNext); }); tmpRenderAnticipate.anticipate(this.onAfterRenderAsync.bind(this)); return tmpRenderAnticipate.wait(tmpCallback); } /** * @return {boolean} */ onAfterRender() { if (this.pict.LogNoisiness > 3) { this.log.trace(`PictApp [${this.UUID}]::[${this.Hash}] ${this.options.Name} onAfterRender:`); } return true; } /** * @param {(error?: Error) => void} fCallback */ onAfterRenderAsync(fCallback) { this.onAfterRender(); return fCallback(); } /** * @return {boolean} */ renderMainViewport() { if (this.pict.LogControlFlow) { this.log.trace(`PICT-ControlFlow APPLICATION [${this.UUID}]::[${this.Hash}] ${this.options.Name} renderMainViewport:`); } return this.render(); } /** * @param {(error?: Error) => void} fCallback */ renderMainViewportAsync(fCallback) { if (this.pict.LogControlFlow) { this.log.trace(`PICT-ControlFlow APPLICATION [${this.UUID}]::[${this.Hash}] ${this.options.Name} renderMainViewportAsync:`); } return this.renderAsync(fCallback); } /** * @return {void} */ renderAutoViews() { if (this.pict.LogNoisiness > 0) { this.log.trace(`PictApp [${this.UUID}]::[${this.Hash}] ${this.options.Name} beginning renderAutoViews...`); } // Now walk through any loaded views and sort them by the AutoRender ordinal let tmpLoadedViews = Object.keys(this.pict.views); // Sort the views by their priority // If they are all the default priority 0, it will end up being add order due to JSON Object Property Key order stuff tmpLoadedViews.sort((a, b) => { return this.pict.views[a].options.AutoRenderOrdinal - this.pict.views[b].options.AutoRenderOrdinal; }); for (let i = 0; i < tmpLoadedViews.length; i++) { let tmpView = this.pict.views[tmpLoadedViews[i]]; if (tmpView.options.AutoRender) { tmpView.render(); } } if (this.pict.LogNoisiness > 0) { this.log.trace(`PictApp [${this.UUID}]::[${this.Hash}] ${th