Avoid repetition code in Svelte for bind values - dry

following this example:
https://svelte.dev/tutorial/select-bindings
I wanted to enhance this example by saving the state of the answer, so I modified the code slightly to achieve this.
But I am worried this will result in a lot of repeated lines in different parts of code, checking if the selected value is defined after render.
I would like to move foundIndex function outside of handleSubmit and handleChange, but it throws an error, because the selected.id is not defined at the initial 'state'.
See below:
<script>
let questions = [
{ id: 1, text: `Where did you go to school?`, answer: "" },
{ id: 2, text: `What is your mother's name?`, answer: "" },
{ id: 3, text: `What is another personal fact that an attacker could easily find with Google?`, answer : "" }
];
let selected;
let answer = '';
function handleSubmit() {
const foundIndex = questions.findIndex(question => question.id === selected.id);
questions[foundIndex].answer = answer;
};
function handleChange() {
const foundIndex = questions.findIndex(question => question.id === selected.id);
answer = questions[foundIndex].answer || "";
};
</script>
<style>
input { display: block; width: 500px; max-width: 100%; }
</style>
<h2>Insecurity questions</h2>
<form on:submit|preventDefault>
<select bind:value={selected} on:change={handleChange}>
{#each questions as question}
<option value={question}>
{question.text}
</option>
{/each}
</select>
<input bind:value={answer}>
<button on:click={handleSubmit}>
Submit
</button>
</form>
<p>selected question {selected ? selected.id : '[waiting...]'}</p>
Link to REPL:
https://svelte.dev/repl/5a8cd70dc6664e4b983e1bc729ac953b?version=3.12.0

Move the function, but pass in the questions and selected.id as a parameter.
function findIndex(questions, selectedId) {
return questions.findIndex(question => question.id === selectedId);
}
Then call it from your handlers.

Related

Vue.js : Range slider with two handles

I want to create a vue js components where it contains a range slider of hours with two handles.
I use vue3 + vite.js
I tried this code to implement the components but when I drag one of handles I have an error
Code :
this is the template :
<template>
<div>
<input type="range" ref="rangeInput" v-model="rangeValue" #input="updateRange"/>
<div class="range-slider">
<div class="handle" :style="{left: leftHandle + '%'}" #mousedown="startHandleDrag(1)">
{{ formatHour(rangeValue[0]) }}
</div>
<div class="handle" :style="{left: rightHandle + '%'}" #mousedown="startHandleDrag(2)">
{{ formatHour(rangeValue[1]) }}
</div>
</div>
</div>
</template>
and this is the script :
<script>
export default {
data() {
return {
rangeValue: [8, 18],
handleDragging: 0
};
},
computed: {
leftHandle() {
return this.rangeValue[0];
},
rightHandle() {
return this.rangeValue[1];
}
},
methods: {
updateRange(event) {
const value = event.target.value;
const range = this.rangeValue;
if (this.handleDragging === 1) {
range[0] = value[0];
} else if (this.handleDragging === 2) {
range[1] = value[1];
} else {
range[0] = value[0];
range[1] = value[1];
}
this.rangeValue = range;
},
startHandleDrag(handle) {
this.handleDragging = handle;
document.addEventListener("mouseup", this.stopHandleDrag);
document.addEventListener("mousemove", this.updateRange);
},
stopHandleDrag() {
this.handleDragging = 0;
document.removeEventListener("mouseup", this.stopHandleDrag);
document.removeEventListener("mousemove", this.updateRange);
},
formatHour(value) {
return value + ":00";
}
}
};
</script>
Error :
any ideas to solve it !!!
In your startHandleDrag() and stopHandleDrag(), you bind updateRange() to the mousemove event:
document.addEventListener("mousemove", this.updateRange);
There are two issues with that:
The target of the mousemove event is the element under the cursor. This can be any element, and unless it happens to be an input, it will not have a value attribute (and if it does, it will not hold an array). If you really want to use the "mousemove" event, use the cursor coordinates like pageX or pageX.
You bind it as a function pointer (addEventListener("mousemove", this.updateRange)), and when called from the listener, this will refer to element.target. To avoid this, either use an arrow function (addEventListener("mousemove", (e) => this.updateRange(e))) or bind this (addEventListener("mousemove", this.updateRange.bind(this))).
I don't fully understand what you want to do with the handles, but my guess is that adding and removing listeners is a workaround, and you actually want to make them draggable? If so, have a look at the drag event. Hope that helps!

Vuejs/Nuxtjs : How to create dynamic V-model names without using the v-for?

I am encountering a tricky issue in my Vuejs/Nuxtjs application. In the application, I am creating multiple Nodes dynamically. These Nodes have the Radio button for which I have assigned a v-model. However, when I change the value of one Vuejs v-model is affecting all other Node Values.
I am aware that this issue is happening because of the same v-model being used for all Nodes. I would like to assign a different V-model to my Radio button but I want to do it without using the v-for.
I have created the sample code in the CodeSandbox
Steps to reproduce:
Drag and drop the Identifiers into the canvas. Now the URN will be selected.
Now Drag and drop another Identifiers into the canvas. Now the first Identifiers Node: URN will disappear. I am unable to handle each Node value independently.
The problem is arising in the file #components/IdentifiersNode.vue and in the radio button.
Code sample based on the Kissu response :
<input
id="identifierTypeURN"
:data="identifierSyntax"
value="URN"
type="radio"
name="instanceIdentifierURN"
#input="instanceIdentifiersSyntaxChange('URN')"
>
<label for="identifierTypeURN">URN</label>
<input
id="identifierTypeWebURI"
:data="identifierSyntax"
value="WebURI"
type="radio"
name="instanceIdentifierWebURI"
#input="instanceIdentifiersSyntaxChange('WebURI')"
>
<label for="identifierTypeWebURI">WebURI</label>
Can someone please check and let me know what am I doing wrong here: https://codesandbox.io/s/cocky-matan-kvqnu?file=/nuxt.config.js
After some effort able to get it working. I was using the Radio button functionalities wrongly. I changed it to something like this and it worked fine:
<template>
<div ref="el">
<div class="header">Identifiers Node: {{ ID }}</div>
<div id="app" class="nodeContainer">
{{ "Value : " + identifierSyntax }}
Syntax:
<input
:id="`identifierTypeURN-${ID}`"
:data="identifierSyntax"
value="URN"
type="radio"
:name="`instanceIdentifier-${ID}`"
:checked="identifierSyntax === 'URN'"
#input="instanceIdentifiersSyntaxChange($event, 'URN')"
/>
<label :for="`identifierTypeURN-${ID}`">URN</label>
<input
:id="`identifierTypeWebURI-${ID}`"
:data="identifierSyntax"
value="WebURI"
type="radio"
:name="`instanceIdentifier-${ID}`"
:checked="identifierSyntax === 'WebURI'"
#input="instanceIdentifiersSyntaxChange($event, 'WebURI')"
/>
<label :for="`identifierTypeWebURI-${ID}`">WebURI</label>
</div>
</div>
</template>
<script>
export default {
data() {
return {
ID: "",
nodeId: "",
bizStep: "",
allNodeInfo: [],
identifierSyntax: "URN",
};
},
mounted() {
console.log("MOUNTED");
this.$nextTick(() => {
const id = this.$el.parentElement.parentElement.id;
const data = this.$df.getNodeFromId(id.slice(5));
this.ID = data.data.ID;
this.nodeId = data.data.nodeId;
this.allNodeInfo = JSON.parse(
JSON.stringify(
this.$store.state.modules.ConfigureIdentifiersInfoStore
.identifiersArray,
null,
4
)
);
this.identifierSyntax = this.allNodeInfo.find(
(node) => node.identifiersId === this.nodeId
).identifierSyntax;
});
},
methods: {
// On change of the IdentifierSyntax change, change the value in the respective node info
instanceIdentifiersSyntaxChange(event, syntaxValue) {
console.log("CHANGED : " + syntaxValue);
console.log(event.target.defaultValue);
this.identifierSyntax = syntaxValue;
// Change the value of the respective syntax within the Node information in IdentifiersNode array
this.$store.commit(
"modules/ConfigureIdentifiersInfoStore/identifiersSyntaxChange",
{ nodeId: this.ID, syntaxValue }
);
},
},
};
</script>
<style>
.header {
background: #494949;
margin-top: -15px;
margin-left: -15px;
margin-right: -15px;
padding: 10px 15px;
margin-bottom: 15px;
}
</style>

How to add to array from method in vue.js?

I'm having a weird proplem that I can't understand
I have a registration form, and on any input I'm executing the method on blur;
<input class='form-control' placeholder='Username' #blur="watchVal" v-model="username">
Method
watchVal : function(){
if(this.username == ""){
this.errors['username'].push('Username is empty');
}
}
Data:
data: function(){
return {
username: "",
errors: {
'username' : []
}
}
}
When I blur without writing any value, nothing is added to this.errors['username'], unless I type a letter in any field.
I've also tried to make validation on submit, but found same problem that no error is added to the array unless I type in any input,
Can anyone explain to me what I am doing wrong??
I faced similar issue. How I solved this.
your data:
data: function(){
return {
username: "",
errors: {}
}
}
your Method
watchVal (key) {
let errors = this.errors
if (this[key] === '') {
errors[key].push('Emai empty')
}
this.errors = errors
}
your HTML
<input class='form-control' placeholder='Username' #blur="watchVal('username')" v-model="username">
<p>{{errors['username']}}</p>
You must display error variable in your HTML template then it will be solved.
Your source is working. See in full mode if you cannot see error messages.
new Vue({
el: "#app",
data: function() {
return {
username: '',
errors: {
username: []
}
}
},
methods: {
watchVal : function(){
if(this.username == ""){
this.errors['username'].push('Username is empty');
}
}
}
})
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.4.1/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<input class='form-control' placeholder='Username' #blur="watchVal" v-model="username">
<p class="text-sm text-danger" v-for="(error, index) in errors.username" :key="index">
{{ error }}
</p>
</div>

What is the opposite of $set in Vue.js?

In a blog app, I'd like to show/hide comments of each post inside a loop of posts. I know how to show the div containing the comments by setting a showComments on the fly:
this.$set(post, 'showComments', true) ;
But I don't know how to hide the post's comments when the div is already open. What I tried is this:
if (this.$get(post, 'showComments')==true) {
this.$set(post, 'showComments', false) ;
return
}
The code above thoes not work and I get this error:
Uncaught TypeError: this.$get is not a function
So I'm wondering how can I acheive this functionaliry.
You should be able to simply read the dynamic property and reapply the value.
new Vue({
el: '#app',
data() {
return {
posts: [
{ content: 'Post #1' },
{ content: 'Post #2' },
{ content: 'Post #3' }
]
}
},
methods: {
toggleComment(post) {
if ('showComment' in post) {
post.showComment = !post.showComment;
}
else {
this.$set(post, 'showComment', true);
}
}
}
})
.post {
background-color: lightgreen;
margin: 10px 0;
padding: 5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div class="post" v-for="(post, index) in posts" :key="index">
{{post.content}}
<p v-if="post.showComment">
Hidden comments.
</p>
<button #click="toggleComment(post)">Toggle comment</button>
</div>
</div>
Use property name to get property value
if ( typeof this.post.showComments !== 'undefined' && this.post.showComments ) {
Vue.set(post, 'showComments', false);
return;
}
Also note that you should try to avoid using this.$set because it was deprecated due to conflicts with other libraries. Consider using Vue.set instead.
https://v2.vuejs.org/v2/api/#Vue-set

how to do filters in vue.js

i am new to vue.js.so i want to apply a filter thing in my project.i tried to do this from past 2 to 3 days..but i couldn't..can any one help me how to do this..
I have 3 input boxes one is experience,expected_ctc,and profile_role depending on these 3 i want to display the results..
Here is my js page:
const app = new Vue({
el: '#app',
data() {
return {
page: 0,
itemsPerPage: 3,
}
},
components: { Candidate },
methods: {
//custom method bound to page buttons
setPage(page) {
this.page = page-1;
this.paginedCandidates = this.paginate()
},
paginate() {
return this.filteredCandidates.slice(this.page * this.itemsPerPage, this.page * this.itemsPerPage + this.itemsPerPage)
},
},
computed: {
//compute number of pages, we always round up (ceil)
numPages() {
console.log(this.filteredCandidates)
return Math.ceil(this.filteredCandidates.length/this.itemsPerPage);
},
filteredCandidates() {
//filter out all candidates that have experience less than 10
const filtered = window.data.candidates.filter((candidate) => {
if(candidate.experience === 5) {
return false;
}
return true;
})
console.log(filtered);
return filtered;
},
paginedCandidates() {
return this.paginate()
}
}
});
here is my buttons from view page:
<div class="container">
<b>Experience:</b><input type="text" v-model="experience" placeholder="enter experience">
<b>Expected CTC:</b><input type="text" v-model="expected_ctc" placeholder="enter expected ctc">
<b>Profile Role:</b><input type="text" v-model="profile_role_id" placeholder="enter role">
<input type="button" name="search" value="search" >
</div>
Can anyone help me..
Thanks in advance..
Ok let's start with a smaller example. Lets say you have "Candidates" one one candidate might look like
{
name: 'John Doe',
expected_ctc: 'A',
experience: 'B',
profile_role_id: 1
}
From your current state I'd say you have all candidates in an array returned by laravel. Let's say we're in your current template where you have your vue app
<div id="app">
<!-- lets start with one filter (to keept it clean) -->
<input type="text" v-model="experienceSearch"/>
<ul class="result-list">
<li v-for="result in candidatelist">{{ result.name }}</li>
</ul>
</div>
And now to the script
// always init your v-model bound props in data
// usually you wouldn't take the candidates from the window prop. However, to keep it easy for you here I will stick to this
data: function() {
return {
experienceSearch: '',
candidates: window.data.candidates
}
},
// the list that is displayed can be defined as computed property. It will re-compute everytime your input changes
computed: {
candidatelist: function() {
// now define how your list is filtered
return this.candidates.filter(function(oCandidate) {
var matches = true;
if (this.experienceSearch) {
if (oCandidate.experience != this.experienceSearch) {
matches = false;
}
}
// here you can define conditions for your other matchers
return matches;
}.bind(this));
}
}
General steps:
all candidates are in data -> candidates
relevant (filtered) candidates are represented by the computed prop candidatelist
inputs are bound with v-model to EXISTING data prop definitions
Fiddle https://jsfiddle.net/sLLk4u2a/
(Search is only exact and Case Sensitive)