Sortable list of components - javascript

I've made my filters section using vue.js. I inject all the components through ajax and they response dynamically to those filters. Components in my case represent cars, they have price, marks, etc...
Now I'd like to add two more filters that allow me to sort them by some field (price, for instance). I've been reading and it's quite easy to sort lists specifying a field and a order...
How should I proceed to create that list and so, being able to sort it.
Here I made a little fiddle, very simple, in which I'd to sort the cars by prize once I click the filter.
var Car = Vue.extend({
template: '#template_box_car',
props: {
show: {
default: true
},
code: {
default: ""
},
prize: {
default: 0
},
description: {
default: "No comment"
}
}
});
//register component
Vue.component('box_car',Car);
//create a root instance
var vm = new Vue({
el: 'body',
methods: {
sortBy: function(field, order){
}
}
});

First, store the data for each car component in a data property in the parent component:
data: function () {
return {
cars: [
{ code: '11A', prize: 5.00, description: 'Ford Ka' },
{ code: '11B', prize: 3.00, description: 'Kia ceed' },
{ code: '11C', prize: 6.00, description: 'Ford Ka' },
{ code: '13A', prize: 45.00, description: 'Mercedes A' },
{ code: '17B', prize: 20.00, description: 'Seat Leon' },
]
}
},
Then, use the v-for directive to create a box_carcomponent for each of the objects in your cars data property:
// In your version of Vue.js it would look like this:
<box_car
v-for="car in cars"
:code="car.code"
:prize="car.prize"
:description="car.description"
:track-by="code"
></box_car>
// In newer versions of Vue.js, you can pass each object to the `v-bind` directive
// so you don't need to explicitly set each property:
<box_car v-for="car in cars" v-bind="car" :key="car.code"></box_car>
Then, in your sortBy method, simply sort the cars array:
// I used lodash, but you can sort it however you want:
methods: {
sortBy: function(field, order) {
this.cars = _.orderBy(this.cars, field, order);
}
}
Here's a working fiddle.

Related

Updating child array in reducer using React Context

I am doing some filtering using React Context and I am having some difficulty in updating a child's array value when a filter is selected.
I want to be able to filter by a minimum price, which is selected in a dropdown by the user, I then dispatch an action to store that in the reducers state, however, when I try and update an inner array (homes: []) that lives inside the developments array (which is populated with data on load), I seem to wipe out the existing data which was outside the inner array?
In a nutshell, I need to be able to maintain the existing developments array, and filter out by price within the homes array, I have provided a copy of my example code before, please let me know if I have explained this well enough!
export const initialState = {
priceRange: {
min: null
},
developments: []
};
// Once populated on load, the developments array (in the initialState object)
// will have a structure like this,
// I want to be able to filter the developments by price which is found below
developments: [
name: 'Foo',
location: 'Bar',
distance: 'xxx miles',
homes: [
{
name: 'Foo',
price: 100000
},
{
name: 'Bar',
price: 200000
}
]
]
case 'MIN_PRICE':
return {
...state,
priceRange: {
...state.priceRange,
min: action.payload
},
developments: [
...state.developments.map(development => {
// Something here is causing it to break I believe?
development.homes.filter(house => house.price < action.payload);
})
]
};
<Select onChange={event=>
dropdownContext.dispatch({ type: 'MIN_PRICE' payload: event.value }) } />
You have to separate homes from the other properties, then you can apply the filter and rebuild a development object:
return = {
...state,
priceRange: {
...state.priceRange,
min: action.payload
},
developments: state.developments.map(({homes, ...other}) => {
return {
...other,
homes: homes.filter(house => house.price < action.payload)
}
})
}

Update dynamic components disabled state based on Vuex state value

