VueJs - Updating class with a setInterval function not working [duplicate] - html

I'm new to Vuejs. Made something, but I don't know it's the simple / right way.
what I want
I want some dates in an array and update them on a event. First I tried Vue.set, but it dind't work out. Now after changing my array item:
this.items[index] = val;
this.items.push();
I push() nothing to the array and it will update.. But sometimes the last item will be hidden, somehow... I think this solution is a bit hacky, how can I make it stable?
Simple code is here:
new Vue({
el: '#app',
data: {
f: 'DD-MM-YYYY',
items: [
"10-03-2017",
"12-03-2017"
]
},
methods: {
cha: function(index, item, what, count) {
console.log(item + " index > " + index);
val = moment(this.items[index], this.f).add(count, what).format(this.f);
this.items[index] = val;
this.items.push();
console.log("arr length: " + this.items.length);
}
}
})
ul {
list-style-type: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.11/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.6/moment.min.js"></script>
<div id="app">
<ul>
<li v-for="(index, item) in items">
<br><br>
<button v-on:click="cha(index, item, 'day', -1)">
- day</button>
{{ item }}
<button v-on:click="cha(index, item, 'day', 1)">
+ day</button>
<br><br>
</li>
</ul>
</div>

EDIT 2
For all object changes that need reactivity use Vue.set(object, prop, value)
For array mutations, you can look at the currently supported list here
EDIT 1
For vuex you will want to do Vue.set(state.object, key, value)
Original
So just for others who come to this question. It appears at some point in Vue 2.* they removed this.items.$set(index, val) in favor of this.$set(this.items, index, val).
Splice is still available and here is a link to array mutation methods available in vue link.

VueJS can't pickup your changes to the state if you manipulate arrays like this.
As explained in Common Beginner Gotchas, you should use array methods like push, splice or whatever and never modify the indexes like this a[2] = 2 nor the .length property of an array.
new Vue({
el: '#app',
data: {
f: 'DD-MM-YYYY',
items: [
"10-03-2017",
"12-03-2017"
]
},
methods: {
cha: function(index, item, what, count) {
console.log(item + " index > " + index);
val = moment(this.items[index], this.f).add(count, what).format(this.f);
this.items.$set(index, val)
console.log("arr length: " + this.items.length);
}
}
})
ul {
list-style-type: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.11/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.6/moment.min.js"></script>
<div id="app">
<ul>
<li v-for="(index, item) in items">
<br><br>
<button v-on:click="cha(index, item, 'day', -1)">
- day</button> {{ item }}
<button v-on:click="cha(index, item, 'day', 1)">
+ day</button>
<br><br>
</li>
</ul>
</div>

As stated before - VueJS simply can't track those operations(array elements assignment).
All operations that are tracked by VueJS with array are here.
But I'll copy them once again:
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
During development, you face a problem - how to live with that :).
push(), pop(), shift(), unshift(), sort() and reverse() are pretty plain and help you in some cases but the main focus lies within the splice(), which allows you effectively modify the array that would be tracked by VueJs.
So I can share some of the approaches, that are used the most working with arrays.
You need to replace Item in Array:
// note - findIndex might be replaced with some(), filter(), forEach()
// or any other function/approach if you need
// additional browser support, or you might use a polyfill
const index = this.values.findIndex(item => {
return (replacementItem.id === item.id)
})
this.values.splice(index, 1, replacementItem)
Note: if you just need to modify an item field - you can do it just by:
this.values[index].itemField = newItemFieldValue
And this would be tracked by VueJS as the item(Object) fields would be tracked.
You need to empty the array:
this.values.splice(0, this.values.length)
Actually you can do much more with this function splice() - w3schools link
You can add multiple records, delete multiple records, etc.
Vue.set() and Vue.delete()
Vue.set() and Vue.delete() might be used for adding field to your UI version of data. For example, you need some additional calculated data or flags within your objects. You can do this for your objects, or list of objects(in the loop):
Vue.set(plan, 'editEnabled', true) //(or this.$set)
And send edited data back to the back-end in the same format doing this before the Axios call:
Vue.delete(plan, 'editEnabled') //(or this.$delete)

One alternative - and more lightweight approach to your problem - might be, just editing the array temporarily and then assigning the whole array back to your variable. Because as Vue does not watch individual items it will watch the whole variable being updated.
So you this should work as well:
var tempArray[];
tempArray = this.items;
tempArray[targetPosition] = value;
this.items = tempArray;
This then should also update your DOM.

Observe object and array reactivity here:
https://v2.vuejs.org/v2/guide/reactivity.html

Related

Svelte virtual list component - function not working after filtering list

I am using the virtuallist component in a svelte project. I have added filtering to the list. My issue is that a function in my project stops working when I filter the list, I'm assuming because the list item is not yet in the dom when filtered?
The project converts medical units from metric units to international units using two inputs. Changing one input automatically converts the other.
Before filtering, everything works well with conversion but after entering a item name, (e.g. Type Zinc), the input conversion fails in the filtered items. No conversion occurs.
I've looked into afterUpdate as an option but not sure how to implement it.
---------Added Info -------------------
The issue is with list items not yet in view. Try typing "zinc" and then changing the input values of Zinc (fails) vs typing Acetone (item already in view) and changing those inputs (it works).
Here is a working REPL
The script:
<script>
import VirtualList from './VirtualList.svelte';
import unitsH from './data.js';
let searchTerm = "";
let start;
let end;
$: filteredList = unitsH.filter(item => item.name.toLowerCase().indexOf(searchTerm) !== -1);
function setBothFromSIH(value, i) {
const {factor, siValue} = unitsH[i];
unitsH[i].siValue = +value;
unitsH[i].usValue = +(value / factor).toFixed(2);
}
function setBothFromUSH(value, i) {
const {factor, usValue} = unitsH[i];
unitsH[i].usValue = +value;
unitsH[i].siValue = +(value * factor).toFixed(2);
}
</script>
With simplified html code:
<VirtualList items={filteredList} bind:start bind:end let:item >
<div class="border" style="overflow-x: scroll;"> <div><div>
<div class="name">{item.name}</div>
<span>Specimen: {item.specimen} </span>
<span> Conversion Factor: {item.factor} </span>
</div>
<div>
<label>US Range:{item.conventionalRange} {item.conventionalUnit}</label>
<input name="us{filteredList.indexOf(item)}" value={item.usValue} on:input="{e => setBothFromUSH(e.target.value, filteredList.indexOf(item))}" type=number placeholder=" US">
</div>
<div>
<label>SI Range: {item.siRange} {item.siUnit}</label>
<input name="si{filteredList.indexOf(item)}" value={item.siValue} on:input="{e => setBothFromSIH(e.target.value, filteredList.indexOf(item))}" type=number placeholder="SI">
</div></div> </div>
</VirtualList>
<p>showing items {start}-{end}</p>
Thanks for any help in getting this to work!
It's a small issue with your filter. You convert the product name to lower case but not the filter term ;) If you enter acetone instead of Acetone, then it works. The fix:
$: filteredList = unitsH.filter(item => item.name.toLowerCase().indexOf(searchTerm.toLowerCase()) !== -1);
Edit:
The issue with not calling the function for some filtered element is that you display the filteredList but still do the lookup on the unitsH list. Change it to this and it works:
function setBothFromSIH(value, i) {
const {factor, siValue} = filteredList[i];
filteredList[i].siValue = +value;
filteredList[i].usValue = +(value / factor).toFixed(2);
}
function setBothFromUSH(value, i) {
const {factor, usValue} = filteredList[i];
filteredList[i].usValue = +value;
filteredList[i].siValue = +(value * factor).toFixed(2);
}
Happy hacking!
Your problem is caused by using the wrong index, in the change handler you pass the index of the item in filteredIndex but then you use that one to change the item on that index in the array unitsH.
You can see that by:
- start anew
- note the value for Acetaminophen (index 0)
- search zinc
- change value of zinc (index 0 in filtered list)
- clear search
->> acetaminophen has changed because that is index 0 in unitsH
You can easily solve this by passing in the index of the original array instead:
<input name="si{filteredList.indexOf(item)}" value={item.siValue} on:input="{e => setBothFromSIH(e.target.value, unitsH.indexOf(item))}" type=number placeholder="SI">
However, if you move the markup for each item to a seperate component you can vastly simplify this by directly interacting with the properties instead of trying to change them in the array.

React: How to provide procedurally generated <li> elements distinct HTML id values?

I'm rendering a map of items retrieved from a database and filtered via the value state of an input field and attempting to then set the state of the input field as the value stored in some list item on click. I figured that using document.getElementById().innerHTML would allow me to retrieve the content stored within the appropriate tag and then set it to state which does work, the issue I'm facing is that it will only retrieve the innerHTML of the first item rendered in the map.
I've tried solutions ranging from applying UUID to making the mapped content available to the window and transfering the state of the individual objects but each disparate solution only moves the value of the first item to state - any ideas?
Rendered Content:
window.filteredItems = this.state.items.filter(
(item) => {
return item.companyNameObj.toLowerCase().indexOf(this.state.search.toLowerCase()) !== -1;
}
);
<div className="fixed-width">
<div className="search-container">
<form>
<input type="text" name="search" className="search-bar" placeholder="Search: " onChange={this.handleChange} value={this.state.search} />
</form>
<ul className="search-results">
{window.filteredItems.map((item) => {
return (
<div className="distinct-result-container">
<li key={item.id}>
<div className="image-container">
<img src={item.imageObj} alt={item.companyNameObj + " logo."}/>
</div>
<div className="company-container">
<span onClick={this.stateTransfer}><h3 id={"ID"}>{item.companyNameObj}</h3></span>
<p>Owned by: {item.ownerNameObj}</p>
</div>
</li>
</div>
)
})}
</ul>
</div>
<Footer />
</div>
);
stateTransfer()
stateTransfer(id) {
var search = this.state.search;
var uniqueID = document.getElementById("ID").innerHTML;
this.setState({
search: uniqueID
});
}
The current content of stateTransfer() doesn't represent any significant attempts at approaching a solution to this issue, it's just the minimum required implementation to move the innerHTML content to the input fields value.
EDIT: I've further clarified on the task at hand and a potential solution in the comments below (which follow this), I'm just hoping someone is able to help me with the actual implementation.
#DILEEPTHOMAS The list is comprised of data pulled from a Firebase Realtime Database and is rendered via mapping the filteredList and a search query; that functoionality works fine - what I need is to be able to click the element of any distinct li and have the innerHTML (the text stored in that li's item.companyNameObj) be moved to the value of the input field (so users can navigate the search content with re-typing).
#JoshuaLink I can't necessarily configure the items of the list any
further as it's just data pulled from an external database - I believe
the appropriate solution is to somehow provide a unique HTML ID value
to each newly rendered li and have that selected ID moved to
stateTransfer() where it can be set as the input fields value, I'm
just struggling with the actual implementation of this.
EDIT 2: I've managed to figure out a solution to both parts of the problem as described above - I'll post it as an answer below.
I managed to solve both parts of my problem:
The key issue, which was moving the text stored in each distinct li to the input value, which was apparently easily solved by making my stateTransfer() function accept an event and passing the .innerText value of the h3 through the event (I assumed I would have to use .innerHTML, which would require me to provide each distinct li with a unique generated ID) as follows:
stateTransfer(e) {
var search = this.state.search;
var innerText = e.target.innerText
this.setState({
search: innerText
})
}
The secondary issue, (which I incorrectly assumed was integral to implementing a solution to my question), assigning unique HTML id values to my procedurally generated li's was solved by implementing a for-loop in a componentDidUpdate() function which iterates through the current total length of the list and and assigns an id with the loop iterator concatenated to the end of the string as follows:
componentDidUpdate() {
var i;
var searchCompanyNames = document.querySelectorAll('.comapnyNames');
for(i = 0; i < searchCompanyNames.length; i++) {
searchCompanyNames[i].id = 'companyName-' + i;
}
}
Whilst I didn't need to assign unique ID's to the li's in the correct implementation, it's a useful trick worth noting nonetheless.

