Change styling of dojo treegrid cell if value changes - dojo

What I have is a treegrid populated with values from ajax. Every 30 seconds the store is refreshed and new data is displayed.
I need to change the styling (color or background-color) of a treegrid cell when it's value differs from the old one. The requirement is to make the comparison and styling from javascript.
Any ideas on how this could be done ?

You could use dijit.Tree's getRowStyle method to modify the style dynamically. It will be called whenever a row needs to be rendered.
Something like this might get you started:
(function(){ // closure for private variables
var previousValues = {};
var myTree = ... // lookup dijit.Tree via dijit.byId, or create
myTree.getRowStyle = function(item){
var style = {};
var itemId = myTree.store.getIdentity(item);
var newValue = myTree.store.getValue(item, "MY_ITEM_VALUE");
if(newValue !== null &&
previousValues[itemId] !== null &&
previousValues[itemId] !== newValue) {
style.backgroundColor = "#0000FF";
previousValues[itemId] = newValue;
}
return style;
};
})();
There may be better ways to keep track of the previous values, but since your store is getting changed, I really can't think of one.

Related

Vuex model update won't reload computed property

I have the following component to quickly configure stops on a delivery/pickup route and how many items are picked up and dropped
and this is the data model, note the 2 is the one next to 'a' on the previous image.
If a click the + or - button, in the first item, it behaves as expected,
But second item doesn't work as expected
I've already checke a couple of posts on object property update likes this ones
Is it possible to mutate properties from an arbitrarily nested child component in vue.js without having a chain of events in the entire hierarchy?
https://forum.vuejs.org/t/nested-props-mutations-hell-internet-need-clarification/99346
https://forum.vuejs.org/t/is-mutating-object-props-bad-practice/17448
among others, and came up with this code:
ADD_ITEM_TO_SELECTED_STOP(state, payload) {
let count = state.selectedStop.categories[payload.catIndex].items[payload.itemIndex].count;
const selectedCat = state.selectedStop.categories[payload.catIndex];
const currentItem = selectedCat.items[payload.itemIndex];
currentItem.count = count + 1;
selectedCat.items[payload.itemIndex] = currentItem;
Vue.set(state.selectedStop.categories, payload.catIndex, selectedCat);
},
and as the button event:
addToItem(item) {
this.$store.dispatch("addItemToSelectedStop", {
catIndex: item.catIndex,
itemIndex: item.itemIndex
})
},
And finally my computed property code:
items() {
let finalArray = [];
this.selectedStop.categories.forEach(
(cat, catIndex) => {
let selected = cat.items.filter((item) => item.count > 0 );
if (selected.length > 0) {
//here we add the catIndex and itemIndex to have it calling the rigth shit
selected = selected.map(val => {
let itemIndex = cat.items.findIndex( itemToFind => itemToFind.id === val.id);
return {
...val,
catIndex: catIndex,
itemIndex: itemIndex,
}})
finalArray = finalArray.concat(selected);
}
});
return finalArray;
}
What confuses me the most is that I have almost the same code in another component, and there it's working as expected, and although the model is changed, the computed property is only recalculated on the first item,
After reading this gist and taking a look again at the posts describing this kind of issue, I decided to give it a try and just make a copy of the whole stored object not just the property, update it, then set it back on vuex using Vue.set, and that did the trick, everything is now working as expected, this is my final store method.
ADD_ITEM_TO_SELECTED_STOP(state, payload) {
let selectedLocalStop = JSON.parse(JSON.stringify(state.selectedStop));
let count = selectedLocalStop.categories[payload.catIndex].items[payload.itemIndex].count;
selectedLocalStop.categories[payload.catIndex].items[payload.itemIndex].count = count + 1;
Vue.set(state,"selectedStop", selectedLocalStop );
//Now we search for this step on the main list
const stepIndex = state.stops.findIndex(val => val.id === selectedLocalStop.id);
Vue.set(state.stops,stepIndex, selectedLocalStop );
},
I had to add the last bit after updating the whole object, because, originally, the array items were updated when the selected item was changed, I guess some sort of reference, but with the object creation, that relationship no longer works "automatic" so I need to update the array by hand

How to prevent closing of cell edit mode on validation errors with custom vue components in ag-grid

