Setting a dynamic initial index for template variable using Vue JS - javascript

I'm using a CMS to create a front-end form where customers (theatre directors) can define several showtimes. I have a button they click to "add another" input field as needed (sidenote: I don't know how to enable a 'remove last' button using Vue, but that's another topic for another day).
I've gotten that working. But now, once a director has created their showtimes and they come back to edit them, I need the starting index of the Vue counter to be the number of fields that already exist. So if they've defined 3 showtimes already (0,1,2), the first field dynamically placed with Vue should start with 3.
Alternatively, I wonder if it'd be easier to generate an array of the existing data in Vue when the page loads, but I don't know where I'd start with that method.
Here's my HTML and Vue JS:
new Vue({
el: '#app',
data: {
timeslots: [
{
count: 0
}
],
count: 0
},
methods: {
addAnother: function(){
this.timeslots.push({
count: ++this.count
});
}
}
});
<div id="app">
{{ performances }}
<input name="performances[{{ zero_index }}][showtime]" value="{{ showtime }}">
{{ /performances }}
<template v-for="slot in timeslots">
<input name="performances[#{{ slot.count }}][showtime]" value="{{ now }}">
</template>
<div>
<button #click.prevent="addAnother">Add another</button>
</div>
</div>
Any help to move forward is appreciated!

I solved it myself in shorter time than expected using the 2nd method I speculated about in my OP, and using $index instead of an arbitrary integer like count. This method requires the JS to be put directly in my template though, not in a separate file. But that's fine by me.
<div id="oven">
<template v-for="biscuit in oven">
<div>
<input type="text" name="performances[#{{ $index }}][showtime]" value="#{{ biscuit.showtime }}">
</div>
</template>
<span class="add small black btn" #click="addAnother"><i class="fa fa-plus-circle"></i> Add another</span>
</div>
<script>
new Vue({
el: '#oven',
data: {
oven: [
{{ performances }}
{
showtime: "{{ showtime }}"
},
{{ /performances }}
]
},
methods: {
addAnother: function(){
this.oven.push({
showtime: '{{ now modify_date="+2 months" format="Y-m-d" }} 7:30 PM'
});
}
}
});
</script>

Related

update data in slot vuejs

hi im using vuejs with laravel project
and this is my vuejs code
Vue.component('search_and_select',{
template:
'<div>'+
'<slot :test_text="test_text"></slot>'+
'</div>',
data:function(){
return {
test_text:"test text",
}
},
methods:{
},
props:{
},
});
new Vue({
el:'.user_search_and_select',
data:{
},
});
and this is my html code
<div is='search_and_select'>
<div slot-scope="{test_text}">
#{{test_text}}
<input type='text' v-model='test_text' />
</div>
</div>
till now everything working so good
but if i keyup <input type='text' v-model='test_text' /> the test_text dont change still the same
so how can i change in slot and change in parent component too
thanks a lot ..
You have to expose a method to the slot for updating the value. This means you won't be able to use v-model because you will need to handle :value and #input separately now.
<slot :test_text="test_text" :update_test_text="update_test_text"></slot>
methods: {
update_test_text(value) {
this.test_text = value
}
}
Now you can use the component like this:
<search_and_select>
<div slot-scope="{ test_text, update_test_text }">
<input
type="text"
:value="test_text"
#input="update_test_text($event.target.value)"
>
</div>
</search_and_select>

Vue.js change model attached to a form upon clicking a list