Display array from json data to cards

So, im a little bit lost here and i need some help.
I have a json that come from the server with data that i dont know.
Based on that i found a solution to display the data on html here on SO:
https://stackoverflow.com/a/50352965/9721446
But the problem is that each "item" is an entry from array, so if i ngfor array, it outputs each line as an item, and i want the item to be all entries of each result.
heres the html:
<ng-container *ngFor="let item of singleArray | paginate: { itemsPerPage:411, currentPage: p} ">
<!-- All the entries -->
<div class="w3-container">
<!-- Table view-->
<table class="center">
<tr *ngIf="!item.tag.includes('URL') && !item.tag.includes('linkChal')">
<td><div class="col-xs-auto thick">{{item.tag.toLowerCase() | translate}}</div></td>
<td class="tab">{{item.value}}</td>
</tr>
<tr *ngIf="item.tag.includes('URL')">
<td>Link da entrada: </td>
<td> - Ver mais -</td>
</tr>
<tr *ngIf="item.tag.includes('linkChal')">
<td>Link do Challenge: </td>
<td> - Challenge -</td>
</tr>
</table>
<div style="background-color: #ff7d2a">
<ul *ngIf=" item.tag.includes('---------')"><p>New Entry</p></ul>
</div>
</div>
</ng-container>
Ts:
for(let i in res)
{
//array with entities from json
this.entity.push(i);
for(let j in res[i])
{
let val = Number(j)+1;
this.cont.push(i +" - nÂș: " + val );
this.singleArray.push({
tag: i,
value: val
});
for(let t in res[i][j])
{
this.test.push(t);
this.cont.push(t +" - "+ this.responseList[i][j][t]) ;
if(t.split(".",2)[1] === "CompositeId")
{
this.test.push("URL:");
//Get the id
this.cont.push(this.moduleName + "/" + t.split(".",2)[0] + "/" + this.responseList[i][j][t].match(/=(.*)_/)[1]);
//debugger;
this.singleArray.push({
tag: "URL:",
value: this.moduleName + "/" + t.split(".",2)[0] + "/" + this.responseList[i][j][t].match(/=(.*)_/)[1]
});
}
else if(t.split(".",2)[1] === "Challenge")
{
this.singleArray.push({
tag: "linkChal",
value: this.moduleName + "/" +t.split(".",2)[1] + "/" + this.responseList[i][j][t].match(/=(.*)_/)[1]
});
}
else {
this.singleArray.push({
tag: t,
value: this.responseList[i][j][t]
});
}
}
this.test.push("\n");
this.cont.push("\n");
this.singleArray.push({
tag: "---------\n",
value: "--------\n"
});
//it ends an item here
}
}
Heres the output i have with that:
Each one line is an entry from the array, the big question is, how to transform all lines/entries until "New Entry" and made an single item to ngfor and display data into a card that i already have..)
I've tried to create an array and push the singleArray into it (hoping each entry of that new array was an item that i want), at the end of for(let j in res[i]) on .ts but it just repeated all the entries creating a bunch of entries..
here, at the end of that for, i've tried to push an array with something, then ngfor it (it gives me the number items that i want, but then i dont have the results to access them..)
Has anyone had this problem before?
thanks in advance
Edit: here's what singleArray looks like:
Your best bet here is to follow the single responsibility principal and separate the concerns of each class.
Stop trying to do this all in the view and separate out the responsibility of formatting the data and the problem will seem much simpler.
Make a new class to define the model you want your view to use
Have your view implement this new ideal model that you control
Generate some test data to make get this looking like what you want
Create a new class who's entire responsibility is to turn the external model from the api response into this new internal model
json2ts may help generate a better external model from the response, but it may not be of much use in this case
Once you have done the above, based on your sample output, it should be fairly simple to convert from the external model into the internal model. It's hard to convey this, but assuming the hyphens are the item separator you could simply do something like the following:
const externalItems = // data from api
const internalItems = [];
let currentInternalItem = {};
externalItems.forEach(item => {
if (item.tag.startsWith('---------')) {
internalItems.push(currentInternalItem);
currentInternalItem = {};
} else {
currentInternalItem[item.tag] = item.value;
}
});
This would group the array back into an object that you can use in your view.
I think I'm complicating too much.. The objective here is to display what comes from JSON into specific locations, like a card, with header and content, to better display the results.
I have a service that gives me a JSON, that i never knows what inside, that depends on the search term and can bring much information. For example:
If the term is "Idea":
If the term is Challenge:
My .ts file is only console.log what comes from the api.
ngOnInit() {
var setting = {setting to connect the server..}
enter code here
$.ajax(settings).done((rest) => this.response(rest));
}
response(res){
console.log(res);
}
How can i display that data the way i want?
Sorry for the long post, and for not beeing objective on the main question.

