I'm trying to write an order form for a food delivery website (to learn more about objects in Javascript), so that each item, when selected, populates an array. It should also recognise the quantity requested, if an item is clicked twice, and not create a duplicate. This is my code, which works, but seems overlong. In particular, my final for loop, for increasing quantity, seems as if it should be superfluous. Is there a smarter way doing this?
The JSON data I'm drawing from looks like this:
"dishes": [
{
"id": "1",
"name": "Jumbo Chicken Wings",
"description": "Honey, Ginger, Chilli, Soy",
"price": "4.50"
},
{
"id": "2",
"name": "Spiced Whitebait",
"description": "Lemon Mayonaise",
"price": "5"
}
And this is my (probably overlong) code:
// ORDER FORM
// begin with empty array
var orderform = [];
function chooseDish() {
// when clicking on item...
$("#menu li").click(function(){
//create new object from item listed properties
var item = {
id: $(this).attr('id'),
dish: $(this).find("span.dish-name").text(),
price: $(this).find("span.dish-price").text()
};
// either add first item to array...
if (!orderform.length) {
item.quantity = 1;
orderform.push(item);
}
// or see if an object is already in the array.
else {
var hasDishBeenSelected = false;
for (var i = 0; i < orderform.length; i++) {
if (orderform[i].id === item.id) {
hasDishBeenSelected = true;
}
}
// if the item hasn't already been selected add the item...
if (!hasDishBeenSelected) {
item.quantity = 1;
orderform.push(item);
}
// otherwise increase that item's quantity by 1
else {
for (var j = 0; j < orderform.length; j++) {
if (orderform[j].id === item.id) {
orderform[j].quantity += 1;
}
}
}
}
console.table(orderform);
});
}
Thanks for any tips!
Related
How could I create a Pagination System in JavaScript? There should be 10 products per page.
I have created an Array of products. I want to loop through all of these products and dsiplay the first 10 products on the first page, and then the next 10 products on the next page.
I have created this Array:
let products = {
data: [
{
productName: "Product1",
},
{
productName: "Product2",
},
{
productName: "Product3",
},
{
productName: "Product4",
},
{
productName: "Product5",
},
{
multiple other products
},
],
};
I have looped through all of the products and displayed them on screen like this:
for (let i of products.data) {
let card = document.createElement("div");
let name = document.createElement("h5");
container.appendChild(name);
card.appendChild(container);
document.getElementById("products").appendChild(card);
}
I want to do this in Vanilla JavaScript
Use the .slice method on the array to to get only a subset of what's within it. .slice(start, end) returns the items in the array starting with <start> and ending with <item before end>.
console.log([0,1,2,3,4,5].slice(2,4));
// returns [2,3]
You'd need additional code and buttons elsewhere on the page to keep track of the start position, but loop would be modified as follows:
for (let i of products.data.slice(start, start + 10)) {
I would make an array for each page:
let pages = new Object;
let pageNumber = 1;
for (let i = 0; i < products.data.length; i++) {
if(i === 0) {
pageNumber = 1
} else {
pageNumber = Math.ceil(i / 10);
}
if (Array.isArray(pages[`page${pageNumber}`])){
pages[`page${pageNumber}`].push(products.data[i]);
} else {
pages[`page${pageNumber}`] = new Array;
pages[`page${pageNumber}`].push(products.data[i]);
}
}
Change this:
for (let i of products.data) {
...
}
To this:
function displayPage(pageNumber) {
for (let i of pages[pageNumber]) {
...
}
}
Then I would run a for loop looping over the pages and making a button for each and displaying it when necessary. I would give each button an onclick that will call the
displayPage()
I am having an array of object. And I am looping every object and performing some operation and again in the end splice that element from that array.
But I am not been able to achieve the desired result.
Here is what I have tried:
This is a sample array:
var arraylist=[{"username":"fzxd","contry":"vxcvxc"},
{"username":"fzxdfsdf","contry":"vxcvxc"},
{"username":"fsd","contry":"fsdf"},
{"username":"fsdf","contry":"werr"}];
var l = arraylist.length;
for(var i = 0; i < l; i++)
{
// For looping the item and doing some operation..
console.log(arraylist[i].username + " " + arraylist.length);
arraylist.splice(i,1); //In the end splicing it from the actual arraylist
}
When I run this only get fzxd 4 and fsd 3 printed on my log and not all elements.
Where I am doing wrong? Please guide me. Thanks!
If you splice out the first element, the element that was at the second position is now at the first position, the element at the second position is the third one. Therefore you will skip the second one. Instead of accessing arraylist[i] take arraylist[0] as well as arraylist.splice(0, 1). Or just:
let users = [{ /*...*/ }, /*...*/ ];
for(const user of users) {
// do stuff with user
}
users = []; // clear array.
on last operation arraylist.length less then index;
.splice it's side effect operation and after every calls as a result your index was moved and your array was changed.
var arraylist = [{ "username": "fzxd", "contry": "vxcvxc" }, { "username": "fzxdfsdf", "contry": "vxcvxc" }, { "username": "fsd", "contry": "fsdf" }, { "username": "fsdf", "contry": "werr" }]
var l = arraylist.length;
for (var i = 0; i < l; i++) {
//For looping the item and doing some operation..
console.log(arraylist.length, i) // on last operation arraylist.length less then index
//console.log(arraylist[i].username + " " + arraylist.length);
arraylist.splice(i, 1); //In the end splicing it from the actual arraylist
}
You can do it like this:
var arraylist = [{ "username": "fzxd", "contry": "vxcvxc" }, { "username": "fzxdfsdf", "contry": "vxcvxc" }, { "username": "fsd", "contry": "fsdf" }, { "username": "fsdf", "contry": "werr" }]
for(var i=arraylist.length-1; i>=0; i--)
{
console.log(arraylist[i].username);
arraylist.splice(i,1);
}
I hope this meets your requirement. Thanks!
How to convert a string to JSON with javascript or jQuery? I've been thinking all day, but I do not get a good idea.
This task is to dynamically create the treeview in the client side (ASP.Net). My idea is to convert the string to an object and convert to JSON type. (String -> object -> JSON) I tried, but the day is gone. It is difficult to construct 2 more depth like A->a3->a31.
String is
var sString = "A//a1,A//a2,A//a3//a31,A//a3//a32,B,C//c1,C//c2";
and JSON format is
{
"title": "A",
"key": "1",
"folder": true,
"children": [{
"title": "a1",
"key": "2"
}, {
"title": "a2",
"key": "3"
}, {
"title": "a3",
"key": "4",
"folder": true,
"children": [{
"title": "a31",
"key": "5"
}...
}]
}
(This is fancytreeview plugin)
‘//‘ is depth and ‘,’ is split.
Please help me..
Edit)
I want to turn ‘sString’ to JSON format.. but It’s ok just JSON type string.
Please understand that my sentence is strange because my native language is not English.
Edit2)
oh.. I want to convert the string to an object and then convert it back to JSON format. I do not have the confidence to convert that string into JSON format right away. Because there are more than 8000 variants. If It’s can, let me know how.
I believe this can be done without recursion:
var string = "A//a1,A//a2,A//a3//a31,A//a3//a32,B,C//c1,C//c2";
// Take all the roots
var roots = string.split(',');
// We will attach it to every node and keep it incrementing
var key = 1;
// The final result will be in this object
var result = [];
// Loop through to found roots
roots.forEach(function(root) {
// Take all the children
var items = root.split('//');
var parent = result;
// Loop through the available children
items.forEach(function(item, i) {
// Find if the current item exists in the tree
var child = getChild(parent, item);
if (!child) {
child = {
title: item,
key: key++
}
// This will ensure that the current node is a folder only
// if there are more children
if (i < items.length - 1) {
child.folder = true;
child.children = [];
}
// Attach this node to parent
parent.push(child);
}
parent = child.children;
});
});
console.log(result);
// Utility function to find a node in a collection of nodes by title
function getChild(parent, title) {
for (var i = 0; i < parent.length; i++) {
if (parent[i].title === title) {
return parent[i];
}
}
}
This is the draft code which came in my mind at first. I believe it can be improved further in terms of complexity.
var key = 1; // keys start at 1
let addPaths = (root, paths) => {
if (!paths || paths.length == 0)
return;
let path = paths.shift();
//add nodes for the current path
addNodes(root, path.split('//'));
// keep going until all paths have been processed
addPaths(root, paths);
};
let addNodes = (root, nodeList) => {
if (!nodeList || nodeList.length == 0)
return;
let title = nodeList.shift();
// find node under root with matching title
let isRootNode = Array.isArray(root);
node = (isRootNode ? root : root.children || []).find((node) => {
return node.title == title;
});
if (!node){
node = {
title: title,
key: key++
}
// are we at root of object?
if (isRootNode)
root.push(node);
else
{
if (!root.children)
root.children = [];
root.children.push(node);
root.folder = true;
}
}
addNodes(node, nodeList);
};
let parse = (string) => {
let object = [];
let nodes = string.split(',');
addPaths(object, nodes);
return object
};
console.log(JSON.stringify(parse("A//a1,A//a2,A//a3//a31,A//a3//a32,B,C//c1,C//c2"), null, 2));
Which results in:
[
{
"title": "A",
"key": 1,
"children": [
{
"title": "a1",
"key": 2
},
{
"title": "a2",
"key": 3
},
{
"title": "a3",
"key": 4,
"children": [
{
"title": "a31",
"key": 5
},
{
"title": "a32",
"key": 6
}
],
"folder": true
}
],
"folder": true
},
{
"title": "B",
"key": 7
},
{
"title": "C",
"key": 8,
"children": [
{
"title": "c1",
"key": 9
},
{
"title": "c2",
"key": 10
}
],
"folder": true
}
]
Try below code. I have used associative array to store already processed folder for faster lookup.
I hope it helps you.
var sString = "A//a1,A//a2,A//a3//a31,A//a3//a32,B,C//c1,C//c2";
var sArr = sString.split(","); // We will split it by comma so that we can iterate through its items.
var output = []; // Final result will be stored here.
var hash = {}; // It used to keep track of itemObjectect's position for faster lookup.
var counter = 1; // Its value will be used to assign to key;
for(var i = 0; i < sArr.length; i++){
var items = sArr[i].split("//");
var itemObject = {}; // Object to store value of each item.
var parentItemObject = {}; // It will refer to current parentObject during iteration.
for(var j = 0; j < items.length; j++){
// Check if item is already processed and stored in hash map.
if(hash.hasOwnProperty(items[j])){
// Check if parent Object value is empty then we will fetch it from hash directly.
if(isEmpty(parentItemObject)){
parentItemObject = output[hash[items[j]]];
}
else{
// It is parent element but is child of another element. Then we will fetch it from it's children array.
if(typeof parentItemObject.children !== "undefined"){
parentItemObject = parentItemObject.children[hash[items[j]]];
}
}
continue;
}
itemObject.title = items[j];
itemObject.key = counter++;
// Check if it is a folder item.
if(j != items.length -1){
itemObject.folder = true;
itemObject.children = [];
if(isEmpty(parentItemObject)){
parentItemObject = itemObject;
hash[itemObject.title] = output.length;
output.push(itemObject);
}
else{
if(typeof parentItemObject.children !== "undefined"){
hash[itemObject.title] = parentItemObject.children.length;
parentItemObject.children.push(itemObject);
}
parentItemObject = itemObject;
}
}
else{
if(isEmpty(parentItemObject)){
parentItemObject = itemObject;
hash[itemObject.title] = output.length;
output.push(itemObject);
}
if(typeof parentItemObject.children !== "undefined"){
hash[itemObject.title] = parentItemObject.children.length;
parentItemObject.children.push(itemObject);
}
}
itemObject = {};
}
//console.log(items);
}
function isEmpty(itemObject) {
return Object.keys(itemObject).length === 0;
}
//console.log(hash);
console.log(JSON.stringify(output,null,2));
I want to check if there exists duplicate outputTypeId in the output array object..
Below is the JSON:
$scope.entities= [
{
"input": {
"id": 134
},
"output": [
{
"id": 135,
"outputTypeId": 4
}
]
},
{
"input": {
"id": 134
},
"output": [
{
"id": 135,
"outputTypeId": 7
}
]
},
{
"input": {
"id": 134
},
"output": [
{
"id": 135,
"outputTypeId": 9
}
]
}
]
Below is the code that I tried but its not going inside the condition after execution..
Let outputTypeId be [7] as I'm checking for multiple outputTypeId's,hence an array
$scope.checkForDuplicateOutputs = (outputTypeId) => {
for (var i = 0; i < $scope.entities.length; i++) {
for (var j = i; j < $scope.entities[i].output[j].length; j++) {
if (outputTypeId.contains($scope.entities[i].output[j].outputTypeId)) {
$scope.isDuplicateOutput = true;
break;
} else {
$scope.isDuplicateOutput = false;
}
}
}
}
function checkForDuplicates(outputTypeIds) {
$scope.isDuplicateOutput = $scope.entities.some(function(entity) { // Loop through entities
return entity.output.some(function(entityOutput) { // Look for any output
return outputTypeIds.indexOf(entityOutput.outputTypeId) != -1; // which is duplicated in 'outputTypeIds'
});
});
}
So this solution uses Array.some - It has a few advantages:
Removes the need to manually break your loops
No need to have i and j variables to keep track of loop counters
No need to duplicate $scope.isDuplicateOutput = <boolean>;
Less lines of code :)
You are breaking only the inner loop with that break statement and the problem is even though the duplicate flag does get set to true, it will be reset to false in the next iteration. Basically, in the end you'll get the result of the last iteration only.
The quickest fix is to use a flag to denote whether the external loop needs to be stopped:
$scope.checkForDuplicateOutputs = (outputTypeId) => {
var breakOut = false; // <--- this is new
for (var i = 0; i < $scope.entities.length; i++) {
if (breakOut) break; // <--- this is new
for (var j = i; j < $scope.entities[i].output[j].length; j++)
if (outputTypeId.contains($scope.entities[i].output[j].outputTypeId)) {
$scope.isDuplicateOutput = true;
breakOut = true; // <--- this is new
break;
} else {
$scope.isDuplicateOutput = false;
}
}
}
}
If you still want to iterate all the entities and have a list of all the duplicates, you can make $scope.isDuplicateOutput an array and just push the duplicate ids into it.
I am trying to figure out an efficient way to remove objects that are duplicates from an array and looking for the most efficient answer. I looked around the internet everything seems to be using primitive data... or not scalable for large arrays. This is my current implementation which is can be improved and want to try to avoid labels.
Test.prototype.unique = function (arr, artist, title, cb) {
console.log(arr.length);
var n, y, x, i, r;
r = [];
o: for (i = 0, n = arr.length; i < n; i++) {
for (x = 0, y = r.length; x < y; x++) {
if (r[x].artist == arr[i].artist && r[x].title == arr[i].title) {
continue o;
}
}
r.push(arr[i]);
}
cb(r);
};
and the array looks something like this:
[{title: sky, artist: jon}, {title: rain, artist: Paul}, ....]
Order does not matter, but if sorting makes it more efficient then I am up for the challenge...
and for people who do not know o is a label and it is just saying jump back to the loop instead of pushing to the new array.
Pure javascript please no libs.
ANSWERS SO FAR:
The Performance Test for the answers below:
http://jsperf.com/remove-duplicates-for-loops
I see, the problem there is that the complexity is squared. There is one trick to do it, it's simply by using "Associative arrays".
You can get the array, loop over it, and add the value of the array as a key to the associative array. Since it doesn't allow duplicated keys, you will automatically get rid of the duplicates.
Since you are looking for title and artist when comparing, you can actually try to use something like:
var arrResult = {};
for (i = 0, n = arr.length; i < n; i++) {
var item = arr[i];
arrResult[ item.title + " - " + item.artist ] = item;
}
Then you just loop the arrResult again, and recreate the array.
var i = 0;
var nonDuplicatedArray = [];
for(var item in arrResult) {
nonDuplicatedArray[i++] = arrResult[item];
}
Updated to include Paul's comment. Thanks!
Here is a solution that works for me.
Helper functions:
// sorts an array of objects according to one field
// call like this: sortObjArray(myArray, "name" );
// it will modify the input array
sortObjArray = function(arr, field) {
arr.sort(
function compare(a,b) {
if (a[field] < b[field])
return -1;
if (a[field] > b[field])
return 1;
return 0;
}
);
}
// call like this: uniqueDishes = removeDuplicatesFromObjArray(dishes, "dishName");
// it will NOT modify the input array
// input array MUST be sorted by the same field (asc or desc doesn't matter)
removeDuplicatesFromObjArray = function(arr, field) {
var u = [];
arr.reduce(function (a, b) {
if (a[field] !== b[field]) u.push(b);
return b;
}, []);
return u;
}
and then simply call:
sortObjArray(dishes, "name");
dishes = removeDuplicatesFromObjArray(dishes, "name");
Basic sort-then-unique implementation, fiddle HERE:
function unique(arr) {
var comparer = function compareObject(a, b) {
if (a.title == b.title) {
if (a.artist < b.artist) {
return -1;
} else if (a.artist > b.artist) {
return 1;
} else {
return 0;
}
} else {
if (a.title < b.title) {
return -1;
} else {
return 1;
}
}
}
arr.sort(comparer);
console.log("Sorted: " + JSON.stringify(arr));
for (var i = 0; i < arr.length - 1; ++i) {
if (comparer(arr[i], arr[i+1]) === 0) {
arr.splice(i, 1);
console.log("Splicing: " + JSON.stringify(arr));
}
}
return arr;
}
It may or may not be the most efficient, and should be entirely scalable. I've added some console.logs so you can see it as it works.
EDIT
In the interest of saving on the space the function used, I did that for loop at the end, but it seems likely that didn't properly find only unique results (depsite it passing my simple jsfiddle test). Please try replacing my for loop with the following:
var checker;
var uniqueResults = [];
for (var i = 0; i < arr.length; ++i) {
if (!checker || comparer(checker, arr[i]) != 0) {
checker = arr[i];
uniqueResults.push(checker);
}
}
return uniqueResults;
I use this function. its not doing any sorting, but produces result. Cant say about performance as never measure it.
var unique = function(a){
var seen = [], result = [];
for(var len = a.length, i = len-1; i >= 0; i--){
if(!seen[a[i]]){
seen[a[i]] = true;
result.push(a[i]);
}
}
return result;
}
var ar = [1,2,3,1,1,1,1,1,"", "","","", "a", "b"];
console.log(unique(ar));// this will produce [1,2,3,"", "a", "b"] all unique elements.
Below is Henrique Feijo's answer with ample explanation and an example that you can cut and paste:
Goal: Convert an array of objects that contains duplicate objects (like this one)...
[
{
"id": 10620,
"name": "Things to Print"
},
{
"id": 10620,
"name": "Things to Print"
},
{
"id": 4334,
"name": "Interesting"
}
]
... Into an array of objects without duplicate objects (like this one):
[
{
"id": 10620,
"name": "Things to Print"
},
{
"id": 4334,
"name": "Interesting"
}
]
Explanation provided in the comments:
var allContent = [{
"id": 10620,
"name": "Things to Print"
}, {
"id": 10620,
"name": "Things to Print"
}, {
"id": 4334,
"name": "Interesting"
}]
//Put Objects Into As Associative Array. Each key consists of a composite value generated by each set of values from the objects in allContent.
var noDupeObj = {} //Create an associative array. It will not accept duplicate keys.
for (i = 0, n = allContent.length; i < n; i++) {
var item = allContent[i]; //Store each object as a variable. This helps with clarity in the next line.
noDupeObj[item.id + "|" + item.name] = item; //This is the critical step.
//Here, you create an object within the associative array that has a key composed of the two values from the original object.
// Use a delimiter to not have foo+bar handled like fo+obar
//Since the associative array will not allow duplicate keys, and the keys are determined by the content, then all duplicate content are removed.
//The value assigned to each key is the original object which is along for the ride and used to reconstruct the list in the next step.
}
//Recontructs the list with only the unique objects left in the doDupeObj associative array
var i = 0;
var nonDuplicatedArray = [];
for (var item in noDupeObj) {
nonDuplicatedArray[i++] = noDupeObj[item]; //Populate the array with the values from the noDupeObj.
}
console.log(nonDuplicatedArray)
For those who love ES6 and short stuff, here it's one solution:
const arr = [
{ title: "sky", artist: "Jon" },
{ title: "rain", artist: "Paul" },
{ title: "sky", artist: "Jon" }
];
Array.from(arr.reduce((a, o) => a.set(o.title, o), new Map()).values());
const arr = [
{ title: "sky", artist: "Jon" },
{ title: "rain", artist: "Paul" },
{ title: "sky", artist: "Jon" },
{ title: "rain", artist: "Jon" },
{ title: "cry", artist: "Jon" }
];
const unique = Array.from(arr.reduce((a, o) => a.set(o.title, o), new Map()).values());
console.log(`New array length: ${unique.length}`)
console.log(unique)
The above example only works for a unique title or id. Basically, it creates a new map for songs with duplicate titles.
Below code compares object with JSON as String format and removes duplicates and works fine with simple arrays.
Array.prototype.unique=function(a){
return function(){
return this.filter(a)
}
}(
function(a,b,c){
var tmp=[];
c.forEach(function(el){
tmp.push(JSON.stringify(el))
});
return tmp.indexOf(JSON.stringify(a),b+1)<0
})
If you are using underscore js, it is easy to remove duplicate object.
http://underscorejs.org/#uniq
function remove_duplicates(objectsArray) {
var arr = [], collection = [];
$.each(objectsArray, function (index, value) {
if ($.inArray(value.id, arr) == -1) {
arr.push(value.id);
collection.push(value);
}
});
return collection;
}