global pub-sub/event-handling in ractive - ractivejs

I'm trying to determine the best way to establish cross-component-communication. My first thought was to use ractive.fire with a wildcard, but that doesn't seem to work. Am I trying to mis-use ractive.fire? What would be the suggested way for doing cross-component-communication with ractive?
Ractive.components.pubSub = Ractive.extend({
oninit() {
this.on('*.customEvent', () => alert('pub sub got your custom event!'))
}
})
Ractive.components.something = Ractive.extend({
template: '#something'
})
let ractive = new Ractive({
target: 'body',
template: '#app'
})
<script src="https://cdn.jsdelivr.net/npm/ractive#0.10.3/ractive.js"></script>
<script id="app" type="text/ractive">
<pubSub />
<something />
</script>
<script id="something" type="text/ractive">
<button on-click="#.fire('customEvent')">Fire Custom Event</button>
</script>

Ractive doesn't prescribe a convention for data sharing/cross-component communication. However, it does give you the facilities to do it. A common practice I've seen is to create a "dummy instance" and use its ractive.fire() and ractive.on() methods.
// The dummy instance, make it visible to components.
const pubsub = Ractive()
const SourceComponent = Ractive.extend({
template: '<button click="send()">Click me</button>',
send(){
pubsub.fire('message')
}
})
const ListeningComponent = Ractive.extend({
onInit(){
pubsub.on('message', () => {
console.log('called')
})
}
})
Alternatively, if all you want is to share state across all components, modify them anywhere you want, and have everyone re-render on change, you can put that state in #shared.

Related

Filter Collections using iron-router

