Render products grouped by dynamic category [duplicate] - json

This question already has answers here:
Render products grouped by category
(2 answers)
Closed 2 years ago.
I have a catalog page with a sidebar which contains a dynamic list:
This sidebar lists products.category and updates whenever there is a new category created/removed.
What I want to achieve
Is to be able to show the filtered table based on category that was clicked. Kind of like using props so I can use products.category to automatically fill the filter without hardcoding each category.
I may be approaching this problem wrong.
const showTable = (props) => {
return (
<>
{products && products.length !== 0 ? (
products
.filter((item) => item.category === props.table) // Kind of like this
.map((product) => ExtendedProduct(product))
) : (
<Loader />
)}
</>
);
};
const TableRoutes = () => {
return (
<>
{products &&
productCategories.map((product) => (
<Tab.Pane key={product.category} eventKey={product.category}>
<ShowTable table={product.category} />
</Tab.Pane>
))}
</>
);
};
The productCategories is where I remove the duplicated categories so they won't be doubled when rendered.
The code above is a functional component.
I'm using https://react-bootstrap.netlify.app/components/tabs/#tabs-custom-layout which explains Tab.Pane.
Things I've looked into:
Render products grouped by category [class-based]

After ~12 hours of fiddling around, apparently the solution is quite the same in the linked question.
Render products grouped by category
const TableRoutes = () => {
return (
<>
<Tab.Pane eventKey="default">
<ExtendedTable
data={
products && products.map((product) => ExtendedProduct(product))
}
/>
</Tab.Pane>
{/* Table filtered by product categories */}
{products &&
productCategories.map((categories) => (
<Tab.Pane key={categories.category} eventKey={categories.category}>
<ExtendedTable
data={
products &&
products
.filter((pane) => pane.category === categories.category)
.map((product) => ExtendedProduct(product))
}
/>
</Tab.Pane>
))}
{/* Table filtered by product types */}
{products &&
productTypes.map((types) => (
<Tab.Pane key={types.type} eventKey={types.type}>
<ExtendedTable
data={
products &&
products
.filter((pane) => pane.type === types.type)
.map((product) => ExtendedProduct(product))
}
/>
</Tab.Pane>
))}
</>
);
};
I guess this can be marked as duplicate...

Related

Datalist input showing options from another Vue component

