dijit.tree with empty folders - javascript

i have created a tree select that shows a dijit.tree in the dropdown. Now I do not want the user to select a folder even if it is empty. User should only be able to select the end nodes or leaves. dijit.tree treats all empty folders as leafs. how do I get that sorted?

You need to override the _onClick or setSelected methods. This gets complicated if you use the multi-parental model ForestStoreModel.
See fiddle.net
Try doing as such, this will only work for select multiple false:
getIconClass: function fileIconClass(item, nodeExpanded) {
var store = item._S,
get = function() {
return store.getValue(item, arguments[0]);
};
// scope: dijit.Tree
if (item.root || get("isDir")) {
if (!item || this.model.mayHaveChildren(item) || get("isDir")) {
return (nodeExpanded ? "dijitFolderOpened" : "dijitFolderClosed");
} else {
return "dijitLeaf";
}
} else {
return "dijitLeaf";
}
},
onClick: function(item, treeNode, e) {
var store = item._S,
get = function() {
return store.getValue(item, arguments[0]);
};
if (get("isDir")) this.set("selectedItems", []);
}
Adapt as you see fit, matching your json data - in particular the isDir, the above works on a sample of json like this
{
identifier: 'id',
label: 'foo',
items: [
{
id: 'item1',
foo: 'file1',
isDir: false},
{
id: 'item2',
foo: 'emptyDir',
isDir: true},
{
id: 'item3',
foo: 'dir',
isDir: true,
children: [
{
id: 'item3_1',
foo: 'fileInDir',
isDir: false}
]}
]
}

Related

JavaScript Recursive Search On An Array Of Objects

