How to make items in a ColdFusion array unique? - arrays

I have a shopping cart that is adding products to the checkout page.
As you can see this is how the structure of the cart is set up:
Problem is, I only want unique values to appear, no repeats. Not sure how to make arrays strict so it only contains unique values.

To make sure your cart has always only one position for each article, I'd use structs instead of arrays to have always one position per article in your cart and avoid populating it with multiple copies of the same article.
By using a struct for your cart you can create a struct key (e.g. with the article ID) that is also the unique reference to one unique article in the cart. If you use arrays you only have numbers and you would need to go and look deeper in the data structure to verify if the article is already present in the cart.
Here is just a simplistic example of how I'd make it with the data structure. I'm not adding functionality like adding/removing single units of articles. This is just an example to see how I'd deal with the cart data structure to have it referenced with an unique key, so it can be quickliy accessed and manipulated further on.
<cfscript>
// function to add an article structure to the cart
function addArticleToCart(struct articleCartData required) {
// If the article is not present in the cart, add it as a struct to the cart wirh it's own articleID as the cart struct key:
If(!structKeyExists(cart, arguments.articleCartData.articleID)) {
cart.append({
"#arguments.articleCartData.articleID#": arguments.articleCartData
});
} else {
// Add the quantity of the cart by one unit
cart[arguments.articleCartData.articleID].quantity++
};
};
//Define empty cart
cart = {};
//Define the article that will be added to the cart
articleToAdd = {
articleID: "ART12345",
articleName: "5650R Sheerweave Oyster",
quantity: 1,
unitPrice: 12.99
};
// Call the function to add the article to the cart
addArticleToCart(
articleToAdd
);
writedump(cart);
addArticleToCart(
articleToAdd
);
writedump(cart);
addArticleToCart(
articleToAdd
);
writedump(cart);
</cfscript>

Here's one solution how to make the array items unique.
Code
<cfscript>
cart = [
{
title: "foo"
},
{
title: "bar"
},
{
title: "foo"
},
{
title: "foo"
},
{
title: "bar"
}
];
function makeItemsUnique(array, property) {
return arrayReduce(array, function(reducedArray, item) {
if (not arraySome(reducedArray, function(uniqueItem) { return uniqueItem[property] eq item[property]})) {
arrayAppend(reducedArray, item);
}
return reducedArray;
}, []);
}
writedump(makeItemsUnique(cart, "title"));
</cfscript>
Output
Explanation
The function makeItemsUnique() is responsible for making the items in the cart unique. It's kept general to allow it to work with different kinds of arrays of structs. It is called with the cart array and the property name used to filter it.
In that function the call to arrayReduce() reduces the array to a new one that only contains unique items. It does so by checking whether the new array already has an item with the same title in it. This check is done using arraySome(). If the new array doesn't contain an element with that title yet, it is added via arrayAppend().

Related

Is this a React prevState issue and how can I solve it?

