src/js/prototypes/atom.js
import AttachmentPoint from './attachmentpoint'
import GlobalVariables from '../globalvariables'
import showdown from 'showdown'
/**
* This class is the prototype for all atoms.
*/
export default class Atom {
/**
* The constructor function.
* @param {object} values An array of values passed in which will be assigned to the class as this.x
*/
constructor(values){
//Setup default values
/**
* An array of all of the input attachment points connected to this atom
* @type {array}
*/
this.inputs = []
/**
* This atom's output attachment point if it has one
* @type {object}
*/
this.output = null
/**
* This atom's unique ID. Often overwritten later when loading
* @type {number}
*/
this.uniqueID = GlobalVariables.generateUniqueID()
/**
* A description of this atom
* @type {string}
*/
this.description = "none"
/**
* The X cordinate of this atom
* @type {number}
*/
this.x = 0
/**
* The Y cordinate of this atom
* @type {number}
*/
this.y = 0
/**
* This atom's radius as displayed on the screen is 1/72 width
* @type {number}
*/
this.radius = 1/75
/**
* This atom's default color (ie when not selected or processing)
* @type {string}
*/
this.defaultColor = '#F3EFEF'
/**
* This atom's color when selected
* @type {string}
*/
this.selectedColor = '#484848'
/**
* The color currently used for strokes
* @type {string}
*/
this.strokeColor = '#484848'
/**
* A flag to indicate if this atom is currently selected
* @type {boolean}
*/
this.selected = false
/**
* This atom's current color
* @type {string}
*/
this.color = '#F3EFEF'
/**
* This atom's name
* @type {string}
*/
this.name = 'name'
/**
* This atom's parent, usually the molecule which contains this atom
* @type {object}
*/
this.parentMolecule = null
/**
* This atom's value...Is can this be done away with? Are we basically storing the value in the output now?
* @type {object}
*/
this.value = null
/**
* A flag to indicate if this atom is currently being dragged on the screen.
* @type {boolean}
*/
this.isMoving = false
/**
* A flag to indicate if we are hovering over this atom.
* @type {boolean}
*/
this.showHover = false
/**
* The X coordinate of this atom now
* @type {number}
*/
this.x = 0
/**
* The Y coordinate of this atom now
* @type {number}
*/
this.y = 0
/**
* A warning message displayed next to the atom. Put text in here to have a warning automatically show up. Cleared each time the output is regenerated.
* @type {string}
*/
this.alertMessage = ''
/**
* A flag to indicate if the atom is currently computing a new output. Turns the molecule blue.
* @type {boolean}
*/
this.processing = false
/**
* The path which contains the geometry represented by this atom
* @type {string}
*/
this.path = ""
/**
* A function which can be called to cancel the processing being done for this atom.
* @type {function}
*/
this.cancelProcessing = () => {console.warn("Nothing to cancel")}
for(var key in values) {
/**
* Assign each of the values in values as this.value
*/
this[key] = values[key]
}
this.generatePath()
}
/**
* Generates the path for this atom from it's location in the graph
*/
generatePath(){
let levelToInspect = this
let topPath = ""
while(!levelToInspect.topLevel){
topPath = "/" + levelToInspect.uniqueID + topPath
levelToInspect = levelToInspect.parent
}
this.path ="source/" + levelToInspect.uniqueID + topPath + this.atomType
}
/**
* Applies each of the passed values to this as this.x
* @param {object} values - A list of values to set
*/
setValues(values){
//Assign the object to have the passed in values
for(var key in values) {
this[key] = values[key]
}
this.generatePath()
if (typeof this.ioValues !== 'undefined') {
this.ioValues.forEach(ioValue => { //for each saved value
this.inputs.forEach(io => { //Find the matching IO and set it to be the saved value
if(ioValue.name == io.name && io.type == 'input'){
io.value = ioValue.ioValue
}
})
})
}
}
/**
* Draws the atom on the screen
*/
draw(drawType) {
let xInPixels = GlobalVariables.widthToPixels(this.x)
let yInPixels = GlobalVariables.heightToPixels(this.y)
let radiusInPixels = GlobalVariables.widthToPixels(this.radius)
this.inputs.forEach(child => {
child.draw()
})
GlobalVariables.c.beginPath()
GlobalVariables.c.font = '10px Work Sans'
if(this.processing){
GlobalVariables.c.fillStyle = 'blue'
}
else if(this.selected){
GlobalVariables.c.fillStyle = this.selectedColor
GlobalVariables.c.strokeStyle = this.selectedColor
this.color = this.selectedColor
this.strokeColor = this.defaultColor
}
else{
GlobalVariables.c.fillStyle = this.defaultColor
GlobalVariables.c.strokeStyle = this.selectedColor
this.color = this.defaultColor
this.strokeColor = this.selectedColor
}
GlobalVariables.c.beginPath()
if (drawType == "rect"){
GlobalVariables.c.rect(xInPixels - radiusInPixels, yInPixels - this.height/2, 2* radiusInPixels, this.height)
}
else{
GlobalVariables.c.arc(xInPixels, yInPixels, radiusInPixels, 0, Math.PI * 2, false)
}
GlobalVariables.c.textAlign = 'start'
GlobalVariables.c.fill()
GlobalVariables.c.strokeStyle = this.strokeColor
GlobalVariables.c.fillStyle = "white"
GlobalVariables.c.stroke()
GlobalVariables.c.closePath()
GlobalVariables.c.beginPath()
GlobalVariables.c.textAlign = 'start'
GlobalVariables.c.fillText(this.name, xInPixels + radiusInPixels, yInPixels - radiusInPixels)
GlobalVariables.c.fill()
GlobalVariables.c.strokeStyle = this.strokeColor
GlobalVariables.c.lineWidth = 1
GlobalVariables.c.stroke()
GlobalVariables.c.closePath()
if (this.showHover){
if (this.alertMessage.length > 0){
this.color = "red"
//Draw Alert block
GlobalVariables.c.beginPath()
const padding = 10
GlobalVariables.c.fillStyle = 'red'
GlobalVariables.c.rect(
xInPixels + radiusInPixels - padding/2,
yInPixels - radiusInPixels + padding/2,
GlobalVariables.c.measureText(this.alertMessage.toUpperCase()).width + padding,
- (parseInt(GlobalVariables.c.font) + padding))
GlobalVariables.c.fill()
GlobalVariables.c.strokeStyle = 'black'
GlobalVariables.c.lineWidth = 1
GlobalVariables.c.stroke()
GlobalVariables.c.closePath()
GlobalVariables.c.beginPath()
GlobalVariables.c.fillStyle = 'black'
GlobalVariables.c.fillText(this.alertMessage.toUpperCase(), xInPixels + radiusInPixels, yInPixels - radiusInPixels)
GlobalVariables.c.closePath()
}
}
}
/**
* Adds a new attachment point to this atom
* @param {boolean} type - The type of the IO (input or output)
* @param {string} name - The name of the new attachment point
* @param {object} target - The atom to attach the new attachment point to. Should we force this to always be this one?
* @param {string} valueType - Describes the type of value the input is expecting options are number, geometry, array
* @param {object} defaultValue - The default value to be used when the value is not yet set
*/
addIO(type, name, target, valueType, defaultValue, ready, primary = false){
if(target.inputs.find(o => (o.name === name && o.type === type))== undefined){ //Check to make sure there isn't already an IO with the same type and name
//compute the baseline offset from parent node
var offset
if (type == 'input'){
offset = -1* target.scaledRadius
}
else{
offset = target.scaledRadius
}
var newAp = new AttachmentPoint({
parentMolecule: target,
defaultOffsetX: offset,
defaultOffsetY: 0,
type: type,
valueType: valueType,
name: name,
primary: primary,
value: defaultValue,
defaultValue: defaultValue,
uniqueID: GlobalVariables.generateUniqueID(),
atomType: 'AttachmentPoint',
ready: true
})
if(type == 'input'){
target.inputs.push(newAp)
}else{
target.output = newAp
}
}
}
/**
* Removes an attachment point from an atom.
* @param {boolean} type - The type of the IO (input or output).
* @param {string} name - The name of the new attachment point.
* @param {object} target - The attom which the attachment point is attached to. Should
* @param {object} silent - Should any connected atoms be informed of the change
*/
removeIO(type, name, target, silent = false){
//Remove the target IO attachment point
target.inputs.forEach(input => {
if(input.name == name && input.type == type){
target.inputs.splice(target.inputs.indexOf(input),1)
input.deleteSelf(silent)
}
})
}
/**
* Set an alert to display next to the atom.
* @param {string} message - The message to display.
*/
setAlert(message){
this.color = 'orange'
this.alertMessage = String(message)
console.warn(message)
}
/**
* Clears the alert message attached to this atom.
*/
clearAlert(){
this.color = this.defaultColor
this.alertMessage = ''
}
/**
* Delineates bounds for selection box.
*/
selectBox(x,y,xEnd,yEnd){
let xIn = Math.min(x, xEnd)
let xOut = Math.max(x, xEnd)
let yIn = Math.min(y, yEnd)
let yOut = Math.max(y, yEnd)
let xInPixels = GlobalVariables.widthToPixels(this.x)
let yInPixels = GlobalVariables.heightToPixels(this.y)
if(xInPixels >= xIn && xInPixels <= xOut){
if(yInPixels >= yIn && yInPixels <= yOut){
//this.isMoving = true
this.selected = true
}
}
}
/**
* Set the atom's response to a mouse click. This usually means selecting the atom and displaying it's contents in 3D
* @param {number} x - The X coordinate of the click
* @param {number} y - The Y coordinate of the click
* @param {boolean} clickProcessed - A flag to indicate if the click has already been processed
*/
clickDown(x,y, clickProcessed){
let xInPixels = GlobalVariables.widthToPixels(this.x)
let yInPixels = GlobalVariables.heightToPixels(this.y)
let radiusInPixels = GlobalVariables.widthToPixels(this.radius)
//If none of the inputs processed the click see if the atom should, if not clicked, then deselected
if(!clickProcessed && GlobalVariables.distBetweenPoints(x, xInPixels, y, yInPixels) < radiusInPixels){
this.isMoving = true
this.selected = true
this.updateSidebar()
this.sendToRender()
clickProcessed = true
}
//Deselect this if it wasn't clicked on, unless control is held
else if (!GlobalVariables.ctrlDown){
this.selected = false
}
//Returns true if something was done with the click
this.inputs.forEach(child => {
if(child.clickDown(x,y, clickProcessed) == true){
clickProcessed = true
}
})
if(this.output){
if(this.output.clickDown(x,y, clickProcessed) == true){
clickProcessed = true
}
}
return clickProcessed
}
/**
* Set the atom's response to a mouse double click. By default this isn't to do anything other than mark the double click as handled.
* @param {number} x - The X cordinate of the click
* @param {number} y - The Y cordinate of the click
*/
doubleClick(x,y){
//returns true if something was done with the click
let xInPixels = GlobalVariables.widthToPixels(this.x)
let yInPixels = GlobalVariables.heightToPixels(this.y)
var clickProcessed = false
var distFromClick = GlobalVariables.distBetweenPoints(x, xInPixels, y, yInPixels)
if (distFromClick < xInPixels){
clickProcessed = true
}
return clickProcessed
}
/**
* Set the atom's response to a mouse click up. If the atom is moving this makes it stop moving.
* @param {number} x - The X cordinate of the click
* @param {number} y - The Y cordinate of the click
*/
clickUp(x,y){
this.isMoving = false
this.inputs.forEach(child => {
child.clickUp(x,y)
})
if(this.output){
this.output.clickUp(x,y)
}
}
/**
* Set the atom's response to a mouse click and drag. Moves the atom around the screen.
* @param {number} x - The X cordinate of the click
* @param {number} y - The Y cordinate of the click
*/
clickMove(x,y){
let xInPixels = GlobalVariables.widthToPixels(this.x)
let yInPixels = GlobalVariables.heightToPixels(this.y)
let radiusInPixels = GlobalVariables.widthToPixels(this.radius)
if (this.isMoving == true){
this.x = GlobalVariables.pixelsToWidth(x)
this.y = GlobalVariables.pixelsToHeight(y)
}
this.inputs.forEach(child => {
child.clickMove(x,y)
})
if(this.output){
this.output.clickMove(x,y)
}
var distFromClick = GlobalVariables.distBetweenPoints(x, xInPixels, y, yInPixels)
//If we are close to the attachment point move it to it's hover location to make it accessible
if (distFromClick < radiusInPixels ){
this.showHover = true
}
else { this.showHover = false}
}
/**
* Set the atom's response to a key press. Is used to delete the atom if it is selected.
* @param {string} key - The key which has been pressed.
*/
keyPress(key){
this.inputs.forEach(child => {
child.keyPress(key)
})
}
/**
* Updates the side bar to display information about the atom. By default this is just add a title and to let you edit any unconnected inputs.
*/
updateSidebar(){
//updates the sidebar to display information about this node
var valueList = this.initializeSideBar()
//Add options to set all of the inputs
this.inputs.forEach(input => {
if(input.type == 'input' && input.valueType != 'geometry' && input.connectors.length == 0){
if(input.valueType == 'number'){
this.createEditableValueListItem(valueList,input,'value', input.name, true)
}
else{
this.createEditableValueListItem(valueList,input,'value', input.name, false)
}
}
})
//Add a delete button if we are using the touch interface
if(GlobalVariables.touchInterface){
this.createButton(valueList,this,"Delete",()=>{this.deleteNode()})
}
return valueList
}
/**
* Initialized the sidebar with a title and create the HTML object.
*/
initializeSideBar(){
//remove everything in the sideBar now
let sideBar = document.querySelector('.sideBar')
//Updates sidebar values before erasing
var editables = document.querySelectorAll(".editing-item")
editables.forEach(function(value) {
value.blur()
})
while (sideBar.firstChild) {
sideBar.removeChild(sideBar.firstChild)
}
//adds the name of the molecule to sideBar
var name2 = document.createElement('p')
name2.textContent = this.name
sideBar.appendChild(name2)
name2.setAttribute('class','molecule_title')
//adds the name of the molecule to sideBar
var description = document.createElement('p')
description.textContent = this.description
sideBar.appendChild(description)
description.setAttribute('class','atom_description')
//add the name as of project title
if (this.atomType == 'Molecule' ){
let headerBar_title = document.querySelector('#headerBar_title')
if(headerBar_title){
while (headerBar_title.firstChild) {
headerBar_title.removeChild(headerBar_title.firstChild)
}
var name1 = document.createElement('p')
name1.textContent = "- " + GlobalVariables.topLevelMolecule.name
headerBar_title.appendChild(name1)
}
}
//Create a list element
var valueList = document.createElement('ul')
sideBar.appendChild(valueList)
valueList.setAttribute('class', 'sidebar-list')
return valueList
}
/**
* Delete this atom. Silent prevents it from telling its neighbors
*/
deleteNode(backgroundClickAfter = true, deletePath = true, silent = false){
//deletes this node and all of it's inputs
this.inputs.forEach(input => { //disable the inputs before deleting
input.ready = false
})
const inputsCopy = [...this.inputs]//Make a copy of the inputs list to delete all of them
inputsCopy.forEach(input => {
input.deleteSelf(silent)
})
if(this.output){
this.output.deleteSelf(silent)
}
this.parent.nodesOnTheScreen.splice(this.parent.nodesOnTheScreen.indexOf(this),1) //remove this node from the list
if(deletePath){
this.basicThreadValueProcessing({op: "deletePath", path: this.path }) //Delete the cached geometry
}
if(backgroundClickAfter){
GlobalVariables.currentMolecule.backgroundClick()
}
}
/**
* Runs with each frame to draw the atom.
*/
update() {
this.inputs.forEach(child => {
child.update()
})
if(this.output){
this.output.update()
}
this.draw()
}
/**
* Create an object containing the information about this atom that we want to save.
*/
serialize(offset = {x: 0, y: 0}){
//Offsets are used to make copy and pasted atoms move over a little bit
var ioValues = []
this.inputs.forEach(io => {
if (typeof io.getValue() == 'number' || typeof io.getValue() == 'string'){
var saveIO = {
name: io.name,
ioValue: io.getValue()
}
ioValues.push(saveIO)
}
})
var object = {
atomType: this.atomType,
name: this.name,
x: this.x + offset.x,
y: this.y - offset.y,
uniqueID: this.uniqueID,
ioValues: ioValues
}
return object
}
/**
* Return any contribution from this atom to the README file
*/
requestReadme(){
//request any contributions from this atom to the readme
return []
}
/**
* Set's the output value and shows the atom output on the 3D view.
*/
decreaseToProcessCountByOne(){
GlobalVariables.topLevelMolecule.census()
}
/**
* Token update value function to give each atom one by default
*/
updateValue(){
}
/**
* Used to walk back out the tree generating a list of constants...used for evolve
*/
walkBackForConstants(callback){
//Pass the call further up the chain
this.inputs.forEach(input => {
input.connectors.forEach(connector => {
connector.walkBackForConstants(callback)
})
})
}
/**
* Displays the atom in 3D and sets the output.
*/
displayAndPropagate(){
//If this has an output write to it
if(this.output){
this.output.setValue(this.path)
this.output.ready = true
}
//If this atom is selected, send the updated value to the renderer
if (this.selected){
this.sendToRender()
}
}
/**
* Sets the atom to wait on coming information. Basically a pass through, but used for molecules
*/
waitOnComingInformation(){
if(this.output){
this.output.waitOnComingInformation()
}
if(this.processing){
console.warn("Processing "+ this.name + " Canceled")
this.cancelProcessing()
this.processing = false
}
}
/**
* Calls a worker thread to compute the atom's value.
*/
basicThreadValueProcessing(toAsk){
//If the inputs are all ready
var go = true
this.inputs.forEach(input => {
if(!input.ready){
go = false
}
})
if(go){ //Then we update the value
this.waitOnComingInformation() //This sends a chain command through the tree to lock all the inputs which are down stream of this one. It also cancels anything processing if this atom was doing a calculation already.
this.processing = true
this.decreaseToProcessCountByOne()
this.clearAlert()
const {answer, terminate} = window.ask(toAsk)
answer.then(result => {
if (result != -1 ){
this.displayAndPropagate()
}else{
this.setAlert("Unable to compute")
}
this.processing = false
})
this.cancelProcessing = terminate //This can be called to interrupt the computation
}
}
/**
* Starts propagation placeholder. Most atom types do not begin propagation.
*/
beginPropagation(){
}
/**
* Returns an array of length two indicating that this is one atom and if it is waiting to be computed
*/
census(){
var waiting = 0
this.inputs.forEach(input => {
if(input.ready != true){
waiting = 1
}
})
return [1,waiting]
}
/**
* Sets all the input and output values to match their associated atoms.
*/
loadTree(){
this.inputs.forEach(input => {
input.loadTree()
})
if(this.output){
this.output.value = this.path
}
return this.path
}
/**
* Send the value of this atom to the 3D display.
*/
sendToRender(){
//Send code to JSxCAD to render
try{
GlobalVariables.writeToDisplay(this.path)
}
catch(err){
this.setAlert(err)
}
}
/**
* Find the value of an input for with a given name.
* @param {string} ioName - The name of the target attachment point.
*/
findIOValue(ioName){
ioName = ioName.split('~').join('')
var ioValue = null
this.inputs.forEach(child => {
if(child.name == ioName && child.type == 'input'){
ioValue = child.getValue()
}
})
return ioValue
}
/**
* Dump the stored copies of any geometry in this atom to free up ram....probably can be deleted
*/
// dumpBuffer(){
// this.inputs.forEach(input => {
// input.dumpBuffer()
// })
// if(this.output){
// this.output.dumpBuffer()
// }
// this.value = null
// }
/**
* Creates an editable HTML item to set the value of an object element. Used in the sidebar.
* @param {object} list - The HTML object to attach the new item to.
* @param {object} object - The object with the element we are editing.
* @param {string} key - The key of the element to edit.
* @param {string} label - The label to display next to the editable value.
* @param {boolean} resultShouldBeNumber - A flag to indicate if the input should be converted to a number.
* @param {object} callBack - Optional. A function to call with the new value when the value changes.
*/
createEditableValueListItem(list,object,key, label, resultShouldBeNumber, callBack = () => console.warn("no callback")){
var listElement = document.createElement('LI')
list.appendChild(listElement)
//Div which contains the entire element
var div = document.createElement('div')
listElement.appendChild(div)
div.setAttribute('class', 'sidebar-item sidebar-editable-div')
//Left div which displays the label
var labelDiv = document.createElement('label')
div.appendChild(labelDiv)
var labelText = document.createTextNode(label + ':')
labelDiv.appendChild(labelText)
labelDiv.setAttribute('class', 'sidebar-subitem label-item')
//Right div which is editable and displays the value
var valueTextDiv = document.createElement('span')
labelDiv.appendChild(valueTextDiv)
var valueText = document.createTextNode(object[key])
valueTextDiv.appendChild(valueText)
valueTextDiv.setAttribute('contenteditable', 'true')
valueTextDiv.setAttribute('class', 'editing-item')
var thisID = label+GlobalVariables.generateUniqueID()
valueTextDiv.setAttribute('id', thisID)
document.getElementById(thisID).addEventListener('focusout',() =>{
var valueInBox = document.getElementById(thisID).textContent.trim()
if(resultShouldBeNumber){
valueInBox = GlobalVariables.limitedEvaluate(valueInBox)
}
//If the target is an attachmentPoint then call the setter function
if(object instanceof AttachmentPoint){
object.setValue(valueInBox)
}
else{
object[key] = valueInBox
callBack(valueInBox)
}
})
//prevent the return key from being used when editing a value
document.getElementById(thisID).addEventListener('keypress', function(evt) {
if (evt.which === 13) {
evt.preventDefault()
document.getElementById(thisID).blur() //shift focus away if someone presses enter
}
})
}
/**
* Creates an non-editable HTML item to set the value of an object element. Used in the sidebar.
* @param {object} list - The HTML object to attach the new item to.
* @param {object} object - The object with the element we are displaying.
* @param {string} key - The key of the element to display.
* @param {string} label - The label to display next to the displayed value.
*/
createNonEditableValueListItem(list,object,key, label){
var listElement = document.createElement('LI')
list.appendChild(listElement)
//Div which contains the entire element
var div = document.createElement('div')
listElement.appendChild(div)
div.setAttribute('class', 'sidebar-item sidebar-editable-div')
//Left div which displays the label
var labelDiv = document.createElement('div')
div.appendChild(labelDiv)
var labelText = document.createTextNode(label + ':')
labelDiv.appendChild(labelText)
labelDiv.setAttribute('class', 'sidebar-subitem label-item')
//Right div which is editable and displays the value
var valueTextDiv = document.createElement('div')
div.appendChild(valueTextDiv)
var valueText = document.createTextNode(object[key])
valueTextDiv.appendChild(valueText)
valueTextDiv.setAttribute('contenteditable', 'false')
valueTextDiv.setAttribute('class', 'sidebar-subitem noediting-item')
var thisID = label+GlobalVariables.generateUniqueID()
valueTextDiv.setAttribute('id', thisID)
}
/**
* Creates a html representation of the passed text. Used in the sidebar.
* @param {object} list - The HTML object to attach the new item to.
* @param {string} texxt - The text used to generate the markdown html.
*/
createMarkdownListItem(list, text){
var converter = new showdown.Converter()
//var text = '# hello, markdown!'
var html = converter.makeHtml(text)
var markdownTextDiv = document.createElement('div')
markdownTextDiv.innerHTML = html
//var valueText = document.createTextNode(text)
//valueTextDiv.appendChild(valueText)
list.appendChild(markdownTextDiv)
}
/**
* Creates dropdown with multiple options to select. Used in the sidebar.
* @param {object} list - The HTML object to attach the new item to.
* @param {object} parent - The parent which has the function to call on the change...this should really be done with a callback function.
* @param {array} options - A list of options to display in the drop down.
* @param {number} selectedOption - The zero referenced index of the selected option.
* @param {string} description - A description of what the dropdown does.
* @param {object} Callback function
*/
createDropDown(list,parent,options,selectedOption, description, callback){
var listElement = document.createElement('LI')
list.appendChild(listElement)
//Div which contains the entire element
var div = document.createElement('div')
listElement.appendChild(div)
div.setAttribute('class', 'sidebar-item')
//Left div which displays the label
var labelDiv = document.createElement('div')
div.appendChild(labelDiv)
var labelText = document.createTextNode(description)
labelDiv.appendChild(labelText)
labelDiv.setAttribute('class', 'sidebar-subitem')
//Right div which is editable and displays the value
var valueTextDiv = document.createElement('div')
div.appendChild(valueTextDiv)
var dropDown = document.createElement('select')
options.forEach(option => {
var op = new Option()
op.value = options.findIndex(thisOption => thisOption === option)
op.text = option
dropDown.options.add(op)
})
valueTextDiv.appendChild(dropDown)
valueTextDiv.setAttribute('class', 'sidebar-subitem')
dropDown.selectedIndex = selectedOption //display the current selection
dropDown.addEventListener(
'change',
function() { callback(dropDown.value) },
false
)
}
/**
* Creates button. Used in the sidebar.
* @param {object} list - The HTML object to attach the new item to.
* @param {object} parent - The parent which has the function to call on the change...this should really be done with a callback function.
* @param {string} buttonText - The text on the button.
* @param {object} functionToCall - The function to call when the button is pressed.
*/
createButton(list,parent,buttonText,functionToCall){
var listElement = document.createElement('LI')
list.appendChild(listElement)
//Div which contains the entire element
var div = document.createElement('div')
listElement.appendChild(div)
div.setAttribute('class', 'runSideBarDiv')
//Right div which is button
var valueTextDiv = document.createElement('div')
div.appendChild(valueTextDiv)
var button = document.createElement('button')
var buttonTextNode = document.createTextNode(buttonText)
button.setAttribute('class', ' browseButton')
button.setAttribute('id', buttonText.replace(/\s+/g, "") + "-button")
button.appendChild(buttonTextNode)
valueTextDiv.appendChild(button)
valueTextDiv.setAttribute('class', 'sidebar-subitem')
button.addEventListener(
'mousedown',
function() { functionToCall() } ,
false
)
}
/**
* Creates file upload button. Used in the sidebar.
* @param {object} list - The HTML object to attach the new item to.
* @param {object} parent - The parent which has the function to call on the change...this should really be done with a callback function.
* @param {string} buttonText - The text on the button.
* @param {object} functionToCall - The function to call when the button is pressed.
*/
createFileUpload(list,parent,buttonText,functionToCall){
var listElement = document.createElement('LI')
list.appendChild(listElement)
//Div which contains the entire element
var div = document.createElement('div')
listElement.appendChild(div)
div.setAttribute('class', 'runSideBarDiv')
//Right div which is button
var valueTextDiv = document.createElement('div')
div.appendChild(valueTextDiv)
var button = document.createElement('input')
button.type = "file"
var buttonTextNode = document.createTextNode(buttonText)
button.setAttribute('class', ' browseButton')
button.setAttribute('id', buttonText.replace(/\s+/g, "") + "-button")
button.appendChild(buttonTextNode)
valueTextDiv.appendChild(button)
valueTextDiv.setAttribute('class', 'sidebar-subitem')
button.addEventListener(
'change',
functionToCall,
false
)
}
/**
* Creates button. Used in the sidebar.
* @param {object} list - The HTML object to attach the new item to.
* @param {string} buttonText - The text on the button.
* @param {boolean} - Flag to see if checkbox is checked
* @param {object} functionToCall - The function to call when the button is pressed.
*/
createCheckbox(sideBar,text,isChecked,callback){
var gridDiv = document.createElement('div')
sideBar.appendChild(gridDiv)
gridDiv.setAttribute('id', text + "-parent")
gridDiv.setAttribute('class', "sidebar-checkbox")
var gridCheck = document.createElement('input')
gridDiv.appendChild(gridCheck)
gridCheck.setAttribute('type', 'checkbox')
gridCheck.setAttribute('id', text)
if (isChecked){
gridCheck.setAttribute('checked', 'true')
}
var gridCheckLabel = document.createElement('label')
gridDiv.appendChild(gridCheckLabel)
gridCheckLabel.setAttribute('for', 'gridCheck')
gridCheckLabel.setAttribute('style', 'margin-right:1em;')
gridCheckLabel.textContent = text
gridCheck.addEventListener('change', event => {
callback(event)
})
}
}