How can I make this for loop more performant - javascript

I would like to improve this for loop. Ideally I do not want the variable 'nodeFound' outside the function scope and I would like to return the 'nodeFound' as soon as it is found not after the loop has completed.
var nodeFound;
proto._getNodeById = function(id, node) {
var data = node || this._data;
var l = data.length;
var i;
for ( i = 0; i < l; i++) {
if (Number(id) === data[i].id) {
nodeFound = data[i];
} else {
if (data[i].children.length) {
this._getNodeById(id, data[i].children);
}
}
}
return nodeFound;
};

proto._getNodeById = function(id, node) {
var data = node || this._data;
var thisNode = null;
var l = data.length;
var i;
id=+id; // convert to number
for ( i = 0; i < l; i++) {
thisNode = data[i] ;
if (id === thisNode.id) {
return thisNode;
} else if (thisNode.children.length) {
thisNode = this._getNodeById(id, thisNode.children);
if (thisNode) return thisNode;
}
}
return null;
};

If you store your data in a JSON object, you won't need a loop.
And so your performance will increase a lot.
But you need to change your data format ...
data = {
'id1': {
'property1': 'value',
'property2': 'value',
'property3': 'value',
'children': {
'property1': 'value',
'property2': 'value',
'property3': 'value',
'id2': {
'children': {
...
}
},
'id3': {
'property1': 'value',
'property2': 'value',
'property3': 'value',
'children': {
...
}
}
}
}
After, the code look like this
proto._getNodeById = function(id, node) {
var data = node || this._data;
if( data[id] !== undefined ) {
nodeFound = data[id];
}
else if ( data[id] && data[id].children ) {
this._getNodeById( id, data[id].children );
}
return nodeFound;
};
Does it helps ?

Use while.
proto._getNodeById = function(id, node) {
var data = node || this._data;
var l = data.length;
var i=0;
var found = false;
var nodeFound = undefined;
while ( i < l && !found ) {
if (Number(id) === data[i].id) {
nodeFound = data[i];
found = true
} else {
if (data[i].children.length) {
var node = this._getNodeById(id, data[i].children);
if (node != undefined) {
nodeFound = node;
found = true;
}
}
}
i++;
}
return nodeFound;
};

Related

Array Data to HTML Table

What is the best way to get data from an array using tabletop.js into an html table? I am trying to take the data from the array and replace the dummy data that I currently have in there. The link below better explains what I'm trying to do.
Example of Data & Table
Here is my Fiddle: https://jsfiddle.net/1qeydx4f/
HTML:
<script type="text/javascript">
var publicSpreadsheetUrl = 'https://docs.google.com/spreadsheets/d/16EypE4AkZVutLblpQEpsBu4ly7ziqBRYPxlv7wO1SJs/edit?usp=sharing';
function init() {
Tabletop.init( { key: publicSpreadsheetUrl,
callback: showInfo,
simpleSheet: true } )
}
function showInfo(data, tabletop) {
console.log(data);
}
window.addEventListener('DOMContentLoaded', init)
</script>
<div class="docs-section" id="tables">
<h6 class="docs-header">3-Pointers Made (2019)</h6>
<p> </p>
<div class="docs-example">
<table class="u-full-width">
<thead>
<tr>
<th>Name</th>
<th>Team</th>
<th>Made</th>
<th>Attempted</th>
<th>Percent</th>
</tr>
</thead>
<tbody>
<tr>
<td>Dave Gamache</td>
<td>GS</td>
<td>150</td>
<td>300</td>
<td>50%</td>
</tr>
<tr>
<td>Dwayne Johnson</td>
<td>DAL</td>
<td>128</td>
<td>298</td>
<td>43%</td>
</tr>
</tbody>
</table>
</div>
</div>
Tabletop.js:
(function() {
'use strict';
var inNodeJS = typeof process !== 'undefined' && !process.browser;
var request = function requestNotProvided() {
throw new Error("The 'request' module is only available while running in Node.");
};
if(inNodeJS) { // This will get stripped out by Uglify, and Webpack will not include it
request = require('request');
}
var supportsCORS = false;
var inLegacyIE = false;
try {
var testXHR = new XMLHttpRequest();
if (typeof testXHR.withCredentials !== 'undefined') {
supportsCORS = true;
} else {
if ('XDomainRequest' in window) {
supportsCORS = true;
inLegacyIE = true;
}
}
} catch (e) { }
// Create a simple indexOf function for support
// of older browsers. Uses native indexOf if
// available. Code similar to underscores.
// By making a separate function, instead of adding
// to the prototype, we will not break bad for loops
// in older browsers
var indexOfProto = Array.prototype.indexOf;
var ttIndexOf = function(array, item) {
var i = 0, l = array.length;
if (indexOfProto && array.indexOf === indexOfProto) {
return array.indexOf(item);
}
for (; i < l; i++) {
if (array[i] === item) {
return i;
}
}
return -1;
};
/*
Initialize with Tabletop.init( { key: '0AjAPaAU9MeLFdHUxTlJiVVRYNGRJQnRmSnQwTlpoUXc' } )
OR!
Initialize with Tabletop.init( { key: 'https://docs.google.com/spreadsheet/pub?hl=en_US&hl=en_US&key=0AjAPaAU9MeLFdHUxTlJiVVRYNGRJQnRmSnQwTlpoUXc&output=html&widget=true' } )
OR!
Initialize with Tabletop.init('0AjAPaAU9MeLFdHUxTlJiVVRYNGRJQnRmSnQwTlpoUXc')
*/
var Tabletop = function(options) {
// Make sure Tabletop is being used as a constructor no matter what.
if(!this || !(this instanceof Tabletop)) {
return new Tabletop(options);
}
if(typeof(options) === 'string') {
options = { key : options };
}
this.callback = options.callback;
this.wanted = options.wanted || [];
this.key = options.key;
this.simpleSheet = !!options.simpleSheet;
this.parseNumbers = !!options.parseNumbers;
this.wait = !!options.wait;
this.reverse = !!options.reverse;
this.postProcess = options.postProcess;
this.debug = !!options.debug;
this.query = options.query || '';
this.orderby = options.orderby;
this.endpoint = options.endpoint || 'https://spreadsheets.google.com';
this.singleton = !!options.singleton;
this.simpleUrl = !!(options.simpleUrl || options.simple_url); //jshint ignore:line
this.authkey = options.authkey;
this.sheetPrivacy = this.authkey ? 'private' : 'public'
this.callbackContext = options.callbackContext;
// Default to on, unless there's a proxy, in which case it's default off
this.prettyColumnNames = typeof(options.prettyColumnNames) === 'undefined' ? !options.proxy : options.prettyColumnNames;
if(typeof(options.proxy) !== 'undefined') {
// Remove trailing slash, it will break the app
this.endpoint = options.proxy.replace(/\/$/,'');
this.simpleUrl = true;
this.singleton = true;
// Let's only use CORS (straight JSON request) when
// fetching straight from Google
supportsCORS = false;
}
this.parameterize = options.parameterize || false;
if (this.singleton) {
if (typeof(Tabletop.singleton) !== 'undefined') {
this.log('WARNING! Tabletop singleton already defined');
}
Tabletop.singleton = this;
}
/* Be friendly about what you accept */
if (/key=/.test(this.key)) {
this.log('You passed an old Google Docs url as the key! Attempting to parse.');
this.key = this.key.match('key=(.*?)(&|#|$)')[1];
}
if (/pubhtml/.test(this.key)) {
this.log('You passed a new Google Spreadsheets url as the key! Attempting to parse.');
this.key = this.key.match('d\\/(.*?)\\/pubhtml')[1];
}
if(/spreadsheets\/d/.test(this.key)) {
this.log('You passed the most recent version of Google Spreadsheets url as the key! Attempting to parse.');
this.key = this.key.match('d\\/(.*?)\/')[1];
}
if (!this.key) {
this.log('You need to pass Tabletop a key!');
return;
}
this.log('Initializing with key ' + this.key);
this.models = {};
this.modelNames = [];
this.model_names = this.modelNames; //jshint ignore:line
this.baseJsonPath = '/feeds/worksheets/' + this.key + '/' + this.sheetPrivacy +'/basic?alt=';
if (inNodeJS || supportsCORS) {
this.baseJsonPath += 'json';
} else {
this.baseJsonPath += 'json-in-script';
}
if(!this.wait) {
this.fetch();
}
};
// A global storage for callbacks.
Tabletop.callbacks = {};
// Backwards compatibility.
Tabletop.init = function(options) {
return new Tabletop(options);
};
Tabletop.sheets = function() {
this.log('Times have changed! You\'ll want to use var tabletop = Tabletop.init(...); tabletop.sheets(...); instead of Tabletop.sheets(...)');
};
Tabletop.prototype = {
fetch: function(callback) {
if (typeof(callback) !== 'undefined') {
this.callback = callback;
}
this.requestData(this.baseJsonPath, this.loadSheets);
},
/*
This will call the environment appropriate request method.
In browser it will use JSON-P, in node it will use request()
*/
requestData: function(path, callback) {
this.log('Requesting', path);
if (inNodeJS) {
this.serverSideFetch(path, callback);
} else {
//CORS only works in IE8/9 across the same protocol
//You must have your server on HTTPS to talk to Google, or it'll fall back on injection
var protocol = this.endpoint.split('//').shift() || 'http';
if (supportsCORS && (!inLegacyIE || protocol === location.protocol)) {
this.xhrFetch(path, callback);
} else {
this.injectScript(path, callback);
}
}
},
/*
Use Cross-Origin XMLHttpRequest to get the data in browsers that support it.
*/
xhrFetch: function(path, callback) {
//support IE8's separate cross-domain object
var xhr = inLegacyIE ? new XDomainRequest() : new XMLHttpRequest();
xhr.open('GET', this.endpoint + path);
var self = this;
xhr.onload = function() {
var json;
try {
json = JSON.parse(xhr.responseText);
} catch (e) {
console.error(e);
}
callback.call(self, json);
};
xhr.send();
},
/*
Insert the URL into the page as a script tag. Once it's loaded the spreadsheet data
it triggers the callback. This helps you avoid cross-domain errors
http://code.google.com/apis/gdata/samples/spreadsheet_sample.html
Let's be plain-Jane and not use jQuery or anything.
*/
injectScript: function(path, callback) {
var script = document.createElement('script');
var callbackName;
if (this.singleton) {
if (callback === this.loadSheets) {
callbackName = 'Tabletop.singleton.loadSheets';
} else if (callback === this.loadSheet) {
callbackName = 'Tabletop.singleton.loadSheet';
}
} else {
var self = this;
callbackName = 'tt' + (+new Date()) + (Math.floor(Math.random()*100000));
// Create a temp callback which will get removed once it has executed,
// this allows multiple instances of Tabletop to coexist.
Tabletop.callbacks[ callbackName ] = function () {
var args = Array.prototype.slice.call( arguments, 0 );
callback.apply(self, args);
script.parentNode.removeChild(script);
delete Tabletop.callbacks[callbackName];
};
callbackName = 'Tabletop.callbacks.' + callbackName;
}
var url = path + '&callback=' + callbackName;
if (this.simpleUrl) {
// We've gone down a rabbit hole of passing injectScript the path, so let's
// just pull the sheet_id out of the path like the least efficient worker bees
if(path.indexOf('/list/') !== -1) {
script.src = this.endpoint + '/' + this.key + '-' + path.split('/')[4];
} else {
script.src = this.endpoint + '/' + this.key;
}
} else {
script.src = this.endpoint + url;
}
if (this.parameterize) {
script.src = this.parameterize + encodeURIComponent(script.src);
}
this.log('Injecting', script.src);
document.getElementsByTagName('script')[0].parentNode.appendChild(script);
},
/*
This will only run if tabletop is being run in node.js
*/
serverSideFetch: function(path, callback) {
var self = this;
this.log('Fetching', this.endpoint + path);
request({url: this.endpoint + path, json: true}, function(err, resp, body) {
if (err) {
return console.error(err);
}
callback.call(self, body);
});
},
/*
Is this a sheet you want to pull?
If { wanted: ["Sheet1"] } has been specified, only Sheet1 is imported
Pulls all sheets if none are specified
*/
isWanted: function(sheetName) {
if (this.wanted.length === 0) {
return true;
} else {
return (ttIndexOf(this.wanted, sheetName) !== -1);
}
},
/*
What gets send to the callback
if simpleSheet === true, then don't return an array of Tabletop.this.models,
only return the first one's elements
*/
data: function() {
// If the instance is being queried before the data's been fetched
// then return undefined.
if (this.modelNames.length === 0) {
return undefined;
}
if (this.simpleSheet) {
if (this.modelNames.length > 1 && this.debug) {
this.log('WARNING You have more than one sheet but are using simple sheet mode! Don\'t blame me when something goes wrong.');
}
return this.models[this.modelNames[0]].all();
} else {
return this.models;
}
},
/*
Add another sheet to the wanted list
*/
addWanted: function(sheet) {
if(ttIndexOf(this.wanted, sheet) === -1) {
this.wanted.push(sheet);
}
},
/*
Load all worksheets of the spreadsheet, turning each into a Tabletop Model.
Need to use injectScript because the worksheet view that you're working from
doesn't actually include the data. The list-based feed (/feeds/list/key..) does, though.
Calls back to loadSheet in order to get the real work done.
Used as a callback for the worksheet-based JSON
*/
loadSheets: function(data) {
var i, ilen;
var toLoad = [];
this.googleSheetName = data.feed.title.$t;
this.foundSheetNames = [];
for (i = 0, ilen = data.feed.entry.length; i < ilen ; i++) {
this.foundSheetNames.push(data.feed.entry[i].title.$t);
// Only pull in desired sheets to reduce loading
if (this.isWanted(data.feed.entry[i].content.$t)) {
var linkIdx = data.feed.entry[i].link.length-1;
var sheetId = data.feed.entry[i].link[linkIdx].href.split('/').pop();
var jsonPath = '/feeds/list/' + this.key + '/' + sheetId + '/' + this.sheetPrivacy + '/values?alt=';
if (inNodeJS || supportsCORS) {
jsonPath += 'json';
} else {
jsonPath += 'json-in-script';
}
if (this.query) {
// Query Language Reference (0.7)
jsonPath += '&tq=' + this.query;
}
if (this.orderby) {
jsonPath += '&orderby=column:' + this.orderby.toLowerCase();
}
if (this.reverse) {
jsonPath += '&reverse=true';
}
toLoad.push(jsonPath);
}
}
this.sheetsToLoad = toLoad.length;
for(i = 0, ilen = toLoad.length; i < ilen; i++) {
this.requestData(toLoad[i], this.loadSheet);
}
},
/*
Access layer for the this.models
.sheets() gets you all of the sheets
.sheets('Sheet1') gets you the sheet named Sheet1
*/
sheets: function(sheetName) {
if (typeof sheetName === 'undefined') {
return this.models;
} else {
if (typeof(this.models[sheetName]) === 'undefined') {
// alert( "Can't find " + sheetName );
return;
} else {
return this.models[sheetName];
}
}
},
sheetReady: function(model) {
this.models[model.name] = model;
if (ttIndexOf(this.modelNames, model.name) === -1) {
this.modelNames.push(model.name);
}
this.sheetsToLoad--;
if (this.sheetsToLoad === 0) {
this.doCallback();
}
},
/*
Parse a single list-based worksheet, turning it into a Tabletop Model
Used as a callback for the list-based JSON
*/
loadSheet: function(data) {
var that = this;
new Tabletop.Model({
data: data,
parseNumbers: this.parseNumbers,
postProcess: this.postProcess,
tabletop: this,
prettyColumnNames: this.prettyColumnNames,
onReady: function() {
that.sheetReady(this);
}
});
},
/*
Execute the callback upon loading! Rely on this.data() because you might
only request certain pieces of data (i.e. simpleSheet mode)
Tests this.sheetsToLoad just in case a race condition happens to show up
*/
doCallback: function() {
if(this.sheetsToLoad === 0) {
this.callback.apply(this.callbackContext || this, [this.data(), this]);
}
},
log: function() {
if(this.debug) {
if(typeof console !== 'undefined' && typeof console.log !== 'undefined') {
Function.prototype.apply.apply(console.log, [console, arguments]);
}
}
}
};
/*
Tabletop.Model stores the attribute names and parses the worksheet data
to turn it into something worthwhile
Options should be in the format { data: XXX }, with XXX being the list-based worksheet
*/
Tabletop.Model = function(options) {
var i, j, ilen, jlen;
this.columnNames = [];
this.column_names = this.columnNames; // jshint ignore:line
this.name = options.data.feed.title.$t;
this.tabletop = options.tabletop;
this.elements = [];
this.onReady = options.onReady;
this.raw = options.data; // A copy of the sheet's raw data, for accessing minutiae
if (typeof(options.data.feed.entry) === 'undefined') {
options.tabletop.log('Missing data for ' + this.name + ', make sure you didn\'t forget column headers');
this.originalColumns = [];
this.elements = [];
this.ready();
return;
}
for (var key in options.data.feed.entry[0]){
if (/^gsx/.test(key)) {
this.columnNames.push(key.replace('gsx$',''));
}
}
this.originalColumns = this.columnNames;
this.original_columns = this.originalColumns; // jshint ignore:line
for (i = 0, ilen = options.data.feed.entry.length ; i < ilen; i++) {
var source = options.data.feed.entry[i];
var element = {};
for (j = 0, jlen = this.columnNames.length; j < jlen ; j++) {
var cell = source['gsx$' + this.columnNames[j]];
if (typeof(cell) !== 'undefined') {
if (options.parseNumbers && cell.$t !== '' && !isNaN(cell.$t)) {
element[this.columnNames[j]] = +cell.$t;
} else {
element[this.columnNames[j]] = cell.$t;
}
} else {
element[this.columnNames[j]] = '';
}
}
if (element.rowNumber === undefined) {
element.rowNumber = i + 1;
}
this.elements.push(element);
}
if (options.prettyColumnNames) {
this.fetchPrettyColumns();
} else {
this.ready();
}
};
Tabletop.Model.prototype = {
/*
Returns all of the elements (rows) of the worksheet as objects
*/
all: function() {
return this.elements;
},
fetchPrettyColumns: function() {
if (!this.raw.feed.link[3]) {
return this.ready();
}
var cellurl = this.raw.feed.link[3].href.replace('/feeds/list/', '/feeds/cells/').replace('https://spreadsheets.google.com', '');
var that = this;
this.tabletop.requestData(cellurl, function(data) {
that.loadPrettyColumns(data);
});
},
beforeReady: function() {
if(this.postProcess) {
for (i = 0, ilen = this.elements.length; i < ilen; i++) {
this.postProcess(element);
}
}
},
ready: function() {
this.beforeReady();
this.onReady.call(this);
},
/*
* Store column names as an object
* with keys of Google-formatted "columnName"
* and values of human-readable "Column name"
*/
loadPrettyColumns: function(data) {
var prettyColumns = {};
var columnNames = this.columnNames;
var i = 0;
var l = columnNames.length;
for (; i < l; i++) {
if (typeof data.feed.entry[i].content.$t !== 'undefined') {
prettyColumns[columnNames[i]] = data.feed.entry[i].content.$t;
} else {
prettyColumns[columnNames[i]] = columnNames[i];
}
}
this.prettyColumns = prettyColumns;
this.pretty_columns = this.prettyColumns; // jshint ignore:line
this.prettifyElements();
this.ready();
},
/*
* Go through each row, substitutiting
* Google-formatted "columnName"
* with human-readable "Column name"
*/
prettifyElements: function() {
var prettyElements = [],
orderedPrettyNames = [],
i, j, ilen, jlen;
for (j = 0, jlen = this.columnNames.length; j < jlen ; j++) {
orderedPrettyNames.push(this.prettyColumns[this.columnNames[j]]);
}
for (i = 0, ilen = this.elements.length; i < ilen; i++) {
var newElement = {};
for (j = 0, jlen = this.columnNames.length; j < jlen ; j++) {
var newColumnName = this.prettyColumns[this.columnNames[j]];
newElement[newColumnName] = this.elements[i][this.columnNames[j]];
}
prettyElements.push(newElement);
}
this.elements = prettyElements;
this.columnNames = orderedPrettyNames;
},
/*
Return the elements as an array of arrays, instead of an array of objects
*/
toArray: function() {
var array = [],
i, j, ilen, jlen;
for (i = 0, ilen = this.elements.length; i < ilen; i++) {
var row = [];
for (j = 0, jlen = this.columnNames.length; j < jlen ; j++) {
row.push(this.elements[i][ this.columnNames[j]]);
}
array.push(row);
}
return array;
}
};
if(typeof module !== 'undefined' && module.exports) { //don't just use inNodeJS, we may be in Browserify
module.exports = Tabletop;
} else if (typeof define === 'function' && define.amd) {
define(function () {
return Tabletop;
});
} else {
window.Tabletop = Tabletop;
}
})();
This can actually be achieved pretty simply. All you need to do is give your table an ID to be able to select it, and have a loop in the tabletop callback:
function gotData(data) {
const table = document.getElementById("table1-id").tBodies[0];
for (const player of data) {
const row = table.insertRow(-1); // index -1 inserts at bottom
const keys = Object.keys(player);
for (let i = 0; i < keys.length; i++) {
const c = row.insertCell(i);
c.appendChild(document.createTextNode(player[keys[i]]));
}
}
}

Popular image picker plugin doesn't show pictures

I was trying to use this plugin on my project: http://rvera.github.io/image-picker/ but it is not showing the images but it's just showing the select-option box. Is there anything else I'm missing as a newbie in javascript?
edit: should've added my codes:
html:
<div class="picker" hidden>
<select class="image-picker show-html">
<option value=""></option>
<option data-img-src="http://placekitten.com/300/200" value="1">Cute Kitten 1</option>
<option data-img-src="http://placekitten.com/150/200" value="2">Cute Kitten 2</option>
<option data-img-src="http://placekitten.com/400/200" value="3">Cute Kitten 3</option>
</select>
</div>
<div class="item">
<select class="row kittens">
<div class="col-md-3"><option data-img-src="http://placekitten.com/300/200" value="1">Cute Kitten 1</option></div>
<div class="col-md-3"><option data-img-src="http://placekitten.com/150/200" value="2">Cute Kitten 2</option></div>
<div class="col-md-3"><option data-img-src="http://placekitten.com/400/200" value="3">Cute Kitten 3</option></div>
<div class="col-md-3"><img src="http://placehold.it/250x250" alt="Image" style="max-width:100%;"></div>
</select><!--.row-->
</div><!--.item-->
javascript: I added the plugin code in image-picker.js then this below*
$(function(){
$( document ).ready(function() {
$("select").imagepicker({
show_label : true
})
$(".kittens").imagepicker({
show_label : true
})
});
});
With the help of you guys, found an error from console
(function() {
var ImagePicker, ImagePickerOption, both_array_are_equal, sanitized_options,
bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
//console said error found from this first line below and another error is found in the last line "}).call(this);"
jQuery.fn.extend({
imagepicker: function(opts) {
if (opts == null) {
opts = {};
}
return this.each(function() {
var select;
select = jQuery(this);
if (select.data("picker")) {
select.data("picker").destroy();
}
select.data("picker", new ImagePicker(this, sanitized_options(opts)));
if (opts.initialized != null) {
return opts.initialized.call(select.data("picker"));
}
});
}
});
sanitized_options = function(opts) {
var default_options;
default_options = {
hide_select: true,
show_label: false,
initialized: void 0,
changed: void 0,
clicked: void 0,
selected: void 0,
limit: void 0,
limit_reached: void 0
};
return jQuery.extend(default_options, opts);
};
both_array_are_equal = function(a, b) {
var i, j, len, x;
if ((!a || !b) || (a.length !== b.length)) {
return false;
}
a = a.slice(0);
b = b.slice(0);
a.sort();
b.sort();
for (i = j = 0, len = a.length; j < len; i = ++j) {
x = a[i];
if (b[i] !== x) {
return false;
}
}
return true;
};
ImagePicker = (function() {
function ImagePicker(select_element, opts1) {
this.opts = opts1 != null ? opts1 : {};
this.sync_picker_with_select = bind(this.sync_picker_with_select, this);
this.select = jQuery(select_element);
this.multiple = this.select.attr("multiple") === "multiple";
if (this.select.data("limit") != null) {
this.opts.limit = parseInt(this.select.data("limit"));
}
this.build_and_append_picker();
}
ImagePicker.prototype.destroy = function() {
var j, len, option, ref;
ref = this.picker_options;
for (j = 0, len = ref.length; j < len; j++) {
option = ref[j];
option.destroy();
}
this.picker.remove();
this.select.off("change", this.sync_picker_with_select);
this.select.removeData("picker");
return this.select.show();
};
ImagePicker.prototype.build_and_append_picker = function() {
if (this.opts.hide_select) {
this.select.hide();
}
this.select.on("change", this.sync_picker_with_select);
if (this.picker != null) {
this.picker.remove();
}
this.create_picker();
this.select.after(this.picker);
return this.sync_picker_with_select();
};
ImagePicker.prototype.sync_picker_with_select = function() {
var j, len, option, ref, results;
ref = this.picker_options;
results = [];
for (j = 0, len = ref.length; j < len; j++) {
option = ref[j];
if (option.is_selected()) {
results.push(option.mark_as_selected());
} else {
results.push(option.unmark_as_selected());
}
}
return results;
};
ImagePicker.prototype.create_picker = function() {
this.picker = jQuery("<ul class='thumbnails image_picker_selector'></ul>");
this.picker_options = [];
this.recursively_parse_option_groups(this.select, this.picker);
return this.picker;
};
ImagePicker.prototype.recursively_parse_option_groups = function(scoped_dom, target_container) {
var container, j, k, len, len1, option, option_group, ref, ref1, results;
ref = scoped_dom.children("optgroup");
for (j = 0, len = ref.length; j < len; j++) {
option_group = ref[j];
option_group = jQuery(option_group);
container = jQuery("<ul></ul>");
container.append(jQuery("<li class='group_title'>" + (option_group.attr("label")) + "</li>"));
target_container.append(jQuery("<li class='group'>").append(container));
this.recursively_parse_option_groups(option_group, container);
}
ref1 = (function() {
var l, len1, ref1, results1;
ref1 = scoped_dom.children("option");
results1 = [];
for (l = 0, len1 = ref1.length; l < len1; l++) {
option = ref1[l];
results1.push(new ImagePickerOption(option, this, this.opts));
}
return results1;
}).call(this);
results = [];
for (k = 0, len1 = ref1.length; k < len1; k++) {
option = ref1[k];
this.picker_options.push(option);
if (!option.has_image()) {
continue;
}
results.push(target_container.append(option.node));
}
return results;
};
ImagePicker.prototype.has_implicit_blanks = function() {
var option;
return ((function() {
var j, len, ref, results;
ref = this.picker_options;
results = [];
for (j = 0, len = ref.length; j < len; j++) {
option = ref[j];
if (option.is_blank() && !option.has_image()) {
results.push(option);
}
}
return results;
}).call(this)).length > 0;
};
ImagePicker.prototype.selected_values = function() {
if (this.multiple) {
return this.select.val() || [];
} else {
return [this.select.val()];
}
};
ImagePicker.prototype.toggle = function(imagepicker_option, original_event) {
var new_values, old_values, selected_value;
old_values = this.selected_values();
selected_value = imagepicker_option.value().toString();
if (this.multiple) {
if (indexOf.call(this.selected_values(), selected_value) >= 0) {
new_values = this.selected_values();
new_values.splice(jQuery.inArray(selected_value, old_values), 1);
this.select.val([]);
this.select.val(new_values);
} else {
if ((this.opts.limit != null) && this.selected_values().length >= this.opts.limit) {
if (this.opts.limit_reached != null) {
this.opts.limit_reached.call(this.select);
}
} else {
this.select.val(this.selected_values().concat(selected_value));
}
}
} else {
if (this.has_implicit_blanks() && imagepicker_option.is_selected()) {
this.select.val("");
} else {
this.select.val(selected_value);
}
}
if (!both_array_are_equal(old_values, this.selected_values())) {
this.select.change();
if (this.opts.changed != null) {
return this.opts.changed.call(this.select, old_values, this.selected_values(), original_event);
}
}
};
return ImagePicker;
})();
ImagePickerOption = (function() {
function ImagePickerOption(option_element, picker, opts1) {
this.picker = picker;
this.opts = opts1 != null ? opts1 : {};
this.clicked = bind(this.clicked, this);
this.option = jQuery(option_element);
this.create_node();
}
ImagePickerOption.prototype.destroy = function() {
return this.node.find(".thumbnail").off("click", this.clicked);
};
ImagePickerOption.prototype.has_image = function() {
return this.option.data("img-src") != null;
};
ImagePickerOption.prototype.is_blank = function() {
return !((this.value() != null) && this.value() !== "");
};
ImagePickerOption.prototype.is_selected = function() {
var select_value;
select_value = this.picker.select.val();
if (this.picker.multiple) {
return jQuery.inArray(this.value(), select_value) >= 0;
} else {
return this.value() === select_value;
}
};
ImagePickerOption.prototype.mark_as_selected = function() {
return this.node.find(".thumbnail").addClass("selected");
};
ImagePickerOption.prototype.unmark_as_selected = function() {
return this.node.find(".thumbnail").removeClass("selected");
};
ImagePickerOption.prototype.value = function() {
return this.option.val();
};
ImagePickerOption.prototype.label = function() {
if (this.option.data("img-label")) {
return this.option.data("img-label");
} else {
return this.option.text();
}
};
ImagePickerOption.prototype.clicked = function(event) {
this.picker.toggle(this, event);
if (this.opts.clicked != null) {
this.opts.clicked.call(this.picker.select, this, event);
}
if ((this.opts.selected != null) && this.is_selected()) {
return this.opts.selected.call(this.picker.select, this, event);
}
};
ImagePickerOption.prototype.create_node = function() {
var image, imgAlt, imgClass, thumbnail;
this.node = jQuery("<li/>");
image = jQuery("<img class='image_picker_image'/>");
image.attr("src", this.option.data("img-src"));
thumbnail = jQuery("<div class='thumbnail'>");
imgClass = this.option.data("img-class");
if (imgClass) {
this.node.addClass(imgClass);
image.addClass(imgClass);
thumbnail.addClass(imgClass);
}
imgAlt = this.option.data("img-alt");
if (imgAlt) {
image.attr('alt', imgAlt);
}
thumbnail.on("click", this.clicked);
thumbnail.append(image);
if (this.opts.show_label) {
thumbnail.append(jQuery("<p/>").html(this.label()));
}
this.node.append(thumbnail);
return this.node;
};
return ImagePickerOption;
})();
}).call(this);
Cheers

Javascript: Determine unknown array length and map dynamically

Going to do my best at explaining what I am trying to do.
I have two models, mine and an api response I am receiving. When the items api response comes in, I need to map it to my model and inserts all the items. This is simple of course. Heres the issue, I need to do so without really knowing what I am dealing with. My code will be passed in two strings, one of my models mapping path and one of the api response mapping path.
Here are the two paths
var myPath = "outputModel.items[].uniqueName"
var apiPath = "items[].name"
Basically FOR all items in apiPath, push into items in myPath and set to uniqueName
What it comes down to is that my code has NO idea when two items need to be mapped, or even if they contain an array or simple field to field paths. They could even contain multiple arrays, like this:
******************** EXAMPLE *************************
var items = [
{
name: "Hammer",
skus:[
{num:"12345qwert"}
]
},
{
name: "Bike",
skus:[
{num:"asdfghhj"},
{num:"zxcvbn"}
]
},
{
name: "Fork",
skus:[
{num:"0987dfgh"}
]
}
]
var outputModel = {
storeName: "",
items: [
{
name: "",
sku:""
}
]
};
outputModel.items[].name = items[].name;
outputModel.items[].sku = items[].skus[].num;
************************ Here is the expected result of above
var result = {
storeName: "",
items: [
{
name: "Hammer",
sku:"12345qwert"
},
{
name: "Bike",
sku:"asdfghhj"
},
{
name: "Bike",
sku:"zxcvbn"
},
{
name: "Fork",
sku:"0987dfgh" }
]
};
I will be given a set of paths for EACH value to be mapped. In the case above, I was handed two sets of paths because I am mapping two values. It would have to traverse both sets of arrays to create the single array in my model.
Question - How can I dynamically detect arrays and move the data around properly no matter what the two model paths look like? Possible?
So you have defined a little language to define some data addressing and manipulation rules. Let's think about an approach which will allow you to say
access(apiPath, function(value) { insert(myPath, value); }
The access function finds all the required items in apiPath, then calls back to insert, which inserts them into myPath. Our job is to write functions which create the access and insert functions; or, you could say, "compile" your little language into functions we can execute.
We will write "compilers" called make_accessor and make_inserter, as follows:
function make_accessor(program) {
return function(obj, callback) {
return function do_segment(obj, segments) {
var start = segments.shift() // Get first segment
var pieces = start.match(/(\w+)(\[\])?/); // Get name and [] pieces
var property = pieces[1];
var isArray = pieces[2]; // [] on end
obj = obj[property]; // drill down
if (!segments.length) { // last segment; callback
if (isArray) {
return obj.forEach(callback);
} else {
return callback(obj);
}
} else { // more segments; recurse
if (isArray) { // array--loop over elts
obj.forEach(function(elt) { do_segment(elt, segments.slice()); });
} else {
do_segment(obj, segments.slice()); // scalar--continue
}
}
}(obj, program.split('.'));
};
}
We can now make an accessor by calling make_accessor('items[].name').
Next, let's write the inserter:
function make_inserter(program) {
return function(obj, value) {
return function do_segment(obj, segments) {
var start = segments.shift() // Get first segment
var pieces = start.match(/(\w+)(\[\])?/); // Get name and [] pieces
var property = pieces[1];
var isArray = pieces[2]; // [] on end
if (segments.length) { // more segments
if (!obj[property]) {
obj[property] = isArray ? [] : {};
}
do_segment(obj, segments.slice());
} else { // last segment
obj[property] = value;
}
}(obj, program.split('.'));
};
}
Now, you can express your whole logic as
access = make_accessor('items[].name');
insert = make_inserter('outputModel.items[].uniqueName');
access(apiPath, function(val) { insert(myPath, val); });
As mentioned in the comments, there is no strict definition of the input format, it is hard to do it with perfect error handling and handle all corner cases.
Here is my lengthy implementation that works on your sample, but might fail for some other cases:
function merge_objects(a, b) {
var c = {}, attr;
for (attr in a) { c[attr] = a[attr]; }
for (attr in b) { c[attr] = b[attr]; }
return c;
}
var id = {
inner: null,
name: "id",
repr: "id",
type: "map",
exec: function (input) { return input; }
};
// set output field
function f(outp, mapper) {
mapper = typeof mapper !== "undefined" ? mapper : id;
var repr = "f("+outp+","+mapper.repr+")";
var name = "f("+outp;
return {
inner: mapper,
name: name,
repr: repr,
type: "map",
clone: function(mapper) { return f(outp, mapper); },
exec:
function (input) {
var out = {};
out[outp] = mapper.exec(input);
return out;
}
};
}
// set input field
function p(inp, mapper) {
var repr = "p("+inp+","+mapper.repr+")";
var name = "p("+inp;
return {
inner: mapper,
name: name,
repr: repr,
type: mapper.type,
clone: function(mapper) { return p(inp, mapper); },
exec: function (input) {
return mapper.exec(input[inp]);
}
};
}
// process array
function arr(mapper) {
var repr = "arr("+mapper.repr+")";
return {
inner: mapper,
name: "arr",
repr: repr,
type: mapper.type,
clone: function(mapper) { return arr(mapper); },
exec: function (input) {
var out = [];
for (var i=0; i<input.length; i++) {
out.push(mapper.exec(input[i]));
}
return out;
}
};
}
function combine(m1, m2) {
var type = (m1.type == "flatmap" || m2.type == "flatmap") ? "flatmap" : "map";
var repr = "combine("+m1.repr+","+m2.repr+")";
return {
inner: null,
repr: repr,
type: type,
name: "combine",
exec:
function (input) {
var out1 = m1.exec(input);
var out2 = m2.exec(input);
var out, i, j;
if (m1.type == "flatmap" && m2.type == "flatmap") {
out = [];
for (i=0; i<out1.length; i++) {
for (j=0; j<out2.length; j++) {
out.push(merge_objects(out1[i], out2[j]));
}
}
return out;
}
if (m1.type == "flatmap" && m2.type != "flatmap") {
out = [];
for (i=0; i<out1.length; i++) {
out.push(merge_objects(out1[i], out2));
}
return out;
}
if (m1.type != "flatmap" && m2.type == "flatmap") {
out = [];
for (i=0; i<out2.length; i++) {
out.push(merge_objects(out2[i], out1));
}
return out;
}
return merge_objects(out1, out2);
}
};
}
function flatmap(mapper) {
var repr = "flatmap("+mapper.repr+")";
return {
inner: mapper,
repr: repr,
type: "flatmap",
name: "flatmap",
clone: function(mapper) { return flatmap(mapper); },
exec:
function (input) {
var out = [];
for (var i=0; i<input.length; i++) {
out.push(mapper.exec(input[i]));
}
return out;
}
};
}
function split(s, t) {
var i = s.indexOf(t);
if (i == -1) return null;
else {
return [s.slice(0, i), s.slice(i+2, s.length)];
}
}
function compile_one(inr, outr) {
inr = (inr.charAt(0) == ".") ? inr.slice(1, inr.length) : inr;
outr = (outr.charAt(0) == ".") ? outr.slice(1, outr.length) : outr;
var box = split(inr, "[]");
var box2 = split(outr, "[]");
var m, ps, fs, i, j;
if (box == null && box2 == null) { // no array!
m = id;
ps = inr.split(".");
fs = outr.split(".");
for (i=0; i<fs.length; i++) { m = f(fs[i], m); }
for (j=0; j<ps.length; j++) { m = p(ps[j], m); }
return m;
}
if (box != null && box2 != null) { // array on both sides
m = arr(compile_one(box[1], box2[1]));
ps = box[0].split(".");
fs = box[0].split(".");
for (i=0; i<fs.length; i++) { m = f(fs[i], m); }
for (j=0; j<ps.length; j++) { m = p(ps[j], m); }
return m;
}
if (box != null && box2 == null) { // flatmap
m = flatmap(compile_one(box[1], outr));
ps = box[0].split(".");
for (j=0; j<ps.length; j++) { m = p(ps[j], m); }
return m;
}
return null;
}
function merge_rules(m1, m2) {
if (m1 == null) return m2;
if (m2 == null) return m1;
if (m1.name == m2.name && m1.inner != null) {
return m1.clone(merge_rules(m1.inner, m2.inner));
} else {
return combine(m1, m2);
}
}
var input = {
store: "myStore",
items: [
{name: "Hammer", skus:[{num:"12345qwert"}]},
{name: "Bike", skus:[{num:"asdfghhj"}, {num:"zxcvbn"}]},
{name: "Fork", skus:[{num:"0987dfgh"}]}
]
};
var m1 = compile_one("items[].name", "items[].name");
var m2 = compile_one("items[].skus[].num", "items[].sku");
var m3 = compile_one("store", "storeName");
var m4 = merge_rules(m3,merge_rules(m1, m2));
var out = m4.exec(input);
alert(JSON.stringify(out));
I have borrowed earlier answer and made improvements so as to solve both your examples and this should be generic. Though if you plan to run this sequencially with 2 sets of inputs, then the behavior will be as I have outlined in my comments to your original question.
var apiObj = {
items: [{
name: "Hammer",
skus: [{
num: "12345qwert"
}]
}, {
name: "Bike",
skus: [{
num: "asdfghhj"
}, {
num: "zxcvbn"
}]
}, {
name: "Fork",
skus: [{
num: "0987dfgh"
}]
}]
};
var myObj = { //Previously has values
storeName: "",
items: [{
uniqueName: ""
}],
outputModel: {
items: [{
name: "Hammer"
}]
}
};
/** Also works with this **
var myPath = "outputModel.items[].uniqueName";
var apiPath = "items[].name";
*/
var myPath = "outputModel.items[].sku";
var apiPath = "items[].skus[].num";
function make_accessor(program) {
return function (obj, callback) {
(function do_segment(obj, segments) {
var start = segments.shift() // Get first segment
var pieces = start.match(/(\w+)(\[\])?/); // Get name and [] pieces
var property = pieces[1];
var isArray = pieces[2]; // [] on end
obj = obj[property]; // drill down
if (!segments.length) { // last segment; callback
if (isArray) {
return obj.forEach(callback);
} else {
return callback(obj);
}
} else { // more segments; recurse
if (isArray) { // array--loop over elts
obj.forEach(function (elt) {
do_segment(elt, segments.slice());
});
} else {
do_segment(obj, segments.slice()); // scalar--continue
}
}
})(obj, program.split('.'));
};
}
function make_inserter(program) {
return function (obj, value) {
(function do_segment(obj, segments) {
var start = segments.shift() // Get first segment
var pieces = start.match(/(\w+)(\[\])?/); // Get name and [] pieces
var property = pieces[1];
var isArray = pieces[2]; // [] on end
if (segments.length) { // more segments
if (!obj[property]) {
obj[property] = isArray ? [] : {};
}
do_segment(obj[property], segments.slice());
} else { // last segment
if (Array.isArray(obj)) {
var addedInFor = false;
for (var i = 0; i < obj.length; i++) {
if (!(property in obj[i])) {
obj[i][property] = value;
addedInFor = true;
break;
}
}
if (!addedInFor) {
var entry = {};
entry[property] = value;
obj.push(entry);
}
} else obj[property] = value;
}
})(obj, program.split('.'));
};
}
access = make_accessor(apiPath);
insert = make_inserter(myPath);
access(apiObj, function (val) {
insert(myObj, val);
});
console.log(myObj);
(old solution: https://jsfiddle.net/d7by0ywy/):
Here is my new generalized solution when you know the two objects to process in advance (called inp and out here). If you don't know them in advance you can use the trick in the old solution to assign the objects on both sides of = to inp and out (https://jsfiddle.net/uxdney3L/3/).
Restrictions: There has to be the same amount of arrays on both sides and an array has to contain objects. Othewise it would be ambiguous, you would have to come up with a better grammar to express rules (or why don't you have functions instead of rules?) if you want it to be more sophisticated.
Example of ambiguity: out.items[].sku=inp[].skus[].num Do you assign an array of the values of num to sku or do you assign an array of objects with the num property?
Data:
rules = [
'out.items[].name=inp[].name',
'out.items[].sku[].num=inp[].skus[].num'
];
inp = [{
'name': 'Hammer',
'skus':[{'num':'12345qwert','test':'ignore'}]
},{
'name': 'Bike',
'skus':[{'num':'asdfghhj'},{'num':'zxcvbn'}]
},{
'name': 'Fork',
'skus':[{'num':'0987dfgh'}]
}];
Program:
function process() {
if (typeof out == 'undefined') {
out = {};
}
var j, r;
for (j = 0; j < rules.length; j++) {
r = rules[j].split('=');
if (r.length != 2) {
console.log('invalid rule: symbol "=" is expected exactly once');
} else if (r[0].substr(0, 3) != 'out' || r[1].substr(0, 3) != 'inp') {
console.log('invalid rule: expected "inp...=out..."');
} else {
processRule(r[0].substr(3).split('[]'), r[1].substr(3).split('[]'), 0, inp, out);
}
}
}
function processRule(l, r, n, i, o) { // left, right, index, in, out
var t = r[n].split('.');
for (var j = 0; j < t.length; j++) {
if (t[j] != '') {
i = i[t[j]];
}
}
t = l[n].split('.');
if (n < l.length - 1) {
for (j = 0; j < t.length - 1; j++) {
if (t[j] != '') {
if (typeof o[t[j]] == 'undefined') {
o[t[j]] = {};
}
o = o[t[j]];
}
}
if (typeof o[t[j]] == 'undefined') {
o[t[j]] = [];
}
o = o[t[j]];
for (j = 0; j < i.length; j++) {
if (typeof o[j] == 'undefined') {
o[j] = {};
}
processRule(l, r, n + 1, i[j], o[j]);
}
} else {
for (j = 0; j < t.length - 1; j++) {
if (t[j] != '') {
if (typeof o[t[j]] == 'undefined') {
o[t[j]] = {};
}
o = o[t[j]];
}
}
o[t[j]] = i;
}
}
process();
console.log(out);
Well, an interesting problem. Programmatically constructing nested objects from a property accessor string (or the reverse) isn't much of a problem, even doing so with multiple descriptors in parallel. Where it does get complicated are arrays, which require iteration; and that isn't as funny any more when it gets to different levels on setter and getter sides and multiple descriptor strings in parallel.
So first we need to distinguish the array levels of each accessor description in the script, and parse the text:
function parse(script) {
return script.split(/\s*[;\r\n]+\s*/g).map(function(line) {
var assignment = line.split(/\s*=\s*/);
return assignment.length == 2 ? assignment : null; // console.warn ???
}).filter(Boolean).map(function(as) {
as = as.map(function(accessor) {
var parts = accessor.split("[]").map(function(part) {
return part.split(".");
});
for (var i=1; i<parts.length; i++) {
// assert(parts[i][0] == "")
var prev = parts[i-1][parts[i-1].length-1];
parts[i][0] = prev.replace(/s$/, ""); // singular :-)
}
return parts;
});
if (as[0].length == 1 && as[1].length > 1) // getter contains array but setter does not
as[0].unshift(["output"]); // implicitly return array (but better throw an error)
return {setter:as[0], getter:as[1]};
});
}
With that, the textual input can be made into a usable data structure, and now looks like this:
[{"setter":[["outputModel","items"],["item","name"]],
"getter":[["items"],["item","name"]]},
{"setter":[["outputModel","items"],["item","sku"]],
"getter":[["items"],["item","skus"],["sku","num"]]}]
The getters already transform nicely into nested loops like
for (item of items)
for (sku of item.skus)
… sku.num …;
and that's exactly where we are going to. Each of those rules is relatively easy to process, copying properties on objects and iterating array for array, but here comes our most crucial issue: We have multiple rules. The basic solution when we deal with iterating multiple arrays is to create their cartesian product and this is indeed what we will need. However, we want to restrict this a lot - instead of creating every combination of all names and all nums in the input, we want to group them by the item that they come from.
To do so, we'll build some kind of prefix tree for our output structure that'll contain generators of objects, each of those recursivley being a tree for the respective output substructure again.
function multiGroupBy(arr, by) {
return arr.reduce(function(res, x) {
var p = by(x);
(res[p] || (res[p] = [])).push(x);
return res;
}, {});
}
function group(rules) {
var paths = multiGroupBy(rules, function(rule) {
return rule.setter[0].slice(1).join(".");
});
var res = [];
for (var path in paths) {
var pathrules = paths[path],
array = [];
for (var i=0; i<pathrules.length; i++) {
var rule = pathrules[i];
var comb = 1 + rule.getter.length - rule.setter.length;
if (rule.setter.length > 1) // its an array
array.push({
generator: rule.getter.slice(0, comb),
next: {
setter: rule.setter.slice(1),
getter: rule.getter.slice(comb)
}
})
else if (rule.getter.length == 1 && i==0)
res.push({
set: rule.setter[0],
get: rule.getter[0]
});
else
console.error("invalid:", rule);
}
if (array.length)
res.push({
set: pathrules[0].setter[0],
cross: product(array)
});
}
return res;
}
function product(pathsetters) {
var groups = multiGroupBy(pathsetters, function(pathsetter) {
return pathsetter.generator[0].slice(1).join(".");
});
var res = [];
for (var genstart in groups) {
var creators = groups[genstart],
nexts = [],
nests = [];
for (var i=0; i<creators.length; i++) {
if (creators[i].generator.length == 1)
nexts.push(creators[i].next);
else
nests.push({path:creators[i].path, generator: creators[i].generator.slice(1), next:creators[i].next});
}
res.push({
get: creators[0].generator[0],
cross: group(nexts).concat(product(nests))
});
}
return res;
}
Now, our ruleset group(parse(script)) looks like this:
[{
"set": ["outputModel","items"],
"cross": [{
"get": ["items"],
"cross": [{
"set": ["item","name"],
"get": ["item","name"]
}, {
"get": ["item","skus"],
"cross": [{
"set": ["item","sku"],
"get": ["sku","num"]
}]
}]
}]
}]
and that is a structure we can actually work with, as it now clearly conveys the intention on how to match together all those nested arrays and the objects within them.
Let's dynamically interpret this, building an output for a given input:
function transform(structure, input, output) {
for (var i=0; i<structure.length; i++) {
output = assign(output, structure[i].set.slice(1), getValue(structure[i], input));
}
return output;
}
function retrieve(val, props) {
return props.reduce(function(o, p) { return o[p]; }, val);
}
function assign(obj, props, val) {
if (!obj)
if (!props.length) return val;
else obj = {};
for (var j=0, o=obj; j<props.length-1 && o!=null && o[props[j]]; o=o[props[j++]]);
obj[props[j]] = props.slice(j+1).reduceRight(function(val, p) {
var o = {};
o[p] = val;
return o;
}, val);
return obj;
}
function getValue(descriptor, input) {
if (descriptor.get) // && !cross
return retrieve(input, descriptor.get.slice(1));
var arr = [];
descriptor.cross.reduce(function horror(next, d) {
if (descriptor.set)
return function (inp, cb) {
next(inp, function(res){
cb(assign(res, d.set.slice(1), getValue(d, inp)));
});
};
else // its a crosser
return function(inp, cb) {
var g = retrieve(inp, d.get.slice(1)),
e = d.cross.reduce(horror, next)
for (var i=0; i<g.length; i++)
e(g[i], cb);
};
}, function innermost(inp, cb) {
cb(); // start to create an item
})(input, function(res) {
arr.push(res); // store the item
});
return arr;
}
And this does indeed work with
var result = transform(group(parse(script)), items); // your expected result
But we can do better, and much more performant:
function compile(structure) {
function make(descriptor) {
if (descriptor.get)
return {inputName: descriptor.get[0], output: descriptor.get.join(".") };
var outputName = descriptor.set[descriptor.set.length-1];
var loops = descriptor.cross.reduce(function horror(next, descriptor) {
if (descriptor.set)
return function(it, cb) {
return next(it, function(res){
res.push(descriptor)
return cb(res);
});
};
else // its a crosser
return function(it, cb) {
var arrName = descriptor.get[descriptor.get.length-1],
itName = String.fromCharCode(it);
var inner = descriptor.cross.reduce(horror, next)(it+1, cb);
return {
inputName: descriptor.get[0],
statement: (descriptor.get.length>1 ? "var "+arrName+" = "+descriptor.get.join(".")+";\n" : "")+
"for (var "+itName+" = 0; "+itName+" < "+arrName+".length; "+itName+"++) {\n"+
"var "+inner.inputName+" = "+arrName+"["+itName+"];\n"+
inner.statement+
"}\n"
};
};
}, function(_, cb) {
return cb([]);
})(105, function(res) {
var item = joinSetters(res);
return {
inputName: item.inputName,
statement: (item.statement||"")+outputName+".push("+item.output+");\n"
};
});
return {
statement: "var "+outputName+" = [];\n"+loops.statement,
output: outputName,
inputName: loops.inputName
};
}
function joinSetters(descriptors) {
if (descriptors.length == 1 && descriptors[0].set.length == 1)
return make(descriptors[0]);
var paths = multiGroupBy(descriptors, function(d){ return d.set[1] || console.error("multiple assignments on "+d.set[0], d); });
var statements = [],
inputName;
var props = Object.keys(paths).map(function(p) {
var d = joinSetters(paths[p].map(function(d) {
var names = d.set.slice(1);
names[0] = d.set[0]+"_"+names[0];
return {set:names, get:d.get, cross:d.cross};
}));
inputName = d.inputName;
if (d.statement)
statements.push(d.statement)
return JSON.stringify(p) + ": " + d.output;
});
return {
inputName: inputName,
statement: statements.join(""),
output: "{"+props.join(",")+"}"
};
}
var code = joinSetters(structure);
return new Function(code.inputName, code.statement+"return "+code.output+";");
}
So here is what you will get in the end:
> var example = compile(group(parse("outputModel.items[].name = items[].name;outputModel.items[].sku = items[].skus[].num;")))
function(items) {
var outputModel_items = [];
for (var i = 0; i < items.length; i++) {
var item = items[i];
var skus = item.skus;
for (var j = 0; j < skus.length; j++) {
var sku = skus[j];
outputModel_items.push({"name": item.name,"sku": sku.num});
}
}
return {"items": outputModel_items};
}
> var flatten = compile(group(parse("as[]=bss[][]")))
function(bss) {
var as = [];
for (var i = 0; i < bss.length; i++) {
var bs = bss[i];
for (var j = 0; j < bs.length; j++) {
var b = bs[j];
as.push(b);
}
}
return as;
}
> var parallelRecords = compile(group(parse("x.as[]=y[].a; x.bs[]=y[].b")))
function(y) {
var x_as = [];
for (var i = 0; i < y.length; i++) {
var y = y[i];
x_as.push(y.a);
}
var x_bs = [];
for (var i = 0; i < y.length; i++) {
var y = y[i];
x_bs.push(y.b);
}
return {"as": x_as,"bs": x_bs};
}
And now you can easily pass your input data to that dynamically created function and it will be transformed quite fast :-)

how to reindex object start from 0

I have an object output from below code how to set the index start from 0 in js?
Object
3: Object
id: 34
type: 0
var obj = {};
var edited = false;
for (var i = 0; i < $(".list").length; i++) {
var data_id = parseInt($(".list").eq(i).attr('data-id'));
var data_type = parseInt($(".list").eq(i).attr('data-type'));
if ((data_type != 0)) {
edited = true;
} else {
edited = false;
}
if (edited == true) {
obj[i] = {};
obj[i]['id'] = data_id;
obj[i]['type'] = data_type;
}
}
console.log(obj);
Needs more jQuery ?
var arr = $(".list").filter(function() {
return $(this).data('type') != 0;
}).map(function() {
return { id : $(this).data('id'), type : $(this).data('type') };
}).get();
FIDDLE
Actually if you want to start in 0, use another variable and not "i" (which I think is 3 when you use it as index).
var obj = {};
var edited = false;
var obj_idx = 0;
for (var i = 0; i < $(".list").length; i++) {
var data_id = parseInt($(".list").eq(i).attr('data-id'));
var data_type = parseInt($(".list").eq(i).attr('data-type'));
if ((data_type != 0)) {
edited = true;
} else {
edited = false;
}
if (edited == true) {
obj[obj_idx] = {};
obj[obj_idx]['id'] = data_id;
obj[obj_idx]['type'] = data_type;
obj_idx += 1;
}
}
console.log(obj);
I think this time obj will be something like:
Object
0: Object
id: 34
type: 0
you could fake object as array by Array.prototype.push.call, in that way you could also gain the side effect: obj.length. it's kinda ninja and elegant :]
var obj = {};
var edited = false;
for (var i = 0; i < $(".list").length; i++) {
var data_id = parseInt($(".list").eq(i).attr('data-id'));
var data_type = parseInt($(".list").eq(i).attr('data-type'));
if ((data_type != 0)) {
edited = true;
} else {
edited = false;
}
if (edited == true) {
Array.prototype.push.call(obj, {id: data_id, type: data_type});
}
}
I am going to give a very simple and readable example. Say you've got an object with the following structure:
Object
0: Object
key: 'some-key'
value: 'some-value'
1: Object
...
Then you might want to delete an entry from it and reindex the whole thing, this is how I do it:
// obj is Object from above
const reIndexed = Object.entries(obj).map((element, index) => {
if (parseInt(element[0] != index) {
element[0] = index.toString();
}
return element;
});

Tree building infinite recursion detection

I am working on JQuery mechanism that is building tree, it has to be as fast as possible. Volume of data is quite large so master record column is used to be able to 'grab' all relevant nodes in one select from webSQL db.
Whole mechanism but one part is done, when assembling tree it must check that there is no infinite recursion being created. What mechanism does at the moment if Record A is Master and Parent of record B, and Record B is Master and Parent of record A then structure like A.Children[0] = B and B.Children[0] = A is being built. It all would work fine, but it has do be knockout.js bound and displayed as expandable list to users, which results in overflow when trying to display the tree.
Requirement is to detect such loops and do not create tree relationships.
Mechanism that would check if item is already in the tree I came up is:
function InTree(master, item) {
return $.inArray(item, $.map(master, function recurs(n) {
return ($.isArray(n) ? $.map(n, recurs) : n);
})) != -1;
}
if(!InTree(tree, record))
{
//attach
} else {
// get next record
}
Is there anything faster than InTree() function that would get me if item is in the tree?
Whole tree building algorithm is below (not that I think it is relevant but rather to avoid the comments 'show the code')
$(document).on('OnPreQuery', onTheMove.PageDataRoles, function (e, options) {
var isChildAttachmentQueued = true;
var knockoutContextName = options.knockoutContextName;
if (TreeEnabled(knockoutContextName)) {
var isModelReadyToAttachChildren = function () {
var isReady = false;
if (PageObj[knockoutContextName] != undefined) {
isReady = (PageObj[knockoutContextName]().length > 0) && isChildAttachmentQueued;
}
return isReady;
};
var businessComponent = eval(knockoutContextName);
var treeSettings = businessComponent.Tree;
treeSettings.knockoutContextName = knockoutContextName;
$(businessComponent).on('OnPreUIUpdate', function (e, options) {
if (isModelReadyToAttachChildren()) {
getChildrenForMasterRecordList({
parentTable: businessComponent.primaryTableName,
knockoutContextName: treeSettings.knockoutContextName,
parentIdColumn: treeSettings.ParentIdColumn,
masterIdColumn: treeSettings.MasterIdColumn
});
isChildAttachmentQueued = false;
}
});
}
});
function TreeEnabled(knockoutContextName) {
var isTreeEnabled = false;
try {
eval(knockoutContextName);
} catch (e) {
return isTreeEnabled;
}
var treeSettings = eval(knockoutContextName).Tree;
if (treeSettings && treeSettings.IncludeChildren) {
isTreeEnabled = true;
}
return isTreeEnabled;
}
function ComposeRecordsToTreeStructure(results, tableArray, columnArray, options) {
if (results.rows.length > 0) {
if (options.parentLayerIdList == undefined) {
options.parentLayerIdList = options.masterIdList;
}
if (options.orphans == undefined) {
options.orphans = [];
}
var knockoutContextName = options.knockoutContextName;
var childRecordIdArray = [];
if (options.runningOnOrphans) {
if (options.orphans.length > 0) {
for (var j = 0; j < options.orphans.length; j++) {
var rowRecord = options.orphans[j];
var rowRecordParentId = rowRecord[options.parentIdColumn];
var result = EstablishParentChildConnectionOnAlreadyProcessedNodes(rowRecord, rowRecordParentId, options.parentLayerIdList, knockoutContextName, childRecordIdArray, options);
if (result.hasEstablishedConnection) {
childRecordIdArray = result.childRecordIdArray;
}
}
options.orphans = $.grep(options.orphans, function (item) {
return $.inArray(item['Id'], childRecordIdArray) == -1;
});
}
} else {
for (var i = 0; i < results.rows.length; i++) {
var rowRecord = results.rows.item(i);
var rowRecordParentId = rowRecord[options.parentIdColumn];
if (rowRecord[options.parentIdColumn] == '' || rowRecord[options.masterIdColumn] == '' || rowRecord[options.masterIdColumn] == rowRecord['Id']) {
rowRecord.isInvalid = true;
} else if ($.inArray(rowRecord['Id'], options.masterIdList) != -1) {
masterRecordClone = $.grep(PageObj[knockoutContextName](), function (item) { return item.Fields.Id() == rowRecord['Id'] })[0];
if (masterRecordClone != undefined && masterRecordClone.Children) {
rowRecord.Children = masterRecordClone.Children;
}
}
if (rowRecord.isInvalid == true) {
if (rowRecord[options.masterIdColumn] != rowRecord['Id']) {
var result = EstablishParentChildConnection(rowRecord, rowRecord[options.masterIdColumn], options.parentLayerIdList, knockoutContextName, childRecordIdArray, options);
if (result.hasEstablishedConnection) {
childRecordIdArray = result.childRecordIdArray;
EstablishParentChildConnectionOnAlreadyProcessedNodes(rowRecord, rowRecordParentId, options.parentLayerIdList, knockoutContextName, childRecordIdArray, options);
}
}
} else {
var result = EstablishParentChildConnectionOnAlreadyProcessedNodes(rowRecord, rowRecordParentId, options.parentLayerIdList, knockoutContextName, childRecordIdArray, options);
if (result.hasEstablishedConnection) {
childRecordIdArray = result.childRecordIdArray;
} else {
var recordObject = AddIsExpandedProperty(rowRecord);
options.orphans.push(recordObject);
options.runningOnOrphans = true;
}
}
}
}
if (options.orphans.length > 0 && childRecordIdArray.length > 0) {
options.parentLayerIdList = childRecordIdArray;
ComposeRecordsToTreeStructure(results, tableArray, columnArray, options);
}
}
onTheMove.seleniumHelper.markPageAsLoaded();
}
function EstablishParentChildConnectionOnAlreadyProcessedNodes(rowRecord, rowRecordParentId, parentLayerIdList, knockoutContextName, childRecordIdArray, options) {
var result = EstablishParentChildConnection(rowRecord, rowRecordParentId, parentLayerIdList, knockoutContextName, childRecordIdArray);
if (result.hasEstablishedConnection) {
childRecordIdArray = result.childRecordIdArray;
} else {
var result = EstablishParentChildConnection(rowRecord, rowRecordParentId, childRecordIdArray, knockoutContextName, childRecordIdArray);
if (result.hasEstablishedConnection) {
childRecordIdArray = result.childRecordIdArray;
} else {
var matchingOrphans = $.grep(options.orphans, function (item) {
return item['Id'] == rowRecordParentId;
});
if (matchingOrphans.length > 0) {
AttachPassedChildRecord(rowRecord, matchingOrphans);
var result = {
hasEstablishedConnection: true
};
}
}
}
return {
childRecordIdArray: childRecordIdArray,
hasEstablishedConnection: result.hasEstablishedConnection
};
}
function EstablishParentChildConnection(rowRecord, rowRecordParentId, parentLayerIdList, knockoutContextName, childRecordIdArray) {
var hasEstablishedConnection = false;
var parentPosition = $.inArray(rowRecordParentId, parentLayerIdList);
if (parentPosition != -1) {
AttachChildRecordsToParents(rowRecord, parentLayerIdList[parentPosition], knockoutContextName);
childRecordIdArray = AddChildRecordsToNextParentList(rowRecord, childRecordIdArray);
childRecordIdArray.push(rowRecord['Id']);
hasEstablishedConnection = true;
}
return {
childRecordIdArray: childRecordIdArray,
hasEstablishedConnection: hasEstablishedConnection
};
}
function AddChildRecordsToNextParentList(childRecord, childRecordIdArray) {
if (childRecord.Children != undefined) {
for (var i = 0; i < childRecord.Children.length; i++) {
childRecordIdArray.push(childRecord.Children[i]['Id']);
if (childRecord.Children[i].Children != undefined) {
AddChildRecordsToNextParentList(childRecord.Children[i], childRecordIdArray);
}
}
}
return childRecordIdArray;
}
function RowsToListDataStructure(results) {
var array = [];
for (var i = 0; i < results.rows.length; i++) {
array.push(results.rows.item(i));
}
return array;
}
function AttachChildRecordsToParents(recordRow, id, knockoutContextName) {
var childTreeOptions = {
id: id,
knockoutContextName: knockoutContextName,
results: []
};
findObjectsInChildTreeById(childTreeOptions);
if (childTreeOptions.results.length > 0) {
AttachPassedChildRecord(recordRow, childTreeOptions.results);
}
}
function AttachPassedChildRecord(recordObject, pageObjParentResults) {
for (var i = 0; i < pageObjParentResults.length; i++) {
if (pageObjParentResults[i].Children == undefined) {
pageObjParentResults[i].Children = [];
}
if ($.grep(pageObjParentResults[i].Children, function (children) {
return children['Id'] == recordObject['Id'];
}).length == 0) {
recordObject = AddIsExpandedProperty(recordObject);
pageObjParentResults[i].Children.push(recordObject);
}
}
}
function AddIsExpandedProperty(recordObject) {
recordObject.IsExpanded = ko.observable(false);
return recordObject;
}
function findObjectsInChildTreeById(options) {
if (options.item == undefined) {
if (typeof PageObj[options.knockoutContextName] != 'undefined') {
for (var item in PageObj[options.knockoutContextName]()) {
findObjectsInChildTreeById({
item: PageObj[options.knockoutContextName]()[item],
id: options.id,
results: options.results
});
}
}
} else {
if (typeof options.item.Fields != 'undefined') {
if (options.item.Fields['Id']() == options.id)
options.results.push(options.item);
} else {
if (options.item['Id'] == options.id)
options.results.push(options.item);
}
if (options.item.Children != undefined) {
for (var item in options.item.Children) {
findObjectsInChildTreeById({
item: options.item.Children[item],
id: options.id,
results: options.results
});
}
}
}
}
function getChildrenForMasterRecordList(options) {
var parentTable = options.parentTable,
masterIdColumn = options.masterIdColumn,
parentIdColumn = options.parentIdColumn,
knockoutContextName = options.knockoutContextName,
masterIds = getParentIdsAndMastersOfParentsFromPageObj(knockoutContextName, masterIdColumn);
for (var item in PageObj[options.knockoutContextName]()) {
AddIsExpandedProperty(PageObj[knockoutContextName]()[item]);
}
var dbManager = new OnTheMoveDatabaseManager();
dbManager.queryDatabase({
statement: {
Tables: [{
Alias: parentTable,
JoinSpec: null,
JoinType: "",
Name: parentTable
}, {
Alias: "Record",
JoinSpec: "Record.Id = " + parentTable + ".Id",
JoinType: "INNER",
Name: "Record"
}],
WhereClause: parentTable + "." + masterIdColumn + " IN ('" + masterIds.join("','") + "') AND Record.RecordType ='" + parentTable + "'",
SelectFields: [{
IsAggregate: false,
Name: "*"
}],
DisablePaging: true,
OrderClause: "Record.Id"
},
knockoutContextName: knockoutContextName,
isObservable: false,
masterIdColumn: masterIdColumn,
masterIdList: masterIds,
parentIdColumn: parentIdColumn,
parentTable: options.parentTable,
success: function (results, tableArray, columnArray, options) {
ComposeRecordsToTreeStructure(results, tableArray, columnArray, options);
}
});
}
function getParentIdsAndMastersOfParentsFromPageObj(knockoutContextName, masterColumnName) {
var list = [];
if (typeof PageObj[knockoutContextName] != 'undefined') {
for (var item in PageObj[knockoutContextName]()) {
if ($.inArray(PageObj[knockoutContextName]()[item].Fields['Id'](), list) == -1) {
list.push(PageObj[knockoutContextName]()[item].Fields['Id']());
}
if (PageObj[knockoutContextName]()[item].Fields[masterColumnName]() != '' && $.inArray(PageObj[knockoutContextName]()[item].Fields[masterColumnName](), list) == -1) {
list.push(PageObj[knockoutContextName]()[item].Fields[masterColumnName]());
}
}
}
return list
}
function InTree(master, item) {
return $.inArray(item, $.map(master, function recurs(n) {
return ($.isArray(n) ? $.map(n, recurs) : n);
})) != -1;
}
it depends on your circumstances. if you're able to run code on add/creation of the tree you may simply create a node id array and do a check like
if(allNodes[searchKey])
//etc
This is a very specific solution of course but technically it'd be as fast as it could possibly be.

Categories

Resources