How can I link paths so results are updated in dom-repeat

I am developing an appointment booking system. It basically consists of a set of Polymer custom elements arranged as follows (indented elements are in the template of the element rather than actually organised as shown)
<my-appointments>
<person-appointment booking="{{booking}}>
<booking-type type="{{booking.type}}">
<div>[[booking.type]]</div>
</booking-type>
</person-appointment>
<appointment-day booking="{{booking}}>
<template is="dom-repeat" items="{{appointments}} as="{{appointment}}">
<div>[[appointment.type]]</div>
</template>
</appointment-day>
</appointment>
inside the <appointment-day> element booking is defined as an "Object" property and appointments as an "Array". As a booking is made, the booking object is spliced into the appointments array at the correct place.
At the same time I use the linkPaths function to join path 'booking' to path 'appointments.n' (where n is 0, 1, 2 etc for where in the appointments array booking is situated)
This is the code that does this
if (foundAppointment) {
//found where to insert appointment, so do so
this.splice(path, j, 0, this.booking);
this.linkPaths('booking', path + '.' + j);
this.linkedBooking = true;
break;
}
Not shown is a mechanism inside <booking-type> to update the type property. SO when I update the type property using this mechanism, the visual representation changes inside the <person-appointment> element but it does not change inside the dom-repeat. I can check that the object located at appointents[n] IS updated, but the display is not updated.
I presume I haven't properly linked booking to the appropriate appointment entry. BUT how should I achieve this
Polymer does not observe the change of sub-properties of appointments.
Try to Force data system to pick up array the mutations with the code below:
// Force data system to pick up the **array** mutations
var array = this.appointments;
this.appointments= [];
this.appointments= array;
Try to add this to your code.
if (foundAppointment) {
//found where to insert appointment, so do so
this.splice(path, j, 0, this.booking);
this.linkPaths('booking', path + '.' + j);
this.linkedBooking = true;
// Force data system to pick up array mutations
var array = this.appointments;
this.appointments= [];
this.appointments= array;
break;
}
Or to Force data system to pick up the Object mutations with the code below:
// Force data system to pick up array mutations
var object = this.appointment;
this.appointment= [];
this.appointment= object;
I'm only guessing that the problem is in binding to array items.
Polymer has special rules for that.
Here is one example of array binding: Plunk
<template is="dom-repeat" items="[[first4People(people, people.*)]]">
<div>
Index: <span>[[index]]</span>
<br>
First as this:[[arrayItem(people.*, index, 'first')]]
<br>
First not as this: <span>[[item.first]]</span>
</div>
<br><br>
</template>
Simplified online example of the problem would help to understand the issue better.

