Ractive computed attributes returned in get() - ractivejs

ref this jsfiddle
html:
<main />
<div id='result' />
code:
window.ractive = new Ractive({
el: 'main',
template: '<p>a thing called {{thing}}</p>',
computed: { thing : function(){return "kablooie"} }
});
$('#result').html(JSON.stringify(ractive.get()))
The ractive.get() here does return the value of the attribute "thing". Even though the docs say that computed attributes are not returned by get().
Is this intentional behaviour or a bug?

In edge Ractive (will be 0.8) which you are using, we added the computed and mapped properties to the root get via ractive.get() as a feature request.
See this issue for current proposal to be able to get only the root data object via ractive.get('.'), which would mean:
window.ractive = new Ractive({
el: 'main',
data: { foo: 'foo' },
template: '<p>a thing called {{thing}}</p>',
computed: { thing : function(){return "kablooie"} }
});
console.log( JSON.stringify( ractive.get() ) );
// { foo: 'foo', thing: 'kablooie' }
console.log( JSON.stringify( ractive.get('.') ) );
// { foo: 'foo' }

Related

How can I make vuefire show loading screen?

As title
Vuefire can auto get data from firebase database, but it needs some loading time.
So I want to display some css animation before data being fetched, is there any event can I $watch when it successed
The readyCallback approach in the other answer didn't work for me. I got an error document.onSnapshot is not a function.
Instead, I used the binding approach to set a flag when loading is complete.
<script>
// ...
export default {
data() {
return {
data: [],
loaded: false,
}
},
mounted() {
this.$bind('data', firebase.firestore().collection('someDocRef'))
.then(() => this.loaded = true);
},
}
</script>
Then my template can have conditionally-rendered loading screens:
<template>
<template v-if="!loaded">
<p>Loading...</p>
</template>
<template v-if="loaded">
<!-- Display data here -->
</template>
</template>
You can do this multiple ways.
Vuefire has readyCallback out of the box which is callback called when the data is fetched (ready).
Here it is:
var vm = new Vue({
el: '#demo',
data: function() {
return {
loaded: false
}
}
firebase: {
// simple syntax, bind as an array by default
anArray: db.ref('url/to/my/collection'),
// can also bind to a query
// anArray: db.ref('url/to/my/collection').limitToLast(25)
// full syntax
anObject: {
source: db.ref('url/to/my/object'),
// optionally bind as an object
asObject: true,
// optionally provide the cancelCallback
cancelCallback: function () {},
// this is called once the data has been retrieved from firebase
readyCallback: function () {
this.loaded = true // NOTE THIS LINE
}
}
}
})

(Meteor + React) Cannot access props from within subcomponent

I am passing data from one component to another (MyApplicants) via my router (FlowRouter):
FlowRouter.route('/applicants', {
name: 'Applicants',
action: function () {
var currentUser = Meteor.user();
ReactLayout.render(App, {
content: <MyApplicants institutionID={Meteor.user().profile.institutionID} />,
nav: <Nav />,
header: <Header />
});
}
});
As you can see I'm passing institutionID to the new component via a prop in the router. I know that the institutionID is being passed because I can see it in the render of the MyApplicants component.
Here is the MyApplicants component:
MyApplicants = React.createClass({
mixins: [ReactMeteorData],
pagination: new Meteor.Pagination(Applicants, {
perPage: 25,
filters: {institution_id: this.props.institutionID },
sort: {institution_id: 1, "last_name":1, "first_name":1}
}),
getMeteorData() {
return {
currentUser: Meteor.user(),
applicants: this.pagination.getPage(),
ready: this.pagination.ready()
}
},
RenderApplicantRow(applicant, key) {
return (
<div key={key}>
<p>[{applicant.institution_id}] {applicant.last_name}, {applicant.first_name}</p>
</div>
)
},
render : function () {
return (
<div>
<section className="content">
{this.data.applicants.map(this.RenderApplicantRow)}
{console.log(this.data.applicants)}
<DefaultBootstrapPaginator
pagination={this.pagination}
limit={6}
containerClass="text-center"/>
</section>
</div>
)
}
});
(FYI, I'm using krounin:pagination.) The problem is that I cannot access this.props.institutionID inside of the pagination component. I know the value is getting passed (I can see it if I'm just testing output in the render) but can't figure out why it doesn't pass into the pagination call. And I know the pagination works because I do not get an error if I hard code in a value.
Thanks for the help.
This is a simple scope problem I think, you need to bind it to the right context
Try something like this:
pagination: function() {
var self= this;
new Meteor.Pagination(Applicants, {
perPage: 25,
filters: {institution_id: self.props.institutionID },
sort: {institution_id: 1, "last_name":1, "first_name":1}
})
}
,