I have an array of objects that have deeply nested children and sometimes children within children. I am attempting to handle this recursively, but I am getting stuck.
The goal of the function is to return a single data object that matches the id.
My Data looks like this:
data: [
{
id: 'RAKUFNUBNY00UBZ40950',
name: 'Grade 1 Cover',
activityId: 'RAKUFNUBNY00UBZ40950',
nodeType: 'activity',
suppressed: false,
hidden: false
},
{
children: [
{
id: 'SLWDYEQHTZAFA3ALH195',
name: 'Build Background Video',
activityId: 'SLWDYEQHTZAFA3ALH195',
nodeType: 'activity',
suppressed: false,
hidden: false,
assetReference: {
referenceId: 'UWFHA5A1E0EGKCM0W899',
assetType: 'image'
}
},
{
children: [
{
id: 'HQUCD2SSRKMYC2PJM636',
name: 'Eat or Be Eaten Splash Card',
activityId: 'HQUCD2SSRKMYC2PJM636',
nodeType: 'activity',
suppressed: false,
hidden: true
},
{
children: [
{
id: 'ZDTWEZFL13L8516VY480',
name: 'Interactive Work Text: Eat or Be Eaten',
activityId: 'ZDTWEZFL13L8516VY480',
nodeType: 'activity',
suppressed: false,
hidden: true,
defaultLaunchMode: 'modal'
}
],
My attempt at solving this is like this:
findNode(id, currentNode) {
console.log('id', id);
console.log('findNode', currentNode);
var i, currentChild, result, counter;
counter = 0;
console.log('first conditional statement', currentNode);
if (id && currentNode.id === id) {
return currentNode[0];
} else {
counter++;
// Use a for loop instead of forEach to avoid nested functions
// Otherwise "return" will not work properly
console.log('counter', counter);
console.log('currentNode', currentNode[counter]);
console.log('currentNode Children', currentNode.children);
for (i = counter; i < currentNode.children.length; i += 1) {
console.log(currentNode[i].children[i]);
currentChild = currentNode[i].children[i];
// Search in the current child
result = this.findNode(id, currentChild);
// Return the result if the node has been found
if (result !== false) {
return result;
}
}
// The node has not been found and we have no more options
return false;
}
}
The code above fails because I having an extremely difficult time keeping track of a counter to loop through everything.
I also added a sample picture of my data output to give you a better example of how my data is structured. Any help will be greatly appreciated.
You shouldn't need a counter to locate a single node with a matching id. Try this simpler approach:
function findNode (id, array) {
for (const node of array) {
if (node.id === id) return node;
if (node.children) {
const child = findNode(id, node.children);
if (child) return child;
}
}
}
It will return undefined if there is no match.
To avoid the need for manual iteration, you might consider using an array method like reduce instead - return the accumulator if it's truthy (that is, an object was found already), or return the object being iterated over if the ID matches, or recursively iterate over the object's children to find a match.
const data=[{id:'RAKUFNUBNY00UBZ40950',name:'Grade 1 Cover',activityId:'RAKUFNUBNY00UBZ40950',nodeType:'activity',suppressed:!1,hidden:!1},{children:[{id:'SLWDYEQHTZAFA3ALH195',name:'Build Background Video',activityId:'SLWDYEQHTZAFA3ALH195',nodeType:'activity',suppressed:!1,hidden:!1,assetReference:{referenceId:'UWFHA5A1E0EGKCM0W899',assetType:'image'}},{children:[{id:'HQUCD2SSRKMYC2PJM636',name:'Eat or Be Eaten Splash Card',activityId:'HQUCD2SSRKMYC2PJM636',nodeType:'activity',suppressed:!1,hidden:!0},{children:[{id:'ZDTWEZFL13L8516VY480',name:'Interactive Work Text: Eat or Be Eaten',activityId:'ZDTWEZFL13L8516VY480',nodeType:'activity',suppressed:!1,hidden:!0,defaultLaunchMode:'modal'}],}],}],}]
function findId(id, arr) {
return arr.reduce((a, item) => {
if (a) return a;
if (item.id === id) return item;
if (item.children) return findId(id, item.children);
}, null);
}
console.log(findId('HQUCD2SSRKMYC2PJM636', data));
If your ids are unique and finding an object by id is a common task, you might want to consider creating a lookup object to improve performance. Creating the lookup object is an O(n) task; afterwards, looking up an object by id is O(1).
const data = [ { id: 'RAKUFNUBNY00UBZ40950', name: 'Grade 1 Cover', activityId: 'RAKUFNUBNY00UBZ40950', nodeType: 'activity', suppressed: false, hidden: false }, { children: [ { id: 'SLWDYEQHTZAFA3ALH195', name: 'Build Background Video', activityId: 'SLWDYEQHTZAFA3ALH195', nodeType: 'activity', suppressed: false, hidden: false, assetReference: { referenceId: 'UWFHA5A1E0EGKCM0W899', assetType: 'image' } }, { children: [ { id: 'HQUCD2SSRKMYC2PJM636', name: 'Eat or Be Eaten Splash Card', activityId: 'HQUCD2SSRKMYC2PJM636', nodeType: 'activity', suppressed: false, hidden: true }, { children: [ { id: 'ZDTWEZFL13L8516VY480', name: 'Interactive Work Text: Eat or Be Eaten', activityId: 'ZDTWEZFL13L8516VY480', nodeType: 'activity', suppressed: false, hidden: true, defaultLaunchMode: 'modal' } ] } ] } ] } ];
const lookup = {};
const registerIds = a => {
a.forEach(o => {
if ('id' in o) {
lookup[o.id] = o;
} else if ('children' in o) {
registerIds(o.children)
}
});
}
registerIds(data);
console.log(lookup)
Sorry for my two cents, just want to add a universal method that includes nested arrays
const cars = [{
id: 1,
name: 'toyota',
subs: [{
id: 43,
name: 'supra'
}, {
id: 44,
name: 'prius'
}]
}, {
id: 2,
name: 'Jeep',
subs: [{
id: 30,
name: 'wranger'
}, {
id: 31,
name: 'sahara'
}]
}]
function searchObjectArray(arr, key, value) {
let result = [];
arr.forEach((obj) => {
if (obj[key] === value) {
result.push(obj);
} else if (obj.subs) {
result = result.concat(searchObjectArray(obj.subs, key, value));
}
});
console.log(result)
return result;
}
searchObjectArray(cars, 'id', '31')
searchObjectArray(cars, 'name', 'Jeep')
I hope this helps someone

Javascript Tree Structure to Array conversion

I am working on a treeview and I build a simple Node-Class: It consists of a name and an array of children:
class Node {
constructor(name, childNodes) {
this.name = name;
this.childNodes = childNodes;
}
}
Now my aim is to create a function that returns an object like this:
var tree = [
{
text: 'Parent 1',
nodes: [
{
text: 'Child 1',
nodes: [
{
text: 'Grandchild 1'
}
]
},
{
text: 'Child 2'
}
]
},
{
text: 'Parent 2'
},
];
I tried using a recursive method. It starts with an empty array and adds children until there are no more left:
function recTreeview(currentNode, treeview) {
var tempChildren = [];
currentNode.childNodes.forEach(child => {
tempChild.push(recTreeview(child, treeview));
});
return treeview.push({
text: currentNode.name,
nodes: tempChildren
})
}
But something with the recursive Treeview Function has to be wrong. When I create the tree and try to open it in the chrome dev console, it just shows a "5" instead of something like (5) [{…}, {…}, {…}, {…}, {…}]. What did I do wrong?
tree = recTreeview(parent, []);
tree;
You are returning the result of the push and not the actual treeview.
As per the Array.prototype.push() docs
Return value
The new length property of the object upon which the method was called.
So instead of return treeview.push(...) do treeview.push(...) and then return treeview
function recTreeview(currentNode, treeview) {
var tempChildren = [];
currentNode.childNodes.forEach(child => {
tempChild.push(recTreeview(child, treeview));
});
treeview.push({
text: currentNode.name,
nodes: tempChildren
});
return treeview;
}

How do I populate a hierarchy with Promises where each level needs the previous level fulfilled?

This is the first version of the code that I have attempted. I've tried a whole lot of other things like mutual exclusion, adding catch blocks everywhere, and Promise anti-patterns, but I can't seem to get over this mental or syntactical block:
populateJoins() {
let promises = [];
for (let c in this.columns) {
let transformColumn = this.columns[c];
if (transformColumn.joins) {
let joinPointer = this.databaseObject;
for (let j in transformColumn.joins) {
let join = transformColumn.joins[j];
if (joinPointer[join.as] != null) {
joinPointer = joinPointer.dataValues[join.as];
} else {
if (this.requestQuery[toCamelCase(join.as) + 'Id']) {
promises.push(
join.model.findOne({where: {id: this.requestQuery[toCamelCase(join.as) + 'Id']}})
.then((tmp) => {
joinPointer.dataValues[join.as] = tmp;
}));
} else if (joinPointer[toSnakeCase(join.as) + '_id']) {
promises.push(
join.model.findOne({where: {id: joinPointer[toSnakeCase(join.as) + '_id']}})
.then((tmp) => {
joinPointer.dataValues[join.as] = tmp;
}));
}
}
}
}
}
return Promises.all(promises);
}
And this is the structure of this.columns:
child1Name: {
name: 'name',
forceSelect: true,
joins: [{
model: Database.getInstance().getModel('child1'),
as: 'Child1'
}],
hidden: true
},
child2Name: {
name: 'name',
forceSelect: true,
joins: [{
model: Database.getInstance().getModel('child2'),
as: 'Child2'
}],
hidden: true
},
child1Status1Name: {
name: 'name',
forceSelect: true,
joins: [{
model: Database.getInstance().getModel('child1'),
as: 'Child1'
},{
model: Database.getInstance().getModel('status1'),
as: 'Status1'
}],
hidden: true
},
child1Status2Name: {
name: 'name',
forceSelect: true,
joins: [{
model: Database.getInstance().getModel('child1'),
as: 'Child1'
},{
model: Database.getInstance().getModel('status2'),
as: 'Grandchild2'
}],
hidden: true
},
serverName: {
name: 'name',
forceSelect: true,
joins: [{
model: Database.getInstance().getModel('child1'),
as: 'Child2'
},{
model: Database.getInstance().getModel('grandchild'),
as: 'Grandchild'
},{
model: Database.getInstance().getModel('great_grandchild'),
as: 'GreatGrandchild'
}],
hidden: true
},
child2Status1Name: {
name: 'name',
forceSelect: true,
joins: [{
model: Database.getInstance().getModel('child2'),
as: 'Child2'
},{
model: Database.getInstance().getModel('status1'),
as: 'Grandchild1'
}],
hidden: true
},
child2Status2Name: {
name: 'name',
forceSelect: true,
joins: [{
model: Database.getInstance().getModel('child2'),
as: 'Child2'
},{
model: Database.getInstance().getModel('status2'),
as: 'Grandchild2'
}],
hidden: true
},
archetypeName: {
name: 'name',
forceSelect: true,
joins: [{
model: Database.getInstance().getModel('child2'),
as: 'Child2'
},{
model: Database.getInstance().getModel('archetype'),
as: 'Archetype'
},{
model: Database.getInstance().getModel('archetype'),
as: 'ArchetypeLink'
}],
hidden: true
},
So for things I've already learned, joinPointer[join.as] != null will never prevent duplicate Child database calls from firing because the property will not be populated until the promises finish resolving.
Similarly, none of the grandchildren will populate because they have to wait for the children to populate, and if the grandchildren fulfill first, then they will never make it into the child object. The same goes for great-grandchildren.
I read this answer, where he says, "If you already have them in an array then they are already executing." I understand that the contents of the Promise will already resolve, which is why in other code I always use numerical indices to populate objects, i.e. jsonObject['list'][i]['anotherList'][j] = ...;, but I don't see how I can do this here.
I've been working on this for a while and haven't come up with a solution, so any workable code is more than appreciated.
The code in the question is difficult to follow but it appears that what you are trying to do is reasonably simple, ie execute a set of asynchronous findOne queries in series and progressively construct an ever-deeper hierarchy comprising the returned results.
If so, then :
you can use a reduce() pattern, see "The Collection Kerfuffle" here.
the full .columns object is unnecessary - you just need .columns.greatGrandchildName.joins.
The code should look at least something like this :
populateJoin(joins) {
return joins.reduce((p, join) => {
return p.then(obj => {
let id = this.requestQuery[toCamelCase(join.as) + 'Id'] || obj[toSnakeCase(join.as) + '_id'] || obj[toSnakeCase(join.as + 's') + '_id'] || null;
if(id) {
return join.model.findOne({'where': { 'id': id }}).then(tmp => {
if (obj.dataValues[join.as]) {
return obj.dataValues[join.as];
} else {
obj.dataValues[join.as] = tmp;
return tmp; // 'tmp' will be `obj` at next iteration of the reduction.
}
return tmp; // 'tmp' will be `obj` at next iteration of the reduction.
});
} else {
return obj; // send unmodified obj to next iteration of the reduction.
}
});
}, Promise.resolve(this.databaseObject)) // starter promise for the reduction
.then(() => this.databaseObject); // useful though not essential to make the top level object available to the caller's .then() callback.
}
populateJoins() {
var promises = [];
for (let c in this.columns) {
let transformColumn = this.columns[c];
if (transformColumn.joins) {
promises.push(this.populateJoin(transformColumn.joins));
}
}
return Promise.all(promises);
}

Update part of model from scope selected object

Ok, I have model with one property(provider) as object. It can change at all.
There is example, where I change provider. There can be any parametrs, image can has dpi, json can has another parametr.
So, when I select anoter provider, how to merge model property(provider) and updated provider?
this.providerWasChange = function() {
// here I should update model with provider parametrs(update full object)
$scope.provider
}
https://jsfiddle.net/77z165uj/11/
Hm,
var model = {
id: '1',
name: '',
childModels: [{
id: '1.1',
name: 'item1',
provider: {
name: 'imageProvider'
options: {
transparent: false,
dpi: 96
}
}
}, {
id: '1.2',
name: 'item2'
provider: {
name: 'jsonProvider'
options: {
uppercase: true,
}
}
}]
}
$scope.providers = [{
name: 'jsonProvider',
displayNmae: "jsonProvider",
options:{
uppercase:$scope.providerOptions,
}
}, {
name: 'imageProvider',
displayNmae: "imageProvider",
options:{
transparent:$scope.transparent,
dpi::$scope.dpi
}
}];
_changeProvider = function(data) {
if (data !== null) {
for (var i = 0; i < $scope.providers.length; i++) {
if ($scope.providers[i].name === data.name) {
$scope.providers[i].options = data.options
return $scope.providers[i];
}
};
}
}
I'm looking for a fuction or angular method, that set chosen provider blank with setted options from model back. For example, I'd like to change provider of item 2 to image provider(old values(if there is coincidence) should rewrites to model(item2), other should be deleted, and new - setted)

How can I make this function more efficient?

I have this data structure of credit card types.
It would be nice to make the hasTransFee more efficient. If I started adding storecards etc to this list it could get quite big so the faster it works the better.
Anyone have any suggestions?
$scope.creditCards = [
{ name: 'VISA DEBIT/DELTA', value: 'DEL', transactionFee: false},
{ name: 'VISA CREDIT', value: 'VIS', transactionFee: true },
{ name: 'MASTERCARD CREDIT', value: 'MSC', transactionFee: true },
{ name: 'MASTERCARD DEBIT', value: 'MCD', transactionFee: false },
{ name: 'MAESTRO', value: 'MAE', transactionFee: false },
{ name: 'SWITCH', value: 'SWI', transactionFee: false },
{ name: 'VISA ELECTRON', value: 'ELC', transactionFee: false },
{ name: 'SOLO', value: 'SOL', transactionFee: false }
];
var hasTransFee = function(cardType)
{
for (var i=0; i < $scope.creditCards.length; i++) {
if($scope.creditCards[i].value==cardType && $scope.creditCards[i].transactionFee == true){
return true;
}
}
return false;
}
It seems that value is a unique identifier, if that's the case you could store the "credit cards" in an object instead, like this:
$scope.creditCards = {
'DEL': { name: 'VISA DEBIT/DELTA', transactionFee: false},
'VIS': { name: 'VISA CREDIT', transactionFee: true },
'MSC': { name: 'MASTERCARD CREDIT', transactionFee: true }
};
And then you don't even need a function for checking if the Credit Card has a transactionFee, if you still want to have a function, that function would look like this:
var hasTransFee = function(cardType){
return $scope.creditCards[cardType].transactionFee;
}
Side-note: This is better suited for CodeReview
Try this:
var hasTransFee = function(cardType)
{
var thecard;
$scope.creditCards.some(function(item) {
return item.value == cardType && (thecard = item);
});
return thecard && thecard.transactionFee;
};
This will only iterate as far as it needs to in order to find the card with the right type, and no further (your original code will continue to scan the whole array if the card is found but has no fee).
Important: Only ONE = sign in && thecard = item. It is deliberately an assignment, required to make the final return work.

Categories