I'm quite new with Vue, and webdev in general. Am creating a simple hiking pack list as a school project. Have a component 'PackItem' that consists of input fields for brand, model and weight. In the backend I have a database with some items that the user can pick from. When selecting a brand, the model input will show a list of models for that brand, and when a model is chosen the weight input gets filled automatically.
<template>
<div class="inputRow">
<h1> Brand: {{ this.brand }}</h1>
<input
type="text"
list="brands"
placeholder="Brand"
v-model="this.brand"
/>
<datalist id="brands">
<option
v-for="(suggested_brand, index) in getProductBrands"
:key="index"
:value="suggested_brand"/>
</datalist>
<input
type="text"
list="models"
v-model="this.model"
/>
<datalist id="models"> -->
<option
v-for="(suggested_model, index) in brandModels"
:key="index"
:value="suggested_model"/>
</datalist>
<input
class="product-inputs"
type="number"
name="Weight"
placeholder="Weight"
v-model="this.getProductWeight"
#change="this.updateItemOnStore($event.target.value);"/>
</div>
</template>
This is the component. The datalists and input gets their values from computed properties, that talk to vuex.
export default defineComponent({
name: 'PackItem',
props: {
},
data() {
return {
model: '',
brand: '',
weight: 0,
}
},
...
...
...
computed: {
getProductBrands(){
return this.$store.getters.productsBrandList
},
getProductWeight(){
let weight = this.$store.getters.productWeight(this.brand, this.model)
return weight
},
brandModels(){
if(this.brand === ''){
return []
}
console.debug('Item %d getting models for %s', this.uuid, this.brand)
let models = this.$store.getters.brandModels(this.brand)
return models
},
},
}
This all works great for the first PackItem, but when i spawn a second PackItem from the parent, the models list for the new PackItem will be the same as the first PackItem, regardless of choosing a different brand.
Image: One item spawned
Image: Two items spawned
If I change computed property brandModels() to a method, and save the returning array from the store in local data and print it, I can see that the model list looks like it should, but those values are not showing up in the list for the user?
Image: Two items spawned with printouts
I can also see in the logs that I'm getting the correct models for the selected brand.
Item 0 getting models for Hyperlite Mountain Gear
index.js:209 Hyperlite Mountain Gear models: Windrider 3400,Junction 3400
index.js:221 Got weight 907g for product Hyperlite Mountain Gear Windrider 3400
index.js:64 Creating item 1 in category 0
index.js:199 Brands list: ZPacks,Hyperlite Mountain Gear,The OMM
index.js:225 Did not find weight for product in database!
index.js:199 Brands list: ZPacks,Hyperlite Mountain Gear,The OMM
PackItem.vue:119 Item 1 getting models for ZPacks
index.js:209 ZPacks models: Arc Blast
index.js:225 Did not find weight for product ZPacks Windrider 3400 in database!
So seems to me like I'm fetching all the correct data, and as far as I can see it should be displayed in the browser, but for some reason is not. I have no idea what is going on here...
EDIT:
As suggested by #yoduh I've fetched the whole products table into the component, and do computed values there instead of the supposedly broken computed values from getters in vuex. This has unfortunately not fixed it. Can still see in logs that brandModels() creates a correct list of models, but the datalist still shows the wrong ones.
computed: {
getProductBrands(){
let brands = []
this.dbProducts.forEach(product => {
//var json = JSON.parse(product);
var brand = product.brand;
if(!brands.includes(brand)){
console.debug('Adding brand %s to brands list', brand)
brands.push(brand)
}
} )
console.log('Brands list: %s', brands)
return brands
},
brandModels(){
if(this.brand === '') {return }
let models = []
this.dbProducts.filter(
product => product.brand === this.brand)
.forEach(product => models.push(product.model)
)
console.debug('%s models: %s', this.brand, models)
return models
},
getProductWeight(){
if(this.brand === '' || this.model === ''){ return }
let product = this.dbProducts.find(
product => (
product.brand === this.brand && product.model == this.model
))
if(product){
let weight = product.weightGrams
console.debug('Got weight %dg for product %s %s', weight, this.brand, this.model)
return weight
}
console.debug('Did not find weight for product %s %s in database!', this.brand, this.model)
return 0
},
},
Turns out the actual issue was with the pair of elements <InputText> and <datalist> on every item having the same id and list values. The value for list and id connect the two elements together, but once a second item is added, another pair of <InputText> and <datalist> with the same id and list values is created. Because no two elements should ever share the same id, the connection between inputs and datalists becomes confused and broken.
The solution then is to bind unique values to the id/list attributes. I used the uuid of each item since it's a unique number:
PackItem.vue
<InputText
type="text"
:list="`models${uuid}`"
placeholder="Model"
class="p-inputtext"
v-model="this.model"
/>
<datalist :id="`models${uuid}`">
<option
v-for="(suggested_model, index) in brandModels"
:key="index"
:value="suggested_model"
/>
</datalist>
updated codesandbox

How to dynamically change the css style of a particular item inside a map function

I currently have two map functions to render a board for a chess game like this:
<div className='board'>
{rows.map((item) => (
<div key={item} className='row'>
{item.map((square) => (
# The square <div> => <div key={square[0]} className='square' onClick={() => {
board.select !== null &&
checkColour(board.select[1]) !== checkColour(square[1]) && dispatch(move(square))
}}>
{isNaN(square[1]) && <img src={square[1]} alt={square[1]}
onClick={() => { board.turns === checkColour(square[1]) && dispatch(select(square)) }}
className={board.select === square ? 'piece-selected' : 'piece'} />}
</div>
))}
</div>
))}
</div>
Now, I want to change the css style of certain squares <div> after I selected a pawn piece in order to forecast a shadow for showing the possible moves. I want to know is it possible to access the css style of that particular square <div> using the key of that <div>?
Feel free the drop a comment below if you have any insight or idea. Let me know if you want more info. Much appreciated.

Trying to display the nested object in react