RactiveJS: Partials with (dynamic) local data

I'm trying to separate data per partials so that the data doesn't mix/overwrite globally.
The problem is that the main template does not re-render when partial's data changes.
The best I've accomplished so far is to get the partial to re-render in the main container, but not within the main template itself.
Am I missing something?
Here's the code (also on JS Bin):
HTML:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JS Bin</title>
<script src="https://code.jquery.com/jquery-2.1.4.js"></script>
<script src="http://cdn.ractivejs.org/latest/ractive.js"></script>
</head>
<body>
<div id="main"></div>
</body>
</html>
JS:
var partial = new Ractive({
template: '{{name.first}} {{name.last}}',
oninit: function () {
this.observe( function ( newValue, oldValue, keyPath ) {
console.info( 'newValue = %o, oldValue = %o, keyPath = %s', newValue, oldValue, keyPath );
});
},
data: {
name: {
first: 'John',
last : 'Doe'
}
}
}),
main = new Ractive({
el: '#main',
// template: 'Hello {{name}}!',
template: 'Hello {{>partial}}!',
partials: {
partial: function ( p ) {
// 1) Not working...
// return '{{name.first}} {{name.last}}';
// 2) ...also not working...
// return partial.template;
// 3) ...still not working...
// return ( p.isParsed( partial.template ) ) ? partial.template : p.parse( partial.template );
// 4) Kind of working... (static - does not re-render)
// return partial.toHTML();
// 5) Kind of working... (returning Promise!)
return partial.render( this.el );
}
},
data: {
name: 'John Doe'
}
});
// partial.set( 'name.first', 'Jane' );
You have two ways to do this. First, you can still use partials, but you force the context of the partial. Note, however, that the data will still reside in the same instance. In the following example, the partial picks up "John Doe" as data. But in the second partial usage, we force the jane object to it, making it use first and last from jane.
var main = new Ractive({
el: '#main',
template: 'Hello {{>personPartial john}} and {{>personPartial jane}}!',
partials: {
personPartial: '{{first}} {{last}}'
},
data: {
first: 'John',
last : 'Doe',
jane: {
first: 'Jane',
last : 'Dee'
}
}
});
main.set( 'jane.name.first', 'Jen' );
If you really total separation of the data, consider using components instead. In this case John and Jane are their own components. Note however, that if a component is missing data (like say John didn't have first), it will look into the parent (main) for that data. If it happens to be there, it will use that (in this case foo). You can use isolated:true to prevent this behavior, like in Jane. You can also explicitly pass data from the parent to the child by way of attributes.
var John = Ractive.extend({
template: 'Hello {{first}} {{last}}!',
data: {
last: 'Doe'
}
});
var Jane = Ractive.extend({
isolated: true,
template: 'Hello {{first}} {{last}}!',
data: {
first: 'Jane',
}
});
var Jay = Ractive.extend({
isolated: true,
template: 'Hello {{first}} {{last}}!'
});
var main = new Ractive({
el: '#main',
template: 'Hello {{>person}}! <john /> <jane /> <jay first="{{first}}" last="{{last}}" />',
partials: {
person: '{{first}} {{last}}'
},
components: {
john: John,
jane: Jane,
jay: Jay
},
data: {
first: 'foo',
last: 'bar'
}
});

Session object inside global template helpers

Session.set('coursesReady', false); on startup.
UPDATE:
I made it into a simpler problem. Consider the following code.
Inside router.js
Router.route('/', function () {
Meteor.subscribe("courses", function() {
console.log("data ready")
Session.set("coursesReady", true);
});
}
and inside main template Main.js
Template.Main.rendered = function() {
if (Session.get('coursesReady')) {
console.log("inject success");
Meteor.typeahead.inject();
}
The message "inject success" is not printed after "data ready" is printed. How come reactivity does not work here?
Reactivity "didn't work" because rendered only executes once (it isn't reactive). You'd need to wrap your session checks inside of a template autorun in order for them to get reevaluated:
Template.Main.rendered = function() {
this.autorun(function() {
if (Session.get('coursesReady')) {
console.log("inject success");
Meteor.typeahead.inject();
}
});
};
Probably a better solution is to wait on the subscription if you want to ensure your data is loaded prior to rendering the template.
Router.route('/', {
// this template will be rendered until the subscriptions are ready
loadingTemplate: 'loading',
waitOn: function () {
// return one handle, a function, or an array
return Meteor.subscribe('courses');
},
action: function () {
this.render('Main');
}
});
And now your rendered can just do this:
Template.Main.rendered = function() {
Meteor.typeahead.inject();
};
Don't forget to add a loading template.
To Solve Your Problem
Template.registerHelper("course_data", function() {
console.log("course_data helper is called");
if (Session.get('coursesReady')) {
var courses = Courses.find().fetch();
var result = [ { **Changed**
name: 'course-info1',
valueKey: 'titleLong',
local: function() {
return Courses.find().fetch();
},
template: 'Course'
}];
Session.set('courseResult', result); **New line**
return Session.get('courseResult'); **New line**
,
Explanation
The answer is at the return of the helper function needs to have be associated with reactivity in order for Blaze, template renderer, to know when to rerender.
Non-reactive (Doesn't change in the DOM as values changes)
Template.Main.helpers({
course_data: UI._globalHelpers.course_data ** Not reactive
});
Essentially: UI._globalHelpers.course_data returns an array of objects which is not reactive:
return [
{
name: 'course-info1',
valueKey: 'titleLong',
local: function() {
return Courses.find().fetch();
},
template: 'Course'
},
Reactive
From Meteor Documentation:
http://docs.meteor.com/#/full/template_helpers
Template.myTemplate.helpers({
foo: function () {
return Session.get("foo"); ** Reactive
}
});
Returning Session.get function to Blaze is reactive; thus, the template will change as the values changes.

How to remove rendered route using gmap3 plugin?

I use GMAP3 plugin to render driving direction. And would like to add a clear button so it can be clear but I haven't been able to find the right syntax in GMAP3. Here is the my js code, modified from the sample in gmap3.net. I have markers plotted already and latlng are retreived from plotted markers instead of from clicks position on the map.
function removePath() {
$(mapID).gmap3({
action: 'clear',
name: 'directionRenderer'
// tag: 'path' // works too with tag instead of name
});
function updatePath() {
$(mapID).gmap3({
action: 'getRoute',
options: {
origin: m1.getPosition(),
destination: m2.getPosition(),
travelMode: google.maps.DirectionsTravelMode.DRIVING
},
callback: function (results) {
if (!results) return;
$(mapID).gmap3({
action: 'setDirections',
directions:results,
});
}
});
};
function updateDirection(mm) { // Directions between m1 and m2
var mmID = $(mm).prop('id');
...
if (mmID == 'clearDirection') {
...
removePath();
return;
};
...
if (m1 && m2) { updatePath(); };
};
function initmap() {
$(mapID).gmap3(
{
action: 'init',
options: defaultMapOptions
},
// add direction renderer to configure options (else, automatically created with default options)
{ action: 'addDirectionsRenderer',
preserveViewport: true,
markerOptions: { visible: false },
options: {draggable:true},
tag: 'path'
},
// add a direction panel
{ action: 'setDirectionsPanel',
id: 'directions'
}
);
};
A is in place in HTML documents as directions panel. It has a a wrapper which is hidden when the route is cleared by using jquery css property change. The wrapper div's display property is changed back to 'block' whenever value is assigned to either m1 or m2.
<body>
...
<div id="direction_container" class="shadowSE">
....
<div id="directions"></div>
....
</div>
</body>
Its absolutely working fine.
$map.gmap3({ action: 'clear', name: 'directionRenderer' });
*Instructions-
If you later draw the route then you must write below code otherwise directions not display.
$map.gmap3({ action: 'addDirectionsRenderer', preserveViewport: true,
markerOptions: { visible: false} },
{ action: 'setDirectionsPanel', id: 'directions' });
Thanks...
Use this:
$(mapID).gmap3({action:"clear", name:"directionRenderer"});
The chosen answer above didn't work for me. I'm unsure if it's version related, but the solution I'm using is more simple:
$(your-selector).gmap3({clear: {}});
Afterwards, you can draw a new route without reconnecting the directions rendered with the map.

Resources