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.
I want to send the selected row's data on click of a button to back-end.
I could achieve that by having a 'Move' button for each row of the table, but my use case is different. I have a single button on the top of the table and I have to select the row first and then click on the 'Move' button to send the selected row's data to back-end.
I am using Smart-table module for data grid.
Here is my HTML code
<body ng-controller="mainCtrl">
<div class="table-container">
<button type="button" ng-click="moveToSafe(row)" class="btn btn-sm btn-success">
Move to safe
</button>
<table st-table="rowCollection" class="table">
<thead>
<tr>
<th st-sort="firstName">first name</th>
<th st-sort="lastName">last name</th>
<th st-sort="birthDate">birth date</th>
<th st-sort="balance">balance</th>
</tr>
</thead>
<tbody>
<tr st-select-row="row" ng-repeat="row in rowCollection">
<td>{{row.firstName | uppercase}}</td>
<td>{{row.lastName}}</td>
<td>{{row.birthDate | date}}</td>
<td>{{row.balance | currency}}</td>
</tr>
</tbody>
</table>
</div>
<div ng-show="isLoading" class="loading-indicator"></div>
</body>
My JS code
angular
.module('myApp', ['smart-table'])
.controller('mainCtrl', ['$scope',
function($scope) {
$scope.isLoading = false;
$scope.rowCollection = [{
firstName: 'Laurent',
lastName: 'Renard',
birthDate: new Date('1987-05-21'),
balance: 102
}, {
firstName: 'Blandine',
lastName: 'Faivre',
birthDate: new Date('1987-04-25'),
balance: -2323.22
}, {
firstName: 'Francoise',
lastName: 'Frere',
birthDate: new Date('1955-08-27'),
balance: 42343
}];
$scope.moveToSafe = function(row) {
console.log(row);
// I want the selected row data here inside 'row'
};
}
]);
Change this variable names according to your variables.
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<script>
angular.module('myApp', []).controller('namesCtrl', function($scope) {
$scope.isLoading = false;
$scope.checkedData = [];
$scope.rowCollection = [{
firstName: 'Laurent',
lastName: 'Renard',
birthDate: new Date('1987-05-21'),
balance: 102
}, {
firstName: 'Blandine',
lastName: 'Faivre',
birthDate: new Date('1987-04-25'),
balance: -2323.22
}, {
firstName: 'Francoise',
lastName: 'Frere',
birthDate: new Date('1955-08-27'),
balance: 42343
}];
$scope.collectedData = function(val){
$scope.checkedData.push(val);
}
$scope.moveToSafe = function() {
console.log($scope.checkedData)
};
})
</script>
</head>
<body ng-app="myApp" ng-controller="namesCtrl">
<div class="table-container">
<button type="button" ng-click="moveToSafe()" class="btn btn-sm btn-success">
Move to Safe
</button>
<table st-table="rowCollection" class="table">
<thead>
<tr>
<th st-sort="firstName">first name</th>
<th st-sort="lastName">last name</th>
<th st-sort="birthDate">birth date</th>
<th st-sort="balance">balance</th>
<th>Select </th>
</tr>
</thead>
<tbody>
<tr st-select-row="row" ng-repeat="row in rowCollection">
<td>{{row.firstName | uppercase}}</td>
<td>{{row.lastName}}</td>
<td>{{row.birthDate | date}}</td>
<td>{{row.balance | currency}}</td>
<td><input type="checkbox" ng-modal="checkedData" ng-click="collectedData(row)"></td>
</tr>
</tbody>
</table>
</div>
</body>
You should place button inside the ng-repeat, then only row variable will hold its value
<tr st-select-row="row" ng-repeat="row in rowCollection">
<td>{{row.firstName | uppercase}}</td>
<td>{{row.lastName}}</td>
<td>{{row.birthDate | date}}</td>
<td>{{row.balance | currency}}</td>
<button type="button" ng-click="moveToSafe(row)" class="btn btn-sm btn-success">
Move to safe
</button>
</tr>
I have a small web application which uses both laravel and stripe.. In my view I give the ability to the logged in user to top up their account. On the view I have 5 packages.
1 Credit
10 Credits etc
I've got stripe to take the right amount from the users card which is great, however i'm struggling passing the data-quantity through to my controller which would add the credits.
View (Just for example I've included just the one package)
<div class="col-sm-6 col-lg-2">
<!-- Small Package -->
<div class="block block-bordered text-center">
<div class="block-header">
<h3 class="block-title">Super!</h3>
</div>
<div class="block-content block-content-full bg-primary-dark">
<div class="h1 font-w700 text-white push-10">200</div>
<div class="h5 font-w300 text-white">Credits</div>
</div>
<div class="block-content">
<table class="table table-borderless table-condensed">
<tbody>
<tr>
<td><strong>£1.80</strong> each <small class="text-muted">ex VAT</small></td>
</tr>
</tbody>
</table>
</div>
<div class="block-content bg-gray-lighter">
<table class="table table-borderless table-condensed">
<tbody>
<tr>
<td>
<button id="" class="btn btn-block btn-success btn-default customButton" data-price="43200" data-credits="200" data-title="200 x Credits">Pay by Card</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- END Small Package -->
</div>
Javascript handling the click event which captures the data-attr against each button.. i.e. the value, the title and quantity.
<script>
var handler = StripeCheckout.configure({
key: 'pk_test_code',
image: '{{ url() }}/assets/img/stripe-icon.jpg',
locale: 'auto',
token: function(token) {
$.post('/call/topup', {token: token}, function(data) {
alert('TOPPED UP!');
});
}
});
$('.customButton').on('click', function(e) {
handler.open({
description: $(this).attr('data-title'),
currency: "gbp",
amount: $(this).attr('data-price')
});
e.preventDefault();
});
// Close Checkout on page navigation
$(window).on('popstate', function() {
handler.close();
});
</script>
Controller which handles the request and charges the card.
public function topup(Request $request)
{
$token = $request->token['id'];
$user = User::FindOrFail(1);
$user->charge(6000, [
'source' => $token,
'currency' => 'gbp',
'receipt_email' => $user->email,
]);
return $request->token;
}
I haven't yet included the topping up the account because I can't figure out how to get the data-quantity to the controller :( help.
I have a print directive in a SPA that seems to lose its 2 way data binding after the first print function is ran. I am sure it has something to do with the cloning and appending of the element.
The Directive is as follows:
agency.directive("pasPrint", ['AgentModel','Dev',
function(agentModel, dev) {
var printSection = document.getElementById("printSection");
function printElement(elem) {
// clones the element you want to print
var domClone = elem.cloneNode(true);
if (!printSection) {
printSection = document.createElement("div");
printSection.id = "printSection";
document.body.appendChild(printSection);
} else {
printSection.innerHTML = "";
}
printSection.appendChild(domClone);
}
return {
restrict: 'A',
link: function($scope, $element, attrs) {
$scope.printModel = {};
$scope.today = new Date().toJSON().slice(0,10);
$element.on("click", function () {
var elemToPrint = document.getElementById(attrs.printElementId);
if (elemToPrint) {
printElement(elemToPrint);
window.print();
}
});
},
controller: ["$scope","$element", "AgentModel", function($scope, $element, agentModel) {
$scope.agentModel = agentModel;
}]
};
}]);
the model that is used in the entire module here is the agentModel.singleAgent which of course represents a single agent. this directive simply prints a cover sheet for an agents hard copy record. the first time we pull up an agents record the cover sheet prints correctly. when we load another agent the print preview show's the correctly updated agent information. in the agentModel.singleAgent 2 way binding (the same values being used in the printSection that gets printed. However the second print attempt also prints the first agent information and so does any other attempt to print any other agents, they all print the first agents data, even thought the singleAgent model has been updated properly.
the html from the print preview is below:
<div class="modal-header ng-scope">
<button type="button" class="close" aria-label="Close" data-ng- click="cancel()"><span aria-hidden="true"> × </span></button>
<h3 class="modal-title ng-binding"> Edit & Print Cover Sheet </h3>
</div>
<div class="modal-body">
<form class="inline-form">
<div class="form-group">
<label class="form-label" for="PRINT_DESC">File Description: </label>
<input class="input-medium" type="text" id="PRINT_DESC" name="PRINT_DESC" data-ng-model="printModel.PRINT_DESC" />
</div>
</form>
<div id="printPreview">
<table class="table table-cell-nowrap table-responsive">
<thead>
<tr>
<th colspan="2"><strong>Type: <em>{{printModel.PRINT_DESC}}</em></strong></th>
<th colspan="2" style="border: 1px #dadada dotted"><div class="clearfix pull-right"><strong>{{agentModel.singleAgent.AGENT_NUMBER}}</strong></div></th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Agent Name: {{ agentModel.singleAgent.AGENT_LNAME }}, {{ agentModel.singleAgent.AGENT_FNAME }} </strong></td>
<td><strong>Agent #: {{ agentModel.singleAgent.AGENT_NUMBER }}</strong></td>
<td><strong>UID: {{ agentModel.singleAgent.AGENT_UID }}</strong></td>
<td></td>
</tr>
<tr><td colspan="4">Printed On: {{ today }} </td></tr>
</tbody>
</table>
</div>
<div id="printSection" class="col-md-12"><!-- place nothing in here except what should be printed. by default the content in the print section IS hidden -->
<table class="table table-cell-nowrap table-responsive printCoverFontSize">
<thead>
<tr>
<th colspan="2"><strong>Type: <em>{{printModel.PRINT_DESC}}</em></strong></th>
<th colspan="2" style="border: 1px #dadada dotted"><div class="clearfix pull-right"><strong>{{agentModel.singleAgent.AGENT_NUMBER}}</strong></div></th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Agent Name: {{ agentModel.singleAgent.AGENT_LNAME }}, {{ agentModel.singleAgent.AGENT_FNAME }} </strong></td>
<td><strong>Agent #: {{ agentModel.singleAgent.AGENT_NUMBER }}</strong></td>
<td><strong>UID: {{ agentModel.singleAgent.AGENT_UID }}</strong></td>
<td></td>
</tr>
<tr><td colspan="4">Printed On: {{ today }} </td></tr>
</tbody>
</table>
</div>
</div>
<div class="modal-footer">
<div class="pull-left">
<button class="btn btn-warning" data-pas-print data-print-element-id="printSection"><i class="fa fa-print fa-lg"></i> Print </button>
</div>
<div class="pull-right">
<button class="btn btn-default" data-ng- click="cancel()">Cancel</button>
</div>
the printModel model simply takes the value from the text box and includes it to be printed, just a descriptive term.
I am at a loss as to why this is not working. I did just piece this together from other scripts I found online to work with our application. I am a bit of an angular Newb and I really appreciate any help I can get so thank you in advance. please if you need more information feel free to ask. If I had to guess I would say it has something to do with cloning and the emptying of the element if I had to guess, but I am at a loss as to how to fix it. HELP please.
First of all I want to apologize for my bad English :)
I am having a problem with AngularJS, when I want to load a modal from datagrid.
Here is my code for the grid:
<tr ng-repeat="data in filtered = (list | filter:search | orderBy : predicate :reverse) | startFrom:(currentPage-1)*entryLimit | limitTo:entryLimit track by $index">
<td>{{data.lastName}} {{data.firstName}}</td>
<td>{{data.email}}</td>
<td>{{data.phoneNumber}}</td>
<td>{{data.cityID}}</td>
<td><button class="btn btn-primary btn-xs" ng-click="show(data.id)">DETAILS</button></td>
Modal code :
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h4 class="modal-title"><strong>{{row.firstName}} {{row.lastName}}</strong> Details</h4>
</div>
<div class="modal-body">
<table class="table table-condensed table-hover info-table">
<tbody>
<tr>
<td>Email:</td>
<td>{{row.email}}</td>
</tr>
</tbody>
</table>
</div>
</div>
and JS code:
$scope.show = function (id) {
$http.get('inc/getDetails.php?id='+id).
success(function(data) {
$scope.row = data;
$('#det-modal').modal('show');
});
};
The modal is loading, but does not load the data.
The code would not work because the row variable is out of scope when the modal is invoked as its in non-angular world.
You should not access the DOM element directly in controller so the following line should not be used in angular world.
$('#det-modal').modal('show');
To fix the issue consider using https://angular-ui.github.io/bootstrap/ which would enable you to pass in the row as a scope variable.