UNPKG

parambox

Version:

Too much parameters to handle ? paramBox is a collection of smart plug and play tools to facilitate the design of javascript projects such as games and cognitive tasks. ParamBox is one of the helper class and allows to modify key variable on the fly witho

611 lines (493 loc) 17.6 kB
class DragBox { constructor (boxElement = null) { // constants this.MAX_BINDED_PROPERTIES = 15; this.INIT_WIDTH = 400; this.INIT_HEIGHT = 300; this.DEFAULT_BOX_CLASS = "dragbox"; this.DEFAULT_DRAGGABLE = true; this.DEFAULT_STICKINESS_TYPE = "magnetized"; // ui variables this.boxElement = boxElement; this.draggable = this.DEFAULT_DRAGGABLE; this.boxHTML = null; // stickness this.shouldStick = null; this.shouldMagnetize = null; this.isStickingX = null; this.isStickingY = null; this._stickiness = this.DEFAULT_STICKINESS_TYPE; this.stickiness = this._stickiness; // private this._beingDragged = false; this._visibility = "hidden"; // keyboard variables // maps the keys pressed with either true on kedown or false on keyup this.map = []; // keep mouse position at all times this.currentMousePos = { x: -1, y: -1 }; var thisObject = this; $(document).mousemove(function(event) { thisObject.currentMousePos.x = event.pageX; thisObject.currentMousePos.y = event.pageY; }); // check if the box already exists, else create it if (!this.boxElement) { $("#paramBox").remove(); // html for creation this.boxHTML = '<div id="dragbox" class="'+this.DEFAULT_BOX_CLASS+'" style="visibility:hidden;" draggable="true">' + '<div class="col-xs-12 dragbox-title"><center><h3>Dragbox</h3></center></div>' + '<div class="col-xs-12 dragbox-content"></div>' + '</div>'; $(document.body).append(this.boxHTML); this.boxElement = $("#dragbox"); } // keyboard show hide hotkeys events $(document.body).keydown(function(e) { thisObject.keyfunction(e); }); $(document.body).keyup(function(e) { thisObject.keyfunction(e); }); $(this.boxElement).find(".dragbox-title").mousedown(function(e) { thisObject.startDrag(e); }); $(this.boxElement).find(".dragbox-title").mouseup(function(e) { thisObject.stopDrag(e); }); // draggin cleanUp event $(document).click(function(e) { thisObject.stopDrag(e); }); } // drag methods startDrag(e) { // prevent classic dragging from happening e.preventDefault(); // check if already being dragged, stop the dragging if so if (this.beingDragged=="true") { this.beingDragged = false; return; } // calculate X and Y offset of the mouse compare to the top left corner of the box var offset = {x: e.clientX-$(this.boxElement).offset().left, y: e.clientY-$(this.boxElement).offset().top}; // set the beingdragged flag to true this.beingDragged = true; // start the update loop for smooth dragging this.loopDrag(offset); // another way of preventing default, just in case return false; } stopDrag() { this.beingDragged = false; } loopDrag(offset) { if (this.beingDragged==true) { var newPosX = this.currentMousePos.x-offset.x; var newPosY = this.currentMousePos.y-offset.y; var element = { offsetLeft: newPosX, offsetTop: newPosY, offsetWidth: $(this.boxElement).width(), offsetHeigth: $(this.boxElement).height() }; // maintain box totally visible and check for sticky borders var constrainedPosition = ParamBox.stayInWindow(element); var constrainedPositionX = constrainedPosition.x; var constrainedPositionY = constrainedPosition.y; // stickiness // glue if (this.shouldStick) { //make the box sticky if collided with x border if ((constrainedPosition.stickyX == -2)||(constrainedPosition.stickyX == 2)) { this.isStickingX = true; } // for sticky window, check if the box got out of the sticky x aera before authorizing movement in x if ((this.isStickingX)&&(constrainedPosition.stickyX!=0)) { constrainedPositionX = constrainedPosition.leftSticky; //stick to the window } // make sure stickiness disapears when out of the sticky zone if (constrainedPosition.stickyX==0) { this.isStickingX = false; } // make the box sticky if collided with y border if ((constrainedPosition.stickyY == -2)||(constrainedPosition.stickyY == 2)) { this.isStickingY = true; } // for sticky window, check if the box got out of the sticky y aera before authorizing movement in y if ((this.isStickingY)&&(constrainedPosition.stickyY!=0)) { constrainedPositionY = constrainedPosition.topSticky; //stick to the window } // make sure stickiness disapears when out of the sticky zone if (constrainedPosition.stickyY==0) { this.isStickingY = false; } } // magnet if (this.shouldMagnetize) { constrainedPositionX = constrainedPosition.leftSticky; constrainedPositionY = constrainedPosition.topSticky; } var thisObject = this; $(this.boxElement).animate({ left: constrainedPositionX, top: constrainedPositionY }, 25, function() { thisObject.loopDrag(offset); }); return false; } } // keyboard functions keyfunction (e) { // check if shift + P hotkeys were stroke and toggle visibility if so this.map[e.keyCode] = e.type == 'keydown'; // hide and show parameter box if ((this.map[16])&&(this.map[80])) { // 16 == Shift - 80 == P //make sure to reset value in case keyup event is ignored (keep shift true for rapid toggle) this.map[80] = false // toggle box visibility this.toggle(); // prevent default action if any e.preventDefault(); } } // visibility functions toggle () { // toggle box visibility if (this.visibility == "hidden") { this.visibility = "visible"; } else { this.visibility = "hidden"; } } show() { this.visibility = "visible"; } hide() { this.visibility = "hidden"; } // setters getters set beingDragged(dragged) { this._beingDragged = dragged; $(this.boxElement).attr("beingDragged", dragged); } get beingDragged() { return(this._beingDragged); } set visibility(visibility) { this._visibility = visibility; $(this.boxElement).css("visibility", visibility); } get visibility () { return(this._visibility); } set stickiness (type) { // different type of stickyness for the box : "none", "glue", "magnetized" switch(type) { case "none": this.shouldMagnetize = false; this.shouldStick = false; console.log("stickiness set to none"); break; case "glue": this.shouldStick = true; this.shouldMagnetize = false; console.log("stickiness set to glue"); break; case "magnetized": this.shouldStick = false; this.shouldMagnetize = true; console.log("stickiness set to magnetized"); break; default: throw new Error("this sticky type does not exist. Types are : none, glue, or magnetized."); } this._stickiness = type } set title(html) { if (this.boxElement) { $(this.boxElement).find(".dragbox-title").html(html); } } // some static helper functions static stayInWindow(element) { if (typeof element === 'undefined') { throw new Error("element is undefined"); } // constants var STOP_BEING_STICKY_AFTER = 0.15; // times the size in distance // coordinates var left = element.offsetLeft; var right = element.offsetLeft + element.offsetWidth; var top = element.offsetTop; var bottom = element.offsetTop + element.offsetHeigth; var maxLeft = window.innerWidth - element.offsetWidth; var maxTop = window.innerHeight - element.offsetHeigth; return({ x: (left < 0) ? 0 : (right > window.innerWidth) ? maxLeft : left, y: (top < 0) ? 0 : (bottom > window.innerHeight) ? maxTop : top, stickyX: (left <= 0) ? -2 : (left <= STOP_BEING_STICKY_AFTER*element.offsetWidth) ? -1 : (right >= window.innerWidth) ? 2 : (right >= window.innerWidth-STOP_BEING_STICKY_AFTER*element.offsetWidth) ? 1 : 0, stickyY: (top <= 0) ? -2 : (top <= STOP_BEING_STICKY_AFTER*element.offsetHeigth) ? -1 : (bottom >= window.innerHeight) ? 2 : (bottom >= window.innerHeight-STOP_BEING_STICKY_AFTER*element.offsetHeigth) ? 1 : 0, leftSticky: (left <= 0) ? 0 : (left <= STOP_BEING_STICKY_AFTER*element.offsetWidth) ? 0 : (right >= window.innerWidth) ? window.innerWidth-element.offsetWidth : (right >= window.innerWidth-STOP_BEING_STICKY_AFTER*element.offsetWidth) ? window.innerWidth-element.offsetWidth : left, topSticky: (top <= 0) ? 0 : (top <= STOP_BEING_STICKY_AFTER*element.offsetHeigth) ? 0 : (bottom >= window.innerHeight) ? window.innerHeight-element.offsetHeigth : (bottom >= window.innerHeight-STOP_BEING_STICKY_AFTER*element.offsetHeigth) ? window.innerHeight-element.offsetHeigth : top }); } } class ParamBox extends DragBox { constructor (boxElement = null) { // call super constructor super (boxElement); // constants this.DEFAULT_ROW_HTML = '<div class="col-md-12 dragbox-row paramboxtmprow"></div>' // ui this.rowHtml = this.DEFAULT_ROW_HTML; // row hold the row object in dom as well as the bindedField object {rowDom: row, bindedField: bindedField} this.rows = [] // set dragbox title this.title = '<h5><i class="fa fa-cog fa-1x"></i> Parameter Box</h5>'; } // binding methods bind(object, properties, constraints = null) { if (typeof object == 'undefined') { throw new Error("object is undefined") } if (properties.constructor === Array) { for (var i = 0; i < properties.length; i++) { if (typeof object[properties[i]] == 'undefined') { throw new Error("object property " + properties[i] + " is undefined"); } var rowDom = this.newRowInDom(); var bindedField = null; // look for a constrained field if (constraints != null) { if (typeof constraints[properties[i]] != 'undefined') { var bindedField = new BindedField(object, properties[i], rowDom, 'selector', constraints[properties[i]]); } } // if not constrained field found, create the most relevant type of field if (!bindedField) { if (object[properties[i]].constructor === Boolean) { var bindedField = new BindedField(object, properties[i], rowDom, 'selector', ["TRUE", "FALSE"]); } else { var bindedField = new BindedField(object, properties[i], rowDom); } } this.rows.push(this.getBindedRow(rowDom, bindedField)); } } else { if (typeof object[properties] == 'undefined') { throw new Error("object property " + properties + " is undefined"); } var rowDom = this.newRowInDom(); var bindedField = null; // look for a constrained field if (constraints != null) { if (typeof constraints[properties] != 'undefined') { var bindedField = new BindedField(object, properties, rowDom, 'selector', constraints[properties]); } } // if not constrained field found, create the most relevant type of field if (!bindedField) { if (object[property].constructor === Boolean) { var bindedField = new BindedField(object, properties, rowDom, 'selector', ["TRUE", "FALSE"]); } else { var bindedField = new BindedField(object, properties, rowDom); } } this.rows.push(this.getBindedRow(rowDom, bindedField)); } } unbind(object, property) { for(var i = 0; i < this.rows.length; i++) { var bindedField = this.rows[i].bindedField; if((bindedField.object === object)&&(bindedField.property == property)) { this.rows.splice(i, 1); bindedField.delete() } } } // ui methods newRowInDom () { var row = null; $(this.boxElement).find(".dragbox-content").append(this.rowHtml); row = this.boxElement.find(".paramboxtmprow"); $(row).removeClass("paramboxtmprow"); return row; } getBindedRow (rowDom, bindedField) { return({rowDom: rowDom, bindedField: bindedField}); } refreshView() { // check if all binded field are displayed in the paramBox // if not add them // get rid of unbinded field } } class BindedProperty { constructor (object = null, property = null) { // constants this.HANDLED_VARIABLE_TYPES = ["number", "string", "boolean"]; // data properties this.property = property; this.object = object; this.propagate = false; // to add ? chain propagation? a subscription system maybe... this.type = null; if (!this.object) { // if parent object is not set consider that the binding is with a variable in the global scope this.object = window; } if (property) { this.bind(object, property); } } // binding function bind(object, property) { var propertyType = typeof this.object[this.property]; if (propertyType === 'undefined') { throw new Error("The variable you are trying to bind is undefined - either this object or the property is incorrect"); } else { if (this.HANDLED_VARIABLE_TYPES.indexOf(propertyType) == -1) { throw new Error("The variable you are trying to bind is of a non-handled type (string, number or boolean"); } this.property = property; this.object = object; this.type = this.object[this.property].constructor; } } convertToType (value) { if (this.type) { if (this.type === Boolean) { switch(String(value).toUpperCase()) { case "0": return(false); break; case "1": return(true); break; case "FALSE": return(false); break; case "TRUE": return(true); break; } } return(this.type(value)); } else { throw new Error("You are trying to convert a value to a the type of the binded property but the object has no property binded to it (or no type)"); } } // getters and setters set value(value) { if (typeof this.object[this.property] === 'undefined') { throw new Error("The variable you are trying to bind is undefined - either this object or the property is incorrect"); } else { this.object[this.property] = this.convertToType(value); } } get value() { if (typeof this.object[this.property] === 'undefined') { throw new Error("The variable you are trying to bind is undefined - either this object or the property is incorrect"); } else { return(this.object[this.property]); } } } class BindedField extends BindedProperty { // this class holds an active input field (select, text input, slider component) // it creates a field from the selected type and bind a binded property to it constructor(object = mandatory("object"), property = mandatory("property"), parent = null, fieldType = 'input', allowedValues = null, addClass = null) { super(object, property); // constant this.VALID_FIELD_TYPE = ["input", "selector", "slider"]; // field this.field = null; this.fieldType = fieldType; this.fieldHTML = null; this.allowedValues = allowedValues; this.tempClass = "binded-"+typeof object+property; // parent this.parent = parent; // build the field html switch(this.fieldType) { case 'input': this.fieldHTML = '<fieldset class="form-group">' + '<label>'+property+'</label>' + '<input type="text" class="form-control '+ this.tempClass +'" data-binded="'+property+'">' + '</fieldset>'; break; case 'selector': if (!allowedValues) { throw new Error("fieldType selector needs at least one allowedValues"); } this.fieldHTML = '<fieldset class="form-group">' + '<label>'+property+'</label>' + '<select class="form-control '+ this.tempClass +'" data-binded="'+property+'">'; for (var i = 0; i < this.allowedValues.length; i++) { this.fieldHTML = this.fieldHTML + '<option value="'+this.allowedValues[i]+'">'+this.allowedValues[i]+'</option>'; } this.fieldHTML = this.fieldHTML + '</select></fieldset>'; break; case 'slider': break; default: throw new Error("fieldType is invalid : input, selector and slider are the only valid type for now"); } if (parent) { this.placeInParent(); } } // ui function placeInParent (parent = null) { if (parent) { this.parent = parent; } $(this.parent).append(this.fieldHTML); this.field = $("."+this.tempClass); this.field.removeClass(this.tempClass); (this.allowedValues) ? (this.allowedValues.constructor == Array) ? this.field.val(this.allowedValues[0]) : this.field.val(this.value) : this.field.val(this.value); var thisObject = this; // add event listener on change this.field.change(function(e) { thisObject.update("field"); }); this.field.keyup(function(e) { thisObject.update("field"); }); } delete () { //delete the fieldset this.field.parent().remove(); this.property = null; this.object = null; } update(origin = "field") { if (origin == "field") { this.value = $(this.field).val(); } else { if ($(this.field).val().toUpperCase()!=String(this.value).toUpperCase()) { $(this.field).val(this.value); } } } // getters and setters set value(value) { if (typeof this.object[this.property] === 'undefined') { throw new Error("The variable you are trying to bind is undefined - either this object or the property is incorrect"); } else { this.object[this.property] = this.convertToType(value); this.update("setter"); } } get value() { if (typeof this.object[this.property] === 'undefined') { throw new Error("The variable you are trying to bind is undefined - either this object or the property is incorrect"); } else { return(this.object[this.property]); } } } // utilities function mandatory(param = "") { throw new Error('Missing parameter ' + param); }