nicescript
Advanced tools
Comparing version 0.3.3 to 0.4.0
114
build.js
@@ -0,2 +1,11 @@ | ||
//TODO: nicescript.org | ||
////TODO: automaticaly unsubscribe reactive boxes //??resubscribe | ||
//TODO: remova singular nice values (e.g. nice.Num()) or make them useful | ||
const fs = require('fs'); | ||
const espree = require('espree'); | ||
const estraverse = require('estraverse'); | ||
const terser = require("terser"); | ||
@@ -8,33 +17,55 @@ const wrap = s => '\n(function(){"use strict";' + s + '\n})();'; | ||
const pachageInfo = JSON.parse(fs.readFileSync('./package.json')); | ||
const order = [ | ||
'core/core', | ||
'core/reflect', | ||
'core/utils', | ||
'doc/doc_lib', | ||
'core/utils_object', | ||
'core/events', | ||
'core/js_type', | ||
'core/anything', | ||
'core/type', | ||
'core/compile_function', | ||
'core/function', | ||
'core/tests', | ||
'core/is', | ||
'core/expect', | ||
'core/type', | ||
'core/simple_types', | ||
'core/spy', | ||
'box/data_source', | ||
'box/box', | ||
// 'box/lazy_box', | ||
'box/box_set', | ||
'box/box_map', | ||
'box/box_array', | ||
'box/r_box', | ||
'box/interval_box', | ||
'box/box_index', | ||
// 'box/box_sorted_map', | ||
// 'box/model', | ||
'box/row_model', | ||
'box/row_model_proxy', | ||
'box/row_model_tests', | ||
// 'core/stream', TODO: change to comply with Box(undefined) | ||
'core/logic', | ||
'core/value', | ||
'types/object', | ||
'types/box', | ||
'types/error', | ||
'types/obj', | ||
'types/err', | ||
'types/single', | ||
'types/array', | ||
'types/number', | ||
'types/string', | ||
'types/boolean', | ||
// 'core/interface', | ||
'types/str', | ||
'types/arr', | ||
'types/num', | ||
'types/bool', | ||
'types/range', | ||
'html/html', | ||
'html/tags', | ||
'html/inputs' | ||
'html/inputs', | ||
'html/routing', | ||
'core/core_tests' | ||
]; | ||
let src = ';let nice;(function(){let create,Div,Func,Switch,expect,is,_each,def,defAll,defGet,Box,Action,Mapping,Check;' + | ||
let src = 'let nice;(function(){const IS_BROWSER = typeof window !== "undefined";let create,Div,NotFound,Func,Test,Switch,expect,is,_each,def,defAll,defGet,Anything,Action,Mapping,Check,reflect,Err,each;' + | ||
order.map(name => fs.readFileSync('./' + name + '.js')) | ||
@@ -46,12 +77,59 @@ .map(wrap) | ||
src += ';})();'; | ||
src += `;nice.version = "${pachageInfo.version}";})();`; | ||
fs.writeFileSync('nice.js', src); | ||
const files = { 'nice.js': src }; | ||
//TODO: BUG: donesn't work: "Uncaught SyntaxError: Unexpected token ';'" | ||
//const srcNoTests = removeTests(src); | ||
fs.writeFileSync( | ||
'index.js', | ||
'module.exports = function(){' + src + '; return nice;}' | ||
); | ||
//TODO: fix or remove | ||
//terser.minify(removeTests(src)) | ||
// .then(min => fs.writeFileSync('nice.min.js', min.code)); | ||
const nodeSrc = 'module.exports = function(){' + src + '; return nice;}'; | ||
files['index.js'] = nodeSrc; | ||
fs.writeFileSync('index.js', nodeSrc); | ||
const nice = require('./index.js')(); | ||
fs.writeFileSync('./doc/doc.json', JSON.stringify(nice.doc())); | ||
const blackList = ['class', 'try', 'with','super']; | ||
files['nice.mjs'] = | ||
src + '; export let ' | ||
+ Object.getOwnPropertyNames(nice) | ||
.filter(k => !blackList.includes(k)) | ||
.map(k => `${k} = nice.${k}`).join(',') | ||
+ '; export default nice;'; | ||
for(let name in files){ | ||
fs.writeFileSync(name, files[name]); | ||
const distDir = '../nicescript'; | ||
if(fs.existsSync(distDir)){ | ||
fs.writeFileSync(distDir + '/' + name, files[name]); | ||
} | ||
}; | ||
nice.Test.run(); | ||
fs.writeFileSync('./doc/doc.json', JSON.stringify(nice.generateDoc())); | ||
function removeTests(source) { | ||
const nodes = []; | ||
const tree = espree.parse(source, { ecmaVersion: 2022 }); | ||
estraverse.traverse(tree, { | ||
enter: function (node, parent) { | ||
const f = node.callee; | ||
if(node.type === 'CallExpression' && f.name === 'Test' | ||
&& f.object === undefined) { | ||
nodes.push(node); | ||
return estraverse.VisitorOption.Skip; | ||
} | ||
}, | ||
}); | ||
nodes.sort((a, b) => b.end - a.end).forEach(n => { | ||
source = source.slice(0, n.start) + source.slice(n.end); | ||
}); | ||
return source; | ||
} |
175
core/core.js
nice = (...a) => { | ||
if(a.length === 0) | ||
return nice.Object(); | ||
return reflect.createItem(Anything); | ||
if(a.length > 1) | ||
return nice.Array(...a); | ||
return nice.Arr(...a); | ||
if(Array.isArray(a[0])) | ||
return nice.Array(...a[0]); | ||
return nice.Arr(...a[0]); | ||
if(a[0] === null) | ||
return nice.NULL; | ||
return nice.Null(); | ||
if(a[0] === undefined) | ||
return nice.UNDEFINED; | ||
return nice.Undefined(); | ||
@@ -23,3 +23,2 @@ if(a[0]._type) | ||
Object.defineProperty(nice, 'define', { value: (target, name, value) => { | ||
@@ -32,3 +31,3 @@ if(value === undefined && typeof name === 'function'){ | ||
if(value === undefined) | ||
throw 'No value'; | ||
throw new Error('No value'); | ||
@@ -42,4 +41,2 @@ Object.defineProperty(target, name, { value }); | ||
def(nice, 'defineAll', (target, o) => { | ||
@@ -55,70 +52,42 @@ for(let i in o) | ||
defAll(nice, { | ||
SOURCE_ERROR: 'Source error', | ||
LOCKED_ERROR: 'Item is closed for modification', | ||
TYPE_KEY: '_nt_', | ||
curry: (f, arity = f.length) =>(...a) => a.length >= arity | ||
? f(...a) | ||
: nice.curry((...a2) => f(...a, ...a2), arity - a.length), | ||
checkers: {}, | ||
checkFunctions: {}, | ||
collectionReducers: {}, | ||
itemTitle: i => i._type || i.name || (i.toString && i.toString()) || ('' + i), | ||
createItem: ({ type, data, assign }, ...a) => { | ||
type = nice.type(type); | ||
const item = create(type.proto, type.creator()); | ||
'name' in type.proto && nice.eraseProperty(item, 'name'); | ||
'length' in type.proto && nice.eraseProperty(item, 'length'); | ||
assign && Object.assign(item, assign); | ||
if(data){ | ||
item.setResult(data); | ||
} else { | ||
type.defaultValue && item.setResult(type.defaultValue(item)); | ||
type.constructor && type.constructor(item, ...a); | ||
'try': (f, ...as) => { | ||
try { | ||
return f(...as); | ||
} catch (e) { | ||
return nice.Err(e); | ||
} | ||
return item; | ||
}, | ||
fromItem: i => i._type.saveValue(i.getResult()), | ||
toItem: v => { | ||
valueType: v => { | ||
const t = typeof v; | ||
if(v === undefined) | ||
return nice.UNDEFINED; | ||
return nice.Undefined; | ||
if(v === null) | ||
return nice.NULL; | ||
return nice.Null; | ||
const type = nice.valueType(v); | ||
if(t === 'number') | ||
return Number.isNaN(v) ? nice.NumberError : nice.Num; | ||
if(type === nice.Box || type === nice.function) | ||
return v; | ||
if(t === 'function') | ||
return nice.Func; | ||
return nice.createItem({ type, data: type.loadValue(v)}); | ||
}, | ||
if(t === 'string') | ||
return nice.Str; | ||
if(t === 'boolean') | ||
return nice.Bool; | ||
valueType: v => { | ||
if(typeof v === 'number') | ||
return nice.Number; | ||
if(typeof v === 'function') | ||
return nice.function; | ||
if(typeof v === 'string') | ||
return nice.String; | ||
if(typeof v === 'boolean') | ||
return nice.Boolean; | ||
if(Array.isArray(v)) | ||
return nice.Array; | ||
return nice.Arr; | ||
if(v._nt_ && v.hasOwnProperty('_nv_')) | ||
return nice[v._nt_]; | ||
if(v[nice.TYPE_KEY]) | ||
return nice[v[nice.TYPE_KEY]]; | ||
if(typeof v === 'object') | ||
return nice.Object; | ||
if(t === 'object') | ||
return nice.Obj; | ||
@@ -128,6 +97,5 @@ throw 'Unknown type'; | ||
defineCached: (target, ...a) => { | ||
const [key, f] = a.length === 2 ? a : [a[0].name, a[0]]; | ||
Object.defineProperty(target, key, { configurable: true, get: function (){ | ||
Object.defineProperty(target, key, { configurable: true, get (){ | ||
let value = f.apply(this); | ||
@@ -139,3 +107,6 @@ def(this, key, value); | ||
defineGetter: (o, n, get) => Object.defineProperty(o, n, { get, enumerable: true }), | ||
defineGetter: (o, ...a) => { | ||
const [key, get] = a.length === 2 ? a : [a[0].name, a[0]]; | ||
return Object.defineProperty(o, key, { get, enumerable: true }); | ||
}, | ||
@@ -148,20 +119,9 @@ with: (o, f) => o === nice | ||
types: {}, | ||
registerType: function(type){ | ||
const title = type.title; | ||
title[0] !== title[0].toUpperCase() && | ||
nice.error('Please start type name with a upper case letter'); | ||
nice.types[title] = type; | ||
def(nice, title, type); | ||
nice.emitAndSave('Type', type); | ||
}, | ||
_each: (o, f) => { | ||
if(o) | ||
for(let i in o) | ||
if(f(o[i], i) === nice.STOP) | ||
break; | ||
if(i !== nice.TYPE_KEY) | ||
f(o[i], i); | ||
// if(nice.isStop(f(o[i], i))) | ||
// break; | ||
return o; | ||
@@ -184,7 +144,60 @@ }, | ||
isEnvBrowser: typeof window !== 'undefined', | ||
serialize: v => { | ||
if(v && v._isAnything) { | ||
const type = v._type.name; | ||
v = { [nice.TYPE_KEY]: type, value: nice.serialize(v()) }; | ||
} else { | ||
if(v && typeof v === 'object'){ | ||
_each(v, (_v, k) => v[k] = nice.serialize(_v)); | ||
} | ||
} | ||
return v; | ||
}, | ||
unwrap: v => is.nice(v) ? v() : v | ||
deserialize: js => { | ||
const niceType = js && js[nice.TYPE_KEY]; | ||
if(niceType){ | ||
return nice[niceType].deserialize(js.value); | ||
} else if (js && typeof js === 'object'){ | ||
_each(js, (v, k) => { | ||
js[k] = nice.deserialize(v); | ||
}); | ||
} | ||
return js; | ||
}, | ||
apply: (o, f) => { | ||
f(o); | ||
return o; | ||
}, | ||
//TODO: write tests | ||
Pipe: (...fs) => { | ||
fs = fs.map(f => { | ||
if(typeof f === 'string') | ||
return o => o[f]; | ||
if(Array.isArray(f)){ | ||
const as = f.slice(1); | ||
return v => f[0](v, ...as); | ||
} | ||
return f; | ||
}); | ||
return function(res){ | ||
const l = fs.length; | ||
for(let i = 0; i < l; i++){ | ||
res = fs[i](res); | ||
} | ||
return res; | ||
}; | ||
} | ||
}); | ||
defGet = nice.defineGetter; | ||
_each = nice._each; | ||
_each = nice._each; | ||
let autoId = 0; | ||
def(nice, 'AUTO_PREFIX', '_nn_'); | ||
def(nice, 'genereteAutoId', () => nice.AUTO_PREFIX + autoId++); | ||
function assertListeners(o, name){ | ||
const listeners = o.hasOwnProperty('_listeners') | ||
const listeners = '_listeners' in o && o.hasOwnProperty('_listeners') | ||
? o._listeners | ||
@@ -10,3 +10,3 @@ : o._listeners = {}; | ||
function assertEvents(o, name){ | ||
const events = o.hasOwnProperty('_events') | ||
const events = '_events' in o | ||
? o._events | ||
@@ -19,3 +19,3 @@ : o._events = {}; | ||
const EventEmitter = { | ||
_on: function (name, f) { | ||
on (name, f) { | ||
const a = assertListeners(this, name); | ||
@@ -25,4 +25,5 @@ if(!a.includes(f)){ | ||
a.push(f); | ||
let es = this._events; | ||
es && es[name] && es[name].forEach(v => f(...v)); | ||
const es = this._events; | ||
es && es[name] && es[name].forEach(v => | ||
f.notify ? f.notify(...v) : f(...v)); | ||
} | ||
@@ -32,3 +33,3 @@ return this; | ||
onNew: function (name, f) { | ||
onNew (name, f) { | ||
const a = assertListeners(this, name); | ||
@@ -42,14 +43,18 @@ if(!a.includes(f)){ | ||
emit: function (name, ...a) { | ||
this.listeners(name).forEach(f => f.apply(this, a)); | ||
emit (name, ...a) { | ||
this.listeners(name).forEach(f => { | ||
f.notify | ||
? f.notify(...a) | ||
: Function.prototype.apply.apply(f, [this, a]); | ||
}); | ||
return this; | ||
}, | ||
emitAndSave: function (name, ...a) { | ||
emitAndSave (name, ...a) { | ||
assertEvents(this, name).push(a); | ||
this.listeners(name).forEach(f => f.apply(this, a)); | ||
this.emit(name, ...a) | ||
return this; | ||
}, | ||
listeners: function (name) { | ||
listeners (name) { | ||
const listeners = this._listeners; | ||
@@ -66,3 +71,3 @@ let a = (listeners && listeners[name]) || []; | ||
listenerCount: function (name){ | ||
countListeners (name){ | ||
return this._listeners | ||
@@ -75,4 +80,4 @@ ? this._listeners[name] | ||
removeListener: function (name, f) { | ||
if(this.hasOwnProperty('_listeners') && this._listeners[name]){ | ||
off (name, f) { | ||
if('_listeners' in this && this._listeners[name]){ | ||
nice._removeArrayValue(this._listeners[name], f); | ||
@@ -84,4 +89,4 @@ this.emit('removeListener', name, f); | ||
removeAllListeners: function (name) { | ||
if(this.hasOwnProperty('_listeners')){ | ||
removeAllListeners (name) { | ||
if('_listeners' in this){ | ||
const a = this._listeners[name]; | ||
@@ -95,4 +100,5 @@ this._listeners[name] = []; | ||
nice.eventEmitter = o => Object.assign(o, EventEmitter); | ||
create(EventEmitter, nice); | ||
nice.eventEmitter = o => defAll(o, EventEmitter); | ||
def(nice, 'EventEmitter', EventEmitter); | ||
create(EventEmitter, nice.reflect); | ||
reflect = nice.reflect; |
@@ -1,41 +0,129 @@ | ||
def(nice, 'expectPrototype', { | ||
toBe: function(value){ | ||
if(!value) { | ||
if(!this.value) | ||
throw this.message || 'Value expected'; | ||
} else { | ||
if(this.value != value) | ||
throw this.message || value + ' expected'; | ||
} | ||
}, | ||
def(nice, 'expectPrototype', {}); | ||
notToBe: function(value){ | ||
if(!value) { | ||
if(this.value) | ||
throw this.message || 'No value expected'; | ||
} else { | ||
if(this.value == value) | ||
throw this.message || value + ' not expected'; | ||
function isFail (v) { | ||
return !v || (v && v._isAnything && v._type === nice.Err); | ||
}; | ||
function composeCallName(name, a) { | ||
return name + '(' + a.map(showValue).join(', ') + ')'; | ||
}; | ||
function showValue(v) { | ||
if(v === undefined) | ||
return "undefined:undefined"; | ||
if(v === null) | ||
return "null:null"; | ||
const type = typeof v; | ||
let s = '' + ((v && v.toString !== Object.prototype.toString) | ||
? v.toString() : JSON.stringify(v)); | ||
if(type === 'string') | ||
s = '"' + s + '"'; | ||
if(type === 'object'){ | ||
s += ':' + (v._type ? 'nice:' + v._type.name : 'object:' + v.constructor.name); | ||
} else { | ||
s += ':' + type | ||
} | ||
return s; | ||
}; | ||
reflect.on('Check', ({name}) => { | ||
name && (nice.expectPrototype[name] = function(...a){ | ||
const res = this._preF ? this._preF(nice[name](this.value, ...a)) : nice[name](this.value, ...a); | ||
if(isFail(res)){ | ||
const s = this.text || | ||
`Expected (${showValue(this.value)}) ${this._preMessage|| ''} ${composeCallName(name, a)}`; | ||
const e = new Error(s); | ||
e.shift = ('' + s).split('\n').length; | ||
throw e; | ||
} | ||
}, | ||
if(this._postCall !== undefined){ | ||
this._postCall(this, name, a); | ||
} | ||
return this; | ||
}); | ||
}); | ||
toMatch: function(f){ | ||
if(!f(this.value)) | ||
throw this.message || ('Value does not match function ' + f); | ||
def(nice, function expect(value, ...texts){ | ||
return create(nice.expectPrototype, { value, texts, item: this}); | ||
}); | ||
defGet(nice.expectPrototype, function text(){ | ||
return nice.format(...this.texts); | ||
}); | ||
defGet(nice.expectPrototype, function not(){ | ||
this._preF = v => isFail(v); | ||
this._preMessage = 'not'; | ||
this._postCall = z => { | ||
delete z._preF; | ||
delete z._preMessage; | ||
delete z._postCall; | ||
} | ||
return this; | ||
}); | ||
nice._on('Check', f => { | ||
f.name && def(nice.expectPrototype, f.name, function(...a){ | ||
if(!f(this.value, ...a)) | ||
throw this.message || (f.name + ' expected'); | ||
}); | ||
defGet(nice.expectPrototype, function either(){ | ||
this._either = this._either || []; | ||
this._preF = res => { this._either.push(res); return true }; | ||
this._postCall = (z, name, a) => { | ||
this._preMessage = this._preMessage || ''; | ||
this._preMessage += composeCallName(name, a) + ' or '; | ||
}; | ||
return this; | ||
}); | ||
def(nice, function expect(value, message){ | ||
return create(nice.expectPrototype, { value, message, item: this}); | ||
defGet(nice.expectPrototype, function or(){ | ||
this._either = this._either || []; | ||
this._preF = res => { | ||
var b = this._either.some(v => !isFail(v)) || !isFail(res); | ||
return b; | ||
}; | ||
delete this._postCall; | ||
return this; | ||
}); | ||
expect = nice.expect; | ||
def(nice.expectPrototype, function message(...a){ | ||
this.texts = a; | ||
return this; | ||
}); | ||
expect = nice.expect; | ||
Test('Not expect followed by expect.', () => { | ||
expect(1).not.is(3).is(1); | ||
expect(1).is(1).not.is(3); | ||
}); | ||
Test('either or', () => { | ||
expect(1).either.is(2).is(1).or.is(nice.Div()); | ||
expect(1).either.is(1).is(2).or.is(nice.Div()); | ||
expect(1).either.is(5).is(2).or.is(1); | ||
expect('qwe').either.isNumber(2).or.isString(); | ||
expect(1).either.is(2).or.not.is(5); | ||
expect(() => { | ||
expect(4).either.is(2).is(1).or.is(5); | ||
}).throws(); | ||
expect(() => { | ||
expect(1).either.is(2).or.not.is(1); | ||
}).throws(); | ||
}); | ||
@@ -0,10 +1,21 @@ | ||
/* | ||
Functions requirements: | ||
- Store and use simple js function | ||
- //TODO: Use function as a dependence for items created with it(Spy) | ||
- | ||
*/ | ||
//nice.reflect.reportUse = true; | ||
const configProto = { | ||
next: function (o) { | ||
let c = Configurator(this.name || o.name); | ||
next (o) { | ||
const c = Configurator(this.name || o.name); | ||
c.signature = (this.signature || []).concat(o.signature || []); | ||
c.existing = o.existing || this.existing; | ||
c.functionType = o.functionType || this.functionType; | ||
c.returnValue = o.returnValue || this.returnValue; | ||
c.description = o.description || this.description; | ||
['existing', 'functionType', 'returnValue', 'description', 'returns'] | ||
.forEach(k => c[k] = o[k] || this[k]); | ||
@@ -14,29 +25,52 @@ return c; | ||
about: function(s) { return this.next({ description: s}); } | ||
about (s) { return this.next({ description: s}); }, | ||
}; | ||
const skippedProto = {}; | ||
const functionProto = { | ||
addSignature (body, types, returns){ | ||
let ss = 'signatures' in this | ||
? this.signatures | ||
: this.signatures = new Map(); | ||
types && types.forEach(type => { | ||
if(ss.has(type)){ | ||
ss = ss.get(type); | ||
} else { | ||
const s = new Map(); | ||
ss.set(type, s); | ||
ss = s; | ||
} | ||
}); | ||
const functionProto = { | ||
addSignature: function (action, signature){ | ||
if(signature && signature.length){ | ||
const ss = this.signatures = this.signatures || new Map(); | ||
const type = signature[0].type; | ||
ss.has(type) || ss.set(type, createFunctionBody({name: this.name})); | ||
ss.get(type).addSignature(action, signature.slice(1)); | ||
} else { | ||
this.action = action; | ||
if(ss.action){ | ||
throw 'Signature already defined: ' + JSON.stringify(types); | ||
} | ||
ss.action = body; | ||
returns && (ss.returns = returns); | ||
return this; | ||
}, | ||
ary: function (n){ | ||
return (...a) => this(...a.splice(0, n)); | ||
}, | ||
about: function(s) { | ||
return configurator({ description: s }); | ||
} | ||
// ary (n){ | ||
// return (...a) => this(...a.splice(0, n)); | ||
// }, | ||
// | ||
// about (s) { | ||
// return configurator({ description: s }); | ||
// } | ||
}; | ||
defGet(functionProto, 'help', function () { | ||
if(!nice.doc) | ||
nice.doc = nice.generateDoc(); | ||
const a = ['']; | ||
_each(nice.doc.fs[this.name], v => { | ||
a.push(v.title); | ||
v.description && a.push(v.description); | ||
a.push(v.source); | ||
a.push(''); | ||
}); | ||
return a.join('\n'); | ||
}); | ||
const parseParams = (...a) => { | ||
@@ -46,5 +80,5 @@ if(!a[0]) | ||
const [name, action] = a.length === 2 ? a : [a[0].name, a[0]]; | ||
const [name, body] = a.length === 2 ? a : [a[0].name, a[0]]; | ||
return typeof action === 'function' ? { name, action } : a[0]; | ||
return typeof body === 'function' ? { name, body } : a[0]; | ||
}; | ||
@@ -54,41 +88,11 @@ | ||
function toItemType({type}){ | ||
return { type: type.jsType | ||
? nice[type.title[0].toUpperCase() + type.title.substr(1)] | ||
: type }; | ||
return { type: type._isJsType ? nice[type.niceType] : type }; | ||
} | ||
function transform(s){ | ||
s.source = s.action; | ||
if(s.signature.length === 0) | ||
return s; | ||
const types = s.signature; | ||
s.signature = types.map(toItemType); | ||
s.action = (...a) => { | ||
const l = types.length; | ||
for(let i = 0; i < l; i++){ | ||
const isNice = a[i] && a[i]._isAnything; | ||
const needJs = types[i].type.jsType; | ||
if(needJs && isNice){ | ||
a[i] = a[i].getResult(); | ||
} else if(!needJs && !isNice){ | ||
a[i] = nice.toItem(a[i]); | ||
} | ||
} | ||
return s.source(...a); | ||
}; | ||
def(s.action, 'length', s.source.length); | ||
return s; | ||
} | ||
function Configurator(name){ | ||
const z = create(configProto, (...a) => { | ||
const { name, action, signature } = parseParams(...a); | ||
const res = createFunction(transform({ | ||
const { name, body, signature } = parseParams(...a); | ||
const res = createFunction({ | ||
returns: z.returns, | ||
description: z.description, | ||
@@ -98,11 +102,9 @@ type: z.functionType, | ||
name: z.name || name, | ||
action: action || z.action, | ||
signature: (z.signature || []).concat(signature || []) | ||
})); | ||
body: body || z.body, | ||
signature: (z.signature || []).concat(signature || []), | ||
}); | ||
return z.returnValue || res; | ||
}); | ||
nice.eraseProperty(z, 'name'); | ||
def(z, 'name', name || ''); | ||
nice.rewriteProperty(z, 'name', name || ''); | ||
return z; | ||
@@ -119,34 +121,34 @@ } | ||
//optimization: create function that don't check fist argument for type.proto | ||
function createFunction({ existing, name, action, source, signature, type, description }){ | ||
const target = type === 'Check' ? nice.checkFunctions : nice; | ||
function createFunction({ name, body, signature, type, description, returns }){//existing, | ||
if(!/^[a-z].*/.test(name[0])) | ||
throw new Error(`Function name should start with lowercase letter. "${name}" is not`); | ||
if(type !== 'Check' && name && typeof name === 'string' | ||
&& name[0] !== name[0].toLowerCase()) | ||
throw "Function name should start with lowercase letter. " | ||
+ `"${nice._deCapitalize(name)}" not "${name}"`; | ||
const reflect = nice.reflect; | ||
existing = existing || (name && target[name]); | ||
const f = existing || createFunctionBody(type); | ||
let cfg = (name && reflect.functions[name]); | ||
const existing = cfg; | ||
if(existing && existing.functionType !== type) | ||
throw `function '${name}' can't have types '${existing.functionType}' and '${type}' at the same time`; | ||
if(cfg && cfg.functionType !== type) | ||
throw `function '${name}' can't have types '${cfg.functionType}' and '${type}' at the same time`; | ||
action && f.addSignature(action, signature); | ||
if(!cfg){ | ||
cfg = create(functionProto, { name, functionType: type }); | ||
reflect.functions[name] = cfg; | ||
} | ||
//optimization: maybe signature might be just an array of types?? | ||
const types = signature.map(v => v.type); | ||
returns && (body.returnType = returns); | ||
body && cfg.addSignature(body, types, returns); | ||
const f = reflect.compileFunction(cfg); | ||
if(name){ | ||
f.name !== name && nice.rewriteProperty(f, 'name', name); | ||
nice[name] = f; | ||
if(!existing){ | ||
if(f.name !== name){ | ||
nice.eraseProperty(f, 'name'); | ||
def(f, 'name', name); | ||
} | ||
existing || def(target, name, f); | ||
reflect.emitAndSave('function', cfg); | ||
type && reflect.emitAndSave(type, cfg); | ||
} | ||
const firstType = signature[0] && signature[0].type; | ||
firstType && !firstType.proto.hasOwnProperty(name) && type !== 'Check' | ||
&& def(firstType.proto, name, function(...a) { return f(this, ...a); }); | ||
if(!existing){ | ||
nice.emitAndSave('function', f); | ||
type && nice.emitAndSave(type, f); | ||
} | ||
action && nice.emitAndSave('signature', | ||
{name, action, signature, type, description, source }); | ||
body && reflect.emitAndSave('signature', | ||
{ name, body, signature, type, description }); | ||
} | ||
@@ -157,67 +159,28 @@ | ||
nice.reflect.on('signature', s => { | ||
if(!Anything) | ||
return; | ||
function createFunctionBody(type){ | ||
const z = create(functionProto, (...a) => { | ||
if(a.includes(nice)) | ||
return skip(a, z); | ||
const first = s.signature[0]; | ||
const type = first ? first.type : Anything; | ||
if(!(s.name in type.proto)) | ||
type.proto[s.name] = function(...a) { return nice[s.name](this, ...a); }; | ||
}); | ||
const s = findAction(z, a); | ||
if(!s) | ||
throw signatureError(z.name, a); | ||
if(type === 'Action'){ | ||
if(is.primitive(a[0])) | ||
return s(...a); | ||
s(...a); | ||
return a[0]; | ||
} | ||
if(type === 'Mapping') | ||
return nice(s(...a)); | ||
return s(...a); | ||
}); | ||
z.functionType = type; | ||
return z; | ||
function signatureError(name, a){ | ||
return `Function ${name} can't handle (${a.map(v => | ||
nice.typeOf(v).name).join(',')})`; | ||
} | ||
function findAction(target, args){ | ||
let res; | ||
if(!args.length || !target.signatures) | ||
return target.action; | ||
for(let i in args) { | ||
let type = nice.typeOf(args[i++]); | ||
while(!res && type){ | ||
if(target.signatures.has(type)){ | ||
target = target.signatures.get(type); | ||
res = target.action; | ||
} else { | ||
type = Object.getPrototypeOf(type); | ||
} | ||
} | ||
if(res) | ||
return res; | ||
} | ||
} | ||
function signatureError(name, a, s){ | ||
return `Function ${name} can't handle (${a.map(v => nice.typeOf(v).title).join(',')})`; | ||
} | ||
function handleType(type){ | ||
type.title === 'Something' && create(type.proto, functionProto); | ||
type.name === 'Something' && create(type.proto, functionProto); | ||
defGet(functionProto, type.title, function() { | ||
return configurator({ signature: [{type}], existing: this }); | ||
defGet(configProto, type.name, function() { | ||
return this.next({signature: [{type}]}); | ||
}); | ||
defGet(configProto, type.title, function() { | ||
return this.next({signature: [{type}]}); | ||
defGet(configProto, 'r' + type.name, function() { | ||
return this.next({returns: type}); | ||
}); | ||
@@ -227,15 +190,5 @@ }; | ||
function skip(a, f){ | ||
return create(skippedProto, (...b) => { | ||
const c = []; | ||
let i = 0; | ||
a.forEach(v => c.push(v === nice ? b[i++] : v)); | ||
return f(...c); | ||
}); | ||
} | ||
for(let i in nice.jsTypes) handleType(nice.jsTypes[i]); | ||
nice._on('Type', handleType); | ||
Func = def(nice, 'Function', configurator()); | ||
reflect.on('type', handleType); | ||
Func = def(nice, 'Func', configurator()); | ||
Action = def(nice, 'Action', configurator({functionType: 'Action'})); | ||
@@ -246,16 +199,17 @@ Mapping = def(nice, 'Mapping', configurator({functionType: 'Mapping'})); | ||
nice._on('function', ({name}) => { | ||
name && !skippedProto[name] && def(skippedProto, name, function(...a){ | ||
return create(skippedProto, a.includes(nice) | ||
? (...b) => { | ||
let c = []; | ||
for (let i = a.length ; i--;){ | ||
c[i] = a[i] === nice ? b.pop() : a[i]; | ||
} | ||
return this(...b)[name](...c); | ||
} | ||
: (...b) => { | ||
return this(...b)[name](...a); | ||
const ro = def(nice, 'ReadOnly', {}); | ||
reflect.on('type', type => { | ||
ro[type.name] = function (...a) { | ||
const [name, f] = a.length === 2 ? a : [a[0].name, a[0]]; | ||
expect(f).isFunction(); | ||
defGet(type.proto, name, function() { | ||
const initType = this._type; | ||
if(initType !== this._type){ | ||
return this[name]; | ||
} else { | ||
return f(this); | ||
} | ||
}); | ||
}); | ||
return this; | ||
}; | ||
}); |
const index = {}; | ||
nice.Type({ | ||
title: 'Interface', | ||
constructor: (z, title, ...a) => { | ||
if(nice[title]) | ||
throw `Can't create interface ${title} name busy.`; | ||
name: 'Interface', | ||
initBy: (z, name, ...a) => { | ||
if(nice[name]) | ||
throw `Can't create interface ${name} name busy.`; | ||
@@ -12,3 +12,3 @@ if(a.length === 0) | ||
z.title = title; | ||
z.name = name; | ||
z.methods = a; | ||
@@ -19,8 +19,8 @@ z.matchingTypes = []; | ||
nice._on('Type', type => match(type, z) && z.matchingTypes.push(type)); | ||
Check(title, type => z.matchingTypes.includes(type._type || type)); | ||
reflect.on('type', type => match(type, z) && z.matchingTypes.push(type)); | ||
Check(name, type => z.matchingTypes.includes(type._type || type)); | ||
Object.freeze(z); | ||
def(nice, title, z); | ||
nice.emitAndSave('interface', z); | ||
def(nice, name, z); | ||
reflect.emitAndSave('interface', z); | ||
} | ||
@@ -30,3 +30,3 @@ }); | ||
nice.onNew('signature', s => { | ||
reflect.onNew('signature', s => { | ||
if(s.type === 'Check') | ||
@@ -49,5 +49,5 @@ return; | ||
while(ok && l--){ | ||
ok &= type.proto.hasOwnProperty(methods[l]); | ||
ok &= methods[l] in type.proto; | ||
} | ||
return ok; | ||
} |
485
core/is.js
@@ -1,30 +0,62 @@ | ||
const isProto = def(nice, 'isProto', {}), { Check } = nice; | ||
nice._on('Check', f => | ||
isProto[f.name] = function(...a) { return f(this.value, ...a); }); | ||
//TODO: is.not. | ||
['Check', 'Action', 'Mapping'].forEach(t => Check | ||
.about(`Checks if value is function and it's type is ${t}.`) | ||
('is' + t, v => v.functionType === t)); | ||
const basicChecks = { | ||
is (a, b) { | ||
if(a === b) | ||
return true; | ||
is = def(nice, 'is', value => create(isProto, { value })); | ||
nice._on('Check', f => is[f.name] = f); | ||
if(a && a._isAnything && '_value' in a) | ||
a = a._value; | ||
Check.about('Checks if two values are equal.') | ||
('equal', (a, b) => a === b || (a && a.getResult ? a.getResult() : a) === (b && b.getResult ? b.getResult() : b)) | ||
if(b && b._isAnything && '_value' in b) | ||
b = b._value; | ||
const basicChecks = { | ||
true: v => v === true, | ||
false: v => v === false, | ||
any: (v, ...vs) => vs.includes(v), | ||
array: a => Array.isArray(a), | ||
"NaN": n => Number.isNaN(n), | ||
object: i => i !== null && typeof i === 'object' && !i._isSingleton, | ||
null: i => i === null, | ||
undefined: i => i === undefined, | ||
nice: v => nice.Anything.proto.isPrototypeOf(v), | ||
primitive: i => { | ||
const type = typeof i; | ||
return i === null || (type !== "object" && type !== "function"); | ||
return a === b; | ||
}, | ||
empty: v => { | ||
if(!v) | ||
isExactly: (a, b) => a === b, | ||
deepEqual: (a, b) => { | ||
if(a === b) | ||
return true; | ||
if(a && a._isAnything && '_value' in a) | ||
a = a._value; | ||
if(b && b._isAnything && '_value' in b) | ||
b = b._value; | ||
if(typeof a !== typeof b) | ||
return false; | ||
return nice.diff(a, b) === false; | ||
}, | ||
isTrue: v => v === true, | ||
isFalse: v => v === false, | ||
isAnyOf: (v, ...vs) => { | ||
if(v && v._isAnything && '_value' in v) | ||
v = v._value; | ||
return vs.includes(v); | ||
}, | ||
isNeitherOf: (v, ...vs) => { | ||
if(v && v._isAnything && '_value' in v) | ||
v = v._value; | ||
return !vs.includes(v); | ||
}, | ||
isTruly: v => v | ||
? v._isAnything | ||
? v.isNothing() ? false : !!v() | ||
: true | ||
: false, | ||
isFalsy: v => !nice.isTruly(v), | ||
isEmpty: v => { | ||
if(nice.isNothing(v) || v === null) | ||
return true; | ||
if(v === 0 || v === '' || v === false) | ||
return false; | ||
if(Array.isArray(v)) | ||
@@ -38,8 +70,16 @@ return !v.length; | ||
}, | ||
subType: (a, b) => { | ||
is.string(a) && (a = nice.Type(a)); | ||
is.string(b) && (b = nice.Type(b)); | ||
isSubType: (a, b) => { | ||
typeof a === 'string' && (a = nice.Type(a)); | ||
typeof b === 'string' && (b = nice.Type(b)); | ||
return a === b || b.isPrototypeOf(a); | ||
}, | ||
browser: () => nice.isEnvBrowser | ||
throws: (...as) => { | ||
try{ | ||
as[0](); | ||
} catch(e) { | ||
return as.length === 1 ? true : as[1] === e; | ||
} | ||
return false; | ||
} | ||
}; | ||
@@ -49,34 +89,102 @@ | ||
Check(i, basicChecks[i]); | ||
is = nice.is; | ||
const basicJS = 'number,function,string,boolean,symbol'.split(','); | ||
for(let i in nice.jsTypes) | ||
nice.is[i] || Check(i, basicJS.includes(i) | ||
? v => typeof v === i | ||
: v => v && v.constructor && v.constructor.name === nice.jsTypes[i].jsName); | ||
for(let i in nice.jsTypes){ | ||
if(i === 'Function'){ | ||
Check.about(`Checks if \`v\` is \`function\`.`) | ||
('is' + i, v => v && v._isAnything | ||
? v._type === nice.Func || v._type === nice.jsTypes.Function | ||
: typeof v === 'function'); | ||
} else { | ||
const low = i.toLowerCase(); | ||
Check.about(`Checks if \`v\` is \`${i}\`.`) | ||
('is' + i, basicJS.includes(low) | ||
? v => typeof v === low//BUG: always true for function since every nice item is function | ||
: new Function('v', `return ${i}.prototype.isPrototypeOf(v);`)); | ||
// : new Function('v', v => v && typeof v === 'object' ? v.constructor.name === i : false); | ||
} | ||
}; | ||
nice._on('Type', function defineReducer(type) { | ||
type.title && Check(type.title, v => | ||
v && v._type ? type.proto.isPrototypeOf(v) : false); | ||
reflect.on('type', function defineReducer(type) { | ||
if(!type.name) | ||
return; | ||
const body = type.singleton | ||
? v => v === type || (v && v._type | ||
? (type === v._type || type.isPrototypeOf(v._type)) | ||
: false) | ||
: v => v && v._type | ||
? (type === v._type || type.isPrototypeOf(v._type)) | ||
: false | ||
Check.about('Checks if `v` has type `' + type.name + '`') | ||
('is' + type.name, body); | ||
}); | ||
const switchProto = create(nice.checkers, { | ||
valueOf: function () { return this.res; }, | ||
check: function (f) { | ||
this._check = f; | ||
const res = switchResult.bind(this); | ||
res.use = switchUse.bind(this); | ||
const throwF = function(...as) { | ||
return this.use(() => { | ||
throw nice.format(...as); | ||
}); | ||
}; | ||
const common = { | ||
pushCheck (f){ | ||
const postCheck = this._check; | ||
this._check = postCheck ? (...a) => postCheck(f(...a)) : f; | ||
return this; | ||
}, | ||
is (v) { | ||
return this.check(a => is(a, v)); | ||
}, | ||
'throw': throwF, | ||
}; | ||
defGet(common, 'not', function (){ | ||
this.pushCheck(r => !r); | ||
return this; | ||
}); | ||
const switchProto = create(common, { | ||
valueOf () { return this.res; }, | ||
check (f) { | ||
this.pushCheck(f) | ||
const res = create(actionProto, v => { | ||
const z = this; | ||
if(!z.done && z._check(...z.args)){ | ||
z.res = v; | ||
z.done = true; | ||
} | ||
z._check = null; | ||
return z; | ||
}); | ||
res.target = this; | ||
return res; | ||
}, | ||
equal: function (v) { | ||
this._check = (...a) => v === a[0]; | ||
const res = switchResult.bind(this); | ||
res.use = switchUse.bind(this); | ||
return res; | ||
} | ||
}); | ||
const $proto = { | ||
check (f) { | ||
return this.parent.check((...v) => f(v[this.pos])); | ||
}, | ||
is (v) { | ||
return this.parent.check((...as) => as[this.pos] === v); | ||
}, | ||
}; | ||
defGet($proto, 'not', function (){ | ||
this.parent.pushCheck(r => !r); | ||
return this; | ||
}); | ||
[1,2,3,4].forEach(n => defGet(common, '_' + n, function () { | ||
return create($proto, {parent: this, pos: n - 1}); | ||
})); | ||
defGet(switchProto, 'default', function () { | ||
@@ -86,10 +194,51 @@ const z = this; | ||
res.use = f => z.done ? z.res : f(...z.args); | ||
res.throw = throwF; | ||
return res; | ||
}); | ||
const actionProto = {}; | ||
const actionProto = { | ||
'throw': throwF, | ||
use (f) { | ||
const z = this.target; | ||
nice._on('function', f => { | ||
if(!z.done && z._check(...z.args)){ | ||
z.res = f(...z.args); | ||
z.done = true; | ||
} | ||
z._check = null; | ||
return z; | ||
} | ||
}; | ||
const delayedActionProto = create(actionProto, { | ||
use (f){ | ||
const z = this.target; | ||
z.cases.push(z._check, f); | ||
z._check = null; | ||
return z; | ||
} | ||
}); | ||
defGet(actionProto, 'and', function (){ | ||
const s = this.target; | ||
const f = s._check; | ||
s._check = r => r && f(...s.args); | ||
return s; | ||
}); | ||
defGet(actionProto, 'or', function (){ | ||
const s = this.target; | ||
const f = s._check; | ||
s._check = r => r || f(...s.args); | ||
return s; | ||
}); | ||
reflect.on('function', f => { | ||
if(f.functionType !== 'Check'){ | ||
actionProto[f.name] = function(...a){ return this.use(v => f(v, ...a)); }; | ||
f.name in actionProto || def(actionProto, f.name, function(...a){ | ||
return this.use(v => nice[f.name](v, ...a)); | ||
}); | ||
} | ||
@@ -99,15 +248,13 @@ }); | ||
const delayedProto = create(nice.checkers, { | ||
check: function (f) { | ||
this._check = f; | ||
const res = create(actionProto, delayedResult.bind(this)); | ||
res.use = delayedUse.bind(this); | ||
const delayedProto = create(common, { | ||
check (f) { | ||
this.pushCheck(f); | ||
const res = create(delayedActionProto, v => { | ||
this.cases.push(this._check, () => v); | ||
this._check = null; | ||
return this; | ||
}); | ||
res.target = this; | ||
return res; | ||
}, | ||
equal: function (f) { | ||
this._check = (...a) => a[0] === f; | ||
const res = create(actionProto, delayedResult.bind(this)); | ||
res.use = delayedUse.bind(this); | ||
return res; | ||
}, | ||
} | ||
}); | ||
@@ -119,2 +266,3 @@ | ||
res.use = f => { z._default = f; return z; }; | ||
res.throw = throwF; | ||
return res; | ||
@@ -124,138 +272,153 @@ }); | ||
function switchResult(v){ | ||
const z = this; | ||
const S = Switch = nice.Switch = (...args) => { | ||
if(args.length === 0) | ||
return DelayedSwitch(); | ||
if(!z.done && z._check(...z.args)){ | ||
z.res = v; | ||
z.done = true; | ||
} | ||
z._check = null; | ||
return z; | ||
} | ||
const f = () => f.done ? f.res : args[0]; | ||
f.args = args; | ||
f.done = false; | ||
return create(switchProto, f); | ||
}; | ||
function switchUse(f){ | ||
const z = this; | ||
reflect.on('Check', ({name}) => name && !common[name] | ||
&& (common[name] = function (...a) { | ||
return this.check((...v) => { | ||
try { | ||
return nice[name](...v, ...a); | ||
} catch (e) { | ||
return false; | ||
} | ||
}); | ||
}) | ||
); | ||
if(!z.done && z._check(...z.args)){ | ||
z.res = f(...z.args); | ||
z.done = true; | ||
} | ||
z._check = null; | ||
return z; | ||
}; | ||
reflect.on('Check', ({name}) => name && !$proto[name] | ||
&& def($proto, name, function (...a) { | ||
return this.parent.check((...v) => { | ||
try { | ||
return nice[name](v[this.pos], ...a); | ||
} catch (e) { | ||
return false; | ||
} | ||
}); | ||
}) | ||
); | ||
function delayedResult(v){ | ||
const z = this; | ||
z.cases.push(z._check, () => v); | ||
z._check = null; | ||
return z; | ||
} | ||
function DelayedSwitch() { | ||
const f = (...args) => { | ||
const l = f.cases.length; | ||
let action = f._default; | ||
for(let i = 0 ; i < l; i += 2){ | ||
if(f.cases[i](...args)){ | ||
action = f.cases[i + 1]; | ||
break; | ||
} | ||
} | ||
return action ? action(...args) : args[0]; | ||
}; | ||
function delayedUse(f){ | ||
const z = this; | ||
z.cases.push(z._check, f); | ||
z._check = null; | ||
return z; | ||
} | ||
f.cases = []; | ||
return create(delayedProto, f); | ||
}; | ||
const S = Switch = nice.Switch = (...args) => { | ||
const f = () => f.done ? f.res : args[0]; | ||
f.args = args; | ||
f.done = false; | ||
f.addCheck = check => { | ||
const preCheck = f._check; | ||
f._check = preCheck ? (...a) => preCheck(check(...a)) : check; | ||
const res = create(actionProto, switchResult.bind(f)); | ||
res.use = switchUse.bind(f); | ||
return res; | ||
}; | ||
Test('Delayed Switch', (Switch, Spy) => { | ||
const spy1 = Spy(() => 1); | ||
const spy2 = Spy(() => 2); | ||
const spy3 = Spy(() => 3); | ||
return create(switchProto, f); | ||
}; | ||
const s = Switch() | ||
.is(3)(10) | ||
.isNumber().use(spy1) | ||
.isString().use(spy2) | ||
.default.use(spy3); | ||
Test('type check', () => { | ||
expect(s('qwe')).is(2); | ||
expect(s(42)).is(1); | ||
}); | ||
Test('is', () => expect(s(3)).is(10)); | ||
Test('default', () => expect(s([])).is(3)); | ||
S.equal = v => DealyedSwitch().equal(v); | ||
S.check = f => DealyedSwitch().check(f); | ||
Test('No extra calls', () => { | ||
expect(spy1).calledOnce(); | ||
expect(spy2).calledOnce(); | ||
expect(spy3).calledOnce(); | ||
}); | ||
}); | ||
defGet(S, 'not', () => { | ||
const res = DealyedSwitch(); | ||
res._check = r => !r; | ||
return res; | ||
Test("not", (Switch) => { | ||
const s = Switch(5) | ||
.isString()(1) | ||
.not.isString()(2) | ||
.default(3); | ||
expect(s).is(2); | ||
}); | ||
defGet(switchProto, 'not', function (){ | ||
this._check = r => !r; | ||
return this; | ||
Test("singleton type", () => { | ||
const s = nice.Stop; | ||
expect(s).isStop(); | ||
}); | ||
defGet(delayedProto, 'not', function (){ | ||
this._check = r => !r; | ||
return this; | ||
Test((isFunction) => { | ||
expect(isFunction(() => 1)).is(true); | ||
expect(isFunction(2)).is(false); | ||
expect(isFunction()).is(false); | ||
}); | ||
Test((Switch, Spy) => { | ||
const spy1 = Spy(); | ||
const spy2 = Spy(() => 2); | ||
const spy3 = Spy(); | ||
function diggSignaturesLength(f, n = 0){ | ||
f.action && f.action.length > n && (n = f.action.length); | ||
f.signatures && f.signatures.forEach(v => n = diggSignaturesLength(v, n)); | ||
return n; | ||
} | ||
const s = Switch('qwe') | ||
.isNumber().use(spy1) | ||
.isString().use(spy2) | ||
.is(3)(4) | ||
.default.use(spy3); | ||
nice._on('Check', f => { | ||
if(!f.name || nice.checkers[f.name]) | ||
return; | ||
if(diggSignaturesLength(f) > 1){ | ||
def(nice.checkers, f.name, function (...a) { | ||
return this.addCheck(v => f(v, ...a)); | ||
}); | ||
} else { | ||
defGet(nice.checkers, f.name, function(){ | ||
return this.addCheck(f); | ||
}); | ||
}; | ||
expect(s).is(2); | ||
expect(spy1).not.called(); | ||
expect(spy2).calledTimes(1); | ||
expect(spy2).calledWith('qwe'); | ||
expect(spy3).not.called(); | ||
}); | ||
create(nice.checkers, S); | ||
Test("switch equal", (Switch, Spy) => { | ||
const spy1 = Spy(); | ||
const spy3 = Spy(); | ||
S.addCheck = function (check) { | ||
const res = DealyedSwitch(); | ||
return res.addCheck(check); | ||
}; | ||
const s = Switch('qwe') | ||
.isNumber().use(spy1) | ||
.is('qwe')(4) | ||
.default.use(spy3); | ||
expect(spy1).not.called(); | ||
expect(spy3).not.called(); | ||
function DealyedSwitch(...a) { | ||
const f = (...a) => { | ||
const l = f.cases.length; | ||
let action = f._default; | ||
expect(s).is(4); | ||
}); | ||
for(let i = 0 ; i < l; i += 2){ | ||
if(f.cases[i](...a)){ | ||
action = f.cases[i + 1]; | ||
break; | ||
} | ||
} | ||
return action ? action(...a) : a[0]; | ||
}; | ||
f.cases = a; | ||
f.addCheck = check => { | ||
const preCheck = f._check; | ||
f._check = preCheck ? (...a) => preCheck(check(...a)) : check; | ||
const res = create(actionProto, delayedResult.bind(f)); | ||
res.use = delayedUse.bind(f); | ||
return res; | ||
}; | ||
Test((is) => { | ||
const n = nice.Num(1); | ||
expect(n.is(1)).is(true); | ||
expect(n.is(2)).is(false); | ||
}); | ||
return create(delayedProto, f); | ||
}; | ||
Test((between) => { | ||
expect(between(1, 2, 3)).is(false); | ||
expect(between(2, 1, 3)).is(true); | ||
}); |
@@ -1,17 +0,68 @@ | ||
nice.jsTypes = { js: { title: 'js', proto: {}, jsType: true }}; | ||
nice.jsTypes = { js: { name: 'js', proto: {}, jsType: true }}; | ||
//TODO: fix isPrimitive | ||
const jsHierarchy = { | ||
js: 'primitive,object', | ||
primitive: 'string,boolean,number,undefined,null,symbol', | ||
object: 'function,date,regExp,array,error,arrayBuffer,dataView,map,weakMap,set,weakSet,promise', | ||
error: 'evalError,rangeError,referenceError,syntaxError,typeError,uriError' | ||
js: 'primitive,Object', | ||
primitive: 'String,Boolean,Number,undefined,null,Symbol', | ||
Object: 'Function,Date,RegExp,Array,Error,ArrayBuffer,DataView,Map,WeakMap,Set,WeakSet,Promise', | ||
Error: 'EvalError,RangeError,ReferenceError,SyntaxError,TypeError,UriError' | ||
}; | ||
const jsTypesMap = { | ||
Object: 'Obj', | ||
Array: 'Arr', | ||
Number: 'Num', | ||
Boolean: 'Bool', | ||
String: 'Str', | ||
// Function: 'Func', | ||
'undefined': 'Undefined', | ||
'null': 'Null' | ||
}; | ||
nice.jsBasicTypesMap = { | ||
object: 'Obj', | ||
array: 'Arr', | ||
number: 'Num', | ||
boolean: 'Bool', | ||
string: 'Str', | ||
// function: 'Func' | ||
}; | ||
nice.typesToJsTypesMap = { | ||
Str: 'String', | ||
Num: 'Number', | ||
Obj: 'Object', | ||
Arr: 'Array', | ||
Bool: 'Boolean', | ||
// Single: 'primitive', | ||
// Func: 'Function', | ||
// 'undefined': 'Undefined', | ||
// 'null': 'Null' | ||
} | ||
for(let i in jsHierarchy) | ||
jsHierarchy[i].split(',').forEach(title => { | ||
jsHierarchy[i].split(',').forEach(name => { | ||
const parent = nice.jsTypes[i]; | ||
const proto = create(parent.proto); | ||
nice.jsTypes[title] = create(parent, | ||
{ title, proto, jsType: true, jsName: nice._capitalize(title) }); | ||
}); | ||
nice.jsTypes[name] = create(parent, | ||
{ name, | ||
proto, | ||
_isJsType: true, | ||
niceType: jsTypesMap[name] }); | ||
}); | ||
nice.jsBasicTypes = { | ||
object: nice.jsTypes.Object, | ||
array: nice.jsTypes.Array, | ||
number: nice.jsTypes.Number, | ||
boolean: nice.jsTypes.Boolean, | ||
string: nice.jsTypes.String, | ||
function: nice.jsTypes.Function, | ||
symbol: nice.jsTypes.Symbol | ||
}; | ||
jsHierarchy['primitive'].split(',').forEach(name => { | ||
nice.jsTypes[name].primitiveName = name.toLowerCase(); | ||
}); | ||
nice.jsTypes.Function.primitiveName = 'function'; |
@@ -5,6 +5,6 @@ Mapping.Anything('or', (...as) => { | ||
v = nice(as[i]); | ||
if(is.Something(v) && (!v.getResult || v.getResult() !== false)) | ||
if(nice.isSomething(v) && v._value !== false) | ||
return v; | ||
} | ||
return v || nice.NOTHING; | ||
return v || nice.Nothing(); | ||
}); | ||
@@ -17,3 +17,3 @@ | ||
v = nice(as[i]); | ||
if(!is.Something(v) || (!v.getResult || v.getResult() === false)) | ||
if(!nice.isSomething(v) || v._value === false) | ||
return v; | ||
@@ -29,3 +29,3 @@ } | ||
v = nice(as[i]); | ||
if(is.Something(v) && (!v.getResult || v.getResult() !== false)) | ||
if(nice.isSomething(v) && v._value !== false) | ||
return nice(false); | ||
@@ -41,3 +41,3 @@ } | ||
const v = nice(as[i]); | ||
if(is.Something(v) && (!v.getResult || v.getResult() !== false)) | ||
if(nice.isSomething(v) && v._value !== false) | ||
count++; | ||
@@ -44,0 +44,0 @@ } |
@@ -1,25 +0,304 @@ | ||
function s(title, itemTitle, parent, description){ | ||
nice.Check('isType', v => Anything.isPrototypeOf(v) || v === Anything); | ||
nice.ReadOnly.Anything(function jsValue(z) { return z._value; }); | ||
function s(name, parent, description){ | ||
const value = Object.freeze({ _type: name }); | ||
nice.Type({ | ||
title: title, | ||
name, | ||
extends: parent, | ||
creator: () => nice[itemTitle], | ||
description, | ||
singleton: true, | ||
proto: { | ||
_isSingleton: true, | ||
} | ||
}); | ||
nice[itemTitle] = Object.seal(create(nice[title].proto, new String(itemTitle))); | ||
})(); | ||
} | ||
s('Nothing', 'NOTHING', 'Anything', 'Parent type for all falsy values.'); | ||
s('Undefined', 'UNDEFINED', 'Nothing', 'Wrapper for JS undefined.'); | ||
s('Null', 'NULL', 'Nothing', 'Wrapper for JS null.'); | ||
s('NotFound', 'NOT_FOUND', 'Nothing', 'Value returned by lookup functions in case nothing is found.'); | ||
s('Fail', 'FAIL', 'Nothing', 'Empty negative signal.'); | ||
s('NeedComputing', 'NEED_COMPUTING', 'Nothing', 'State of the Box in case it need some computing.'); | ||
s('Pending', 'PENDING', 'Nothing', 'State of the Box when it awaits input.'); | ||
s('Stop', 'STOP', 'Nothing', 'Value used to stop iterationin .each() and similar functions.'); | ||
s('Nothing', 'Anything', 'Parent type for all falsy values.'); | ||
s('Undefined', 'Nothing', 'Wrapper for JS undefined.'); | ||
s('Null', 'Nothing', 'Wrapper for JS null.'); | ||
s('NotFound', 'Nothing', 'Value returned by lookup functions in case nothing is found.'); | ||
NotFound = nice.NotFound; | ||
s('Fail', 'Nothing', 'Empty negative signal.'); | ||
s('Pending', 'Nothing', 'State when item awaits input.'); | ||
s('Stop', 'Nothing', 'Value used to stop iterationin .each() and similar functions.'); | ||
s('NumberError', 'Nothing', 'Wrapper for JS NaN.'); | ||
s('AssignmentError', 'Nothing', `Can't assign`); | ||
s('Something', 'SOMETHING', 'Anything', 'Parent type for all non falsy values.'); | ||
s('Ok', 'OK', 'Something', 'Empty positive signal.'); | ||
s('Something', 'Anything', 'Parent type for all non falsy values.'); | ||
s('Ok', 'Something', 'Empty positive signal.'); | ||
nice.Nothing.defaultValueBy = () => null; | ||
nice.ReadOnly.Nothing(function jsValue(z) { | ||
return {[nice.TYPE_KEY]: z._type.name}; | ||
}); | ||
defGet(nice.Null.proto, function jsValue() { | ||
return null; | ||
}); | ||
defGet(nice.Undefined.proto, function jsValue() { | ||
return undefined; | ||
}); | ||
nice.simpleTypes = { | ||
number: { | ||
cast(v){ | ||
const type = typeof v; | ||
if(type === 'number') | ||
return v; | ||
if(v === undefined) | ||
throw `undefined is not a number`; | ||
if(v === null) | ||
throw `undefined is not a number`; | ||
if(v._isNum) | ||
return v._value; | ||
if(type === 'string'){ | ||
const n = +v; | ||
if(!isNaN(n)) | ||
return n; | ||
} | ||
throw `${v}[${type}] is not a number`; | ||
} | ||
}, | ||
string: { | ||
cast(v){ | ||
const type = typeof v; | ||
if(type === 'string') | ||
return v; | ||
if(v === undefined) | ||
throw `undefined is not a string`; | ||
if(v === null) | ||
throw `undefined is not a string`; | ||
if(v._isStr) | ||
return v._value; | ||
if(type === 'number') | ||
return '' + v; | ||
if(Array.isArray(v)) | ||
return nice.format(...v); | ||
throw `${v}[${type}] is not a string`; | ||
} | ||
}, | ||
boolean: { | ||
cast(v){ | ||
const type = typeof v; | ||
if(type === 'boolean') | ||
return v; | ||
if(v === undefined) | ||
false; | ||
if(v === null) | ||
false; | ||
if(v._isBool) | ||
return v._value; | ||
if(type === 'number' || type === 'string') | ||
return !!v; | ||
throw `${v}[${type}] is not a boolean`; | ||
} | ||
}, | ||
function: { | ||
cast(v){ | ||
const type = typeof v; | ||
if(type === 'function') | ||
return v; | ||
throw `${v}[${type}] is not a function`; | ||
} | ||
}, | ||
object: { | ||
cast(v){ | ||
const type = typeof v; | ||
if(type === 'object') | ||
return v; | ||
throw `${v}[${type}] is not an object`; | ||
} | ||
} | ||
}; | ||
const defaultValueBy = { | ||
string: () => '', | ||
boolean: () => false, | ||
number: () => 0, | ||
object: () => ({}), | ||
'function': x => x, | ||
}; | ||
//TODO: test function | ||
['string', 'boolean', 'number', 'function'].forEach(typeName => { | ||
def(nice.Anything.configProto, typeName, function (name, defaultValue) { | ||
if(defaultValue !== undefined && typeof defaultValue !== typeName) | ||
defaultValue = nice.simpleTypes[typeName].cast(defaultValue); | ||
def(this.target.proto, name, function(...vs){ | ||
if(vs.length === 0) | ||
return name in this._value ? this._value[name] : defaultValue; | ||
if(typeof vs[0] !== typeName) | ||
vs[0] = nice.simpleTypes[typeName].cast(vs[0]); | ||
this._value[name] = vs[0]; | ||
return this; | ||
}); | ||
return this; | ||
}); | ||
}); | ||
def(nice.Anything.configProto, 'object', function (name, defaultValue) { | ||
if(defaultValue !== undefined && typeof defaultValue !== 'object') | ||
throw `Default value for ${name} should be object.`; | ||
def(this.target.proto, name, function(...vs){ | ||
const v = this._value; | ||
if(!(name in v)) | ||
v[name] = defaultValue ? Object.assign({}, defaultValue) : {}; | ||
if(vs.length === 0) | ||
return v[name]; | ||
if(vs.length === 1) | ||
return v[name][vs[0]]; | ||
v[name][vs[0]] = vs[1]; | ||
return this; | ||
}); | ||
return this; | ||
}); | ||
//Array | ||
nice.Anything.configProto.array = function (name, defaultValue = []) { | ||
Object.defineProperty(this.target.proto, name, { | ||
get: function(){ | ||
let value = this._value[name]; | ||
if(value === undefined) | ||
value = this._value[name] = defaultValue; | ||
return value; | ||
}, | ||
set: function(value){ | ||
if(!Array.isArray(value)) | ||
throw `Can't set ${name}[Array] to ${value}[${typeof value}]`; | ||
this._value[name] = value; | ||
}, | ||
enumerable: true | ||
}); | ||
return this; | ||
}; | ||
Func('partial', (f, template, ...cfgAs) => { | ||
const a = template.split(''); | ||
const l = cfgAs.length; | ||
const useThis = template[0] === 'z'; | ||
useThis && a.shift(); | ||
return function(...callAs){ | ||
let cur = 0; | ||
const as = a.map(n => { | ||
return n === '$' ? cfgAs[cur++]: callAs[n-1]; | ||
}); | ||
cur < l && as.push(...cfgAs.slice(cur)); | ||
return useThis ? f.apply(as.shift(), as) : f(...as); | ||
}; | ||
}); | ||
Test('Arguments order', (partial) => { | ||
const f = partial((...as) => as.join(''), '21'); | ||
expect(f('a', 'b')).is('ba'); | ||
}); | ||
Test('Partial arguments', (partial) => { | ||
const f = partial((...as) => as.join(''), '2$1', 'c', 'd'); | ||
expect(f('a', 'b')).is('bcad'); | ||
}); | ||
Test('Partial `this` argument', (partial) => { | ||
const f = partial(String.prototype.concat, 'z2$1', 'c', 'd'); | ||
expect(f('a', 'b')).is('bcad'); | ||
}); | ||
Test('Partial type constructor', (partial) => { | ||
const f = nice.Str.partial('$1', 'Hello'); | ||
// const f = partial(String.prototype.concat, 'z2$1', 'c', 'd'); | ||
expect(f('world')).is('Hello world'); | ||
}); | ||
def(nice, 'sortedIndex', (a, v, f) => { | ||
let low = 0, | ||
high = a === null ? low : a.length; | ||
while (low < high) { | ||
var mid = (low + high) >>> 1, | ||
vv = a[mid]; | ||
// if (computed !== null && !isSymbol(computed) && | ||
// (retHighest ? (computed <= value) : (computed < value))) { | ||
//!isSymbol(computed) && | ||
if (vv !== null && (f === undefined ? vv < v : f(vv, v) < 0)) { | ||
low = mid + 1; | ||
} else { | ||
high = mid; | ||
} | ||
} | ||
return high; | ||
}); | ||
Test((sortedIndex) => { | ||
const a = [1,5,7,8]; | ||
const f = (a, b) => a - b; | ||
expect(sortedIndex(a, 0)).is(0); | ||
expect(sortedIndex(a, 0, f)).is(0); | ||
expect(sortedIndex(a, 2)).is(1); | ||
expect(sortedIndex(a, 2, f)).is(1); | ||
expect(sortedIndex(a, 7)).is(2); | ||
expect(sortedIndex(a, 7, f)).is(2); | ||
expect(sortedIndex(a, 11)).is(4); | ||
expect(sortedIndex(a, 11, f)).is(4); | ||
expect(sortedIndex([], -11)).is(0); | ||
expect(sortedIndex([], 11)).is(0); | ||
}); | ||
Test((sortedIndex) => { | ||
const a = ['a', 'aaa', 'aaaaa', 'aaaaaa']; | ||
const f = (a, b) => a.length - b.length; | ||
expect(sortedIndex(a, 'aa', f)).is(1); | ||
expect(sortedIndex(a, 'aaaa', f)).is(2); | ||
expect(sortedIndex(a, '', f)).is(0); | ||
}); |
146
core/type.js
@@ -1,2 +0,4 @@ | ||
function extend(child, parent){ | ||
def(nice, function extend(child, parent){ | ||
if(parent.extensible === false) | ||
throw `Type ${parent.name} is not extensible.`; | ||
create(parent, child); | ||
@@ -6,96 +8,46 @@ create(parent.proto, child.proto); | ||
create(parent.types, child.types); | ||
parent.defaultResult && create(parent.defaultResult, child.defaultResult); | ||
nice.emitAndSave('Extension', { child, parent }); | ||
parent.defaultArguments && create(parent.defaultArguments, child.defaultArguments); | ||
reflect.emitAndSave('extension', { child, parent }); | ||
child.super = parent; | ||
} | ||
nice.registerType({ | ||
title: 'Anything', | ||
description: 'Parent type for all types.', | ||
extend: function (...a){ | ||
return nice.Type(...a).extends(this); | ||
}, | ||
proto: { | ||
_isAnything: true, | ||
apply: function(f){ | ||
f(this); | ||
return this; | ||
} | ||
}, | ||
configProto: { | ||
extends: function(parent){ | ||
const type = this.target; | ||
is.string(parent) && (parent = nice[parent]); | ||
expect(parent).Type(); | ||
extend(type, parent); | ||
return this; | ||
}, | ||
about: function (...a) { | ||
this.target.description = nice.format(...a); | ||
return this; | ||
} | ||
}, | ||
types: {} | ||
}); | ||
Object.defineProperties(nice.Anything.proto, { | ||
switch: { get: function() { return Switch(this); } }, | ||
is: { get: function() { | ||
const f = v => is(this).equal(v); | ||
f.value = this; | ||
return create(nice.isProto, f); | ||
} } | ||
}); | ||
nice.ANYTHING = Object.seal(create(nice.Anything.proto, new String('ANYTHING'))); | ||
nice.Anything.proto._type = nice.Anything; | ||
let _counter = 0; | ||
defAll(nice, { | ||
saveValue: v => v, | ||
loadValue: v => v, | ||
type: t => { | ||
is.string(t) && (t = nice[t]); | ||
expect(nice.Anything.isPrototypeOf(t) || nice.Anything === t, | ||
'' + t + ' is not a type').toBe(); | ||
typeof t === 'string' && (t = nice[t]); | ||
expect(Anything.isPrototypeOf(t) || Anything === t, | ||
'' + t + ' is not a type').is(true); | ||
return t; | ||
}, | ||
Type: (config = {}) => { | ||
if(is.string(config)){ | ||
if(nice.types[config]) | ||
Type: (config = {}, by) => { | ||
if(typeof config === 'string'){ | ||
if(reflect.types[config]) | ||
throw `Type "${config}" already exists`; | ||
config = {title: config}; | ||
config = {name: config}; | ||
} | ||
is.object(config) | ||
nice.isObject(config) | ||
|| nice.error("Need object for type's prototype"); | ||
!config.title || is.string(config.title) | ||
|| nice.error("Title must be String"); | ||
config.name = config.name || 'Type_' + (_counter++); | ||
config.types = {}; | ||
config.proto = config.proto || {}; | ||
config.configProto = config.configProto || {}; | ||
config.defaultResult = config.defaultResult || {}; | ||
config.defaultArguments = config.defaultArguments || {}; | ||
by === undefined || (config.initBy = by); | ||
const type = (...a) => nice.createItem({ type }, ...a); | ||
if(config.customCall !== undefined || config.itemArgs0 !== undefined | ||
|| config.itemArgs1 !== undefined || config.itemArgsN !== undefined){ | ||
config.isFunction = true; | ||
} | ||
config.proto._type = type; | ||
delete config.by; | ||
const type = (...a) => reflect.createItem(type, a); | ||
Object.defineProperty(type, 'name', { writable: true }); | ||
Object.assign(type, config); | ||
extend(type, config.hasOwnProperty('extends') ? nice.type(config.extends) : nice.Object); | ||
nice.extend(type, 'extends' in config ? nice.type(config.extends) : nice.Obj); | ||
const cfg = create(config.configProto, nice.Configurator(type, '')); | ||
config.title && nice.registerType(type); | ||
config.name && reflect.registerType(type); | ||
return cfg; | ||
@@ -105,5 +57,3 @@ }, | ||
nice.Check('Type', v => nice.Anything.isPrototypeOf(v)); | ||
nice.typeOf = v => { | ||
@@ -121,3 +71,3 @@ if(v === undefined) | ||
if(primitive !== 'object'){ | ||
const res = nice[primitive[0].toUpperCase() + primitive.substr(1)]; | ||
const res = nice[nice.jsBasicTypesMap[primitive]]; | ||
if(!res) | ||
@@ -129,6 +79,44 @@ throw `JS type ${primitive} not supported`; | ||
if(Array.isArray(v)) | ||
return nice.Array; | ||
return nice.Arr; | ||
return nice.Object; | ||
return nice.Obj; | ||
}; | ||
nice.getType = v => { | ||
if(v === undefined) | ||
return nice.Undefined; | ||
if(v === null) | ||
return nice.Null; | ||
if(v && v._isAnything) | ||
return v._type; | ||
let res = typeof v; | ||
if(res === 'object'){ | ||
const c = v.constructor; | ||
//ugly for performance | ||
res = nice.jsTypes[c === Object | ||
? 'Object' | ||
: c === Number | ||
? 'Number' | ||
: c === String | ||
? 'String' | ||
: c.name]; | ||
if(!res) | ||
throw 'Unsupported object type ' + v.constructor.name; | ||
return res; | ||
} | ||
res = nice.jsBasicTypes[res]; | ||
if(!res) | ||
throw 'Unsupported type ' + typeof v; | ||
return res; | ||
}; | ||
defGet(Anything, 'help', function () { | ||
return this.description; | ||
}); |
@@ -10,3 +10,4 @@ def(nice, '_set', (o, ks, v) => { | ||
let k; | ||
while(k = ks.shift()){ | ||
while(ks.length){ | ||
k = ks.shift() | ||
o = o[k] = o[k] || {}; | ||
@@ -29,3 +30,4 @@ } | ||
let k; | ||
while(o !== undefined && (k = ks.shift())){ | ||
while(o !== undefined && ks.length){ | ||
k = ks.shift(); | ||
o = o[k]; | ||
@@ -32,0 +34,0 @@ } |
const formatRe = /(%([jds%]))/g; | ||
const formatMap = { s: String, d: Number, j: JSON.stringify }; | ||
const ID_SYMBOLS = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';//Crockford's base 32 | ||
defAll(nice, { | ||
_map: (o, f) => { | ||
_map (o, f) { | ||
let res = {}; | ||
for(let i in o) | ||
res[i] = f(o[i]); | ||
res[i] = f(o[i], i); | ||
return res; | ||
}, | ||
orderedStringify: o => !is.object(o) | ||
? JSON.stringify(o) | ||
: Array.isArray(o) | ||
? '[' + o.map(v => nice.orderedStringify(v)).join(',') + ']' | ||
: '{' + nice.reduceTo((a, key) => { | ||
a.push("\"" + key + '\":' + nice.orderedStringify(o[key])); | ||
}, [], Object.keys(o).sort()).join(',') + '}', | ||
_pick (o, a) { | ||
let res = {}; | ||
for(let i in o) | ||
a.includes(i) && (res[i] = o[i]); | ||
return res; | ||
}, | ||
objDiggDefault: (o, ...a) => { | ||
_size (o) { | ||
let res = 0; | ||
for(let i in o) | ||
res++; | ||
return res; | ||
}, | ||
_every (o, f) { | ||
for(let i in o) | ||
if(!f(o[i], i)) | ||
return false; | ||
return true; | ||
}, | ||
_some (o, f) { | ||
for(let i in o) | ||
if(f(o[i], i)) | ||
return true; | ||
return false; | ||
}, | ||
sortedPosition(a, v, f = (a, b) => a === b ? 0 : a > b ? 1 : -1){ | ||
let low = 0; | ||
let high = a.length;// - 1; | ||
while (low < high) { | ||
let m = (high + low) >> 1; | ||
let cmp = f(a[m], v); | ||
if (cmp < 0) { | ||
low = m + 1; | ||
} else if(cmp > 0) { | ||
high = m; | ||
} else { | ||
return m; | ||
} | ||
} | ||
return low; | ||
}, | ||
_if(c, f1, f2) { | ||
return c ? f1(c) : (typeof f2 === 'function' ? f2(c) : f2); | ||
}, | ||
orderedStringify: o => o === null | ||
? 'null' | ||
: !nice.isObject(o) | ||
? JSON.stringify(o) | ||
: Array.isArray(o) | ||
? '[' + o.map(v => nice.orderedStringify(v)).join(',') + ']' | ||
: '{' + nice.reduceTo(Object.keys(o).sort(), [], (a, key) => { | ||
a.push("\"" + key + '\":' + nice.orderedStringify(o[key])); | ||
}).join(',') + '}', | ||
objDiggDefault (o, ...a) { | ||
const v = a.pop(), l = a.length; | ||
@@ -34,3 +86,3 @@ let i = 0; | ||
objDiggMin: (o, ...a) => { | ||
objDiggMin (o, ...a) { | ||
const n = a.pop(); | ||
@@ -44,3 +96,3 @@ const k = a.pop(); | ||
objDiggMax: (o, ...a) => { | ||
objDiggMax (o, ...a) { | ||
const n = a.pop(); | ||
@@ -54,3 +106,3 @@ const k = a.pop(); | ||
objMax: (...oo) => { | ||
objMax (...oo) { | ||
return nice.reduceTo((res, o) => { | ||
@@ -67,5 +119,10 @@ _each(o, (v, k) => { | ||
eraseProperty: (o, k) => { | ||
Object.defineProperty(o, k, {writable: true}) && delete o[k]; | ||
Object.defineProperty(o, k, { writable: true, configurable: true }) && delete o[k]; | ||
}, | ||
rewriteProperty: (o, k, v) => { | ||
nice.eraseProperty(o, k); | ||
def(o, 'name', v); | ||
}, | ||
stripFunction: f => { | ||
@@ -82,2 +139,103 @@ nice.eraseProperty(f, 'length'); | ||
minutes: () => Date.now() / 60000 | 0, | ||
speedTest (f, times = 1) { | ||
const start = Date.now(); | ||
let i = 0; | ||
while(i++ < times) f(); | ||
const res = Date.now() - start | ||
console.log('Test took', res, 'ms'); | ||
return res; | ||
}, | ||
generateId () { | ||
let left = 25; | ||
let a = [Math.random() * 8 | 0]; | ||
while(left--){ | ||
a.push(ID_SYMBOLS[(Math.random() * 32 | 0)]); | ||
} | ||
return a.join(''); | ||
}, | ||
parseTraceString (s) { | ||
const a = s.match(/\/(.*):(\d+):(\d+)/); | ||
return a ? { location: '/' + a[1], line: +a[2], symbol: +a[3]} : a; | ||
}, | ||
throttle (f, dt = 250) { | ||
let lastT = 0, lastAs = null, lastThis = null, r, timeout = null; | ||
return function (...as) { | ||
const t = Date.now(); | ||
lastThis = this; | ||
if((lastT + dt) < t){ | ||
r = f.apply(lastThis, as); | ||
lastT = t; | ||
} else { | ||
lastAs = as; | ||
if(timeout === null){ | ||
timeout = setTimeout(() => { | ||
timeout = null; | ||
r = f.apply(lastThis, as); | ||
lastT = t; | ||
lastAs = null; | ||
lastThis = null; | ||
}, dt); | ||
} | ||
} | ||
return r; | ||
}; | ||
}, | ||
throttleTrailing (f, dt = 250) { | ||
let lAs = null, lThis = null, r, t = null; | ||
return function (...as) { | ||
lThis = this; | ||
lAs = as; | ||
if(t === null){ | ||
t = setTimeout(() => { | ||
t = null; | ||
r = f.apply(lThis, lAs); | ||
lAs = null; | ||
}, dt); | ||
} | ||
return r; | ||
}; | ||
}, | ||
throttleLeading (f, dt = 250) { | ||
let lastT = 0, r; | ||
return function (...as) { | ||
const t = Date.now(); | ||
if((lastT + dt) < t){ | ||
r = f.apply(this, as); | ||
lastT = t; | ||
} | ||
return r; | ||
}; | ||
}, | ||
_count (o, f){ | ||
let n = 0; | ||
_each(o, (v, k) => f(v,k,o) && n++); | ||
return n; | ||
}, | ||
_findFirstKeys (o, f, n) { | ||
const res = []; | ||
for(let k in o){ | ||
if(f(o[k], k)){ | ||
res.push(k); | ||
if(res.length === n) | ||
break; | ||
} | ||
}; | ||
return res; | ||
} | ||
}); | ||
@@ -95,15 +253,7 @@ | ||
const counterValues = {}; | ||
def(nice, function counter(name){ | ||
if(!name) | ||
return counterValues; | ||
counterValues[name] = counterValues[name] || 0; | ||
counterValues[name]++; | ||
}); | ||
defAll(nice, { | ||
format: (t, ...a) => { | ||
t = '' + t; | ||
format (t, ...a) { | ||
t = t ? '' + t : ''; | ||
if(a.length === 0) | ||
return t; | ||
a.unshift(t.replace(formatRe, (match, ptn, flag) => | ||
@@ -114,3 +264,3 @@ flag === '%' ? '%' : formatMap[flag](a.shift()))); | ||
objectComparer: (o1, o2, add, del) => { | ||
objectComparer (o1, o2, add, del) { | ||
_each(o2, (v, k) => o1[k] === v || add(v, k)); | ||
@@ -130,9 +280,9 @@ _each(o1, (v, k) => o2[k] === v || del(v, k)); | ||
let res; | ||
if(is.nice(o)){ | ||
if(o && o._isAnything){ | ||
res = o._type(); | ||
res._result = nice.clone(o.getResult()); | ||
res._result = nice.clone(o._getResult()); | ||
return res; | ||
} else if(Array.isArray(o)) { | ||
res = []; | ||
} else if(is.object(o)) { | ||
} else if(nice.isObject(o)) { | ||
res = {}; | ||
@@ -151,9 +301,9 @@ } else { | ||
return o; | ||
} else if(is.nice(o)) { | ||
res = o._type(); | ||
res._result = nice.cloneDeep(o.getResult()); | ||
} else if(o && o._isAnything) { | ||
res = reflect.newItem(o._type); | ||
res._result = nice.cloneDeep(o._getResult()); | ||
return res; | ||
} else if(Array.isArray(o)) { | ||
res = []; | ||
} else if(is.object(o)) { | ||
} else if(nice.isObject(o)) { | ||
res = {}; | ||
@@ -168,3 +318,3 @@ } else { | ||
diff: (a, b) => { | ||
diff (a, b) { | ||
if(a === b) | ||
@@ -175,6 +325,6 @@ return false; | ||
const ab = calculateChanges(a, b); | ||
(!is.Nothing(ab) && is.empty(ab)) || (add = ab); | ||
(!nice.isNothing(ab) && nice.isEmpty(ab)) || (add = ab); | ||
const ba = calculateChanges(b, a); | ||
(!is.Nothing(ba) && is.empty(ba)) || (del = ba); | ||
(!nice.isNothing(ba) && nice.isEmpty(ba)) || (del = ba); | ||
@@ -184,9 +334,11 @@ return (add || del) ? { del, add } : false; | ||
memoize: f => { | ||
const results = {}; | ||
return (k, ...a) => { | ||
if(results.hasOwnProperty(k)) | ||
return results[k]; | ||
return results[k] = f(k, ...a); | ||
memoize: (f, keyConverter) => { | ||
const res = (...a) => { | ||
const key = keyConverter ? keyConverter(a) : a[0]; | ||
if(key in res.cache) | ||
return res.cache[key]; | ||
return res.cache[key] = f(...a); | ||
}; | ||
res.cache = {}; | ||
return res; | ||
}, | ||
@@ -205,6 +357,41 @@ | ||
}, | ||
argumentNames (f) { | ||
const s = '' + f; | ||
const a = s.split('=>'); | ||
if(a.length > 1 && !a[0].includes('(')){ | ||
const s = a[0].trim(); | ||
if(/^[$A-Z_][0-9A-Z_$]*$/i.test(s)) | ||
return [s]; | ||
} | ||
let depth = 0; | ||
let lastEnd = 0; | ||
const res = []; | ||
for(let k in s) { | ||
const v = s[k]; | ||
k = +k; | ||
if(v === '(') { | ||
depth++; | ||
depth === 1 && (lastEnd = k); | ||
} else if (v === ','){ | ||
if(depth === 1){ | ||
res.push(s.substring(lastEnd + 1, k).trim()); | ||
lastEnd = k; | ||
} | ||
} else if( v === ')' ){ | ||
depth--; | ||
if(depth === 0) { | ||
res.push(s.substring(lastEnd + 1, k).trim()); | ||
break; | ||
} | ||
} | ||
}; | ||
return res; | ||
} | ||
}); | ||
defAll(nice, { | ||
super: (o, name, v) => { | ||
super (o, name, v) { | ||
v = v || o[name]; | ||
@@ -220,6 +407,7 @@ const parent = Object.getPrototypeOf(o); | ||
prototypes: o => { | ||
const parent = Object.getPrototypeOf(o); | ||
return parent | ||
? [parent].concat(nice.prototypes(parent)) | ||
: []; | ||
const res = []; | ||
let parent = o; | ||
while(parent = Object.getPrototypeOf(parent)) | ||
res.push(parent); | ||
return res; | ||
}, | ||
@@ -231,25 +419,21 @@ | ||
_deCapitalize: s => s[0].toLowerCase() + s.substr(1), | ||
_decapitalize: s => s[0].toLowerCase() + s.substr(1), | ||
doc: () => { | ||
const res = { types: {}, functions: [] }; | ||
times: (n, f, a) => { | ||
n = n > 0 ? n : 0; | ||
let i = 0; | ||
if(Array.isArray(a)){ | ||
while(i < n) | ||
a.push(f(i++, a)); | ||
} else if (a !== undefined){ | ||
throw 'Accumulator should be an Array'; | ||
} else { | ||
while(i < n) | ||
f(i++); | ||
} | ||
return a; | ||
}, | ||
nice._on('signature', s => { | ||
const o = {}; | ||
_each(s, (v, k) => nice.Switch(k) | ||
.equal('action')() | ||
.equal('source').use(() => o.source = v.toString()) | ||
.equal('signature').use(() => o[k] = v.map(t => t.type.title)) | ||
.default.use(() => o[k] = v)); | ||
res.functions.push(o); | ||
}); | ||
nice._on('Type', t => { | ||
const o = { title: t.title }; | ||
t.hasOwnProperty('description') && (o.description = t.description); | ||
t.extends && (o.extends = t.super.title); | ||
res.types[t.title] = o; | ||
}); | ||
return res; | ||
fromJson (v) { | ||
return nice.valueType(v).fromValue(v); | ||
} | ||
@@ -289,8 +473,8 @@ }); | ||
function calculateChanges(a, b){ | ||
if(!a) | ||
if(a === undefined) | ||
return b; | ||
if(Array.isArray(b)){ | ||
return Array.isArray(a) ? compareObjects(a, b) : b; | ||
} else if(is.object(b)) { | ||
return is.object(a) ? compareObjects(a, b) : b; | ||
} else if(nice.isObject(b)) { | ||
return nice.isObject(a) ? compareObjects(a, b) : b; | ||
} else { | ||
@@ -297,0 +481,0 @@ if(a !== b) |
nice.Type({ | ||
title: 'Value', | ||
name: 'Value', | ||
extends: 'Something', | ||
init: i => i.setResult(i._type.default()), | ||
default: () => undefined, | ||
defaultValue: () => ({}), | ||
isSubType, | ||
creator: () => { throw 'Use Single or Object.' }, | ||
initBy: (z, v) => { | ||
if(v === undefined) | ||
v = z._type.defaultValueBy(); | ||
constructor: (z, ...a) => a.length && z.setValue(...a), | ||
fromResult: function(result){ | ||
return this().setResult(result); | ||
z._type.setValue(z, v); | ||
}, | ||
saveValue: function (_nv_) { | ||
const _nt_ = this.title; | ||
return _nt_ === 'Object' ? _nv_ : { _nt_, _nv_ }; | ||
}, | ||
creator: () => { throw 'Use Single or Object.' }, | ||
loadValue: v => v._nv_ || v, | ||
proto: create(nice.Anything.proto, { | ||
_isSingleton: false, | ||
setResult: function(v) { | ||
this._result = v; | ||
return this; | ||
}, | ||
getResult: function() { | ||
return this._result; | ||
}, | ||
valueOf: function (){ return this.getResult(); } | ||
proto: create(Anything.proto, { | ||
valueOf (){ return this._value; } | ||
}), | ||
@@ -46,9 +25,13 @@ | ||
by: function(f){ | ||
this.target.constructor = f; | ||
by(...a){ | ||
if(typeof a[0] === 'function') | ||
this.target.initBy = a[0]; | ||
else if(typeof a[0] === 'string') | ||
this.target.initBy = (z, ...vs) => { | ||
a.forEach((name, i) => z.set(name, vs[i])); | ||
} | ||
return this; | ||
}, | ||
assign: function (...o) { | ||
assign (...o) { | ||
Object.assign(this.target.proto, ...o); | ||
@@ -58,3 +41,3 @@ return this; | ||
addProperty: function (name, cfg){ | ||
addProperty (name, cfg){ | ||
Object.defineProperty(this.target.proto, name, cfg); | ||
@@ -64,3 +47,3 @@ return this; | ||
Const: function(name, value){ | ||
Const (name, value){ | ||
def(this.target, name, value); | ||
@@ -70,9 +53,2 @@ def(this.target.proto, name, value); | ||
}, | ||
ReadOnly: function(...a){ | ||
const [name, f] = a.length === 2 ? a : [a[0].name, a[0]]; | ||
expect(f).function(); | ||
defGet(this.target.proto, name, f); | ||
return this; | ||
} | ||
}, | ||
@@ -82,3 +58,3 @@ }).about('Parent type for all values.'); | ||
defGet(nice.Value.configProto, 'Method', function () { | ||
defGet(nice.Value.configProto, function Method() { | ||
const type = this.target; | ||
@@ -99,5 +75,5 @@ return Func.next({ returnValue: this, signature: [{type}] }); | ||
function isSubType(t){ | ||
is.string(t) && (t = nice.Type(t)); | ||
typeof t === 'string' && (t = nice.Type(t)); | ||
return t === this || t.isPrototypeOf(this); | ||
}; | ||
nice.jsTypes.isSubType = isSubType; |
1036
html/html.js
@@ -1,22 +0,44 @@ | ||
nice.Type('Html') | ||
//TODO: separate attributes and tagValues | ||
const runtime = {}; | ||
nice.Type('Html', (z, tag) => tag && z.tag(tag)) | ||
.about('Represents HTML element.') | ||
.by((z, tag) => tag && z.tag(tag)) | ||
.String('tag', 'div') | ||
.Object('eventHandlers') | ||
.Action.about('Adds event handler to an element.')(function on(z, name, f){ | ||
if(name === 'domNode' && nice.isEnvBrowser){ | ||
if(!z.id()) | ||
throw `Give element an id to use domNode event.`; | ||
const el = document.getElementById(z.id()); | ||
el && f(el); | ||
.string('tag', 'div') | ||
.boolean('forceRepaint', false) | ||
.object('eventHandlers') | ||
.object('cssSelectors') | ||
.Action.about('Adds event handler to an element.')(function on(e, name, f){ | ||
if(IS_BROWSER){ | ||
//TODO: check if adding those to eventHandlers is necessary | ||
if(name === 'domNode'){ | ||
if(!e.id()) | ||
throw `Give element an id to use domNode event.`; | ||
const el = document.getElementById(e.id()); | ||
el && f(el); | ||
} | ||
if(name === 'detach'){ | ||
'__detachListeners' in e.properties() | ||
? e.properties('__detachListeners').push(f) | ||
: e.properties('__detachListeners', [f]); | ||
} | ||
} | ||
nice.Switch(z.eventHandlers(name)) | ||
.Nothing.use(() => z.eventHandlers(name, [f])) | ||
.default.use(a => a.push(f)); | ||
const hs = e.eventHandlers(); | ||
hs[name] ? hs[name].push(f) : e.eventHandlers(name, [f]); | ||
return e; | ||
}) | ||
.Object('style') | ||
.Object('attributes') | ||
.Array('children') | ||
.Method('class', (z, ...vs) => { | ||
const current = z.attributes('className').or('')(); | ||
.Action.about('Removes event handler from an element.')(function off(e, name, f){ | ||
const handlers = e.eventHandlers(name); | ||
handlers && nice.removeValue(handlers, f); | ||
return e; | ||
}) | ||
.object('style') | ||
.object('attributes') | ||
.object('properties') | ||
.Method('assertId', z => { | ||
z.id() || z.id(nice.genereteAutoId()); | ||
return z.id(); | ||
}) | ||
.Method.about('Adds values to className attribute.')('class', (z, ...vs) => { | ||
const current = z.attributes('className') || ''; | ||
if(!vs.length) | ||
@@ -32,85 +54,217 @@ return current; | ||
.ReadOnly(html) | ||
.Method(function scrollTo(z, offset = 10){ | ||
z.on('domNode', node => { | ||
node && window.scrollTo(node.offsetLeft - offset, node.offsetTop - offset); | ||
.ReadOnly('dom', createDom) | ||
.Method.about('Scroll browser screen to an element.')(function scrollTo(z, offset = 10){ | ||
z.on('domNode', n => { | ||
n && window.scrollTo(n.offsetLeft - offset, n.offsetTop - offset); | ||
}); | ||
return z; | ||
}) | ||
.Action('focus', z => z.on('domNode', node => node.focus())) | ||
.Action(function add(z, ...children) { | ||
.Action.about('Focuses DOM element.')('focus', (z, preventScroll) => | ||
z.on('domNode', node => node.focus(preventScroll))) | ||
.Action('rBox', (z, ...as) => z.add(RBox(...as))) | ||
.Action.about('Adds children to an element.')(function add(z, ...children) { | ||
if(z._children === undefined){ | ||
z._children = []; | ||
} else { | ||
if(z._children && z._children._isBoxArray) | ||
throw 'Children of this element already bound to BoxArray'; | ||
} | ||
children.forEach(c => { | ||
if(is.array(c)) | ||
return _each(c, _c => z.add(_c)); | ||
if(c === undefined || c === null) | ||
return; | ||
if(is.Array(c)) | ||
return c.each(_c => z.add(_c)); | ||
if(typeof c === 'string' || c._isStr) | ||
return z._children.push(c); | ||
if(c === undefined || c === null) | ||
if(Array.isArray(c)) | ||
return c.forEach(_c => z.add(_c)); | ||
if(c instanceof Set){ | ||
for(let _c of c) z.add(_c); | ||
return; | ||
} | ||
if(is.string(c)) | ||
return z.children(c); | ||
if(c._isArr) | ||
return c.each(_c => z.add(_c)); | ||
if(is.number(c)) | ||
return z.children('' + c); | ||
if(c._isNum || typeof c === 'number') | ||
return z._children.push(c); | ||
if(c._isBox) | ||
return z._children.push(c); | ||
if(c === z) | ||
return z.children(`Errro: Can't add element to itself.`); | ||
return z._children.push(`Errro: Can't add element to itself.`); | ||
if(!c || !is.Anything(c)) | ||
return z.children('Bad child: ' + c); | ||
if(c._isErr) | ||
return z._children.push(c.toString()); | ||
c.up = z; | ||
c._up_ = z; | ||
z.children.push(c); | ||
if(!c || !nice.isAnything(c)) | ||
return z._children.push('Bad child: ' + | ||
(c && c.toString) ? c.toString() : JSON.stringify(c)); | ||
if(c !== undefined){ | ||
c = extractUp(c); | ||
} | ||
z._children.push(c); | ||
}); | ||
}); | ||
nice.ReadOnly.Anything('dom', z => document.createTextNode("" + z._value)); | ||
const Html = nice.Html; | ||
Test('Simple html element with string child', Html => { | ||
expect(Html().add('qwe').html).is('<div>qwe</div>'); | ||
}); | ||
nice._on('Extension', o => o.parent === nice.Html && | ||
def(Html.proto, o.child.title, function (...a){ | ||
const res = nice[o.child.title](...a); | ||
this.add(res); | ||
return res; | ||
}) | ||
); | ||
Test("insert Html", (Html) => { | ||
const div = Html('li'); | ||
const div2 = Html('b').add('qwe'); | ||
div.add(div2); | ||
expect(div.html).is('<li><b>qwe</b></li>'); | ||
}); | ||
Test("Html tag name", (Html) => { | ||
expect(Html('li').html).is('<li></li>'); | ||
}); | ||
Html.proto.Box = function(...a) { | ||
const res = Box(...a); | ||
res.up = this; | ||
this.add(res); | ||
return res; | ||
Test("Html class name", (Html) => { | ||
expect(Html().class('qwe').html).is('<div class="qwe"></div>'); | ||
}); | ||
Test("Html of single value", (Single) => { | ||
expect(Single(5).html).is('5'); | ||
}); | ||
Test("Html children array", (Div) => { | ||
expect(Div(['qwe', 'asd']).html).is('<div>qweasd</div>'); | ||
}); | ||
Test("Html children Arr", (Div, Arr) => { | ||
expect(Div(Arr('qwe', 'asd')).html).is('<div>qweasd</div>'); | ||
}); | ||
Html.map = function(f = v => v){ | ||
return a => this(...a.map(f)); | ||
}; | ||
//v => Div(...v.map(o => Div(JSON.stringify(o))))) | ||
//v => Div(...v.map(Pipe(JSON.stringify, Div)) | ||
//map($1, Pipe(JSON.stringify, Div)) | ||
//Pipe( map, ..., Div) | ||
// | ||
//v => Div.map(v, Pipe(JSON.stringify, Div)) | ||
'clear,alignContent,alignItems,alignSelf,alignmentBaseline,all,animation,animationDelay,animationDirection,animationDuration,animationFillMode,animationIterationCount,animationName,animationPlayState,animationTimingFunction,backfaceVisibility,background,backgroundAttachment,backgroundBlendMode,backgroundClip,backgroundColor,backgroundImage,backgroundOrigin,backgroundPosition,backgroundPositionX,backgroundPositionY,backgroundRepeat,backgroundRepeatX,backgroundRepeatY,backgroundSize,baselineShift,border,borderBottom,borderBottomColor,borderBottomLeftRadius,borderBottomRightRadius,borderBottomStyle,borderBottomWidth,borderCollapse,borderColor,borderImage,borderImageOutset,borderImageRepeat,borderImageSlice,borderImageSource,borderImageWidth,borderLeft,borderLeftColor,borderLeftStyle,borderLeftWidth,borderRadius,borderRight,borderRightColor,borderRightStyle,borderRightWidth,borderSpacing,borderStyle,borderTop,borderTopColor,borderTopLeftRadius,borderTopRightRadius,borderTopStyle,borderTopWidth,borderWidth,bottom,boxShadow,boxSizing,breakAfter,breakBefore,breakInside,bufferedRendering,captionSide,clip,clipPath,clipRule,color,colorInterpolation,colorInterpolationFilters,colorRendering,columnCount,columnFill,columnGap,columnRule,columnRuleColor,columnRuleStyle,columnRuleWidth,columnSpan,columnWidth,columns,content,counterIncrement,counterReset,cursor,cx,cy,direction,display,dominantBaseline,emptyCells,fill,fillOpacity,fillRule,filter,flex,flexBasis,flexDirection,flexFlow,flexGrow,flexShrink,flexWrap,float,floodColor,floodOpacity,font,fontFamily,fontFeatureSettings,fontKerning,fontSize,fontStretch,fontStyle,fontVariant,fontVariantLigatures,fontWeight,height,imageRendering,isolation,justifyContent,left,letterSpacing,lightingColor,lineHeight,listStyle,listStyleImage,listStylePosition,listStyleType,margin,marginBottom,marginLeft,marginRight,marginTop,marker,markerEnd,markerMid,markerStart,mask,maskType,maxHeight,maxWidth,maxZoom,minHeight,minWidth,minZoom,mixBlendMode,motion,motionOffset,motionPath,motionRotation,objectFit,objectPosition,opacity,order,orientation,orphans,outline,outlineColor,outlineOffset,outlineStyle,outlineWidth,overflow,overflowWrap,overflowX,overflowY,padding,paddingBottom,paddingLeft,paddingRight,paddingTop,page,pageBreakAfter,pageBreakBefore,pageBreakInside,paintOrder,perspective,perspectiveOrigin,pointerEvents,position,quotes,r,resize,right,rx,ry,shapeImageThreshold,shapeMargin,shapeOutside,shapeRendering,speak,stopColor,stopOpacity,stroke,strokeDasharray,strokeDashoffset,strokeLinecap,strokeLinejoin,strokeMiterlimit,strokeOpacity,strokeWidth,tabSize,tableLayout,textAlign,textAlignLast,textAnchor,textCombineUpright,textDecoration,textIndent,textOrientation,textOverflow,textRendering,textShadow,textTransform,top,touchAction,transform,transformOrigin,transformStyle,transition,transitionDelay,transitionDuration,transitionProperty,transitionTimingFunction,unicodeBidi,unicodeRange,userZoom,vectorEffect,verticalAlign,visibility,whiteSpace,widows,width,willChange,wordBreak,wordSpacing,wordWrap,writingMode,x,y,zIndex,zoom' | ||
.split(',').forEach( property => { | ||
nice.define(Html.proto, property, function(...a) { | ||
is.object(a[0]) | ||
? _each(a[0], (v, k) => this.style(property + nice.capitalize(k), v)) | ||
: this.style(property, is.string(a[0]) ? nice.format(...a) : a[0]); | ||
return this; | ||
}); | ||
nice.Type('Style') | ||
.about('Represents CSS style.'); | ||
const Style = nice.Style; | ||
defGet(Html.proto, function hover(){ | ||
const style = Style(); | ||
this.needAutoClass = true; | ||
this.cssSelectors(':hover', style); | ||
return style; | ||
}); | ||
def(Html.proto, 'Css', function(s = ''){ | ||
s = s.toLowerCase(); | ||
if(s in this.cssSelectors()) | ||
return this.cssSelectors(s); | ||
this.needAutoClass = true; | ||
const style = Style(); | ||
style.up = this; | ||
this.cssSelectors(s, style); | ||
return style; | ||
}); | ||
function addCreator(type){ | ||
def(Html.proto, type.name, function (...a){ | ||
const res = type(...a); | ||
const parent = this; | ||
this.add(res); | ||
Object.defineProperty(res, 'up', { configurable: true, get(){ | ||
delete res.up; | ||
return parent; | ||
}}); | ||
return res; | ||
}); | ||
const _t = nice._decapitalize(type.name); | ||
_t in Html.proto || def(Html.proto, _t, function (...a){ | ||
return this.add(type(...a)); | ||
}); | ||
} | ||
reflect.on('extension', ({child, parent}) => { | ||
if(parent === Html || Html.isPrototypeOf(parent)){ | ||
addCreator(child); | ||
} | ||
}); | ||
'value,checked,accept,accesskey,action,align,alt,async,autocomplete,autofocus,autoplay,autosave,bgcolor,buffered,challenge,charset,cite,code,codebase,cols,colspan,contenteditable,contextmenu,controls,coords,crossorigin,data,datetime,default,defer,dir,dirname,disabled,download,draggable,dropzone,enctype,for,form,formaction,headers,hidden,high,href,hreflang,icon,id,integrity,ismap,itemprop,keytype,kind,label,lang,language,list,loop,low,manifest,max,maxlength,media,method,min,multiple,muted,name,novalidate,open,optimum,pattern,ping,placeholder,poster,preload,radiogroup,readonly,rel,required,reversed,rows,rowspan,sandbox,scope,scoped,seamless,selected,shape,sizes,slot,span,spellcheck,src,srcdoc,srclang,srcset,start,step,summary,tabindex,target,title,type,usemap,wrap' | ||
//removed: src removed to avoid conflict with 'src' attribute | ||
'alignContent,alignItems,alignSelf,alignmentBaseline,all,animation,animationDelay,animationDirection,animationDuration,animationFillMode,animationIterationCount,animationName,animationPlayState,animationTimingFunction,appearance,backdropFilter,backfaceVisibility,background,backgroundAttachment,backgroundBlendMode,backgroundClip,backgroundColor,backgroundImage,backgroundOrigin,backgroundPosition,backgroundPositionX,backgroundPositionY,backgroundRepeat,backgroundRepeatX,backgroundRepeatY,backgroundSize,baselineShift,blockSize,border,borderBlockEnd,borderBlockEndColor,borderBlockEndStyle,borderBlockEndWidth,borderBlockStart,borderBlockStartColor,borderBlockStartStyle,borderBlockStartWidth,borderBottom,borderBottomColor,borderBottomLeftRadius,borderBottomRightRadius,borderBottomStyle,borderBottomWidth,borderCollapse,borderColor,borderImage,borderImageOutset,borderImageRepeat,borderImageSlice,borderImageSource,borderImageWidth,borderInlineEnd,borderInlineEndColor,borderInlineEndStyle,borderInlineEndWidth,borderInlineStart,borderInlineStartColor,borderInlineStartStyle,borderInlineStartWidth,borderLeft,borderLeftColor,borderLeftStyle,borderLeftWidth,borderRadius,borderRight,borderRightColor,borderRightStyle,borderRightWidth,borderSpacing,borderStyle,borderTop,borderTopColor,borderTopLeftRadius,borderTopRightRadius,borderTopStyle,borderTopWidth,borderWidth,bottom,boxShadow,boxSizing,breakAfter,breakBefore,breakInside,bufferedRendering,captionSide,caretColor,clear,clip,clipPath,clipRule,color,colorInterpolation,colorInterpolationFilters,colorRendering,colorScheme,columnCount,columnFill,columnGap,columnRule,columnRuleColor,columnRuleStyle,columnRuleWidth,columnSpan,columnWidth,columns,contain,containIntrinsicSize,content,counterIncrement,counterReset,cursor,cx,cy,d,direction,display,dominantBaseline,emptyCells,fill,fillOpacity,fillRule,filter,flex,flexBasis,flexDirection,flexFlow,flexGrow,flexShrink,flexWrap,float,floodColor,floodOpacity,font,fontDisplay,fontFamily,fontFeatureSettings,fontKerning,fontOpticalSizing,fontSize,fontStretch,fontStyle,fontVariant,fontVariantCaps,fontVariantEastAsian,fontVariantLigatures,fontVariantNumeric,fontVariationSettings,fontWeight,gap,grid,gridArea,gridAutoColumns,gridAutoFlow,gridAutoRows,gridColumn,gridColumnEnd,gridColumnGap,gridColumnStart,gridGap,gridRow,gridRowEnd,gridRowGap,gridRowStart,gridTemplate,gridTemplateAreas,gridTemplateColumns,gridTemplateRows,height,hyphens,imageOrientation,imageRendering,inlineSize,isolation,justifyContent,justifyItems,justifySelf,left,letterSpacing,lightingColor,lineBreak,lineHeight,listStyle,listStyleImage,listStylePosition,listStyleType,margin,marginBlockEnd,marginBlockStart,marginBottom,marginInlineEnd,marginInlineStart,marginLeft,marginRight,marginTop,marker,markerEnd,markerMid,markerStart,mask,maskType,maxBlockSize,maxHeight,maxInlineSize,maxWidth,maxZoom,minBlockSize,minHeight,minInlineSize,minWidth,minZoom,mixBlendMode,objectFit,objectPosition,offset,offsetDistance,offsetPath,offsetRotate,opacity,order,orientation,orphans,outline,outlineColor,outlineOffset,outlineStyle,outlineWidth,overflow,overflowAnchor,overflowWrap,overflowX,overflowY,overscrollBehavior,overscrollBehaviorBlock,overscrollBehaviorInline,overscrollBehaviorX,overscrollBehaviorY,padding,paddingBlockEnd,paddingBlockStart,paddingBottom,paddingInlineEnd,paddingInlineStart,paddingLeft,paddingRight,paddingTop,pageBreakAfter,pageBreakBefore,pageBreakInside,paintOrder,perspective,perspectiveOrigin,placeContent,placeItems,placeSelf,pointerEvents,position,quotes,r,resize,right,rowGap,rubyPosition,rx,ry,scrollBehavior,scrollMargin,scrollMarginBlock,scrollMarginBlockEnd,scrollMarginBlockStart,scrollMarginBottom,scrollMarginInline,scrollMarginInlineEnd,scrollMarginInlineStart,scrollMarginLeft,scrollMarginRight,scrollMarginTop,scrollPadding,scrollPaddingBlock,scrollPaddingBlockEnd,scrollPaddingBlockStart,scrollPaddingBottom,scrollPaddingInline,scrollPaddingInlineEnd,scrollPaddingInlineStart,scrollPaddingLeft,scrollPaddingRight,scrollPaddingTop,scrollSnapAlign,scrollSnapStop,scrollSnapType,shapeImageThreshold,shapeMargin,shapeOutside,shapeRendering,size,speak,stopColor,stopOpacity,stroke,strokeDasharray,strokeDashoffset,strokeLinecap,strokeLinejoin,strokeMiterlimit,strokeOpacity,strokeWidth,tabSize,tableLayout,textAlign,textAlignLast,textAnchor,textCombineUpright,textDecoration,textDecorationColor,textDecorationLine,textDecorationSkipInk,textDecorationStyle,textIndent,textOrientation,textOverflow,textRendering,textShadow,textSizeAdjust,textTransform,textUnderlinePosition,top,touchAction,transform,transformBox,transformOrigin,transformStyle,transition,transitionDelay,transitionDuration,transitionProperty,transitionTimingFunction,unicodeBidi,unicodeRange,userSelect,userZoom,vectorEffect,verticalAlign,visibility,webkitAlignContent,webkitAlignItems,webkitAlignSelf,webkitAnimation,webkitAnimationDelay,webkitAnimationDirection,webkitAnimationDuration,webkitAnimationFillMode,webkitAnimationIterationCount,webkitAnimationName,webkitAnimationPlayState,webkitAnimationTimingFunction,webkitAppRegion,webkitAppearance,webkitBackfaceVisibility,webkitBackgroundClip,webkitBackgroundOrigin,webkitBackgroundSize,webkitBorderAfter,webkitBorderAfterColor,webkitBorderAfterStyle,webkitBorderAfterWidth,webkitBorderBefore,webkitBorderBeforeColor,webkitBorderBeforeStyle,webkitBorderBeforeWidth,webkitBorderBottomLeftRadius,webkitBorderBottomRightRadius,webkitBorderEnd,webkitBorderEndColor,webkitBorderEndStyle,webkitBorderEndWidth,webkitBorderHorizontalSpacing,webkitBorderImage,webkitBorderRadius,webkitBorderStart,webkitBorderStartColor,webkitBorderStartStyle,webkitBorderStartWidth,webkitBorderTopLeftRadius,webkitBorderTopRightRadius,webkitBorderVerticalSpacing,webkitBoxAlign,webkitBoxDecorationBreak,webkitBoxDirection,webkitBoxFlex,webkitBoxOrdinalGroup,webkitBoxOrient,webkitBoxPack,webkitBoxReflect,webkitBoxShadow,webkitBoxSizing,webkitClipPath,webkitColumnBreakAfter,webkitColumnBreakBefore,webkitColumnBreakInside,webkitColumnCount,webkitColumnGap,webkitColumnRule,webkitColumnRuleColor,webkitColumnRuleStyle,webkitColumnRuleWidth,webkitColumnSpan,webkitColumnWidth,webkitColumns,webkitFilter,webkitFlex,webkitFlexBasis,webkitFlexDirection,webkitFlexFlow,webkitFlexGrow,webkitFlexShrink,webkitFlexWrap,webkitFontFeatureSettings,webkitFontSizeDelta,webkitFontSmoothing,webkitHighlight,webkitHyphenateCharacter,webkitJustifyContent,webkitLineBreak,webkitLineClamp,webkitLocale,webkitLogicalHeight,webkitLogicalWidth,webkitMarginAfter,webkitMarginBefore,webkitMarginEnd,webkitMarginStart,webkitMask,webkitMaskBoxImage,webkitMaskBoxImageOutset,webkitMaskBoxImageRepeat,webkitMaskBoxImageSlice,webkitMaskBoxImageSource,webkitMaskBoxImageWidth,webkitMaskClip,webkitMaskComposite,webkitMaskImage,webkitMaskOrigin,webkitMaskPosition,webkitMaskPositionX,webkitMaskPositionY,webkitMaskRepeat,webkitMaskRepeatX,webkitMaskRepeatY,webkitMaskSize,webkitMaxLogicalHeight,webkitMaxLogicalWidth,webkitMinLogicalHeight,webkitMinLogicalWidth,webkitOpacity,webkitOrder,webkitPaddingAfter,webkitPaddingBefore,webkitPaddingEnd,webkitPaddingStart,webkitPerspective,webkitPerspectiveOrigin,webkitPerspectiveOriginX,webkitPerspectiveOriginY,webkitPrintColorAdjust,webkitRtlOrdering,webkitRubyPosition,webkitShapeImageThreshold,webkitShapeMargin,webkitShapeOutside,webkitTapHighlightColor,webkitTextCombine,webkitTextDecorationsInEffect,webkitTextEmphasis,webkitTextEmphasisColor,webkitTextEmphasisPosition,webkitTextEmphasisStyle,webkitTextFillColor,webkitTextOrientation,webkitTextSecurity,webkitTextSizeAdjust,webkitTextStroke,webkitTextStrokeColor,webkitTextStrokeWidth,webkitTransform,webkitTransformOrigin,webkitTransformOriginX,webkitTransformOriginY,webkitTransformOriginZ,webkitTransformStyle,webkitTransition,webkitTransitionDelay,webkitTransitionDuration,webkitTransitionProperty,webkitTransitionTimingFunction,webkitUserDrag,webkitUserModify,webkitUserSelect,webkitWritingMode,whiteSpace,widows,width,willChange,wordBreak,wordSpacing,wordWrap,writingMode,x,y,zIndex,zoom' | ||
.split(',').forEach( property => { | ||
Html.proto[property] = function(...a){ | ||
return a.length | ||
? this.attributes(property, ...a) | ||
: nice.Switch(this.attributes(property)).Value.use(v => v()).default(''); | ||
Html.proto[property] = function(...a) { | ||
if(a.length === 0) | ||
return this.style(property); | ||
nice.Switch(a[0]) | ||
.isObject().use(o => _each(o, (v, k) => this.style(property + nice.capitalize(k), v))) | ||
.default.use(() => this.style(property, a.length > 1 ? nice.format(...a) : a[0])) | ||
return this; | ||
}; | ||
Style.proto[property] = function(...a) { | ||
nice.isObject(a[0]) | ||
? _each(a[0], (v, k) => this.set(property + nice.capitalize(k), v)) | ||
: this.set(property, a.length > 1 ? nice.format(...a) : a[0]); | ||
return this; | ||
}; | ||
}); | ||
function text(){ | ||
return this.children | ||
//'span' removed to avoid conflict with creating 'span' child | ||
'value,checked,accept,accesskey,action,align,alt,async,autocomplete,autofocus,autoplay,autosave,bgcolor,buffered,challenge,charset,cite,code,codebase,cols,colspan,contextmenu,controls,coords,crossorigin,data,datetime,default,defer,dir,dirname,disabled,download,draggable,dropzone,enctype,for,form,formaction,headers,hidden,high,href,hreflang,icon,id,integrity,ismap,itemprop,keytype,kind,label,lang,language,list,loop,low,manifest,max,maxlength,media,method,min,multiple,muted,name,novalidate,open,optimum,pattern,ping,placeholder,poster,preload,radiogroup,readonly,rel,required,reversed,rows,rowspan,sandbox,scope,scoped,seamless,selected,shape,sizes,slot,spellcheck,src,srcdoc,srclang,srcset,start,step,summary,tabindex,target,title,type,usemap,wrap' | ||
.split(',').forEach( property => def(Html.proto, property, function(...a){ | ||
if(a.length){ | ||
this.attributes(property, a.length > 1 ? nice.format(...a) : a[0]); | ||
return this; | ||
} else { | ||
return this.attributes(property); | ||
} | ||
})); | ||
def(Html.proto, 'contentEditable', function(...a){ | ||
if(a.length){ | ||
this.attributes('contentEditable', !!a[0]); | ||
this.forceRepaint(true); | ||
return this; | ||
} else { | ||
return this.attributes(property); | ||
} | ||
}); | ||
Test('Css propperty format', Div => { | ||
expect(Div().border('3px', 'silver', 'solid').html) | ||
.is('<div style="border:3px silver solid"></div>'); | ||
}); | ||
function text(z){ | ||
return z._children | ||
? z._children | ||
.map(v => v.text | ||
? v.text | ||
: nice.htmlEscape(is.function(v) ? v(): v)) | ||
.getResult().join(''); | ||
: nice.htmlEscape(nice.isFunction(v) ? v() : v)) | ||
.jsValue.join('') | ||
: ''; | ||
}; | ||
@@ -120,4 +274,5 @@ | ||
function compileStyle (s){ | ||
const a = []; | ||
_each(s, (v, k) => a.push(k.replace(/([A-Z])/g, "-$1").toLowerCase() + ':' + v)); | ||
let a = []; | ||
_each(s, (v, k) => | ||
a.push(k.replace(/([A-Z])/g, "-$1").toLowerCase() + ':' + v)); | ||
return a.join(';'); | ||
@@ -127,77 +282,327 @@ }; | ||
const resultToHtml = r => { | ||
const a = ['<', r.tag]; | ||
function compileSelectors (h){ | ||
const a = []; | ||
_each(h.cssSelectors(), (v, k) => a.push('.', | ||
getAutoClass(h.attributes('className')), | ||
k[0] === ':' ? '' : ' ', k, '{', compileStyle(v), '}')); | ||
return a.length ? '<style>' + a.join('') + '</style>' : ''; | ||
}; | ||
const style = compileStyle(r.style); | ||
style && a.push(" ", 'style="', style, '"'); | ||
_each(r.attributes, (v, k) => { | ||
k === 'className' && (k = 'class'); | ||
a.push(" ", k , '="', v, '"'); | ||
const _html = v => v._isAnything ? v.html : nice.htmlEscape(v); | ||
//nice.ReadOnly.Box('html', ({_value}) => _value && _html(_value)); | ||
nice.ReadOnly.Single('html', z => _html(z._value)); | ||
nice.ReadOnly.Arr('html', z => z.reduceTo([], (a, v) => a.push(_html(v))) | ||
.map(_html).join('')); | ||
function html(z){ | ||
const tag = z.tag(); | ||
const selectors = compileSelectors(z) || ''; | ||
let as = ''; | ||
let style = compileStyle(z.style()); | ||
style && (as = ' style="' + style + '"'); | ||
_each(z.attributes(), (v, k) => { | ||
k === 'className' && (k = 'class', v.trim()); | ||
as += ` ${k}="${v}"`; | ||
}); | ||
a.push('>'); | ||
let body = ''; | ||
let cc = z._children; | ||
if(cc){ | ||
cc._isAnything && (cc = cc()); | ||
cc.forEach(c => body += c._isAnything ? c.html : nice.htmlEscape(c)); | ||
} | ||
_each(r.children, c => a.push(c && c._nv_ && c._nv_.tag | ||
? resultToHtml(c._nv_) | ||
: nice.htmlEscape(c))); | ||
return `${selectors}<${tag}${as}>${body}</${tag}>`; | ||
}; | ||
a.push('</', r.tag, '>'); | ||
return a.join(''); | ||
function toDom(e) { | ||
if(e === undefined) | ||
return document.createTextNode(''); | ||
if(e && e._isBox) | ||
throw `toDom(e) shoud never recieve Box`; | ||
return e._isAnything | ||
? e.dom | ||
: document.createTextNode(e); | ||
}; | ||
function createDom(e){ | ||
const value = e._value; | ||
const res = document.createElement(value.tag); | ||
_each(value.style, (v, k) => res.style[k] = '' + v); | ||
_each(value.attributes, (v, k) => res.setAttribute(k,v)); | ||
_each(value.properties, (v, k) => res[k] = v); | ||
if(e._children) | ||
e._children._isBoxArray | ||
? attachBoxArrayChildren(res, e._children) | ||
: e._children.forEach(c => attachNode(c, res)); | ||
_each(value.eventHandlers, (ls, type) => { | ||
if(type === 'domNode' || type === 'detach') | ||
return ls.forEach(f => f(res)); | ||
ls.forEach(f => res.addEventListener(type, f, true)); | ||
}); | ||
e.needAutoClass === true && assertAutoClass(res); | ||
addSelectors(value.cssSelectors, res); | ||
return res; | ||
} | ||
const childrenCounter = (o, v) => { | ||
v && (o[v] ? o[v]++ : (o[v] = 1)); | ||
return o; | ||
}; | ||
function html(){ | ||
return resultToHtml(this._result); | ||
function cancelNestedSubscription(subscription){ | ||
const nested = subscription.nestedSubscription; | ||
nested.source.unsubscribe(nested); | ||
delete subscription.nestedSubscription; | ||
delete nested.parentSubscription; | ||
nested.nestedSubscription !== undefined && cancelNestedSubscription(nested); | ||
} | ||
function createSubscription(box, state, dom){ | ||
const f = function(newState){ | ||
if(f.nestedSubscription !== undefined){ | ||
cancelNestedSubscription(f); | ||
} | ||
if(newState !== undefined && newState._isBox === true){ | ||
f.nestedSubscription = createSubscription(newState, f.state, f.dom); | ||
f.nestedSubscription.parentSubscription = f; | ||
newState.subscribe(f.nestedSubscription); | ||
} else { | ||
while(newState !== undefined && newState._up_ && newState._up_ !== newState) | ||
newState = newState._up_; | ||
let old = f.state; | ||
while(old && old._isBox) old = old(); | ||
const newDom = refreshElement(newState, old, f.dom); | ||
if(newDom !== f.dom){ | ||
f.dom = newDom; | ||
f.dom.__boxListener = f; | ||
let parent = f; | ||
while (parent = parent.parentSubscription) { | ||
parent.dom = newDom; | ||
}; | ||
} | ||
} | ||
f.state = newState; | ||
}; | ||
dom.__boxListener = f; | ||
f.dom = dom; | ||
f.source = box; | ||
f.state = state; | ||
return f; | ||
} | ||
function attachNode(child, parent, position){ | ||
if(child && child._isBox){ | ||
let state = ''; | ||
// child(); | ||
// if(state === undefined) | ||
// state = ''; | ||
let dom = toDom(state); | ||
insertAt(parent, dom, position); | ||
child.subscribe(createSubscription(child, state, dom)); | ||
} else { | ||
insertAt(parent, toDom(child), position); | ||
} | ||
} | ||
function detachNode(dom, parentDom){ | ||
//optimization: consider using Mutation Observer | ||
const bl = dom.__boxListener; | ||
bl !== undefined && bl.source.unsubscribe(bl); | ||
const cl = dom.__childrenListener; | ||
cl !== undefined && cl.source.unsubscribe(cl); | ||
emptyNode(dom); | ||
parentDom !== undefined && parentDom.removeChild(dom); | ||
if(dom.__detachListeners) | ||
dom.__detachListeners.forEach(l => l(dom)); | ||
} | ||
function emptyNode(node){ | ||
const children = node.childNodes; | ||
if(children !== undefined) { | ||
for (let child of children) { | ||
detachNode(child); | ||
} | ||
} | ||
const assertedClass = node.assertedClass; | ||
assertedClass !== undefined && killAllRules(assertedClass); | ||
} | ||
const extractKey = v => { | ||
let res; | ||
if(v._isBox){ | ||
return v.assertId(); | ||
} | ||
if(v._isAnything){ | ||
v = v.jsValue; | ||
} | ||
if(typeof v === 'object') | ||
res = v.id || v.attributes?.id || v.key; | ||
else | ||
res = v; | ||
return res; | ||
}; | ||
defAll(nice, { | ||
htmlEscape: s => (''+s).replace(/&/g, '&') | ||
.replace(/"/g, '"') | ||
.replace(/</g, '<') | ||
.replace(/>/g, '>') | ||
}); | ||
function refreshElement(e, old, domNode){ | ||
expect(e).not.isDataSource(); | ||
expect(old).not.isDataSource(); | ||
const eTag = (e !== undefined) && e._isHtml && e.tag(), | ||
oldTag = (old !== undefined) && old._isHtml && old.tag(); | ||
let newDom = domNode; | ||
if (eTag !== oldTag || (old && old._isHtml && old.forceRepaint())){ | ||
newDom = toDom(e); | ||
emptyNode(domNode); | ||
domNode.parentNode.replaceChild(newDom, domNode); | ||
} else if(!eTag) { | ||
domNode.nodeValue = e; | ||
} else { | ||
const newV = e._value, oldV = old._value; | ||
if(nice.isEnvBrowser){ | ||
const addStyle = Switch | ||
.Box.use((s, k, node) => { | ||
const f = v => addStyle(v, k, node); | ||
s.listen(f); | ||
nice._set(node, ['styleSubscriptions', k], () => s.unsubscribe(f)); | ||
}) | ||
.default.use((v, k, node) => node.style[k] = v); | ||
const newStyle = newV.style || {}, oldStyle = oldV.style || {}; | ||
_each(oldStyle, (v, k) => (k in newStyle) || (domNode.style[k] = '')); | ||
_each(newStyle, (v, k) => oldStyle[k] !== v && (domNode.style[k] = v)); | ||
const delStyle = Switch | ||
.Box.use((s, k, node) => { | ||
node.styleSubscriptions[k](); | ||
delete node.styleSubscriptions[k]; | ||
node.style[k] = ''; | ||
}) | ||
.default.use((v, k, node) => node.style && (node.style[k] = '')); | ||
const newAtrs = newV.attributes || {}, oldAtrs = oldV.attributes || {}; | ||
_each(oldAtrs, (v, k) => (k in newAtrs) || (domNode.removeAttribute(k))); | ||
_each(newAtrs, (v, k) => oldAtrs[k] !== v && (domNode.setAttribute(k, v))); | ||
e.needAutoClass === true && assertAutoClass(domNode); | ||
if(e.needAutoClass || domNode.assertedClass) | ||
refreshSelectors(newV.cssSelectors, oldV.cssSelectors, domNode); | ||
const addAttribute = Switch | ||
.Box.use((s, k, node) => { | ||
const f = v => addAttribute(v, k, node); | ||
s.listen(f); | ||
nice._set(node, ['attrSubscriptions', k], () => s.unsubscribe(f)); | ||
}) | ||
.default.use((v, k, node) => node[k] = v); | ||
const newHandlers = newV.eventHandlers || {}, oldHandlers = oldV.eventHandlers || {}; | ||
nice._eachEach(oldHandlers, (f, i, type) => { | ||
if(!(newHandlers[type] && newHandlers[type].includes(f))) | ||
domNode.removeEventListener(type, f, true); | ||
}); | ||
nice._eachEach(newHandlers, (f, i, type) => { | ||
if(!(oldHandlers[type] && oldHandlers[type].includes(f))) | ||
domNode.addEventListener(type, f, true); | ||
}); | ||
const delAttribute = Switch | ||
.Box.use((s, k, node) => { | ||
node.attrSubscriptions[k](); | ||
delete node.attrSubscriptions[k]; | ||
node[k] = ''; | ||
}) | ||
.default.use((v, k, node) => node[k] = ''); | ||
refreshChildren(e._children, old._children, domNode); | ||
} | ||
return newDom; | ||
}; | ||
function killNode(n){ | ||
n && n.parentNode && n.parentNode.removeChild(n); | ||
function refreshBoxChildren(aChildren, bChildren, domNode) { | ||
let ac = aChildren, bc = bChildren; | ||
if(bChildren._isBoxArray){ | ||
while (domNode.firstChild) { | ||
// TODO: unsubscribe | ||
// domNode.removeChild(domNode.lastChild); | ||
detachNode(domNode.lastChild, domNode); | ||
} | ||
bc = []; | ||
} | ||
if(aChildren._isBoxArray) | ||
ac = []; | ||
refreshChildren(ac, bc, domNode); | ||
if(aChildren._isBoxArray) | ||
attachBoxArrayChildren(domNode, aChildren); | ||
} | ||
function refreshChildren(aChildren, bChildren, domNode){ | ||
if(aChildren === bChildren) | ||
return; | ||
aChildren = aChildren || []; | ||
bChildren = bChildren || []; | ||
if(bChildren._isBoxArray || aChildren._isBoxArray){ | ||
return refreshBoxChildren(aChildren, bChildren, domNode); | ||
} | ||
const aKeys = aChildren.map(extractKey); | ||
const bKeys = bChildren.map(extractKey); | ||
const aCount = aKeys.reduce(childrenCounter, {}); | ||
const bCount = bKeys.reduce(childrenCounter, {}); | ||
let ai = 0, bi = 0; | ||
while(ai < aKeys.length){ | ||
const aChild = aKeys[ai], bChild = bKeys[bi]; | ||
if(aChild === bChild && aChild !== undefined){ | ||
refreshElement(aChildren[ai], bChildren[bi], domNode.childNodes[ai]); | ||
ai++, bi++; | ||
} else { | ||
if(!bCount[aChild]){//assume insert | ||
attachNode(aChildren[ai], domNode, ai); | ||
ai++; | ||
} else if(!aCount[bChild]) {//assume delete | ||
detachNode(domNode.childNodes[ai], domNode); | ||
bi++; | ||
} else {//assume ugly reorder - brute force | ||
const old = domNode.childNodes[bi]; | ||
old | ||
? refreshElement(aChildren[ai], bChildren[bi], old) | ||
: attachNode(aChildren[ai], domNode, bi); | ||
// old && detachNode(old, domNode); | ||
ai++, bi++; | ||
} | ||
} | ||
}; | ||
while(bi < bKeys.length){ | ||
detachNode(domNode.childNodes[ai], domNode); | ||
bi++; | ||
} | ||
}; | ||
nice.htmlEscape = s => (''+s).replace(/&/g, '&') | ||
.replace(/"/g, '"') | ||
.replace(/</g, '<') | ||
.replace(/>/g, '>'); | ||
const getAutoClass = s => s.match(/(_nn_\d+)/)[0]; | ||
if(IS_BROWSER){ | ||
const styleEl = document.createElement('style'); | ||
document.head.appendChild(styleEl); | ||
runtime.styleSheet = styleEl.sheet; | ||
function insertBefore(node, newNode){ | ||
@@ -214,120 +619,321 @@ node.parentNode.insertBefore(newNode, node); | ||
Func.Html('show', (div, parentNode = document.body, position) => { | ||
return insertAt(parentNode, extractUp(div).dom, position); | ||
}); | ||
} | ||
function handleNode(add, del, oldNode, parent){ | ||
let node; | ||
if(del && !is.Nothing(del) && !oldNode) | ||
throw '!oldNode'; | ||
function extractUp(e){ | ||
let up = e.up; | ||
if(up && up._isHtml) | ||
return extractUp(up); | ||
return e; | ||
} | ||
del && Switch(del) | ||
.Box.use(b => { | ||
b.unsubscribe(oldNode.__niceSubscription); | ||
oldNode.__niceSubscription = null; | ||
}) | ||
.object.use(o => { | ||
const v = o._nv_; | ||
if(v.tag && add === undefined){ | ||
killNode(oldNode); | ||
} else { | ||
_each(v.style, (_v, k) => delStyle(_v, k, oldNode)); | ||
_each(v.attributes, (_v, k) => delAttribute(_v, k, oldNode)); | ||
nice._eachEach(v.eventHandlers, (f, _n, k) => | ||
oldNode.removeEventListener(k, f, true)); | ||
} | ||
}) | ||
.default.use(t => add !== undefined || t !== undefined && killNode(oldNode)); | ||
function insertAt(parent, node, position){ | ||
typeof position === 'number' | ||
? parent.insertBefore(node, parent.childNodes[position]) | ||
: parent.appendChild(node); | ||
return node; | ||
} | ||
if(is.Box(add)) { | ||
const f = () => { | ||
const diff = add.getDiff(); | ||
node = handleNode(diff.add, diff.del, node, parent); | ||
}; | ||
add.listen(f); | ||
node = node || oldNode || document.createTextNode(' '); | ||
node.__niceSubscription = f; | ||
oldNode || parent.appendChild(node); | ||
} else if(add !== undefined) { | ||
if (add && add._nv_) { //full node | ||
const v = add._nv_; | ||
const newHtml = v.tag; | ||
if(newHtml){ | ||
if(del && !is.string(del) && !is.Nothing(del)){ | ||
node = changeHtml(oldNode, newHtml); | ||
} | ||
node = node || document.createElement(newHtml); | ||
oldNode ? insertBefore(oldNode, node) : parent.appendChild(node); | ||
} else { | ||
node = oldNode; | ||
} | ||
_each(v.style, (_v, k) => addStyle(_v, k, node)); | ||
_each(v.attributes, (_v, k) => addAttribute(_v, k, node)); | ||
addHandlers(v.eventHandlers, node); | ||
} else { | ||
const text = is.Nothing(add) ? '' : '' + add; | ||
node = document.createTextNode(text); | ||
oldNode ? insertBefore(oldNode, node) : parent.appendChild(node); | ||
} | ||
oldNode && (oldNode !== node) && killNode(oldNode); | ||
function attachBoxArrayChildren(node, box) { | ||
const f = (v, k, oldV, oldK) => { | ||
if(oldK !== null) { | ||
const child = node.childNodes[oldK]; | ||
detachNode(child, node); | ||
} | ||
is.Box(add) || (node && node.nodeType === 3) | ||
|| handleChildren(add, del, node || oldNode); | ||
return node || oldNode; | ||
if(k !== null) | ||
attachNode(v, node, k); | ||
}; | ||
f.source = box; | ||
node.__childrenListener = f; | ||
box.subscribe(f); | ||
}; | ||
function detachBoxArrayChildren(node, box) { | ||
// const f = (v, k, oldV, oldK) => { | ||
// if(oldK !== null) { | ||
// const child = node.childNodes[oldK]; | ||
// detachNode(child, node); | ||
// } | ||
// if(k !== null) | ||
// attachNode(v, node, k); | ||
// }; | ||
// f.source = box; | ||
// node.__childrenListener = f; | ||
box.unsubscribe(f, node.__childrenListener); | ||
}; | ||
if(IS_BROWSER){ | ||
function killNode(n){ | ||
n && n !== document.body && n.parentNode && n.parentNode.removeChild(n); | ||
} | ||
function handleChildren(add, del, target){ | ||
const a = add && add._nv_ && add._nv_.children; | ||
const d = del && del._nv_ && del._nv_.children; | ||
const f = k => handleNode(a && a[k], d && d[k], target.childNodes[k], target); | ||
const keys = []; | ||
function insertBefore(node, newNode){ | ||
node.parentNode.insertBefore(newNode, node); | ||
return newNode; | ||
} | ||
_each(a, (v, k) => f( + k)); | ||
_each(d, (v, k) => (a && a[k]) || keys.push( + k)); | ||
keys.sort((a,b) => b - a).forEach(f); | ||
}; | ||
function insertAfter(node, newNode){ | ||
node.parentNode.insertBefore(newNode, node.nextSibling); | ||
return newNode; | ||
} | ||
Func.Box(function show(source, parent = document.body){ | ||
const i = parent.childNodes.length; | ||
let node = null; | ||
source.listenDiff(diff => node = handleNode(diff.add, diff.del, node, parent)); | ||
return source; | ||
Func.primitive('show', (v, parentNode = document.body, position) => { | ||
const node = document.createTextNode(v); | ||
return insertAt(parentNode, node, position); | ||
}); | ||
function newNode(tag, parent = document.body){ | ||
return parent.appendChild(document.createElement(tag)); | ||
}; | ||
Func.primitive('hide', (v, node) => { | ||
killNode(node); | ||
}); | ||
Func.Html(function show(source, parent = document.body){ | ||
handleNode({_nv_: source.getResult()}, undefined, null, parent); | ||
return source; | ||
Func.Single('show', (e, parentNode = document.body, position) => { | ||
const node = document.createTextNode(''); | ||
e._shownNodes = e._shownNodes || new WeakMap(); | ||
e._shownNodes.set(node, e.listen(v => node.nodeValue = v())); | ||
return insertAt(parentNode, node, position); | ||
}); | ||
function changeHtml(old, tag){ | ||
const node = document.createElement(tag); | ||
while (old.firstChild) node.appendChild(old.firstChild); | ||
for (let i = old.attributes.length - 1; i >= 0; --i) { | ||
node.attributes.setNamedItem(old.attributes[i].cloneNode()); | ||
Func.Single('hide', (e, node) => { | ||
const subscription = e._shownNodes && e._shownNodes.get(node); | ||
subscription(); | ||
killNode(node); | ||
}); | ||
// Func.Box('show', (e, parentNode = document.body, position) => { | ||
// let node; | ||
// e._shownNodes = e._shownNodes || new WeakMap(); | ||
// const f = (v, oldValue) => { | ||
// const oldNode = node; | ||
// node && (position = Array.prototype.indexOf.call(parentNode.childNodes, node)); | ||
// if(v !== null){ | ||
// node = nice.show(v, parentNode, position); | ||
// e._shownNodes.set(node, f); | ||
// } else { | ||
// node = undefined; | ||
// } | ||
// if(oldNode){ | ||
// oldValue && oldValue.hide ? oldValue.hide(oldNode) : killNode(oldNode); | ||
// } | ||
// }; | ||
// e.listen(f); | ||
// }); | ||
// Func.Box('hide', (e, node) => { | ||
// e.unsubscribe(e._shownNodes.get(node)); | ||
// e._shownNodes.delete(node); | ||
// e._value && e._value.hide && e._value.hide(node); | ||
// }); | ||
Func.Nothing('show', (e, parentNode = document.body, position) => { | ||
return insertAt(parentNode, document.createTextNode(''), position); | ||
}); | ||
Func.Err('show', (e, parentNode = document.body, position) => { | ||
return insertAt(parentNode, | ||
document.createTextNode('Error: ' + e().message), position); | ||
}); | ||
Func.Bool('show', (e, parentNode = document.body, position) => { | ||
if(e()) | ||
throw `I don't know how to display "true"`; | ||
return insertAt(parentNode, document.createTextNode(''), position); | ||
}); | ||
Func.Html.BoxArray('bindChildren', (z, b) => { | ||
z._children = b; | ||
}); | ||
Func.Html('hide', (e, node) => { | ||
const subscriptions = e._shownNodes && e._shownNodes.get(node); | ||
e._shownNodes.delete(node); | ||
subscriptions && subscriptions.forEach(f => f()); | ||
node && e._children.forEach((c, k) => nice.hide(c, node.childNodes[0])); | ||
killNode(node); | ||
}); | ||
}; | ||
const addRules = (vs, selector, className) => { | ||
const rule = assertRule(selector, className); | ||
vs.each((v, k) => rule.style[k] = v); | ||
}; | ||
const changeRules = (values, oldValues, selector, className) => { | ||
const rule = assertRule(selector, className); | ||
_each(values, (v, k) => rule.style[k] = v); | ||
_each(oldValues, (v, k) => k in values || (rule.style[k] = null)); | ||
}; | ||
const findRule = (selector, className) => { | ||
const s = `.${className}${selector}`.toLowerCase(); | ||
let rule; | ||
for (const r of runtime.styleSheet.cssRules) | ||
r.selectorText === s && (rule = r); | ||
return rule; | ||
}; | ||
const assertRule = (selector, className) => { | ||
const name = selector[0] === ':' | ||
? className + selector | ||
: className + ' ' + selector; | ||
return findRule(selector, className) || runtime.styleSheet | ||
.cssRules[runtime.styleSheet.insertRule(`.${name}` + '{}')]; | ||
}; | ||
const killRules = (vs, selector, id) => { | ||
const rule = findRule(selector, id); | ||
rule && _each(vs, (value, prop) => rule.style[prop] = null); | ||
}; | ||
const killAllRules = className => { | ||
const a = []; | ||
[...runtime.styleSheet.cssRules].forEach((r, i) => | ||
r.selectorText.indexOf(className) === 1 && a.unshift(i)); | ||
a.forEach(i => runtime.styleSheet.deleteRule(i)); | ||
}; | ||
function addSelectors(selectors, node){ | ||
_each(selectors, (v, k) => addRules(v, k, getAutoClass(node.className))); | ||
}; | ||
function refreshSelectors(selectors, oldSelectors, node){ | ||
const className = getAutoClass(node.className); | ||
_each(selectors, (v, k) => changeRules(v, oldSelectors[k], k, className)); | ||
_each(oldSelectors, (v, k) => (k in selectors) || killRules(v, k, className)); | ||
}; | ||
function assertAutoClass(node) { | ||
const className = node.className || ''; | ||
if(className.indexOf(nice.AUTO_PREFIX) < 0){ | ||
let name = node.assertedClass; | ||
if(!name){ | ||
name = nice.genereteAutoId(); | ||
node.assertedClass = name; | ||
} | ||
addHandlers(old.__niceListeners, node); | ||
delete(old.__niceListeners); | ||
return node; | ||
node.className = className !== '' ? (className + ' ' + name) : name; | ||
} | ||
} | ||
function addHandlers(eventHandlers, node){ | ||
nice._eachEach(eventHandlers, (f, _n, k) => { | ||
if(k === 'domNode') | ||
return f(node); | ||
node.addEventListener(k, f, true); | ||
node.__niceListeners = node.__niceListeners || {}; | ||
node.__niceListeners[k] = node.__niceListeners[k] || []; | ||
node.__niceListeners[k].push(f); | ||
}); | ||
} | ||
IS_BROWSER && Test((Div) => { | ||
const testPane = document.createElement('div'); | ||
document.body.appendChild(testPane); | ||
}; | ||
Test((Div, show) => { | ||
const div = Div('q') | ||
.b('w') | ||
.I('e').up | ||
.color('red'); | ||
const node = div.show(testPane); | ||
expect(node.textContent).is('qwe'); | ||
expect(node.style.color).is('red'); | ||
}); | ||
Test((Div, Box, show) => { | ||
const box = Box('asd'); | ||
const div = Div(box); | ||
const node = div.show(testPane); | ||
expect(node.textContent).is('asd'); | ||
box(Div('zxc')); | ||
expect(node.textContent).is('zxc'); | ||
}); | ||
Test('Reorder children', (Div, Box, show) => { | ||
const d1 = Div('d1'); | ||
const d2 = Div('d2'); | ||
const d3 = Div('d3'); | ||
const box = Box(Div(d1,d2,d3)); | ||
const div = Div(box); | ||
const node = div.show(testPane); | ||
expect(node.textContent).is('d1d2d3'); | ||
box(Div(d2,d3,d1)); | ||
expect(node.textContent).is('d2d3d1'); | ||
}); | ||
Test((Div, Box, Css, show, I, B) => { | ||
const box = Box(0); | ||
const initialRulesCount = runtime.styleSheet.rules.length; | ||
const div = Div(RBox(box, a => { | ||
return a === 0 ? I('qwe') : B('asd') | ||
.Css(':first-child').backgroundColor('red').up; | ||
})); | ||
const node = div.show(testPane); | ||
expect(node.textContent).is('qwe'); | ||
box(1); | ||
expect(node.textContent).is('asd'); | ||
expect(window.getComputedStyle(node.firstChild).backgroundColor) | ||
.is('rgb(255, 0, 0)'); | ||
box(0); | ||
expect(node.textContent).is('qwe'); | ||
expect(window.getComputedStyle(node.firstChild).backgroundColor) | ||
.is('rgba(0, 0, 0, 0)'); | ||
expect(runtime.styleSheet.rules.length).is(initialRulesCount); | ||
}); | ||
Test((Div, Box, B) => { | ||
const box = Box(Div(B(1).id('b1'), B(2))); | ||
const div = Div(box).show(); | ||
expect(div.textContent).is('12'); | ||
box(Div(B(11).id('b1'), B(2))); | ||
expect(div.textContent).is('112'); | ||
box(Div(B(2), B(11).id('b1'))); | ||
expect(div.textContent).is('211'); | ||
}); | ||
Test((Div) => { | ||
const div = Div('1').Div('2').Div('3'); | ||
const node = div.show(testPane); | ||
expect(node.textContent).is('123'); | ||
}); | ||
Test((Div, prop) => { | ||
expect(nice.Div().properties('qwe', 'asd').show().qwe).is('asd'); | ||
}); | ||
document.body.removeChild(testPane); | ||
}); |
@@ -1,2 +0,1 @@ | ||
let autoId = 0; | ||
const Html = nice.Html; | ||
@@ -11,11 +10,17 @@ | ||
function attachValue(target, setValue = defaultSetValue){ | ||
//function attachValue(target, box, setValue = defaultSetValue){ | ||
function attachValue(target, box, valueAttribute = 'value'){ | ||
let node, mute; | ||
target.value = Box(""); | ||
target.value._parent = target; | ||
const initValue = box(); | ||
if(nice.isEnvBrowser){ | ||
// setValue(target, initValue); | ||
target.attributes(valueAttribute, initValue); | ||
if(IS_BROWSER){ | ||
let lastValue = initValue; | ||
changeEvents.forEach(k => target.on(k, e => { | ||
mute = true; | ||
target.value((e.target || e.srcElement).value); | ||
const v = (e.target || e.srcElement)[valueAttribute]; | ||
v !== lastValue && box(lastValue = v); | ||
mute = false; | ||
@@ -25,47 +30,239 @@ return true; | ||
target.id() || target.id('_nn_' + autoId++); | ||
target.on('domNode', n => node = n); | ||
target.on('domNode', n => { | ||
node = n; | ||
node[valueAttribute] = box(); | ||
}); | ||
} | ||
target.value.listen(v => node ? node.value = v : setValue(target, v)); | ||
box.subscribe(v => { | ||
if(mute) | ||
return; | ||
node ? node[valueAttribute] = v : target.attributes(valueAttribute, v); | ||
}); | ||
return target; | ||
} | ||
Html.extend('Input') | ||
.about('Represents HTML <input> element.') | ||
.by((z, type) => attachValue(z.tag('input').attributes('type', type || 'text'))); | ||
Html.extend('Input', (z, type) => | ||
z.tag('input').attributes('type', type || 'text').assertId()) | ||
.about('Represents HTML <input> element.'); | ||
const Input = nice.Input; | ||
Html.extend('Button') | ||
.about('Represents HTML <input type="button"> element.') | ||
.by((z, text, action) => { | ||
z.tag('input').attributes({type: 'button', value: text}).on('click', action); | ||
}); | ||
nice.defineCached(Input.proto, function boxValue() { | ||
const res = Box(''); | ||
attachValue(this, res); | ||
return res; | ||
}); | ||
Html.extend('Textarea') | ||
.about('Represents HTML <textarea> element.') | ||
.by((z, value) => { | ||
def(Input.proto, 'value', function(v){ | ||
if(v !== undefined && v._isBox) { | ||
attachValue(this, v); | ||
} else { | ||
this.attributes('value', v); | ||
} | ||
return this; | ||
}); | ||
Test((Input) => { | ||
const i1 = Input(); | ||
expect(i1.html).is('<input type="text" id="' + i1.id() + '"></input>'); | ||
const i2 = Input('date'); | ||
expect(i2.html).is('<input type="date" id="' + i2.id() + '"></input>'); | ||
const i3 = Input().value('qwe') | ||
expect(i3.html).is('<input type="text" id="' + i3.id() + '" value="qwe"></input>'); | ||
}); | ||
Test('Box value html', (Input, Box) => { | ||
const b = Box('qwe'); | ||
const input = Input().value(b); | ||
expect(input.html).is('<input type="text" id="' + input.id() + '" value="qwe"></input>'); | ||
b('asd'); | ||
expect(input.html).is('<input type="text" id="' + input.id() + '" value="asd"></input>'); | ||
}); | ||
IS_BROWSER && Test('Box value dom', (Input, Box) => { | ||
const b = Box('qwe'); | ||
const input = Input().value(b); | ||
expect(input.html).is('<input type="text" id="' + input.id() + '" value="qwe"></input>'); | ||
b('asd'); | ||
expect(input.html).is('<input type="text" id="' + input.id() + '" value="asd"></input>'); | ||
}); | ||
Html.extend('Button', (z, text = '', action) => { | ||
z.super('button').type('button').on('click', action).add(text); | ||
}) | ||
.about('Represents HTML <input type="button"> element.'); | ||
Test((Button) => { | ||
const b = Button('qwe'); | ||
expect(b.html).is('<button type="button">qwe</button>'); | ||
}); | ||
Input.extend('Textarea', (z, v) => { | ||
z.tag('textarea'); | ||
attachValue(z, (t, v) => t.children.removeAll().push(v)); | ||
z.value(value ? '' + value : ""); | ||
if(v !== undefined && v._isBox){ | ||
attachValue(this, v, (t, v) => t.children.removeAll().push(v)); | ||
} else { | ||
z.add(v); | ||
}; | ||
}) | ||
.about('Represents HTML <textarea> element.'); | ||
Test(Textarea => { | ||
const ta = Textarea('qwe'); | ||
expect(ta.html).is('<textarea>qwe</textarea>'); | ||
}); | ||
Html.extend('Submit', (z, text, action) => { | ||
z.tag('input') | ||
.attributes('type', 'submit') | ||
.attributes('value', text || 'Submit') | ||
.assertId(); | ||
action && z.on('click', action); | ||
}) | ||
.about('Represents HTML <input type="submit"> element.'); | ||
Html.extend('Form', (z, handler) => { | ||
z.tag('form').assertId(); | ||
handler && z.on('submit', e => { | ||
const input = {}, form = e.currentTarget; | ||
e.preventDefault(); | ||
for(let field of form.elements) { | ||
field.name && (input[field.name] = field.value); | ||
} | ||
handler(input); | ||
}); | ||
}) | ||
.about('Represents HTML Form element.'); | ||
nice.Form.fromConfig = cfg => { | ||
expect(cfg.handler).isFunction(); | ||
expect(cfg.fields).isArray(); | ||
const errors = memoize(() => Box('')); | ||
const index = {}; | ||
const form = nice.Form((data) => { | ||
_each(errors.cache, e => e('')); | ||
_some(data, (v, k) => _if(index[k]?.check, check => | ||
_if(check(v), res => (errors(k)(res),res))) | ||
) || cfg.handler(data); | ||
// ) || app.actions[action]({data, id}, r => r.error ? formError(r.error) : cb()); | ||
}); | ||
form.error = Box(''); | ||
_each(cfg.fields, f => { | ||
expect(f.name).isString(); | ||
index[f.name] = f; | ||
const editor = f.editor | ||
? f.editor(f) | ||
: Input().name(f.name).value(f.value || ''); | ||
Html.extend('Submit') | ||
.about('Represents HTML <input type="submit"> element.') | ||
.by((z, text) => z.tag('input').attributes({type: 'submit', value: text})); | ||
form.add(cfg.styling ? cfg.styling(f, editor, errors(name)) : | ||
nice.P(f.title || f.name).div(editor).Div(errors(name)).color('red').up); | ||
}); | ||
Html.extend('Checkbox') | ||
.about('Represents HTML <input type="checkbox"> element.') | ||
.by((z, status) => { | ||
form.reportError = es => _each(es, (v, k) => errors(k)(v)); | ||
form.P() | ||
.Submit().up | ||
.div(form.error); | ||
return form; | ||
}; | ||
//function rowForm({ action, data, cb = () => app.router.go('/'), id }){ | ||
// const fs = BoxArray(Object.keys(data)); | ||
// const errors = memoize(() => Box('')); | ||
// const formError = Box(''); | ||
// | ||
// const submit = (data) => { | ||
// _each(errors.cache, e => e('')); | ||
// _some(data, (v, k) => _if(fields[k]?.check, check => | ||
// _if(check(v), res => (errors(k)(res),res))) | ||
// ) || app.actions[action]({data, id}, r => r.error ? formError(r.error) : cb()); | ||
// }; | ||
// | ||
// function showField(name){ | ||
// if(ignoreFields[name]) | ||
// return; | ||
// const field = fields[name]; | ||
// return P().h3(name) | ||
// .Div((fieldTypes[field.type]?.edit || defaultEdit)(field, data[name])).up | ||
// .Div(errors(name)).color('red').up; | ||
// } | ||
// | ||
// return Form(submit) | ||
// .div(fs, showField) | ||
// .P() | ||
// .Submit().up | ||
// .div(formError); | ||
//} | ||
Input.extend('Checkbox', (z, status = false) => { | ||
let node; | ||
z.tag('input').attributes({type: 'checkbox'}); | ||
z.checked = Box(status || false); | ||
z.checked._parent = z; | ||
z.tag('input'); | ||
z.attributes('type', 'checkbox'); | ||
z.attributes('checked', status); | ||
z.assertId(); | ||
// const value = Box(status || false); | ||
// def(z, 'checked', value); | ||
// def(z, 'value', value); | ||
// let mute; | ||
// z.on('change', e => { | ||
// mute = true; | ||
// value((e.target || e.srcElement).checked); | ||
// mute = false; | ||
// return true; | ||
// }); | ||
// if(IS_BROWSER){ | ||
//// z.assertId(); | ||
// z.on('domNode', n => node = n); | ||
// } | ||
// value.subscribe(v => node ? node.checked = v : z.attributes('checked', v)); | ||
}) | ||
.about('Represents HTML <input type="checkbox"> element.'); | ||
nice.defineCached(nice.Checkbox.proto, function boxValue() { | ||
const res = Box(this.attributes('checked')); | ||
// attachValue(this, res, (t, v) => t.attributes('checked', v)); | ||
attachValue(this, res, 'checked'); | ||
return res; | ||
}); | ||
Input.extend('Select', (z, values, selected) => { | ||
let node; | ||
z.tag('select').assertId(); | ||
// const value = Box(null); | ||
// def(z, 'value', value); | ||
let mute; | ||
z.on('change', e => { | ||
mute = true; | ||
z.checked((e.target || e.srcElement).checked); | ||
// value((e.target || e.srcElement).value); | ||
mute = false; | ||
@@ -75,8 +272,36 @@ return true; | ||
if(nice.isEnvBrowser){ | ||
z.id() || z.id('_nn_' + autoId++); | ||
if(IS_BROWSER){ | ||
// z.assertId(); | ||
z.on('domNode', n => node = n); | ||
} | ||
z.checked.listen(v => node ? node.checked = v : z.attributes('checked', v)); | ||
}); | ||
_each(values, (v, k) => { | ||
const o = Html('option').add(v); | ||
selected === v && o.selected(true); | ||
z.add(o); | ||
}); | ||
// z.options.listenChildren(v => z.add(Html('option').add(v.label) | ||
// .apply(o => o.attributes('value', v.value))) | ||
// ); | ||
// Switch(values) | ||
// .isObject().each(z.option.bind(z)) | ||
// .isArray().each(v => Switch(v) | ||
// .isObject().use(o => z.options.push(o)) | ||
// .default.use(z.option.bind(z))); | ||
// value.listen(v => node && z.options.each((o, k) => | ||
// o.value == v && (node.selectedIndex = k))); | ||
}) | ||
.arr('options') | ||
.Action.about('Adds Option HTML element to Select HTML element.') | ||
// .test((Select) => { | ||
// expect(Select().id('q').option('v1', 1).html) | ||
// .is('<select id="q"><option value="1">v1</option></select>'); | ||
// }) | ||
(function option(z, label, value){ | ||
value === undefined && (value = label); | ||
z.options.push({label, value}); | ||
}) | ||
.about('Represents HTML <select> element.'); |
const Html = nice.Html; | ||
'Div,I,B,Span,H1,H2,H3,H4,H5,H6,P,Li,Ul,Ol,Pre'.split(',').forEach(t => | ||
Html.extend(t).by((z, ...cs) => z.tag(t.toLowerCase()).add(...cs)) | ||
.about('Represents HTML <%s> element.', t.toLowerCase())); | ||
'Div,I,B,Span,H1,H2,H3,H4,H5,H6,P,Li,Ul,Ol,Pre,Table,Tr,Td,Th'.split(',').forEach(t => { | ||
const l = t.toLowerCase(); | ||
Html.extend(t).by((z, a, ...as) => { | ||
z.tag(l); | ||
if(a === undefined) | ||
return; | ||
const type = nice.getType(a).name; | ||
const f = as[0]; | ||
if(a._isBoxArray){ | ||
z.bindChildren(f ? a.map(f) : a); | ||
} else if(a._isBoxSet){ | ||
const ba = nice.BoxArray(); | ||
a.subscribe((v, old) => v === null ? ba.removeValue(old) : ba.push(v)); | ||
z.bindChildren(f ? ba.map(f) : ba); | ||
} else if( a._isArr ) { | ||
a.each((v, k) => z.add(f ? f(v, k) : v)); | ||
} else if( type === 'Array' || type === 'Object') { | ||
_each(a, (v, k) => z.add(f ? f(v, k) : v)); | ||
} else if( a instanceof Set ) { | ||
for(let c of a) z.add(f ? f(c) : c); | ||
} else { | ||
z.add(a, ...as); | ||
} | ||
}) | ||
.about('Represents HTML <%s> element.', l); | ||
}); | ||
const protocolRe = /^([a-zA-Z0-9]{3,5})\:\/\//; | ||
Html.extend('A').by((z, url, ...children) => { | ||
z.tag('a'); | ||
z.add(...children); | ||
is.function(url) | ||
? z.on('click', e => {url(e); e.preventDefault();}).href('#') | ||
: z.href(url || '#'); | ||
z.tag('a').add(...children); | ||
if (nice.isFunction(url) && !url._isAnything) { | ||
z.on('click', e => {url(e); e.preventDefault();}).href('#'); | ||
} else { | ||
const router = nice.Html.linkRouter; | ||
if(!router || (protocolRe.exec(url) && !url.startsWith(router.origin))) { | ||
z.href(url || '#'); | ||
} else { | ||
z.on('click', e => e.preventDefault(router.go(url))).href(url); | ||
} | ||
} | ||
}).about('Represents HTML <a> element.'); | ||
Html.extend('Img').by((z, src) => z.tag('img').src(src)) | ||
Html.extend('Img').by((z, src, x, y) => { | ||
z.tag('img').src(src); | ||
x === undefined || z.width(x); | ||
y === undefined || z.height(y); | ||
}) | ||
.about('Represents HTML <img> element.'); |
{ | ||
"name": "nicescript", | ||
"version": "0.3.3", | ||
"version": "0.4.0", | ||
"keywords": [ | ||
@@ -9,5 +9,8 @@ "util", | ||
"client", | ||
"reactive" | ||
"reactive", | ||
"css", | ||
"html", | ||
"dom" | ||
], | ||
"author": "Sergey Kashulin", | ||
"author": "Sergei Kashulin", | ||
"main": "index.js", | ||
@@ -24,5 +27,5 @@ "scripts": { | ||
"devDependencies": { | ||
"chai": "3.x", | ||
"chai-spies": "^0.7.1", | ||
"mocha": "latest" | ||
"espree": "9.*", | ||
"estraverse": "5.*", | ||
"terser": "^5.10.0" | ||
}, | ||
@@ -29,0 +32,0 @@ "repository": { |
351
README.md
NiceScript | ||
========= | ||
[![Build Status](https://travis-ci.org/nicescript/nicescript.svg?branch=master)](https://travis-ci.org/nicescript/nicescript) | ||
A naive attempt to simplify life of a fellow JavaScript programmer. | ||
[NiceScript.org](http://nicescript.org) | ||
[Twitter](https://twitter.com/NiceScriptJS) | ||
Set of JavaScript functions that provide following features without compilation | ||
or configuration: | ||
* [Reactive state management](./doc/boxes.md) | ||
* [Type System](./doc/types.md) | ||
* [HTML and CSS](./doc/html.md) | ||
* [Unit tests](./doc/tests.md) | ||
* Utility functions and control structues | ||
Example ( [JS Bin](https://jsbin.com/kenedihasi/edit?html,output) ) | ||
```javascript | ||
const { Box, Div, B, Switch } = nice; | ||
You can use any of them independently or as a whole in any JS project. | ||
const tasks = Box(['Feed the fish', 'Buy milk']); | ||
const decorate = Switch | ||
.equal('Watch tv')('Read book') | ||
.match(/buy/i).use(s => [s, B(' $').color('#3A3')]); | ||
## Rationale | ||
Create web applications with | ||
* less code overall | ||
* much less imperative code | ||
* only JS syntax (less build code) | ||
const taskView = t => Div(t) | ||
.margin('1em 0') | ||
.padding('.5em') | ||
.borderRadius('.5em') | ||
.backgroundColor('#DEF'); | ||
NiceScript encourage use of component approach, functional programming, and unit tests. | ||
But doesn't force you to do so. | ||
Box.by(tasks, ts => Div(ts.map(decorate).map(taskView))).show(); | ||
## Example application: To Do list | ||
tasks.push('Walk the dog', 'Watch tv'); | ||
Here you can search, add, and remove tasks. | ||
Notice how little imperative code does it have. | ||
```javascript | ||
const { Box, RBox, Div, Input, wrapMatches } = nice; | ||
const tasks = Box(['Feed the fish', 'Buy milk', 'Walk the dog']); | ||
const taskView = t => Div() | ||
.A(() => tasks.removeValue(t), 'x').float('right').textDecoration('none').up | ||
.add(wrapMatches(t, input.boxValue())) | ||
.margin('1em 0').padding('.5em').borderRadius('.3em').backgroundColor('#DEF'); | ||
const input = Input().padding('.5em').width('100%').boxSizing('border-box') | ||
.on('keyup', e => e.key === 'Enter' | ||
&& (tasks.push(e.target.value), e.target.value = '')); | ||
const list = RBox(tasks, input.boxValue, (tt, s) => | ||
Div(tt.filter(t => t.toLowerCase().includes(s.toLowerCase())), taskView)); | ||
Div(input, list).font('1.2rem Arial').show(); | ||
``` | ||
[JS Bin](https://jsbin.com/gajowevuvo/edit?html,output) | | ||
[Detailed explanation](./doc/todo_example.md) | ||
<!-- | ||
More examples: | ||
@@ -39,13 +68,17 @@ | ||
( [Tutorial](https://medium.com/@sergey.kashulin/creating-web-applications-with-nicescript-338184d18331) ) | ||
--> | ||
## Install | ||
Node.js: | ||
`npm install nicescript` | ||
Then in node.js script: | ||
use: | ||
`const nice = require('nicescript')();` | ||
| ||
Browser: | ||
<!--//TODO: check sourse--> | ||
`<script src="https://unpkg.com/nicescript/nice.js"></script>` | ||
@@ -56,279 +89,1 @@ | ||
`<script src="https://cdn.jsdelivr.net/npm/nicescript/nice.js"></script>` | ||
## Tests | ||
`npm test` | ||
## Basic features | ||
* [Types](#types) | ||
* [Functions](#functions) - adds couple features to regular JS functions. | ||
* [Switch](#switch) - finally convenient. | ||
* [Boxes](#boxes) - to handle state changes. | ||
* [Html](#html) - use all above to to create html UI. | ||
### Nice values | ||
#### Single values | ||
```javascript | ||
const n = nice(5); | ||
//read value | ||
n(); // 5 | ||
//write value | ||
n(6); // n | ||
n(); // 6 | ||
``` | ||
#### Object values | ||
```javascript | ||
const o = nice.Object({ a: 1 }); | ||
//get value | ||
o('a'); // 1 | ||
o('b'); // nice.NOT_FOUND | ||
//set value | ||
o('b', 5); // o | ||
o('b'); // 5 | ||
``` | ||
### Types | ||
Each value in NiceScript has a type. Here is a root of types hierarchy: | ||
+ Anything | ||
+ Something | ||
+ Value | ||
+ Object | ||
+ Array | ||
+ [Html](#html) | ||
+ Single | ||
+ String | ||
+ Number | ||
+ Boolean | ||
+ [Function](#functions) | ||
+ [Box](#boxes) | ||
+ Ok | ||
+ Nothing | ||
+ Error | ||
+ Undefined | ||
+ Null | ||
+ NotFound | ||
+ Fail | ||
+ ... | ||
#### Wrapping values | ||
Call nice with js value to wrap it with most appropriate type. | ||
```javascript | ||
const nice = require('nicescript')(); | ||
nice(4); // nice.Number; | ||
nice(""); // nice.String; | ||
nice(true); // nice.Boolean; | ||
nice({}); // nice.Object; | ||
nice([]); // nice.Array; | ||
nice(1, 2, 3); // nice.Array; | ||
nice(null); // nice.Null; | ||
``` | ||
#### User types | ||
```javascript | ||
nice.Type('Dog') | ||
.String('title') | ||
.Number('weight') | ||
.by((z, title) => z.title(title)); | ||
let d = nice.Dog('Jim').weight(5); | ||
d.name(); // Jim | ||
d.weight(); // 5 | ||
// by default created type extends nice.Object | ||
d.is.Object() // true | ||
``` | ||
Type name should start with capital letter. | ||
### Functions | ||
```javascript | ||
// Creating anonymous function | ||
const f = nice.Function(n => n + 1); | ||
f(1); // 2 | ||
// Named functions will be added to nice | ||
const plusTwo = nice.Function('plusTwo', n => n + 2); | ||
//or nice.Function(function plusTwo(n) { return n + 2; }); | ||
plusTwo(1); // 3 | ||
nice.plusTwo(1); // 3 | ||
// Check argument type | ||
const x2 = nice.Function.number('x2', n => n * 2); | ||
x2(21); // 42 | ||
nice.x2(21); // 42 | ||
nice.Number(1).x2();// 42 | ||
x2('q'); // throws "Function can't handle (String)" | ||
// now let's overload x2 for strings | ||
x2.string(s => s + '!'); | ||
x2(21); // 42 | ||
x2('q'); // q! | ||
``` | ||
Function name should start with lowercase letter. | ||
#### Function types | ||
##### Mapping | ||
Clean function that do not changes it's arguments. | ||
NiceScript will always [wrap](#wrapping-values) result of Mapping. | ||
```javascript | ||
nice.Mapping.Number.Number('times', (a, b) => a * b); | ||
const n = nice(5); | ||
const n2 = n.times(3).times(2); // nice.Number(30) | ||
n() // 5 | ||
n2() // 30; | ||
``` | ||
##### Check | ||
Returns boolean. Never changes it's arguments. | ||
After definition named Check can be used in [Switch](#switch) and 'is' statements. | ||
##### Action | ||
Changes first argument. Action always returns it's first argument so you can | ||
call multiple actions in a row. | ||
```javascript | ||
nice.Action.Number.Number('times', (a, b) => a * b); | ||
const n = nice(5); | ||
n.times(3).times(2); // n | ||
n(); // 30; | ||
``` | ||
### Switch | ||
Delayed argumet | ||
```javascript | ||
const f = nice.Switch | ||
.equal(1)(11) | ||
.number(22) | ||
.string.use(s => s + '!') | ||
.Nothing(':(') | ||
.default(42); | ||
f(1); // 11 | ||
f(3); // 22 | ||
f('qwe'); // "qwe!" | ||
f([]); // 42 | ||
f(0); // 42 | ||
f(undefined); // :( | ||
f(null); // :( | ||
``` | ||
Instant argument | ||
```javascript | ||
nice.Check('meat', v => ['pork', 'beef'].includes(v)); | ||
const tiger = { say: console.log }; | ||
function feedTiger(tiger, food){ | ||
tiger.hungry = nice.Switch(food) | ||
.meat(false) | ||
.default.use(name => tiger.say('I do not like ' + name) || true); | ||
} | ||
feedTiger(tiger, 'apple'); // tiger.hungry === true | ||
// > I do not like apple | ||
feedTiger(tiger, 'beef'); // tiger.hungry === false | ||
``` | ||
#### Switch vs Function overload | ||
Overloaded Function will search for best match while Switch will use first match. | ||
```javascript | ||
nice.Function.Nothing(() => 1).Null(() => 2)(null); // 2 | ||
nice.Switch.Nothing.use(() => 1).Null.use(() => 2)(null); // 1 | ||
``` | ||
Besides current implementation of Switch use only first argument. | ||
### Boxes | ||
Stateful observable components. | ||
```javascript | ||
const { Box } = nice; | ||
let b = Box(1); // create box with 1 in it | ||
b.listen(console.log) // listen for updates | ||
b(2); // write value | ||
b(); // read value | ||
// create Box that follows changes in b | ||
let b2 = Box.use(b).by(n => n * 2); | ||
// short version Box.by(b, n => n * 2); | ||
b(3); // b2() === 6 | ||
// Named inputs | ||
let square = Box() | ||
.Number('x', 5) | ||
.Number('y', 5) | ||
.by((x, y) => x * y); | ||
square(); // 25 | ||
square.x(10).y(b)(); // 30 | ||
``` | ||
Calling [mapping](#mapping) on box will create new box that follows changes in the original. | ||
```javascript | ||
const a = nice.Box('qwe'); | ||
const b = a.concat('!').listen(console.log); | ||
// qwe! | ||
a('asd'); | ||
// asd! | ||
``` | ||
Calling [action](#action) on box will change its content. | ||
```javascript | ||
const a = nice.Box([1, 2]).listen(console.log); | ||
// [1, 2]; | ||
a.push(3); | ||
// [1, 2, 3]; | ||
``` | ||
### Html | ||
```javascript | ||
const div = nice.Div('Normal ', 'text ') | ||
.I('italic ').up | ||
.add('normal ') | ||
.B('red bold').color('red').up | ||
.margin('10px') | ||
.fontSize('20px'); | ||
// browser and server | ||
div.html | ||
// <div style="margin:10px;font-size:20px">Normal text <i>italic </i>normal <b style="color:red">red bold</b></div> | ||
// browser only | ||
div.show(); // attach dom node to document.body or provided node | ||
``` | ||
Add some [Boxes](#boxes) to handle asynchronous cases. | ||
```javascript | ||
const { Box, Div, Switch, Nothing } = nice; | ||
const data = Box(Nothing); | ||
const div = Box.by(data, Switch | ||
.string.use(s => Div('Data: ', s)) | ||
.default(Div('Loading...'))); | ||
div.listen(d => console.log(d.html)); | ||
// <div>Loading...</div> | ||
data('Some data'); | ||
// <div>Data: Some data</div> | ||
div.show(); // will create and attach dome node and update it's state according to boxes states | ||
``` |
nice.Type('Range') | ||
.about('Represent range of numbers.') | ||
.Number('start', 0) | ||
.Number('end', Infinity) | ||
.by((z, a, b) => b === undefined ? z.end(a) : z.start(a).end(b)) | ||
.Method(function each(z, f){ | ||
let i = z.start(); | ||
let end = z.end(); | ||
let n = 0; | ||
while(i <= end) f(i++, n++); | ||
.by((z, start, end, step = 1) => { | ||
expect(start).isNumber(); | ||
expect(end).isNumber(); | ||
expect(step).isNumber(); | ||
z._value = { start, end, step }; | ||
}) | ||
.Mapping(function map(f){ | ||
let i = this.start(); | ||
let n = 0; | ||
const a = nice.Array(); | ||
while(i <= this.end()) a(f(i++, n++)); | ||
.Method(function each(z, f) { | ||
const { start, end, step } = z._value; | ||
let i = start; | ||
while(i <= end) { | ||
f(i); | ||
i += step; | ||
} | ||
}) | ||
.Mapping(function map(z, f) { | ||
const { start, end, step } = z._value; | ||
let i = start, n = 0; | ||
const a = nice.Arr(); | ||
while(i <= end){ | ||
a.push(f(i, n++)); | ||
i += step; | ||
} | ||
return a; | ||
}) | ||
.Mapping(function toArray(z){ | ||
.Mapping(function filter(z, f) { | ||
const a = nice.Arr(); | ||
z.each(v => f(v) && a.push(v)); | ||
return a; | ||
}) | ||
.Mapping(function toArray(z) { | ||
const a = []; | ||
const end = z.end(); | ||
let i = z.start(); | ||
while(i <= end) a.push(i++); | ||
z.each(v => a.push(v)); | ||
return a; | ||
}) | ||
.Check(function includes(z, n){ | ||
return n >= z.start && n <= z.end; | ||
.Check(function includes(z, n) { | ||
const { start, end } = z._value; | ||
return n >= start && n <= end; | ||
}); | ||
Func.Number.Range(function within(v, r){ | ||
return v >= r.start && v <= r.end; | ||
Func.Number.Range(function within(v, r) { | ||
const { start, end } = r._value; | ||
return v >= start && v <= end; | ||
}); | ||
Test((Range, each, Spy) => { | ||
const spy = Spy(); | ||
Range(1,3).each(spy); | ||
expect(spy).calledTimes(3); | ||
expect(spy).calledWith(1); | ||
expect(spy).calledWith(2); | ||
expect(spy).calledWith(3); | ||
}); | ||
Test('Test Range with step', (Range, each, Spy) => { | ||
const spy = Spy(); | ||
Range(1, 3, 2).each(spy); | ||
expect(spy).calledTimes(2); | ||
expect(spy).calledWith(1); | ||
expect(spy).calledWith(3); | ||
}); | ||
Test((Range, includes) => { | ||
const r = Range(1, 6); | ||
expect(r.includes(5)).is(true); | ||
expect(r.includes(15)).is(false); | ||
}); | ||
Test((Range, within, Num) => { | ||
const r = Range(1, 5); | ||
expect(within(5, r)).is(true); | ||
expect(within(15, r)).is(false); | ||
}); | ||
Test((Range, map) => { | ||
const r = Range(2, 4); | ||
expect(r.map(x => x * 2)).deepEqual([4, 6, 8]); | ||
}); | ||
Test((Range, filter) => { | ||
const r = Range(2, 7); | ||
expect(r.filter(x => x % 2)).deepEqual([3, 5, 7]); | ||
}); | ||
Test((Range, toArray) => { | ||
const r = Range(2, 7); | ||
expect(r.toArray()).deepEqual([2, 3, 4, 5, 6, 7]); | ||
}); |
nice.Type({ | ||
title: 'Single', | ||
name: 'Single', | ||
defaultValue: () => undefined, | ||
extends: nice.Value, | ||
creator: () => { | ||
const f = (...a) => { | ||
if(a.length === 0) | ||
return f.getResult(); | ||
itemArgs0: z => { | ||
return z._value; | ||
}, | ||
f.setValue(...a); | ||
return f._parent || f; | ||
}; | ||
return f; | ||
itemArgs1: (z, v) => { | ||
z._type.setValue(z, v); | ||
}, | ||
extends: nice.Value, | ||
isFunction: true, | ||
proto: { | ||
setValue: function(...a) { | ||
const { set } = this._type; | ||
this.setResult(set ? set(...a) : a[0]); | ||
}, | ||
set: null, | ||
get: null, | ||
setByType: null, | ||
remove: null, | ||
removeAll: null, | ||
[Symbol.toPrimitive]() { | ||
return this.valueOf(); | ||
} | ||
} | ||
}).about('Parent type for all non composite types.'); | ||
}).about('Parent type for all single value types.'); | ||
nice._on('Type', type => { | ||
def(nice.Single.configProto, type.title, () => { | ||
throw "Can't add properties to SingleValue types"; | ||
reflect.on('type', type => { | ||
def(nice.Single.configProto, type.name, () => { | ||
throw new Error("Can't add properties to SingleValue types"); | ||
}); | ||
}); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1108703
71
32383
88
1