I have tried the below code to display the nested values ,but got the parsing error.Please check the link to see the object
Line 116: Parsing error: Unexpected token, expected ","
render(){
return(
{this.state.SearchBooks.map(book =>
<h1>{ book.bookname}</h1>
{book.assignedto.map(assbook=>
<h1>{assbook.userid}</h1>
)}
)} )}
This is the object present in firebase i have stored it in this.state
Your curly braces are not balanced:
{book.assignedto.map(assbook => <h1>{assbook.user,id</h1>)}
should be
{book.assignedto.map(assbook => <h1>{assbook.user},{id}</h1>)}
or, following your edit/correction:
{book.assignedto.map(assbook => <h1>{assbook.userid}</h1>)} // the } after userid
Last, you need to wrap it all by a top level element, so add a fragment:
<>
<h1>title</h1>
{book.assignedto.map(assbook => <h1>...</h1>)}
</>
Change:
{this.state.SearchBooks.map(book => (
<h1>{ book.bookname}</h1>
{book.assignedto.map(assbook => <h1>{assbook.userid}</h1>
)}
To:
{this.state.SearchBooks.length && this.state.SearchBooks.map(book => (
// add this
<>
<h1>{ book.bookname}</h1>
{book.assignedto && book.assignedto.map(assbook => <h1>{assbook.userid}</h1>
// and this
</>
)}

Algolia - How to customize the tags display name in the menuSelect dropdown list

I found this menuSelect widget in Algolia instantSearch and I implemented successfully. But currently the filter names it displayed are the tags directly from the indices config. Is there a way to replace those name with my own filters? e.g. instead of react_pwa, display React storefront Here is the html output.
<MenuSelect class="tags" attribute="tags" transformItems={items =>
items.map(item => ({
...item,
}))}/>
Does anyone know if I can play around with transformItemsprops to solve this problem? Thanks!
Build a custom menuSelect widget can do it.
const MenuSelect = ({ items, currentRefinement, refine }) => (
<select
value={currentRefinement || ''}
onChange={event => refine(event.currentTarget.value)}
>
<option value="">See all options</option>
{items.map(item => (
<option
key={item.label}
value={item.isRefined ? currentRefinement : item.value}
>
{item.label==="react_pwa" && 'React PWA Storefront'}
{item.label==="cloudops-aws" && 'CloudOps for AWS'}
{item.label==="commerce-manager" && 'Commerce Manager'}
{item.label==="cloudops-azure" && 'CloudOps for Azure'}
{item.label==="chatbot" && 'Reference Chatbot'}
{item.label==="alexa-skill" && 'Alexa Skill'}
{item.label==="account-management" && 'Account Management'}
</option>
))}
</select>
);
const CustomMenuSelect = connectMenu(MenuSelect);
then in the search class add <CustomMenuSelect attribute="tags" />

Yii2 DynamicForm: Demo 3: Nested Dynamic Form with types of rooms

Is it possible to have differents types of rooms in the "demo 3: Nested Dynamic Form" and get the rooms separated by their type? For example, if I wanna create the same as the demo 3 shows and adding a type for each room. I want to have rooms type A, rooms type B and rooms type C. Is it possible? (types are preloaded in the database, the user doesn't have to insert a new type nor select one type. If I want to insert a "room type A", I just add it where it belongs)
I've changed the html but the room's array in the controller didn't has all the rooms (because the first ones are overwritten). So, what do I have to do to make it work fine?
In the picture you can see the approach, I want to make it work because by just editing the html didn't work.
Ok, here is my solution:
Create 2 more clases extended of "Model". So I have 3 clases: Room (ActiveRecord), RoomB (Model), RoomC (Model). So I can representate the 3 types of room.
The attributes of the two "Model" clases, are the "id" and the "description" of the "ActiveRecord" class (remember, we are talking about Room).
In the "_form.php", I've put two "render('_form-rooms')" more, inside divs with class "col-md-4" to get the separation.
<td>
<div class="row">
<div class="col-md-4">
<?= $this->render('_form-rooms', [
'form' => $form,
'indexHouse' => $indexHouse,
'modelsRoom' => $modelsRoom[$indexHouse],
]) ?>
<button type="button" class="remove-house btn btn-danger btn-xs"><span class="fa fa-minus"></span></button>
</div>
<div class="col-md-4">
<?= $this->render('_form-rooms', [
'form' => $form,
'indexHouse' => $indexHouse,
'modelsRoom' => $modelsRoomB[$indexHouse],
]) ?>
<button type="button" class="remove-house btn btn-danger btn-xs"><span class="fa fa-minus"></span></button>
</div>
<div class="col-md-4">
<?= $this->render('_form-rooms', [
'form' => $form,
'indexHouse' => $indexHouse,
'modelsRoom' => $modelsRoomC[$indexHouse],
]) ?>
<button type="button" class="remove-house btn btn-danger btn-xs"><span class="fa fa-minus"></span></button>
</div>
</div>
</td>
In the "actionCreate", I've made 2 extra arrays representing the 2 "Model" clases, so I have those 3 arrays: $modelsRoom = [[new Room]]; $modelsRoomB = [[new RoomB]]; $modelsRoomC = [[new RoomC]];
I've changed all the logic of the code inside "actionCreate" ad hoc with the two extra arrays, so, for example, in the "isset($_POST['Room'][0][0])", I've asking for "isset($_POST['RoomB'][0][0])" and "isset($_POST['RoomC'][0][0])" as well:
// validate person and houses models
$valid = $modelPerson->validate();
$valid = Model::validateMultiple($modelsHouse) && $valid;
$valid2 = $valid3 = $valid;
if (isset($_POST['Room'][0][0])) {
foreach ($_POST['Room'] as $indexHouse => $rooms) {
foreach ($rooms as $indexRoom => $room) {
$data['Room'] = $room;
$modelRoom = new Room;
$modelRoom->load($data);
$modelsRoom[$indexHouse][$indexRoom] = $modelRoom;
$valid = $modelRoom->validate();
}
}
}
if (isset($_POST['RoomB'][0][0])) {
foreach ($_POST['RoomB'] as $indexHouse => $rooms) {
foreach ($rooms as $indexRoom => $room) {
$data['Room'] = $room;
$modelRoom = new Room;
$modelRoom->load($data);
$modelsRoomB[$indexHouse][$indexRoom] = $modelRoom;
$valid2 = $modelRoom->validate();
}
}
}
if (isset($_POST['RoomC'][0][0])) {
foreach ($_POST['RoomC'] as $indexHouse => $rooms) {
foreach ($rooms as $indexRoom => $room) {
$data['Room'] = $room;
$modelRoom = new Room;
$modelRoom->load($data);
$modelsRoomC[$indexHouse][$indexRoom] = $modelRoom;
$valid3 = $modelRoom->validate();
}
}
}
so I ask if the 2 extra "valid" variables are true to continue
if ($valid && $valid2 && $valid3) {
$transaction = Yii::$app->db->beginTransaction();
try {
if ($flag = $modelPerson->save(false)) {
foreach ($modelsHouse as $indexHouse => $modelHouse) {
if ($flag === false) {
break;
}
... (continue with the same code)
and, in the render of the form, I've pass as a parameter the extra arrays created:
return $this->render('create', [
'modelPerson' => $modelPerson,
'modelsHouse' => (empty($modelsHouse)) ? [new House] : $modelsHouse,
'modelsRoom' => (empty($modelsRoom)) ? [[new Room]] : $modelsRoom,
'modelsRoomB' => (empty($modelsRoomB)) ? [[new RoomB]] : $modelsRoomB,
'modelsRoomC' => (empty($modelsRoomC)) ? [[new RoomC]] : $modelsRoomC,
]);
In the "_form.php" view, you can see the code above, I've used the two extra arrays in the render of the two extra "_form-rooms".
In the "_form-rooms", I've removed the code representing the model's id, because in the "actionUpdate", I've remove all the "Houses", so, all their "rooms" will be deleted too. After this, I've just do the same as in the "actionCreate" (after the post).
I hope you can understand my solution. It maybe not the best solution, but it works for me. There are more details that I've omited to not to extend too much this reply, but you can always contact me ;)
If you need more details, email me.