compound index/searching on child array - javascript

Based on the data below, I'm looking to do something like "find block 1 where the parent objects name is 'Panel'"
So, I tried setting up a compound index like this:
objStore.createIndex('by_name_and_block', ['Name', 'blocks.Name']);
And then calling it (sort-of) like this:
var index = objStore.index("by_name_and_block");
var request = index.get("Panel", "1");
// I've also tried:
// var request = index.get(["Panel","1"]);
...
But this doesn't work. Is there a way to set up this compound index in indexeddb?
Sample data:
[
{
Name: "Post",
blocks: [
{
Name:"1",
Arrays:[]
},
{
Name:"2",
Arrays:[]
},
]
},
{
Name: "Panel",
blocks: [
{
Name:"1",
Arrays:[]
},
{
Name:"2",
Arrays:[]
},
]
},
]

Your data is not able to index with current specification. See steps for extracting key from keyPath. Notice that object is not valid key value in array key path.
In v2, you will be able to use with index function expression.
Currently, you will have to generate extra variable before you persist into the database and remove it after retrieval. Use multiEntry index without compound index.

Is this script that what you want?
var obj = [
{
Name: "Post",
blocks: [
{
Name:"1",
Arrays:[]
},
{
Name:"2",
Arrays:[]
},
]
},
{
Name: "Panel",
blocks: [
{
Name:"1",
Arrays:[]
},
{
Name:"2",
Arrays:[]
},
]
},
];
function getBlockByName(objName, index){
for(var i = 0; i < obj.length; i++){
if(obj[i].Name == objName)
return obj[i].blocks[index];
}
return false;
}
//Index starting from 0
console.log(getBlockByName("Panel", 1));
//Will return {Name:"2", Arrays:[]} object

Related

Mongodb change the order of an array