This is kind of complicated to explain. I'm trying to make a toggle function that adds and deletes items on a different page with useContext. Everything adds and deletes fine until I tab to the other page, and that's where the error begins. Once I do that the function ignores what's in the array and will duplicate items in the array. What's odd about it is if I console.log or manually check the new item with the current array items it shows me everything. For example in order to add the new item to the array it checks if the index of new item is -1. If it is it will add the item and if not it will delete the item. However once I leave the page it doesn't see the item anymore and adds it anyway. If I console.log the item name and new item name, I can see both, and if I use === to check it also works fine until I switch tabs and then even though it's still console logging both names somehow it's still adding the item and ignoring that it already contains the item.
The code directory in my sandbox is src/Helpers/MyPicsContext. Here is the link to my sandbox codesandbox
The tabs on the website are Picks and the search page which you can access on Picks before any items are added or by clicking the magnifying glass in the top right of page.
And here is the actual code for the context page.
export const MyPicksContextProvider = props => {
const [picksList, setPicksList] = useState(
JSON.parse(localStorage.getItem('picksList'))
||
[]
)
//console.log(picksList)
useEffect(() => {
localStorage.setItem("picksList", JSON.stringify(picksList));
}, [picksList]);
const deleteCoin = coin => {
if (picksList.length === 1) {
setPicksList([]);
} else {
setPicksList(picksList.filter(list => {
console.log(list)
//console.log(coin)
return list !== coin;
}))
}
console.log('deleted');
console.log(picksList)
}
const toggleCoin = (coin) => {
if (picksList.length === 0) {
setPicksList([...picksList, coin]);
} else {
if (picksList.indexOf(coin) === -1 ) {
setPicksList([...picksList, coin]);
console.log('added 1')
} else {
deleteCoin(coin)
}
}
}
Perhaps I just don't understand useState and prevState, but I can't seem to find any examples that apply to what I'm trying to do here. It makes total sense in creating a counter or something simple like that.
The issue is that .indexOf checks for referential equality of items, same as using ===. This means that const a = {id: 'x'}; const b = a; console.log(a === b); will output true, but const a = {id: 'x'}; const b = {id: 'x'}; console.log(a === b); will output false.
In your situation, upon changing pages / refreshing, the state is reset and loaded from local storage. However, this creates new objects which are not referentially equal. Instead of using .indexOf you want to use .find, something like array.find((element) => element.id === newItem.id) to find the index of the item. You could also do your own deep equality check (confirming every field matches), but I suspect the ID alone is sufficient.
In fact, I would also recommend only keeping the array of "picks" as an array of string ID's. Then you can lookup the full data from your table for each of these ID's. Otherwise the current price will be stored in localStorage, and could be out of date.

trying to keep original array (cloning, react)

I'm making a menu app where people can filter food and drinks. It's divided in categories, and categories contain items. One of the properties is 'alcohol' -> does the drink contain alcohol?
I have a state-object with 2 arrays: an 'original' array (containig all items) and a 'filter' array (where the filtering happens).
Every time a filter is changed, I want to copy the original array and start filtering all over again. But it seems that after filtering one time, the original array is changed, and it's weird because I clone the array.
My code:
const [arrays, setArrays] = useState({
original: [{ catName: 'drinks', items: [{},{},...]}],
filter: [] // same as original in the beginning
});
useEffect(() => {
const copy = [...arrays.original];
copy.forEach((cat, idx) => {
if (!filter.alcohol) {
copy[idx].items = cat.items.filter(e => e.properties.alcohol === false);
}
});
setArrays(p => ({
...p,
filter: copy
});
}, [filter]);
I never 'touch' the original array, but after a filter changes, not only the 'filter'-array is changed (like expected), but also the original array.
Edit: expected behaviour:
when I filter on alcohol, I'm expecting that all drinks containing alcohol are removed from the filter-array. This works, but when I remove the filter, all the alcohol-drinks should be back in the filter-array. This doesn't work because they are also deleted from the original array.
Sounds like you may just be returning a shallow copy of the original array. Have you tried Lodash's clonedeep?

How to organise my nested object for better state management?

This is more of an organisation than technical question. I think I may be adding complexity, where a more experienced dev would simplify. I lack that experience, and need help.
It's a menu editor, where I load a menu object from my database into state:
state = {
user_token: ####,
loadingMenu: true,
menu: {} // menu will be fetched into here
}
The object looks like this:
{
menuID: _605c7e1f54bb42972e420619,
brandingImg: "",
specials: "2 for 1 drinks",
langs: ["en", "es"],
items: [
{
id: 0,
type: "menuitem",
isVisible: true,
en: {
name: "sandwich 1",
desc: "Chicken sandwish"
},
es: {
name: "torta 1"
},
price: 10
},
// ...
// ABOUT 25 MORE ITEMS
]
}
The UI allows user to click on and update the items individually. So when they change the text I find myself having to do weird destructuring, like this:
function reducer(state, action) {
if (action.type === UPDATE_NAME) {
const newMenuItems = state.menu.items.map((oldItem) => {
if (oldItem.id === action.payload.id) {
return { ...oldItem, ["en"]: { ...oldItem["en"], name: action.payload.newName } }
// ["en"] for now, but will be dynamic later
}
return oldItem
})
return { ...state, menu: { ...state.menu, items: newMenuItems } }
}
}
This seems like a a bad idea, because I'm replacing the entirety of state with my new object. I'm wondering if there is a better way to organize it?
I know there are immutability managers, and I tried to use immer.js, but ran into an obstacle. I need to map through all my menu items to find the one user wants to edit (matching the ID to the event target's ID). I don't know how else to target it directly, and don't know how to do this:
draft.menu.items[????][lang].name = "Sandwich One"
So again, I'm thinking that my organisation is wrong, as immutability managers should probably make this easy. Any ideas, what I can refactor?
First of all, your current reducer looks fine. That "weird destructuring" is very typical. You will always replace the entirety of state with a new object, but you are dealing with shallow copies so it's not an entirely new object at every level. The menu items which you haven't modified are still references to the same objects.
I need to map through all my menu items to find the one user wants to edit (matching the ID to the event target's ID). I don't know how else to target it directly.
You would use .findIndex() to get the index of the item that you want to update.
const {lang, name, id} = action.payload;
const index = draft.menu.items.findIndex( item => item.id === id);
if ( index ) { // because there could be no match
draft.menu.items[index][lang].name = name;
}
This is more of an organisation than technical question. I think I may be adding complexity, where a more experienced dev would simplify. I lack that experience, and need help.
My recommendation for the state structure is to store all of the items in a dictionary keyed by id. This makes it easier to update an item because you no longer need to find it in an array.
const {lang, name, id} = action.payload;
draft.items[index][lang].name = name;
The menu object would just have an array of ids instead of an array of objects for the items property. When you select a menu, your selector can replace the ids with their objects.
const selectMenu = (state) => {
const menu = state.menu;
return { ...menu, items: menu.items.map((id) => state.items[id]) };
};

How to transform a 'flat' observableArray into a nested observableArray with Knockout

I have a 'flat' array with 3 items:
[{"title":"welcome","file":"default.aspx","category":"finance"},
{"title":"test2","file":"test2.aspx","category":"finance"},
{"title":"test1","file":"test1.aspx","category":"housing"}]
The objective is to transform this into a nested observableArray with 2 items:
[{"category":"finance","content":[
{"title":"welcome","file":"default.aspx","category":"finance"},
{"title":"test2","file":"test2.aspx","category":"finance"}]},
{"category":"housing","content":[
{"title":"test1","file":"test1.aspx","category":"housing"}]}]
http://www.knockmeout.net/2011/04/utility-functions-in-knockoutjs.html helped me to extract unique categories in two steps:
self.getcategories = ko.computed(function () {
var categories = ko.utils.arrayMap(self.pages(), function (item) {
return item.category();
});
return categories.sort();
});
self.uniqueCategories = ko.dependentObservable(function() {
return ko.utils.arrayGetDistinctValues(self.self.getcategories()).sort();
});
//uniqueCategories: ["finance","housing"]
However I can't figure out how to create the nested array. I got as far as this:
self.createCategories = ko.computed(function () {
ko.utils.arrayForEach(self.uniqueCategories(), function (item) {
var content = getCategoryContent(item);
var c = new category(item, content);
self.Categories.push(c);
});
return true;
});
function getCategoryContent(whichcategory) {
return ko.utils.arrayFilter(self.pages(), function (page) {
return page.category() === whichcategory;
});
}
It results however in 5 category items (finance 4x, housing 1x) where I expect just 2.
Your computed function createCategories is probably being invoked more than once. e.g. if you are adding items to your pages() array one at a time, this will trigger the createCategories function each time you add an item.
Normally you would make the categories array and return it from a computed function. Currently you are adding to the Categories observable array without clearing it each time.
An easy fix would be to clear out the Categories array at the top of the createCategories function. This would leave you in the odd situation of having to call createCategories at least once to set up the dependencies, but after that it would work automatically when the data changed.
Another option would be create an array and return it from the createCategories functions, then you could just rename the function to Categories and not have an observable array. This would be the way I would normally use computed functions.
On more option would be to just do the work as normal JavaScript functions (rather than computed) and just call createCategories manually when you change the original array (e.g. when you get a result back from your server).

angularjs ng-grid keep selection on update

I'm trying to use the ng-grid setup and I have the following problem.
The data I am displaying changes very frequently, every 5 seconds or so. But not a lot of new data gets added to the list.
When i set data to the ng-grid the user can start looking at the data. but when I update the data after about 5 seconds the selections the user has made and the grouping is lost.
http://plnkr.co/edit/eK1aeRI67qMROqDUtPnb
Is there anyway to keep the selection and/or the grouping?
You're going to have to go through and merge the data in a for loop. If you replace the entire array, you're replacing the object references, and therefor you will lose any changes you've made.
The other option would be to keep your selections in a different array or dictionary, then remap your properties after you replace your array. Notice here you're going to need to use a reference type so changes persist to your selections array.
So like [psuedo-code]:
// a dictionary of reference types (IMPORTANT that they are objects!)
// to hold selection data.
var selections = {
'Name1' : { value: 'selection' },
'Name2': { value: 'selection2' }
}
$scope.getMyData = function () {
// do whatever to get your data here.
$scope.myData = [
{ name: 'Name1' },
{ name: 'Name2' }
];
// update your objects in your array.
for(var i = 0; i < $scope.myData.length; i++) {
var data = $scope.myData[i];
var selection = selections[data.name];
if(selection) {
data.selection = selection;
}
}
};
// initial load
$scope.getMyData();
// your test interval
setInterval(function () {
$scope.$apply(function (){
$scope.getMyData();
});
}, 5000);
We are going to be adding a primaryKey option in the next version that will allow the grid to key off that instead of references.

Resources