I'm trying to make a category system and i cant seem to figure out how to make it work.
here's a mock js and html to demonstrate what im trying to accomplish
test.js
Categories = new Meteor.Collection('categories');
Rooms = new Meteor.Collection('rooms');
if (Meteor.isClient) {
Template.Category_System.categories = function () {
return Categories.find({});
};
Template.Category_System.rooms = function () {
return Rooms.find({}); //here i want to return the rooms and users in the Rooms collection but only the rooms that fall under the category and subcategory of the Categories collection
};
}
if (Meteor.isServer) {
Meteor.startup(function () {
Categories.insert({category:"category1",subcategories:[{subcategory:"subcategory1",rooms:[]},{subcategory:"subcategory2",rooms:[]}]};
Categories.insert({category:"category2",subcategories:[{subcategory:"subcategory1",rooms:[]},{subcategory:"subcategory2",rooms:[]}]};
Rooms.insert({category:"category1",subcategory:"subcategory1",room:'Room_01',users:[a,b,c]});
Rooms.insert({category:"category1",subcategory:"subcategory1",room:'Room_02',users:[d,e,f,g,h]});
Rooms.insert({category:"category1",subcategory:"subcategory2",room:'Room_03',users:[i]});
Rooms.insert({category:"category2",subcategory:"subcategory1",room:'Room_01',users:[j,k]});
Rooms.insert({category:"category2",subcategory:"subcategory2",room:'Room_02',users:[l,m,n]});
Rooms.insert({category:"category2",subcategory:"subcategory2",room:'Room_03',users:[o,p,q,r]});
});
}
test.html -> just the template
<template name="Category_System">
{{#each categories}}
{{category}}
{{#each subcategories}}
{{subcategory}}
{{#each rooms}}
{{room}}{{users}}
{{/each}}
{{/each}}
{{/each}}
</template>
the outcome i'm trying to achieve
category1
-subcategory1
-Room_01 a,b,c
-Room_02 d,e,f,g,h
-subcategory2
-Room_03 i
category2
-subcategory1
-Room_01 j,k
-subcategory2
-Room_02 l,m,n
-Room_03 o,p,q,r
thanks in advance
Based on your mock code, you could do something like the following:
category.js
Categories = new Meteor.Collection('categories');
Rooms = new Meteor.Collection('rooms');
if (Meteor.isClient) {
Template.Category_System.categories = function () {
return Categories.find();
};
Template.Category_System.rooms = function ( cat ) {
var _subcat = this.subcategory,
_cat = cat;
return Rooms.find({ category: _cat, subcategory: _subcat });
};
}
if (Meteor.isServer) {
Meteor.startup(function () {
if ( !Categories.find().count() ) {
Categories.insert({category:"category1",subcategories:[{subcategory:"subcategory1",rooms:[]},{subcategory:"subcategory2",rooms:[]}]});
Categories.insert({category:"category2",subcategories:[{subcategory:"subcategory1",rooms:[]},{subcategory:"subcategory2",rooms:[]}]});
}
if ( !Rooms.find().count() ) {
Rooms.insert({category:"category1",subcategory:"subcategory1",room:'Room_01',users:["a","b","c"]});
Rooms.insert({category:"category1",subcategory:"subcategory1",room:'Room_02',users:["d","e","f","g","h"]});
Rooms.insert({category:"category1",subcategory:"subcategory2",room:'Room_03',users:["i"]});
Rooms.insert({category:"category2",subcategory:"subcategory1",room:'Room_01',users:["j","k"]});
Rooms.insert({category:"category2",subcategory:"subcategory2",room:'Room_02',users:["l","m","n"]});
Rooms.insert({category:"category2",subcategory:"subcategory2",room:'Room_03',users:["o","p","q","r"]});
}
});
}
category.html
<head>
<title>Category System Test</title>
</head>
<body>
{{> Category_System}}
</body>
<template name="Category_System">
<ul>
{{#each categories}}
<li>
{{category}}
<ul>
{{#each subcategories}}
<li>
{{subcategory}}
<ul>
{{#each rooms ../category }}
<li>{{room}} - {{users}}</li>
{{/each}}
</ul>
</li>
{{/each}}
</ul>
</li>
{{/each}}
</ul>
</template>
Key thing to note is the passing of ../category in the block helper for rooms and the template helper for rooms which accepts the category parameter and also makes use of the current subcategory data context to filter the Room collection.
If you run this under Meteor 0.8.0, you should see the following output...
You could make this easier by completing the reference to the rooms array in each of your Category docs. Either do this through a UI or capture the insertId for each Room doc and update the appropriate Category doc as you seed.
By the way, you'll probably want to throw in a check when you are seeding your collections (see the code above)...otherwise, you'll wind up with lots and lots of records every time you make a change and the app restarts.
Related
I'm new in meteor framework and I try to create a project that every time I clicked on the name on a single <li> the background should change to yellow. And if I click another <li> the previous one should go back to it's original color and the next <li> should turn to yellow.
So I try to add .selected class on that using the unique id from MongoDb. By comparing the id's using if statement, but it's not working.
Here is the code:
body.html:
<body>
<h1>Leaderboard</h1>
{{> leaderboard}}
</body>
<template name="leaderboard">
<ul>
{{#each player}}
<li class="player {{selectedClass}}">{{name}}: {{score}}</li>
{{/each}}
</ul>
</template>
body.js:
import {Template} from 'meteor/templating';
import './body.html';
import {PlayersList} from '../api/players.js';
Template.leaderboard.helpers({
'player': function(){
return PlayersList.find();
},
'selectedClass': function(){
var playerId = this._id;
var selectedPlayer = Session.get('selectedPlayer');
if(playerId == selectedPlayer){
return "selected"
}
}
});
Template.leaderboard.events({
'click .player': function(){
var playerId = this._id;
Session.set('selectedPlayer', playerId);
}
});
main.css
.selected{
background-color: yellow;
}
Here is the output:
I would recommend making a few changes to your Template. I've added them here with some annotation.
leaderboard.html
(note that we are now passing in _id to the helper)
<template name="leaderboard">
<ul>
{{#each player}}
<li class="player {{selectedClass _id}}">{{name}}: {{score}}</li>
{{/each}}
</ul>
</template>
leaderboard.js
import {Template} from 'meteor/templating';
import {Tracker} from 'meteor/tracker'; // import Tracker here
import './body.html';
import {PlayersList} from '../api/players.js';
Template.leaderboard.onCreated(function() {
// subscribe to your publication in the onCreated lifecycle call
// to ensure the playerList is available for the component
// http://blazejs.org/api/templates.html#Blaze-TemplateInstance-subscribe
this.subscribe('<publication-name>');
});
Template.leaderboard.helpers({
'player': function(){
return PlayersList.find();
},
// pass in data to helper
// http://blazejs.org/guide/reusable-components.html#Pass-data-into-helpers
'selectedClass': function(playerId) {
// use Session.equals here - fewer invalidations are triggered
// https://docs.meteor.com/api/session.html
return Session.equals('selectedPlayer', playerId)
? 'selected' : '';
}
});
Template.leaderboard.events({
'click .player': function() {
var playerId = this._id;
Session.set('selectedPlayer', playerId);
}
});
The key takeaway is to know what this is within your helpers and event maps.
I'm working on learning Ember and am trying to do some small ideas with it. Currently, I am trying to receive text field input to filter a list and return the matching results. I have all of this working, you know, the 'hard' stuff. However, the part that isn't working is Handlebars reading the 'title' property of my array that I am returning. It's just blank.
Here is my template:
<script data-template-name="application" type="text/x-handlebars">
{{input type="text" value=searchString placeholder="Search..."}}
{{filterMovies}}
<ul>
{{#each searchResults}}
<li>{{title}}</li>
{{/each}}
</ul>
</script>
And now my controller:
App.ApplicationController = Ember.Controller.extend({
filterMovies: function() {
var self = this,
searchString = self.get('searchString'),
searchResults = [],
filterArrLength = null,
theFullMovieList,
theFilteredMovieList = [];
if (!searchString) {
return;
}
var url = 'http://www.json-generator.com/api/json/get/clVKyWQSnC';
Ember.$.getJSON(url).then(function(data) {
theFullMovieList = data;
theFullMovieList.filter(function(movie) {
if (movie.title.toLowerCase().startsWith(searchString)) {
theFilteredMovieList.push(movie);
}
});
console.log(theFilteredMovieList);
self.set('searchResults', theFilteredMovieList);
});
}.property('searchString')
});
I have tried printing using {{this}}, {{this.title}}, {{searchResults.title}}, and {{title}} with no luck. However, logging the array shows the correct values.
Any ideas? View On CodePen
Your each syntax is invalid. You have to use new syntax:
<ul>
{{#each searchResults as |movie|}}
<li>{{movie.title}}</li>
{{/each}}
</ul>
See working demo on CodePen.
I am trying to filter my collection based on the selection of value from select dropdown list. I have tried with the solutions here and here and its not working enough for me. Below is the template where i want to filter.the select dropdown is in another template categoryFilter which iam calling here using {{> categoryFilter}} to filter the list of collections under {{#each car}} block. The field i am using in schema is ccategory which i want to filter
<template name="carsList">
<div class="col s12 filter-holder">
<div class="col m4 s4 filter-box">
{{> categoryFilter}}
</div>
</div>
<div class="col s12">
<ul class="collection" id="listings">
{{#each cars}}
<li>
{{> carItem}}
</li>
{{/each}}
</ul>
</div>
</template>
this is my existing helper for calling all cars
Template.carsList.helpers ({
'allCars': function() {
return Cars.find();
},
});
this is how my event look like for this. var pageSession=ReactiveDict();
Template.carsList.events({
"click .categoryselection": function(e, t){
var text = $(e.target).text();
pageSession.set("selectedCategory",text);
}
});
I am using ReactiveDict() package for this filtering.Am i doing it right till now? How can i filter the values and call them on the <ul> list and filter <li> values.Please help me
Since you are only storing one value at a time (the selected category), there is no real need to use a ReactiveDict for this case, is there? If so, you could do it with a ReactiveVar instead:
Template.carsList.onCreated(function () {
this.selectedCategory = new ReactiveVar();
});
Template.carsList.helpers ({
'allJobs': function() {
var category = Template.instance().selectedCategory.get();
return Cars.find(category ? {ccategory: category} : {});
},
});
Template.carsList.events({
"click .categoryselection": function(e, t){
var text = $(e.target).text();
t.selectedCategory.set(text);
}
});
If you still want to use a ReactiveDict for multiple filter values such as ccategory and city, you could go with:
Template.carsList.helpers ({
'allJobs': function() {
var filter = {};
var category = pageSession.get("selectedCategory");
var city = pageSession.get("selectedCity");
if (city)
filter.city = city;
if (category)
filter.ccategory = category;
return Cars.find(filter);
},
});
Template.carsList.events({
"click .categoryselection": function(e, t){
var text = $(e.target).text();
pageSession.set("selectedCategory",text);
},
"click .cityselection": function(e, t){
var text = $(e.target).text();
pageSession.set("selectedCity",text);
}
});
My aim is to pass filtered data to my controller and then to my template. I've tried not using the filter and everything works as expected. If I even try to use a filter that lets everything through, I don't get any data. I've even tried using false instead of true and fiddling with the argument list of the filter.
I'm using ember-data fixtures to test this. I'm following the name conventions so much of the work is done for me under the hood. That all seems to be working though (otherwise the first example should also have a problem).
Works (arrives in the controller and eventually gets rendered on the page):
App.DomainDirRoute = Ember.Route.extend({
model: function(params) {
return this.store.find('domain_dir');
}
});
Fails (controller gets an empty array):
App.DomainDirRoute = Ember.Route.extend({
model: function(params) {
return this.store.filter('domain_dir', function(item){
return true;
});
}
});
UPDATE (ATTEMPT 1):
Okay, so I've tried a couple of things based on Sam Selikoff's answer. I've defined 4 properties (2 filters, one map, one plain copy) in the controller and tried to display each in the mockup page. Only the property copyDomain gives a result.
Properties in controller:
filteredDomains: Ember.computed.filterBy('domain', 'domain', true),
upperCaseDomains: Ember.computed.map('domain', function(domain, index) {
return domain.toUpperCase() + '!';
}),
filteredDomains2: function() {
return this.get("model").filterBy('domain', true);
}.property('model.#each.domain'),
copyDomains: function(){
result = [];
this.forEach(function(item) {
result.pushObject(item);
})
console.log(result);
return result;
}.property('model.#each.domain')
Mockup:
<ul>
<li>filteredDomains</li>
{{#each domainDir in controller.filteredDomains}}
<li>domainDir.domain</li>
{{/each}}
</ul>
<ul>
<li>filteredDomains2</li>
{{#each domainDir in controller.filteredDomains2}}
<li>domainDir.domain</li>
{{/each}}
</ul>
<ul>
<li>upperCaseDomains</li>
{{#each domainDir in controller.upperCaseDomains}}
<li>domainDir.domain</li>
{{/each}}
</ul>
<ul>
<li>copyDomains</li>
{{#each domainDir in controller.copyDomains}}
<li>domainDir.domain</li>
{{/each}}
</ul>
Filtering is generally done at the controller/component level. store.find makes an AJAX request. Is your goal to only retrieve the filtered subset of data from the server, or to filter the data you already have at the view layer?
Typically if you're just wanting to do some live filtering, you'll do it in the controller. Leave your model hook as this.store.find('domain_dir') and add a filter in your controller:
App.DomainDirController = Ember.Controller.extend({
filteredDomains: function() {
return this.get("model").filterBy('someProp', true);
}.property('model.#each.someProp')
});
You should also check out the computed macros for some shorthands:
App.DomainDirController = Ember.Controller.extend({
filteredDomains: Ember.computed.filterBy('model', 'someProp');
});
Now in your template, you can do
{{#each domain in filteredDomains}}
...
I recently started using Ember.js. In my small application I currently have problems regarding Ember.computed.alias, because an {{#if}}-section is updated properly, but the bind-attr helper in the same template is not updated accordingly.
The application controller and the action influencing the value look as follows:
App.ApplicationController = Ember.ObjectController.extend({
isEditing: false,
actions: {
toggleEdit: function() {
var a = this.get('isEditing');
this.set('isEditing', !a);
}
}
});
The controller taking care of the template causing problems:
App.CategoriesController = Ember.ArrayController.extend({
needs: ['application'],
isEditing: Ember.computed.alias('controllers.application.isEditing'),
general: function() { // example depending on the alias
var result = this.filterBy('type', 1);
if (!this.get('isEditing')) {
result = result.filterBy('isHidden', false);
}
return result;
}.property('#each.type', '#each.isHidden', 'isEditing'),
// ......
The related template:
<ul id="categories">
{{#if isEditing}}YES!{{else}}NO!{{/if}}
{{#each general}}
<li {{bind-attr class=":general isEditing:editing"}}>
{{name}}
</li>
{{/each}}
</ul>
When the action toggleEdit is triggered, the {{#if}} section is updated and swaps between YES! and NO!, but the editing class is not applied to the list element. I tried encapsulated the alias into another property of the controller depending on the alias, but without success.
I assume it's a beginners mistake, but I can't figure out what I am overlooking.
Thanking you in anticipation.
isEditing is no longer in scope, use controller.isEditing, sorry phone response
Here's an example that would keep it in scope, but I'm fully qualifying it just to show you.
{{#each item in general}}
<li {{bind-attr class=":general controller.isEditing:editing"}}>
{{item.name}}
</li>
{{/each}}