Case - Need to select/unselect all checkboxes on change of header checkbox.
Problem - I can emit and receive events. Can see my modified data array. But some reactivity isn't working in Vue I guess.
I have laid out very minimal cut out example for same on CodeSandbox - https://88nz9z64j0.codesandbox.io/
Code -
https://codesandbox.io/s/88nz9z64j0
Note - This has to do something with Array mutation. As I can select individual checkboxes and can see my header checkbox marked check after all are selected. However, reverse is just not happening via same mechanism. How Strange!
There has to be a learning from Array(100).fill(length) as reactive data else I'm doing something silly.
The problem is that Array.fill was modifying the array in place. Vue can't detect changes to an array when you directly modify its elements. You need to create a new array first using split, modify the newly created array and then re-assign it.
this.checkSelections = this.checkSelections.splice(0).fill(allSelected);
CodeSandbox
you should check this article about reactivity in vue and it pitfalls https://medium.com/js-dojo/reactivity-in-vue-js-and-its-pitfalls-de07a29c9407, and certain the section on arrays.
For arrays you better make use of the Vue.set
see fork : https://codesandbox.io/s/m7xqk9qn0j
for(var i = 0; i <= this.checkSelections.length; i++)
{
Vue.set(this.checkSelections, i , allSelected);
}
detailed information can also be found in on the official vue site:
https://v2.vuejs.org/v2/guide/list.html#Caveats
(in this case the length of the array does not change, so caveat1)
On your mounted function on table/index.vue you can use this.$forceUpdate() after changing the array, since vue can't see the changes.
mounted() {
this.$root.$on("selectAll", ({ allSelected }) => {
console.log("selectAll index.vue", allSelected);
this.checkSelections = this.checkSelections.fill(allSelected);
console.log("selectAll", this.checkSelections);
this.$forceUpdate();
});
}
You can see more details on the documentation
Working example
Related
I'm new to React and working on the Select All functionality for a table.
this.state.selectedAccounts = [];
this.props.accountList.forEach((account, index) => {
var row = document.getElementById('accountSelection_' + index);
if (row !== null) {
row.checked = checked;
This is how I'm looping over the selected accounts of the table and passing the checked parameter.
Now, the issue is in the case of pagination, only the accounts that are in current page return the DOM element
document.getElementById('accountSelection_' + index);
, so I can only pass the checked parameter to those and only those check boxes are selected.
For the accounts in next page
document.getElementById('accountSelection_' + index);
returns null, so couldn't pass the checked parameter.
The library used doesn't provide any event on selecting pages, so couldn't capture the page change event and pass the Checked parameter to new rows in the view. I want Select all the Check boxes at once.
Can you tell us what library of pagination your are using ? It will be way more easy to answer you.
My guess on your issue is that your pagination library populate the DOM only with your visible elements. It means that your items in the others pages are simply not in the DOM and that would explain why they are not recoverable by the document.getElementById function.
I see different ways of resolving your issue :
Find another pagination library or make your own implementation of
a simple pagination that hide the items of other pages with CSS.
This would populate the dom with your items and make them
recoverable. Note that this kind of resolution is absolutely not
optimized and can lead of performances issues if you are dealing
with a lot of items.
If your are sending your items via props to your pagination library, that means that your items are stored in your pagination library main component state. Find in the library documentation if there is no ways to checks items by changing an intern property.
Do the check by yourself. You can add to your items an additional property like isChecked, and manually check them all or uncheck them all. Then, when you are rendering your items, you can check if the intern isChecked property of a specific item is marked as checked or not. Based on that, you can easily mark an item as checked. If you cannot control directly how items are rendered by your library, try to find is there is a prop that can allow you to overload the item rendering method of your library like a onRenderingItem prop. Here is a more visual representation of what im saying :
// Item interface
interface Item {
name: string;
isChecked: boolean
}
// Rendering
{items.map((item: Item) =>
<button checked={isChecked} >{name}</button>
)}
Hope that i've helped you at least a little. Feel free to answer me :)
Happy coding !
I'm using Angular 7 along with NgRx. I have created a selector to get some data from the store using filter, but this selector emits when anything on the store changes, even if it is not related to my selector.
I have created a demonstration of my issue. Here is my selector:
export const getMyArrayFilter = createSelector(
getCounterState,
state => state.myArray.filter(x => x === 'new Item')
);
And here I am using my getMyArrayFilter selector:
this.store.pipe(select(fromRoot.getMyArrayFilter)).subscribe(x => console.log(x));
But as mentioned, this selector will emit anytime anything changes on the state.
Please take a look at this StackBlitz demonstration.
If you try clicking on either the "Add item to array" or "-" or "+" buttons, then my getMyArrayFilter will emit, and log to the console each time. Should't my selector only emit values IF the myArray on my state changes?
I have taken a look at this SOF question which mentions using distinctUntilChanged but that doesn't seem to work for me.
As Alejandro pointed out, this is because there's a new reference for myArray.
You can modify the memoization to your needs, Alex Okrushko does exactly this in his talk NgRx: Selectors are more powerful than you think
Doing some testing with your example I just notice that ngrx compare the arrays by reference and not by every item inside of it, what this means is that since the function filter return a new array with a new reference ngrx thinks that is different and that is why the subscribe is called every time something changes.
Creating a new copy will have the same effect:
export const getMyArrayFilter = createSelector(
getCounterState,
state => [ ...state.myArray ]
);
I am attempting to use react-spring's useSprings to enable users to reorder the items in a formik FieldArray. The useSprings Draggable List demo (found here) uses useRef to manage the order of items. FieldArray comes with a number of array helper functions for inserting, removing, and moving items.
The issues that I'm having are:
1) Re-ordering existing items using formik's move array helper method successfully changes the array order, but requires an additional click to render the correct order
2) Adding or removing array items using array helper methods produces unexpected results. Mutating the length of the ref doesn't change the length of order.current inside of useGesture
I've also tried using useState instead of useRef and updating the state with useEffect when props change.
Here is a code sandbox I made: https://codesandbox.io/s/usesprings-with-fieldarray-56bex
In the bind function, commenting out order.current = newOrder; and uncommenting // arrayHelpers.move(currIndex, currRow); shows issue #1 that I mentioned above.
I would like to be able to use formik's move, insert, and remove helper functions with react-spring to seamlessly re-order, add, and delete items within a FieldArray.
maybe you can try setting the new order.current after adding the new element
onClick={() =>{
arrayHelpers.insert(items.length, {
name: `Item ${items.length + 1}`
})
order.current = [...order.current, order.current.length];
}
}
this will add the new item at the end of the list.
I encountered at least your #1 issue.
Note that the the setSprings function doesn't re-render anything on its own, and the useSprings is missing a dependencies array to auto-update.
react-springs#9.0.0.beta-23 has a dependencies array, and together with the useSpringsFixed wrapper in the sandbox that is linked here it should force-rerender on changed props.
Hope that helps your issue too.
Am working on ember app (1.11) and having an issue with computed property not taking updated value when its updated in code . Code as below (have trimmed down, only showing relevant snippet).
Am showing a list of things on UI when iterating through "data" which is a computed property. Then I make a selection from drop down, I am sending a action, triggering that computed property and adding things to that list. When I do this once, I get all the updated list or iteration on UI, but when I do the selection again to increase the list/iteration, the computed property is taking the initial value of list, not the updated one, where I just added one more item, hence not showing correct details.
I am not getting what's going wrong. I could not create a twiddle as well, as its a lot of code and got stuck in error in twiddle.
Parent Component- ehbs
{{pax-detail list=list}}
Parent Component - js
list: function(){
return this.get('arr') //This arr comes from route/controller via query string in url
}.property('arr')
Pax Detail Component- ehbs
{{pax-select action="changed"}}
{{ages ageData=data}}
Pax Detail Component - js
countChanged: '',
actions: {
changed: function(e){
this.set('countChanged',e.target.value)
}
},
data : function(){
var arr = this.get('list')
// doing lot to manipulation - constructing arr/object
if(this.get('countChanged')){
arr.pushObject({}) // basically modifying the initial arr
return arr
} else {
return arr
}
}.property('list','countChanged')
Pax Select Component - ehbs
<select>
<option>0</option>
<option>1</option>
<option>2</option>
</select>
Pax Select Component - js
change: function (e) {
this.sendAction('action',e)
}
Your arr doesn't change if your example ( Changes it's content, but the array is the same )
Try next forms
.property('arr.[]') or .property('arr.length')
or call .notifyPropertyChange('list') manually on modifying data.
In fact your example is just
list: Ember.computed.alias('arr')
What I tried, was that whatever I was doing inside "if" condition in computed property "data" , i moved that to action itself. And then I observed it started working. And not to mention, I had to use arr.[], as i understood that in case items of array are changing, i need to listen to all items.
I am still not sure, why is that so but I thought would post this.
I am trying to update ng-grid with array splice.
I have a plunk here.
Add button adds new row. Update button updates last item in the array.
Select a row & press update button. Nothing happens.
Press add button. Now UI gets updated with new element & as well as the previously updated element.
Same behavior gets repeated again & again.
I tried $scope.$apply. I get:
“Error: $apply already in progress”
I even tried by placing $scope.$apply block inside a setTimeout call. Again the same error!
Any pointers!
Thanks!
That's because data $watcher in ng-grid (incorrectly) compares the data object for reference, instead on object equality. You might remedy this by setting the third parameter to true in data $watch function (line 3128):
$scope.$parent.$watch(options.data, dataWatcher, true);
Plunker
UPDATE (2015-04-10)
Angular has improved their code base (1.4.0), try the $scope.$watchCollection method first, and see if it works for you. (Link)
ANSWER
If you don't feel like hacking into a 3rd party library, you could add the hack in your code using:
$scope.updateData = function() {
var data = angular.copy($scope.myData);
data.splice(data.length - 1, 1, {name: 'UPDATED', age: '4'})
$scope.myData = data;
};
plunkr
As #Stewie mentions, the problem is that for performance reasons ngGrid compares the data object superficially, and in the case of arrays, this is by reference. ngGrid also compares by the array length, so if the array doesn't change it's length the grid wont' get updated.
This solution creates a copy of the array (different place in memory) so that when angularjs $watcher checks for changes it will find a different object and run the ngGrid update callback.
NOTE: Because this solution creates a copy of the data array on every call to updateData, it could lead to performance problems if your data is too big, also Javascript doesn't have a great garbage collection.
Old Incorrect Answer:
$timeout(angular.noop, 0);
This simply sets a timeout to trigger a $scope.$apply() after the current one is done. A way of forcing a dirty check.
I am using ui-grid v3.0.0 (from an April 2015 unstable build). I found this post and wanted to show others how I refreshed my grid after I removed a row from the grid data object using splice:
// Remove the row and refresh the grid.
$scope.myData.splice(rowIndex, 1);
$scope.gridApi.grid.refresh(true);
where my gridApi scope variable was set with this function:
$scope.gridOptions.onRegisterApi = function(gridApi){
$scope.gridApi = gridApi;
}