On my backend, I have a nested categories structure like this:
Category
--------
id
parent_id
name
That I load completely into $scope.categories as a single dimension array.
On screen, I want to show one dropdown per level, showing the correspondent path. For example, if the path is cars/sedan/ford/taurus, I want to show four dropdowns:
Cars v Sedans v Ford v Taurus v
Whenever a dropdown is changed, the ones to the right should be deleted, and the immediate one populated with all child categories whose parent is the one I just selected. Regular stuff.
I also created a category_path array that contains each category_id in the path to the product category: [null, category_id, category_id, category_id].
And then I did this:
<select
ng-repeat="item in category_path"
ng-model="selected_category[$index]"
ng-options="category.name for category in (categories | filter : { parent_id: item } : true ) track by category.id "
ng-change="select_category(selected_category[$index], $index)"
></select>
On my controller:
$scope.select_category = function ( selected_category, index ) {
if ( $filter('filter') ($scope.categories, { parent_id: selected_category.id }, true).length ) {
if ( $scope.category_path[index+1] != undefined ) {
$scope.category_path = $scope.category_path.slice(0, index+1);
$scope.category_path[index+1] = selected_category.id;
} else {
$scope.category_path.push( selected_category.id );
}
} else {
$scope.product.category_id = selected_category.id;
}
}
This works like charm, except for the fact that I can't populate the default values for each dropdown, based on the current product category_path array.
I tried:
ng-init="select_category[$index] = category_path[$index]"
without success. Ideas?
Your category_path array holds different values than the model, i.e. the selected_category array. The latter seems to hold JSON strings while the former holds a single value (for the id key).
To make the default values work, insert the full JSON objects into the model array.
Related
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().
I'm using Contentful as a CMS, wherein I have the content models "Category" and "Subcategory". Each subcategory has a category as its parent. Both models have a property called order which I set within the CMS to decide the order in which they will appear on the navigation.
Here's my GraphQL query:
query {
allContentfulCategory(sort: {fields: [order, subcategory___order]}) {
edges {
node {
order
title
subcategory {
order
title
}
}
}
}
}
The first sort works correctly and the query outputs my categories by the order field, rather than in reverse-creation order which seems to be the default output from Contentful (the newest categories show first).
However, my second sort of subcategory___order, which was inspired by this blog post, does not evaluate. In other words, the subcategories (per Category) show up in reverse order so that the top item has an order of 8, and it decreases to 7, and 6, and so on.
For example, say I had category "Animals" with order 2, and "Trees" with order 1. My content in reverse-creation order: "Cat" (order 3), "Dog" (order 1), "Chicken" (order 2), "Birch" (order 2), "Oak" (order 3), and "Willow" (order 1).
These will come through the query like so:
Trees (1):
Birch (2)
Oak (3)
Willow (1)
Animals (2):
Cat (3)
Dog (1)
Chicken (2)
I can't find a way to make the GraphQL query perform both sort commands simultaneously so that my data is ordered according to the order I set in the CMS.
So many thanks in advance!
Edit: Here's a component solution:
Within each category node that comes from my query, I can access the subcategories and their order in an array.
const categories = data.allContentfulCategory.edges
{categories.map((category) => {
return (
<div>
{category.node.subcategory && category.node.subcategory.sort((a, b) => {return a.order - b.order}).map((subcategory) => {
return (
<div>
<p>{subcategory.title}</p>
</div>
)
})}
</div>
)
})}
The key element here is how I sort the array by subcategory order within the component:
category.node.subcategory.sort((a, b) => {return a.order - b.order})
To be clear, this is not a satisfying solution, nor does it answer my question about how I might write multiple GraphQL sorts. But this code does do what I wanted with regards to organising the subcategories.
You are querying category and not subcategory:
query {
allContentfulCategory(sort: {fields: [order, subcategory___order]}) {
edges {
node {
order
title
subcategory {
order
title
}
}
}
}
}
In the query above, only the returned array data.allContentfulCategory.edges is sorted. Gatsby won't sort the data nested in category.subcategory.
Further more, the second field you specified (subcategory___order) will only be considered if the first field (order) is the same. Since each category has a unique order, subcategory will never be used.
If it's possible for you to query for something like allContentfulSubcategory instead, it will work.
Otherwise, you can move the sort logic to build time instead by using createSchemaCustomization. I don't know what your schema look like so this is just an example:
// gatsby-node.js
exports.createSchemaCustomization = ({ actions, schema }) => {
const { createTypes } = actions
const typeDefs = [
schema.buildObjectType({
name: "ContentfulCategory",
fields: {
subcategory: {
type: "[ContentfulSubcategory]",
resolve: (src) => {
const subcategories = src.subcategory
return subcategories.sort()
}
},
},
interfaces: ["Node"],
extensions: { infer: true },
}),
]
createTypes(typeDefs)
}
If you need to use createSchemaCustomization, see the Gatsby docs on the topic here.
The docs' info could be overwhelming, I also wrote a bit about this API here. It is more about field extension, but the resolver function is basically the same.
In one component I can filter my array using the following:
// Array of product objects
const result = products.filter(p => p.name.includes('val'));
and value of products remains same as the first value but filtered value stores in result.
But in the following code, filter() filters array of strings itself:
// Array of strings
const result = strs.filter(s => s.includes('val'));
The question is how can I filter strings and return result without modifying the strs itself?
Note: I tried with array.filter(function() { return res; }); but didn't make any change.
It returns the filtered ones and don't change the actual array. You are doing something wrong
const strs = ['valval', 'bal', 'gal', 'dalval'];
const result = strs.filter(s => s.includes('val'));
console.log(strs);
console.log(result);
First thing we need to know is, if we filter our list we loose our original data
products: any[] = [
{
"productId": 1,
"productName": "foo-bar",
"price": 32.99
}
]
and can't get it back without re-getting the data from it's source so we have to make another list to store the filtered list.
filteredProduce: any[];
Next if you are working to show a list of filtered product on a grid or something like this we need a way to know when the user changes the filter criteria. we could use event binding and watch for key presses or value changes, but an easier way is to change our _listFilter property into a getter and setter, like this
get listFilter: string {
return this._listFilter;
}
set listFilter(value:string) {
this._listFilter= value;
}
next we want to set our filteredProducts array to the filtered list of products like this
set listFilter(value:string) {
this._listFilter= value;
this.filteredProducts = this._listFilter? this.performFilter(this._listFilter) : this.products;
}
in preceding code we are using js conditional operator to handle the posibility that _listFilterstring is empty, null or undefined.
Next we use this.performFilter(this._listFilter) to filter our list.
performFilter(filterBy: string): any[] {
filterBy = filterBy.toLocaleLowerCase();
return this.products.filter((product: any) =>
product.productName.toLocaleLowerCase().indexOf(filterBy) !== -1);
}
Finally we should assign the main list of products to the filteredProducts and _listFilter to what we want.
constructor() {
this.filteredProducts = this.products;
this._listFilter= 'foo-bar';
}
last step is to change our template to bind to our filteredProducts property.
Okay so i have the following object:
$scope.search = {};
this allows me to search through the following elements:
<div class="col-md-12" ng-repeat="question in questions | filter:search:strict">
Now one of the things i can do is to set a variable called category_id which then filters the category_id of each item:
$scope.search.category_id = selected.id;
However there is a problem. once i try to reset it AKA call $scope.search.category_id = null
All result disapear.
So it seem it is searching for categories where id = null which is false in every case.
So my question is how do i reset it then?
You should delete the property from the object rather than setting it to null. That will remove it from being listed in the search criteria.
delete $scope.search.category_id
You can see how this is different from setting it to null below
var a = { hello: 5 }
console.log(a, Object.keys(a)) // Object {hello: 5} ['hello']
a.hello = null
console.log(a, Object.keys(a)) // Object {hello: null} ['hello']
delete a.hello
console.log(a, Object.keys(a)) // Object {} []
I have this code in my router:
category: (id) ->
alert id
filteredProducts = new App.Collections.Products(#productsCollection.where({category_id: id}))
#productsView.renderResults(filteredProducts)
The alert id call works properly (e.g. app.com/categories/6 > alerts "6") and when I change the id in the where function to an actual number the products collection filters properly like so:
filteredProducts = new App.Collections.Products(#productsCollection.where({designer_id: 6}))
But when I set the designer_id: id the where loop return an empty collection.
My foreign key category_id was a number not a string. The answer is:
filteredProducts = new App.Collections.Products(#productsCollection.where({category_id: parseInt(id)}))