Parse
File Parse core/head.js
This tree is parsed live from the source file.
Classes
-
{{ item.name }}
- {{ key }}
Not Classes
{{ getTree() }}
Comments
{{ getTreeComments() }}
Source
/*
The _head_ represents the library and its accessibles. In the browser this manifests as the
first object to loadout: `Polypoint`.
The head contains a range of hoisting functions to late-load installables.
## Quick Guide
Install a class:
Polypoint.head.install(PointListPen)
Deferred prop (called once to generate):
class PointConstraints {}
Polypoint.head.deferredProp('Point',
function constraint() {
return new PointConstraints(this)
}
);
Install Functions:
Polypoint.installFunctions('Point', {
track(other, settings) {
return constraints.distance(other, this, settings)
}
, leash(other, settings) {
return constraints.within(other, this, settings)
}
});
Install more complex Mixins (defineProperties):
Polypoint.head.mixin('Point', {
isNaN: {
value(any=false) {
//...
return false
}
, writable: true
}
, distanceTo: {
value(other) {
return distance(this, other)
}
}
, distance2D: {
value(other) {
return distance2D(this, other)
}
}
})
## Usage
1. Add this file
2. Load assets with `Polypoint.head.install()` ...
Once loaded, assets are available through the same object `Polypoint.MyClass`
## Example
We can load standardclasses:
class A {
foo() {
return 'foo'
}
}
class B extends A {
bar() {
return 'bar'
}
}
class C extends B {
baz() {
return 'baz'
}
}
Polypoint.head.install(A)
Polypoint.head.install(B)
Polypoint.head.install(C)
Once loaded, we can access them within the object:
Polypoint.A
// class A ...
And target the loaded classes for live property mixin:
Polypoint.head.mixin('C', {
one: {
get() {
return 'one'
}
}
})
c = new C;
c.one == 'one'
This can occur _late_, and decent through dependant children:
// Property on _c_ does not exist yet.
c.two == undefined
// Load a property into "B"
Polypoint.head.mixin('B', {
two: {
get() {
return 'two'
}
}
})
// New B as the new property.
b = new B;
b.two == 'two'
// Existing "C" instances gain the new property.
c.two == 'two'
This works for many mixins:
// Another late mixin, targeting the root class
Polypoint.head.mixin('A', {
three: {
get() {
return 'three'
}
}
})
// All (a,b,c) instances gain the new property
(new A).three == 'three'
b.three == 'three'
c.three == 'three'
*/
;(function(parent, name=undefined, debug=false, strict=true){
var dlog = debug?console.log.bind(console): ()=>{}
try{
if(logger) {
dlog = logger.create("head")
}
} catch{}
const waiting = {}
const currentScr = document.currentScript
const currentLoc = document.currentScript.src
/* Options to configure the lib. Append with `lib.cofigure(d)` */
const exposedConfig = { debug, strict, waiting, dlog }
/*
Test the parent during entry for the exposed `name`. This asset parking
on the expected name. In this current form its used as a early config for
library config.
However this can also serve as a protection from accidental deletion.
*/
let parkedEntity = undefined;
const resolveName = function(n){
/* Return the name, by order:
Attribute `name`
Dataset `name`
hash value `#name`
~filename `name.js`~
Polypoint (default-name)
*/
if(n === undefined) {
/* Grab the `name` from the script, then the `data-name` */
n = currentScr.dataset.name
const attr = currentScr.attributes.name
if(attr) {
n = attr.value
};
}
if(n === undefined) {
// Check for the HASH name,
let src = currentScr.src;
let u = new URL(src).hash.slice(1)
if(u.length > 0) {
n = u;
}
/* default to the filename.*/
// src.split('/').pop()
}
if(n === undefined) {
n = 'Polypoint'
}
return n;
}
name = resolveName(name)
if(parent[name] !== undefined) {
dlog('Parked asset on', name)
parkedEntity = parent[name]
}
/*
The Polypoint.file object, to assign meta data to the concurrent file.
Note this is exposed on the primary object, and not the 'head'
*/
const fileObject = {
meta(data) {
/* Incoming mets data for the incoming file.*/
dlog('meta config', data)
if(data.files) {
let src = document.currentScript
dlog(src)
}
}
}
/* Install properties onto an incoming unit
Polypoint.mixin('Point', {
_draggable: {
value: true,
writable: true
}
, draggable: {
get() {
return this._draggable
}
}
})
this.center.draggable == true
this.center._draggable = false
this.center.draggable == false
*/
const mixin = function(target, addon, targetPrototype=true) {
const targetName = target.getMixinTarget? target.getMixinTarget(): target
if(exposed[targetName]) {
dlog(`Installing mixin for "${targetName}"`)
populateAddon(targetName, addon, targetPrototype)
return
}
dlog('Mixin Waiting for unit', targetName)
if(waiting[targetName] == undefined) {
waiting[targetName] = []
}
waiting[targetName].push(addon)
}
/* Install static methods:
Polypoint.static('Point', {
mouse: {
value: autoMouse
// , writable: true
// , enumerable: false
// , configurable: true
}
})
Point.mouse == autoMouse
(new Point).mouse == undefined
*/
const staticMixin = function(target, addon) {
return mixin(target, addon, false)
}
/*
Install many static functions. Expect:
Polypoint.head.staticFunctions('PointList', {
fromJSON(text) {
let output = JSON.parse(text)
return (PointList.from(output)).cast()
}
})
Install:
Polypoint.head.static('PointList', {
fromJSON: {
value: function(text) {
let output = JSON.parse(text)
return (PointList.from(output)).cast()
}
}
})
*/
const staticFunctions = function(target, methods) {
let def = {}
for(let k in methods) {
let func = methods[k]
def[k] = {
value: func
, writable: true
}
}
return mixin(target, def, false) //targetPrototype=false
// return mixin(target, methods, false)
// return mixin(name, def)
}
const installMap = new Map;
const dispatch = function(eventType, entity) {
let name = `Polypoint:${eventType}`
let detail = {
entity
}
dispatchEvent(new CustomEvent(name, {detail}))
}
/* "Install" an entity, allowing the automated mixin population
And Polypoint calling.
Note, this isn't required - but without it, the autoloading mechanism
won't function
Polypoint.install(Point)
Polypoint.installed
{ "Point" }
When applied, we can use mixins:
Polypoint.mixin('Point', {
foo: { value: 1000 }
})
This can work in any order; late or early.
*/
const install = function(entity, name=undefined) {
if(name == undefined) {
/* Expecting a _thing_ with a name, such as a class; Point */
name = entity.name
}
// dlog(`Installing entity "${name}"`)
dispatch('install', entity)
exposed[name] = entity;
if(waiting[name] != undefined) {
// loop install
populateAddons(name, waiting[name])
}
/*Install to a map so we can infer later.
Polypoint.installed
[...]
*/
installMap.set(name, entity)
}
const installMany = function() {
/* Receive many items to install
installMany(A, B, C)
*/
Array.from(arguments).forEach(C => install(C))
}
const populateAddons = function(name, items, targetPrototype=true) {
dlog('Installing addons', items.length, 'to', name)
for (var i = 0; i < items.length; i++) {
populateAddon(name, items[i], targetPrototype)
}
}
const populateAddon = function(name, item, targetPrototype=true) {
dlog('populateAddon', name, item)
let Klass = exposed[name]
let proto = targetPrototype? Klass.prototype.constructor.prototype: Klass
// let proto = Object.getPrototypeOf(head[name])
let r = {}
// proto[name] = item
let names = Object.getOwnPropertyNames(item);
for(let name of names) {
// define(proto, name, { value: item, writable: false})
try {
Object.defineProperties(proto, item)
r[name] = true
} catch(e) {
r[name] = false
}
}
return r
}
const define = function(proto, name, def) {
Object.defineProperty(proto, name, def)
// Object.defineProperties(proto, name )
}
/*
Assume many functions to install:
Polypoint.installFunctions('Point', {
track(other, settings) {
return constraints.distance(other, this, settings)
}
, leash(other, settings) {
return constraints.within(other, this, settings)
}
});
synonymous to:
Polypoint.mixin('Point', {
track: {
value(any=false) {
/// ...
}
, writable: true
}
, leash: { ... }
})
*/
const installFunctions = function(name, functionsDict) {
let def = {}
for(let k in functionsDict) {
let func = functionsDict[k]
def[k] = {
value: func
, writable: true
}
}
return mixin(name, def)
}
/*
Assume many correctly named functions to access values on first call.
e.g from:
Polypoint.mixin('Point', {
pen: {
get() {
let r = this._pen
if(r == undefined) {
r = new PointPen(this)
this._pen = r
}
return r
}
}
})
to:
Polypoint.lazyProp('Point', {
pen() {
let r = this._pen
if(r == undefined) {
r = new PointPen(this)
this._pen = r
}
return r
}
})
stage.center._pen == undefined
stage.center.pen == Pen
stage.center._pen == Pen
*/
const lazyProp = function(name, propsDict) {
// dlog('lazyProp', name, Object.keys(propsDict))
dispatch('install:lazyProp', {name, propsDict})
let def = {}
for(let key in propsDict) {
let propName = key
let val = propsDict[key]
def[propName] = { get: val }
}
return mixin(name, def)
}
/*
Note: This is a singleton.
Polypoint.lazierProp(name,
function screenshot() {
return new Screenshot(this)
}
);
Synonymous to:
const scope = Polypoint
Polypoint.lazyProp('Stage', {
screenshot() {
let s = scope._screenshot;
if(s) { return s };
scope._screenshot = new Screenshot(this)
return scope._screenshot
}
})
*/
const lazierProp = function(name, method, reference) {
let methodName = reference==undefined? method.name: reference
let target = exposed;
lazyProp(name, {
[methodName]() {
let innerName = `_${methodName}`
let s = exposed[innerName];
if(s) { return s };
return exposed[innerName] = method.bind(this)()
// return this[innerName]
}
})
}
/* Apply a lazy getter property to a target to return an instance of a thing.
The instance of the thing, is created _once_ on-demand, on the target.
Future calls return the first created object.
Polypoint.head.deferredProp('Point', function screenshot() {
return new Screenshot(this)
}
})
*/
const deferredProp = function(name, method, reference) {
let methodName = reference==undefined? method.name: reference
return lazyProp(name, {
[methodName]() {
let innerName = `_${methodName}`
let s = this[innerName];
if(s) { return s };
return this[innerName] = method.bind(this)()
// return this[innerName]
}
})
}
const load = function(name, callback){
/* A shortcut for loading a stub*/
return ljs.load(name, function() {
dlog('Loaded', name, arguments);
return callback && callback()
})
}
/* Load data into the 'head', allowing for a _preconfigure_ before the
main loading sequences.
Note this function is configured to run once, and does not account for
repeat calls, and does not deep-merge the data dictionary with the
existing configuration */
const configure = function(data) {
let r = Object.assign(exposedConfig, data)
let files = data.files
if(files) {
if(typeof(files) == 'function') {
/* a function expects the srcPath*/
let srcPath = r.srcPath;
if(srcPath == undefined) {
console.warn('No "srcPath" given, assuming no path: ""')
srcPath = ''
}
assets = files(srcPath)
ljs.addAliases(assets)
}
}
if(data.load) {
// a load is requested
load(data.load, data.onLoad)
}
return r
}
const head = {
add: function(func, scope) {
let obj = exposed[scope]
if(obj == undefined) {
obj = exposed[scope] = {}
}
obj[func.name] = func
}
, config: exposedConfig
, configure
, load
, staticFunctions
, static: staticMixin
, mixin, install, installMany
, installFunctions
, define
, lazyProp, lazierProp, deferredProp
/* Return a map iterator of the installed items.*/
, get installed() {
return installMap.keys()
}
}
const depSet = new Set;
const printOnce = function(methodName) {
if(depSet.has(methodName)){
return
}
let old = `${name}.${methodName}`
let _new = `${name}.head.${methodName}`
depSet.add(methodName)
console.warn(`Deprecated: "${old}()", Use "${_new}()"`)
}
/* Push a warning log upon access of the function.
The log is printed once. */
const deprecate = function(innerFunc, name) {
const methodName = name || innerFunc.name;
let f = function(){
printOnce(methodName)
exposed[methodName] = innerFunc
return innerFunc.apply(exposed, arguments)
}
f.name = methodName
return f
}
const exposed = {
ready: false
, head
, file: fileObject
}
if(!strict){
/* When not in strict mode, the exposed functions are applied to the
root*/
Object.assign(exposed, {
mixin: deprecate(mixin)
, static: staticMixin
, install: deprecate(install)
, installFunctions: deprecate(installFunctions)
, define: deprecate(define)
, lazyProp: deprecate(lazyProp)
, lazierProp: deprecate(lazierProp)
})
}
class Stub {
/* The stub applies a reference to a non-existence [future] class.
Allowing the extension of future classes when they appear.
*/
constructor(prop, history=[]) {
this.assignedName = prop
this.history = history
const proxy = new Proxy(this, {
get(target, property, receiver) {
if(Reflect.has(target, property)){
return Reflect.get(target, property, receiver)
}
return target.getUndefined(target, property, receiver)
},
});
return proxy;
}
getMixinTarget() {
/* This unit was given in place of a mixin definition.
Return the _name_ of the target*/
return this.assignedName
}
getUndefined(target, property, receiver) {
dlog('get unknown', property, 'on stub')
this.history.push(this.assignedName)
return new this.constructor(property, this.history)
}
get(v) {
dlog(v)
}
get [Symbol.toStringTag]() {
return this.toString()
}
[Symbol.toPrimitive](hint) {
if (hint === 'string') {
return this.toString()
}
return Reflect.apply(...arguments)
}
toString(){
return this.assignedName;
}
}
const exposedProxyHandler = {
get(target, prop, receiver) {
/* Upon _get_ of a property within the exposed object,
we first test to ensure the target value exists,
If not, return a stub allowing capture and import.
Else the stub can be reactive to usage.
If true, return the prop object.
*/
if(!Reflect.has(target, prop)) {
return this.getUnknown(target, prop, receiver)
}
// dlog('Exposed Get', prop)
return Reflect.get(target, prop, receiver)
}
, getUnknown(target, prop, receiver) {
/* A request to a property of which does not exist (yet).
Return a stub in place of the requested future import.
*/
// console.warn('Unknown prop response', prop)
return new Stub(prop)
}
}
const exposureProxy = new Proxy(exposed, exposedProxyHandler)
parent[name] = exposureProxy;
}).apply({}, [this, undefined, true]);
copy