How to dynamically set an Image object src in javascript and Vue - javascript

I'm trying to teach myself javascript and Vue.js. I was following the documentation on Vue's site and modifying their demonstrations as an exercise. I wanted to change their looping directive example to dynamically add images to the list from a specified url. I can't seem to get the image to show despite setting the image properties src field. I have verified that everything runs and the field is in fact getting set. I assume I must be misunderstanding something related to the DOM or ordering of events.
Thanks in advance.
HTML
<script src="https://unpkg.com/vue#next"></script>
<div id="list-rendering" class="demo">
<input v-model="imgsrc"></input>
<button v-on:click="setImage"> Set</button>
<ol>
<li v-for="todo in todos">
{{ todo.text }} {{todo.image}}
</li>
</ol>
</div>
CSS
.demo {
font-family: sans-serif;
border: 1px solid #eee;
border-radius: 2px;
padding: 20px 30px;
margin-top: 1em;
margin-bottom: 40px;
user-select: none;
overflow-x: auto;
}
Javascript
const ListRenderingApp = {
data() {
return {
todos: [
{ text: 'Learn JavaScript',
image: new Image(16,16)},
{ text: 'Learn Vue',
image: new Image(16, 16)},
{ text: 'Build something awesome',
image: new Image(16, 16)}
],
imgsrc: ""
}
},
methods:{
setImage(){
this.todos.map(todo => todo.image.src = this.imgsrc)
}
}
}
Vue.createApp(ListRenderingApp).mount('#list-rendering')

