Reputation: 2156
I'm very new to meteor, so apologies if I'm missing something very basic here.
I thought it would be fun to create a very simple textpad style app to check out meteor. I took the todo app and changed the data structures to be 'folders' and 'docs' rather than 'lists' and 'todos', so I have a list of folders and when you click on the folder you get a list of the documents in that folder.
I've then added some code to show the 'content' attribute of a single 'doc' when one of the docs in the list is clicked.
I'm using ace to add some pretty print to the content of the doc (https://github.com/ajaxorg/ace). I've set ace up to work with a hidden textarea containing the plaintext version of my document, and the editor object takes this text and pretty prints it.
The problem with ace is that I don't want the template containing the ace editor to be replaced every time the contents of the doc changes (as it takes half a second to reinitialise, which is a crappy experience after every character is typed!). Instead, I want to update the textarea template and then use the ace API to tell the editor to update it's input based on what is in the textarea.
Now, this is probably the wrong way to approach the problem, but I've ended up using two templates. The first contains a textarea containing doc.contents, which is reactive to the underlying model:
<template name="doc_content">
<textarea name="editor">{{content}}</textarea>
</template>
The second one contains the 'editor' div which ace uses to display the pretty printed text.
<template name="doc_init">
<div id="editor"></div>
</template>
The idea is that the first template will update every time the user types (on all clients), and the second template is only ever re-loaded for each new doc we load.
Template.doc_content.content = function() {
var doc_id = Session.get('viewing_itemname');
if (!doc_id) {
return {};
}
var doc = Docs.findOne({_id:doc_id});
if (doc && doc.content) {
// #1 Later
var editor = Session.get('editor');
if (editor) {
editor.getSession().setValue(doc.content);
}
return doc.content;
} else {
return '';
}
};
When you enter text into the editor div I make a call to Docs.update(doc_id, {$set: {content: text}});
, which updates the value in the textarea on each client. All good so far.
editor.getSession().on('change', function(){
var text = editor.getSession().getValue();
Docs.update(doc_id, {$set: {content: text}});
});
What I want to do, for all clients other than the client which made the change, is to subscribe to the change for that doc and call editor.getSession().setContent() with the text which has just been changed, taking the text from the textarea and using it to fill the editor.
I've tried to do this by making that call from the template containing the textarea (as this changes whenever the doc is updated - see #1 above). However, this puts the clients into an infinite loop because changing the value in the editor causes another call to Docs.update
.
Obviously this doesn't happen when you render a template, so I'm assuming there's some magic in meteor which can prevent this happening, but I'm not sure how.
Any thoughts?
TIA!
Upvotes: 4
Views: 1691
Reputation: 2820
There's a lot to absorb in your question, but if I understand correctly, you might simply be after Deps.autorun
:
Deps.autorun(function () {
var doc_id = Session.get('viewing_itemname');
if (!doc_id) {
return {};
}
var doc = Docs.findOne({_id:doc_id});
// do stuff with doc
});
Deps.autorun
is really useful in that it will get re-run if any of its
dependencies change. These dependencies are limited to those that are "reactive"
such as Collections and Sessions, or anything that implements the reactive API.
In your case, both Session.get
and findOne
are reactive so if their values
change at all, Deps.autorun
will run the function again.
Upvotes: 4