SankeySheets/scripts/Elementer.js
2016-06-07 13:08:41 +01:00

569 lines
No EOL
15 KiB
JavaScript

function Elementer() {
var self = this;
var elements_, detail_, layout_, container_, main_, classes_, root_,initial_, initialSource_ ="standard",standard_;
// the default layout
// anything added by .setLayout() will be merged with this
var defLayout_ = {
settings: {
prefix: "layout",
root: "root"
},
pages: {
root: {
label: "Settings",
items: [],
classes: {
branch: "",
nav: {
prevIcon: ""
}
}
}
}
};
// anything added with setClasses() will be merged with this
var defClasses_ = {
branch: "",
subhead: "mui--text-subhead",
backLabel: "mui--text-accent-secondary",
title: "mui--text-title",
table: "mui-table",
tr: "",
td: "",
label: "mui--text-dark-secondary",
element: "",
elementContainer: "",
tdElement: "mui--text-right mui--align-middle",
tdLabel: "",
icon: "mui--text-dark-secondary material-icons md-18",
tdIcon: "mui--align-middle",
option:"",
nav: {
nextIcon: "chevron_right",
prevIcon: "chevron_left"
},
hide: "mui--hide",
show: "mui--show"
};
var defStyles_ = {
td: "padding:3px",
tdLabel: "padding:3px",
tdIcon: "padding:3px",
tdElement: "padding:3px",
backLabel:"position:relative;vertical-align: middle;",
icon:"vertical-align: middle;",
elementContainer:"padding:0px;margin:0;",
navCursor:"cursor: pointer; cursor: hand;",
backLabel:"cursor: pointer; cursor: hand;"
};
var defDetail_ = {
selectTemplate: {
tag: "SELECT",
classes:{
elementContainer:"mui-select"
},
values:{
property:"value",
resetable:true
}
},
textAreaTemplate: {
tag: "TEXTAREA",
label:"Input text area",
classes:{
elementContainer:"mui-textfield"
},
values:{
property:"value",
resetable:true,
value:"",
}
},
buttonTemplate:{
tag:"BUTTON",
label:"",
classes:{
element:"mui-btn"
},
properties:{
type:"button"
},
values:{
property:"innerHTML",
value:"Submit",
resetable:false
}
},
textTemplate: {
tag: "INPUT",
label: "Input text",
icon: "input",
properties: {
type: "text"
},
styles: {
element: "max-width:100px;"
},
classes:{
elementContainer:"mui-textfield"
},
values: {
property:"value",
value:"",
resetable:true
}
},
numberTemplate: {
tag: "INPUT",
label: "Input number",
icon: "input",
properties: {
type: "number",
min: 0,
max: 100
},
classes:{
elementContainer:"mui-textfield"
},
values: {
property:"value",
value:0,
resetable:true
}
},
checkboxTemplate: {
tag: "INPUT",
properties: {
type: "checkbox"
},
classes:{
elementContainer:"mui-checkbox"
},
values: {
property:"checked",
value:false,
resetable:true
}
},
radioTemplate: {
tag: "INPUT",
properties: {
type: "radio",
name: "radioGroup"
},
classes:{
elementContainer:"mui-radio"
},
values: {
property:"checked",
value:false,
resetable:true
}
},
subheadTemplate: {
label: "subhead",
classes: {
label: "mui--text-subhead"
}
},
dividerTemplate: {
label: "subhead",
classes: {
label: "mui--text-accent"
}
},
contentTemplate: {
label: "some content",
classes: {
label: "mui--text-dark-primary mui--text-body1"
}
}
};
/**
* these are the computed values/styles and classes generated by the elementer
* @return {object} the computed values/styles etc
*/
self.getStandard = function () {
return standard_;
};
/**
* these are the computed values/styles and classes generated by the elementer
* @return {object} the computed values/styles etc
*/
self.getInitial = function () {
return initial_;
};
self.setInitial = function (initial,initialSource) {
if (initial) {
initialSource_ = initialSource;
initial_ = initial;
self.restoreInitial();
return self.getInitial();
}
return null;
};
/**
* return the elements generated
* @return {object} the element view
*/
self.getElements = function() {
return elements_;
};
/**
* @param {object} detail an object with the item details
* @return {Elementer} self
*/
self.setDetail = function(detail) {
detail_ = Utils.vanMerge([defDetail_, detail]);
return self;
};
/**
* @param {object} detail an object with the item layout
* @return {Elementer} self
*/
self.setLayout = function(layout) {
layout_ = Utils.vanMerge([defLayout_, layout]);
return self;
};
/**
* this is the container that hosts the main
* content of the elementer
* @param {string|element} container elementer content
* @return {Elementer} self
*/
self.setContainer = function(container) {
container_ = DomUtils.elem(container);
return self;
};
/**
*this is the container of the main page hosting the
* link to the elementer content
* if specified it will be hidden or shown
* depending on whether we are at the top level
* @param {string|element} main main hosting element
* @return {Elementer} self
*/
self.setMain = function(main) {
main_ = DomUtils.elem(main);
return self;
};
/**
*this is the root element
* which will normally be a child of main
* @param {string|element} root hook to elementer
* @return {Elementer} self
*/
self.setRoot = function(root) {
root_ = DomUtils.elem(root);
return self;
};
/**
*this is an object with the required formatting classes
* if you are using muicss, then this is optional
* @param {object} classes your class definitions to merge
* @return {Elementer} self
*/
self.setClasses = function(classes) {
classes_ = Utils.vanMerge([defClasses_, classes]);
return self;
};
/**
* put the settings back to standard
* @return {Elementer} self
*/
self.restorestandard = function() {
self.applySettings (standard_);
return self;
};
/**
* put the settings back to what they were initially
* @return {Elementer} self
*/
self.restoreInitial = function() {
self.applySettings (initial_);
return self;
}
self.applySettings = function (target) {
if (!target) return false;
// reset all the settable values
Object.keys(target).forEach(function(k) {
elements_.controls[k][elements_.values[k].property] = target[k];
});
return true;
};
self.getCurrent = function () {
return Object.keys(initial_).reduce(function(p,c) {
p[c] = elements_.controls[c][elements_.values[c].property]
return p;
},{});
};
/**
*this creates the elements
* @return {Elementer} self
*/
self.build = function() {
// in case just using default
classes_ = classes_ || defClasses_;
// check we have all needed
if (!detail_ || !layout_ || !container_ || !root_) {
throw 'a container, detail, root and layout are all required'
}
// this is the final result
elements_ = {
controls: {},
pages: {},
clicked: {},
values:{}
};
// this is the computed styles etc.
initial_ = {};
// clear out the container
container_.innerHTML = "";
// short cut
var ea = DomUtils.addElem;
// do the root
doLayout(root_, layout_.settings.root, layout_, detail_);
// do the others
Object.keys(layout_.pages).forEach(function(k) {
if (k !== layout_.settings.root) {
doLayout(container_, k, layout_, detail_);
}
});
// set the initial visibility state
var rt = layout_.settings.root;
var onEnter = layout_.pages[rt].on ? layout_.pages[rt].on.enter : null;
showThis(elements_.pages[rt], onEnter);
return self;
function doLayout(parent, branchName, layout, details) {
// working on this layout
var lob = layout.pages[branchName];
// merge with any specific classes for this layout page
var ec = Utils.vanMerge([classes_, lob.classes]);
var es = Utils.vanMerge ([defStyles_ , lob.styles || {}]);
// the container for this layout
var dv = ea(parent, "div", "", ec.branch);
dv.id = layout.settings.prefix + "-" + branchName;
elements_.pages[branchName] = dv;
// prev icon - hide this and go back to whoever called
if (ec.nav.prevIcon) {
var dvp = ea(dv, "div", "", ec.backLabel, es.backLabel);
var backIcon = ea(dvp, "span", ec.nav.prevIcon, ec.icon, es.icon);
ea(dvp, "span", "Back to ???", ec.backLabel, es.backLabel).id = layout.settings.prefix + "-" + branchName + "-backcomment";
dvp.addEventListener('click', function(e) {
// do exit processing
var onExit = layout_.pages[branchName].on ? layout_.pages[branchName].on.exit : null;
if (onExit) {
onExit(self, branchName);
}
// do entry to the new page processing
var elb = elements_.clicked[branchName];
var onEnter = layout_.pages[elb.branchName].on ? layout_.pages[elb.branchName].on.enter : null;
showThis(elb.element, onEnter);
});
}
// use a table to lay out
var tab = ea(dv, "table", "", ec.table, es.table);
// the section title
var tr = ea(tab, "tr", "", ec.tr, es.tr);
if (ec.nav.prevIcon) {
var td = ea(tr, "td", "", ec.td, es.td);
td.colSpan = 3;
ea(td, "span", DomUtils.fillLabel(lob.label), ec.title, es.title);
}
lob.items.forEach(function(d) {
if (layout.pages[d]) {
// its a reference to another section
var ob = layout.pages[d];
// bring in any override classes
var fc = Utils.vanMerge([ec, ob.classes]);
var fs = Utils.vanMerge([es, ob.styles]);
var tr = ea(tab, "tr", "", fc.tr, fc.nav.nextIcon ? fs.navCursor : fs.tr);
// the icon
if (ob.icon) {
var td = ea(tr, "td", "", fc.tdIcon, fs.tdIcon);
ea(td, "i", ob.icon, fc.icon, fs.icon);
}
// the label
var td = ea(tr, "td", "", fc.tdLabel, fs.tdLabel);
var label = ob.label ? ea(td, "label", DomUtils.fillLabel(ob.label), fc.subhead, fc.nav.nextIcon ? fs.navCursor : fs.subhead) : null;
if (!ob.icon) {
td.colSpan = 2;
}
// the next icon
var td = ea(tr, "td", "", fc.tdIcon, fs.tdIcon);
function clickPointer (elem , itemName, brName ) {
elem.addEventListener ('click', function () {
if (!elements_.clicked[itemName] || itemName !== layout.settings.root) {
elements_.clicked[itemName] = {
element: dv,
branchName: brName
}
var backComment = DomUtils.elem(layout.settings.prefix + "-" + d + "-backcomment");
if (backComment) {
var lb = DomUtils.fillLabel(layout_.pages[brName].label);
backComment.innerHTML = "Back to " + lb;
}
}
var onEnter = layout_.pages[itemName].on ? layout_.pages[itemName].on.enter : null;
showThis(layout.settings.prefix + "-" + itemName, onEnter);
});
}
// add navigation to next level
if (fc.nav.nextIcon) {
ea(td, "i", fc.nav.nextIcon, fc.icon, fs.icon);
clickPointer (tr,d, branchName);
}
} else {
// it should be some real stuff
if (!details[d]) {
throw d + ' layout reference not found in details';
}
if (details[d].template && !details[details[d].template]) {
throw 'template ' + details[d].template + ' not found';
}
var ob = Utils.vanMerge([details[details[d].template], details[d]]);
var fc = Utils.vanMerge([ec, ob.classes]);
var fs = Utils.vanMerge([es, ob.styles]);
var tr = ea(tab, "tr", "", fc.tr, fs.tr);
// the label
if (ob.custom && ob.custom.spanCols) {
// this element spans all columns
var td = ea(tr, "td", "", fc.tdElement, fs.tdElement);
td.colSpan = 3;
}
else {
// the icon
if (ob.icon) {
var td = ea(tr, "td", "", fc.tdIcon, fs.tdIcon);
ea(td, "i", ob.icon, fc.icon);
}
var td = ea(tr, "td", "", fc.tdLabel, fs.tdLabel);
var label = ob.label ? ea(td, "label", DomUtils.fillLabel(ob.label), fc.label, fs.label) : null;
if (!ob.icon) {
td.colSpan = 2;
}
// the element
var td = ea(tr, "td", "", fc.tdElement, fs.tdElement);
}
if (ob.tag) {
var ediv = ea(td, "span", "", fc.elementContainer, fs.elementContainer);
var elem = ea(ediv, ob.tag, "", fc.element, fs.element);
elem.id = dv.id + "-" + d + "-elem";
if (label) label['for'] = elem.id;
// this should only apply to selects
// but i'll leave it unchecked and just do it anyway
if (ob.options){
ob.options.forEach(function(g){
ea (elem,"option",g,fc.option,fs.option).value = g;
});
}
if (ob.properties) {
Object.keys(ob.properties).forEach(function(e) {
elem[e] = ob.properties[e];
});
}
if (ob.values && ob.values.property) {
elem[ob.values.property] = ob.values.value;
elements_.values[d] = ob.values;
if(ob.values.resetable) {
initial_[d] = ob.values.value;
}
}
elements_.controls[d] = elem;
}
}
});
standard_ = Utils.clone(initial_);
return dv;
}
function showThis(keep, onEnter) {
var dh = DomUtils.hide,
de = DomUtils.elem,
ep = elements_.pages;
// ensure all other pages are hidden
Object.keys(ep).forEach(function(p) {
dh(ep[p], true, classes_.hide);
});
// if the active page is the root then show the main too.
if (main_) {
var showMain = de(keep).id === ep[layout_.settings.root].id;
dh(main_, !showMain, classes_.hide);
}
// do any on enter
if (onEnter) {
onEnter(self, de(keep).id.match(/-(.+)$/)[1])
}
// show the active page
dh(keep, false, classes_.hide);
}
};
};