I have an array of objects. These objects are loaded into a list in vue.js.
Aside from this list, I have a form that displays data from one of these objects. I want to, when clicking one of the list's elements, it will bind this specific object to the form and show its data.
How can do this in Vue.js?
My list code is:
<div id="app-7">
<ul id="food-list" v-cloak>
<food-item v-for="item in foodList" v-bind:food="item" v-bind:key="item.id" inline-template>
<li class="food">
<div class="food-header">
<img :src="'img/' + food.slug +'.png'">
<div class="food-title">
<p>{{food.name}} |
<b>{{food.slug}}</b>
</p>
<p>quantity: {{food.quantity}}</p>
</div>
<div class="food-load"> // load into form upon clicking this
</div>
</div>
</li>
</food-item>
</ul>
</div>
Since I do not have the code for the form, this is my best guess without clarification.
You can add a click handler to the item you want to be clicked. It will pass the value of the food item into the method.
<div class="food-load" #click="setFoodItem(item)">
</div>
And when that method is called, it can assign the clicked item to a data property. I'm not sure where your form is, and if it is in a different component. If it is in a child component, you would have to pass it in as a prop, or emit an event to pass it to a parent component.
data() {
return {
//create a reactive field to store the current object for the form.
foodItemForm: null
};
},
methods: {
//method for setting the current item for the form.
setFoodItem(item) {
this.foodItemForm = item;
}
}
Missing quite a bit of info in your sample code, your script is very important to see to make sense of what you would like to accomplish and where things might be going wrong.
Here's a quick list of the issue I came across with your code:
v-for refers to an individual food item as 'item', inside the loop you're trying to access properties as 'food'
You don't wrap your code in a component unless you're importing the component
When binding a value to 'v-bind:src' (or shorthand ':src') only pass the url, you should be specifying this in your script not inline.
You're better off using a button and the 'v-on:click' (or shorthand '#click') to load your selected food item into your form
You should also include your Javascript
Regardless, here's how I would handle this (took the liberty in filling in some blanks):
<template>
<div id="app">
<ul id="food-list">
<!--<food-item v-for="item in foodList" v-bind:food="item" v-bind:key="item.id" inline-template>-->
<li v-for="item in foodList" class="food">
<div class="food-header">
<img :src="item.slug" v-bind:alt="item.slug" width="250px" height="auto">
<div class="food-title">
<p>{{item.name}} | <b>{{item.slug}}</b></p>
<p>quantity: {{item.quantity}}</p>
</div>
<button class="food-load" #click="loadFoodItem(item.id)">Load Food Item</button>
</div>
</li>
<!--</food-item>-->
</ul>
<form v-if="activeFoodId != null" id="foodItemForm" action="#">
<h3>Food Form</h3>
<label for="food-id">Id:</label>
<input id="food-id" type="number" v-bind:value="foodList[activeFoodId].id"><br/>
<label for="food-slug">Slug:</label>
<input id="food-slug" type="text" v-bind:value="foodList[activeFoodId].slug"><br/>
<label for="food-name">Name:</label>
<input id="food-name" type="text" v-bind:value="foodList[activeFoodId].name"><br/>
<label for="food-quantity">Quantity:</label>
<input id="food-quantity" type="number" v-bind:value="foodList[activeFoodId].quantity">
</form>
</div>
</template>
<script>
export default {
name: 'app',
data: function () {
return {
activeFoodId: null,
foodList: [
{
id: 1,
slug: 'http://3.bp.blogspot.com/-QiJCtE3yeOA/TWHfElpIbkI/AAAAAAAAADE/Xv6osICLe6E/s320/tomato.jpeg',
name: 'tomatoes',
quantity: 4
}, {
id: 2,
slug: 'https://img.purch.com/rc/300x200/aHR0cDovL3d3dy5saXZlc2NpZW5jZS5jb20vaW1hZ2VzL2kvMDAwLzA2NS8xNDkvb3JpZ2luYWwvYmFuYW5hcy5qcGc=',
name: 'bananas',
quantity: 12
}, {
id: 3,
slug: 'https://media.gettyimages.com/photos/red-apples-picture-id186823339?b=1&k=6&m=186823339&s=612x612&w=0&h=HwKqE1MrsWrofYe7FvaevMnSB89FKbMjT-G1E_1HpEw=',
name: 'apples',
quantity: 7
}
]
}
},
methods: {
loadFoodItem: function (foodItemId) {
console.log(foodItemId)
this.activeFoodId = foodItemId
}
}
}
</script>
<style>
/# Irrelevant #/
</style>
Hope it helps!

Form modal binding in laravel with vue js

