Code Context
We are trying to make our own custom data binding function work. So far, so good, as we got it working, but now we are refactoring.
Problem
I am trying to make the constructing of the function a little more user friendly. Currently to invoke it, it looks like this:
var obj = new MyBind(document.querySelectorAll('[data-bind="total"]'), 1234);
Error: Any elements bound are not being updated.
Ideally, I want to be able to pass a single parameter. But for this first round of refactoring, I want to simply pass the element name. Like this:
var obj = new MyBind("total", 1234);
Failed Efforts
I have tried the following without success:
// inside my function
this.elements = document.querySelectorAll(`[data-bind="${elements}"]`);
Live Demo
https://jsbin.com/qakutudole/edit?html,js,output
Anyone see why this isn't working?
Source Code
Here is the entire function:
function MyBind(elements, data) {
this.data = data;
this.elements = elements;
//this.elements = document.querySelectorAll(`[data-bind="${elements}"]`);
for( var i = 0; i < elements.length; i++ ) {
if(elements[i].tagName == 'INPUT' || elements[i].tagName == 'SELECT' || elements[i].tagName == 'TEXTAREA' ) {
elements[i].value = data;
} else if( elements[i].tagName == 'DIV' || elements[i].tagName == 'SPAN' || elements[i].tagName == 'B' ) {
elements[i].innerHTML = data;
}
elements[i].addEventListener("change", this, false);
}
}
MyBind.prototype.handleEvent = function(event) {
switch (event.type) {
case "change": this.change(event.target.value);
}
};
MyBind.prototype.change = function(value, updateAmounts) {
updateAmounts = typeof updateAmounts !== 'undefined' ? updateAmounts : true;
this.data = value;
for( var i = 0; i < this.elements.length; i++ ) {
if(this.elements[i].tagName == 'INPUT' || this.elements[i].tagName == 'SELECT' || this.elements[i].tagName == 'TEXTAREA' ) {
this.elements[i].value = value;
} else if( this.elements[i].tagName == 'DIV' || this.elements[i].tagName == 'SPAN' || this.elements[i].tagName == 'B' ) {
this.elements[i].innerHTML = value;
}
}
if( updateAmounts === true ) {
alert("Updating amounts!");
}
};
var obj = new MyBind(document.querySelectorAll('[data-bind="invoice_total"]'), 1234);
//var obj = new MyBind("invoice_total", 1234);
You will want to change
function MyBind(selector, data) {
// ^^^^^^^^
const elements = document.querySelectorAll(`[data-bind="${selector}"]`);
// ^^^^^^^^^^^^^^ ^^^^^^^^
this.data = data;
this.elements = elements;
so that the elements variable that you use subsequently in the code actually holds the elements, instead of being a string.
Related
I am using tableToJson, which you may or may not be familiar with, to convert a table to JSON (Ive included the code for it here if you don't know it). Simple enough. The output is entirely as expected and looks like:
{"data":[
{"Time":"6:30am - 8:30am","Monday":"","Tuesday":"Maths","Wednesday":"","Thursday":"","Friday":""},
{"Time":"11:15am - 12:45pm","Monday":"Maths","Tuesday":"","Wednesday":"English","Thursday":"","Friday":""},
{"Time":"3:00pm - 5:00pm","Monday":"","Tuesday":"","Wednesday":"English","Thursday":"","Friday":""},
{"Time":"5:00pm - 7:00pm","Monday":"","Tuesday":"Science","Wednesday":"","Thursday":"","Friday":""},
{"Time":"7:00pm - 9:00pm","Monday":"","Tuesday":"Science","Wednesday":"","Thursday":"","Friday":""}]
}
However, I need to expand on it to take the #ids of each <tr> so the JSON looks like: (Assuming the table HTML is <tr id="session 1"></tr> etc.)
{
"data":{
"session 1":{"Time":"6:30am - 8:30am","Monday":"","Tuesday":"sdfsdf","Wednesday":"","Thursday":"","Friday":""},
"session 2":{"Time":"11:15am - 12:45pm","Monday":"sdfsdfsdf","Tuesday":"","Wednesday":"","Thursday":"","Friday":""},
"session 3":{"Time":"3:00pm - 5:00pm","Monday":"","Tuesday":"","Wednesday":"","Thursday":"","Friday":""},
"session 4":{"Time":"5:00pm - 7:00pm","Monday":"","Tuesday":"","Wednesday":"","Thursday":"","Friday":""},
"session 5":{"Time":"7:00pm - 9:00pm","Monday":"","Tuesday":"","Wednesday":"","Thursday":"","Friday":""}
}
}
For reference The code for tableToJson looks like:
(function( $ ) {
'use strict';
$.fn.tableToJSON = function(opts) {
// Set options
var defaults = {
ignoreColumns: [],
onlyColumns: null,
ignoreHiddenRows: true,
ignoreEmptyRows: false,
headings: null,
allowHTML: false,
includeRowId: false,
textDataOverride: 'data-override',
textExtractor: null
};
opts = $.extend(defaults, opts);
var notNull = function(value) {
return value !== undefined && value !== null;
};
var ignoredColumn = function(index) {
if( notNull(opts.onlyColumns) ) {
return $.inArray(index, opts.onlyColumns) === -1;
}
return $.inArray(index, opts.ignoreColumns) !== -1;
};
var arraysToHash = function(keys, values) {
var result = {},index=0, name = 'test';
$.each(values, function(i, value) {
// when ignoring columns, the header option still starts
// with the first defined column
if ( index < keys.length && notNull(value) ) {
result[ keys[index] ] = value;
index++;
}
});
return result;
};
var cellValues = function(cellIndex, cell, isHeader) {
var $cell = $(cell),
// textExtractor
extractor = opts.textExtractor,
override = $cell.attr(opts.textDataOverride);
// don't use extractor for header cells
if ( extractor === null || isHeader ) {
return $.trim( override || ( opts.allowHTML ? $cell.html() : cell.textContent || $cell.text() ) || '' );
} else {
// overall extractor function
if ( $.isFunction(extractor) ) {
return $.trim( override || extractor(cellIndex, $cell) );
} else if ( typeof extractor === 'object' && $.isFunction( extractor[cellIndex] ) ) {
return $.trim( override || extractor[cellIndex](cellIndex, $cell) );
}
}
// fallback
return $.trim( override || ( opts.allowHTML ? $cell.html() : cell.textContent || $cell.text() ) || '' );
};
var rowValues = function(row, isHeader) {
var result = [];
var includeRowId = opts.includeRowId;
var useRowId = (typeof includeRowId === 'boolean') ? includeRowId : (typeof includeRowId === 'string') ? true : false;
var rowIdName = (typeof includeRowId === 'string') === true ? includeRowId : 'rowId';
if (useRowId) {
if (typeof $(row).attr('id') === 'undefined') {
result.push(rowIdName);
}
}
$(row).children('td,th').each(function(cellIndex, cell) {
result.push( cellValues(cellIndex, cell, isHeader) );
});
return result;
};
var getHeadings = function(table) {
var firstRow = table.find('tr:first').first();
return notNull(opts.headings) ? opts.headings : rowValues(firstRow, true);
};
var construct = function(table, headings) {
var i, j, len, len2, txt, $row, $cell,
tmpArray = [], cellIndex = 0, result = [];
table.children('tbody,*').children('tr').each(function(rowIndex, row) {
if( rowIndex > 0 || notNull(opts.headings) ) {
var includeRowId = opts.includeRowId;
var useRowId = (typeof includeRowId === 'boolean') ? includeRowId : (typeof includeRowId === 'string') ? true : false;
$row = $(row);
var isEmpty = ($row.find('td').length === $row.find('td:empty').length) ? true : false;
if( ( $row.is(':visible') || !opts.ignoreHiddenRows ) && ( !isEmpty || !opts.ignoreEmptyRows ) && ( !$row.data('ignore') || $row.data('ignore') === 'false' ) ) {
cellIndex = 0;
if (!tmpArray[rowIndex]) {
tmpArray[rowIndex] = [];
}
if (useRowId) {
cellIndex = cellIndex + 1;
if (typeof $row.attr('id') !== 'undefined') {
tmpArray[rowIndex].push($row.attr('id'));
} else {
tmpArray[rowIndex].push('');
}
}
$row.children().each(function(){
$cell = $(this);
// skip column if already defined
while (tmpArray[rowIndex][cellIndex]) { cellIndex++; }
// process rowspans
if ($cell.filter('[rowspan]').length) {
len = parseInt( $cell.attr('rowspan'), 10) - 1;
txt = cellValues(cellIndex, $cell);
for (i = 1; i <= len; i++) {
if (!tmpArray[rowIndex + i]) { tmpArray[rowIndex + i] = []; }
tmpArray[rowIndex + i][cellIndex] = txt;
}
}
// process colspans
if ($cell.filter('[colspan]').length) {
len = parseInt( $cell.attr('colspan'), 10) - 1;
txt = cellValues(cellIndex, $cell);
for (i = 1; i <= len; i++) {
// cell has both col and row spans
if ($cell.filter('[rowspan]').length) {
len2 = parseInt( $cell.attr('rowspan'), 10);
for (j = 0; j < len2; j++) {
tmpArray[rowIndex + j][cellIndex + i] = txt;
}
} else {
tmpArray[rowIndex][cellIndex + i] = txt;
}
}
}
txt = tmpArray[rowIndex][cellIndex] || cellValues(cellIndex, $cell);
if (notNull(txt)) {
tmpArray[rowIndex][cellIndex] = txt;
}
cellIndex++;
});
}
}
});
$.each(tmpArray, function( i, row ){
if (notNull(row)) {
// remove ignoredColumns / add onlyColumns
var newRow = notNull(opts.onlyColumns) || opts.ignoreColumns.length ?
$.grep(row, function(v, index){ return !ignoredColumn(index); }) : row,
// remove ignoredColumns / add onlyColumns if headings is not defined
newHeadings = notNull(opts.headings) ? headings :
$.grep(headings, function(v, index){ return !ignoredColumn(index); });
txt = arraysToHash(newHeadings, newRow);
result[result.length] = txt;
}
});
return result;
};
// Run
var headings = getHeadings(this);
return construct(this, headings);
};
})( jQuery );
I'm probably missing something really simple, but honestly cannot see how to do this. Any help would be super appreciated. Thank you all in advance!
Please rethink why you'd need to change it. This already provides valid ordered content in a JSON array (accessible via foo.data[0], foo.data[1] etc, or iterable with for(var i; i<foo.data.length;i++){}).
What you want will give you something with no guaranteed order (properties order in an object is irrelevant in JSON) and goes against the flow).
Edit: Stupid API.
If you must conform to some obscure and evil standard, edit the data after it was created. You can't expect $.fn.tableToJSON not to conform to JSON & javascript standards...
var foo = // output from $.fn.tableToJSON
var out = {data:{}}; //declare an *object* instead of an array
for(var i = 0; i < foo.data.length; i++){
out.data['Session ' + (i + 1)] = foo.data[i];
}
//debug output
console.log(JSON.stringify(out))
Basically, I want to wait for an element to appear (e.g. dynamically loaded element). I want to know how I can edit it to support xpath,different DOM documents (e.g. dynamically loaded iframe) and also set a timeout value so that it will quit if after X number of seconds, the element is till not found, throw an error that I can handle.
waitUntilExists(xpath,document,5,function(){
// Element exists, if after 5 seconds and it doesn't exist handle it here.
})
original code to modify:
(function(){
var _waitUntilExists = {
pending_functions : [],
loop_and_call : function()
{
if(!_waitUntilExists.pending_functions.length){return}
for(var i=0;i<_waitUntilExists.pending_functions.length;i++)
{
var obj = _waitUntilExists.pending_functions[i];
var resolution = document.getElementById(obj.id);
if(obj.id == document){
resolution = document.body;
}
if(resolution){
var _f = obj.f;
_waitUntilExists.pending_functions.splice(i, 1)
if(obj.c == "itself"){obj.c = resolution}
_f.call(obj.c)
i--
}
}
},
global_interval : setInterval(function(){_waitUntilExists.loop_and_call()},5)
}
if(document.addEventListener){
document.addEventListener("DOMNodeInserted", _waitUntilExists.loop_and_call, false);
clearInterval(_waitUntilExists.global_interval);
}
window.waitUntilExists = function(id,the_function,context){
context = context || window
if(typeof id == "function"){context = the_function;the_function = id;id=document}
_waitUntilExists.pending_functions.push({f:the_function,id:id,c:context})
}
waitUntilExists.stop = function(id,f){
for(var i=0;i<_waitUntilExists.pending_functions.length;i++){
if(_waitUntilExists.pending_functions[i].id==id && (typeof f == "undefined" || _waitUntilExists.pending_functions[i].f == f))
{
_waitUntilExists.pending_functions.splice(i, 1)
}
}
}
waitUntilExists.stopAll = function(){
_waitUntilExists.pending_functions = []
}
})()
I'm making a code that removes a videoplayer from the page and then places it back when needed (even if the element doesn't have an id).
I'm finding issues with IE7
Here is my code:
var weboElem, weboElemPar, weboElemIndex, weboStored;
function weboRemoveVideoplayer(vpId){
weboElem = document.getElementById(vpId);
if(!weboElem) return false;
weboElemPar = weboElem.parentNode;
weboElemIndex = 0;
var child = weboElem;
while( (child = child.previousSibling) != null )
weboElemIndex++;
weboElemPar.removeChild(weboElem);
return true;
}
function weboPlaceVideoplayerBack(){
if(weboElemPar.insertBefore !== undefined && weboElemPar.childNodes !== undefined)
{
weboElemPar.insertBefore(weboElem, weboElemPar.childNodes[weboElemIndex]);
return true;
}
return false;
}
var result = document.evaluate(
'//*/param[contains(#value, "autoplay=1")]/..', // XPath expression
document, // context node
null, // namespace resolver
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE
);
if(result.snapshotLength > 0)
{
var node = result.snapshotItem(0);
node.id = "webo";
document.getElementById('info').innerHTML = node.nodeName.toLowerCase()+" -> "+node.id;
} else document.getElementById('info').innerHTML = "not found";
(Note that document.evaluate WORKS because I imported javascript-xpath library)
On IE7 if the XPath finds an IFRAME there are no problems and it works but if it finds an OBJECT does nothing and stops at weboElem = document.getElementById(vpId); as if it didn't find the id.
I tried modifying the code like this:
if(result.snapshotLength > 0)
{
var node = result.snapshotItem(0);
node.id = "webo";
node.parentNode.removeChild(node);
document.getElementById('info').innerHTML = node.nodeName.toLowerCase()+" -> "+node.id;
if(node.nodeName.toLowerCase() == "object") weboStored = node;
else weboStored = null;
} else document.getElementById('info').innerHTML = "not found";
and it works, the videoplayer disappears at page load. I want to use the function though, so I edited everything like this (storing the node into a global var that later I get in the weboRemoveVideoplayer function):
var weboElem, weboElemPar, weboElemIndex, weboStored;
function weboRemoveVideoplayer(vpId){
if(!weboStored) weboElem = document.getElementById(vpId);
else weboElem = weboStored;
if(!weboElem) return false;
weboElemPar = weboElem.parentNode;
weboElemIndex = 0;
var child = weboElem;
while( (child = child.previousSibling) != null )
weboElemIndex++;
weboElemPar.removeChild(weboElem);
alert("5");
return true;
}
function weboPlaceVideoplayerBack(){
if(weboElemPar.insertBefore !== undefined && weboElemPar.childNodes !== undefined)
{
weboElemPar.insertBefore(weboElem, weboElemPar.childNodes[weboElemIndex]);
return true;
}
return false;
}
// bind XPath methods to document and window objects
// NOTE: This will overwrite native XPath implementation if it exists
//XPathJS.bindDomLevel3XPath(); //solo per xpathJs
var result = document.evaluate(
'//*/param[contains(#value, "autoplay=1")]/..', // XPath expression
document, // context node
null, // namespace resolver
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE
);
if(result.snapshotLength > 0)
{
var node = result.snapshotItem(0);
node.id = "webo";
node.parentNode.removeChild(node);
document.getElementById('info').innerHTML = node.nodeName.toLowerCase()+" -> "+node.id;
if(node.nodeName.toLowerCase() == "object") weboStored = node;
else weboStored = null;
} else document.getElementById('info').innerHTML = "not found";
This way the code blocks itself when trying to retrieve the parent node.
Could someone suggest me what to do here?
PS: with chrome and firefox the code works perfectly in the first version I posted.
Fixed it!
I solved the issue by wrapping the OBJECT inside a div with an id of my choice which I can retrieve whenever I want. I do this in the resolveXpath function.
Here the code:
var weboElem, weboElemPar, ieObject = false;
var weboElemIndex = 0;
function weboRemoveVideoplayer(vpId){
var child;
if(!ieObject) weboElem = document.getElementById(vpId);
else weboElem = document.getElementById('my_usage');
if(!weboElem) return false;
weboElemPar = weboElem.parentNode;
weboElemIndex = 0;
child = weboElem;
while( (child = child.previousSibling) != null ) weboElemIndex++;
if(typeof weboElemPar.removeChild !== 'undefined') weboElemPar.removeChild(weboElem);
else return false;
return true;
}
function weboPlaceVideoplayerBack(){
if(typeof weboElemPar.insertBefore !== 'undefined' && typeof weboElemPar.childNodes !== 'undefined' && typeof weboElemPar.appendChild !== 'undefined'){
if(weboElemPar.childNodes.length > 0 && weboElemIndex < weboElemPar.childNodes.length) weboElemPar.insertBefore(weboElem, weboElemPar.childNodes[weboElemIndex]);
else weboElemPar.appendChild(weboElem);
return true;
}
return false;
}
function resolveXpath(path)
{
//XPathJS.bindDomLevel3XPath(); //solo per xpathJs
var result = document.evaluate(path,document,null,XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);
if(result.snapshotLength > 0){
var child, node = result.snapshotItem(0);
if(node.nodeName.toLowerCase() == 'object'){
ieObject = true;
child = node;
while( (child = child.previousSibling) != null ) weboElemIndex++;
var div = document.createElement('div');
div.id = 'my_usage';
if(typeof node.parentNode.insertBefore !== 'undefined' && typeof node.parentNode.childNodes !== 'undefined' && typeof node.parentNode.appendChild !== 'undefined'){
if(node.parentNode.childNodes.length > 0 && weboElemIndex < node.parentNode.childNodes.length) node.parentNode.insertBefore(div,node.parentNode.childNodes[weboElemIndex]);
else node.parentNode.appendChild(div);
div.appendChild(node);
} else return false;
} else node.id = 'my_usage';
return true;
} else return false;
}
resolveXpath('//*/param[contains(#src, "autoplay=1")]/..');
I have a function like this:
Session.get = function(key) {
if (!window["_SESSION"] || typeof key == 'undefined') {
return window["_SESSION"] || {};
}
if (key.indexOf('.') === -1) {
return window["_SESSION"][key] || {};
}
var keyArr = key.split('.'), val = window["_SESSION"];
for ( var i = 0; i < keyArr.length; i++) {
if (typeof val[keyArr[i]] === 'undefined') {
return null;
}
val = val[keyArr[i]];
}
return val;
}
This function allows me to get nested values without temporary variable outside of the function. Example Session.get('var.nestedvar') is returns value of window[_SESSION']['var']['nestedvar'].
Bat how can I (un)set variables like so? Tried to delete val; but didn't work.. How do the javascript references work? Does anybody know any alternative to accomplish similiar functionality?
You can delete by parent like this:
[10:00:00.380] a = {'root': {'home':'~'}}
[10:00:00.385] ({root:{home:"~"}})
--
[10:00:09.625] b = a['root']
[10:00:09.631] ({home:"~"})
--
[10:00:20.569] delete b['home']
[10:00:20.573] true
[10:00:21.684] a
[10:00:21.688] ({root:{}})
You can use a slight modification of your existing code, like this:
Session.delete = function(key) {
if (!window["_SESSION"] || typeof key == 'undefined') {
return false;
}
if (key.indexOf('.') === -1) {
if (key) {
delete key;
return true;
}
}
var keyArr = key.split('.'), val = window["_SESSION"];
var keyDepth = keyArr.length;
for ( var i = 0; i < keyDepth-1; i++) {
if (typeof val[keyDepth] === 'undefined') {
return null;
}
val = val[keyDepth];
}
if (val[keyDepth-1]) {
delete val[keyDepth-1];
return true;
}
return false;
}
I have a string like this "namespace.fun1.fun2.fun3" passed from the client. It's telling the server which function to use.
How do I safely run the function?
right now i'm doing:
var runthis = string.split('.')
namespace[runthis[1]][runthis[2]][runthis[3]]
How do I handle arbitrary depth of nesting safely?
A little function I wrote. I use it in most of my applications:
Object.lookup = (function _lookup() {
var cache = { };
return function _lookupClosure( lookup, failGracefully ) {
var check = null,
chain = [ ],
lastkey = '';
if( typeof lookup === 'string' ) {
if( cache[ lookup ] ) {
chain = cache[ lookup ].chain;
check = cache[ lookup ].check;
}
else {
lookup.split( /\./ ).forEach(function _forEach( key, index, arr ) {
if( check ) {
if( typeof check === 'object' ) {
if( key in check ) {
chain.push( check = check[ key ] );
lastkey = key;
}
else {
if( !failGracefully ) {
throw new TypeError( 'cannot resolve "' + key + '" in ' + lastkey );
}
}
}
else {
if( !failGracefully ) {
throw new TypeError( '"' + check + '" ' + ' does not seem to be an object' );
}
}
}
else {
lastkey = key;
chain.push( check = window[ key ] );
}
});
if( check ) {
cache[ lookup ] = {
chain: chain,
check: check
};
}
}
}
return {
execute: function _execute() {
return typeof check === 'function' ? check.apply( chain[chain.length - 2], arguments ) : null;
},
get: function _get() {
return check;
}
};
}
}());
usage:
Object.lookup( 'namespace.fun1.fun2.fun3' ).execute();
The first parameter is the object/method/property to resolve. The second (optional) parameter indicates whether or not the lookup() method shall fail silently or throw an exception if some property or object could not get resolved. default is 'throw'. To avoid that call
Object.lookup( 'namespace.fun1.fun2.fun3', true ).execute( 'with', 'paras' );
If .fun3 is a function, you can pass in any parameters into .execute() instead.
if you just want to receive the property value, you can also call .get() instead of .execute()
var val = Object.lookup( 'namespace.fun1.fun2.fun3' ).get();
(I may be misinterpreting the question, but this is what came to mind)
var s = "space.f.g.h.i.j.k.l.m",
a = s.split( "." ),
fn = eval( a[0] );
for ( var i = 1; i < a.length; i++ ) {
fn = fn[ a[i] ];
}
fn();
Note: this won't guard against the namespace being specified incorrectly or maliciously.
This should do:
var get = function(obj, key) {
var s = key.split('.')
, i = 1
, l = s.length;
for (; i < l; i++) {
obj = obj[s[i]];
if (!obj) return;
}
return obj;
};
get({hello:{world:{}}}, 'ns.hello.world');
edit: changed code a bit
Here's a simple for loop that should do find each object specified strating from the global scope, and then run the function it finds.
window.namespace = { fun1: { fun2: { fun3: function() { alert('hi!') }}}};
runFunc = function(address) {
var addressArray = address.split('.'),
current = window,
i = 0;
for (i = 0; i < addressArray.length; i++) {
current = current[addressArray[i]];
}
current();
};
runFunc('namespace.fun1.fun2.fun3');
http://jsfiddle.net/jfWra/1/
And here's some eror protection that will throw something meaningful if the value referenced doesnt exist or is not a function: http://jsfiddle.net/jfWra/2/
Here's another simple solution using a recursive function:
function run(str, context) {
var path = str.split(".")
if path.length == 1 {
context[path[0]].call()
return;
}
if(typeof context == 'undefined') {
context = window[path[0]]
} else {
context = context[path[0]]
}
run(path.splice(1).join('.'), context)
}
Two years later there is this module: https://github.com/daaku/nodejs-dotaccess.
var get = require("dotaccess").get;
var o = {a:{b:{c:'deep'}}};
console.log(get(o, 'a.b.c'));