using contenteditable with wysiwyg in meteor - meteor

I'm trying to use redactor.js to edit in place some divs in meteor;
in the template I have:
<template name="home">
<section class="editable">
</section>
...
</template>
and in the js:
Template.home.events({
"click section.editable": function(event) {
$(event.target).redactor();
},
});
this creates the redactor wysiwyg editor correctly when I click on the section; the problem is that by clicking again, another editor (nested inside the previous one is created); I'm trying without success to limit the execution of redactor() method only if the editor is not there already.

Could you wrap the state in a session variable? Of course, you'd need to set it back again once the redactor was finished (maybe try hooking into a blur event?)
Template.home.events({
"click section.editable": function(event) {
var isEditorActive = Session.get("editorActive", false);
if (!isEditorActive) {
Session.set("editorActive",true);
$(event.target).redactor({
blurCallback: function(e)
{
Session.set("editorActive",false);
this.core.destroy(); // destroy redactor ?
}
});
}
}
});
PREVIOUSLY:
Is there a particular reason you want to use meteor events for this? You could just use redactor.
if (Meteor.isClient) {
Meteor.startup(function() {
$('section.editable').redactor({
focus: false
});
});
}

Related

meteor simple reactive var

I'm working in meteor trying to use a reactive var to switch the content in the main panel between two tabs. I've been able to test the content successfully on it's own so I'm fairly confident the issue lies in the reactive var code. Specifically I think the issue is with the tab: function() but after many searches and reading documentation I haven't found a solution.
The relevant js:
Template.content.onCreated( function() {
this.currentTab = new ReactiveVar('form');
});
Template.content.helpers({
tab: function() {
return Template.instance().currentTab.get();
}
});
Template.content.events({
'click .nav li': function (event, template) {
var currentTab = $( event.target ).closest( "li" );
currentTab.addClass( "active" );
$( ".nav li" ).not( currentTab ).removeClass( "active" );
Template.currentTab.set();
}
});
The relevant html:
<template name ="content">
<ul class ="nav">
<li data-template="form">Form</li>
<li data-template="results">Results</li>
</ul>
{{ > Template.dynamic template=tab}}
</template>
{{ > Template.dynamic template=tab}}
This is calling the tab helper to get a string that is the name of the template you want to show here. It should work the first time because you start out by setting the value of currentTab to "form".
To change the template that gets shown, you need to change the value of currentTab to a string matching the name of the new template. You're not doing that.
Template.currentTab.set();
This is where you should be doing that. Instead you're calling set() on the currentTab property of Template, which I don't think exists. Template with a capital T is a Meteor object, not the template instance that I think you're trying to refer to. And to set a new value for currentTab, you actually need to provide a value. Like so:
Template.content.events( {
'click .nav li': function(event, instance) {
//logic to decide which template you want to show
//and put the name of that template in templateName
instance.currentTab.set(templateName)
}
});

Polymer two way binding

Banging my head on how to use polymer two way binding.
I have a home made Polymer element that defines a boolean property through
Polymer({
is: "test-element",
ready: function() {},
properties: {
propEnabled: {
type: Boolean,
notify: true,
value: false,
observer: "propEnabledChanged"
}
},
// Called when aoEnabled is changed
propEnabledChanged: function() { console.log("propEnabled value switched to " + this.propEnabled); },
});
Now I'm using this in an HTML page
<body>
<template id="t" is="dom-bind">
<test-element id="testElement"></test-element>
<paper-toggle-button checked="{{Model.propEnabled}}">prop. enabled</paper-toggle-button>
<button id="switchInternalState">switch state</button>
</template>
</body>
<script>
var t = document.querySelector('#t');
document.addEventListener('WebComponentsReady', function() {
console.log('WebComponentsReady');
// We have to bind the template with the model
var t = document.querySelector('#t');
t.Model = document.getElementById("testElement");
// chaging the property directly does not reflect in the GUI... :-(
var button = document.getElementById("switchInternalState");
button.addEventListener("click", function() {
t.Model.set("propEnabled", !t.Model.propEnabled);
});
});
</script>
But when clicking on the switch state button...
I get the log propEnabled value switched to true
But the toogle button on the page does not change...
If I add a simple
<label>{{Model.propEnabled}}</label>
The label does not change either...
To me it looks a bit like one way binding where it should be 2 way as
toggling the button fire the log and properly change the component propEnabled value. So it really looks like one way binding to me.
So... How can we actually benefit from two way binding with Polymer templates ????
You need to assign the propEnabled property from dom-bind to the test-element through html.
<test-element id="testElement" prop-enabled={{propEnabled}}></test-element>
<paper-toggle-button checked="{{propEnabled}}">prop. enabled</paper-toggle-button>
Also you don't need the variable t.Model in your script. You can remove it. The event listener should be like below
button.addEventListener("click", function() {
t.propEnabled = !t.propEnabled;
});
The following plunker has the working code: http://embed.plnkr.co/13QJ7QFETIBg4bEiCMS7/preview

When the page loads scroll down (not so simple) : Meteor JS

