I am trying to solve a similar problem and running into issues.
Javascript recursion completes before traversing the whole tree?.
I have the same function listFiles but with two parameters. The second parameter is the folder name that is passed to this function. The function should return the filenames that fall under the passed foldername. For eg. if the folder name passed is 'js' then the function should return 5 files i.e - main.js', 'app.js', 'misc.js', 'jquery.js' and 'underscore.js'. Here is the code i am trying to write to achieve the result -
function listSubfolder(fileSystem, subFolder){
var result = [];
for(var i=0; i<fileSystem.files.length; i++){
if(typeof fileSystem.files[i] !== 'string') {
fileSystem = fileSystem.files[i];
findFiles(fileSystem);
}
}
function findFiles(fileSystem) {
var files = fileSystem.files;
if (typeof files !== 'undefined') {
for (var i = 0; i < files.length; i++) {
if (typeof files[i] === 'string') {
result.push(files[i]);
} else {
findFiles(files[i]);
}
}
}
}
return result;
}
There are a couple problems here. The first is scope.
You're passing a parameter fileSystem into the function (L1):
function listSubfolder(fileSystem, subFolder){
but you're redefining it within the function (L5):
fileSystem = fileSystem.files[i];
The same thing is happening on your findFiles function (L10) as well. You're specifying fileSystem again (which doesn't happen to break the original fileSystem object from listSubFolder but it may cause errors in the future) so I would rename that parameter as well.
The second problem now is you need to check if the files are from the specified sub folder (L4):
if(typeof fileSystem.files[i] !== 'string' && fileSystem.files[i].dir === subFolder) {
The final edit should look something like this:
function listSubfolder(fileSystem, subFolder){
var result = [],
fArr;
for(var i=0; i<fileSystem.files.length; i++){
if(typeof fileSystem.files[i] !== 'string' && fileSystem.files[i].dir === subFolder) {
fArr = fileSystem.files[i];
findFiles(fArr);
}
}
function findFiles(f) {
var files = f.files;
if (typeof files !== 'undefined') {
for (var i = 0; i < files.length; i++) {
if (typeof files[i] === 'string') {
result.push(files[i]);
} else {
findFiles(files[i]);
}
}
}
}
return result;
}
The main problem with the code you provided is that you are not checking the current folder to see if you are within subFolder. You can add an extra Boolean parameter to the findFiles method that tracks if you are in the specified subfolder or not. Only if that is true should you add the files to result.
There's also a bug in your first loop where you were changing fileSystem each time through the loop. You can just remove that loop though and have findFiles process the root folder like all the others.
function listSubfolder(fileSystem, subFolder){
var result = [];
function findFiles(fileSystem, inSubFolder) {
var files = fileSystem.files;
if (typeof files !== 'undefined') {
for (var i = 0; i < files.length; i++) {
if (typeof files[i] === 'string') {
if (inSubFolder) {
result.push(files[i]);
}
} else {
var currentInSubFolder = (files[i].dir === subFolder);
findFiles(files[i], inSubFolder || currentInSubFolder);
}
}
}
}
findFiles(fileSystem, false);
return result;
}
Related
I'm trying to get the directory list, level by level, using JavaScript.
I have this paths array as input.
var _paths = [];
_paths.push("meta1/meta2/test/home/myself/hi.jpg");
_paths.push("meta1/meta2/test/home/myself/hi1.jpg");
_paths.push("meta1/meta2/test/home/myself/hi2.jpg");
_paths.push("meta1/meta2/test/work/she/100.jpg");
_paths.push("meta1/meta2/test/work/she/110.jpg");
_paths.push("meta1/meta2/test/work/she/120.jpg");
_paths.push("meta1/meta2/test/work/hard/soft/she/120.jpg");
_paths.push("meta1/meta2/test/work/hard/soft/she/121.jpg");
_paths.push("meta1/meta2/test/work/she/220.jpg");
and I want to have a "test" as output, which will be clickable. After click "test", it should be replaced by "home" and "work". After click on "home" - "myself", on "work" - "hard" and "she".
I wrote this:
CodepenCode
and it works only once, only when clicking on "test".
Simply rebind the listeners after the directories have been drawn. You bind them only once, thus they work only once.
Wrap the binding function into a named function:
function bindListeners(){
$('.sub').click(function() {
word = $(this).text();
filteredArr = findString(_paths, word);
drawList(filteredArr, word);
});
}
And call it at the end of drawList:
var drawList = function (paths, word) {
var folders = getFolders(paths, word);
if (folders.length > 0) {
$('.canvas').html('');
for (i = 0; i < folders.length; i++) {
$('.canvas').append("<div class='sub'>" + folders[i] + "</div><br />");
}
}
bindListeners();
}
Demo.
If anyone is curious about building out the data structure:
(function iteratePaths() {
var dirs = [];
for(var i = 0; i < _paths.length; i++) {
buildDirectories(dirs, _paths[i].split('/'));
}
})();
function findDir(dir, obj) {
for(var i = 0; i < dir.length; i++) {
if(dir[i].name === obj.name) {
return dir[i];
}
}
return undefined;
}
function buildDirectories(dir, subs) {
if(subs.length === 0) return;
var obj = {name: subs.shift(), dirs: []};
var existingDir = findDir(dir, obj);
if(!existingDir) {
dir.push(obj);
}
buildDirectories((existingDir || obj).dirs, subs);
}
I'm using all of the latest dependencies for emberjs and yet I end up with an error on initializing the scripts in the page at ember-data-latest.min.js with:
Uncaught TypeError: Object function () {...} has no method 'registerInjection'
Before even writing a single line for my app. Why is that?
Appreciate any kind help.
Here's my script list:
jquery-1.10.2.min.js
handlebars-v1.3.0.js
ember-1.5.1.js
ember-states.js
ember-data-latest.min.js
And here's the full error:
Uncaught TypeError: Object function () {
if (!wasApplied) {
Class.proto(); // prepare prototype...
}
o_defineProperty(this, GUID_KEY, nullDescriptor);
o_defineProperty(this, '__nextSuper', undefinedDescriptor);
var m = meta(this), proto = m.proto;
m.proto = this;
if (initMixins) {
// capture locally so we can clear the closed over variable
var mixins = initMixins;
initMixins = null;
this.reopen.apply(this, mixins);
}
if (initProperties) {
// capture locally so we can clear the closed over variable
var props = initProperties;
initProperties = null;
var concatenatedProperties = this.concatenatedProperties;
for (var i = 0, l = props.length; i < l; i++) {
var properties = props[i];
Ember.assert("Ember.Object.create no longer supports mixing in other definitions, use createWithMixins instead.", !(properties instanceof Ember.Mixin));
if (typeof properties !== 'object' && properties !== undefined) {
throw new Ember.Error("Ember.Object.create only accepts objects.");
}
if (!properties) { continue; }
var keyNames = Ember.keys(properties);
for (var j = 0, ll = keyNames.length; j < ll; j++) {
var keyName = keyNames[j];
if (!properties.hasOwnProperty(keyName)) { continue; }
var value = properties[keyName],
IS_BINDING = Ember.IS_BINDING;
if (IS_BINDING.test(keyName)) {
var bindings = m.bindings;
if (!bindings) {
bindings = m.bindings = {};
} else if (!m.hasOwnProperty('bindings')) {
bindings = m.bindings = o_create(m.bindings);
}
bindings[keyName] = value;
}
var desc = m.descs[keyName];
Ember.assert("Ember.Object.create no longer supports defining computed properties. Define computed properties using extend() or reopen() before calling create().", !(value instanceof Ember.ComputedProperty));
Ember.assert("Ember.Object.create no longer supports defining methods that call _super.", !(typeof value === 'function' && value.toString().indexOf('._super') !== -1));
Ember.assert("`actions` must be provided at extend time, not at create " +
"time, when Ember.ActionHandler is used (i.e. views, " +
"controllers & routes).", !((keyName === 'actions') && Ember.ActionHandler.detect(this)));
if (concatenatedProperties && indexOf(concatenatedProperties, keyName) >= 0) {
var baseValue = this[keyName];
if (baseValue) {
if ('function' === typeof baseValue.concat) {
value = baseValue.concat(value);
} else {
value = Ember.makeArray(baseValue).concat(value);
}
} else {
value = Ember.makeArray(value);
}
}
if (desc) {
desc.set(this, keyName, value);
} else {
if (typeof this.setUnknownProperty === 'function' && !(keyName in this)) {
this.setUnknownProperty(keyName, value);
} else if (MANDATORY_SETTER) {
Ember.defineProperty(this, keyName, null, value); // setup mandatory setter
} else {
this[keyName] = value;
}
}
}
}
}
finishPartial(this, m);
this.init.apply(this, arguments);
m.proto = proto;
finishChains(this);
sendEvent(this, "init");
} has no method 'registerInjection'
You need to make sure to use the latest version which gets published at Ember build site.
The latest build for Ember Data is found here.
Is there an existing javascript library which will deserialize Json.Net with reference loop handling?
{
"$id": "1",
"AppViewColumns": [
{
"$id": "2",
"AppView": {"$ref":"1"},
"ColumnID": 1,
}
]
}
this should deserialize to an object with a reference loop between the object in the array and the outer object
The answers given almost worked for me, but the latest version of MVC, JSON.Net, and DNX uses "$ref" and "$id", and they may be out of order. So I've modified user2864740's answer.
I should note that this code does not handle array references, which are also possible.
function RestoreJsonNetReferences(g) {
var ids = {};
function getIds(s) {
// we care naught about primitives
if (s === null || typeof s !== "object") { return s; }
var id = s['$id'];
if (typeof id != "undefined") {
delete s['$id'];
// either return previously known object, or
// remember this object linking for later
if (ids[id]) {
throw "Duplicate ID " + id + "found.";
}
ids[id] = s;
}
// then, recursively for each key/index, relink the sub-graph
if (s.hasOwnProperty('length')) {
// array or array-like; a different guard may be more appropriate
for (var i = 0; i < s.length; i++) {
getIds(s[i]);
}
} else {
// other objects
for (var p in s) {
if (s.hasOwnProperty(p)) {
getIds(s[p]);
}
}
}
}
function relink(s) {
// we care naught about primitives
if (s === null || typeof s !== "object") { return s; }
var id = s['$ref'];
delete s['$ref'];
// either return previously known object, or
// remember this object linking for later
if (typeof id != "undefined") {
return ids[id];
}
// then, recursively for each key/index, relink the sub-graph
if (s.hasOwnProperty('length')) {
// array or array-like; a different guard may be more appropriate
for (var i = 0; i < s.length; i++) {
s[i] = relink(s[i]);
}
} else {
// other objects
for (var p in s) {
if (s.hasOwnProperty(p)) {
s[p] = relink(s[p]);
}
}
}
return s;
}
getIds(g);
return relink(g);
}
I'm not aware of existing libraries with such support, but one could use the standard JSON.parse method and then manually walk the result restoring the circular references - it'd just be a simple store/lookup based on the $id property. (A similar approach can be used for reversing the process.)
Here is some sample code that uses such an approach. This code assumes the JSON has already been parsed to the relevant JS object graph - it also modifies the supplied data. YMMV.
function restoreJsonNetCR(g) {
var ids = {};
function relink (s) {
// we care naught about primitives
if (s === null || typeof s !== "object") { return s; }
var id = s['$id'];
delete s['$id'];
// either return previously known object, or
// remember this object linking for later
if (ids[id]) {
return ids[id];
}
ids[id] = s;
// then, recursively for each key/index, relink the sub-graph
if (s.hasOwnProperty('length')) {
// array or array-like; a different guard may be more appropriate
for (var i = 0; i < s.length; i++) {
s[i] = relink(s[i]);
}
} else {
// other objects
for (var p in s) {
if (s.hasOwnProperty(p)) {
s[p] = relink(s[p]);
}
}
}
return s;
}
return relink(g);
}
And the usage
var d = {
"$id": "1",
"AppViewColumns": [
{
"$id": "2",
"AppView": {"$id":"1"},
"ColumnID": 1,
}
]
};
d = restoreJsonNetCR(d);
// the following works well in Chrome, YMMV in other developer tools
console.log(d);
DrSammyD created an underscore plugin variant with round-trip support.
Ok so I created a more robust method which will use $id as well as $ref, because that's actually how json.net handles circular references. Also you have to get your references after the id has been registered otherwise it won't find the object that's been referenced, so I also have to hold the objects that are requesting the reference, along with the property they want to set and the id they are requesting.
This is heavily lodash/underscore based
(function (factory) {
'use strict';
if (typeof define === 'function' && define.amd) {
define(['lodash'], factory);
} else {
factory(_);
}
})(function (_) {
var opts = {
refProp: '$ref',
idProp: '$id',
clone: true
};
_.mixin({
relink: function (obj, optsParam) {
var options = optsParam !== undefined ? optsParam : {};
_.defaults(options, _.relink.prototype.opts);
obj = options.clone ? _.clone(obj, true) : obj;
var ids = {};
var refs = [];
function rl(s) {
// we care naught about primitives
if (!_.isObject(s)) {
return s;
}
if (s[options.refProp]) {
return null;
}
if (s[options.idProp] === 0 || s[options.idProp]) {
ids[s[options.idProp]] = s;
}
delete s[options.idProp];
_(s).pairs().each(function (pair) {
if (pair[1]) {
s[pair[0]] = rl(pair[1]);
if (s[pair[0]] === null) {
if (pair[1][options.refProp] !== undefined) {
refs.push({ 'parent': s, 'prop': pair[0], 'ref': pair[1][options.refProp] });
}
}
}
});
return s;
}
var partialLink = rl(obj);
_(refs).each(function (recordedRef) {
recordedRef['parent'][recordedRef['prop']] = ids[recordedRef['ref']] || {};
});
return partialLink;
},
resolve: function (obj, optsParam) {
var options = optsParam !== undefined ? optsParam : {};
_.defaults(options, _.resolve.prototype.opts);
obj = options.clone ? _.clone(obj, true) : obj;
var objs = [{}];
function rs(s) {
// we care naught about primitives
if (!_.isObject(s)) {
return s;
}
var replacementObj = {};
if (objs.indexOf(s) != -1) {
replacementObj[options.refProp] = objs.indexOf(s);
return replacementObj;
}
objs.push(s);
s[options.idProp] = objs.indexOf(s);
_(s).pairs().each(function (pair) {
s[pair[0]] = rs(pair[1]);
});
return s;
}
return rs(obj);
}
});
_(_.resolve.prototype).assign({ opts: opts });
_(_.relink.prototype).assign({ opts: opts });
});
I created a gist here
Ideally, I would like them all running, but when I have the second and/or third one active, Script 1 fails. (It just does nothing)
I wonder, is it possible to merge them into one script? Would that solve the problem? (I am tempted to try cutting and pasting them into one script just to see what happens)
Script 1 (the reloader)
(function () {
"use strict";
function walkTheDOM(node, func) {
if (node && node.nodeType) {
if (typeof func === "function") {
func(node);
}
node = node.firstChild;
while (node) {
walkTheDOM(node, func);
node = node.nextSibling;
}
}
}
function filterElementsByContains(elements, string) {
var toStringFN = {}.toString,
text = toStringFN.call(elements),
result,
length,
i,
element;
if (text !== "[object NodeList]" && text !== "[object Array]" && !($() instanceof jQuery)) {
return result;
}
result = [];
if (typeof string === "string") {
string = new RegExp("^" + string + "$");
} else if (toStringFN.call(string) !== "[object RegExp]") {
return result;
}
function getText(node) {
if (node.nodeType === 3) {
text += node.nodeValue;
}
}
length = elements.length;
i = 0;
while (i < length) {
text = "";
element = elements[i];
walkTheDOM(element, getText);
if (string.test(text)) {
result.push(element);
}
i += 1;
}
return result;
}
if(!filterElementsByContains([document.getElementsByTagName("table")[0]], /We are proud to announce that the November discounts have been chosen/).length) {
location.reload();
}
}());
Script 2 (Jump to last sheet, if it's multi sheet)
function getPreviousLink(){
var nextLink = document.getElementById('pagination-next-link');
var links = document.getElementsByClassName('v_page_nav')[0].getElementsByTagName("a");
for(var i=0; i < links.length; i++){
if(links[i] == nextLink) { return links[i-1]; }
}
}
var link = getPreviousLink();
link.target="_blank";
link.click();
Script 3 (open previous sheet, if there is one)
var link = document.getElementById('pagination-prev-link');
link.target="_blank";
link.click();
If the second 2 scripts aren't changing anything that would cause your first script to break (like something the first script looks for gets removed by script 2).... I would suggest going into 'manage scripts' and changing the order they run in... sometimes that can fix issues like this.
I am using Typeahead by twitter. I am running into this warning from Intellij. This is causing the "window.location.href" for each link to be the last item in my list of items.
How can I fix my code?
Below is my code:
AutoSuggest.prototype.config = function () {
var me = this;
var comp, options;
var gotoUrl = "/{0}/{1}";
var imgurl = '<img src="/icon/{0}.gif"/>';
var target;
for (var i = 0; i < me.targets.length; i++) {
target = me.targets[i];
if ($("#" + target.inputId).length != 0) {
options = {
source: function (query, process) { // where to get the data
process(me.results);
},
// set max results to display
items: 10,
matcher: function (item) { // how to make sure the result select is correct/matching
// we check the query against the ticker then the company name
comp = me.map[item];
var symbol = comp.s.toLowerCase();
return (this.query.trim().toLowerCase() == symbol.substring(0, 1) ||
comp.c.toLowerCase().indexOf(this.query.trim().toLowerCase()) != -1);
},
highlighter: function (item) { // how to show the data
comp = me.map[item];
if (typeof comp === 'undefined') {
return "<span>No Match Found.</span>";
}
if (comp.t == 0) {
imgurl = comp.v;
} else if (comp.t == -1) {
imgurl = me.format(imgurl, "empty");
} else {
imgurl = me.format(imgurl, comp.t);
}
return "\n<span id='compVenue'>" + imgurl + "</span>" +
"\n<span id='compSymbol'><b>" + comp.s + "</b></span>" +
"\n<span id='compName'>" + comp.c + "</span>";
},
sorter: function (items) { // sort our results
if (items.length == 0) {
items.push(Object());
}
return items;
},
// the problem starts here when i start using target inside the functions
updater: function (item) { // what to do when item is selected
comp = me.map[item];
if (typeof comp === 'undefined') {
return this.query;
}
window.location.href = me.format(gotoUrl, comp.s, target.destination);
return item;
}
};
$("#" + target.inputId).typeahead(options);
// lastly, set up the functions for the buttons
$("#" + target.buttonId).click(function () {
window.location.href = me.format(gotoUrl, $("#" + target.inputId).val(), target.destination);
});
}
}
};
With #cdhowie's help, some more code:
i will update the updater and also the href for the click()
updater: (function (inner_target) { // what to do when item is selected
return function (item) {
comp = me.map[item];
if (typeof comp === 'undefined') {
return this.query;
}
window.location.href = me.format(gotoUrl, comp.s, inner_target.destination);
return item;
}}(target))};
I liked the paragraph Closures Inside Loops from Javascript Garden
It explains three ways of doing it.
The wrong way of using a closure inside a loop
for(var i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
Solution 1 with anonymous wrapper
for(var i = 0; i < 10; i++) {
(function(e) {
setTimeout(function() {
console.log(e);
}, 1000);
})(i);
}
Solution 2 - returning a function from a closure
for(var i = 0; i < 10; i++) {
setTimeout((function(e) {
return function() {
console.log(e);
}
})(i), 1000)
}
Solution 3, my favorite, where I think I finally understood bind - yaay! bind FTW!
for(var i = 0; i < 10; i++) {
setTimeout(console.log.bind(console, i), 1000);
}
I highly recommend Javascript garden - it showed me this and many more Javascript quirks (and made me like JS even more).
p.s. if your brain didn't melt you haven't had enough Javascript that day.
You need to nest two functions here, creating a new closure that captures the value of the variable (instead of the variable itself) at the moment the closure is created. You can do this using arguments to an immediately-invoked outer function. Replace this expression:
function (item) { // what to do when item is selected
comp = me.map[item];
if (typeof comp === 'undefined') {
return this.query;
}
window.location.href = me.format(gotoUrl, comp.s, target.destination);
return item;
}
With this:
(function (inner_target) {
return function (item) { // what to do when item is selected
comp = me.map[item];
if (typeof comp === 'undefined') {
return this.query;
}
window.location.href = me.format(gotoUrl, comp.s, inner_target.destination);
return item;
}
}(target))
Note that we pass target into the outer function, which becomes the argument inner_target, effectively capturing the value of target at the moment the outer function is called. The outer function returns an inner function, which uses inner_target instead of target, and inner_target will not change.
(Note that you can rename inner_target to target and you will be okay -- the closest target will be used, which would be the function parameter. However, having two variables with the same name in such a tight scope could be very confusing and so I have named them differently in my example so that you can see what's going on.)
In ecmascript 6 we have new opportunities.
The let statement declares a block scope local variable, optionally initializing it to a value.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let
Since the only scoping that JavaScript has is function scope, you can simply move the closure to an external function, outside of the scope you're in.
Just to clarify on #BogdanRuzhitskiy answer (as I couldn't figure out how to add the code in a comment), the idea with using let is to create a local variable inside the for block:
for(var i = 0; i < 10; i++) {
let captureI = i;
setTimeout(function() {
console.log(captureI);
}, 1000);
}
This will work in pretty much any modern browser except IE11.