I've been looking at this for awhile and I'm pretty sure it has something to do with an infinite callback loop.
I have a method that returns an integer from a collection called Sessions. Here are my methods:
Meteor.methods({
going: function(sessionsId) {
return Sessions.update(sessionsId, {$addToSet: {participants: Meteor.userId()}, $inc: {slots:-1}});
},
retract: function(sessionsId) {
return Sessions.update(sessionsId, {$pull: {participants: Meteor.userId()}, $inc: {slots:1}});
},
sessionFull: function(sessionsId) {
var session = Sessions.findOne({_id:sessionsId});
console.log("gets here");
return session.slots;
}
});
Then in my client I have:
if (Meteor.isClient) {
Template.hello.sessions = function () {
return Sessions.find();
};
Template.session.this_info = function () {
return this._id;
};
Template.session.isGoing = function() {
var session = Sessions.find({_id:this._id, participants:Meteor.userId()}).count();
if (session > 0) return true;
else return false;
};
Template.session.sessionFull = function() {
if (this.slots === 0) return true;
else return false;
};
Template.session.slotsMethod = function () {
Meteor.call('sessionFull',this._id, function(error, slots) {
Session.set("slots",slots);
});
return Session.get("slots");
};
Template.session.events({
'click input.going' : function () {
//Sessions.update(this._id, {$inc: {slots: -1}});
Meteor.call('going', this._id, function(error, updated) {
if (error)
return alert(error.reason);
});
},
'click input.retract' : function () {
Meteor.call('retract', this._id, function(error, removed) {
if (error)
return alert(error.reason);
});
}
});
So I basically have a couple buttons that will increase or decrease the slots field and I want to have a method that will return what the slots field contains. Here is my template:
{{#each sessions}}
{{> session}}
{{/each}}
<template name="session">
<br>
{{date_time}}, {{duration}}
{{#if isGoing}}
<input type="button" class="retract" value="not going/give slot" />
{{else}}
{{#if sessionFull}}
<h1>SORRY SESSION FULL</h1>
{{else}}
<input type="button" class="going" value="going/subract slot" />
{{/if}}
{{/if}}
{{participants}},{{sessionFull}},{{this_info}}
</template>
If I try to add the Template.session.slotsMethod to my template (which calls the sessionFull Meteor method) I get an infinite loop, as in, it will display a rapidly changing integer for each session.
Am I doing something wrong?? Can't figure it out, I think it has something to with callbacks/async/sync but not sure.
Yes, your Template.session.slotsMethod will cause an infinite loop since Session is reactive.
This is what happens:
Whenever Session.get("slots") changes, Template.session.slotsMethod will be called because its dependent on Session.get("slots").
However, Template.session.slotsMethod itself is also updating the value of Session.get("slots") so the process starts all over again.
Not quite sure when you want Template.session.slotsMethod to be run, but you probably want to break it up into two pieces, something like:
Template.session.getSlots = function () {
return Session.get("slots");
};
and
Meteor.call('sessionFull',this._id, function(error, slots) {
Session.set("slots",slots);
});
needs to go wherever/whenever you need to do the sessionFull check, perhaps in Template.session.rendered?
Related
Here's my issue. I created a tool with vue.js and the WordPress API to search through the search endpoints for any keyword and display the result. So far so good, everything is working, except for a bug that I spotted.
Here's the deal:
const websiteurl = 'https://www.aaps.ca'; //yourwebsite or anything really
var vm = new Vue({
el: '#blog-page',
data: {
noData: false,
blogs: [],
page: 0,
search: '',
totalPagesFetch: "",
pageAmp: "&page=",
apiURL: `${websiteurl}/wp-json/wp/v2/posts?per_page=6`,
searchbyid: `${websiteurl}/wp-json/wp/v2/posts?per_page=6&include=`,
searchUrl: `${websiteurl}/wp-json/wp/v2/search?subtype=post&per_page=6&search=`,
},
created: function () {
this.fetchblogs();
},
methods: {
fetchblogs: function () {
let self = this;
self.page = 1;
let url = self.apiURL;
fetch(url)
.then(response => response.json())
.then(data => vm.blogs = data);
},
searchonurl: function () {
let ampersand = "&page=";
searchPagination(1, this, ampersand);
},
}
});
function searchPagination(page, vm, pagen) {
let self = vm;
let searchword = self.search.toLowerCase();
let newsearchbyid = self.searchbyid;
let url;
self.page = page;
url = self.searchUrl + searchword + pagen + self.page;
self.mycat = 'init';
fetch(url)
.then(response => {
self.totalPagesFetch = response.headers.get("X-WP-TotalPages");
return response.json();
})
.then(data => {
let newid = [];
data.forEach(function (item, index) {
newid.push( item.id );
});
if (newid.length == 0) {
return newsearchbyid + '0';
} else {
return newsearchbyid + newid;
}
})
.then(response2 => {
return fetch(response2)
})
.then(function(data2) {
return data2.json();
})
.then(function(response3) {
console.log(response3)
if (response3.length == 0) {
vm.noData = true;
vm.blogs = response3;
} else {
vm.noData = false;
vm.blogs = response3;
}
})
}
<link href="https://cdn.jsdelivr.net/npm/bootstrap#4.3.1/dist/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div class="lazyblock-blogs testblog" id="blog-page">
<div class="container">
<div class="row controls">
<div class="col-md-12">
<div class="search-blog">
<img height="13" src="" alt="search">
<input id="sb" type="text" v-model="search" #keyup="searchonurl" placeholder="search">
</div>
</div>
</div>
<div class="row">
<div class="col-md-4" v-for="(b, index) in blogs">
<div class="h-100 box" v-cloak>
<img width="100%" v-bind:src=b.featured_image_url>
<a v-bind:href="b.link">
<h3 v-html=b.title.rendered></h3>
</a>
<div v-html=b.excerpt.rendered></div>
<p class="read-more"><a v-bind:href="b.link">read more</a></p>
</div>
</div>
<div class="no-data" v-if="noData">
<div class="h-100">
No post
</div>
</div>
</div>
</div>
</div>
I'm using a keyup event which is causing me some problems because it works, but in same cases, for example, if the user is very fast to type characters and then suddenly he wants to delete the word and start again, the response for the API has some sort of lag.
The problem is that I guess that the Vue framework is very responsive (I create a variable call search that will update immediately) but the API call in the network is not (please check my image here):
This first image appears if I type lll very fast, the third result will return nothing so it is an empty array, but if I will delete it immediately, it will return an url like that: https://www.aaps.ca//wp-json/wp/v2/search?subtype=post&per_page=6&search=&page=1 which in turn should return 6 results (as a default status).
The problem is that the network request won't return the last request but it gets crazy, it flashs and most of the time it returns the previous request (it is also very slow).
Is that a way to fix that?
I tried the delay function:
function sleeper(ms) {
return function(x) {
return new Promise(resolve => setTimeout(() => resolve(x), ms));
};
}
and then I put before the then function:
.then(sleeper(1000))
but the result is the same, delayed by one second (for example)
Any thought?
This is the case for debounced function. Any existing implementation can be used, e.g. Lodash debounce. It needs to be declared once per component instance, i.e. in some lifecycle hook.
That searchPagination accepts this as an argument means that something went wrong with its signature. Since it operates on component instance, it can be just a method and receive correct this context:
methods: {
searchPagination(page, pagen) {
var vm = this;
...
},
_rawsearchonurl() {
let ampersand = "&page=";
this.searchPagination(1, ampersand);
}
},
created() {
this.searchonurl = debounce(this._rawsearchonurl, 500);
...
}
You could use debounce, no call will leave until the user stop typing in the amount of time you chose
function debounce(func, wait, immediate) {
var timeout;
return function() {
var context = this, args = arguments;
clearTimeout(timeout);
timeout = setTimeout(function() {
timeout = null;
if (!immediate) func.apply(context, args);
}, wait);
if (immediate && !timeout) func.apply(context, args);
};
}
// in your "methods" (I put 1000ms of delay) :
searchonurl: function () {
let ampersand = "&page=";
debounce(searchPagination, 1000)(1, this, ampersand);
}
One of best ways is to use Debounce which is mentioned in this topic
Or use a function and combine it with watch. Follow these lines:
In mounted or created make an interval with any peroid you like (300 etc.) define a variable in data() and name it something like searched_value. In interval function check the value of your input and saerch_value, if they were not equal (===) then replace search_value with input value. Now you have to watch search_value. When it changed you call your api.
I use this method and works fine for me. Also it`s managable and everything is in your hand to config and modify.
===== UPDATE: =====
A simple code to do what I said above
<template>
<div>
<input type="search" v-model="search_key">
</div> </template>
<script> export default {
name: "SearchByApi",
data() {
return {
search_key: null,
searched_item: null,
loading: false,
debounceTime: 300,
}
},
created() {
this.checkTime()
const self = this
setInterval(function() {
self.checkTime()
}, this.debounceTime);
},
watch: {
searched_item() {
this.loadApi()
}
},
methods: {
checkTime() {
if (this.searched_item !== this.search_key && !this.loading) {
this.searched_item = this.search_key
}
},
loadApi() {
if (!this.loading && this.searched_item?.length > 0) {
this.loading = true
const api_url = 'http://api.yourdomain.com'
axios(api_url, {search: this.searched_item}).then(res => {
// whatever you want to do when SUCCESS
}).catch(err => {
// whatever you want to do when ERROR
}).then(res => {
this.loading = false
})
}
}
}
}
</script>
I have created a settings page where users can update their email addresses. Everything worked fine but suddenly the validation is not updating anymore. Only the first change of the input field triggers validateState().
Any further changes will not trigger this function so the status of that field stays as it is.
I have compared the code with other components that use the same code and they still work fine.
I am using bootstrap-vue components for the form.
<template>
<div class="row">
<div class="col-md-12">
<b-form #submit="onSubmit">
<b-form-group :label="$t('general.email')"
label-for="settingsEmail"
:invalid-feedback="errors.first('email')">
<b-form-input id="settingsEmail"
type="text"
v-model="form.email"
:disabled="saving"
name="email"
:state="validateState('email')"
v-validate="{required: true, email: true}">
</b-form-input>
</b-form-group>
<b-button type="submit" variant="primary" :disabled="saving || !hasChanged() || errors.any()"><i class="fa fa-refresh fa-spin fa-fw" v-if="saving"></i> {{$t('general.save')}}</b-button>
</b-form>
</div>
</div>
</template>
<script>
import {UPDATE_USER} from '../config/actions'
export default {
name: 'settingsAccount',
data() {
return {
form: {},
saving: false
}
},
computed: {
user: function() {
return this.$store.getters.getUser;
}
},
created() {
this.init();
},
methods: {
init() {
this.form.email = this.user.email;
},
hasChanged() {
if(this.form.email !== this.user.email) {
return true;
}
return false;
},
onSubmit(event) {
event.preventDefault();
this.saving = true;
this.$validator.validateAll().then((result) => {
if (result) {
let data = {};
if(this.form.email !== this.user.email) {
data.email = this.form.email;
}
this.$store.dispatch(UPDATE_USER, data).then(() => {
this.saving = false;
this.$validator.reset();
}).catch(() => {
this.saving = false;
});
} else {
this.saving = false;
}
});
},
validateState(ref) {
if (this.veeFields[ref] && (this.veeFields[ref].dirty || this.veeFields[ref].validated)) {
return !this.errors.has(ref)
}
return null
},
}
}
</script>
The problem you're having is that the form data element is an empty object, so it will only trigger reactivity when the whole object changes. Either you need to change your data to be this:
data() {
return {
form: {email:''},
saving: false
}
},
Or in your init function, explicitly add the email property as reactive:
methods: {
init() {
this.$set(form,'email',this.user.email)
},
//...
If you're not clear on why, you can read the details here: https://v2.vuejs.org/v2/guide/reactivity.html
A working example (minus vuex) here: https://codesandbox.io/s/x4kp93w3o
PS, when writing questions about vue, it's very helpful to boil it down to a simpler example. Get rid of vuex, remove your translation stuff. Sometimes the answer will jump out at you once you have it as simple as possible.
I want to get the data or document for meteor server in event.
Someone has the other solution from this.
// meteor in server
Meteor.methods({
findPost: function() {
return Post.find().fetch();
}
}
});
//client template
<template name="Demo">
<input type="text" id="input-test">
<button id="test">Test</button>
</template>
//client event
Template.Demo.events({
'click #test':function(){
Meteor.call('findPost',function(error,result){
if(result) {
Session.set('foundPosts',result);
}
}
var posts=Session.get('foundPosts');
if(posts.length>0){
$('#input-test').val('Found');
}else{
$('#input-test').val('No result');
}
}
});
In this example I use the session to get value for meteor method callback function so that I can use it to do something else outside the method callback function. Do someone have better solution to get value from method callback function or have other solution to get data from meteor server in template event.
Template.Demo.onCreated(function () {
this.inputValue = new ReactiveVar('No result');
})
Template.Demo.events({
'click #test': function () {
var tmpl = Template.instance();
Meteor.call('findPost', function (err, res) {
if (!err && res.length > 0) {
tmpl.inputValue.set('Found');
}
});
}
});
Template.Demo.helpers({
inputVal: function () {
var tmpl = Template.instance();
return tmpl.inputValue.get();
}
});
//client template
<template name="Demo">
<input type="text" id="input-test" value="{{inputVal}}">
<button id="test">Test</button>
</template>
I am using meteor-postgres and want to display results from SQL query.
Html:
...
<template name="myForm">
<form class="search" method="GET">
<input required="Required" type="text" name="width" placeholder="cm"/>
<input type="submit"/>
</form>
Results:
<ul>
<!--How do I render results here-->
</ul>
...
JS
Services = new SQL.Collection('services');
// in client
Template.myForm.events({
"submit form": function (event) {
var width = event.target.width.value;
// TypeError: table is undefined,
// Maybe because I am on client side?
console.log(services.first().fetch());
// How can I get data here and render it to html?
}
});
I dont know what else should I say but StackOverflows want me to add more text!
I decided to use https://github.com/numtel/meteor-pg because it has nice examples.
Update
It is better and simpler to use PgSubscription.change:
Template:
<template name="hello">
<label>id</label>
<input type="number" id="myid" />
{{#each services}}
{{id}}
{{/each}}
</template>
JS
myServices = new PgSubscription('services');
if (Meteor.isClient) {
Template.hello.helpers({
services: function () {
return myServices.reactive();
}
});
function handleInput(event, template) {
var idVal = template.find("#myid").value;
console.log("client change detected, new value:");
console.log(idVal);
myServices.change(idVal);
}
Template.hello.events({
"keyup #myid": function(event,template) {
handleInput(event,template);
},
"change #myid": function(event,template) {
handleInput(event,template);
}
});
}
if (Meteor.isServer) {
var PG_CONNECTION_STRING = "postgres://root:root#localhost:5433/parcelService";
var liveDb = new LivePg(PG_CONNECTION_STRING, "myApp");
var closeAndExit = function () {
liveDb.cleanup(process.exit);
};
// Close connections on hot code push
process.on('SIGTERM', closeAndExit);
// Close connections on exit (ctrl + c)
process.on('SIGINT', closeAndExit);
Meteor.publish('services', function (id) {
console.log("server services, id:");
console.log(id);
return liveDb.select(
'SELECT * FROM services WHERE id = $1', [ id ]
);
});
}
Old Method:
On client side I defined ReactiveVar for template
Template.myForm.created = function () {
this.asyncServices = new ReactiveVar(["Waiting for response from server..."]);
}
and helper for data
Template.myForm.helpers({
myData: function () {
return Template.instance().asyncServices.get();
}
});
now trigggering action with
Template.myForm.events({
'keyup input': function (evt, template) {
var asyncServicesX = Template.instance().asyncServices;
Meteor.call('services', params, function(error, response){
if (error) throw error;
asyncServicesX.set(response);
});
}
And finally method on server side, where sql is executed:
Meteor.methods({
'services': function (params) {
Future = Npm.require('fibers/future');
var future = new Future();
// Obtain a client from the pool
pg.connect(PG_CONNECTION_STRING, function (error, client, done) {
if (error) throw error;
// Perform query
client.query(
'select * from services where col1=$1 and col2=$2',
params,
function (error, result) {
// Release client back into pool
done();
if (error) throw error;
future["return"](result.rows);
}
)
});
return future.wait();
}
});
I want to use x-editable-reactive-template in my MeteorJS project. My template looks like this:
<template name="jobSeeker">
<div class="items-form">
{{> xEditable type="text" success=onSuccess placement="right" mode="inline emptytext="Your name..." value=usernameValue }}
</div>
</template>
My template helper looks like this:
Template.jobSeeker.helpers({
usernameValue: function()
{
Meteor.call('getProfileUsername',function(error, result) {
if (error) return alert(error.reason);
return result;
});
}
});
Meteor method on server-side:
Meteor.methods({
getProfileUsername: function() {
var user = Meteor.user();
var currentUsername = Meteor.users.findOne(user._id);
return currentUsername.username;
}
});
User is saved in MongoDB and inside collection there is document with field username. But something isn't working! Any help ?
you are returning value inside a method callback that will not return value to the helper
try this
Template.jobSeeker.helpers({
usernameValue: function()
{
Meteor.call('getProfileUsername',function(error, result) {
if (error)
return alert(error.reason);
Session.set("username",username);
});
return Session.get("username");
}
});