I have no idea if what I'm doing is correct or not, but here's a simplified version of what I'm trying to do:
I want to have 3 file inputs, with the 2nd and 3rd disabled until the 1st one has had a file selected.
I've tried to do is set the Vuex state variable to whatever the first file input is has selected, but upon doing that the other 2 inputs don't update their disabled state.
I have some file inputs that are created dynamically, like so:
Vue.component('file-input', {
props: ['items'],
template: `<div><input type="file" v-on:change="fileSelect(item)" v-bind:id="item.id" v-bind:disabled="disabledState"></div>`,
methods: {
fileSelect: function(item) {
store.commit('fileSelect', file);
}
},
computed: {
disabledState: function (item) {
return {
disabled: item.dependsOn && store.getters.getStateValue(item.dependsOn)
}
}
}
}
The data for the component is from the instance:
var vm = new Vue({
data: {
items: [
{ text: "One", id: "selectOne" },
{ text: "Two", id: "selectTwo", dependsOn: "fileOne" },
{ text: "Three", id: "selectThree", dependsOn: "fileOne" }
}
});
Now, notice the "dependsOn". In the Vuex store, I have a corresponding state item:
const store = new Vuex.Store({
state: {
files: [
{
fileOne: null
}
]
},
mutations: {
fileSelect(state, file) {
state.files.fileOne = file;
}
},
getters: {
getStateValue: (state) => (stateObject) => {
return state.files.findIndex(x => x[stateObject] === null) === 0 ? true : false;
}
}
});
Now, the above works when everything is first initialized. But once the first input has something selected, the other two inputs don't change.
I'm not sure how to update the bindings once a mutation of the state occurs.
I think you need to refactor your mutation to make the state property mutable, like this:
fileSelect(state, file) {
Vue.set(state.files[0].fileOne, file);
}
Well, I figured it out...
Because my state object is an array of objects, I can't just change one of the property's values with state.files.fileOne. I needed to do state.files[0].fileOne.

How to deep clone the state and roll back in Vuex?

In Vuex I would like to take a snapshot / clone of an object property in the tree, modify it, and later possibly roll back to the former snapshot.
Background:
In an application the user can try out certain changes before applying them. When applying the changes, they should effect the main vuex tree. The user can also click «cancel» to discard the changes and go back to the former state.
Example:
state: {
tryout: {},
animals: [
dogs: [
{ breed: 'poodle' },
{ breed: 'dachshund' },
]
]
}
User enters »Try out« mode and changes one breed from poodle to chihuahua. She then decides either to discard the changes or apply them.
state: {
animals: [
dogs: [
{ breed: 'poodle' },
{ breed: 'dachshund' },
]
],
tryout: {
animals: [
dogs: [
{ breed: 'chihuahua' },
{ breed: 'dachshund' },
]
]
}
}
Discard (rolls back to previous state):
state: {
animals: [
dogs: [
{ breed: 'poodle' },
{ breed: 'dachshund' },
]
],
tryout: {}
}
Apply (saves the changes in main vuex tree):
state: {
animals: [
dogs: [
{ breed: 'chihuahua' },
{ breed: 'dachshund' },
]
],
tryout: {}
}
What are good solutions to deep clone a state, make changes on the clone, and later on either discard the changes or apply them?
The example here is very basic, the solution must work with more complex objects / trees.
Edit 1:
There is a library called vuex-undo-redo, which basically logs mutations, but has some problems. In another Stack Overflow topic Going back to States like Undo Redo on Vue.js vuex it is recommended to use the vuex function replaceState(state).
You can use JSON.stringify and JSON.parse with replaceState.
In vuex:
const undoStates = [];
// save state
undoStates.push(JSON.stringify(state));
// call state (remove from stack)
if (undoStates.length > 0) {
this.replaceState(JSON.parse(undoStates.pop()));
}
That will create a copy of the entire state, but you can also use a part of the store:
const animalStates = [];
// save state
animalStates.push(JSON.stringify(state.animals));
// call state (remove from stack)
if (animalStates.length > 0) {
let animals = JSON.parse(animalStates.pop());
this.replaceState({...state, animals} );
}
This will merge the current state with an object you chose (like animals in this case).

Normalizr - is it a way to generate IDs for non-ids entity model?

I'm using normalizr util to process API response based on non-ids model. As I know, typically normalizr works with ids model, but maybe there is a some way to generate ids "on the go"?
My API response example:
```
// input data:
const inputData = {
doctors: [
{
name: Jon,
post: chief
},
{
name: Marta,
post: nurse
},
//....
}
// expected output data:
const outputData = {
entities: {
nameCards : {
uniqueID_0: { id: uniqueID_0, name: Jon, post: uniqueID_3 },
uniqueID_1: { id: uniqueID_1, name: Marta, post: uniqueID_4 }
},
positions: {
uniqueID_3: { id: uniqueID_3, post: chief },
uniqueID_4: { id: uniqueID_4, post: nurse }
}
},
result: uniqueID_0
}
```
P.S.
I heard from someone about generating IDs "by the hood" in normalizr for such cases as my, but I did found such solution.
As mentioned in this issue:
Normalizr is never going to be able to generate unique IDs for you. We
don't do any memoization or anything internally, as that would be
unnecessary for most people.
Your working solution is okay, but will fail if you receive one of
these entities again later from another API endpoint.
My recommendation would be to find something that's constant and
unique on your entities and use that as something to generate unique
IDs from.
And then, as mentioned in the docs, you need to set idAttribute to replace 'id' with another key:
const data = { id_str: '123', url: 'https://twitter.com', user: { id_str: '456', name: 'Jimmy' } };
const user = new schema.Entity('users', {}, { idAttribute: 'id_str' });
const tweet = new schema.Entity('tweets', { user: user }, {
idAttribute: 'id_str',
// Apply everything from entityB over entityA, except for "favorites"
mergeStrategy: (entityA, entityB) => ({
...entityA,
...entityB,
favorites: entityA.favorites
}),
// Remove the URL field from the entity
processStrategy: (entity) => omit(entity, 'url')
});
const normalizedData = normalize(data, tweet);
EDIT
You can always provide unique id's using external lib or by hand:
inputData.doctors = inputData.doctors.map((doc, idx) => ({
...doc,
id: `doctor_${idx}`
}))
Have a processStrategy which is basically a function and in that function assign your id's there, ie. value.id = uuid(). Visit the link below to see an example https://github.com/paularmstrong/normalizr/issues/256

Updating KnockoutJS associative observableArray values

This should be really simple.
I have an associative observable array with a name and a boolean value.
this.items = ko.observableArray([
{ name: "name1", boolVal: true },
{ name: "name2", boolVal: true },
]);
Then a simple function to change boolVal.
this.changeValue = function (item) {
item.boolVal = false;
};
When I call the changeValue function, boolVal does change (see console.log(data) in my jsfiddle) but the view doesn't update. The value on the screen remains "true". I must be making an incorrect assumption regarding how KnockoutJS works.
JS Fiddle Link
In order to the KO update UI you need to have observable properties:
this.items = ko.observableArray([
{ name: "name1", boolVal: ko.observable(true) },
{ name: "name2", boolVal: ko.observable(true) },
]);
And set it with:
this.changeValue = function (item) {
item.boolVal(false);
};
The ko.observableArray only tracks item addition and removal. So it won't notify the UI if one of its items changed. For that you need to have ko.observable on the items.
Demo JSFiddle.

Categories

Resources