简体   繁体   中英

What is best practice for app/user settings in Meteor.js?

I've looked around quite a bit for Meteor examples showing how to implement app settings and user specific settings. The only thing I found was Telesc.pe . And it uses a Settings collection. However, it only has a global (meaning same for everyone) app settings.

Building on that example, I've created my own settings collections which is available on both the server and the client.

// Server and Client
Settings = new Meteor.Collection('settings');

In each Settings record there is a userId field which is equal to 'Default' or the user's id.

{
  ...
  userId: 'Default' // <-- 'Default' or Meteor.userId()
}

I have my publish function publishing both the Default (app) settings AND the user's settings. (Side note: in this app everyone IS logged in, no guests allowed)

// Server
Meteor.publish('settings', function() {
  return Settings.find({userId: {$in: [this.userId, 'default']}});
});

The idea here is that the user will use the Default settings until they change a settings thereby reducing the number of records in the collection.

I've also tried to abstract away a lot of the tedious stuff and create a few helpers to get and set settings for the user.

// Server and Client

// get user specific settings, fallback to default settings
// (not sure if this is the best way, but it works)
settings = function() {
  return Settings.findOne({userId:Meteor.userId()}) 
      || Settings.findOne({userId:'default'});
};

// Get value of a specific setting
getSetting = function(key) {
  return Session.get('settingsLoaded') ? settings()[key] : undefined;
};

// Set value of a specific setting
setSetting = function(key, value) {
   // bunch of logic here to see if a user specific record exists
   // if so, do update
   // if not, do insert
};

So far, this implementation seems to be working fairly well. I can set and get settings in the console via m helper functions.

// in the console...
setSetting('foo', 'bar');
getSetting('foo') // == 'bar'

The issue I'm having happens when I go to start making my app act differently based on certain settings. For example, I have a template called 'phrases' with a variable inside called 'phrases'. I want to change how they are sorted based on the user's settings.

Template.phrases.phrases = function () {
  var sort = {};

  var sortKey = getSetting('sortPhrasesBy'); // if I console.log this it is ALWAYS equal to 'title', 'body', or 'date', which is what I want.
  sort[sortKey] = 1;

  // returns cursor with all phrases, sorted
  return Phrases.find({}, {sort:sort});
};

Except that I keep getting Deps exceptions and I can't tell what is wrong.

Exception from Deps recompute: TypeError: Cannot read property 'nodeName' of null
at Patcher.match (http://localhost:3000/packages/spark.js?3a050592ceb34d6c585c70f1df11e353610be0ab:1540:12)
at http://localhost:3000/packages/spark.js?3a050592ceb34d6c585c70f1df11e353610be0ab:1364:23
at visitNodes (http://localhost:3000/packages/spark.js?3a050592ceb34d6c585c70f1df11e353610be0ab:1320:11)
at visitNodes (http://localhost:3000/packages/spark.js?3a050592ceb34d6c585c70f1df11e353610be0ab:1321:9)
at visitNodes (http://localhost:3000/packages/spark.js?3a050592ceb34d6c585c70f1df11e353610be0ab:1321:9)
at visitNodes (http://localhost:3000/packages/spark.js?3a050592ceb34d6c585c70f1df11e353610be0ab:1321:9)
at visitNodes (http://localhost:3000/packages/spark.js?3a050592ceb34d6c585c70f1df11e353610be0ab:1321:9)
at patch (http://localhost:3000/packages/spark.js?3a050592ceb34d6c585c70f1df11e353610be0ab:1334:3)
at http://localhost:3000/packages/spark.js?3a050592ceb34d6c585c70f1df11e353610be0ab:698:7
at LiveRange.operate (http://localhost:3000/packages/liverange.js?b3097d72d458e645fd4f0021c8ff5189abe8d98a:491:9)

I have no idea what could be causing this. :/

However, this code actually works! It does actually sort the phrases based on what the user has set. But in the console I can see this exception being thrown every time the settings are changed. First load is fine.

So, am I doing something fundamentally wrong here? I must admit I don't yet have my head fully wrapped around what Meteor is doing behind the curtain yet.

I don't know if this is helpful or not. But before I tried implementing a settings collection, I used the Session object. So I had something like this:

// on client
Session.setDefault('sortPhrasesBy', 'title);

Template.phrases.phrases = function () {
  var sort = {};

  var sortKey = Session.get('sortPhrasesBy');
  sort[sortKey] = 1;

  // returns cursor with all phrases, sorted a certain way
  return Phrases.find({}, {sort:sort});
};

This worked without issue. It's just not real flexible.

Is there another way I should be doing this? I'd love to know what the guys building meteor are doing for settings in there personal testing/projects if anyone happens to know.

Sorry so long of a post, I was trying to anticipate what questions might be asked about what I already tried and such.

Thanks for any help you can provide!

The Meteor Accounts system (ex. accounts-password) has the concept of user-specific settings built-in as the 'profile' field of the user object. It is automatically published and is reactive as well (since Meteor.user() is reactive).

Here are the relevant docs: http://docs.meteor.com/#meteor_user

Regarding the "nodeName" error, it is hard to debug but generally when I get that error it is because I am trying to access part of the DOM in a template helper that doesn't actually exist. I've also seen it when I accidentally had two DOM elements with the same ID (fairly easy to do with sub-templates).

To track it down I remove code until it stops happening then add it back piece-by-piece until I find the root cause.

The user-session smart package I wrote for Meteor is perfect for this use-case.

It's basically like Meteor's Session , but each variable is related to a user. It's reactive, and all the changes are persistent. It's available on the client, and on the server with an additional userId argument.

The error here looks like you have created a server side Meteor.publish() but not a corresponding Meteor.subscribe() in the client. You are returning an empty set and attempting to change properties on it. You can put the subscribe in a Deps.autorun block, or just include it directly in the helper, like so:

Template.phrases.phrases = function () {
  var sort = {};
  Meteor.subscribe('settings');

  var sortKey = getSetting('sortPhrasesBy'); // if I console.log this it is ALWAYS equal to 'title', 'body', or 'date', which is what I want.
  sort[sortKey] = 1;

  // returns cursor with all phrases, sorted
  return Phrases.find({}, {sort:sort});
};

You probably would want to use a package like iron-router, which has hooks to insure youactually have your subscription before the data is loaded. See Iron Router .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM