Add conditions to a v-for in Vue - vuejs3

Is there possible to add conditions on looping out arrays in Vue? Like with the example down below, I loop an array of persons into a table:
<CTableRow v-for="person in persons" :key="person.status">
<CTableDataCell>{{ person.id }}</CTableDataCell>
<CTableDataCell>{{ person.name }}</CTableDataCell>
<CTableDataCell>{{ person.age }}</CTableDataCell>
</CTableRow>
If I have an element in persons named "active" (i.e person.active) can I somehow only loop through persons who are active and further down the Vue file loop through the persons who are not active, using the same data source?
If I can use some pseudocode, I want to achieve something like this:
<CTableRow v-for="person in persons" :key="person.status">
if( person.active === true) {
<CTableDataCell>{{ person.id }}</CTableDataCell>
<CTableDataCell>{{ person.name }}</CTableDataCell>
<CTableDataCell>{{ person.age }}</CTableDataCell>
}
</CTableRow>
<CTableRow v-for="person in persons" :key="person.status">
if( person.active === false) {
<CTableDataCell>{{ person.id }}</CTableDataCell>
<CTableDataCell>{{ person.name }}</CTableDataCell>
<CTableDataCell>{{ person.age }}</CTableDataCell>
}
</CTableRow>
Is this possible in Vue, and what would be the best practice?
Update:
I realized I could add a v-if on all the TableDataCells as a solution to my problem. Or is it better to create two arrays in the composition api?

You should be able to do this:
<CTableRow v-for="person in persons" :key="person.status">
<template v-if="person.active">
<CTableDataCell>{{ person.id }}</CTableDataCell>
<CTableDataCell>{{ person.name }}</CTableDataCell>
<CTableDataCell>{{ person.age }}</CTableDataCell>
</template>
<template v-else>
<CTableDataCell>Something Else</CTableDataCell>
</template>
</CTableRow>
The <template> is just a container which won't get rendered.

Related

add dynamic class with css binding

I want to add a class behind payment-method by function with the knockout css binding (in Magento 2.1).
So this is the current code:
<div class="payment-method" data-bind="css: {'_active': (getCode() == isChecked())}">
<div class="payment-method-title field choice">
<input type="radio"
name="payment[method]"
class="radio"
data-bind="attr: {'id': getCode()}, value: getCode(), checked: isChecked, click: selectPaymentMethod, visible: isRadioButtonVisible()"/>
The class is returned by getCode() which works above with the id and value.
So I thought I could do just:
<div class="payment-method" data-bind="css: {'_active': (getCode() == isChecked()), getCode()}">
But then it fails with:
knockout.js:2624 Uncaught SyntaxError: Unable to parse bindings.
Bindings value: css: {'_active': (getCode() == isChecked()), getCode() }
Message: Unexpected token }
<div class="payment-method" data-bind="css: getCode()">
This works.
<div class="payment-method" data-bind="css: {getCode()}">
This doesn't.
<div class="payment-method" data-bind="css: {'_active': (getCode() == isChecked())}, attr: {'class': getCode()}">
This works too but will overwrite the payment-method class and the _active class isn't set either initally anymore.
How do I set that dynamic class?
This piece of code is redundant, as the css data-bind is getting overwrite with your attr binding.
<div class="payment-method" data-bind="css: {'_active': (getCode() == isChecked())}, attr: {'class': getCode()}">
This is how you can do your dynamic class (assumption these properties are observable):
<div class="payment-method" data-bind="css: CSS">
self.CSS = ko.computed(function() {
var code = self.getCode();
var isChecked = self.isChecked();
return code + ' ' + (code == isChecked ? '_active' : '');
}, viewModel);
The comment from #tyler_mitchell helped me find the solution myself through this thread: https://stackoverflow.com/a/21528681/928666
I can do:
<div class="payment-method" data-bind="attr: { 'class': 'payment-method ' + getCode() }, css: {'_active': (getCode() == isChecked())}">
Not brilliant but well...
Another example should anyone need it
<div data-bind="attr: { 'class': 'mainClass' + (dynamicClassSetOnce ? ' ' + dynamicClassSetOnce : '' }, css: { 'mainClass--focussed': isFocussed }">
...
</div>

Meteor: Querying a collection based on a URL parameter

