I have several components on a page, and I find that updates to a parent object are reflected in one of the components, but not in the other.
The main page, PatientEditor.vue includes these components:
<notes-editor v-model="pt" />
<chart-completion v-model="pt" arrange="horiz" />
and has this script:
module.exports = {
props: ["pt"]
};
So, the pt object is in the parent, and it is passed to several components as a v-model
The component ChartCompletion.vue works well. It has these in it.
module.exports = {
props: ["value", "arrange"],
computed: {
completed_notes: function() {
return this.value.notes.filter(function(note) {
return note.signed_at;
}).length;
},
My problem child, however is the NotesEditor.vue template which contains the following:
module.exports = {
props: ["value"],
computed: {
completed_notes: function() {
return this.value.notes.filter(function(note) {
return note.signed_at;
}).length;
}
},
Not sure if it's important, but the notes object is populated from an ajax call in another component like this:
this.value.notes.splice(0, this.value.notes.length, ...response.body.notes);
this.$emit("input", this.value);
Sooooo, here's the problem.
When this.value.notes is updated, the results are seen in the ChartCompletion component, but they are not seen in the NotesEditor component. When I use the Vue debugger in chrome, I can see that the notes object is changed, but, for some reason, the computed property doesn't re-fire, even though it has the identical definition in the ChartCompletion component. Also, I have a v-for in NotesEditor that doesn't change either.
What is the best way to debug this?
EDIT 1 -- including the NotesEditor component
<template>
<span>
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Status</th>
<th>Audio</th>
<th>Text</th>
</tr>
</thead>
<tbody>
<tr v-for="n in value.notes" :key="n.id">
<th>{{n.created_at}}</th>
<td>{{n.note_status_id}}</td>
<td>
<span v-if="n.audio_length > 0">
<audio controls="controls" preload="none">
<source :src="audioURL(n.id)" type="audio/webm"> Your browser does not support the audio element.
</audio>
({{n.audio_length}}s)
</span>
<span v-else>
None
</span>
</td>
<td>
<span v-if="n.note_text">
<button data-toggle="tooltip" :title="n.note_text" class="btn btn-outline-primary btn-sm" #click.prevent="openChartEditor(n.id)">
<span class="glyphicon glyphicon-edit"></span> Edit Note ({{ n.note_text.length }} chars)
</button>
</span>
<span v-else>
<button class="btn btn-primary btn-sm" #click.prevent="openChartEditor(n.id)">
<span class="glyphicon glyphicon-pencil"></span> Create Note
</button>
</span>
</td>
</tr>
<tr>
<td colspan="3">
<record v-model="value" />
</td>
<td>
<button class="btn btn-primary" #click.prevent="openChartEditor(0)">
<span class="glyphicon glyphicon-pencil"></span> Create Note
</button>
</td>
</tr>
</tbody>
</table>
</span>
</template>
<script>
module.exports = {
props: ["value"],
data: function() {
return {
sendFaxWorking: false
};
},
computed: {
completed_notes: function() {
return this.value.notes.filter(function(note) {
return note.signed_at;
}).length;
}
},
methods: {
audioURL: function(id) {
return "/notes/getAudio/" + id;
},
openChartEditor: function(id) {
this.$root.$emit("showEditor", id);
}
}
};
</script>
<style>
audio {
vertical-align: middle;
border: 0;
margin: 0;
}
</style>
I don't think this is a good answer, but it does work. I added a watcher function that calls $forceUpdate()
mounted: function() {
this.$watch("value.notes", this.$forceUpdate);
},
Related
I'm trying to use this code hosting on Firebase, but it doesn't work. {{Item.name}} appears instead of the value :(
I already tested the same code on Codepen and it worked. Does the firebase accept vue.min.js?
When deploying, the site displays the {{var}} instead of the table value in Google Sheets.
I'm trying to use this code hosting on Firebase, but it doesn't work. {{Item.name}} appears instead of the value :(
I already tested the same code on Codepen and it worked. Does the firebase accept vue.min.js?
When deploying, the site displays the {{var}} instead of the table value in Google Sheets.
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<script>
var app = new Vue({
el: '#app',
mounted() {
let vm = this
axios
.get(
'https://sheets.googleapis.com/v4/spreadsheets/{sheetsID}/values/A2:C20?key={apiKey}'
)
.then(function (response) {
let specials = response.data.values
for (let index = 0; index < specials.length; index++) {
const element = specials[index]
let mitem = {
name: element[0],
description: element[1],
price: element[2]
}
if (vm.isEven(index)) {
vm.menuItems_L = vm.menuItems_L.concat(mitem)
} else {
vm.menuItems_R = vm.menuItems_R.concat(mitem)
}
}
console.log(response)
})
},
data: {
menuItems_L: [],
menuItems_R: [],
menuStyle: {
background: '#f2f2f2',
color: '#000'
}
},
computed: {},
methods: {
isEven: function (n) {
return n % 2 == 0
}
}
});
</script>
<body>:
<div id="app">
<section id="specialssection" class="specials-container" v-if="menuItems_L" :style="menuStyle">
<div id="special_component" :style="menuStyle">
<div class="specials-table-container">
<table>
<tbody v-for="item in menuItems_L" :key="item.name">
<tr class="nameandprice">
<td>
<span :style="menuStyle">{{item.name}}</span>
</td>
<td>
<span :style="menuStyle">R${{item.price}}</span>
</td>
</tr>
<tr class="description">
<td colspan="2">{{item.description}}</td>
</tr>
</tbody>
</table>
<table>
<tbody v-for="item in menuItems_R" :key="`specialmenu-${item.name}`">
<tr class="nameandprice">
<td>
<span :style="menuStyle">{{item.name}}</span>
</td>
<td>
<span :style="menuStyle">${{item.price}}</span>
</td>
</tr>
<tr class="description">
<td colspan="2">{{item.description}}</td>
</tr>
</tbody>
</table>
</div>
</div>
</section>
</div>
It looks like the only thing wrong is the order of the tags.
You just need to run the vue code after the <div id="app"> tag is loaded into the DOM. Here's an example:
<html>
<head>
<!-- Include all CDN scripts here -->
</head>
<body>
<div id="app" >
</div>
<script>
// Needs to be called after the <div id="app"> tag is loaded into the DOM
var app = new Vue({
el: '#app',
...
})
</script>
</body>
</html>
I am facing a problem in deleting item from an array. Array splice supposed to work but its not working like I want. Its always delete the item from last. I am using Vue.js . I am pushing item dynamically to an array. But after click remove its delete from the last. why I am facing this. I am attaching the codes.
<template>
<div>
<span class="badge badge-pill mb-10 px-10 py-5 btn-add" :class="btnClass" #click="addBtn"><i class="fa fa-plus mr-5"></i>Button</span>
<div class="block-content block-content-full block-content-sm bg-body-light font-size-sm" v-if="buttons.length > 0">
<div v-for="(item, index) in buttons">
<div class="field-button">
<div class="delete_btn"><i #click="remove(index)" class="fa fa-trash-o"></i></div>
<flow-button v-model="item.title" :showLabel="false" className="btn btn-block min-width-125 mb-10 btn-border" mainWrapperClass="mb-0" wrapperClass="pt-0" placeholder="Button Title"></flow-button>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import flowButton from '../assets/flow-button'
export default {
name: "textArea",
props:{
index : Number
},
data() {
return {
buttons : [],
btnClass : 'badge-primary',
}
}
components : {
flowButton
},
methods : {
addBtn () {
if(this.buttons.length >= 2) {
this.btnClass = 'btn-secondary'
}
if(this.buttons.length < 3) {
this.buttons.push({
title : ''
});
}
},
remove(index) {
this.buttons.splice(index, 1)
}
}
}
</script>
This must be because of your flow-button I have tried to replicate your error but endup to this code. I just replaced the flow-button with input and it works. Try the code below.
Use v-bind:key="index", When Vue is updating a list of elements rendered with v-for, by default it uses an “in-place patch” strategy. If the order of the data items has changed, instead of moving the DOM elements to match the order of the items, Vue will patch each element in-place and make sure it reflects what should be rendered at that particular index. This is similar to the behavior of track-by="$index"
You have missing comma between data and components, I remove the component here it won't cause any error now, and more tips don't mixed double quotes with single qoutes.
<template>
<div>
<span class="badge badge-pill mb-10 px-10 py-5 btn-add" :class="btnClass" #click="addBtn"><i class="fa fa-plus mr-5"></i>Button</span>
<div class="block-content block-content-full block-content-sm bg-body-light font-size-sm" v-if="buttons.length > 0">
<div v-for="(item, index) in buttons" v-bind:key="index">
<div class="field-button">
<div class="delete_btn"><i #click="remove(index)" class="fa fa-trash-o">sdfsdff</i></div>
<input type="text" v-model="item.title" :showLabel="false" className="btn btn-block min-width-125 mb-10 btn-border" mainWrapperClass="mb-0" wrapperClass="pt-0" placeholder="Button Title"/>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'textArea',
props: {
index: Number
},
data () {
return {
buttons: [],
btnClass: 'badge-primary'
}
},
methods: {
addBtn () {
if (this.buttons.length >= 2) {
this.btnClass = 'btn-secondary'
}
if (this.buttons.length < 3) {
this.buttons.push({
title: ''
})
}
},
remove (index) {
this.buttons.splice(index, 1)
}
}
}
</script>
I think that you may be facing a conflict with the index prop of your component. Try to use a different name for the index of your v-for loop:
<div v-for="(item, ind) in buttons">
<div class="field-button">
<div class="delete_btn"><i #click="remove(ind)" class="fa fa-trash-o"></i></div>
<flow-button v-model="item.title" :showLabel="false" className="btn btn-block min-width-125 mb-10 btn-border" mainWrapperClass="mb-0" wrapperClass="pt-0" placeholder="Button Title"></flow-button>
</div>
</div>
Try this. Removing an item correctly using this.
<div v-for="(item, ind) in buttons" :key="JSON.stringify(item)">
I'm trying to route from an index list of items to a page that will display a detailed view of that item.
In my index view I have a table that iterates through all the items that are saved in the database.
There is a button under the actions column that will take me to events/show route using ng-click="go('events/show')"
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>Title</th>
<th class="col-md-2">Actions</th>
</tr>
</thead>
<tbody>
<tr scope="row" ng-repeat="event in events | reverse | filter:filterByUID">
<td>{{event.title}}</td>
<td class="col-md-2">
<div class="btn-group" role="group" aria-label="actions">
<button class="btn btn-primary" ng-click="go('events/show')">
<span class="glyphicon glyphicon-eye-open" aria-hidden="true"></span>
</button>
<button class="btn btn-primary" ng-click="events.$remove(event)">
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
</button>
</div>
</td>
</tr>
</tbody>
</table>
The table looks like this:
In my controller I have:
$scope.go = function ( path ) {
$location.path( path );
};
in my routes.js I have:
.whenAuthenticated('/events/show', {
templateUrl: 'views/eventShow.html',
controller: 'eventShowCtrl'
})
Everything works so far.
However, what is unclear to me is how do I pass the event id to the eventShow.html page, so I know which item was clicked from the index list, so I can display the detailed information?
My firebase database looks like this:
Check out ui-router, it makes dynamic routing much easier
https://github.com/angular-ui/ui-router
But if you want to keep what you have, you should pass the event id into your path, like such
$scope.go = function ( path, event ) {
$location.path( path + "/" + event.id );
};
.whenAuthenticated('/events/show/:eventId', {
templateUrl: 'views/eventShow.html',
controller: 'eventShowCtrl'
})
and in your controller, access $stateParams.eventId to load that event.
You should use a variable in your router:
.whenAuthenticated('/events/:id', {
templateUrl: 'views/eventShow.html',
controller: 'eventShowCtrl'
})
Then you can simply use the ID in your function call:
go('events/:id')
Here's a great tutorial (and I highly recommend watching all of both parts).
And you'll have nicer URLs that can be bookmarked.
One you could pass the UID(uid is just an example for user id) onClick
<tr scope="row" ng-repeat="event in events | reverse | filter:filterByUID">
<td>{{event.title}}</td>
<td class="col-md-2">
<div class="btn-group" role="group" aria-label="actions">
<button class="btn btn-primary" ng-click="go('events/show', event.UID)">
<span class="glyphicon glyphicon-eye-open" aria-hidden="true"></span>
</button>
<button class="btn btn-primary" ng-click="events.$remove(event)">
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
</button>
</div>
</td>
</tr>
Then in your js file
$scope.go = function ( path, uid ) {
$location.path( path + "/" + uid );
};
.whenAuthenticated('/events/show/:eventId', {
templateUrl: 'views/eventShow.html',
controller: 'eventShowCtrl'
})
Then to query firebase, say you have a field in your objects called uid, you can use startAT and endAT methods.
See here for example
And here to read more on filtering
Im trying to use jquery to manipulate elements created by angular, but I am not able to get it to work. I was wondering if anyone could help me out. Thanks
Here is the HTML
<div class="patients">
<tbody ng-repeat="patient in patients">
<tr>
<td>{{patient.name}}</td>
<td>{{patient.number}}</td>
<td>{{patient.date}}</td>
<td id="item-{{$index}}">{{patient.reminded}}</td>
<div class="sendreminder">
<td>
<a href="" class="btn btn-info btn-sm sendreminder" style=" background-color: #00e699; border-color:#00e699; " ng-click="post($index) " "$parent.selected = $index" id="button-{{$index}}">
<span class="glyphicon glyphicon-send"></span> Request Payment
</a>
</td>
</div>
<td>
<a href="" style="text-decoration:none; color:inherit; scale: 4" class="pe-7s-info">
</a>
</td>
</tr>
</tbody>
</div>
Here is the jquery
$(function() {
$('.patients').on('click', ".sendreminder",function(e){
alert('worked');
});
});
ng-repeat recreates DOM everytime it detects changes(and hence, all the attached events will be gone). So to reattach the events after ng-repeat finishes, you can do
<tbody ng-repeat="patient in patients" ng-init="$last && ngRepeatFinish()">
$last will be set to true if its the last item for ng-repeat
and in you controller, create ngRepeatFinish() function
$scope.ngRepeatFinish = function(){
$('.sendreminder').click(function(e){
alert('worked');
});
}
you can also make custom directives for this which is better than this, but this will suffice for a quick solution.
See this for a solution with custom directives
You should call that code immediately after you dynamically create the new element since that code sets the handler for the actual elements (when you call the function) that have class .patients, not the new ones...
i recommend you to use Angular instead of Jquery
added both methods below
//using Jquery
$('.patients').on('click', ".sendreminder", function(e) {
alert('from JQuery');
});
function TestCtrl($scope) {
$scope.patients = [{
name: 'one',
number: 1,
date: '2016-08-16',
reminded: true
}, {
name: 'two',
number: 2,
date: '2016-08-16',
reminded: true
}, {
name: 'three',
number: 3,
date: '2016-08-16',
reminded: false
}];
//using angular
$scope.post = function(i) {
alert('from Angular');
var selectedPatient = $scope.patients[i];
console.log(selectedPatient);
};
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div ng-app>
<div class="patients" ng-controller="TestCtrl">
<table>
<thead>
<tr>
<th>Name</th>
<th>Number</th>
<th>Date</th>
<th>Reminded</th>
<th>Request</th>
<th>Info</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="patient in patients">
<td>{{patient.name}}</td>
<td>{{patient.number}}</td>
<td>{{patient.date}}</td>
<td id="item-{{$index}}">{{patient.reminded}}</td>
<td>
<a href="" class="btn btn-info btn-sm sendreminder" style="background-color: #00e699; border-color:#00e699;" ng-click="post($index)" id="button-{{$index}}">
<span class="glyphicon glyphicon-send"></span> Request Payment
</a>
</td>
<td>
<a href="" style="text-decoration:none; color:inherit; scale: 4" class="pe-7s-info">test
</a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
I am playing with Ember, and building a basic contact management app to learn Ember. I am following the Emberjs getting started guide. Only instead of doing a "to-do" app, Im doing my own thing in hopes of picking it up better.
My Router, and Routes:
App.Router.map(function() {
this.resource('users', function() {
this.resource('user', { path: ':user_id' });
this.route('motoDigitalTrue');
});
this.resource('about');
});
App.UsersRoute = Ember.Route.extend({
model: function() {
return App.User.find();
}
});
App.UsersMotoDigitalTrueRoute = Ember.Route.extend({
model: function(){
return App.User.filter(function(user) {
if (user.get('motoDigital')) {
return true;
}
});
},
renderTemplate: function(controller) {
this.render('users', {
controller:controller
});
}
});
Essentially, I have a template named 'users' that I want to reuse. This template lists all the users. I have a sorting button that when clicked, will only display the users who have the motoDigitalTrue property set to true. The sorting is correct, but it just displays another Users template, rather than re-populating the original.
My Users template:
<script type="text/x-handlebars" id="users">
<div class="span10 tableContainer">
<button class="btn btn-primary createUser" {{action createUser}}><i class="icon-plus icon-white"></i> Add a Contact</button>
<div class="btn-group">
<a class="btn dropdown-toggle" data-toggle="dropdown" href="#">Sort<span class="caret"></span></a>
<ul class="dropdown-menu">
{{#linkTo 'users.motoDigitalTrue' activeClass="selected"}}Receiving MOTO Digital{{/linkTo}}
</ul>
</div>
<div class="tableScrollable">
<table class="table table-striped">
<thead>
<tr>
<th class="nameHead">Name</th>
<th class="companyHead">Company</th>
<th class="emailHead">Email</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"> </td>
<td class="company"> </td>
<td class="email"> </td>
</tr>
{{#each model}}
<tr>
<td class="name"><i class="icon-user"></i> <strong>{{#linkTo 'user' this }}{{firstName}} {{lastName}}{{/linkTo}}</strong></td>
<td class="company">{{company}}</td>
<td class="email"><i class="icon-envelope"></i> <a {{bindAttr mailto="email"}}>{{email}}</a></td>
</tr>
{{/each}}
</tbody>
</table>
</div>
</div>
<div class="span3">
{{#if isCreateUser}}
<div class="well">
{{partial 'users/createUser'}}
<button {{action 'saveUser'}} class="btn btn-primary"><i class="icon-ok icon-white"></i> Save</button>
</div>
{{else}}
{{outlet}}
{{/if}}
</div>
</script>
I have been unable to find an answer, and any help would be appreciated!
I guess in your case to reuse templates, you should try using a partial, have a look here.
For example, rename your users template to _users
<script type="text/x-handlebars" data-template-name='_users'>
...
</script>
and then use the partial helper to render it
{{partial users}}
Note that {{partial}} takes the template to be rendered as an argument, and renders that template in place. This means that it does not change context or scope. It simply renders the given template with the current scope.
Hope it helps.