How do i Meteor Reset client subscription - javascript

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

Related

vue.js: how to handle click and dblclick events on same element

I have a vue component with separate events for click/dblclick. Single click (de)selects row, dblclick opens edit form.
<ul class="data_row"
v-for="(row,index) in gridData"
#dblclick="showEditForm(row,$event)"
#click="rowSelect(row,$event)"
>
Doing it like this, i get 3 events fired on double click. Two click events and lastly one dblclick. Since the click event fires first , is there a way (short of deferring click event for a fixed amount of ms) for stopping propagation of click event on double click ?
Fiddle here
As suggested in comments, You can simulate the dblclick event by setting up a timer for a certain period of time(say x).
If we do not get another click during that time span, go for the single_click_function().
If we do get one, call double_click_function().
Timer will be cleared once the second click is received.
It will also be cleared once x milliseconds are lapsed.
See below code and working fiddle.
new Vue({
el: '#app',
data: {
result: [],
delay: 700,
clicks: 0,
timer: null
},
mounted: function() {
console.log('mounted');
},
methods: {
oneClick(event) {
this.clicks++;
if (this.clicks === 1) {
this.timer = setTimeout( () => {
this.result.push(event.type);
this.clicks = 0
}, this.delay);
} else {
clearTimeout(this.timer);
this.result.push('dblclick');
this.clicks = 0;
}
}
}
});
<div id="example-1">
<button v-on:dblclick="counter += 1, funcao()">Add 1</button>
<p>The button above has been clicked {{ counter }} times.</p>
</div>
var example1 = new Vue({
el: '#example-1',
data: {
counter: 0
},
methods: {
funcao: function(){
alert("Sou uma funcao");
}
}
})
check out this working fiddle https://codepen.io/robertourias/pen/LxVNZX
i have a simpler solution i think (i'm using vue-class but same principle apply):
private timeoutId = null;
onClick() {
if(!this.timeoutId)
{
this.timeoutId = setTimeout(() => {
// simple click
}, 50);//tolerance in ms
}else{
clearTimeout(this.timeoutId);
// double click
}
}
it does not need to count the number of clicks.
The time must be short between click and click.
In order to get the click and double click, only one counter is required to carry the number of clicks(for example 0.2s) and it is enough to trap the user's intention when he clicks slowly or when he performs several that would be the case of the double click or default case.
I leave here with code how I implement these features.
new Vue({
el: '#app',
data: {numClicks:0, msg:''},
methods: {
// detect click event
detectClick: function() {
this.numClicks++;
if (this.numClicks === 1) { // the first click in .2s
var self = this;
setTimeout(function() {
switch(self.numClicks) { // check the event type
case 1:
self.msg = 'One click';
break;
default:
self.msg = 'Double click';
}
self.numClicks = 0; // reset the first click
}, 200); // wait 0.2s
} // if
} // detectClick function
}
});
span { color: red }
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.2.0/vue.js"></script>
<div id='app'>
<button #click='detectClick'>
Test Click Event, num clicks
<span>{{ numClicks }}</span>
</button>
<h2>Last Event: <span>{{ msg }}</span></h2>
</div>
I use this approach for the same problem. I use a promise that is resolved either by the timeout of 200ms being triggered, or by a second click being detected. It works quite well in my recent web apps.
<div id="app">
<div
#click="clicked().then((text) => {clickType = text})">
{{clickType}}
</div>
</div>
<script>
new Vue({
el: "#app",
data: {
click: undefined,
clickType: 'Click or Doubleclick ME'
},
methods: {
clicked () {
return new Promise ((resolve, reject) => {
if (this.click) {
clearTimeout(this.click)
resolve('Detected DoubleClick')
}
this.click = setTimeout(() => {
this.click = undefined
resolve('Detected SingleClick')
}, 200)
})
}
}
})
</script>
Working fiddle:
https://jsfiddle.net/MapletoneMartin/9m62Lrwf/
vue Component
// html
<div class="grid-content">
<el-button
   #click.native="singleClick"
   #dblclick.native="doubleClick"
   class="inline-cell">
click&dbclickOnSameElement</el-button>
</div>
// script
<script>
let time = null; // define time be null
export default {
name: 'testComponent',
data() {
return {
test:''
};
},
methods: {
singleClick() {
// first clear time
clearTimeout(time);
time = setTimeout(() => {
console.log('single click ing')
}, 300);
},
  
doubleClick() {
clearTimeout(time);
console.log('double click ing');
}
}
}
</script>
selectedFolder = ''; // string of currently selected item
folderSelected = false; // preview selected item
selectFolder(folder) {
if (this.selectedFolder == folder) {
// double click
this.folderSelected = false;
this.$store.dispatch('get_data_for_this_folder', folder);
} else {
// single click
this.selectedFolder = folder;
this.folderSelected = true;
}
},
#click.stop handles a single click and #dblclick.stop handles double click
<v-btn :ripple="false"
class="ma-0"
#click.stop="$emit('editCompleteGrvEvent', props.item)"
#dblclick.stop="$emit('sendCompleteGrvEvent',props.item)">
<v-icon>send</v-icon>
</v-btn>
Unless you need to do expensive operations on single select, you can rework rowSelect into a toggle. Setting a simple array is going to be a lot faster, reliable, and more straightforward compared to setting up and canceling timers. It won't matter much if the click event fires twice, but you can easily handle that in the edit function.
<template>
<ul>
<li :key="index" v-for="(item, index) in items">
<a
:class="{ 'active-class': selected.indexOf(item) !== -1 }"
#click="toggleSelect(item)"
#dblclick="editItem(item)"
>
{{ item.title }}
</a>
<!-- Or use a checkbox with v-model
<label #dblclick="editItem(item)">
<input type="checkbox" :value="item.id" v-model.lazy="selected" />
{{ item.title }}
</label>
-->
</li>
</ul>
</template>
<script>
export default {
data: function () {
return {
items: [
{
id: 1,
title: "Item 1",
},
{
id: 2,
title: "Item 2",
},
{
id: 3,
title: "Item 3",
},
],
selected: [],
};
},
methods: {
editItem(item) {
/*
* Optionally put the item in selected
* A few examples, pick one that works for you:
*/
// this.toggleSelect(item); // If the item was selected before dblclick, it will still be selected. If it was unselected, it will still be unselected.
// this.selected = []; // Unselect everything.
// Make sure this item is selected:
// let index = this.selected.indexOf(item.id);
// if (index === -1) {
// this.selected.push(item.id);
// }
// Make sure this item is unselected:
// let index = this.selected.indexOf(item.id);
// if (index !== -1) {
// this.selected.splice(index, 1);
// }
this.doTheThingThatOpensTheEditorHere(item);
},
toggleSelect(item) {
let index = this.selected.indexOf(item.id);
index === -1
? this.selected.push(item.id)
: this.selected.splice(index, 1);
},
// For fun, get an array of items that are selected:
getSelected() {
return this.items.filter((item) => this.selected.indexOf(item.id) !== -1);
},
},
};
</script>

Associate Lists and Tasks in Meteor todo

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.

React - State not updating

When I click the button that triggers subscribe() I'm trying to change this.state.subscribe to be the opposite boolean of what it is. I'm not even managing change the value of this.state.subscribed much less render different text when clicking the button. I've tried replaceState and adding call back function. Not exactly sure what I should be putting in the call back function though, if that's what I'm doing wrong.
SingleSubRedditList = React.createClass({
mixins: [ReactMeteorData],
getMeteorData: function(){
Meteor.subscribe('posts');
var handle = Meteor.subscribe('profiles');
return {
loading: !handle.ready(),
posts: Posts.find().fetch(),
profiles: Profiles.find({}).fetch()
};
},
getInitialState: function(){
var subscribed;
return {
subscribed: subscribed
};
},
populateButton: function(){
//fetch is cumbersome, later try and do this another way to make it faster
var profile = Profiles.find({_id: Meteor.userId()}).fetch();
if (profile.length > 0){
this.state.subscribed = profile[0].subreddits.includes(this.props.subreddit);
}
},
subscribe: function(){
this.setState({
subscribed: ! this.state.subscribed
}
);
},
renderData: function(){
return this.data.posts.map((post) => {
return <SinglePost title={post.title} content={post.content} key={post._id} id={post._id} />
});
},
render: function(){
this.populateButton()
return (
<div>
<button onClick={this.subscribe}>
<p>{this.state.subscribed ? 'Subscribed' : 'Click To Subscribe'}</p>
</button>
<p></p>
<ul>
{this.renderData()}
</ul>
</div>
);
}
});
You can't set the state using this.state.subscribed = ..., which you are currently doing in populateButton. Trying to directly mutate the state like that will cause it to behave strangely.
You are also initializing subscribed with an undefined variable. You should give it an initial status of true or false.

Meteor: publish dynamically requested range of items

I have huge collection of over 5000+ records. I want to be able to view records 10 at a time. How can I dynamically publish the data that way?
I've tried this so far:
My server.js file :
Meteor.methods({
publishSongs : function (first, last) {
Meteor.publish('adminSongs', function() {
return Songs.find({}, {
skip : first,
limit : last,
sort : {
date : -1
}
});
});
}
});
My client.jsfile :
Template.admin.events({
'click #previous' : function() {
updateSession(-10);
publishSong();
},
'click #next' : function() {
updateSession(10);
publishSong();
}
});
Template.admin.onCreated(function() {
Session.setDefault('limit', {
first : 0,
last : 10
});
publishSong()
})
function publishSong() {
Meteor.call(
'publishSong',
Session.get('limit').first,
Session.get('limit').last
);
}
function updateSession(value) {
Session.set('limit', {
first: Session.get('limit').first + value,
last: Session.get('limit').last + value,
});
}
The server is printing this error message:
Ignoring duplicate publish named 'adminSongs'
It seems like I'm using publications wrong and could use some guidance.
It doesn't look like you're never updating your Session.get('limit'). You'll need to update then you press next/previous otherwise you're always going to get the same records. You'll also need to change the way you're doing publications:
Template.admin.events({
'click #previous' : function() {
updateSession(-10);
},
'click #next' : function() {
updateSession(10);
}
});
Template.admin.onCreated(function() {
Session.setDefault('limit', {
first : 0,
last : 10
});
Template.instance().autorun( function() {
Template.instance().subscribe('adminSongs', Session.get('limit').first, Session.get('limit').last);
});
});
function updateSession(value) {
Session.set('limit', {
first: Session.get('limit').first + value,
last: Session.get('limit').last + value,
});
}
I'm assuming based on your code that you already have a helper defined to return the available songs. The code above makes it so that you have one subscription, and that subscription will update any time your session variable changes.
Your server code will also need to be updated:
Meteor.publish('adminSongs', function(first, last) {
return Songs.find({}, {
skip : first,
limit : last,
sort : {
date : -1
}
});
});
Can be outside of a Meteor.method.

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")});
},
})

Categories

Resources