I have a template rendering my Collection using {{#each Collection}}, I'm using iron-router to route this template like this:
Router.route('/home', function () {
this.render('home');
this.layout('header');
});
how can i use a "books" filter button so iron-router apply that filter to the subscription so i can only see the filtered version of my Collection like the following:
Collection.find({category: "books"});
There are a number of ways to approach this but one simple way would be to define a sub-route that takes a category as a parameter:
Router.route('/collections/:category',{
name: 'collections',
layoutTemplate: 'header',
data(){
return Collections.find({category: this.params.category});
}
});
Then your button code can just do Router.go('/collections/books') but you can now have multiple buttons each tied to different categories.
As suggested here https://guide.meteor.com/data-loading.html#organizing-subscriptions you can manage your subscription in Template and not in the route. So you can do:
import {ReactiveDict} from 'meteor/reactive-dict';
import {Template} from 'meteor/templating';
Template.home.onCreated(function(){
var that = this;
that.state = new ReactiveDict();
//that.state.set('selected-category',YOUR_DEFAULT_CATEGORY);
that.autorun(function(){
that.subscribe('subscription-name',that.state.get('selected-category'));
});
});
Template.home.events({
'click .js-category':function(e,tpl){
let category = tpl.$(e.currentTarget).data('category');
tpl.state.set('selected-category',category);
}
});
And in your Template:
<button type="button" data-category="books" class="js-category">Books</button>
Hope this will help you to find the right solution for your

Recursive component inclusion

I'm looking at the Authoring Ractive.js components document on github from Rich-Harris.
It starts with invoking the foo component and including it this way:
<link rel='ractive' href='foo.html' name='foo'>
<p>This is an imported 'foo' component: <foo/></p>
Which I understand as declaring foo.html as a component and calling it on the foo tag, and this would not require doing a ractive.load (although I did not understand yet where the data loading would occur).
As it does not work at all (no loading of the component), I'm wondering if I mis-understood this part.
Has anyone use this and could give me a complete example?
Components themselves are independent of the loading mechanism.
In the simplest form, components can be declared in javascript:
Ractive.components.foo = Ractive.extend({
template: '#foo' // id of script tag, or can be inline string,
// other options
});
var ractive = new Ractive({
template: '<p>This is an main view with a 'foo' component: <foo/></p>'
});
Creating components is covered here in the docs.
There are many ways to package and load components. Using the link tag, as in your example, requires using ractive-load to actually load the components:
<!-- name is optional if same as file name -->
<link rel='ractive' href='foo.html' name='foo'>
<script src='ractive.js'></script>
<script src='ractive-load.js'></script>
<script>
// calling load() with no parameters loads all link tags and
// registers them as global components
Ractive.load().then( function () {
var ractive = new Ractive({
el: 'body',
template: 'I have access to the <foo/> component!',
data: { ... }
});
// or you could instantiate a component via:
var app = new Ractive.components.app({
... /options
});
}).catch( handleError );
</script>

Having onchange event fire when input value set from data

I've been looking at many two-way data binding libraries and so far haven't found one that will fire onchange events when the input's value is set from a change on the model. Is there any way to do that with ractive.js?
It's possible, but is a little bit hacky. Browsers only fire the change event as a result of user interaction (rather than input.value = 'someNewValue'), so you have to watch the model and fire the event yourself:
var ractive = new Ractive({
el: 'main',
template: '#template',
data: { name: 'world' },
twoway: false
});
ractive.observe( 'name', function () {
// name has changed, need to simulate an event
var event = new Event( 'change' );
ractive.find( 'input' ).dispatchEvent( event );
});
ractive.find( 'input' ).addEventListener( 'change', function ( event ) {
console.log( 'event was fired', event );
});
// check the console!
ractive.set( 'name', 'everybody' );
<script src="http://cdn.ractivejs.org/latest/ractive.js"></script>
<main></main>
<script id='template' type='text/ractive'>
<h1>Hello {{name}}!</h1>
<input value='{{name}}'/>
</script>
Note that twoway binding has been disabled, otherwise you'd get extra events firing all over the place when the user did interact with the input - so you would need to listen for input/change events and handle those interactions yourself.
The answer (for my purposes) is actually quite simple. First a little background - which I probably should have outlined in the original question. Let's say you're viewing/editing a customer profile. Meanwhile someone else is doing the same. They update a phone# and resave the profile. Without anything special being done you aren't going to see that new phone# unless you reload the profile. My goal is to make our data/forms 'reactive'. One of the difficulties was updating a form. In itself that's easy enough but how to handle any onchange events on inputs. Let's say a country is changed so a new list of regions needs to appear. Me changing the country would fire off the country's onchange event and a new list would appear. If a reactive change occurred and updated the country the onchange wouldn't fire. That's a problem. To make a long story short, the answer is to not have any onchange events on inputs but have a Ractive.on('change') event and then parse the key path for anything of interest. That will catch changes from the human and changes from the 'beyond', either with set or merge.
var cust = {
firstname: 'Fred',
lastname: 'Flintstone'
}
ractive = new Ractive({
el: '#spot',
template: '#tmpl1',
data: {customer: cust},
lazy: true
})
ractive.on('change', function(kp) {
console.log(kp)
})
setTimeout(function() {
ractive.set('customer.firstname', 'Wilma')
}, 2000)
setTimeout(function() {
ractive.merge('customer', {firstname: 'Barney', lastname: 'Rubble'})
}, 4000)
<script src="http://cdn.ractivejs.org/latest/ractive.js"></script>
<body>
<div id='spot'></div>
<script id='tmpl1' type='text/tmpl'>
<input value={{customer.firstname}}>
<input value={{customer.lastname}}>
</script>
</body>

How to pass functions to capture events in custom component in Meteor with Blaze

I want to know how to bind/set template-passed-parameter-value to click event of an item in the template in Meteor.
I'm using Meteor 0.7.0.1 with Blaze UI package. My main idea is to build a re-usable custom components in Meteor with Blaze template engine.
I have the following component which is working fine at the moment but I want this to be more customizable and remove some dependencies.
This is my component template named postLinks
<template name="postLinks">
<div id="link-popover-wrapper" >
<ul class="link-popover">
{{#each linkOptions}}
<li><a tabindex="-1" class="link-action" id="link-{{value}}" href="#">{{label}}</a>
</li>
{{/each}}
</ul>
</div>
</template>
This postLinks component is used in the myPostItem helper.
Template.myPostItem.events({
'click .post-item-link-picker': function (evt, tmpl) {
var tmpPostId = this._id;
var tempData = {linkOptions:[{label:'Favorite', value : 'favorite'},{label:'Wish list', value : 'wishlist'},{label:'Later', value : 'later'}, {label:"Read", value:"read"}]};
var linkContent = Template.postLinks(tempData);
$(".item-link-picker").popover({
content: linkContent, html: true, placement: 'bottom', trigger: "manual",
template: "UI_POPOVER_TEMPLATE"});
$("#item-link-picker-"+tmpPostId).popover('show');
},
'click .link-action': function (evt, tmpl) {
//.... some code here to update link selection in db
}
});
Above code is working fine and I want to improve it to have following
Pass item click event externally to be bind to link-action like
After above two changes it will look like :
Template.myPostItem.events({
'click .post-item-link-picker': function (evt, tmpl) {
var tmpPostId = this._id;
var tempData = { itemClick:function(){}, linkOptions:[{label:'Favorite', value : 'favorite'},...]};
var linkContent = Template.postLinks(tempData);
$(".item-link-picker").popover({
content: linkContent, html: true, placement: 'bottom', trigger: "manual",
template: "UI_POPOVER_TEMPLATE"});
$("#item-link-picker-"+tmpPostId).popover('show');
}
});
I lack knowledge how/where to bind that passed event handling function to link-action elements in template or helper. I really appreciate if anybody could help to find a way to do that.
You go the other way around and use jQuery event triggering system, so
Template.myPostItem.events({
'click .link-action': function (evt, tmpl) {
$(evn.target).trigger('post-link-action', this /* extra data */);
},
});
This event can be easily catched in any parent template:
<template name="someOtherTamplate">
{{> myPostItem}}
</template>
Template.someOtherTemplate.events({
'post-link-action': function (evt, tmpl, extra) {
// the user of your component can define their custom behavior here
},
});
Please note that the event extra parameter will only be supported in the next Meteor release. Currently (0.8.0) it is included in the devel branch.

Jsviews helpers don't update on observable update (Array) anymore

After Commit 48 (Beta Candidate) i can't get observable array logic anymore. I know it has changed. I've read the changelog and been playing with new commit for some time but couldn't get it working. Helpers just don't update anymore. Any help appreciated.
Here is a simple example. Clicking "add friend" should call friends_names again.. but it doesn't anymore:
<!DOCTYPE html>
<html>
<head>
<script src="http://code.jquery.com/jquery.js"></script>
<script src="http://www.jsviews.com/download/jsviews.js"></script>
</head>
<body>
<div id="people"></div>
<script id="peopleTemplate" type="text/x-jsrender">
<button id="add">Add person</button><br />
{^{for people}}
<div>
Name: {{>name}},
Friends: <span data-link="html{:~friends_names(#data.friends)}"></span>
<button class="friend-add">add friend</button>
</div>
{{/for}}
</script>
<script>
var data = {
people: [
{
name: "Adams",
friends: [
{name:'Petere'},
{name:'Steve'}
]
},
{
name: "Eugenia",
friends: [
{name:'Bob'}
]
}
]
};
$.templates({
peopleTmpl: "#peopleTemplate"
});
var friends_names = function(friends){
friends = friends || []
var names = []
for (var i=0, l=friends.length; i<l; i++) {
names.push(friends[i].name);
}
return '<b>' + names.join(', ') + '</b>';
};
$.views.helpers({friends_names:friends_names});
$.templates.peopleTmpl.link("#people", data);
//debug
$.observable(data).observeAll(function (ev, obj) { console.log('change', obj); });
$("#add").on("click", function() {
$.observable(data.people).insert({
name: "Amos",
friends: []
});
})
$('#people').on('click', '.friend-add', function(e){
e.preventDefault();
var name = 'Some anonymous friend' + Math.floor((Math.random()*100)+1);
var friends = $.view(this).data.friends;
$.observable(friends).insert({
name: name
});
});
</script>
</body>
</html>
I know nested template can be used (not sure if it will solve the problem) but in real application there is much more logic in helper, thus nested template won't help.
Yes, this is deliberate: See the commit note:
Data linking to arrays is simplified and more consistent. Now tags DO NOT automatically bind to arrays, and refresh when the array
updates. {^{myTag path.to.array/}} will now update when the to.array
property is update (property change) but not when the to.array
itself changes observably. (array change). A tag should opt in to
arraybinding either by deriving from the "for" tag - as in the
'range' sample: http://www.jsviews.com/#samples/tag-controls/range,
or by following the using onAfterLink and onDispose to add/remove
the onArrayChange handler, as in the {^{myWidget .../}} sample in
the JsViews unit tests. This change relates to
https://github.com/BorisMoore/jsviews/issues/158
Here is a really simple fix. If you include the array.length as a parameter (even if your helper function doesn't use it) then JsViews will respond to changes in the array length (which is a property change, not an array change) and will trigger a refresh for your helper: ~friends_names(friends, friends.length)
{^{for people}}
<div>
Name: {{>name}},
Friends: <span data-link="html{:~friends_names(friends, friends.length)}"></span>
<button class="friend-add">add friend</button>
</div>
{{/for}}

Resources