I have succesfully rendered my own component as the cellEditor and would like and on-leave I would like it to try to validate the value and prevent the closing if it fails.
If I look at this then https://www.ag-grid.com/javascript-grid-cell-editing/#editing-api there's cancelable callback functions for editing. But in this callback function is there a way to access the current instantiated component? I would think that would be the easiest way to handle this.
I'm using vee-validate so the validation function is async, just to keep in mind.
Use Full row editing.
Create a global variable like
var problemRow = -1;
Then Subscribe to this events:
onRowEditingStarted: function (event) {
if (problemRow!=-1 && event.rowIndex!=problemRow) {
gridOptions.api.stopEditing();
gridOptions.api.startEditingCell({
rowIndex: problemRow,
colKey: 'the column you want to focus',
});
}
},
onRowEditingStopped: function (event) {
if (problemRow==-1) {
if (event.data.firstName != "your validation") {
problemRow = event.rowIndex
gridOptions.api.startEditingCell({
rowIndex: problemRow,
colKey: 'the column you want to focus',
});
}
}
if (problemRow == event.rowIndex) {
if (event.data.firstName != "your validation") {
problemRow = event.rowIndex
gridOptions.api.startEditingCell({
rowIndex: problemRow,
colKey: 'the column you want to focus',
});
}
else{
problemRow=-1;
}
}
},
I had a similar issue - albeit in AngularJS and the non-Angular mode for ag-grid - I needed to prevent the navigation when the cell editor didn't pass validation.
The documentation is not very detailed, so in the end I added a custom cell editor with a form wrapped around the input field (to handle the niceties such as red highlighting etc), and then used Angular JS validation. That got me so far, but the crucial part was trying to prevent the user tabbing out or away when the value was invalid so the user could at least fix the issue.
I did this by adding a value parser when adding the cell, and then within that if the value was invalid according to various rules, throw an exception. Not ideal, I know - but it does prevent ag-grid from trying to move away from the cell.
I tried loads of approaches to solving this - using the tabToNextCell events, suppressKeyboardEvent, navigateToNextCell, onCellEditingStopped - to name a few - this was the only thing that got it working correctly.
Here's my value parser, for what it's worth:
var codeParser = function (args) {
var cellEditor = _controller.currentCellEditor.children['codeValue'];
var paycodeId = +args.colDef.field;
var paycodeInfo = _controller.paycodes.filter(function (f) { return f.id === paycodeId; })[0];
// Check against any mask
if (paycodeInfo && paycodeInfo.mask) {
var reg = new RegExp("^" + paycodeInfo.mask + '$');
var match = args.newValue.match(reg);
if (!match) {
$mdToast.show($mdToast.simple().textContent('Invalid value - does not match paycode format.').position('top right').toastClass('errorToast'))
.then(function(r) {
_controller.currentCellEditor.children['codeValue'].focus();
});
throw 'Invalid value - does not match paycode format.';
}
}
return true;
};
The _controller.currentCellEditor value is set during the init of the cell editor component. I do this so I can then refocus the control after the error has been shown in the toast:
CodeValueEditor.prototype.init = function (params) {
var form = document.createElement('form');
form.setAttribute('id', 'mainForm');
form.setAttribute('name', 'mainForm');
var input = document.createElement('input');
input.classList.add('ag-cell-edit-input');
input.classList.add('paycode-editor');
input.setAttribute('name', 'codeValue');
input.setAttribute('id', 'codeValue');
input.tabIndex = "0";
input.value = params.value;
if (params.mask) {
input.setAttribute('data-mask', params.mask);
input.setAttribute('ng-pattern','/^' + params.mask + '$/');
input.setAttribute('ng-class',"{'pattern-error': mainForm.codeValue.$error.pattern}");
input.setAttribute('ng-model', 'ctl.currentValue');
}
form.appendChild(input);
this.container = form;
$compile(this.container)($scope);
_controller.currentValue = null;
// This is crucial - we can then reference the container in
// the parser later on to refocus the control
_controller.currentCellEditor = this.container;
$scope.$digest();
};
And then cleared in the grid options onCellEditingStopped event:
onCellEditingStopped: function (event) {
$scope.$apply(function() {
_controller.currentCellEditor = null;
});
},
I realise it's not specifically for your components (Vue.js) but hopefully it'll help someone else. If anyone has done it a better way, I'm all ears as I don't like throwing the unnecessary exception!

Dojo - How to set "label" of togglebutton inside a grid from row.data.SOMEFIELD value?