When using v-for make sure to add :key. Docs
Also pay attention to your html elements. <img> & <input>.
Also return the result of .map() to this.todos. Docs
new Vue({
el: "#app",
data: {
imgSrc: 'https://via.placeholder.com/150/FF0000',
todos: [
{ text: "Learn JavaScript", image: 'https://via.placeholder.com/150/0000FF' },
{ text: "Learn Vue", image: 'https://via.placeholder.com/150/0000FF' },
{ text: "Play around in JSFiddle", image: 'https://via.placeholder.com/150/0000FF' },
{ text: "Build something awesome", image: 'https://via.placeholder.com/150/0000FF' }
]
},
methods: {
setImage: function(todo){
this.todos = this.todos.map(todo => ({ ...todo, image: this.imgSrc }))
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div>
<small>image url</small>
<input v-model="imgSrc" />
</div>
<br />
<div>
<button #click="setImage()">
Click to set Image
</button>
</div>
<ol>
<li v-for="(todo, i) in todos" :key="i">
<label>
{{ todo.text }}
</label>
<img :src="todo.image" alt="img" width="100" height="100">
</li>
</ol>
</div>
try running this snippet & click the "Click to set Image"

try this.
<div id="list-rendering" class="demo">
<input v-model="imgsrc"></input>
<button v-on:click="setImage"> Set</button>
<ol>
<li v-for="(todo, index) in todos :key="index"">
{{ todo.text }}
<img :src="todo.image"/>
</li>
</ol>
</div>

Related

Getting Unexpected mutation of "buttonText" prop

I have created simple component for button in vuejs,When I clicked on the button then the text should show "I have been clicked". and it does but with this I also getting error that say: 49:7 error Unexpected mutation of "buttonText" prop vue/no-mutating-props
I have tried with this.$emit() but didnot mange to fix the mutation error issue. Can someone explain me what I am doing wrong and why I cannot mutate the buttonText props. Any help will be much appreciated!
here is my code:
App.vue
<template>
<div>
<ButtonDisabled buttonText="ChangedText" />
</div>
</template>
ButtonDisabled.vue:
<template>
<div class="main-container">
<h1>Button Disable if nothing is enter in email field</h1>
<div class="wrapper">
<input type="email" placeholder="email" v-model="email" />
<button :disabled="email.length < 1" class="btn-sub" #click="handleClick">
Subcribe
</button>
<!-- <button :disabled="!email.length">Subscribe</button> -->
<h2>{{ email }}</h2>
<h1>{{ buttonText }}</h1>
</div>
</div>
</template>
<script>
export default {
name: "ButtonDynamic",
props: {
buttonText: {
type: String,
default: "clickMe",
required: false,
}
},
data() {
return {
email: "",
};
},
methods: {
handleClick() {
// this.$emit("change-text-onclick", this.buttonText);
this.buttonText = "I have been clicked!!!";
},
},
};
</script>
<style scoped>
.main-container {
width: 300px;
height: 150px;
margin: 0 auto;
}
.wrapper {
display: inline-flex;
flex-direction: column;
}
.btn-sub {
margin: 10px;
}
</style>
Getting this error:
But my component still works when clicked:
Is it a bad practice to mutate a prop see link, you can mutate an inner component variable inside data or use v-model to have two-way dataflow on your props using v-model.
in this case, you can:
set up your internal data variable with the value of your prop, and then mutate that data variable ( not the prop )
Create and set the prop as a model value and expose it with a emit.
I will show you the easier path to get out of the error, but you need to reconsider your component responsibilities.
Vue.component('mybutton', {
template: `
<div class="main-container">
<h1>Button Disable if nothing is enter in email field</h1>
<div class="wrapper">
<input type="email" placeholder="email" v-model="email" />
<button :disabled="email.length < 1" class="btn-sub" #click="handleClick">
Subcribe
</button>
<!-- <button :disabled="!email.length">Subscribe</button> -->
<h2>{{ email }}</h2>
<h1>{{ inputMessage }}</h1>
</div>
</div>
`,
props: {
buttonText: {
type: String,
default: "clickMe",
required: false,
}
},
data() {
return {
email: "",
inputMessage: this.buttonText
};
},
methods: {
handleClick() {
this.inputMessage = "I have been clicked!!!";
},
},
});
new Vue({
el: '#app',
data(){
return {
message: "Changed Text"
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div>
<mybutton button-text="Change Text" />
</div>
</div>

How to bind style on-click in a loop

I've an array of objects (items) that can be incremented with other items, I show them by looping into the array.
see:
and I would like to high-light an item when I click on it (e.g. : another border-top-color), but I'm failing at targeting one specific element and applying a style without applying the style to the whole array. Any idea?
Template, I'm using vuedraggable, don't mind it:
<template #item="{ element }">
<div
class="item"
:key="element"
#click="
messageItemTitle(element.title);
messageItemID(element.id);
"
>
<div class="item-title">
{{ element.title }}
</div>
<div class="item-subType">
{{ element.type }}
</div>
</div>
</template>
The script : Well, none of what I've coded previously worked, so here's the data only :
data() {
return {
dragItems: dragItemsList, //15 items that I can clone into dropItems
dropItems: [], //Array where I can add the items from dragItems
};
},
You can conditionally apply class:
const app = Vue.createApp({
data() {
return {
items: [{id:1, title: 'aaa', type: 'type1'}, {id:2, title: 'bbb', type: 'type2'}, {id:3, title: 'ccc', type: 'type3'}],
selected: null
};
},
methods: {
message(el) {
this.selected = el.id
}
}
})
app.mount('#demo')
.item {
border: 3px solid transparent;
border-top-color: black;
}
.selected {
border-top-color: green;
}
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<div id="demo">
<div v-for="(element, i) in items" :key="i">
<!--<template #item="{ element }">-->
<div
class="item"
:class="selected === element.id && 'selected'"
:key="element"
#click="message(element)"
>
<div class="item-title">
{{ element.title }}
</div>
<div class="item-subType">
{{ element.type }}
</div>
</div>
<!--</template>-->
</div>
</div>

How do I make it so that I can edit my vue to do list to add items?

Ok, so, I am new to Vue and I am trying to make a simple to do list. I have managed to display it and stuff but I am having trouble with the code to add items to the list. When I click the button to add the task, it adds a bullet point but it doesn’t show the text given. For example, in the input box, I will type the name of a task but when I click the button, what I wrote doesn’t show up, just the bullet point.
I have tried putting the newTask part in other places but the same problem happens. I know this is probably a pretty silly question and the solution is probably pretty obvious but please help as fast as you can, I need to do this for school.
Here is my HTML:
<body>
<div id="app">
<!-- Vue App HTML Code -->
<h1>To Do List</h1>
<div id="app">
<ul>
<li v-for="x in tasks">
{{ x.text }}
</li>
</ul>
</div>
<div id="newTask">
<input type="text" value="taskName" placeholder="Task Name" v-model="newTask"></input>
<button v-on:click="tasks.push(newTask)">Add Task</button>
</div>
</body>
And my JavaScript/ Vue:
myObject = new Vue({
el: '#app',
data: {
tasks: [
{ text: '1' },
{ text: '2' },
{ text: '3' },
],
newTask: ''
},
})
I would really appreciate any other Vue tips (relevant or not) because it is my first week learning it and anything would be really useful.
You can achieve this by pushing the newTask in the existing tasks array.
Working Demo :
var vm = new Vue({
el: '#app',
data: {
tasks: [
{ text: '1' },
{ text: '2' },
{ text: '3' },
],
newTask: ''
},
methods: {
addTask() {
this.tasks.push({ text: this.newTask })
this.newTask = '';
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<h1>To Do List</h1>
<ul>
<li v-for="x in tasks">
{{ x.text }}
</li>
</ul>
<input type="text" placeholder="Task Name" v-model="newTask"/>
<button v-on:click="addTask">Add Task</button>
</div>

issue with duplicating divs and array values in vue

I have a code block here that is 90% working. The idea is that I have an autocomplete (searches via axios with a LIKE condition on the database but I simplified the result set here). When you type in the input, it searches and returns matching results in a dropdown. If you select an option, it replaces the search text in the input with the actual selected value. Then if you click to add another zone it clones the divs with the input.
The issue is that when I create another div and start searching in the input it glitches and then it also triggers the dropdown on both divs (or more if you add more than two). But it doesn't actually allow text to be entered in the newly added zones.
How can I fix this so that each zone actually has it's own input and results dropdown so that I can send something on save that has distinct values for each div?
new Vue({
components: {},
el: "#commonNameDiv",
data() {
return {
searchString: [],
results: [],
savedAttributes: [],
cards: []
}
},
methods: {
autoComplete() {
this.results = [];
console.log(this.searchString);
if (this.searchString.length > 2) {
this.results = [
{attribute_value:"apple"},
{attribute_value:"banane"}
]
}
},
saveAttribute(result) {
this.savedAttributes = [];
console.log('cool');
this.savedAttributes.push(result.attribute_value);
console.log('here is the attribute');
console.log(this.savedAttributes);
this.searchString = result.attribute_value;
this.results = [];
},
addCard: function() {
this.cards.push({
index: ''
})
}
}
})
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="commonNameDiv">
<div class="uk-grid">
<div class="uk-width-2-10" >
<input size="4" type="text" name="mapNumber">
</div>
<div class="uk-width-6-10">
<input style="width:100%" type="text" placeholder="what are you looking for?" v-model="searchString" v-on:keyup="autoComplete" class="form-control">
<div class="panel-footer componentList" v-if="results.length">
<ul class="list-group">
<li class="list-group-item" v-for="result in results">
<a v-on:click="saveAttribute(result)">{{ result.attribute_value }}</a>
</li>
</ul>
</div>
</div>
<div class="uk-width-2-10" style="border: 1px solid black; height:50px; width: 50px; margin: 0 auto;" >
</div>
</div>
<div v-for="(card,index) in cards" class="uk-grid">
<div class="uk-width-2-10">
<input size="4" type="text" name="mapNumber">
</div>
<div class="uk-width-6-10">
<input style="width:100%" type="text" placeholder="what are you looking for?" v-model="searchString[index]" v-on:keyup="autoComplete" class="form-control">
<div class="panel-footer componentList" v-if="results.length">
<ul class="list-group">
<li class="list-group-item" v-for="result in results">
<a v-on:click="saveAttribute(result)">#{{ result.attribute_value }}</a>
</li>
</ul>
</div>
</div>
<div class="uk-width-2-10" style="border: 1px solid black; height:50px; width: 50px; margin: 0 auto;">
</div>
</div>
<div style="height: 35px;">
</div>
<div>
<a v-on:click="addCard">Add another zone</a>
</div>
</div>
All your variables and/or properties need to be unique, currently they are not. The easiest way would be in my mind to just store all that you need inside the card object you are using. When you are done, you can just extract the data you need from your array.
Template where we have removed the first div you have, let's just push an empty card object to the array. Our card object will look like this:
{
index: "",
value: "" // the end value
results: [] // the search results will be stored here
}
So in template we use value as v-model for the input and display results for the user to pick their value. Also I suggest you use in all v-for iterations a key. Here I use the index, but that is really not that efficient! But anyway, so the stripped down version of template would look like:
<div v-for="(card, i) in cards" :key="i">
<div>
<input
placeholder="what are you looking for?"
v-model="card.value"
v-on:keyup="autoComplete($event, card)"
>
<div v-if="card.results.length">
<ul>
<li v-for="(result, i) in card.results" :key="i">
<a v-on:click="saveAttribute(result, card)">#{{ result.attribute_value }}</a>
</li>
</ul>
</div>
</div>
</div>
<div>
<a v-on:click="addCard">Add another zone</a>
</div>
The methods would be:
methods: {
autoComplete(ev, card) {
if (ev.target.value.length > 2) {
// here would be actual results...
card.results = [
{ attribute_value: "apple" },
{ attribute_value: "banane" }
];
}
},
saveAttribute(result, card) {
card.value = result.attribute_value;
},
addCard() {
this.cards.push({
index: "",
value: "",
results: []
});
}
}
and lastly, like mentioned, call addCard in a life cycle hook. Here I use created:
created() {
this.addCard();
}

To display dynamic JSON data in 2 different div's

My JSON looks like this:
users: [
{ 'name': 'User 1'},
{ 'name': 'User 2'},
{ 'name': 'User 3'},
{ 'name': 'User 4'},
{ 'name': 'User 5'},
{ 'name': 'User 6'},
]
Now i am looping this and i am displaying in the same div, But i need to diaplay the data by these conditions:
Up to length 3 should display in one div and rest things should display in another div(Ex another div right to that div). Here the JSON will be dynamic, The length may be <3 or >3. My requirement looks like this:
JSFiddle link
Vue is convenient to accomplish such tasks.
You need another two computed properties based on users.
template:
<div>{{left}}</div>
<div>{{right}}</div>
computed: {
left: function(){
return this.users.slice(0, 3);
},
reight: function() {
return this.users.slice(3);
}
}
Another option: https://jsfiddle.net/kth61cLu/1/
<div id="app">
<h3>Users</h3>
<div class="users1">
<div v-for="(user, index) in users" v-if="index < 3">
<p>{{user.name}}</p>
</div>
</div>
<div v-if="users.length > 3" class="users2">
<div v-for="(user, index) in users" v-if="index > 3">
<p>{{user.name}}</p>
</div>
</div>
</div>
Create a computed property that splits your array into two with the first having three elements and the second having the rest.
Then loop over the splitUsers array to display.
new Vue({
el: '#app',
data: {
users: [{"name":"User 1"},{"name":"User 2"},{"name":"User 3"},{"name":"User 4"},{"name":"User 5"},{"name":"User 6"}]
},
computed: {
splitUsers () {
const split = [ this.users.slice(0, 3) ]
if (this.users.length > 3) {
split.push(this.users.slice(3))
}
return split
}
}
})
#app { display: flex; }
#app div { padding: 1rem; border: 1px solid black; }
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
<div id="app">
<div v-for="chunk in splitUsers">
<p v-for="user in chunk">{{ user.name }}</p>
</div>
</div>
OK, here is what you can try.
<h3>Users</h3>
<div v-for="(user) in users.slice(0,3)">
<p>{{user.name}}</p>
</div>
<div v-for="(user) in users.slice(3)">
<p>{{user.name}}</p>
</div>
Hope it helps!

Categories