nv-data-selection
Version:
nv-data-selection ============ - nv-data-selection ,nest selection - multi, radio,value,validator - notifier callback
447 lines (426 loc) • 12.7 kB
JavaScript
const {_UiNode} = require("nv-data-tree-csp-node");
const {noexist,kvlist2d,is_str} = require("nv-facutil-basic");
const {
wfs_eng,
wfs_tac,
} = require("nv-data-tree-csp-jconvert")
const ERROR = {
can_only_change_type_between_radio_and_multi:'can_only_change_type_between_radio_and_multi',
only_value_or_setter_can_assign_value:'ERROR.only_value_or_setter_can_assign_value',
only_setter_have_validate:'only_setter_have_validate',
type_must_in_option_type:'type_must_in_option_type',
setter_validate_failed:'setter_validate_failed',
slcted_children_gt_one:'slcted_children_gt_one'
}
const sym_typ = Symbol("");
const sym_slct_self = Symbol("");
const sym_recv = Symbol("");
const sym_val = Symbol("");
const sym_as = Symbol("");
function _calc(that) {
if(that.type_ === 'Multi') {
let opts = that.slcted_children_;
opts = opts.filter(opt=>opt.val_!==noexist);
let ks = opts.map(opt=>opt.key_);
let vs = opts.map(opt=>opt.val_);
if(that.as_ === 'ary') {
that[sym_val] = vs
} else {
that[sym_val] = kvlist2d(ks,vs);
}
} else if(that.type_ === 'Radio') {
let opts = that.slcted_children_;
if(opts.length === 1) {
that[sym_val] = opts[0].val_;
} else {
that[sym_val] = noexist;
}
} else if(
that.type_ === 'Setter' ||
that.type_ === 'Value'
) {
} else {
let opts = that.slcted_children_;
opts = opts.filter(opt=>opt.val_!==noexist);
let ks = opts.map(opt=>opt.key_);
let vs = opts.map(opt=>opt.val_);
that[sym_val] = kvlist2d(ks,vs);
}
}
function _unparse(rt) {
let nrt = rt.$clone(
(nd,nnd) => {
if(nd.type_==='Root') {
nnd.T = '%root%';
nnd.A = {};
} else {
nnd.T = nd.key_;
if(nd.type_==='Value' && is_str(nd.val_)) {
nnd.A = {}
} else {
nnd.A = {type:nd.type_}
if(nd.as_!=='val') { nnd.A.as = nd.as_}
if(nd.type_==='Setter') {
if(nd.val_ !== noexist) {
nnd.A.val = nd.val_;
} else {}
if(nd.validate_!==noexist) {
nnd.A.validate = nd.validate_
} else {
nnd.A.validate = DFLT_VALI;
}
}
if(nd.noti_!==noexist) { nnd.A.as = nd.as_}
if(nd.type_==='Value' && !is_str(nd.val_)) {
nnd.A.val = nd.val_
}
}
if(nd.is_slcted()) {nnd.A.slcted = true}
}
}
);
return(nrt)
}
const DFLT_VALI = (v,self) => true;
const DFLT_NOTI = (src,msg,self)=>{};
class Option extends _UiNode {
#slcted = false
#type = 'Root'
#as = 'val'
#val = noexist
#validate = noexist
#key = noexist
#noti = noexist
////
regis_$noti$(f=(src,msg,self)=>{}) {this.#noti = f}
get noti_() {return(this.#noti)}
[sym_recv](src,msg) {
////
_calc(this);
////
let p = this.$parent_;
if(p!==null && this.is_slcted()) {
p[sym_recv](src,msg)
} else {}
////
if(this.#noti !== noexist) {
this.#noti(src,msg,this);
} else {}
}
////
get [sym_typ]() {return(this.#type)}
set [sym_typ](T) {
if(Object.values(Option.TYPE).includes(T)) {
this.#type = T
} else {
console.log(ERROR.type_must_in_option_type)
}
}
////
get validate_() {
if(this.#type !== Option.TYPE.setter) {
console.log(ERROR.only_setter_have_validate)
} else {
return(this.#validate)
}
}
regis_$validate$(f=(v,self)=>true) {
if(this.#type !== Option.TYPE.setter) {
console.log(ERROR.only_setter_have_validate)
} else {
this.#validate = f;
}
}
////
get key_() {return(this.#key)}
set key_(k) {this.#key = k}
////
get type_() {return(this.#type)}
change_$type_to_multi$() {
if(this.#type === Option.TYPE.multi || this.#type === Option.TYPE.radio) {
this.#type = Option.TYPE.multi
} else {
console.log(ERROR.can_only_change_type_between_radio_and_multi)
}
}
change_$type_to_radio$() {
if(this.#type === Option.TYPE.multi || this.#type === Option.TYPE.radio) {
if(
this.#type === Option.TYPE.multi &&
this.slcted_children_.length>1
) {
console.log(ERROR.slcted_children_gt_one)
} else {
this.#type = Option.TYPE.radio
}
} else {
console.log(ERROR.can_only_change_type_between_radio_and_multi)
}
}
////
set [sym_val](v) {this.#val=v} //internal using
get val_() {return(this.#val)}
set val_(v) {
if(this.#type === Option.TYPE.value) {
this.#val = v;
let p = this.$parent_;
if(p!==null) {p[sym_recv](this,'set')}
} else if(this.#type === Option.TYPE.setter) {
if(this.#validate === noexist) {
this.#val = v;
let p = this.$parent_;
if(p!==null) {p[sym_recv](this,'set')}
} else {
let cond = this.#validate(v,this);
if(cond) {
this.#val = v;
let p = this.$parent_;
if(p!==null) {p[sym_recv](this,'set')}
} else {
console.log(ERROR.setter_validate_failed)
}
}
} else {
console.log(ERROR.only_value_or_setter_can_assign_value)
}
}
////
set [sym_as](v) {this.#as =v}
get as_() {return(this.#as)}
set_$as_ary$() {
if(this.#type === Option.TYPE.multi) {
this[sym_as] = Option.AS.ary;
} else {
console.log(ERROR.only_multi_can_set_as)
}
}
set_$as_dict$() {
if(this.#type === Option.TYPE.multi) {
this[sym_as] = Option.AS.dict;
} else {
console.log(ERROR.only_multi_can_set_as)
}
}
////
////return this
[sym_slct_self] () {
this.#slcted=true;
let p = this.$parent_
if(p!==null){p[sym_recv](this,'slct')}
}
slct_self() {
let p = this.$parent_
if(p!==null && p.type_ === Option.TYPE.radio) {
let i = this.$sibseq_;
p.slct_child(i)
} else {
this.#slcted = true;
}
if(p!==null){p[sym_recv](this,'slct')}
return(this)
}
unslct_self() {
this.#slcted = false;
let p = this.$parent_;
if(p!==null){p[sym_recv](this,'unslct')}
return(this)
}
////
get opts_() { return(this.$children_)}
////return child or undefined
slct_child(i) {
let opts = this.opts_;
if(opts[i]!==undefined) {
opts[i][sym_slct_self]();
if(this.#type === Option.TYPE.radio) {
for(let idx=0;idx<i;idx++) {
opts[idx].unslct_self();
}
for(let idx=i+1;idx<opts.length;idx++) {
opts[idx].unslct_self();
}
} else {}
return(opts[i])
} else {
}
}
unslct_child(i) {
let opts = this.opts_;
if(opts[i]!==undefined) {
opts[i].unslct_self();
return(opts[i])
} else {
}
}
////return array | undefined
slct_all_children() {
if(this.#type === Option.TYPE.radio) {
console.log(ERROR.radio_can_not_slct_all)
} else {
this.opts_.forEach(child=>child.slct_self())
return(this.opts_)
}
}
unslct_all_children() {
this.opts_.forEach(child=>child.unslct_self());
return(this.opts_)
}
////
is_slcted() {return(this.#slcted)}
get slcted_children_() {
return(this.opts_.filter(c=>c.is_slcted()))
}
get unslcted_children_() {
return(this.opts_.filter(c=>!c.is_slcted()))
}
////
json() {
let nrt = _unparse(this);
let j = wfs_tac.jsonize(nrt);
nrt.$erase_r();
return(j)
}
////
tree() {
let rd = {}
let unhandled = [{d:rd,nd:rt}];
let next_unhandled = []
while(unhandled.length>0) {
for(let ele of unhandled) {
let {d,nd} = ele;
let children = nd.$children_;
for(let c of children) {
if(c.is_slcted()) {
d[c.key_] = {}
let D = d[c.key_];
if(c.$is_leaf()) {
d[c.key_] = c.val_;
} else {
next_unhandled.push({d:D,nd:c})
}
} else {
}
}
}
unhandled = next_unhandled;
next_unhandled = []
}
return(rd)
}
////
get chks_() {
let opts = this.opts_;
return(opts.map(r=>r.key_))
}
kget(k) {
let opts = this.opts_;
opts = opts.filter(r=>r.key_ === k);
if(opts.length === 0) {
return(noexist)
} else if(opts.length ===1) {
return(opts[0])
} else {
return(opts)
}
}
////
get [Symbol.toStringTag]() {
if(this.#type === 'Root') {
return(` ${this.#type} `)
} else {
let slcted = this.is_slcted()?'<slcted>':'<unslcted>';
return(` ${this.#type} : ${slcted} `)
}
}
}
Option.TYPE = {
multi:'Multi',
radio:'Radio',
value:'Value',
setter:'Setter'
}
Object.freeze(Option.TYPE)
Option.MSG = {
slct:'slct',
unslct:'unslct',
set:'set',
}
Object.freeze(Option.MSG)
Option.AS = {
ary:'ary',
dict:'dict',
}
Object.freeze(Option.AS)
function _add_getter(nd,k) {
let children = nd.$children_;
children.forEach(
ch=>{
Object.defineProperty(nd,ch[k],{get:function(){return(ch)}})
}
);
}
function load_from_json(j) {
let [rt,forest] = wfs_tac.tree_lize(j,undefined,Option);
////
rt[sym_as] = 'dict';
rt[sym_val] = {};
delete rt.T;
delete rt.A;
_add_getter(rt,'T');
////
let sdfs = rt.$sdfs_;
sdfs = sdfs.slice(1)
sdfs.forEach(
nd => {
nd.key_ = nd.T;
////
if(nd.A.noti !== undefined) {
nd.regis_$noti$(nd.A.noti);
} else {}
////
if(nd.A.slcted === true) {
nd.slct_self();
} else {}
////
if(nd.A.type === undefined) {
nd[sym_typ] = Option.TYPE.value;
nd.val_ = nd.T;
} else if(nd.A.type === Option.TYPE.value) {
nd[sym_typ] = Option.TYPE.value;
nd.val_ = nd.A.val;
} else if(nd.A.type === Option.TYPE.setter) {
nd[sym_typ] = Option.TYPE.setter;
if(nd.A.validate === undefined) {
} else {
nd.regis_$validate$(nd.A.validate);
if(nd.A.hasOwnProperty('dflt')) {
let cond = nd.A.validate(nd.A.dflt);
if(cond) {
nd.val_ = nd.A.dflt;
} else {}
} else {}
}
} else if(nd.A.type === Option.TYPE.radio) {
nd[sym_typ] = Option.TYPE.radio;
} else if(nd.A.type === Option.TYPE.multi) {
nd[sym_typ] = Option.TYPE.multi;
nd[sym_as] = nd.A.as??'ary';
nd[sym_val] = (nd.as_ === 'ary')?[]:{}
} else {
}
delete nd.T;
delete nd.A;
_add_getter(nd,'T')
}
);
rt.slct_all_children();
return([rt,forest])
}
module.exports = {
noexist,
ERROR,
DFLT_VALI,
DFLT_NOTI,
Option,
load_from_json,
}