i'm new to Dojo.
I have a "FullEditable" grid, with many columns, and one of them is a widget, it's a ToggleButton. This grid is used to show details of a purchase, so every line represents a product sold. When i click a button inside one of the columns it launches the product (dialog) browser and then the data of that product is passed to the grid. After some work i managed to display the button and being able to set it's label accordingly to the row.data.FIELD value received from that dialog.
But now i am editing that purchase and i need to be able to do the same, to be able to load the data from the preloaded array with data ("detalle") (as it's label) into the togglebutton, but nothing seems to work, not even the direct assignment of the widget or the row data or anything.
Here's a fragment of my code:
var grid = this;
var selectedRow = grid.row(i);
var cell = grid.cell(selectedRow, 'Moneda');
selectedRow.Moneda = detalle.MonedaItem; //didn't work
if(cell.row !== null && cell.row !== undefined && cell.row.data !== null && cell.row.data !== undefined)
cell.row.data.Moneda = detalle.MonedaItem; //didn't work
if(cell.element !== null && cell.element !== undefined){
cell.element.innerText = detalle.MonedaItem; //didn't work
cell.element.textContent = detalle.MonedaItem; //didn't work
if(detalle.MonedaItem === 'UF')
cell.element.widget.set('checked', true);
if(detalle.MonedaItem === 'CLP')
cell.element.widget.set('checked', false);
cell.element.widget.set('label', detalle.MonedaItem); //didn't work
cell.element.widget.set('value', detalle.MonedaItem); //didn't work
}
}
Whenever i set the value through the change event with "cell.element.widget.set('checked', boolean);" i get an error message from the editor, when it tries to get a cell but the variable is null, so it crashes.
The values of "label" and "value" of the widget, row.data en cell are perfectly set, BUT the grid DISPLAYS the "emptyValue" value of the widget and not the one that is actually set.
AFAIK, best way to change the grid data is to update the store/ collection that is associated with the grid and just invoke grid.refresh(). The new data will be loaded automatically.
The statement cell.row.data.Moneda = detalle.MonedaItem is actually changing the data in your store. Just refresh the grid after this and you are done!
Never try to set something in grid DOM element directly. Always work with store, renderCell and renderColumn. This will ensure consistency in store and grid attributes that reference the store.
I finally managed to get what i need through renderCell like this:
,{
id: 'Moneda',
field: 'Moneda',
label: "Moneda",
renderCell: function (object, value, node, options) {
try {
var button = new ToggleButton({
node: node,
isWidgetInGrid: true,
parameterName: 'codigoMoneda',
label: object.Moneda,
showLabel: true,
onChange: function(newValue){
var grid = this.getParent();
var row = grid.row(this.node);
var data = row.data;
if(this.get('label') === 'CLP')
{
data.Moneda = 'UF';
this.set('label','UF');
this.set('value', 'UF');
}
else
{
data.Moneda = 'CLP';
this.set('label','CLP');
this.set('value', 'CLP');
}
grid.getParent().getParent()._calcularTotales();
}
});
node.appendChild(button.domNode);
}
catch (ex) {
Debug.log(this.declaredClass, arguments, ex);
}
},
emptyValue: 'CLP',
autoSave: true
}

How to refresh datagrid