I have chat app, and if you use Slack you know that when you enter to the room, you will automatically find yourself at the bottom of your chat room.
So I decided to do this and what I have
Template.room.onCreated(function() {
console.log($(document).height(),$(window).height());
$('html, body').scrollTop($(document).height()-$(window).height());
});
it output 437 , 437
BUT when I do this in console:
$('html, body').animate({ scrollTop: $(document).height()-$(window).height() + 64}, "fast");
it outputs 2000,437 , and that means that my messages is not loaded fully in my template. (If someone want more code, just ask.)
Any idea how to build this ?
EDIT:
This part of template.html
<div class="messages-wrap">
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
{{#if haseMoreMessages}}
<div class="loadmore text-center" id="incLimit">Load More</div>
{{/if}}
{{#if Template.subscriptionsReady }}
{{#each messages}}
{{> message}}
{{/each}}
{{/if}}
</div>
</div>
</div>
</div>
And template.js (part of it)
Template.room.onRendered(function() {
Session.setDefault('messageLimit', 200);
});
Template.room.onCreated(function() {
var self = this;
self.autorun(function() {
if (self.subscriptionsReady()) {
Tracker.afterFlush(function () {
$('html, body').scrollTop($(document).height() - $(window).height());
});
}
});
});
Template.room.events({
'click #incLimit' : function(e,template){
Session.set('messageLimit',Session.get('messageLimit') + 100);
}
});
Template.room.helpers({
messages: function(){
return Messages.find({},{sort:{createdAt:1}});
},
haseMoreMessages:function(){
if (Session.get('messageLimit') > Messages.find().count()) return false;
return true;
},
});
This is one very frustrating aspect of Blaze. Try this, though:
Template.room.onRendered(function () {
var template = this;
this.autorun(function () {
if (template.subscriptionsReady()) {
Tracker.afterFlush(function () {
$('html, body').scrollTop($(document).height() - $(window).height());
});
}
});
});
This waits till all the template subscriptions are ready first, and then waits till any computations are fully complete (Tracker.afterFlush), and then executes the scroll. Tracker.afterFlush is usually necessary if your template has {{#if}} blocks that depend on other things before they get evaluated and rendered.
UPDATE:
Without seeing all your code and knowing why or when you want to scroll to the top, it's hard to say what you're aiming for. But see the Meteorpad link below for a working version of what you were trying to do with the message limits (I'm only incrementing the limit by 1 since there are 3 messages).
Meteorpad Link
A few things you should note:
Set default variables and subscribe to things in Template.x.onCreated, not Template.x.onRendered.
You forgot to actually subscribe to your collection.
Limit messages on the server side, in the Meteor.publish callback.
Different Approach via tracking of Template helpers:
I took advantage of the Template helper, as it already tracks reactively all changes (and new) messages. Hence, there you can place your scroll-down command.
Template.room.helpers({
messages: function(){
//put your scroll-up command here
//as this helper is keeping track on changes already
$('html, body').scrollTop($(document).height() - $(window).height());
return Messages.find({},{sort:{createdAt:1}});
},
haseMoreMessages:function(){
if (Session.get('messageLimit') > Messages.find().count()) return false;
return true;
},
});
Hence, you save the resources to have an additional tracker observing the collection (you would be double-tracking the same collection).

Magnific-popup fails to open from button inside Google Maps infoBox

I have an infoBox that opens when clicking on a google maps marker. Inside the infoBox there is a button '#open-popup' that when clicked should to open a magnificPopup modal window but nothing happens.
As a test I have put the same button outside the div containing the google map, which opens the modal window but only on the second click! What is going on?
I have tried all sort of things for days but all have worst side effects.
Any help will be much appreciated.
HTML for button inside infoBox:
<div style=...>
<centre>
<button id="open-popup">Open popup</button>
<div id="my-popup" class="mfp-hide white-popup">Inline popup</div>
</center>
</div>
HTML for button outside google maps div:
<button id="open-popup">Open popup</button>
<div id="my-popup" class="mfp-hide white-popup">Inline popup</div>
JS:
myapp.triggerClick = function (){
google.maps.event.trigger(gmarkers[id],"click")
};
var infoboxOptions = {
content: ''
,disableAutoPan: false
,alignBottom: true
,maxWidth: 0
,pixelOffset: new google.maps.Size(0, 0)
,zIndex: 1000
,boxStyle: {
background:''
,opacity: 0.9
}
,closeBoxMargin: "4px 4px 0 0"
,closeBoxURL: "http://www.google.com/intl/en_us/mapfiles/close.gif"
,infoBoxClearance: new google.maps.Size(1,1)
,isHidden: false
,pane: "floatPane"
,enableEventPropagation: false
};
var ib = new InfoBox(infoboxOptions);
function createMarker(latlng, html, id) {
var contentString = html;
var marker = new google.maps.Marker({
position: latlng,
map: map
//zIndex: Math.round(latlng.lat()*-100000)<<5
});
google.maps.event.addListener(marker, 'click', function() {
ib.setContent(contentString);
ib.open(map,marker);
});
gmarkers[id] = marker;
}
$(document).on('click', '#open-popup', function () {
$(this).magnificPopup({
items: {
src: 'http://upload.wikimedia.org/wikipedia/commons/thumb/6/64/Peter_%26_Paul_fortress_in_SPB_03.jpg/800px-Peter_%26_Paul_fortress_in_SPB_03.jpg'
},
type: 'image' // this is default type
});
});
Try placing your jQuery click event inside a map event listener, that is set to fire when an infobox is clicked. A map event listener is needed because click events inside a google map are handled by the Google Map API.
So for you, something like:
window.google.maps.event.addListener(ib, "domready", function () {
$('#open-popup').on('click', function () {
$(this).magnificPopup({
items: {
src: 'http://upload.wikimedia.org/wikipedia/commons/thumb/6/64/Peter_%26_Paul_fortress_in_SPB_03.jpg/800px-Peter_%26_Paul_fortress_in_SPB_03.jpg'
},
type: 'image' // this is default type
});
});
});
Notice that I updated the selector for your on click event also. You may want to play around with that as the usual on selector syntax wouldn't work in my case, e.g. $('.preExistingElementSelector').on('click', '.dynamicElementSelector', function(){});
The infobox is dynamically added and removed from the dom every time it is shown and
closed. So what the function above is essentially doing is, after the new infobox instance has been added to the dom (i.e. is visible), add this new click event to it. Once you close that infobox, it is removed from the dom which also means that the event handler you attached to it is gone also. Which is why we add a new one each time.
I'm sure there's probably a neater solution out there, I just haven't had time to find one!
Also, make sure to keep enableEventPropagation: false in the infobox options so that the click event doesn't get swallowed by Google maps.
UPDATE
Here is a working example: http://jsfiddle.net/gavinfoley/4WRMc/10/
What you really need to be able to open the magnific popup through the API. So the only change I made was changing
$(this).magnificPopup({...});
to
$.magnificPopup.open({...});
And that solved it.
window.google.maps.event.addListener(ib, "domready", function () {
$('.open-popup').on('click', function () {
// Open magnificPopup through API
// See http://dimsemenov.com/plugins/magnific-popup/documentation.html#inline_type
$.magnificPopup.open({
items: {
src: 'http://upload.wikimedia.org/wikipedia/commons/thumb/2/23/0418_-_Palermo%2C_Museo_archeologico_-_Testa_dal_tempo_E_di_Selinunte_-_Foto_Giovanni_Dall%27Orto.jpg/180px-0418_-_Palermo%2C_Museo_archeologico_-_Testa_dal_tempo_E_di_Selinunte_-_Foto_Giovanni_Dall%27Orto.jpg'
},
type: 'image' // this is default type
});
});
});

in Meteor, how do i update a property on only one instance of a template?

If I have an {{# each}} binding in Meteor, and I want to update a property on only one instance of the template inside the #each. How would I do that? I've tried setting a value on the "template" object inside the events map, but that doesn't seem to be reactive. I've also tried binding to a Session property, but that will cause every instance to update instead of just the one I want...
for example:
{{#each dates}}
{{> dateTemplate}}
{{/each}}
<template name="dateTemplate">
{{date}}
<span style="color: red;">{{errorMsg}}</span> <--- how do i update errorMsg?
</template>
Template.dateTemplate.events({
'click': function(event, template) {
template.errorMsg = 'not valid'; <--- this doesn't do anything
}
});
EDIT TO ADDRESS ANSWER BELOW:
Template.dateTemplate.events({
'click': function(event, template) {
template.errorMsg = function() { return 'not valid';} <--- this also doesn't do anything
}
});
You don't have to use handlebars for this, because its not something that needs reactivity to pass the message through, reactive variables work best with db data, or data that would be updated by another client over the air.
You could use JQuery (included by default) to update it, it can also get a bit fancier:
<template name="dateTemplate">
{{date}}
<span style="color: red;display: none" class="errorMessage"></span>
</template>
Template.dateTemplate.events({
'click': function(event, template) {
$(template.find('.errorMessage')).html('Your Error Message').slideDown();
}
});
Ive edited it so the error is hidden by default, and slides down with an animation
I'm experimenting handling this by passing a different reactive object to each instance of the template. Then the template can bind to the reactive object (which is unique per instance) and we don't have any extra boilerplate.
It ends up looking like this:
Initial render:
Template.firstTemplateWithPoll(ContextProvider.getContext())
Template.secondTemplateWithPoll(ContextProvider.getContext())
// (I actually pass getContext an identifier so I always get the same context for the same template)
JS:
Template.poll.events = {
'click .yes' : function() {
this.reactive.set('selection', 'yes');
},
'click .no' : function() {
this.reactive.set('selection', 'no');
}
};
Template.poll.selection = function(arg) {
return this.reactive.get('selection');
}
Template:
<template name="poll">
<blockquote>
<p>
Your selection on this poll is {{selection}}
</p>
</blockquote>
<button class='yes'>YES</button>
<button class='no'>NO</button>
</template>
template.errorMsg should be a function that returns your error.
Template.dateTemplate.events({
'click': function(event, template) {
template.errorMsg = function() { return 'not valid'; };
}
});

Resources