Vue.js counter variable causing infinite loop - vue.js

So, I have some data coming in and rendering a table with an indeterminate number of columns. I would like to count those columns to correctly display a colspan td in the footer. I initialize a variable called footerCallspan with a value of 2...
var app = new Vue({
el: '#order-products',
data: {
footerColspan: 2, // default to 2 for title and quantity
},
//...
My footer table cell looks like ...
<td v-bind:colspan="footerColspan" class="summary">some text</td>
Each of the columns in the main table check a function to determine whether or not they should display ...
<th v-if="checkShowField(null, 'field_name')"scope="col">Name</th>
Finally, that function looks like ...
checkShowField(p, field) {
let pTest = p;
if (p == null) {
pTest = this.products[Object.keys(this.products)[0]];
}
if (pTest.hasOwnProperty(field)){
if (p == null) { // only increment on header row
// let span = this.footerColspan + 1;
// alert(span);
// this.footerColspan = span;
// alert(this.footerColspan);
}
return true;
}
return false;
},
No matter how I try to increment footerColspan inside that function, it seems to create an infinite loop.
I'm new to Vue, so I'm not sure if trying to increment that variable causes the table to re-render therefore triggering more calls to that function or what.
Anyone have any guidance here? If not, any other suggestions for counting the number of columns for my purpose?
Thanks!

I suspect your infinite loop issue is caused by incrementing this.footerColspan inside a v-if check. Basically footerColspan is reactive and the redraw happens every time it is changed and then v-if changed it again, and thus the infinite loop.
if there is a sample of your products structure and what determines the columns to be displayed, it will be easier to try and come up with a solution to count the columns

Related

Input changins in V-for does not make update placeholder and input

I'm creating a passengers form, In this form it gets adult and child counts and children ages. At the beginning childCount equals 0 and inputs of childAges are invisible. When i increase child count, they are visible one by one. So far it is ok. However while increasing childAge, input and placeholder value do not change. By the way at the the background value is changing.
I want to be updated value in input while changing. I am sharing code via jsfiddle
Please, not only fix my code, please share how it works.
Thank you.
enter code here
Due to limitations in JavaScript, Vue cannot detect the following
changes to an array:
When you directly set an item with the index, e.g.
vm.items[indexOfItem] = newValue. When you modify the length of the
array, e.g. vm.items.length = newLength.
Reference.
increaseChildAge: function(index) {
this.$set(this.childAges, index, this.childAges[index] + 1);
},
decreaseChildAge: function(index) {
if (this.childAges[index] > 0) {
this.$set(this.childAges, index, this.childAges[index] - 1);
}
}

Changing complex computed object in vue.js

I have complicated object for a table. Looks like this:
{
1510002000: {
date: "07.11.17"
hours: {
1510002000:{
activity:{
activity: "Тест",
color: "#00ff00",
end_at: 1510005600,
start_at: 1510002000,
type_id: 1
}
},
1510005600: {
...
},
...
}
},
....
}
This is a code from template that uses this object:
<tr v-for="date in this.tds">
<td>{{ date.date }}</td>
<td is="hour-td"
v-for="hour in date.hours"
:color="hour.activity.color"
:start_at="hour.activity.start_at"
:end_at="hour.activity.end_at"
:activity="hour.activity.activity"
:type_id="hour.activity.type_id"
>
</td>
</tr>
I evaluated it as a computed property, but I need to rerender table when parent component provides data assync, so I have a watcher for prop (prop called "activities"):
watch: {
activities: function(){
var vm = this;
let dth = new DateTimeHelper;
if (this.activities.length > 0){
this.activities.forEach(function(activity){
let dateTimestamp = dth.getDateTimestampFromTimestamp(activity.start_at); // just getting the key
if (vm.tds[dateTimestamp]){
if (vm.tds[dateTimestamp].hours[activity.start_at]){
vm.tds[dateTimestamp].hours[activity.start_at].activity.activity = activity.activity;
vm.tds[dateTimestamp].hours[activity.start_at].activity.color = activity.color;
vm.tds[dateTimestamp].hours[activity.start_at].activity.type_id = activity.type_id;
}
}
});
}
console.log(vm.tds) // here I can see that the object is filled with new data
}
},
The problem is that the table doesn't rerender. More precisely, the component "hour-td" does not contain the new data.
Also I've tried to use Vue.set, but no success with that
Can you help me with the updating table? I've spent like 5 hours for refactoring and attempts.
Thanks in advance
SOLUTION
In my case there can be two states: there are activities, there are no activities. So I made two computed props for each case and render them separately and switch by v-if="activities.length"
I think that your problem is with Vue known issue for Change Detection Caveats (you can read here) with array direct assignation, that don't detect changes.
You should change this part of code (with direct array assignation):
if (vm.tds[dateTimestamp]){
if (vm.tds[dateTimestamp].hours[activity.start_at]){
vm.tds[dateTimestamp].hours[activity.start_at].activity.activity = activity.activity;
vm.tds[dateTimestamp].hours[activity.start_at].activity.color = activity.color;
vm.tds[dateTimestamp].hours[activity.start_at].activity.type_id = activity.type_id;
}
}
With the Vue.set() option for arrays in order to detect the change and re-renders the component. It worked for me in differents occassions:
// Vue.set(object, key, value)
// something like this:
Vue.set(vm.tds[dateTimestamp].hours[activity.start_at].activity.activity,1,activity.activity);
More info here: https://codingexplained.com/coding/front-end/vue-js/array-change-detection
Edit:
I see now that you said:
Also I've tried to use Vue.set, but no success with that
What you mean with: "no success" ? Can you share the code? I have the same issue and I resolved with Vue.set..
You can also take a look to vm.$forceUpdate(), try to execute after the last console.log or grab all the code inside a vm.$nextTick( [callback] ) in order to execute all the actions (load data in the table) and then, re-render the component on next tick.
More info here: https://v2.vuejs.org/v2/api/#vm-forceUpdate && https://v2.vuejs.org/v2/api/#Vue-nextTick
Edit 2:
I think that your problem is with the index of the array, you should take a look here: https://v2.vuejs.org/v2/guide/list.html#Array-Change-Detection .
Try changing the:
if (vm.tds[dateTimestamp]){
if (vm.tds[dateTimestamp].hours[activity.start_at]){
vm.tds[dateTimestamp].hours[activity.start_at].activity.activity = activity.activity;
vm.tds[dateTimestamp].hours[activity.start_at].activity.color = activity.color;
vm.tds[dateTimestamp].hours[activity.start_at].activity.type_id = activity.type_id;
}
}
and simplify with:
if (vm.tds[dateTimestamp] && vm.tds[dateTimestamp].hours[activity.start_at]){
Vue.set( vm.tds, vm.tds.indexOf(vm.tds[dateTimestamp].hours[activity.start_at]), activity);
}
Hope it helps!

DataTables - createdRow in conjunction with dynamic column hide/unhide

I implemented a dynamic column visibility to hide/show columns, e.g. DT.columns(5).visible(true) and DT.columns(5).visible(false) to show/hide column 5. I also have a callback for createdRow which let's say makes a html button out of the text in column 5.
Currently in createdRow I check this.api().columns(5).visible() and if true then I proceed to do $('td:eq(5)',row).html('<button>'+data[5]+'</button>') cell content, and if false then I return because otherwise I'd overwrite the current 5th <td> which is not the intended one. But then when I unhide column 5, it comes up as text since I did not run the previous code.
I'd like to be able to run the code and cache the html of the cell in column 5 so that when it's unhidden, it comes up with the intended <button>. What's the best way to do that? The column-visibility event?
Note that I know I can use the render: function (...) {} option for the column, but I do not want to use that because it affects the raw data of the table and affects filtering and searching - I want the raw data to remain unaffected, which is why I was using the createdRow or rawCallback callbacks.
Assume column 5 has visibile: false in the DataTable initialization object which looks like this:
var DT = $('#dt').DataTable({
...
createdRow: function (row, data) {
if (this.api().column(5).visible()) {
$('<button>' + data[5] + </button>')
.appendTo($('td:eq(5)',row).text(''));
}
);
Cheers
Look into using columnDefs and the 'render' function instead...
var DT = $('#dt').DataTable({
...
"columnDefs":[
{
'targets':[0],
'visible':false,
'render':function(data, type, row, meta){
...
}
]
});
https://datatables.net/reference/option/columns.render
https://datatables.net/reference/option/columns.data

extjs 4.1 how to reset the itemselector

I am using extjs 4.1.1a for developing some application.
I had a form consisting of two combo-boxes and an item-selector.
Based on the value selected in first combo-box , the itemselector will load its data from database. This is working fine.
My problem is, if i reselect the first combo-box the new data will be displayed in itemselector along with previous data displayed in itemseletor .That is previous data displayed in itemselector will remain there itself.
for example: name "test1" consists of ids 801,2088,5000. on selecting test1 in firstcombobox itemselector must show output as below.
and if "test2" consists of ids 6090,5040. on selecting test2 in firstcombobox itemselector must show output as below.
problem is. for first time if i select "test1" from firstcombobox , output will come as expected. if i reselect "test2" from firstcombobox , output will come as below.
as you can see, previous data displayed (marked in red rectagle) remains there itself with new data displayed (marked with green rectangle).
I want for every reselection of first combobox, previously displayed data in itemselector to be erased before printing new data on itemselector.
How can I reset the itemselector for every reselection of first combobox?
You should remove all items from the store of the itemselector by the removeAll command. After that you should load the store of the itemselector.
itemselector.store.removeAll();
itemselector.store.load();
Any solutions above solve my problem.
i found solution from Sencha Forum.
https://www.sencha.com/forum/showthread.php?142276-closed-Ext-JS-4.0.2a-itemselector-not-reloading-the-store
in the itemselector.js file, change the line marked below.
populateFromStore: function(store) {
var fromStore = this.fromField.store;
// Flag set when the fromStore has been loaded
this.fromStorePopulated = true;
// THIS LINE BELOW MUST BE CHANGED!!!!!!!!!!!!
fromStore.loadData(store.getRange()); //fromStore.add(store.getRange());
// setValue waits for the from Store to be loaded
fromStore.fireEvent('load', fromStore);
},
You need to insert...
this.reset();
at the head of the function that is inserting the data.
As an example...
Ext.override( Ext.ux.ItemSelector, {
setValue: function(val) {
this.reset();
if (!val) return;
val = val instanceof Array ? val : val.split(this.delimiter);
var rec, i, id;
for (i = 0; i < val.length; i++) {
var vf = this.fromMultiselect.valueField;
id = val[i];
idx = this.toMultiselect.view.store.findBy(function(record){
return record.data[vf] == id;
});
if (idx != -1) continue;
idx = this.fromMultiselect.view.store.findBy(function(record){
return record.data[vf] == id;
});
rec = this.fromMultiselect.view.store.getAt(idx);
if (rec) {
this.toMultiselect.view.store.add(rec);
this.fromMultiselect.view.store.remove(rec);
}
}
}
});
are u got it?
when u select that combobox frist stoe of item selector is null after store load with ur pass the para meters
for example
store.load(null),
store.proxey.url='jso.php?id='+combobox.getrawvalue(),
store.load();
like that so when ur select a value in ur combobox that time ur used a listeners to ur combobox in that listners ur used above code , select ur some value in combobox that time frist store is get null after ur pass some values to json.php then store load with responce so that time old data is remove and new data load in that store
if u post ur code i will give correct code
I ran into the same issue with ExtJS 4.2.1. I got it to work by calling reload() on the data store and then setValue() with an empty string on the item selector in the data store's reload() callback.
Ext.create("Ext.form.field.ComboBox", {
// Other properties removed for brevity
listeners: {
change: function(field, newValue, oldValue, eOpts) {
Ext.getStore("ExampleStore").reload({
callback: function() {
Ext.getCmp("ExampleItemSelector").setValue("");
}
});
}
}
});
Ext.create("Ext.data.Store", {
storeId: "ExampleStore",
// Other properties removed for brevity
});
Ext.create("Ext.form.FormPanel", {
// Other properties removed for brevity
items:[{
xtype: "itemselector",
id: "ExampleItemSelector",
// Other properties removed for brevity
}]
});
For any folks that are curious, I'm fairly convinced there's a bug in the item selector's populateFromStore() function. When the function is called, it blindly adds all of the values from the bound store (store) to the internal store (fromStore). I suspect there should be a call to fromStore.removeAll() prior to the call to fromStore.add(). Here's the relevant code from ItemSelector.js.
populateFromStore: function(store) {
var fromStore = this.fromField.store;
// Flag set when the fromStore has been loaded
this.fromStorePopulated = true;
fromStore.add(store.getRange());
// setValue waits for the from Store to be loaded
fromStore.fireEvent('load', fromStore);
},
EDIT 12/18/2013
If you've configured any callback events on the item selector (e.g. change), you may want to disable the events temporarily when you call setValue(""). For example:
var selector = Ext.getCmp("ExampleItemSelector");
selector.suspendEvents();
selector.setValue("");
selector.resumeEvents();
I had the same problem and finally I decided to modify the extjs source code, not considering it a big issue as extjs itself its saying in the start of the file
Note that this control will most likely remain as an example, and not as a core Ext form
control. However, the API will be changing in a future release and so should not yet be
treated as a final, stable API at this time.
Based on that, as jstricker guessed (and sadly I didn't read and took me a while to arrive to the same conclusion), adding fromStore.removeAll() before fromStore.add() solves the problem.
Outside of the problem (but I think it can be interesting as well), additionally, I also added listConfig: me.listConfig in the MultiSelect configuration (inside createList), that way it's possible to format each item additional options (such as images, etc.) setting in the 'itemselector' the option listConfig as it's explained in the (irrealistic) documentation.
Need to reset the store used in ItemSelector that can be done by setting Empty object like below. Also need to call clearValue() method of ItemSelector component.
store.setData({});
ItemSelectorComponent.clearValue();

Change styling of dojo treegrid cell if value changes

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.