change.delegate is very slow - aurelia

I have an object wich has two arrays of objects, like this:
Interests = {
MostInterests: [],
DistinctInterests: []
};
I also have an input that when is changed, uses a function to search elements in the Interests.DistinctInterests, but It looks like the change.delegate="function()" is taking a long time to trigger.
<input ref="textsearch" change.delegate="searchInterest($event.target.value)" type="text" />
searchInterest(value){
console.log('SEARCH');
this.searchedInterests = [];
var i = 0, j = 0;;
var upperValue = value.toUpperCase();
for(i = 0 ; i < this.Interests.DistinctInterests.length ; i++){
if(this.Interests.DistinctInterests[i].normalizedName.indexOf(upperValue) !=-1){
this.searchedInterests[j] = this.Interests.DistinctInterests[i];
j++;
}
}
console.log('END SEARCH');
}
The goal is update the view with the elements in this.searchedInterests, which contains items that match the searched text.
I don't know if It is an Aurelia problem or a javascript performance. I have aldo tried with $.each() function.
PS: the list contains 50 elements.

The change event is only fired when a change to the element's value is committed by the user.
Think of commited as CTRL+Z steps
This is the reason your function took more time to execute: it just wasn't called.
Instead, by using the input event, your function will get called every time the value changes.
<input ref="textsearch" input.delegate="searchInterest($event.target.value)" type="text" />

Related

Vue binding is sometimes putting "\" \"" around the strings, but v-html makes it always say Object object?

I am trying to make a quiz game and after getting the answers to shuffle around, they sometimes are surrounded by "\" \"" marks. When just displaying the correct answer, it will not have these marks. I have tried using v-html which normally gets rid of this issue, however then all the answers are [Object object]. I have tried trying to retrieve just part of the arrays and objects with [0] [2] and so one but that didn't work either.
Here is my html:
Style binded so that if that button is selected, the background colour of the button will change. Colour is a variable changed when answer is checked -->
<button class="answer" :disabled="answered" #click="checkAnswer(shuffled[0])" :style="selected == shuffled[0].value && {backgroundColor: colour}">{{shuffled[0]}}</button>
<button class="answer" :disabled="answered" #click="checkAnswer(shuffled[1])" :style="selected == shuffled[1].value && {backgroundColor: colour}">{{shuffled[1]}}</button>
<button class="answer" :disabled="answered" #click="checkAnswer(shuffled[2])" :style="selected == shuffled[2].value && {backgroundColor: colour}">{{shuffled[2]}}</button>
<button class="answer" :disabled="answered" #click="checkAnswer(shuffled[3])" :style="selected == shuffled[3].value && {backgroundColor: colour}">{{shuffled[3]}}</button>
<!-- Button running function to end game with parameters of current correctCount and totalQuestions -->
<button type="button" #click="finish(correctCount, totalQuestions)" class="btn button done">Finish</button> <div class="correct"><p v-if="answered"><strong>Correct Answer:</strong> {{correctAnswer}}</p></div><button type="button" :disabled="!nextAvailable" #click="getQ(selectedDifficulty)" #click="shuffle(options)" class="btn button next">Next</button>
<!-- Button which is disabled when a question loads and runs functions to shuffle the answers and get a new question -->
</div>
<h3>Debugging</h3>
<p><strong>Correct Answer: </strong>{{correctAnswer}}</p>
</div>
And here is my relevant JS:
let getQ = async function() {
let response = await fetchURL('https://the-trivia-api.com/api/questions?categories=film_and_tv&limit=1&region=AU'); // api source link
this.correctAnswer = response[0].correctAnswer;
this.incorrectAnswerOne = response[0].incorrectAnswers[0];
this.incorrectAnswerTwo = response[0].incorrectAnswers[1];
this.incorrectAnswerThree = response[0].incorrectAnswers[2];
this.question = response[0].question;
this.shuffle(options); // shuffles the answers in options variable
this.answered = false; // resets the disabled buttons
this.nextAvailable = false; // disables 'next' button
this.totalQuestions++; // increases number of total questions
}
// function to shuffle the answers
let shuffle = function(array) {
this.selected = null; // resets selected variable
let num = array.length, t, raInt;
// while there are remaining elements to shuffle
while (num) {
// choose random
raInt = Math.floor(Math.random() * num--);
// swap with current element
t = array[num]
array[num] = array[raInt];
array[raInt] = t;
}
this.shuffled = array.value; // saves to shuffled variable
}
All variables are declared and returned in the component. What I want is for it to always have every answer shuffled and without these markings.
There are some error messages but I am not quite sure what they mean:
If I haven't provided enough information or code, please feel free to say if you need more.

Vue 2.x non-clicked checkbox are checked

