Upgrading from 0.7 to 0.9: how do I access my event's keypath? - ractivejs

https://jsfiddle.net/zFiddler/gm9k4mrc/1/
Given this:
var view = new Ractive({
el : '#container',
template : "{{#item}}<a href='#' on-click='doSomething'>Do Something</a>{{/}}",
data : {item : {name: "item"}}
})
view.on('doSomething', event => {
event.original.preventDefault();
console.log(event);
alert(event.keypath);
});
How would I access the keypath for the fired event?

In 0.9, in jQuery-esque fashion, and to be consistent between component and element events, handlers attached via ractive.on will now receive an instance of the Context object as first argument which should contain everything you need to know about the event, the node it's from, the Ractive instance attached to it, and so on. DOM events should be available via the event property of that object (no longer the funny-sounding original). The keypath can be obtained by context.resolve().
view.on('doSomething', context => {
const event = context.event
event.original.preventDefault();
console.log(event);
alert(context.resolve());
});

Related

Updating data that is passed into child component Angular2

I am new to development in Angular2 - Meteor. I have stumbled upon a problem while working with parent-child components. In the parent component, I have meteor subscribing to a collection.
users: any;
constructor(private _zone:NgZone){
var sub = Meteor.subscribe("accounts/users");
Tracker.autorun(function() {
_zone.run(function() {
if(sub.ready()){
this.users = Meteor.users.find().fetch();
console.log(this.users);
}
});
});
}
The user collection has 90 users in total. When the app initially loads, only the current user is found, thus the console log shows one user.
I am not sure if my placement of Tracker.autorun() and NgZone are correct, but after a second of the app loading, the console log shows an array of all 90 users. I assume this happens because the subscribe is not ready at first.
My child component takes in the fetched users as a parameter like this <sd-table [data]="users"></sd-table>. Upon loading of the application, there is only one user that is seen on the drawn template of the child component. Is there any way that the template can be updated when the subscribe happens and all users become accessible?
If you want to refer to this of the current class, don't use function()
users: any;
constructor(private _zone:NgZone){
var sub = Meteor.subscribe("accounts/users");
Tracker.autorun(() => {
_zone.run(() => {
if(sub.ready()){
this.users = Meteor.users.find().fetch();
console.log(this.users);
}
});
});
}
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Functions/Arrow_functions

Meteor.js: template.<html>.events vs Template.<template>.events 'this' binding seems inconsistent

