I'm running into an issue with Meteor subscription not setting checkbox "checked" after refresh. Basically, if I have Meteor running and change the JS, the app works as expected (pulls data from Meteor.user() and sets checkbox accordingly). However, if I refresh the app all my checkboxes are set to false. I don't see why that should happen, any ideas?
My subscription looks as follows:
Server-side
Meteor.publish("user-preferences", function () {
if (this.userId) {
return Meteor.users.find({_id: this.userId},
{fields: {'notification': 1}});
} else {
this.ready();
}
});
Client-side:
Meteor.subscribe("user-preferences", function() {
if (Meteor.user()) {
var notification = Meteor.user().notification;
// Check to see if notification and systems exists; if not, default to TRUE
if (notification) {
Session.set('aNotificationPreference', notification.hasOwnProperty('a') ? notification.a : true);
}
else {
Session.set('aNotificationPreference', true);
}
}
else {
// TRUE too if no user yet
Session.set('aNotificationPreference', true);
}
});
This is the helper that will look up the session variable to make things reactive:
Template.preferences.helpers({
isChecked: function() {
console.log('#### PREF INITIAL: ' + Session.get("aNotificationPreference"));
var pref = Session.get("aNotificationPreference");
if (typeof pref === 'undefined') {
Meteor.setTimeout(function() {
pref = Session.get("aNotificationPreference");
console.log('#### PREF IN TIMEOUT: ' + pref);
return pref ? 'checked' : false;
}, 1250);
}
else {
console.log('#### PREF in ELSE: ' + pref);
return pref ? 'checked' : false;
}
}
});
Finally, this is the HTML checkbox field:
<input type="checkbox" data-role="flipswitch" data-theme="b" name="aNotificationSwitch" id="aNotificationSwitch" data-mini="true" checked={{isChecked}}>
This is based of the Blaze documentation on this specifically and other posts I found.
I know the issue is in the helper but I'm not sure how to address it. The logs on failure show as follows:
Application Cache Checking event (index):1
Application Cache NoUpdate event (index):1
#### PREF INITIAL: undefined index.js?8b2a648142fb63b940f4fb04771d18f25b5bf173:63
Connected to Meteor Server. index.js?8b2a648142fb63b940f4fb04771d18f25b5bf173:37
#### PREF INITIAL: true index.js?8b2a648142fb63b940f4fb04771d18f25b5bf173:63
#### PREF in ELSE: true index.js?8b2a648142fb63b940f4fb04771d18f25b5bf173:73
Connected to Meteor Server. index.js?8b2a648142fb63b940f4fb04771d18f25b5bf173:37
#### PREF IN TIMEOUT: true
I am not an expert in Meteor yet.
But I would change the JavaScript you have for:
if(Meteor.isClient){
Template.preferences.helpers({
isChecked: function() { return Meteor.user().notification ? 'checked' : false; }
});
Template.preferences.events({
'click #aNotificationSwitch': function(event){
var checked = event.target.checked; // true or false
Meteor.users.update({_id: Meteor.userId()}, $set: {notification: checked});
}
});
}
It's different from your approach though. With this way you don't need to use session variables.
Allowing to update users:
Meteor.users.allow({
update: function(userId, user){
return user._id == userId;
}
});
The best solution I found was to set the flipswitch with jQueryMobile by doing the following on my panel's open event in the Template.body.events:
'panelbeforeopen #mypanel': function() {
//Refresh flipswitches prior to opening sidepanel otherwise they default to disabled
$("#aNotificationSwitch").flipswitch("enable").flipswitch("refresh");
$("#bNotificationSwitch").flipswitch("enable").flipswitch("refresh");
$("#cNotificationSwitch").flipswitch("enable").flipswitch("refresh");
}
Related
Please help with the script function on how to make select2 plugin work in wizard jquery template it is not firing and i'm using the search box, when it clicked it jams.Thank you
$('.class').select2(); AFTER $('#form').steps();
This has helped me thanks but..
$("#form").steps({
bodyTag: "fieldset",
onStepChanging: function (event, currentIndex, newIndex)
{
// Always allow going backward even if the current step contains invalid fields!
if (currentIndex > newIndex)
{
return true;
}
// Forbid suppressing "Warning" step if the user is to young
if (newIndex === 3 && Number($("#age").val()) < 18)
{
return false;
}
var form = $(this);
// Clean up if user went backward before
if (currentIndex < newIndex)
{
// To remove error styles
$(".body:eq(" + newIndex + ") label.error", form).remove();
$(".body:eq(" + newIndex + ") .error", form).removeClass("error");
}
// Disable validation on fields that are disabled or hidden.
form.validate().settings.ignore = ":disabled,:hidden";
// Start validation; Prevent going forward if false
return form.valid();
},
onStepChanged: function (event, currentIndex, priorIndex)
{
// Suppress (skip) "Warning" step if the user is old enough.
if (currentIndex === 2 && Number($("#age").val()) >= 18)
{
$(this).steps("next");
}
// Suppress (skip) "Warning" step if the user is old enough and wants to the previous step.
if (currentIndex === 2 && priorIndex === 3)
{
$(this).steps("previous");
}
},
onFinishing: function (event, currentIndex)
{
var form = $(this);
// Disable validation on fields that are disabled.
// At this point it's recommended to do an overall check (mean ignoring only disabled fields)
form.validate().settings.ignore = ":disabled";
// Start validation; Prevent form submission if false
return form.valid();
},
onFinished: function (event, currentIndex)
{
var form = $(this);
// Submit form input
form.submit();
}
}).validate({
errorPlacement: function (error, element)
{
element.before(error);
},
rules: {
confirm: {
equalTo: "#password"
}
}
});
//IMPORTANT
var form =$("#form");
form.find("#personel_id").select2({
theme: 'bootstrap4',
});
React document states that the render function should be pure which mean it should not use this.setState in it .However, I believe when the state is depended on 'remote' i.e. result from ajax call.The only solution is setState() inside a render function
In my case.Our users can should be able to log in. After login, We also need check the user's access (ajax call )to decide how to display the page.The code is something like this
React.createClass({
render:function(){
if(this.state.user.login)
{
//do not call it twice
if(this.state.callAjax)
{
var self=this
$.ajax{
success:function(result)
{
if(result==a)
{self.setState({callAjax:false,hasAccess:a})}
if(result==b)
{self.setState({callAjax:false,hasAccess:b})}
}
}
}
if(this.state.hasAccess==a) return <Page />
else if(this.state.hasAccess==a) return <AnotherPage />
else return <LoadingPage />
}
else
{
return <div>
<button onClick:{
function(){this.setState({user.login:true})}
}>
LOGIN
</button>
</div>
}
}
})
The ajax call can not appear in componentDidMount because when user click LOGIN button the page is re-rendered and also need ajax call .So I suppose the only place to setState is inside the render function which breach the React principle
Any better solutions ? Thanks in advance
render should always remain pure. It's a very bad practice to do side-effecty things in there, and calling setState is a big red flag; in a simple example like this it can work out okay, but it's the road to highly unmaintainable components, plus it only works because the side effect is async.
Instead, think about the various states your component can be in — like you were modeling a state machine (which, it turns out, you are):
The initial state (user hasn't clicked button)
Pending authorization (user clicked login, but we don't know the result of the Ajax request yet)
User has access to something (we've got the result of the Ajax request)
Model this out with your component's state and you're good to go.
React.createClass({
getInitialState: function() {
return {
busy: false, // waiting for the ajax request
hasAccess: null, // what the user has access to
/**
* Our three states are modeled with this data:
*
* Pending: busy === true
* Has Access: hasAccess !== null
* Initial/Default: busy === false, hasAccess === null
*/
};
},
handleButtonClick: function() {
if (this.state.busy) return;
this.setState({ busy: true }); // we're waiting for ajax now
this._checkAuthorization();
},
_checkAuthorization: function() {
$.ajax({
// ...,
success: this._handleAjaxResult
});
},
_handleAjaxResult: function(result) {
if(result === a) {
this.setState({ hasAccess: a })
} else if(result ===b ) {
this.setState({ hasAccess: b })
}
},
render: function() {
// handle each of our possible states
if (this.state.busy) { // the pending state
return <LoadingPage />;
} else if (this.state.hasAccess) { // has access to something
return this._getPage(this.state.hasAccess);
} else {
return <button onClick={this.handleButtonClick}>LOGIN</button>;
}
},
_getPage: function(access) {
switch (access) {
case a:
return <Page />;
case b:
return <AnotherPage />;
default:
return <SomeDefaultPage />;
}
}
});
I encounter an error using Meteor. I call an Method.method.
Template.WelcomeTemplate.events({
'click #btn-findgame': function(e) {
e.preventDefault();
console.log('clicked find game button');
Meteor.call('allocateGame', function(error, id) {
if (error) {
alert(error.reason);
} if (id) {
Router.go('gameRoom', {_id: id})
}
})
}
})
With my Method, I check if there is an room available, create one when the isn't otherwise join. And return the ID of this room.
Meteor.methods({
allocateGame: function () {
console.log('allocateGame method called')
var user = Meteor.user();
// find game where one player is in the room
var gameWaiting = Games.findOne({players: {$size: 1}})
if (!gameWaiting) {
console.log('no game available, create a new one');
var newGameId = Games.insert({players: [user._id], active: false, finished: false});
GameDetails.insert({gameId: newGameId, gameData: []});
return newGameId
} else {
if (_.contains(gameWaiting.players, user._id)) {
console.log('Cannot play against yourself sir')
} else {
console.log('Joining game');
Games.update({_id: gameWaiting._id}, {
$set: {active: true},
$push: {players: user._id}
});
return gameWaiting._id;
}
};
}
})
And my Router:
Router.map(function () {
this.route('welcome', {
path: '/',
controller: WelcomeController})
this.route('gameRoom', {
path: '/game/_:id'
})
});
The Error I recieve is:
Exception in delivering result of invoking 'allocateGame': TypeError: Cannot read property 'charAt' of null
at Object.IronLocation.set (http://localhost:3000/packages/iron-router.js?e9fac8016598ea034d4f30de5f0d356a9a24b6c5:1293:12)
And indeed, If I don't return an ID the Routing will continue as normal. However when I return an ID in my WelcomeTemplate an error will occur.
EDIT:
Even thought my MongoDB is updating my MiniMongo DB is empty. There must be a problem with syncing. Any idea where to look?
In the route, you set the path to be '/game/_:id', that is, a parameter with the name id. In your call to Router.go, you pass a parameter with the name _id.
Don't know if this solves your problem, but it's an error.
This kind of embarrassing taking in account how many hours I've spent on fixing this. The error was created because of an error in my routers.js
this.route('gameRoom', {
path: '/game/_:id'
})
Should be:
this.route('gameRoom', {
path: '/game/:_id'
})
Happy coding.
I have a Meteor app with an editor page that should only be accessible to editors. I am using Iron-Router and my Router.map looks like the following. However, this is not working in an odd way. If I provide a link to the editor page, then all is well, but if I try entering the /editor url, then it always redirects to home, even if the user role is correctly set.
(One thing I ruled out was if Meteor.userId() is not set before Roles.userIsInRole is called in before.)
Anyone know why this would be?
Router.map(function() {
...
this.route('editor', {
path: '/editor',
waitOn: function() {
//handle subscriptions
},
data: function() {
//handle data
},
before: function() {
if ( !Roles.userIsInRole(Meteor.userId(), 'editor') ) {
this.redirect('home');
}
}
});
...
});
The Roles package sets up an automatic publication that sends the roles property on the Meteor.users collection. Unfortunately, you can't get a subscription handle for automatic publications, so you'll need to make your own.
Set up a new subscription that publishes the required data of a user, then configure Router to check that the data is ready before showing any page.
eg:
if (Meteor.isServer) {
Meteor.publish("user", function() {
return Meteor.users.find({
_id: this.userId
}, {
fields: {
roles: true
}
});
});
}
if (Meteor.isClient) {
var userData = Meteor.subscribe("user");
Router.before(function() {
if (Meteor.userId() == null) {
this.redirect('login');
return;
}
if (!userData.ready()) {
this.render('logingInLoading');
this.stop();
return;
}
this.next(); // Needed for iron:router v1+
}, {
// be sure to exclude the pages where you don't want this check!
except: ['register', 'login', 'reset-password']
});
}
In short, I want to do:
Meteor.publish('items', function(){
return Item.find({categoryId: Categories.find({active: true} });
});
The flag 'active' as part of 'Categories' changes regularly.
I also tried unsub/resub to the Items collection by leveraging reactivity on the Categories collections, and it works, unfortunately it re-triggers on ANY modification to the Categories collection, regardless if it affected the 'active' flag or not.
What are my options?
Nothing solved the issue of the items not being 'deleted' locally when the category is flagged as inactive on the server. Solution (ish) is to:
Client:
Categories.find({active: true}).observeChanges({
added: function(){
itemsHandle && itemsHandle.stop();
itemsHandle = Meteor.subscribe("items");
}
});
Server:
Meteor.publish('items', function(){
var category = Categories.findOne({active: true});
return category && Items.find({categoryId: Categories.findOne({active: true}._id);
});
I realize this isn't perfect (still uses client side code), but it works and its the cleanest I could think of. I hope it helps someone!
A possible solution is to create a dependency object, watch for all categories change, and trigger the dep change if the active flag was toggled. Something along these lines:
var activeCount = Categories.find({active: true}).count();
var activeDep = new Deps.Dependency();
Deps.autorun(function() {
var activeCountNow = Categories.find({active: true}).count();
if(activeCountNow !== activeCount) {
activeCount = activeCountNow;
activeDep.changed();
}
});
Meteor.publish('items', function(){
activeDep.depend();
return Item.find({categoryId: Categories.find({active: true} });
});
Note: I'm only verifying whether the number of active categories have changes so that I don't have to keep the active list in the memory. This may or may not be appropriate depending on how your app works.
Edit: Two-sided flavor mentioned in the comments:
Client:
var activeCount = Categories.find({active: true}).count();
var activeDep = new Deps.Dependency();
Deps.autorun(function() {
var activeCountNow = Categories.find({active: true}).count();
if(activeCountNow !== activeCount) {
activeCount = activeCountNow;
activeDep.changed();
}
});
Deps.autorun(function(){
activeDep.depend();
Meteor.subscribe('items', new Date().getTime());
});
Server:
Meteor.publish('items', function(timestamp) {
var t = timestamp;
return Item.find({categoryId: Categories.find({active: true} });
});
Meteor.startup(function() {
Categories.find().observe({
addedAt: function(doc) {
trigger();
},
changedAt: function(doc, oldDoc) {
if(doc.active != oldDoc.active) {
trigger();
}
},
removedAt: function(oldDoc) {
trigger();
}
});
});
Now, the trigger function should cause the publish to rerun. This time it's easy when it's on the client (change subscription param). I'm not sure how to do this on the server - perhaps run publish again.
I use the following publish to solve a similar issue. I think it is only the one line nesting of queries that limits the reactivity. Breaking one query out inside the publish function seems to avoid the issue.
//on server
Meteor.publish( "articles", function(){
var self= this;
var subscriptions = [];
var observer = Feeds.find({ subscribers: self.userId }, {_id: 1}).observeChanges({
added: function (id){
subscriptions.push(id);
},
removed: function (id){
subscriptions.splice( subscriptions.indexOf(id)) , 1);
}
});
self.onStop( function() {
observer.stop();
});
var visibleFields = {_id: 1, title: 1, source: 1, date: 1, summary: 1, link: 1};
return Articles.find({ feed_id: {$in: subscriptions} }, { sort: {date: -1}, limit: articlePubLimit, fields: visibleFields } );
});
//on client anywhere
Meteor.subscribe( "articles" );
Here is another SO example which gets the search criteria from the client through subscribe if you decide that is acceptable.
Update: Since the OP struggled to get this going I made a gist and launched a working version on meteor.com. If you just need the publish function it is as above.