Vue 2 - get clossest element with a class - javascript

I am having the following in my template:
<tbody>
#foreach ($countries as $country)
<tr class="parent">
<td><input class="form-control" type="text" value="{{ $country->name }}"></td>
<td>{{ $country->show ? 'Yes' : 'No' }}</td>
<td><input class="form-control" type="number" value="{{ $country->order }}"></td>
<td>
<button class="btn btn-primary">{{ $country->show ? "Hide" : "Show" }}</button>
<button class="btn btn-success" #click="updateCountry">Update</button>
</td>
</tr>
#endforeach
</tbody>
And this is the Vue code:
import Vue from 'vue'
let App = new Vue({
el: '#app-container',
data: {
},
methods: {
filterCountries() {
},
updateCountry(event) {
console.log(event.target.parentElement);
}
}
})
So far I can get a reference to the parent (which is the td that contains the buttons). Is it possible to get the closest element with the class parent (similar to the jquery) and then get the values of the input elements contained in the parent element?

What you are describing is a very non Vue way to go about things. What I would do is define a component. I realize that this doesn't work completely with your code as it is, but bear with me.
console.clear()
Vue.component("country",{
props:["country"],
template: "#country-template",
data(){
return {
internalCountry: this.country
}
},
methods:{
updateCountry(){
console.log(this.internalCountry)
}
}
})
let App = new Vue({
el: '#app-container',
data: {
country:{
name:"Germany",
order: 1,
show: true
}
},
methods: {
filterCountries() {
},
updateCountry(event) {
console.log(event.target.parentElement);
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.js"></script>
<div id="app-container">
<table>
<tbody>
<tr is="country" :country="country"></tr>
</tbody>
</table>
</div>
<template id="country-template">
<tr class="parent">
<td><input class="form-control" type="text" v-model="internalCountry.name"></td>
<td>{{ internalCountry.show ? 'Yes' : 'No' }}</td>
<td><input class="form-control" type="number" v-model="internalCountry.order"></td>
<td>
<button class="btn btn-primary">{{ internalCountry.show ? "Hide" : "Show" }}</button>
<button class="btn btn-success" #click="updateCountry">Update</button>
</td>
</tr>
</template>
Your template would then become something like this
#foreach ($countries as $country)
<tr is="country" :country="{{$country}}"></tr>
#endforeach
The individual countries are passed into the component as properties. Inside the component, the code uses v-model to bind the data to the internal country data so they are automatically updated. Then, when you need to do something in updateCountry you already have the values.

Related

How to remove selected option from multiple dropdown on click a button in angular 8

I have select dropdown whose option value comes from the array of object.When I click add button new dropdown will be added and on click delete button particular dropdown will be deleted.On page load select an option(need to onchange the dropdown) then click add button and you will get other dropdown below.Now here requirement is, selected options should not be repeated again into added dropdown after adding a new dropdown. Till now working fine.The problem is all dropdown is getting updated.Here is the code below
https://stackblitz.com/edit/angular-sxfanw?file=src%2Fapp%2Fapp.component.html
app.component.html
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>Code</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<select (change)="onChange($event.target.value)">
<option *ngFor="let title of arraydata" [value]="title.name" >{{title.name}}</option>
</select>
</td>
<td>
<button class="btn btn-default" type="button" (click)="addFieldValue()">Add</button>
</td>
</tr>
<tr *ngFor="let field of fieldArray; let i = index">
<td>
<select (change)="onChange($event.target.value)">
<option *ngFor="let title of arraydata" [value]="title.name" >{{title.name}}</option>
</select>
</td>
<td>
<button class="btn btn-default" type="button" (click)="deleteFieldValue(i)">Delete</button>
</td>
</tr>
</tbody>
</table>
app.component.ts
import { Component ,OnInit} from '#angular/core';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
fieldArray: Array<any> = [];
newAttribute: any = {};
arraydata = [{"name":"name1","value":1},{"name":"name2","value":2},{"name":"name3","value":3},{"name":"name4","value":4}]
selectedDevice:any;
ngOnInit() {
console.log(this.arraydata)
}
onChange(deviceValue) {
this.selectedDevice = deviceValue;
}
addFieldValue() {
const index = this.arraydata.findIndex(x => x.name ===this.selectedDevice);
console.log(index);
this.arraydata.splice(index, 1);
console.log(this.arraydata);
this.fieldArray.push(this.newAttribute)
this.newAttribute = {};
}
deleteFieldValue(index) {
this.fieldArray.splice(index, 1);
}
}
i've worked on your code and here is how i make it work
first you have to change arraydata to an array of arrays
i = 0;
arraydata = [
[
{ name: "name1", value: 1 },
{ name: "name2", value: 2 },
{ name: "name3", value: 3 },
{ name: "name4", value: 4 }
]
];
and here is the add dropdown method
addFieldValue() {
const arr = [...this.arraydata];
this.arraydata.push(
arr[this.i].filter(x => x.name !== this.selectedDevice)
);
this.fieldArray.push(this.newAttribute);
this.newAttribute = {};
this.i += 1;
}
and your html becomes
<tr>
<td>
<select (change)="onChange($event.target.value)">
<option *ngFor="let title of arraydata[0]" [value]="title.name" >{{title.name}}</option>
</select>
</td>
<td>
<button class="btn btn-default" type="button" (click)="addFieldValue()">Add</button>
</td>
</tr>
<tr *ngFor="let field of fieldArray; let i = index">
<td>
<select (change)="onChange($event.target.value)">
<option *ngFor="let title of arraydata[i+1]" [value]="title.name" >{{title.name}}</option>
</select>
</td>
<td>
<button class="btn btn-default" type="button" (click)="deleteFieldValue(i)">Delete</button>
</td>
</tr>
here is the working code https://stackblitz.com/edit/angular-ujyrdr?file=src/app/app.component.html

Vue template doesn't update

I'm building my first project in VueJS, and I'm having trouble getting a template to show/hide using v-if. I have a data model boolean variable (groups.categories.descEditable) that I am toggling to show/hide a template. For some reason the template isn't reactively updating itself when I change that value.
<tbody v-for="group in groups">
...
<tr v-for="cat in group.categories">
...
<td class="td-indent">
<input v-if="cat.descEditable" :value="cat.description" type="text" class="form-control">
<div v-else v-on:click="editDesc(cat.id)">{{ cat.description }}</div>
<div>{{cat.descEditable}}</div>
</td>
...
</tr>
</tbody>
methods: {
editDesc (cat_id) {
let vm = this
this.groups.forEach(function(group, gr_ind){
group.categories.forEach(function(cat, ind) {
if (cat_id == cat.id)
cat.descEditable = true
else
cat.descEditable = false
})
})
}
},
So I would like the text input to show if descEditable is true (once the div containing the description is clicked), otherwise show the div with the static description value. The descEditable property seems to be updating properly, but the v-if on the input element isn't reacting to it. I must be misunderstanding something fundamental to vuejs, just can't figure out what it is.
I think you can ditch the editDesc method entirely.
console.clear()
const groups = [
{
categories:[
{
descEditable: false,
description: "click me"
}
]
}
]
new Vue({
el:"#app",
data:{
groups
}
})
<script src="https://unpkg.com/vue#2.2.6/dist/vue.js"></script>
<div id="app">
<table>
<tbody v-for="group in groups">
<tr v-for="cat in group.categories">
<td class="td-indent">
<div v-if="cat.descEditable">
<input v-model="cat.description" type="text" class="form-control">
<button #click="cat.descEditable = false">Save</button>
</div>
<div v-else #click="cat.descEditable = true">{{ cat.description }}</div>
</td>
</tr>
</tbody>
</table>
</div>

boostrap modal not updating with vue js v2 two way binding

I'm new to vue js and trying to use it with a bootstrap modal to view more data. My scenario is a table with multiple records and a button to see in depth details for the clicked record in a bootstrap modal. After clicking the first button it caches and doesn't update it while selecting another button for different details.
Does anyone see what I'm doing wrong?
(It's a combination of Laravel, jQuery and VueJS)
HTML Table:
<table class="table table-striped">
<thead>
<tr>
<th>E-mail address</th>
<th>Status</th>
<th>Sent at</th>
<th>Expires in</th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td class="v-align-middle">
john#doe.example
</td>
<td class="v-align-middle">
<span class="label label-default">
pending
</span>
</td>
<td class="v-align-middle">
2017-06-05 17:59:15
</td>
<td class="v-align-middle">
29 days
</td>
<td>
<div class="btn-group pull-right">
<a href="#" class="btn btn-default" data-toggle="modal" data-target="#inviteDetailsModal" data-email="john#doe.example">
<i class="fa fa-eye"></i>
</a>
</div>
</td>
</tr>
<tr>
<td class="v-align-middle">
jane#doe.example
</td>
<td class="v-align-middle">
<span class="label label-default">
pending
</span>
</td>
<td class="v-align-middle">
2017-06-05 13:27:25
</td>
<td class="v-align-middle">
29 days
</td>
<td>
<div class="btn-group pull-right">
<a href="#" class="btn btn-default" data-toggle="modal" data-target="#inviteDetailsModal" data-email="jane#doe.example">
<i class="fa fa-eye"></i>
</a>
</div>
</td>
</tr>
</tbody>
JavaScript:
$('[data-target="#inviteDetailsModal"]').on('click', function () {
let email = $(this).data('email'),
baseUrl = $('html').data('base');
Vue.component('invite-details', {
data: function () {
return {
email: null,
token: null,
logs: [],
expires: null
}
},
methods: {
update: function (data) {
this.email = data['email'];
this.token = data['token'];
this.logs = data['logs'];
this.expires = data['expires'];
},
fetchData: function () {
this.$http.get(baseUrl + '/system/invites/' + email + '/details')
.then(response => {
this.update(response.body);
}, response => {
console.log('whoops something went wrong');
});
}
},
mounted: function () {
this.$el.addEventListener('shown.bs.modal', this.fetchData());
},
beforeDestroy: function () {
this.$el.removeEventListener('shown.bs.modal', this.fetchData());
}
});
new Vue({
el: '#inviteDetailsModal'
});
});
The bootstrap modal:
<div class="modal fade slide-up" id="inviteDetailsModal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content-wrapper">
<invite-details inline-template>
<div class="modal-content" id="details">
<div class="modal-header clearfix text-left">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">
<i class="pg-close fs-14"></i>
</button>
<h5>
Invite details for <span class="semi-bold">#{{ email }}</span>
</h5>
<p class="p-b-10">
<span data-tooltip="true" data-placement="bottom" title="token">
<em>#{{ token }}</em>
</span>
</p>
</div>
<div class="modal-body">
<table class="table table-striped">
<thead>
<tr>
<th>#</th>
<th>Sent at</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr v-for="log in logs">
<td>#{{ log.number }}</td>
<td>#{{ log.sentAt }}</td>
<td>#{{ log.status }}</td>
</tr>
</tbody>
</table>
</div>
<div class="modal-footer text-center">
<span class="hint-text">
<em>expires in <strong>#{{ expires }}</strong> days</em>
</span>
</div>
</div>
</invite-details>
</div>
</div>
Alright I dug in deep to get this working and went the extra mile because you mentioned your were new at VueJS. I recognised my old self in your code where you go into VueJS with a JQuery mindset ;-)
First few quick notes before dropping the full code:
Use the table DB row's integer ID to reference the user everywhere.
This way when the email is updated, you still know who it is in the
frontend. By examining the code I saw that the email can change
(because it's an updateable data property, but it's also used in
your GET request: baseUrl + '/system/invites/' + email +
'/details'
Subsequently, you can use this ID to easily generate unique instances
of your modal :-) This is the way you want to be working with VueJS!
Since you're using the same data in multiple places, have a look at
Vuex for a store. It may look daunting at first but it's great once
you get to grips with it. In your case, the same dataset would be
used for the original table and the modals. If one updates,
everything updates!
With Vuex you can trigger updates from anywhere. Right now the data
gets updated every time the eye button is clicked. However, this is
pretty hacky as I've made the button part of the modal's template,
and every time it gets clicked it calls fetchData() (check the
console). What you want to do ideally is use Vuex and generate
everything from the single point of truth dataset. Currently, if the
modal's data is updated, the original table is not.
With VueJS it's just as easy to create your own modal. The upside of
this is less overhead in your code, as you can use v-if, so it
won't be loaded into the DOM unless actually required. And judging
from your current code, the details button would be clicked
occasionally.
Install VueJS debugger if you haven't already:
https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd,
it will help you 'see' under the hood.
Alright so now for the code! First, add this line to your app.js file:
import JaimyTable from './components/stackoverflow/JaimyTable.vue'
Right above the var app = new Vue({ line. And add it to your components, so you end up with something like this:
import JaimyTable from './components/stackoverflow/JaimyTable.vue'
var app = new Vue({
components: {
JaimyTable,
},
});
Here's the JaimyTable.vue file:
<template>
<div class="container">
<table class="table table-striped">
<thead>
<tr>
<th>E-mail address</th>
<th>Status</th>
<th>Sent at</th>
<th>Expires in</th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td class="v-align-middle">
john#doe.example
</td>
<td class="v-align-middle">
<span class="label label-default">
pending
</span>
</td>
<td class="v-align-middle">
2017-06-05 17:59:15
</td>
<td class="v-align-middle">
29 days
</td>
<td>
<div class="btn-group pull-right">
<jaimy-modal id="1"></jaimy-modal>
</div>
</td>
</tr>
<tr>
<td class="v-align-middle">
jane#doe.example
</td>
<td class="v-align-middle">
<span class="label label-default">
pending
</span>
</td>
<td class="v-align-middle">
2017-06-05 13:27:25
</td>
<td class="v-align-middle">
29 days
</td>
<td>
<div class="btn-group pull-right">
<jaimy-modal id="2"></jaimy-modal>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
import JaimyModal from './JaimyModal.vue'
export default {
components: { JaimyModal },
props: [],
mixins: [],
data: function () {
return {
//
}
},
computed: {
computed_variable() {
return '';
}
},
created() {
//
},
mounted() {
//
},
methods: {
//
},
watch: {
//
}
}
</script>
And please note the <jaimy-modal id="1"></jaimy-modal> lines. You probably want to use v-for to generate the all the <tr> rows automatically :) Make sure the id= corresponds to the ID in your database.
Now for the JaimyModal.vue where all the magic happens:
<template>
<div>
<a href="#" class="btn btn-default" data-toggle="modal" :data-target="'#inviteDetailsModal' + id" #click="fetchData()">
<i class="fa fa-eye"></i>
</a>
<div class="modal fade slide-up" :id="'inviteDetailsModal' + id" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content-wrapper">
<div class="modal-content" id="details">
<div class="modal-header clearfix text-left">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">
<i class="pg-close fs-14"></i>
</button>
<h5>
Invite details for <span class="semi-bold">{{ email }}</span>
</h5>
<p class="p-b-10">
<span data-tooltip="true" data-placement="bottom" title="token">
<em>{{ token }}</em>
</span>
</p>
</div>
<div class="modal-body">
<table class="table table-striped">
<thead>
<tr>
<th>#</th>
<th>Sent at</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr v-for="log in logs">
<td>{{ log.number }}</td>
<td>{{ log.sentAt }}</td>
<td>{{ log.status }}</td>
</tr>
</tbody>
</table>
</div>
<div class="modal-footer text-center">
<span class="hint-text">
<em>expires in <strong>{{ expires }}</strong> days</em>
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
props: ['id'],
data: function () {
return {
email: null,
token: null,
logs: [],
expires: null,
baseUrl: 'https://yourbaseurl.com',
}
},
mounted: function () {
//
},
methods: {
update: function (data) {
this.email = data['email'];
this.token = data['token'];
this.logs = data['logs'];
this.expires = data['expires'];
},
fetchData: function () {
console.log('data fetched');
// Commented out for local purposes
// this.$http.get(this.baseUrl + '/system/invites/' + this.email + '/details')
// .then(response => {
// this.update(response.body);
// }, response => {
// console.log('whoops something went wrong');
// });
// Dummy data
this.update({
email: 'test#test ' + this.id + '.com',
token: 'token123123asdsasdasdasd',
logs: [
{
number: 1,
sentAt: '2017-01-01',
status: 'Ok',
},
{
number: 2,
sentAt: '2017-02-01',
status: 'Failed',
},
],
expires: '2017-10-01'
});
}
},
}
</script>
Important to note here is the :data-target="'#inviteDetailsModal' + id" part in the button, where the number corresponds to the id of the prop. By using the : you're making it an expression, and it resolves to a unique reference.
As you can see the setup is completely different than you had before. Where the modal is a nested component of your table row. Once you start thinking of Components as recurring parts of look and functionality, but with unique data within it, things will click fast. Think of it as Model in Laravel.
After a long time of being told that you need to separate design (CSS), mark-up (HTML) and functionality (JS), it's weird to have it all sitting there in 1 file. But embrace it, and you'll fall in love with VueJS :D
And give Vuex a look! The second you get multiple components that use and manipulate the same dataset, it's a god-send!
Ow and finally: the second you start thinking of using things like fn.trigger and whatnot, you're approaching VueJS wrong. Keep that in mind :) It has all the tools necessary for you to work with the page, and then some!
Happy coding!
Since you're using one modal component and are attempting to change its content based on the specific button in the table clicked, the mounted hook is only going to fire once when the component is initially mounted.
What you want is for the content to update each time the modal is shown.
First of, it looks like your component only needs one email prop. Move the rest of the props to be in the data method for the component, since (I'm assuming) they aren't being passed into the component:
props: ['email'],
data: function() {
return {
token: null,
logs: null,
expires: null,
}
}
Then, I would create a new method called fetchData to put the $http.get call in:
fetchData: function() {
this.$http.get('http://localhost:2000/system/invites/' + this.email + '/details')
.then(response => {
this.update(response.body);
}, response => {
console.log('whoops something went wrong');
});
}
In your modal component's mounted hook, add a listener to the bootstrap modal's show event using jQuery. And be sure to remove the listener in the component's beforeDestroy hook:
mounted() {
$(this.$el).on('shown.bs.modal', this.fetchData);
},
beforeDestroy() {
$(this.$el).off('shown.bs.modal', this.fetchData);
}
Now, every time the modal's show event fires, the $http.get request will fire based on the current value of the component's email property.
Here's an example fiddle.

updating data object ractive.js ractive.sortable.js

I'm using https://github.com/Nijikokun/ractive.sortable.js to add drag sortable functionality to a table.
var resources = [
{
rid: "22222225",
title: "Archive.zip",
filename: "222343.zip",
type: "foo",
duration: "10"
},
{
rid: "22222225",
title: "Archive.zip",
filename: "222343.zip",
type: "foo",
duration: "10"
}
...
]
<tbody id="resourceList" on-sortable='sort-items'>
{{#each resources:num}}
<tr rv-each-item="data.resources">
<td>
<i class="dragHandle glyphicon glyphicon-move">M</i>
</td>
<td class="rTitle">{{title}}</td>
<td class="rType">{{filename}}</td>
<td class="rType">{{type}}</td>
<td class="rDuration">
{{#if duration}}
<input type="number" min="10" step="1" value="{{duration}}"> seconds
{{/if}}
</td>
<td>
{{#if resources.length != 1 }}
<button class="btn" type="button" on-click='removeItem'><i class="deleteRow glyphicon glyphicon-trash"></i>
</button>
{{/if}}
</td>
</tr>
{{/each}}
</tbody>
The dragging works as intended - I'm just having difficulty trying to figure out how I extract the new sort order, and use it to update the order of objects in the data array. I've been playing about with ractive.observe - but can't grok it....
You should not need to update the array in any way. The sortable plugin will do that for you.

jsViews - re-evaluate 'if' tag on root property change

I have created a table/grid using jsViews. Each row has an edit button which when clicked selects the row and shows input controls instead of text values.
If I show/hide the inputs using data-link="visible{:#parent.parent.data.selectedIndex!==#index}" then it works fine.
However, I was trying a different approach using {^{if #parent.parent.data.selectedIndex===#index}}...{{else}}...{{/if}} to show/hide the inputs and this doesn't work when selectedIndex changes on my data object.
I also tried with {^{if ~root.selectedIndex===#index}} but that didn't work either. Is it possible to do this with {{if}}? The reason I am trying this over the first method that worked was to avoid rendering lots of select boxes which will just be hidden anyway.
My data object looks like this:
app = {
data: [...],
selectedIndex: null,
select: function select(index) {
if (this.selectedIndex !== index) {
$.observable(this).setProperty("selectedIndex", index);
}
}
};
I link the template like this:
$.templates("#myTemplate").link("#divHolder", app)
.on("click", ".data .editButton", function() {
app.select($.view(this).index);
})
.on("click", ".data .saveButton", function() {
// save details
})
.on("click", ".transmittals .cancelButton", function() {
// reset values
app.select(null);
});
My template is like this:
<script id="myTemplate" type="text/x-jsrender">
<table id="tblData" class="data">
<thead>
<tr>
<th></th>
<th>A</th>
<th>B</th>
<th>C</th>
</tr>
</thead>
<tbody>
{^{for data}}
<tr class="item">
<td>
{{if #parent.parent.data.selectedIndex===#index}}
<span class="editItem">
<button class="cancelButton">Cancel</button></span>
{{else}}
<span class="viewItem">
<button class="editButton">Edit</button></span>
{{/if}}
</td>
<td>
{^{if #parent.parent.data.selectedIndex===#index}}
<span class="editItem"><input type="text" data-link="B" /></span>
{{else}}
<span class="viewItem" data-link="B"></span>
{{/if}}
</td>
<td>
{^{if #parent.parent.data.selectedIndex===#index}}
<span class="editItem"><input type="text" data-link="C" /></span>
{{else}}
<span class="viewItem" data-link="C"></span>
{{/if}}
</td>
</tr>
{{/for}}
</tbody>
</table>
</script>
When you add an {{if}} block, it is a nested view, so the button click is not getting you the item view with the index. You need to use $.view(this).parent.index - or, simpler, $.view(this).getIndex() - which automatically steps up through nested views (if any) to the item view and gets its index.
app.select($.view(this).getIndex());
(See discussion here: https://github.com/BorisMoore/jsrender/issues/173#issuecomment-11058106)
BTW here is a modified form of your sample, just to give you some ideas. It uses <button data-link="{on ~root.select #getIndex()}">Edit</button> to hook up the click handler on the button and call the select method directly, passing it the index:
<script id="myTemplate" type="text/x-jsrender">
<table id="tblData" class="data">
<thead>
...
</thead>
<tbody>
{^{for data}}
<tr class="item">
{^{if ~root.selectedIndex===#index}}
<td><button class="cancelButton" data-link="{on ~root.select null}">Cancel</button></td>
<td><input data-link="A" /></td>
<td><input data-link="B" /></td>
{{else}}
<td><button class="editButton" data-link="{on ~root.select #getIndex()}">Edit</button></td>
<td data-link="A"></td>
<td data-link="B"></td>
{{/if}}
</tr>
{{/for}}
</tbody>
</table>
</script>
<div id="divHolder"></div>
<script>
var app = {
data: [{A:"aa", B: "bb"},{A:"aa2", B: "bb2"}],
selectedIndex: null,
select: function(index) {
if (this.selectedIndex !== index) {
$.observable(this).setProperty("selectedIndex", index);
}
}
};
$.templates("#myTemplate").link("#divHolder", app);
</script>

Categories