I'm looking through the Meteor simple tutorial and the way that 'this' binding in the different Template objects works seems inconsistent to me in my unknowledgeable state.
Template.body.events({
"submit .new-task": function(event) {
console.log(this); // Logs an empty object
}
})
Template.task.events({
"click .toggle-checked": function() {
console.log(this); // logs a task
}
});
I can see that task is an xml template defined in the view, which is a visual representation of the items returned by a function in the Template.body.helpers object.
I guess that the task objects are bound the html representation of each object (though I can't see how as there doesn't seem to be any identifying property within the li elements??)
Anyhow. When I click the task, this is the task. But when I submit the form, I was expecting this to be the body. Why is it not?
I was expecting Meteor to handle Template.body and Template.task in a similar way
In Meteor this referes to the data context. You define it with helpers or with the route controller ( IronRouter or FlowRouter)
Example:
{{#with myData}}
<h1>{{title}}</h1>
{{/with}}
js
Template.yourTemplate.helpers({
myData : function(){
return {
title : "My title"
}
}
})
You need to use the "event" argument
Template.task.events({
"click .toggle-checked": function( event , instance ) {
console.log( event );
}
});
The instance argument is also very useful. You have access to a jQuery selector like: instance.$() and it will only search for elements on your template and also child templates.
Personally I use the instance a lot. My Favorite pattern is:
Template.task.onCreated(function(){
this.vars = new ReactiveDict();
this.data = "some data";
});
Later if you want to access vars or data:
Events - You get this on the arguments
Helpers - var instance = Template.instance();
With instance you avoid storing states in the global namespace, like Session, and your code is a lot easier to maintain and understand. I hope this helps you to understand how template works in Blaze.

Ractive proxy event keypath missing

ref: jsfiddle
I have this:
var Item = Ractive.extend({
template : "<p on-click='doSomething'>{{name}}</p>"
})
var collection = new Ractive({
el : '#container',
template : "{{#items}}<item name='{{name}}'/>{{/items}}",
data : {items : [{name: "first item"},{name: "second item"}]},
components : {item : Item}
})
collection.on('item.doSomething',function(event){
console.dir(event)
})
works fine except that the value of event.keypath is "". Why is this?
The keypath context of the event is the root template context of the component, in this case the root .. In general keypaths don't necessarily bubble up well from component to parent, as they may be totally different.
Recasting events initially looks like an attractive option:
{{#items}}<item name='{{name}}' on-doSomething='doSomething'/>{{/items}}
But there a few open issues as component events are definitely in need of some love.
They don't quite cut it in this case as you don't get what you need (and expect) which is the keypath in the calling parent context (items.0 for example) and you can't supply arguments like on-doSomething='doSomething:{{#keypath}}'.
What you can do is pass the needed info into the component
{{#items}}<item name='{{name}}' keypath='{{#keypath}}' index='{{#index}}'/>{{/items}}
and then use the event.component reference to access the data:
collection.on('item.doSomething',function(){
console.dir(this.event.component.get('keypath'));
})
see http://jsfiddle.net/6owmfeaj/14/

Bootboxjs: how to render a Meteor template as dialog body

I have the following template:
<template name="modalTest">
{{session "modalTestNumber"}} <button id="modalTestIncrement">Increment</button>
</template>
That session helper simply is a go-between with the Session object. I have that modalTestNumber initialized to 0.
I want this template to be rendered, with all of it's reactivity, into a bootbox modal dialog. I have the following event handler declared for this template:
Template.modalTest.events({
'click #modalTestIncrement': function(e, t) {
console.log('click');
Session.set('modalTestNumber', Session.get('modalTestNumber') + 1);
}
});
Here are all of the things I have tried, and what they result in:
bootbox.dialog({
message: Template.modalTest()
});
This renders the template, which appears more or less like 0 Increment (in a button). However, when I change the Session variable from the console, it doesn't change, and the event handler isn't called when I click the button (the console.log doesn't even happen).
message: Meteor.render(Template.modalTest())
message: Meteor.render(function() { return Template.modalTest(); })
These both do exactly the same thing as the Template call by itself.
message: new Handlebars.SafeString(Template.modalTest())
This just renders the modal body as empty. The modal still pops up though.
message: Meteor.render(new Handlebars.SafeString(Template.modalTest()))
Exactly the same as the Template and pure Meteor.render calls; the template is there, but it has no reactivity or event response.
Is it maybe that I'm using this less packaging of bootstrap rather than a standard package?
How can I get this to render in appropriately reactive Meteor style?
Hacking into Bootbox?
I just tried hacked into the bootbox.js file itself to see if I could take over. I changed things so that at the bootbox.dialog({}) layer I would simply pass the name of the Template I wanted rendered:
// in bootbox.js::exports.dialog
console.log(options.message); // I'm passing the template name now, so this yields 'modalTest'
body.find(".bootbox-body").html(Meteor.render(Template[options.message]));
body.find(".bootbox-body").html(Meteor.render(function() { return Template[options.message](); }));
These two different versions (don't worry they're two different attempts, not at the same time) these both render the template non-reactively, just like they did before.
Will hacking into bootbox make any difference?
Thanks in advance!
I am giving an answer working with the current 0.9.3.1 version of Meteor.
If you want to render a template and keep reactivity, you have to :
Render template in a parent node
Have the parent already in the DOM
So this very short function is the answer to do that :
renderTmp = function (template, data) {
var node = document.createElement("div");
document.body.appendChild(node);
UI.renderWithData(template, data, node);
return node;
};
In your case, you would do :
bootbox.dialog({
message: renderTmp(Template.modalTest)
});
Answer for Meteor 1.0+:
Use Blaze.render or Blaze.renderWithData to render the template into the bootbox dialog after the bootbox dialog has been created.
function openMyDialog(fs){ // this can be tied to an event handler in another template
<! do some stuff here, like setting the data context !>
bootbox.dialog({
title: 'This will populate with content from the "myDialog" template',
message: "<div id='dialogNode'></div>",
buttons: {
do: {
label: "ok",
className: "btn btn-primary",
callback: function() {
<! take some actions !>
}
}
}
});
Blaze.render(Template.myDialog,$("#dialogNode")[0]);
};
This assumes you have a template defined:
<template name="myDialog">
Content for my dialog box
</template>
Template.myDialog is created for every template you're using.
$("#dialogNode")[0] selects the DOM node you setup in
message: "<div id='dialogNode'></div>"
Alternatively you can leave message blank and use $(".bootbox-body") to select the parent node.
As you can imagine, this also allows you to change the message section of a bootbox dialog dynamically.
Using the latest version of Meteor, here is a simple way to render a doc into a bootbox
let box = bootbox.dialog({title:'',message:''});
box.find('.bootbox-body').remove();
Blaze.renderWithData(template,MyCollection.findOne({_id}),box.find(".modal-body")[0]);
If you want the dialog to be reactive use
let box = bootbox.dialog({title:'',message:''});
box.find('.bootbox-body').remove();
Blaze.renderWithData(template,function() {return MyCollection.findOne({_id})},box.find(".modal-body")[0]);
In order to render Meteor templates programmatically while retaining their reactivity you'll want to use Meteor.render(). They address this issue in their docs under templates.
So for your handlers, etc. to work you'd use:
bootbox.dialog({
message: Meteor.render(function() { return Template.modalTest(); })
});
This was a major gotcha for me too!
I see that you were really close with the Meteor.render()'s. Let me know if it still doesn't work.
This works for Meteor 1.1.0.2
Assuming we have a template called changePassword that has two fields named oldPassword and newPassword, here's some code to pop up a dialog box using the template and then get the results.
bootbox.dialog({
title: 'Change Password',
message: '<span/>', // Message can't be empty, but we're going to replace the contents
buttons: {
success: {
label: 'Change',
className: 'btn-primary',
callback: function(event) {
var oldPassword = this.find('input[name=oldPassword]').val();
var newPassword = this.find('input[name=newPassword]').val();
console.log("Change password from " + oldPassword + " to " + newPassword);
return false; // Close the dialog
}
},
'Cancel': {
className: 'btn-default'
}
}
});
// .bootbox-body is the parent of the span, so we can replace the contents
// with our template
// Using UI.renderWithData means we can pass data in to the template too.
UI.insert(UI.renderWithData(Template.changePassword, {
name: "Harry"
}), $('.bootbox-body')[0]);

fullCalendar adding a class to events

I am trying to select events on fullcalendar, based on user selection.
Example: if user selects class A, then all classes with the same ID should turn green (using applied className).
I am having trouble applying classes to the other events that I can successfully select by ID. I guess my issue is combining the event objects with jQuery objects.
sample code:
eventClick: function(event) {
$(this).addClass("reg_selected"); //this works fine on selected event
var selectedID = event.id
alert(selectedID); //get event.ID, and use it to find similar ones.
var similarEvents = $("#calendar").fullCalendar('clientEvents',selectedID).addClass("reg_selected");
the error I get is:
addClass is not a function
I also tried this method of looping, and got the same error:
for (var i = 0; similarEvents.length > i ; i++){
alert(similarEvents[i].title);
similarEvents[i].className("reg_selected");
}
the alert() worked, but the className() generated the same error as above
This answer for a very similar situation, but when event classes are selected with round-trip to the event source for possible persistence in the db or checks.
Class name can be specified in the event object in the source as follows (start and end given for the context only):
[{
...
"className": "selected-event",
"start": '2017-05-01T08:30:00.0',
"ends": '2017-05-01T09:00:00.0',
...
}, ...]
The idea is that user clicks the event; ajax call to select events goes to backend; onsuccess, frontend javascript does$calendar.fullCalendar('rerenderEvents'); and receives the event source with events' classes. The immediate child of .fc-event-container gets the specified class, in the example above - selected-event.
As a result, the selection can be persisted on the backend.
clientEvents returns an array of matching objects. You need to iterate through the array (in your case similarEvents) and call addClass for each item
Update:
There is also issues using an id to update multiple events, using a filter function instead is a better way to go.
eventClick: function(event) {
var similarEvents = $("#calendar").fullCalendar('clientEvents', function(e) { return e.test === event.test });
for (var i = 0; similarEvents.length > i ; i++){
similarEvents[i].className = 'reg_selected';
$('#calendar').fullCalendar('updateEvent', similarEvents[i]);
}
},
See jsfiddle
For fullcalendar add event class, id and title see this.
if($('#eventTitle').val() == "Avilable") {
eventClass = "avilable";
}else {
eventClass = "unavilable";
}
$myCalendar.fullCalendar('renderEvent', {
id:response,
title: title.val(),
start: start.val(),
end: end.val(),
allDay: true,
className: eventClass,
color: color
}, true
);
I was able to get it working with the following code:
eventRender: function (eventObj, $el) {
$el.addClass(eventObj.ClassName);
},
eventObj.ClassName = "calendar-priority-warning"

Resources