Vue.js - data access with dynamic components - vue.js

I have simple web page that shows a result list and users can switch between table or li style.
I got simple Vue with two dynamic components : results-array and results-list.
It works like a charm except when I switch from the first component to the second one: I loose results properties called in mustache (I got blank values with no error) :
{{contact.img_path}} does not show anything
whereas
<img :src="contact.img_path" /> works great
** UPDATE **
Here a simplified jsfiddle to try out: https://jsfiddle.net/eywraw8t/151906/
My files :
contact.js
var list = Vue.component('results-list', {
template: '#results-list-template',
props: ['display_mode', 'contacts']
});
var table = Vue.component('results-array', {
template: '#results-array-template',
props: ['display_mode', 'contacts'],
});
const app = new Vue({
el: '#app',
router,
data: {
currentResultsView: 'results-array',
display_mode: 'array',
contacts: [],
contact: { company: {}, phones: {}, section: {}, schedule_type: {}}, // Declaring Reactive Properties
phone: {} // Declaring Reactive Properties
},
created () {
this.navigate(this.$route.query.page);
},
methods: {
changeDisplay: function(event, type){
this.currentResultsView = (type == "array") ? "results-array" : "results-list";
this.display_mode = type;
console.log('contacts', this.contacts[0].lastname) // WORKS !
},
navigate: function(page){
var page = page > 0 ? page : 1;
axios.get('/', {params: {page: page, fulltext_search: this.fulltext_search, sort_dir: this.sort_dir}})
.then((response) => {
this.contacts = response.data.entries;
});
}
}
});
index.html
<ul>
<li #click="changeDisplay($event, 'hcard')" :class="{active:display_mode == 'hcard'}">Carte de visite</li>
<li #click="changeDisplay($event, 'vcard')" :class="{active:display_mode == 'vcard'}">Vignette verticale</li>
<li #click="changeDisplay($event, 'array')" :class="{active:display_mode == 'array'}">Tableau</li>
</ul>
<div id="app">
<script type="text-x-template" id="results-array-template">
<table>
<tr>
<th></th>
<th>Firstname</th>
<th>Lastname</th>
</tr>
<tr v-for="contact in contacts">
<td><img :src="contact.img_path" class="contact_img" /></td>
<td>{{ contact.firstname }}</td>
<td>{{ contact.lastname }}</td>
</tr>
</table>
</script>
<script type="text-x-template" id="results-list-template">
<ul>
<li v-for="contact in contacts">
{{contact.firstname}} <!-- **Does not show anything** -->
<img :src="contact.img_path" /> <!-- **Works great!** -->
</li>
</ul>
</script>
<div id="results" :class="display_mode" class="clearfix">
<keep-alive>
<component v-bind:is="currentResultsView" :display_options="display_options" :display_mode="display_mode" :contacts="contacts" ></component>
</keep-alive>
</div>
</div>

You should either remove the key contact from the data part of your Vue root instance, or use another name in the v-for iterator (e.g. v-for="myContact in contacts")
UPDATE
Also, you should not use script tags for the template - use template instead, because Chrome ignores non-JavaScript script tags.
The solution - https://codepen.io/TMCDOS/pen/gjYWNY

The solution is to move the two template scripts outside of the #app div

Related

Vue js: How to add a class to the closest td on click of Button

I am new to Vue coming off of JS/JQuery. I have a table, and each row has 2 possible buttons and two inputs, all wrapped in <td>. When I click a button I want the nearest input to have a class added. In JQuery I would have used the closest method in selecting the neighbouring <td> Here is my Vue template syntax. Many thanks!
<tbody>
<tr v-for="child in registeredChildren">
<td class="col-2"><a :href="'/getchild/'+ child.child_id">{{child.childFirstName}}</a>&nbsp &nbsp {{child.childLastName}}</td>
<!--========TIME IN===========-->
<td class="col-3 pb-2"}"><input style="text-align:center;" class="form-control editChild initial" type="text" v-model="child.timeIn" ></td>
<td><button v-on:click="updateTimeIn(child)" class="btn btn-outline-success">Reset</button></td>
<!-- //========TIME Out ===========//-->
<td class="col-3 pb-2" ><input style="text-align:center;" class="form-control editChild" type="text" v-model="child.timeOut" ></td>
<td><button v-on:click="updateTimeOut(child)" class="btn btn-outline-success">Reset</button></td>
</tr>
</tbody>
Methods: I was thinking if I could add some code to the UpdateTimeIn and TimeOut methods, this could be an approach?
methods:{
updateTimeIn(child){
this.updatedChild = child;
console.log(child.child_id,child.timeIn)
axios.post('http://kidsclub.test/upDateChildTimeIn', {
child_id: child.child_id,
timeIn: child.timeIn,
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
},
**NB** I have the same for updateTimeOut
You are using Vue, which unlike jQuery, means the data should drive the DOM. In Vue, you don’t have to consider selecting certain dom nodes.
I used to switch from jQuery to Vue, too. I have provided a demo, hope you can find ideas in it.
<button #click="onClick">click me</button>
<div class="fixed-classes" :class="{'dynamic-class': isClick}"></div>
data() {
return {
isClick: false
};
},
methods: {
onClick() {
this.isClick = !this.isClick;
}
}
You can run it online through the code sandbox link: codesandbox
I updated the code based on the comments in the code sandbox.

Datatable v-for to produce checkbox or input based on data

Wondering how I can get my datatable to build a column that produces either a checkbox or input based on a value from data. This is the what I have but I have a good feeling there is a way better way of doing this.
<div v-for=”shirt in shirts”>
<div v-if=”stock.shirts < 2”>
<td><input type="checkbox"></td>
</div>
<div v-else>
<td><input type="text"> of {{ props.item.shirts }}</td>
</div>
</div>
Any help would be greatly appreciated
reduce your if clause
<td v-for=”shirt in shirts”><input type="checkbox"></td>
<td v-else><input type="text"> of {{ props.item.shirts }}</td>
vue docs - Conditional Rendering
or you can use dynamic components, like so:
<template>
<td>
<component :is="component" :data="passthroughdata" />
</td>
</template>
//...
props: ["value", "passthroughdata"],
data() {
return {
component: {}
},
},
watch: {
value:{
handler: async function(){
try{
await import(`./components/${valueBasedComponent}/index.vue`)
this.component = () => import(`./${valueBasedComponent}/index.vue`)
} catch() {
this.component = () => import(`./${someDefaultComponent}/index.vue`)
}
},
// immediate: true
}
}
vue docs - Dynamic & Async Components

Getting data back into the root with nested components in vue

I am building a multiple page app with latest Laravel and latest Vue.js. At the end of this post you will see what I am trying to achieve - which I have done visually. However the user needs to be able to edit the text, assigned user and the date of each item. I have started with the date and as you can see I have the date picker working as well.
Where I am struggling is updating the main model of data in the root so that I can save the changes that the user has made via a HTTP request. Initially the tree's data is loaded in via HTTP as well (example below).
I have built the below using nested components and I have read that two binding has been depreciated for props on components. I know that I need to emit and user events but I'm sure how this would work if the components are nested?
Here is an example of the data that get's loaded via HTTP. Below is a very small example, however this could be much larger
{
"objective":"Test",
"user_id":null,
"by":"08\/09\/2018",
"colour":"#1997c6",
"children":[
{
"objective":"Test",
"user_id":11,
"by":"08\/09\/2018",
"colour":"#d7e3bc",
"children":[]
}, {
"objective":"Test",
"user_id":11,
"by":null,
"colour":"#1997c6",
"children":[]
}
]
}
Here are the components that I have put together so far.
Vue.component('tree-date', {
props: ['date'],
data () {
return {
id: 0
}
},
mounted() {
this.id = uniqueId();
$('#picker-' + this.id).datetimepicker({
format: 'DD/MM/YYYY',
ignoreReadonly: true
});
},
template: `
<div class="date-container" :id="'picker-' + id" data-target-input="nearest" data-toggle="datetimepicker" :data-target="'#picker-' + id">
<div class="row">
<div class="col-2">
<div class="icon">
<i class="fa fa-calendar-alt"></i>
</div>
</div>
<div class="col-10">
<input type="text" class="form-control datetimepicker-input" readonly="readonly" :data-target="'#picker-' + id" v-model="date">
</div>
</div>
</div>`
});
Vue.component('tree-section', {
props: ['data', 'teamUsers', 'first'],
methods: {
test () {
this.$emit('test');
}
},
template: `
<table v-if="data.length != 0">
<tr>
<td :colspan="data.children !== undefined && (data.children.length * 2) > 0 ? data.children.length * 2 : 2">
<div class="node" :class="{'first': first == true}">
<div class="inner">
<tree-date :date="data.by"></tree-date>
<div class="objective">
{{ data.objective }}
</div>
<div class="author" v-if="data.user_id !== null">
{{ teamUsers[data.user_id].first_name }} {{ teamUsers[data.user_id].last_name }}
</div>
<div class="author" v-if="data.user_id === null">
Unassigned
</div>
</div>
</div>
</td>
</tr>
<tr class="lines" v-if="data.children.length > 0">
<td :colspan="data.children.length * 2"><div class="downLine"></div></td>
</tr>
<tr class="lines" v-if="data.children.length > 0">
<td class="rightLine"></td>
<td class="topLine" v-for="index in ((data.children.length * 2) - 2)" :key="index" :class="{'rightLine': index % 2 == 0, 'leftLine': Math.abs(index % 2) == 1}"></td>
<td class="leftLine"></td>
</tr>
<tr v-if="data.children.length > 0">
<td colspan="2" v-for="child in data.children">
<tree-section :data="child" :team-users="teamUsers" :first="false"></tree-section>
</td>
</tr>
</table>
`
});
This all get's called in the view by:
<tree-section :data="data" :team-users="teamUsers" :first="true"></tree-section>
Any help getting data update in the components back into the root will be most helpful.
by default, vue props (if objects or arrays) are being passed by reference- that means that if you change your object on the child component, the original object on the parent component will get changed too.
from vue api:
Note that objects and arrays in JavaScript are passed by reference, so
if the prop is an array or object, mutating the object or array itself
inside the child component will affect parent state.
https://v2.vuejs.org/v2/guide/components-props.html

Vue UI not updating using axios after closing semantic modal

I have a vue that displays an html table based on an axios call to get a list of points from a db table during mounted. I also have a semantic modal that I use in the same vue to add records to the points db table. During the modal's onHidden, I make the same axios call to update the html table in order to display the new record. However, the html table isn't updating.
<template>
<div>
<h1 class="ui header">Points</h1>
<button class="ui icon button" v-on:click="showModal()">
<i class="add icon"></i>
</button>
<div class="ui modal">
<div class="header">
Header Text
</div>
<div class="content">
<div class="ui form">
<div class="field">
<label>Date</label>
<datepicker v-model="newPoint.earnedDate" id="earned_date"></datepicker>
</div>
<div class="ui grid">
<div class="four wide column">
<div class="ui dropdown" id="name_dd">
<div class="text">Name</div>
<i class="dropdown icon"></i>
</div>
</div>
<div class="eight wide column">
<div class="ui dropdown" id="rule_dd">
<div class="text">Rule</div>
<i class="dropdown icon"></i>
</div>
</div>
</div>
</div>
</div>
<div class="actions">
<div class="ui black deny button">
Cancel
</div>
<div class="ui positive right labeled icon button">
Save
<i class="checkmark icon"></i>
</div>
</div>
</div>
<table class="ui celled table">
<thead>
<tr>
<th>Date</th>
<th>Name</th>
<th>Points</th>
</tr>
</thead>
<tbody>
<tr v-for="point in points">
<td>{{point.earnedDate}}</td>
<td>{{point.name}}</td>
<td>{{point.points}}</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
import axios from "axios";
import Datepicker from 'vuejs-datepicker';
export default {
name: 'earnings',
components: {
Datepicker,
},
data() {
return {
points: [],
newPoint: {
earnedDate: "1/1/1970",
name: "",
points: ""
},
earners: [],
errors: [],
}
},
methods: {
showModal: function() {
$('.ui.modal')
.modal('show');
},
},
mounted () {
//THIS CALL UPDATES THE HTML TABLE
axios
.get('api/earnings')
.then(response => (this.points = response.data));
//Set the modal approve and deny callbacks
$('.ui.modal')
.modal({
closable: true,
onDeny: function () {
return;
},
onApprove: function () {
/*****************************************
* Add the new points using web API
*****************************************/
//Get the modal values
var earned_date = $('#earned_date').val();
var earner_id = $('#name_dd').dropdown('get value');
var rule_id = $('#rule_dd').dropdown('get value');
//Call the post route
axios
.post('api/earnings', { earnedDate: earned_date, earnerId: earner_id, ruleId: rule_id})
.then(response => {})
.catch(e => {
console.log(e)
})
return;
},
onHidden: function () {
//THIS CALL DOES NOT UPDATE THE HTML TABLE
axios
.get('api/earnings')
.then(response => (this.points = response.data));
}
});
//Load the name dropdown on the add new points modal
$('.four.wide.column .ui.dropdown')
.dropdown({
apiSettings: {
// this url just returns a list of tags (with API response expected above)
url: 'api/earners/semantic_dd',
cache: false
},
});
//Load the rule dropdown on the add new points modal
$('.eight.wide.column .ui.dropdown')
.dropdown({
apiSettings: {
// this url just returns a list of tags (with API response expected above)
url: 'api/rules/semantic_dd',
cache: false
},
});
},
created: function () {
// Remove the modal so it doesn't duplicate when the user navigates away from the component
// and then returns to it
$('.ui.modal').remove();
}
}
</script>
It looks like the this is not available during the axios call using =>. At the start of mounted I set var self = this and used self.points instead of this.points during onHidden. The binding to the template is working now.
Edit after-the-fact
In Vue.js, use this.$data.property-name which points to the component's properties. See documentation at https://v2.vuejs.org/v2/api/#data

Passing a variable to a Vue component from the parent page that called it

I have a simple table that displays all of my data:
main file.php
<table class="table table-bordered table-hover" id="all-jobs">
<thead>
<tr>
<th>{{ __('Job Name') }}</th>
<th>{{ __('Job Description') }}</th>
<th>{{ __('Job Status') }}</th>
<th>{{ __('Job Applications') }}</th>
<th>{{ __('Manage') }}</th>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td class="non_searchable"></td>
<td class="non_searchable"></td>
</tr>
</thead>
</table>
<div id="app">
<div id="editJob" class="modal fade in" role="dialog">
<div class="modal-dialog">
<!-- Modal content-->
<div class="modal-content">
<edit-job id=""></edit-job>
</div>
</div>
</div>
</div>
Now, I have a edit button that I am trying to open an edit modal for that specific row:
<a href='' data-id='{$job->id}' class='btn btn-xs btn-danger' data-toggle='modal' data-target='#editJob'><i class='fa fa-close'></i></a>";
The href is is location in one of the of my data table, I am trying to pass that to my .vue file so I can use it for my get and post requests:
myfile.vue
<template>
<div>
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">×</button>
<h4 class="modal-title">Edit Job</h4>
</div>
<div class="modal-body">
<form method="post" #submit.prevent="signIn" #keydown="errors.clear($event.target.name)">
<!-- Removed code, it's just inputs !-->
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-info btn-fill btn-wd" v-on:click="addJob">Save</button>
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</template>
<script>
export default
{
props: ['id'],
data: function ()
{
return {
countries: [],
name: '',
summary: '',
salarytype: '',
salaryfrom: '',
salaryto: '',
location: '',
contactemail: '',
contactphone: '',
errors: new Errors()
}
},
methods:
{
addJob: function()
{
axios.post('/jobs/edit', this.$data)
.then(response => {
if(response.data.status === true){
$('#editJob').modal('hide');
getJobTable();
}
else{
formError = response.data.message;
}
})
.catch(error => this.errors.record(error.data))
}
},
mounted: function()
{
console.log($(this).data('id'));
axios.get('/jobs/my-job/')
.then(response => {
this.name = response.data.name
this.summary = response.data.summary
this.salarytype = response.data.salary_type
this.salaryfrom = response.data.salary_from
this.salaryto = response.data.salary_to
this.location = response.data.location
this.contactemail = response.data.contact
this.contactphone = response.data.phone
})
axios.get('/countries')
.then(response => {
this.countries = response.data;
})
}
}
</script>
How can I past my href id to my to use for my request? Thanks
MY structure:
Created-jobs.blade.php
https://pastebin.com/TPBnC1qP
Edit-Job.vue
https://pastebin.com/30UWR5Nn
app.js
https://pastebin.com/1yxZWvVC
The table just populates the data, and adds the dropdown like so:
<ul class='icons-list'>
<li class='dropdown'>
<a href='#' class='dropdown-toggle' data-toggle='dropdown' aria-expanded='false'>
<i class='icon-menu9'></i>
</a>
<ul class='dropdown-menu dropdown-menu-right'>
<li>
<a data-id='{$job->id}' onclick='getID({$job->id})' data-toggle='modal' data-target='#editJob'>
<i class='icon-file-pdf'></i> Edit Job
</a>
</li>
<li>
<a href='javascript:void();' data-id='{$job->id}' onclick='deleteJob({$job->id})'>
<i class='icon-cross'></i> Delete Job
</a>
</li>
</ul>
</li>
</ul>
You don't give a lot of information about the structure of your application, but it looks like you are using at least one single file component to display the data inside your modal which is being entirely displayed via Bootstrap. It also looks like the table with the id values you want to pass to Vue is outside of the Vue itself.
That being the case, the way you can pass the data you want to the single file component is to capture the Vue in a variable and then set the id whenever the link in your table is clicked.
Let's suppose your main.js or app.js looks like this:
import Vue from 'vue'
import EditJob from './EditJob.vue'
Vue.component('edit-job', EditJob)
const app = new Vue({
el: '#app',
data:{
id: null
}
})
// Add a click handler for the links with the `data-id` property.
// This is using jQuery (because you are using Bootstrap) but you
// can do this any way you want.
$("[data-id]").on("click", function(){
// Set the Vue's data to the data-id property in the link.
app.id = this.dataset.id
})
Notice how the code captures the result of new Vue(...) in the variable app. Then I've added the data property, id to the Vue and a click handler for all of your links that sets app.id to this.dataset.id whenever a link is clicked. In this way, every time a link is clicked, the data property in the Vue will be set to the id of the clicked link.
Then, all you need to do is bind the id property to your component.
<edit-job :id="id"></edit-job>
and your EditJob component will always get the updated id.
Here is a working example.
Edit
In the code you added to your example, you are defining all of your jQuery script in Created-jobs.blade.php. That being the case, the function you wrote, getID doesn't have access to the app variable you defined in your webpack bundle because of normal javascript scoping rules. To make app accessible to your jQuery code, add it to the window.
window.app = new Vue({
el: '#app',
data:{
id: null
}
});
Secondly, though you defined the getID function, nothing calls it. It needs to be called when the links are clicked. Add this somewhere to your jQuery code in Created-jobs.blade.php (ideally in a document ready function).
$("[data-id]").on("click", getID)