I am stumped on this one. I have an images array with in my collection, the users can rearrange the order of images on the client side and I am trying to save the new order to the database. The imagesOrder array is the new images in the new order and it only has the url so I want to match the url to the urls in the database. I am not sure how to make the index a variable or if this is possible:
this is what I have so far. my code editor shows and error on [index] so I know that is not the proper format but not sure what is:
imagesOrder.forEach((index, image) => {
const imageUrl = image.url
const index = index
Users.update({
id
}, {
$set: {
images[index]: imageUrl
}
})
});
So that is not actually the way you would do this. Basically there is no need to actually send an update request to the server for every single indexed position for the array. Also the update() method is asynchronous, so it's not something you ever put inside a forEach() which does not respect awaiting the completion of an asynchronous call.
Instead what is usually the most practical solution is to just $set the entire array content in one request. Also mocking up your imagesOrder to something practical since forEach() even actually has the signature of .forEach((<element>,><index>) => ..., which seems different to what you were expecting given the code in the question.
var imagesOrder = [
{ index: 0, url: '/one' }, { index: 1, url: '/two' }, { index: 2, url: '/three' }
];
let response = await Users.updateOne(
{ id },
{ "$set": { "images": imagesOrder.map(({ url }) => url) } }
);
// { "$set": { "images": ["/one","/two","/three"] } }
Much like the forEach() a map() does the same array iteration but with the difference that it actually returns an array generated by the processing function. This is actually what you want since all that is needed here is to simply extract the values of the url property from each object.
Note the index properties are actually already in order and really redundant here, but I'm just approximating what it sounds like you have from your question. Since an "array" actually maintains it's own order then such a property "should" be redundant and it would be advisable that your source array data actually conforms to this.
If however you managed to record such index values in a way they are actually out of order, then the best solution is to add a sort():
var imagesOrder = [
{ index: 2, url: '/three' }, { index: 0, url: '/one' }, { index: 1, url: '/two' }
];
let response = await Users.updateOne(
{ id },
{ "$set": {
"images": imagesOrder.sort((a,b) => a.index - b.index)
.map(({ url }) => url)
}}
);
// { "$set": { "images": ["/one","/two","/three"] } }
As for what you "attempted", it's not really benefiting you in any way to actually attempt updating each element at a given position. But if you really wanted to see it done, then again you actually would just instead build up a single update request:
var imagesOrder = [
{ index: 2, url: '/three' }, { index: 0, url: '/one' }, { index: 1, url: '/two' }
];
var update = { $set: {} };
for ( let { url, index } of imagesOrder.sort((a,b) => a.index - b.index) ) {
update.$set['images.'+index] = url;
}
/*
* Creates:
*
* { "$set": {
* "images.0": "/one",
* "images.1": "/two",
* "images.2": "/three"
* }}
*/
let response = await Users.updateOne({ id }, update);
Or in the case where the index property was not there or irrelevant since the array is already ordered:
var imagesOrder = [
{ index: 2, url: '/three' }, { index: 0, url: '/one' }, { index: 1, url: '/two' }
];
for ( let [index, { url }] of Object.entries(imagesOrder) ) {
update.$set['images.'+index] = url;
}
/*
* Creates:
*
* { "$set": {
* "images.0": "/one",
* "images.1": "/two",
* "images.2": "/three"
* }}
*/
let response = await Users.updateOne({ id }, update);
So it's all pretty much the same thing. Note the common form of notation is actually a "string" for the key which includes the index position numerically. This is described in Dot Notation within the core documentation for the MongoDB query language.
The one main difference here is that should your new array contain more entries than the actual array stored in the document to be modified, that second form using the "dot notation" to the indexed position is going to fail since it cannot "set" an index position which does not exist.
For this reason even though there are other pitfalls to "replacing" the array as the original examples show, it's a lot safer than attempting to update via the positional index in the stored document.
Note that this should be enough to have you at least started in the right direction. Making this work with multiple users possibly updating the data at once can become pretty complicated in terms of update statements for both checking and merging changes.
In most cases the simple "replace" will be more than adequate at least for a while. And of course the main lesson here should be to not loop "async" methods in places where it is completely unnecessary. Most of the time what you really want to "loop" is the construction of the statement, if of course any looping is required at all and most of the time it really isn't.
Addendum
Just in case you or anyone had it in mind to actually store an array of objects with the index position values stored within them, this can become a little more complex, but it can also serve as an example of how to actually issue an update statement which does not "replace" the array and actually is safe considering it does not rely on indexed positions of the array but instead using matching conditions.
This is possible with the positional filtered $[<identifier>] syntax introduced in MongoDB 3.6. This allows conditions to specify which element to update ( i.e by matching url ) instead of including the index positions within the statement directly. It's safer since if no matching element is found, then the syntax allows for not attempting to change anything at all.
Also as demonstration the method to $sort the elements based on updated index values is shown. Noting this actually uses the $push modifier even though in this statement we are not actually adding anything to the array. Just reordering the elements. But it's how you actually do that atomically:
const { Schema, Types: { ObjectId } } = mongoose = require('mongoose');
const uri = 'mongodb://localhost:27017/longorder';
const opts = { useNewUrlParser: true };
// sensible defaults
mongoose.Promise = global.Promise;
mongoose.set('debug', true);
mongoose.set('useFindAndModify', false);
mongoose.set('useCreateIndex', true);
// schema defs
const imageSchema = new Schema({
index: Number,
url: String
})
const userSchema = new Schema({
images: [imageSchema]
});
const User = mongoose.model('User', userSchema);
// log helper
const log = data => console.log(JSON.stringify(data, undefined, 2));
(async function() {
try {
const conn = await mongoose.connect(uri, opts);
// clean models
await Promise.all(
Object.entries(conn.models).map(([k,m]) => m.deleteMany())
);
// Create data
let _id = new ObjectId();
let user = await User.findOneAndUpdate(
{ _id },
{
'$push': {
'images': {
'$each': [
{ index: 2, url: '/one' },
{ index: 0, url: '/three' },
{ index: 1, url: '/two' }
],
'$sort': { 'index': 1 }
}
}
},
{ 'new': true, 'upsert': true }
);
log(user);
// Change order request
let orderImages = [
{ index: 2, url: '/three' },
{ index: 0, url: '/one' },
{ index: 1, url: '/two' }
];
let $set = { };
let arrayFilters = [];
for ( { index, url } of orderImages ) {
let key = url.replace(/^\//,'');
arrayFilters.push({ [`${key}.url`]: url });
$set[`images.$[${key}].index`] = index;
}
let ops = [
// Update the index value of each matching item
{ 'updateOne': {
'filter': { _id },
'update': { $set },
arrayFilters
}},
// Re-sort the array by index value
{ 'updateOne': {
'filter': { _id },
'update': {
'$push': {
'images': { '$each': [], '$sort': { 'index': 1 } }
}
}
}}
];
log(ops);
let response = await User.bulkWrite(ops);
log(response);
let newuser = await User.findOne({ _id });
log(newuser);
} catch(e) {
console.error(e)
} finally {
mongoose.disconnect()
}
})()
And the output, showing initial document state, the update and actual changes made:
Mongoose: users.deleteMany({}, {})
Mongoose: users.findOneAndUpdate({ _id: ObjectId("5bf6116621293f2ab3dec3d3") }, { '$setOnInsert': { __v: 0 }, '$push': { images: { '$each': [ { _id: ObjectId("5bf6116621293f2ab3dec3d6"), index: 2, url: '/one' }, { _id: ObjectId("5bf6116621293f2ab3dec3d5"), index: 0, url: '/three' }, { _id: ObjectId("5bf6116621293f2ab3dec3d4"), index: 1, url: '/two' } ], '$sort': { index: 1 } } } }, { upsert: true, remove: false, projection: {}, returnOriginal: false })
{
"_id": "5bf6116621293f2ab3dec3d3",
"__v": 0,
"images": [
{
"_id": "5bf6116621293f2ab3dec3d5",
"index": 0,
"url": "/three"
},
{
"_id": "5bf6116621293f2ab3dec3d4",
"index": 1,
"url": "/two"
},
{
"_id": "5bf6116621293f2ab3dec3d6",
"index": 2,
"url": "/one"
}
]
}
[
{
"updateOne": {
"filter": {
"_id": "5bf6116621293f2ab3dec3d3"
},
"update": {
"$set": {
"images.$[three].index": 2,
"images.$[one].index": 0,
"images.$[two].index": 1
}
},
"arrayFilters": [
{
"three.url": "/three"
},
{
"one.url": "/one"
},
{
"two.url": "/two"
}
]
}
},
{
"updateOne": {
"filter": {
"_id": "5bf6116621293f2ab3dec3d3"
},
"update": {
"$push": {
"images": {
"$each": [],
"$sort": {
"index": 1
}
}
}
}
}
}
]
Mongoose: users.bulkWrite([ { updateOne: { filter: { _id: 5bf6116621293f2ab3dec3d3 }, update: { '$set': { 'images.$[three].index': 2, 'images.$[one].index': 0, 'images.$[two].index': 1 } }, arrayFilters: [ { 'three.url': '/three' }, { 'one.url': '/one' }, { 'two.url': '/two' } ] } }, { updateOne: { filter: { _id: 5bf6116621293f2ab3dec3d3 }, update: { '$push': { images: { '$each': [], '$sort': { index: 1 } } } } } } ], {})
{
"ok": 1,
"writeErrors": [],
"writeConcernErrors": [],
"insertedIds": [],
"nInserted": 0,
"nUpserted": 0,
"nMatched": 2,
"nModified": 2,
"nRemoved": 0,
"upserted": [],
"lastOp": {
"ts": "6626503031506599940",
"t": 139
}
}
Mongoose: users.findOne({ _id: ObjectId("5bf6116621293f2ab3dec3d3") }, { projection: {} })
{
"_id": "5bf6116621293f2ab3dec3d3",
"__v": 0,
"images": [
{
"_id": "5bf6116621293f2ab3dec3d6",
"index": 0,
"url": "/one"
},
{
"_id": "5bf6116621293f2ab3dec3d4",
"index": 1,
"url": "/two"
},
{
"_id": "5bf6116621293f2ab3dec3d5",
"index": 2,
"url": "/three"
}
]
}

trying to create an array of objects given 2 arrays + map

I have the following 2 arrays here:
> chNameArr
[ 'chanel1',
'chanel2',
'chanel3',
'chanel4',
'chanel5',
'chanel6',
'chanel7' ]
and here:
> a
[ 'channelName'
'status',
'connections',
'socketIds',
'lastRun',
'numberOfRuns',
'timeout' ]
what I am trying to achieve is the following objects per channel in an array where channelName from a get the value from chNameArr but the rest of 'a' gets an empty string
file=[{"channelName":"chanel1","status":"", "connections":"", "socketIds":"", "lastRun":"", "numberOfRuns":"", "timeout":""},
.
.
.
{"channelName":"chanel7","status":"", "connections":"", "socketIds":"", "lastRun":"", "numberOfRuns":"", "timeout":""}]
this is my attempt
> chNameArr.map(function(d){return {channelName:d}})
[ { channelName: 'chanel1' },
{ channelName: 'chanel2' },
{ channelName: 'chanel3' },
{ channelName: 'chanel4' },
{ channelName: 'chanel5' },
{ channelName: 'chanel6' },
{ channelName: 'chanel7' } ]
chNameArr.map(function(d) {
result = {};
result[a[0]] = d;
for (var i=1; i<a.length; i++) {
result[a[i]] = "";
}
return result;
})
There is no one-liner to solve this problem in general, although if you don't actually need to use the array a you could manually construct {channelName:d, status: "", ...} in your original map.

Modify a JavaScript Object while iterating its properties

Can javascript implement pass-by-reference techniques on function call? You see, I have the JSON below and I need to traverse all its node. While traversing, if the current item is an Object and contains key nodes, I must add another property isParent: true to that exact same item. But I'm having difficulty on creating a traversal function with such feature, and I tried to search for traversal functions, but all I found only returns a new JSON object instead of changing the exact JSON that is being processed.
var default_tree = [
{
text: "Applications",
nodes: [
{
text: "Reports Data Entry",
nodes: [
{ text: "Other Banks Remittance Report" },
{ text: "Statement of Payroll Deduction" },
...
]
},
{
text: "Suspense File Maintenance",
nodes: [
{ text: "Banks with Individual Remittances" },
{ text: "Employers / Banks with Employers" },
...
]
}
]
},
{
text: "Unposted Transactions",
nodes: [
{ text: "Unposted Borrower Payments"},
{ text: "Unposted CMP Payments"}
]
},
{ text: "Maintenance" },
{
text: "Reports",
nodes: [
{
text: "Daily Reports",
nodes: [
{
text: "List of Remittance Reports",
nodes: [
{ text: "Banks" },
...
{
text: "Employers-LBP",
nodes: [
{ text: "Employers-Zonal" }
]
},
]
},
...
]
},
...
]
}
]
Considering we have this traversal function:
function traverse(json_object) {
// perform traversal here
}
traverse(default_tree)
After it runs the traverse function, the default_tree's value will remain the same unless we do something like:
default_tree = traverse(default_tree)
Can someone help me create an iterator will really alter the Object being processed while iterating, instead of returning a new Object?
Please check this one
var default_tree = [....] //Array
function traverse(arrDefaultTree){
arrDefaultTree.forEach(function(val,key){
if(val.hasOwnProperty("nodes")){
val.isParent = true;
traverse(val.nodes);
}
})
}
traverse(default_tree);
console.log(default_tree);
Hope this helpful.
With two functions, one that call then self. One find the nodes and the other one loop througgh the arrays.
traverseTree(default_tree);
function traverseTree (tree) {
var i = 0, len = tree.length;
for(;i < len; i++) {
var obj = tree[i];
findNodes(obj);
}
}
function findNodes (obj) {
var keys = Object.keys(obj);
if (keys.indexOf('nodes') > -1) {
obj.isParent = true;
traverseTree(obj.nodes);
}
}

How to bind an array to a ListView

I know how to parse with Json and bind a file like this to a listview:
[
{
"key": "Arthur Schopenhauer",
"numeroFrasi": 3,
"foto" : "images/TEST.jpg",
},
{
"key": "Nietzsche",
"numeroFrasi": 1,
"foto" : "images/TEST.jpg",
},
.........
But I can't understand nor find on Web how to bind just every "frasi" (that is an array) in a file like this:
[
{
"key": "Arthur Schopenhauer",
"numeroFrasi": 3,
"foto" : "images/TEST.jpg",
"frasi": [
"Fras1",
"Frase 2 schopenahuer",
"Frase 3 schopenhahuer"
]
},
{
"key": "Nietzsche",
"numeroFrasi": 1,
"foto" : "images/TEST.jpg",
"frasi": [
"Frase 2 nietzsche",
"Frase 3 nietzsche"
]
},
...............
My Object isn't an array, but it is definied like this form a txt file parsed with Json:
This is the generic definition:
(function () {
"use strict";
var list = new WinJS.Binding.List();
var groupedItems = list.createGrouped(
function groupKeySelector(item) { return item.group.key; },
function groupDataSelector(item) { return item.group; }
);
WinJS.xhr({ url: "/data/frasi.txt" }).then(function (xhr) {
var items = JSON.parse(xhr.responseText);
// Add the items to the WinJS.Binding.List
items.forEach(function (item) {
list.push(item);
});
});
Then this is the specific definition (because when I navigate to my page, I select just an "item", so only one "key, "numeriFrasi", "foto", "frasi":
WinJS.UI.Pages.define("/pages/itemDetail/itemDetail.html", {
ready: function (element, options) {
item = options && options.item ? Data.resolveItemReference(options.item) : Data.items.getAt(0);
"resolveItemReference" gets one item from all the items created
Strip out frasis to an Array first. You can use underscore.js
frasis = YOUROBJECT.map(function(el){return el.frasi;});
frasis = _(frasis).faltten();
Then use it to build your ListView

Jquery object search

I have the following object and what I would like achieve is to get the index of theme if the name has match with a variable.
for example: I'm making a loop in the views and if my task (something1) variable has matches with the name element than to return the index of object.
By the given example I should have as result 0,
var views = [
{
name: "something1",
type: something1,
columns: something1
},
{
name: "something2",
type: something2,
columns: something2
},
{
name: "something3",
type: something3,
columns: something3
}
];
var task = 'something1';
$.each(views, function(index, value) {
if (value.name = task) {
alert(index);
}
});
You dont really need jQuery for this:
See: http://jsfiddle.net/enNya/2/
var views = [
{
name: "something1",
type: "something1",
columns: "something1"
},
{
name: "something2",
type: "something2",
columns: "something2"
}
];
var task = 'something2';
// Set a var and maintain scope
var i;
// Loop each element of the array
for (i = 0; i < views.length; i++) {
// If the X = Y the stop looping
if (views[i].name == task) {
break;
}
}
// Check if it was not found
i = i == views.length ? false : i;
// Log the result
console.log(i);
It's just a matter of syntax, as lgt said don't forget toseparate elements within your object with commas. Aslo the correct 'equal' operator is '=='.
'value.name=task' would be always true. It means can I affect the content of 'task' into 'value.name'.
Here is your valid js.
Note that in this example you'll get 2 alertbox.. ;)
var views=[
{
name:"something1",
type:'something1',
columns:'something1'
},
{
name:"something1",
type:'something1',
columns:'something1'
},
{
name:"something2",
type:'something2',
columns:'something2',
},
];
var task='something1';
$.each(views, function(index, value) {
if (value.name==task){
alert(index);
}
});
replace something1 variable for 0 and value.name == task (double =)
var views=[{
name:"something1",
type:0,
columns:0
}, {
name:"something1",
type:0,
columns:0
}, {
name:"something2",
type:0,
columns:0
}];
var task='something1';
$.each(views, function(index, value) {
if (value.name==task){
return index;
}
});

Categories