boostrap modal not updating with vue js v2 two way binding - javascript

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.

Related

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>

Laravel Stripe Integration Passing Variable

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.

angularJS print directive losing 2 way binding

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.

Need help using jquery .find() to dynamically change span text in a dialog window

I need to dynamically set the values of the span tags in a dialog window. The way I am doing this is by going through an object array and matching the key of the object array to the class names in the dialog window (see html below). Once matched I do a .find(selector) and change the value of that span. The problem is that the
var myObsModal = $("#observationModal").html().trim();
$myObservationModal = $(myObsModal);
$myObservationModal.find(key).text(value);
is not working. If I just set the text based on an id it works fine:
$("#observationModalName").text(thisObservationDataObject['heading']);
Below is the complete javascript. Note I have checked that the class in the html matches the keys in the data object.
//allObservationArray is an array of observationDataObject
var observationDataObject = {
heading: '',
headingid: '',
timestamp: '',
userid: '',
status: '',
fileSize: '',
priorityLevel: '',
comment: '',
tags: [],
photoFiles: [],
videoFiles: [],
audioFiles: [],
photoCount: '',
videoCount: '',
audioCount: ''
};
var $myObservationModal
/**
* Setup the modal with the selected observation information
* #param {type} observationSelected
* #returns {undefined}
*/
function setupObsModal(observationSelected) {
var myObsModal = $("#observationModal").html().trim();
$myObservationModal = $(myObsModal);
//Note that thisObservationDataObject has the structure of the observationDataObject shown above
var thisObservationDataObject = allObservationArray[observationSelected];
for (var key in thisObservationDataObject) {
if (thisObservationDataObject.hasOwnProperty(key)) {
console.log(key + " => " + thisObservationDataObject[key]);
if (key === "comment") {
$myObservationModal.find('.observationModal').val(thisObservationDataObject[key]);
}else if (key === "heading") {
addDataToObsModal('#observationModalName', thisObservationDataObject[key]);
$myObservationModal.find('#observationModalName').attr("id", thisObservationDataObject[key]);
}else {
addDataToObsModal('.' + key, thisObservationDataObject[key]);
}
}
}
//Testing. This works
$("#modalTimeStamp").text(thisObservationDataObject.timestamp);
//either approach works
$("#observationModalName").text(thisObservationDataObject['heading']);
//end testing
$("#observationModal").modal('show');
}
function addDataToObsModal(key, value) {
$myObservationModal.find(key).text(value); //<================Not working
}
Just for completeness here is a small portion of the html modal
<div class="modal fade" id="observationModal" tabindex="-1" role="dialog" aria-labelledby="basicModal" aria-hidden="true" data-backdrop="static">
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true" data-backdrop="true">×</button>
<h4 class="modal-title" id="observationModalName">Observation Name</h4>
</div>
<div class="modal-body">
<div id="fullView" class="container-fluid">
<div class="row">
<!-- Left column -->
<div class="col-md-3">
<table class="table table-condensed table-hover ">
<tbody>
<tr>
<td>Time & Date:</td>
<td><span class="timestamp" id="modalTimeStamp"> </span></td>
</tr>
<tr>
<td>File Size (KB):</td>
<td><span class="fileSize" id="modalFileSize"> </span></td>
<tr>
<td> User:</td>
<td><span class="userid" id="modalUserID"> </span></td>
</tr>
<tr>
<td>Priority Level:</td>
<td><span class="priorityLevel" id="modalPriorityLevel"> </span></td>
</tr>
<tr>
<td>Status:</td>
<td><span class="status"> </span></td>
</tr>
<tr>
<td>Mission</td>
<td><span class="mission"> </span></td>
</tr>
<tr>
<td>Sch/Bonus:</td>
<td><span class="schBonus"> </span></td>
</tr>
<tr>
<td>Datatype:</td>
<td><span class="datatype"> </span></td>
</tr>
</tbody>
</table>
.....
UPDATE
Using log statements I have discovered that the text is being set
console.log("here is the key and value: " + key + " => " + value);
console.log("$myObservationModal.find(key).attr(class) " + $myObservationModal.find(key).attr('class'));
$myObservationModal.find(key).text(value);
console.log(" $myObservationModal.find(key).text() " + $myObservationModal.find(key).text());
So the problem appears to occur after leaving the function. I wonder if
$("#observationModal").modal('show');
is not being updated when $myObservationModal is changed
The two code snippets at the beginning of your question do completely different things.
This one selects the actual DOM element with the id observationModalName and changes its content in your page:
$("#observationModalName").text( thisObservationDataObject['heading'] );
This one gets the HTML code (a text string) for the observationModal element, then creates a temporary DOM object ($myObservationModal) which is not part of your actual page content, and modifies that temporary object. There is no connection between the temporary DOM object and your page content, so this has no effect on the page itself:
var myObsModal = $("#observationModal").html().trim();
$myObservationModal = $(myObsModal);
$myObservationModal.find(key).text(value);
Perhaps what you want to do here is something like this:
$("#observationModal").find(key).text(value);
That will modify the actual page content.

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