I am trying display the unique profile of a babysitter (i.e: babysitter username, city, postal code, etc ... ) from a schema/collection called "Babysitters" .
The URL correctly contains the babysitter unique _id, say:
http://localhost:3000/test/PqMviBpYAmTA2b5ec
but I don't manage to retrieve all the other fields.
meteor question - screenshot
I have tried querying the MongoDB in two files: routes.js and the template test.js
1) in routes.js
Router.route('/test/:_id', {
name: 'test',
data: function () {
return Babysitters.findOne({ _id: this.params._id });
}
});
2) in test.js
Template.test.helpers({
data: function () {
//sitterusername = this.params.sitterusername;
//console.log(this.params._id );
return Babysitters.findOne( { _id: this.params._id });
}
});
the html file: test.html
<template name="test">
{{#with data}}
<ul>
<li><img src="/" {{photourl}} height="100" width="100" ></li>
<li>Babysitter username: {{ sitterusername }}</li>
<li>Presentation: {{ presentation }}</li>
<li>City: {{ city }}</li>
<li>Postal Code: {{ postalcode }}</li>
<li>Mother tongue: {{ mothertongue }}</li>
<li>Languages spoken {{ languagesspoken }}</li>
<li>Experience {{ experience }}</li>
<li>Homework help: {{ homeworkhelpavailable }}</li>
<li>Hourly wages: {{ hourlywages }} €/h</li>
</ul>
{{/with}}
</template>
I have tried all sorts of ways but the Collection fields never appear in the HTML file.
Thanks for your help, a newbie here.
K.
Most likely you are not publishing all Babysitters to the client so your .findOne() returns nothing.
This is a common router pattern where you want to display a single document which is not normally published. A good way to solve this in i-r is to waitOn a subscription to the individual document:
waitOn: function(){
return Meteor.subscribe('oneBabysitter', this.params._id);
}
On the server, publish:
Meteor.publish('oneBabysitter',function(_id){
return Babysitters.find(_id);
});
Note that even though this publication is only going to return a single document you still have to do a .find() and not a .findOne() because publications need to return a cursor or array of cursors, not objects.

Meteor, publish:composite. how to access joined data in the template?

So I used publishComposite to do a collection join in Meteor. I have a parent collection (Subscriptions) with a user_id foreign key. I look up the user name in the Meteor.users collection to get the actual username, but how do I actually print this in the html template. My subscription data is there but how do I actually refer to the username?
Here is the publish code:
//publish subscriptions course view
Meteor.publishComposite('adminCourseSubscriptions', function(courseId){
return {
//get the subs for the selected course
find: function(){
return Subscriptions.find(
{course_id: courseId}
);
},
children:
[
{
//get the subscriber details for the course
find: function(sub){
return Meteor.users.find({_id:sub.user_id});
}
}
]
};
});
here are the template subdcriptions:
Template.adminCourseDetail.helpers({
courseDetail: function(id){
var id = FlowRouter.getParam('id');
return Courses.findOne({ _id: id });
},
courseSubscriptions: function(){
var id = FlowRouter.getParam('id');
return Subscriptions.find({course_id:id})
},
users: function(){
return Meteor.users.find();
}
});
and the template (which is garbage) ps the course details come from a separate collection. It was easier and I think more performant to get the details separately and this works fine. It's just the username that I cannot display correctly:
<template name="adminCourseDetail">
<h1>Course Details</h1>
<p>Title: {{courseDetail.title}}</p>
<p>Description: {{courseDetail.description}}</p>
<p>Start Date: {{courseDetail.startDate}}</p>
<p>Number of sessions: {{courseDetail.sessions}}</p>
<p>Duration: {{courseDetail.duration}}</p>
<p>Price: {{courseDetail.price}}</p>
<p>{{userTest}}</p>
edit
delete
<h2>Course Subscriptions</h2>
{{#each courseSubscriptions}}
<div class="row">
<div class="col-md-3">{{username}}</div>
<div class="col-md-3">{{sub_date}}</div>
</div>
{{/each}}
</template>
Thanks in advance for any suggestions!
As far as I understand your question, documents of the Subscriptions collection contain only the attribute user_id, referencing the corresponding user document in the Meteor.users collection. If this is the case, then you need to add an additional template helper which returns the username:
Template.adminCourseDetail.helpers({
// ...
getUsername: function() {
if (this.user_id) {
let user = Meteor.users.find({
_id: this.user_id
});
return user && user.username;
}
return "Anonymous";
}
// ...
});
After that, just replace {{username}} with {{getUsername}}:
<template name="adminCourseDetail">
<!-- ... -->
<h2>Course Subscriptions</h2>
{{#each courseSubscriptions}}
<div class="row">
<div class="col-md-3">{{getUsername}}</div>
<div class="col-md-3">{{sub_date}}</div>
</div>
{{/each}}
<!-- ... -->
</template>
Probably you misunderstood the concept of the reywood:publish-composite package. Using Meteor.publishComposite(...) will just publish a reactive join, but it will not return a new set of joined data.
For anyone else having a similar issue and looking at my specfic example. In my case the following code worked. Based on Matthias' answer:
In the template helper:
getUsername: function() {
let user = Meteor.users.findOne({
_id: this.user_id
});
return user;
}
and then in the template:
{{getUsername.username}}
My each block is looping through the cursor returned from the subscriptions collection rather than the course collection which is why it is simpler than the code Matthias provided.

How do I get templates inserted from custom block helpers to be individually rerendered in Meteor?

When I use the built-in block helper #each, book templates are rerendered individually when changed:
users =
_id: 'foo'
books: [
{name: 'book1'}
{name: 'book2'}
]
<template name="user">
{{#each books}}
{{> book}}
{{/each}}
</template>
<template name="book">
<div>{{name}}</div>
</template>
When the data is changed - the first book name is set to 'bookone' instead of 'book1' - only the book template (the div containing 'book1') is rerendered. This is the desired behavior. When I use a custom block helper, the behavior is different:
<template name="user">
{{#each_with_id}}
{{> book}}
{{/each}}
</template>
<template name="book">
<div data-id="{{_id}}">{{name}}</div>
</template>
Templates.user.each_with_id = (options) ->
html = "
for book, i in this.books
this.name = book.name
html += Spark.labelBranch i.toString(), ->
options.fn this
html
Now when the name of the first book changes, the whole user template is rerendered.
It does not work as you expect, because the implementation of built-in each is based on the cursor.observeChanges feature. You will not be able to achieve the same exact result without using an auxiliary collection of some sort. The idea is quite simple. It seems that you don't have a "books" collection but you can create a client-side-only cache:
Books = new Meteor.Collection(null);
where you will need to put some data dynamically like this:
Users.find({/* probably some filtering here */}).observeCanges({
added: function (id, fields) {
_.each(fields.books, function (book, index) {
Books.insert(_.extend(book, {
owner: id,
index: index,
}));
}
},
changed: function (id, fields) {
Books.remove({
owner:id, name:{
$nin:_.pluck(fields.books, 'name')
},
});
_.each(fields.books, function (book, index) {
Books.update({
owner : id,
name : book.name,
}, {$set:_.extend(book, {
owner : id,
index : index,
})}, {upsert:true});
}
},
removed: function (id) {
Books.remove({owner:id});
},
});
Then instead of each_with_id you will be able to the built-in each with appropriate cursor, e.g.
Books.find({owner:Session.get('currentUserId')}, {sort:{index:1}});
You may also look at this other topic which basically covers the same problem you're asking about.

How to set a Symfony2 path to Jeditable plugin

I'm trying to use Jeditable plugin for javascript. Here is the code (I took it from here):
In a .js file:
$('.edit').editable(function (value, settings) {
var data = {};
data[this.id] = value;
data["_token"] = "{{form._token.vars.value}}";
$.post("{{ path('edit_category', { 'id': cat.id}) }}", data);
return(value);
}, {
indicator:'Saving...',
tooltip:'Click to edit',
cancel:'Cancel',
submit:'Save'
});
This isn't working, it says that
No route found for "POST /{{ path('edit_category', { 'id': cat.id}) }}"
which I understand, because I have no idea how to pass the id parameter to the path (cat.id).
This is the way I'd do the edit only with Symfony in a template file:
<a href="{{ path('edit_category', { 'id': cat.id}) }}">
<i class="icon-pencil right-spacer"></i>
</a>
Any help will be appreciated! Thanks in advance!
The expression {{ path() }} is a twig expression, so it must be parsed by the twig template parser. Javascript files are usually not parsed.
You have some options on how to work around this. One idea is to inline your javascript code into your twig template. Of course that's not suitable for big code blocks and not very clean.
A better approach is to store the pathes in a variable in your layout twig template:
<script>
var token = "{{form._token.vars.value}}";
var path = "{{ path('edit_category', { 'id': cat.id}) }}";
</script>
In your js file, you only use the variables:
data["_token"] = token;
$.post(path, data);
Of course you might need to tweek that code if you have many pathes or variables.

Resources