Associate Lists and Tasks in Meteor todo - javascript

I'm building the todo application from the Meteor tutorial and continue it. I'm building some lists based on the task model, but I don't know how to join them and say when I click on one list, I want all the tasks from this one.
For the moment, I have the Tasks.js with:
'tasks.insert'(text, privacy, priority, listId) {
...
Tasks.insert({
text,
listId: listId,
owner: this.userId,
username: Meteor.users.findOne(this.userId).username,
});
},
Body.js
Template.body.events({
'submit .new-task' (event) {
event.preventDefault();
const listId = ???
const target = event.target;
const text = target.text.value;
...
Meteor.call('tasks.insert', text, privacy, priority, listId);
...
},
And then where I display it:
Template.body.helpers({
tasks() {
const instance = Template.instance();
if (instance.state.get('hideCompleted')) {
return Tasks.find({ checked: { $ne: true } }, { sort: Session.get("sort_order") });
}
return Tasks.find({}, { sort: Session.get("sort_order")});
},
lists() {
return Lists.find({}, { sort: { createdAt: -1 } });
},
I my body.html, I just display each items (lists and tasks) separately. But the problem is I don't know how to make the relation between both ...
Can you help me please ?
Thanks a lot

I see you are already using Session. Basically, you will use a Session variable that tracks what the list the user has selected, and then filter your tasks with that variable.
In your body, where you're displaying your list names, add the list's id as an HTML attribute:
{{#each lists}}
<a href='#' class='list-name' data-id='{{this._id}}'>
{{this.name}}
</a>
{{/each}}
Add an event for clicking on a list name that saves its id to a Session variable:
Template.body.events({
'click .list-name' (event) {
event.preventDefault();
Session.set('listId', event.currentTarget.attr('data-id'))
}
})
In your tasks helper, filter your query using the Session variable:
return Tasks.find(
{ listId: Session.get('listId') },
{ sort: Session.get("sort_order") }
);
Let me know if anything could be more clear.

Related

Displaying backend responses on the webpage

I'm new to this, please be kind!
How do I transfer the value of the object that was returned to me in the console to the webpage? As of now, the balance value is in the console but it is not displayed on the page.
edit: If I wish to display the objects in the console separately, do I use myObj.key? eg. I want to display the value of balance on the left and the value of block on the right of my webpage, do I use myObj.balance and myObj.block ?
attached a screenshot of my browser
This is my code, do guide me, thank you!
<template>
<div class="box-card">
<p class="title-text">余额</p>
<p class="number-text">{{Balance}}</p>
</div>
</template>
<script>
export default {
data() {
return {
userId: 0,
// page config
currentPage: 1,
total: 0,
pageSize: 20,
userBalance: [],
Balance: '',
}
},
watch: {},
mounted() {
this.userId = this.$route.query["user_id"];
this.userId = 41;
this.getUserBalance();
this.getUserIncomeRecord();
console.log('hello');
},
methods: {
pageChange(val) {
this.currentPage = val;
},
getUserBalance() {
Core.Api.User.getUserBalance(this.userId).then(res => {
console.log(res);
res == this.Balance;
})
},
</script>
EDITED: If you want to print in a element with certain ID instead of console.log("WHAT YOU WANT TO PRINT") use this:
document.getlementById("YOUR ELEMENT ID HERE").innerHtml("WHAT YOU WANT TO PRINT");
If you use Jquery this is equivalent to the above code:
$("#ELEMENT ID HERE").html("WHAT YOU WANT TO PRINT");
make a slight change:
getUserBalance() {
Core.Api.User.getUserBalance(this.userId).then(res => {
console.log(res);
this.Balance = res;
})
},

Meteor Blaze order sub-documents by sub-document property

Profile:
_id: Pe0t3K8GG8,
videos: [
{id:'HdaZ8rDAmy', url:'VIDURL', rank: 2},
{id:'22vZ8mj9my', url:'VIDURL2', rank: 0},
{id:'8hyTlk8H^6', url:'VIDURL3', rank: 1},
]
The profile is displayed together with the list of videos. I have a Drag & Drop which updates the videos rank using a Server Method.
1) the database updates correctly on Drop.
2) To sort the videos Array - I declare a helper on the Profile Template and SORT the videos array based on a custom comparison function.
Template.Profile.helpers({
'videosSorted': function(){
let videos = (this.videos);
let videosSorted = videos.sort(function(a, b) {
return parseFloat(a.rank) - parseFloat(b.rank);
});
return videosSorted;
}
});
Problem:
A) In Blaze the {{#each videosSorted}} does not reactively update.
If I F5 refresh then i can see the new order.
I think the issue is because I am providing videosSorted which does not update on changes to the document in the db.
How can I make videosSorted reactive?
Update:
All related code:
Iron Router Controller - I subscribe and set the data context for the layout
ProfileController = RouteController.extend({
subscriptions: function() {
this.subscribe('profile',this.params.slug).wait();
},
data: function () {
//getting the data from the subscribed collection
return Profiles.findOne({'slug':this.params.slug});
},
})
Publication:
Meteor.publish('profile', function (slug) {
const profile = Profiles.find({"slug":slug});
if(profile){
return profile;
}
this.ready();
});
The Profile HTML template:
<template name="Profile">
<ul class="sortlist">
{{#each videosSorted}}
{{> Video}}
{{/each}}
</ul>
</template>
I am using mrt:jquery-ui - sortable function
Template.Profile.onRendered(function () {
thisTemplate = this;
this.$('.sortlist').sortable({
stop: function(e, ui) {
el = ui.item.get(0);
before = ui.item.prev().get(0);
after = ui.item.next().get(0);
if(!before) {
newRank = Blaze.getData(after).rank - 1
} else if(!after) {
newRank = Blaze.getData(before).rank + 1
}
else {
newRank = (Blaze.getData(after).rank +
Blaze.getData(before).rank) / 2
}
let queryData = {
_id: thisTemplate.data._id, //the id of the profile record
videos_objId: Blaze.getData(el).objId, //the id of the sub document to update
new_rank: newRank //the new rank to give it
};
//Update the sub document using a server side call for validation + security
Meteor.call("updateVideoPosition", queryData, function (error, result) {
if(!result){
console.log("Not updated");
}
else{
console.log("successfully updated Individual's Video Position")
}
});
}
})
});
And finally the Meteor method that does the updating
'updateVideoPosition': function (queryData){
let result = Individuals.update(
{_id: queryData._id, 'videos.objId': queryData.videos_objId },
{ $set:{ 'videos.$.rank' : queryData.new_rank } }
)
return result;
}
Note :
As i mentioned - the database updates correctly - and if i have an Incognito window open to the same page - i see the videos reactivly (magically !) switch to the new order.
The schema
const ProfileSchema = new SimpleSchema({
name:{
type: String,
}
videos: {
type: [Object],
optional:true,
},
'videos.$.url':{
type:String,
},
'videos.$.rank':{
type:Number,
decimal:true,
optional:true,
autoform: {
type: "hidden",
}
},
'videos.$.subCollectionName':{
type:String,
optional:true,
autoform: {
type: "hidden",
}
},
'videos.$.objId':{
type:String,
optional:true,
autoform: {
type: "hidden",
}
}
});
I came up with really crude solution, but I don't see other options right now. The simplest solution I can think of is to rerender template manually:
Template.Profile.onRendered(function () {
var self = this;
var renderedListView;
this.autorun(function () {
var data = Template.currentData(); // depend on tmeplate data
//rerender video list manually
if (renderedListView) {
Blaze.remove(renderedListView);
}
if (data) {
renderedListView = Blaze.renderWithData(Template.VideoList, data, self.$('.videos-container')[0]);
}
});
});
Template.VideoList.onRendered(function () {
var tmpl = this;
tmpl.$('.sortlist').sortable({
stop: function (e, ui) {
var el = ui.item.get(0);
var before = ui.item.prev().get(0);
var after = ui.item.next().get(0);
var newRank;
if (!before) {
newRank = Blaze.getData(after).rank - 1
} else if (!after) {
newRank = Blaze.getData(before).rank + 1
}
else {
newRank = (Blaze.getData(after).rank +
Blaze.getData(before).rank) / 2
}
let queryData = {
_id: tmpl.data._id, //the id of the profile record
videos_objId: Blaze.getData(el).objId, //the id of the sub document to update
new_rank: newRank //the new rank to give it
};
//Update the sub document using a server side call for validation + security
Meteor.call("updateVideoPosition", queryData, function (error, result) {
if (!result) {
console.log("Not updated");
}
else {
console.log("successfully updated Individual's Video Position")
}
});
}
});
});
Template.VideoList.helpers({
videosSorted: function () {
return this.videos.sort(function (a, b) {
return a.rank - b.rank;
});
}
});
And HTML:
<template name="Profile">
<div class="videos-container"></div>
</template>
<template name="VideoList">
<ul class="sortlist">
{{#each videosSorted}}
<li>{{url}}</li>
{{/each}}
</ul>
</template>
Reativeness was lost in your case because of JQuery UI Sortable. It doesn't know anything about Meteor's reactiveness and simply blocks template rerendering.
Probably you should consider using something more adopted for Meteor like this (I am not sure it fits your needs).

Meteor collection sorting not working as expected

I'm trying to sort one collection when the user clicks on a button. It works as expected the first time I click, but then when I click it again nothing happens.
On meteor.startup i'm sorting my collection by 'date'. When the user clicks the category button, it changes the sort by to 'category', and then I am trying to handle each click that same button, to change the sort from ascending to descending.
Heres the snippet that handles the user click:
(I'm almost sure the problem is somewhere here)
layout.js
Template.layout.events({
'click #cat': function(event) {
event.preventDefault();
//sets the session to a variable
var sortBy = Session.get('sort_by');
if (sortBy.category == 'desc') {
return Session.set('sort_by', {
category: 'asc'
});
} else {
return Session.set('sort_by', {
category: 'desc'
});
}
}
})
This is my router.js:
Router.configure({
layoutTemplate: 'layout',
waitOn: function() {
return Estagios.find({},{ sort: Session.get("sort_by")});
},
})
Publications.js
Meteor.publish('nestagios', function() {
return Estagios.find({});
})
This is my main.js
Meteor.startup(function() {
Session.set("sort_by", {
date: -1,
});
});
Can anyone help me find out, what is wrong here? Thank you.
Since you're just toggling the direction of the sort you can simplify your event handler down to:
Template.layout.events({
'click #cat': function(event) {
event.preventDefault();
Session.set('sort_by',{category: -Session.get('sort_by').category});
});
The session variable will evaluate to either {category: 1} or {category: -1}
In your router you should use $orderBy and not sort
Router.configure({
layoutTemplate: 'layout',
waitOn: function() {
return Estagios.find({},{ $orderBy: Session.get("sort_by")});
},
})

Prevent inserting duplicate elements - instead routing to existing element

This is how I'm adding some elements to a list (which consists of links to articles) via an input field:
Template.addForm.events({
'submit form': function(event){
event.preventDefault();
var title = event.target.text.value;
MongoValues.insert({
title: title,
slug: title.toLowerCase()
}, function(error, result) { if(error) console.warn(error); });
event.target.text.value = "";
}
});
Now I want to prevent double entries: If the user wants to add an already existing title he should be routed to this already existing element (route to article/_id), instead of adding the title to the list.
Assuming you are using iron:router and have a route like this :
Router.route('article/:_id', {
name: 'article'
// other route stuff
});
You could adjust your code as follows:
Template.addForm.events({
'submit form': function(event){
event.preventDefault();
var title = event.target.text.value;
var existing = MongoValues.findOne({title : title});
if (!!existing) {
// title already exists, go to article page
Router.go("article", {_id : existing._id});
} else {
// title doesnt exist, so go ahead and insert
MongoValues.insert({
title: title,
slug: title.toLowerCase()
}, function(error, result) {
if(error) {
console.warn(error);
}
});
event.target.text.value = "";
}
}
});
Note that this will not prevent duplicates if the user bypasses this code (i.e. by doing the insert from the console).
If you are using Collection2 and SimpleSchema, you can set a unique constraint on the title field to ensure that only unique values ever get inserted, regardless of where the insert happens. To do this, just specify "unique: true" in your field definition, like so:
title : {
type: String,
unique: true
}

How do i Meteor Reset client subscription

i have subscribe and publish like this :
publish.js :
Meteor.publish('ProductWithSkipAndLimit', function(skip,limit){
return Product.find({}, {
sort: {
createdAt: 1
},
skip: skip,
limit: limit
});
});
subscribe.js :
Meteor.subscribe('ProductWithSkipAndLimit',0,10);
and it will return to client 10 products from 0 sort by createdAt.
Nah i have an event click like this :
'click' : function(e){
e.preventDefault();
Meteor.subscribe('ProductWithSkipAndLimit',10,10);
}
I want to get 10 more products. okay i get that products, but 10 products not reset. so on client i have 20 products.
how i can reset client subscription? so client only have 10 products every subscribe.
Meteor.subscribe:
Subscribe to a record set. Returns a handle that provides stop() and ready() methods.
You need to take handle of Meteor.subscribe
subscription = Meteor.subscribe('ProductWithSkipAndLimit',10,10);
And in events object :
var subscription;
Template.NAME.events({
'click' : function(e){
e.preventDefault();
subscription && subscription.stop();
subscription = Meteor.subscribe('ProductWithSkipAndLimit',10,10);
}
})
I think, in click event you have to set Session variable Session.set('more', true);
On client:
Deps.autorun(function() {
if(Session.get('more')) {
Meteor.subscribe('ProductWithSkipAndLimit',10,10);
Session.set('more', false);
}
});
Or some logic to set current position in collection (10, 20, etc.)
You asked about subscription resetting, but it looks like there is no need to do it manually in your case.
You can subscribe within Tracker.autorun and pass reactive values as subscription parameters.
Then on each skip/limit session variable change the subscription will be reset automatically.
From Meteor official documentation:
If you call Meteor.subscribe within a reactive computation, for example using Tracker.autorun, the subscription will automatically be cancelled when the computation is invalidated or stopped; it's not necessary to call stop on subscriptions made from inside autorun.
Here is working example (METEOR#1.1.0.2):
Items = new Meteor.Collection("items");
if(Meteor.isClient) {
Tracker.autorun(function() {
Meteor.subscribe("items", Session.get("skip"), Session.get("limit"));
});
Template.main.helpers({
items: function() {
return Items.find({});
}
});
Template.main.events({
'click #next' : function(e){
e.preventDefault();
var skip = Session.get("skip");
Session.set("skip", skip + Session.get("limit"));
},
'click #prev' : function(e){
e.preventDefault();
var skip = Session.get("skip");
Session.set("skip", skip - Session.get("limit"));
}
});
Meteor.startup(function() {
Session.set("skip", 0);
Session.set("limit", 10);
});
}
if(Meteor.isServer) {
if (Items.find({}).fetch().length < 100) {
_.times(100, function(n) {
Items.insert({
name: String(n),
createdAt: new Date()
});
});
}
Meteor.publish("items", function(skip, limit) {
return Items.find({}, { limit: limit, skip: skip, sort: { createdAt: 1} });
});
}
template
<template name="main">
<header>
<h1>Items</h1>
<nav>
<button id="prev">prev</button>
<button id="next">next</button>
</nav>
</header>
<ul>
{{#each items}}
<li>{{name}}</li>
{{/each}}
</ul>
</template>
P.S. Don't forget to remove "autopublish" package

Categories

Resources