I am encountering this strange behavior in my code in Vue 2
What I am trying to achieve here is when I clicked on one of the checkbox, other checkbox(es) are checked as well. I only want the selected checkbox to be checked only. Is there a way to do so?
this is my component
<div v-for="(item,index) in api[0].choice" :key="index">
<input type="checkbox" #change="detect" v-model="option[index].checked">
<label>{{item}}</label>
</div>
data(){}
option: null,
mounted(){}
this.option = Array(this.api[0].choice.length).fill({checked: false})
Array.prototype.fill() passes a reference and not a new instance. So essentially all of your items inside of your array are mapped together. Thus when index n changes so do all the other values.
The easiest way to fix this, is to fill with undefined, by not passing an argument to fill, and then update the array, mapping the same object over and over again. Thus creating new instances of the object, instead of referencing the original object.
this.option = Array(5)
.fill()
.map((x) => ({ checked: false }));
Example on codesandbox
If you are expecting to have really large arrays .map() will be inefficient. Use a for loop instead
this.option = new Array(5);
for (let i = 0; i < 5; i++) {
this.option[i] = { checked: false };
}

Vue component method behaving weirdly - loader animation not working while data is processing

I'm trying to figure out why this happens - loader for long processing of data does not show.. only after the processing is done. Huge (few thousands items) object of key-value items and want them to make filterable - that works - but takes few seconds. I'm using VueJS 2.
I wanted to show "please wait" message while it runs, using the isworking value. I have a span with v-if="isworking", defined with value false as initial value.
On first line I set the this.isworking prop, but instead of seeing the "please wait", the function hangs for few seconds to do a search, and THEN sets the isworking prop to true - I tried that by commenting the last isworking=false - can't figure out why it waits to change it to true for the huge processing to end.
That window.deaccent method is fn to replace all accented characters in string with basic ansii chars, nothing special.
In template, I have a simple:
<form #submit="searchmath">
<span v-if="working">please wait</span>
<input v-model=...>
<div v-for="(item,index) in searchmatchitems"> ... </div>
</form>
Method in component:
searchmatch: function($event){
this.isworking = true;
this.$forceUpdate(); // tried also this, does not help
$event.stopPropagation();$event.preventDefault();
try{
var searchid = window.deaccent(this.search_string.toLowerCase());
var searchobj = this.cdata;
let result = Object.keys(this.cdata).filter(function(el,i,c){
var elk = window.deaccent(el).toLowerCase();
var elv = window.deaccent(searchobj[el]);
return elk.indexOf(searchid) > -1 || elv.indexOf(searchid) > -1;
}, searchid);
this.searchmatchitems = result;
this.isworking = false;
} catch(e){ console.log(e); this.isworking = false; return [];}
}
I also tried moving the event.preventDefault() to bottom, just to be sure it does not affect anything, but no luck.
That cdata is a simple key-value object with many props like this, counting about 4000 items
data: {
cdata: {
"lorem": "aa",
"ipsum": "bc",
"dolor": "de",
....
},
isworking: false,
....
}
2 issues here.
First, you use working in your template, and isworking in your data and code.
Second, your method does not call any async code, so it sets isworking to true, does work, and then sets isworking to false. You template will never have a chance to refresh in this case, and the UI will freeze until the method returns.
For instance, if you made an async call to a network endpoint, and then set isworking to false in the callback, you would get the results you are expecting.
If you have long running code and wish to prevent the UI from freezing, you will need to use a web worker thread

Vue.js counter variable causing infinite loop

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

VuesJS, generate randomkey in v-for loop

Good evening,
My problem is this: I have a loop that displays simple divs.
I have a method that specifies the dimensiosn of my div (mandatory in my case). However, when I call the method a second time by changing the sizes of the divs, it does not work because there is no re-render.
To overcome this, I generate a guid on my: key of v-for with a variable such as:
<div v-for="task in tasks" :key="task.id + guid()">...blabla...</div>
Is it possible to generate this code directly during the loop to avoid concatenation?
<div v-for="(task, maVar=guid()) in tasks" :key="maVar">...blabla...</div>
PS : code for guid() method :
guid() {
return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16))
}
Thanks
You could create a computed property that returns an array of task with a guid added, or if you want to leave tasks untouched, return an object containing each task plus a guid,
computed: {
tasksWithGuid: function() {
return this.tasks.map(task => { return {task, key: task.id + guid() } })
}
}
<div v-for="taskWithGuid in tasksWithGuid" :key="taskWithGuid.key">
{{taskWithGuid.task.someProperty}}
</div>
There is a simpler, more concise technique shown below. It avoids polluting the iterated object with a redundant property. It can be used when there is no unique property in the objects you iterate over.
First in your viewmodel add the method to generate a random number (e.g. with Lodash random)
var random = require('lodash.random');
methods: {
random() {
return random(1000);
}
}
Then in your template reveal the index in v-for and randomize it in v-bind:key with your random() method from the viewmodel by concatenation.
<div v-for="(task, index) in tasks" v-bind:key="index + random()">
// Some markup
</div>
This is as clean as easy.
However note this approach would force redrawing each item in the list instead of replacing only items that differ. This will reset previously drawn state (if any) for unchanged items.
I do like this
function randomKey() {
return (new Date()).getTime() + Math.floor(Math.random() * 10000).toString()
}