Dynamic search/filter core-list (Polymer 0.5)

I need to implement a filter-type search which hides items in core list if they do not match the search. I created a .hidden class that is applied to an item if an expression returns false:
class = {{ {hidden: !match(model.host, hQuery)} | tokenList }}
The elements are hidden, but the list does not reflow elements unless I click on a visible row. Is there a way to force a reflow using a function call?
After a week of struggling, hiding list items is just not the right way to handle this. Iterate through the original array, push any matching objects to a temporary array, and then replace core-list's .data array with the temporary array: this.$.list_id.data = tmpArray. Performance is good for lists up to 10K records.
This is what I'm doing in my code, and it works:
<div style="{{hide_part1}}">
...content to show/hide...
</div>
....
Switching it based on route change(flatron-director):
routeChanged: function(oldValue, newValue) {
if ('some_route_1' == this.route) {
this.hide_part1 = ''
this.hide_part2 = 'display: none;'
} else if ('some_route_2' == this.route) {
this.hide_part1 = 'display: none;'
this.hide_part2 = ''
}
},
Also using core-list's updateSize() and especially scrollToItem(0), i.e. back to top, here and there helps as I also had problems with the 'reflow':
https://stackoverflow.com/questions/29432700/polymer-core-list-is-not-rendering-some-of-the-elements