I have 2 models Tour.php
public function Itinerary()
{
return $this->hasMany('App\Itinerary', 'tour_id');
}
and Itinerary.php
public function tour()
{
return $this->belongsTo('App\Tour', 'tour_id');
}
tours table:
id|title|content
itineraries table:
id|tour_id|day|itinerary
In tour-edit.blade.php view I have used vue js to create or add and remove input field for day and plan dynamically.
Code in tour-create.blade.php
<div class="row input-margin" id="repeat">
<div class="col-md-12">
<div class="row" v-for="row in rows">
<div class="row">
<div class="col-md-2">
<label >Day:</label>
<input type="text" name="day[]"
class="form-control">
</div>
<div class="col-md-8">
{{ Form::label('itinerary', " Tour itinerary:", ['class' => 'form-label-margin'])}}
{{ Form::textarea('itinerary[]',null, ['class' => 'form-control','id' => 'itinerary']) }}
</div>
<div class="col-md-2">
<button class="btn btn-danger" #click.prevent="deleteOption(row)">
<i class="fa fa-trash"></i></button>
</div>
</div>
</div>
<div class="row">
<button class="btn btn-primary add" #click.prevent="addNewOption" >
<i class="fa fa-plus"></i> Add Field</button>
</div>
</div>
</div>
I want to populate these fields with their respective data. But all data i.e itinerary belonging to a tour are being displayed in itinerary textbox in JSON format.
My vue js sript is:
<script>
var App = new Vue({
el: '#repeat',
data: {
day:1 ,
rows:[
#foreach ($tour->itinerary as $element)
{day: '{{$element->day}}', plan: '{{$element->plan}}'},
#endforeach
]
},
methods: {
addNewOption: function() {
var self = this;
self.rows.push({"day": "","itinerary":""});
},
deleteOption: function(row) {
var self = this;
self.rows.splice(row,1);
},
}
});
</script>
I would avoid mixing blade into JavaScript, instead the best option is to make an ajax call to an api route which returns your data in json, which can then be processed by Vue:
methods:{
getItinerary(){
axios.get('api/itinerary').then(response => {
this.itinerary = response.data;
})
}
}
However, with this approach you will likely need to use vue-router rather than laravel web routes, which puts us into SPA territory.
If that's not an option (i.e. you still want to use blade templates), you should take a look at this answer I gave the other day which shows you how to init data from your blade templates.
What you seem to be doing is using laravel's form model binding to populate your forms, not Vue, so your model data is not bound to the view. So, you will need to decide which one you want to use. If it's vue you just want to use a normal form and bind the underlying data to it using v-model:
Now any updates in the view will automatically be updated by Vue. I've put together a JSFiddle that assumes you will want to continue using Laravel web routes and blade templates to show you one approach to this problem: https://jsfiddle.net/w6qhLtnh/

Vuejs - How to communicate changes between parent and child in scoped slots

I'm trying to create a component which has two slots. The second slot is repeating based on the number of items in the first slot. I have achieved this using scoped slots, however, when the data is updated on the first slot, the second slot does not automatically update it until an event is triggered, eg: click of a button which calls a method.
Is there a way to make the second slot updates its view when the data is changed on the first slot?
Here is the example that I have:
Jsfiddle: https://jsfiddle.net/89vykm75/1/
new Vue({
el: '#app',
components: {
'repeat-for-each-item': {
data: function() {
return {
items: []
}
},
template: `<div>
<slot name="item" v-for="item in items" :item="item"></slot>
<button #click="addItem()">Add item</button>
<slot name="repeat" v-for="item in items" :item="item"></slot>
</div>
`,
methods: {
addItem() {
this.items.push({});
}
}
}
}
});
<div id="app">
<repeat-for-each-item>
<template slot="item" scope="props">
<div>
<input type="text" v-model="props.item.name">
</div>
</template>
<template slot="repeat" scope="props">
<div>
<label>
<span v-if="props.item.name">{{props.item.name}}:</span>
<span v-else>No Name:</span>
</label>
<input type="text">
</div>
</template>
</repeat-for-each-item>
</div>
I found a solution by calling a method on keyup.
Basically, I added #keyup event on the slot
<input type="text" v-model="props.item.name" #keyup="props.onchange()">
And on the component template, pass on the onchange method to the slot
<slot name="item" v-for="item in items" :item="item" :onchange="onchange"></slot>
And then have the onchange function to force the re-render
onchange:() => {
// hack to trigger changes
this.$set(this.items, 0, this.items[0]);
}
Here is the full working JsFiddle: https://jsfiddle.net/89vykm75/2/
I wonder if there is a cleaner solution?
You are falling into a Vue change detection caveat. The issue here is if you add a property to an object that didn't exist before when the object was added to the Vue data, then Vue cannot detect the change. Here is the problem:
this.items.push({})
You're adding an object with no properties, and then you bind v-model to the name property of that object, which does not exist. Vue cannot detect the change, and does not update the other items bound to that property.
If you instead did this:
this.items.push({name: null})
You will find your code works. Here is an updated fiddle.

Different if on each item in a vue list

I'm making a list with 'v-for' where each item has to have an if with a different value corresponding the array, but the Vue not allows to create a 'v-if' expression starting from {{ i.some_data_to_evaluate }} .
Have a way to solve this?
Here is my code:
HTML
<div id='test' v-for="i in items">
<p v-if={{i.value}}>{{i.some_text}}</p>
<p v-else>{{i.other_text}}</p>
</div>
JS
let test = new Vue({
el: '#test',
data: [
{some_text: 'jhon', other_text: 'jonas', value:false},
{some_text: 'joao', other_text: 'maria', value:true}
]
})
I'm just trying to change the version that is in the Vue guide.
Here's the link: http://vuejs.org/guide/list.html
You should replace the brackets with quotes on the v-if directive:
<div id="test" v-for="i in items">
<p v-if="i.value">{{i.some_text}}</p>
<p v-else>{{i.other_text}}</p>
</div>

Categories

Resources