I create dojox.grid.datagrid and I fill content from array like on example last example on page. During time, I change value of that array in code. How to refresh content of that grid ? How to load new data from changed array ?
To change values in the grid, you will need to change the value in the grid's store. The grid data is bound to the store data, and the grid will update itself as needed.
So the key is to understand Dojo's data api and how stores work in Dojo. Rather than manipulating the data directly in the grid, manipulate it in the store.
Ideally, the store is your array that you manipulate as the application runs and you should not be needing to sync the array to the grid. Just use the ItemFileWriteStore as your data holder unless thats not possible.
Also, using the dojo data identity api makes it much simple to find items in the grid if that is possible. Assuming you know when an item is updated, deleted, or changed in your application you should be able to modify the grid store as needed when the action happens. This is definitely the preferred approach. If you can't do that you will have to do a general fetch and use the onComplete callback to manually sync your arrays which will be very slow and won't scale well, in which case you may as well just create a new store all together and assign it to the grid with grid.setStore(myNewStore)
Here is a fiddle with a basic create, update, and delete operation: http://jsfiddle.net/BC7yT/11/
These examples all take advantage of declaring an identity when creating the store.
var store = new dojo.data.ItemFileWriteStore({
data: {
identifier : 'planet',
items: itemList
}
});
UPDATE AN EXISITNG ITEM:
//If the store is not in your scope you can get it from the grid
var store = grid.store;
//fetchItemByIdentity would be faster here, but this uses query just to show
//it is also possible
store.fetch({query : {planet : 'Zoron'},
onItem : function (item ) {
var humans = store.getValue(item, 'humanPop');
humans += 200;
store.setValue(item, 'humanPop', humans);
}
});
INSERT A NEW ITEM:
store.newItem({planet: 'Endron', humanPop : 40000, alienPop : 9000});
} catch (e) {
//An item with the same identity already exists
}
DELETE AN ITEM:
store.fetchItemByIdentity({ 'identity' : 'Gaxula', onItem : function (item ) {
if(item == null) {
//Item does not exist
} else {
store.deleteItem(item);
}
}});
The following code snippet can be used to update the grid:
var newStore = new dojo.data.ItemFileReadStore({data: {... some new data ...});
var grid = dijit.byId("gridId");
grid.setStore(newStore);
EDIT:
Dogo data grid reference guide (add/remove rows example, updating grid data examples )
(I suppose you already have a working grid and you want to completely change the grid's store)
Create a new datastore with your new value :
dataStore = new ObjectStore({ objectStore:new Memory({ data: data.items }) });
(data is the reponse from an ajax request for me)
Change your grid's store with the new one :
grid.store = dataStore;
Render :
grid.render();
This Will update Grid Store and refresh the View of the Grid in latest Version of Dojo 1.9
grid.store = store;
grid._refresh();
I had a server-side filtered EnhancedGrid, which was refreshing happily by changing the store, and shown in the other answers.
However I had another EnhancedGrid that would not refresh when a filter was applied. It may have been to do with the fact it was filtered client side (but data still coming from server using JsonRest store), but I don't really know the cause. Eitherway, the solution was to refresh with the following code:
grid.setFilter(grid.getFilter());
It's hacky and strange, but if it all else fails...
with this i can update a specifi row. this example is for a treegrid.
var idx = this.treeGrid.getItemIndex(item);
if(typeof idx == "string"){
this.treeGrid.updateRow(idx.split('/')[0]);
}else if(idx > -1){
this.treeGrid.updateRow(idx);
}

jQuery - Page with a ton of checkboxes, how to bind?

I have a page of checkboxes, in some cases more than 100. I'm currently doing this:
$('form[name=myForm] input[name=myCheckbox]').change(function(){
var numChkd = $('input[name=myCheckbox]:checked').size();
console.log(numChkd);
};
But as you could imagine this can get wicked slow. Is there a better way to bind an event to multiple elements? This works, but I want to know if there is a better way?
You can bind an event to the parent container that will wrap all of the checkboxes and then check if the object that caused an event is a checkbox. This way you only bind one event handler. In jQuery you can use $.live event for this.
Don't recount every time a checkbox changes. Just use a global variable, like this:
var CheckboxesTicked = 0;
$(document).ready(function() {
CheckboxesTicked = $(":checkbox:checked").length;
$(":checkbox").change(function() {
if ($(this).is(":checked")) {
CheckboxesTicked += 1
} else {
CheckboxesTicked -= 1
}
});
});
Btw, the documentation states that you'd better use .length instead of .size() performance wise.
You could create a container element (like a Div with no styling) and attach the event handler to the container. That way, when the change() event happens on one of the checkboxes and percolates up the DOM, you'll catch it at the container level. That's one way to make this faster.
You should use .delegate(). One binding on a parent element can replace all the individual bindings on the child elements. It's perfect for this situation (and also solves the problem of attaching behavior to dynamically-added elements, should the need arise).
$('form[name=myForm]').delegate('input[name=myCheckbox]','change', function(){
var numChkd = $(this).siblings(':checked').length; // assuming siblings
console.log(numChkd);
});
This is how I would approach it:
$('form[name=myForm]').each(function() {
var $form = $(this),
$boxes = $form.find('input[name=myCheckbox]');
$form.delegate('input[name=myCheckbox]', 'change', function() {
var numChkd = $boxes.filter(':checked').length;
console.log(numChkd);
});
});
This takes advantage of caching the $boxes selection. It will look for all the boxes when it sets up the event. It uses .delegate() to attach an event to the form which will get fired anytime an child input[name=myCheckbox] creates a change event. In this event handler, you can easily filter the already obtained list of checkboxes by which ones are :checked and get the length of the matched elements. (The documentation for .size() basically states there is no reason to ever use it... It just returns this.length anyway...)
See this fiddle for a working demo
demo: http://jsfiddle.net/kKUdm/
$(':checkbox').change(function(){
if($(this).is(':checked')){
var name = $(this).attr('name');
var value = $(this).val();
console.log(name + ':' + value);
}
});
Var $chks = $(":checkbox");
Var ChkCount =0;
Var chktimer =0;
Function updateChkCount(){
ChkCount = $chks.filter(":checked").length;
$chks.end();
// do something witb ChkCount
}
$chks.bind("check change", function(){
clearInterval(chktimer);
chktimer = setInterval("updateChkCount()",250);
});