pcjs/my_modules/ecpjs-client/lib/selector.js

228 lines
8.3 KiB
JavaScript

/*
* selector.js
* by Jeff Parsons, May 24, 2012
*/
/*
* Creation of a Selector object is controlled by the following properties of the
* parmsSel object:
*
* nGates: number of gates
* color: color of the gate(s)
* single: true if single gate image to be used, false otherwise (default)
* idSource: ID of the source (eg, component with matching number of bits)
* idTarget: ID of the target (eg, component with matching number of bits)
* sourceStart: starting bit index of source corresponding to gate index 0
* targetStart: starting bit index of target corresponding to gate index 0
*
* However, by the time we're called, initSelectors() has already looked up the
* corresponding source and target components by the above IDs (so that we don't
* have to be involved in the "wiring" process) and passes them to us as:
*
* regSource
* regTarget
*
* A Selector object can be a single gate or an array of gates. The behavior of
* these gates is extensible, but by default, gates do nothing more than propagate
* bits from a "source" input to a "target" output whenever they are "selected".
*
* Internally, the gate indexes of a Selector correspond to the array indexes of
* aGates (ie, aGates[0] contains the [source, target, selected] triplet for gate
* index 0). The source, target and selected values for each gate triplet are stored
* at positions GATE_SOURCE, GATE_TARGET, and GATE_SELECTED.
*
* The display of individual gates is handled by the given updateGate() function:
*
* updateGate(iGate, f)
*
* If f is true, the gate should be displayed in an "selected" state, otherwise it
* should be displayed in a "deselected" state.
*/
function Selector(parmsSel, regSource, regTarget, updateGate) {
Component.call(this, "Sel", parmsSel);
this.aGates = new Array(parmsSel.nGates);
this.selected = false; // the selected state of the entire selector
this.single = (parmsSel.single === true);
this.source = regSource;
this.target = regTarget;
var sourceIndex = (parmsSel.sourceStart? parmsSel.sourceStart : 0);
var targetIndex = (parmsSel.targetStart? parmsSel.targetStart : 0);
var sourceCount = (this.source? this.source.count() : -1); // bit index limit for the given source
var targetCount = (this.target? this.target.count() : -1); // bit index limit for the given target
for (var iGate=0; iGate < this.aGates.length; iGate++) {
var s = sourceIndex;
var t = targetIndex;
if (sourceIndex < 0 || sourceIndex >= sourceCount) s = -1;
if (targetIndex < 0 || targetIndex >= targetCount) t = -1;
this.aGates[iGate] = [s, t, false];
sourceIndex++;
targetIndex++;
}
this.updateGate = (updateGate === undefined? function(iGate, f) {} : updateGate);
this.updateAll();
}
var GATE_SOURCE = 0;
var GATE_TARGET = 1;
var GATE_SELECTED = 2;
Component.subclass(Component, Selector, {
count: function() {
return this.aGates.length;
},
selectGate: function(iGate, f) {
this.aGates[iGate][GATE_SELECTED] = f;
if (f) {
var sourceIndex = this.aGates[iGate][GATE_SOURCE];
var targetIndex = this.aGates[iGate][GATE_TARGET];
if (sourceIndex >= 0 && targetIndex >= 0)
this.target.writeBit(targetIndex, this.source.readBit(sourceIndex));
}
},
selectAll: function(f) {
this.selected = f;
for (var iGate=0; iGate < this.aGates.length; iGate++) {
this.selectGate(iGate, f);
}
},
updateAll: function() {
for (var iGate=0; iGate < this.aGates.length; iGate++)
this.updateGate(iGate, this.aGates[iGate][GATE_SELECTED]);
this.target.updateAll();
}
});
/*
* initSelectors()
*
* Initializes all the necessary HTML to construct every Selector gate or
* gate-array as spec'ed.
*
* This function operates on every element (e) of class "selector" and inserts
* the appropriate HTML child elements of class "gate".
*
* Note that each element (e) of class "selector" is expected to have a "data-value"
* attribute containing the same JSON-encoded parameters that the Selector constructor
* expects.
*/
function initSelectors()
{
var aeSels = Component.getElementsByClass(window.document, "selector");
for (var iSel=0; iSel < aeSels.length; iSel++) {
var eSel = aeSels[iSel];
var parmsSel = Component.getComponentParms(eSel);
//
// Let's find the specified source/target components next, because if nGates isn't defined,
// then we will set a default value equal to the number of bits in the source component, if any.
//
var regSource = Component.getComponentByID(parmsSel.idSource);
var regTarget = Component.getComponentByID(parmsSel.idTarget);
if (parmsSel.nGates === undefined) {
parmsSel.nGates = (regSource? regSource.count() : 1);
}
var nGate = 1;
var sHTML = "";
if (parmsSel.single) {
sHTML += "<div class=\"gate gateLarge\"><img src=\"/my_modules/shared/images/selector.png\"/></div>\n";
}
else {
for (var iGate=0; iGate < parmsSel.nGates; iGate++,nGate++) {
sHTML += "<div class=\"gate gateSmall\"><img src=\"/my_modules/shared/images/selector.png\"/></div>\n";
}
}
eSel.innerHTML = sHTML;
if (parmsSel.id) {
eSel.setAttribute("id", "sel" + parmsSel.id);
}
//
// Now that all the document elements have been defined, we can
// create an array that refers to all "gate" elements in bit index order
// (ie, reverse of display order).
//
var aeGates = [];
var aeCells = Component.getElementsByClass(eSel, "gate");
for (var i=aeCells.length-1; i >= 0; i--) {
aeGates.push(aeCells[i]);
}
//
// Now we can create the Selector object, record it, and wire it up to the associated document elements.
//
var sel = new Selector(parmsSel, regSource, regTarget, function(ae, color, single) {
return function(iGate, f) {
var s = (f===true? "white": color);
ae[single?0:iGate].style.backgroundColor = s;
};
}(aeGates, parmsSel.color, parmsSel.single)
);
for (var i=0; i < aeGates.length; i++) {
aeGates[i].onclick = function (selParm) {
return function() {
selectSelector(selParm);
};
}(sel);
}
initSelectorControls(sel, eSel);
//
// For testing purposes, we could tweak a few of the gates, just to see if all the "wiring" works.
//
// sel.selectGate(7, true);
// sel.selectGate(9, false);
//
}
if (aeSels.length == 0)
console.log("warning: no selectors on page");
}
/*
* initSelectorControls(sel, eSel)
*
* For each Selector object created by initSelectors(), this function looks for any controls that have been defined
* along with the selector element in the current document, and "wires" them as needed.
*
* The following controls are supported:
*
* One optional 'button' control of class "select"
*/
function initSelectorControls(sel, eSel)
{
var aeControls = Component.getElementsByClass(eSel.parentNode, "controls");
for (var iControl = 0; iControl < aeControls.length; iControl++) {
var aeChildren = aeControls[iControl].childNodes;
for (var i=0; i < aeChildren.length; i++) {
var e = aeChildren[i];
if (e.nodeType != document.ELEMENT_NODE)
continue;
var sClass = e.getAttribute("class");
if (e.nodeName == "BUTTON" && sClass == "select") {
e.onclick = function(e) {
return function() { selectSelector(sel, e); };
}(e);
continue;
}
}
}
}
/*
* Function called by the anonymous click handlers for the "Select" button.
*/
function selectSelector(sel, e)
{
sel.selectAll(!sel.selected);
sel.updateAll();
if (e !== undefined) {
e.innerHTML = (f? "Deselect" : "Select");
}
}
/*
* Initialize all the selectors on the page.
*/
web.onInit(initSelectors);