What is 'Template.instance().view'? - meteor

I read [Template.instance().view]1 at Blaze docs.
Also I read Blaze.view().
I even saw the view object in the console log.
But I can't understand.
Could anyone explain it more intuitively and smoothly, please? :)

If you want to understand Views more deeply, you need to understand the relationship between Templates, TemplateInstances, and Views. Views are just reactive parts of the DOM. Template instances contain one View, but templates can create more views through functions that create renderable content like Blaze.with ({{#with}}) or Blaze.if ({{#if}}). These "child" views will will then store a parent pointer, which you can use to reconstruct the View tree.
What might help your understanding is playing around with how Templates and Views interact in Chrome tools. You can find a template instance by using any DOM element. Here is an example to get you started:
templateInstance = Blaze.findTemplate($('<some component in dom>')[0])
view = templateInstance.view
You can extend Blaze to contain findTemplate like this:
Blaze.findTemplate = function(elementOrView) {
if(elementOrView == undefined) {
return;
}
let view = Object.getPrototypeOf(elementOrView) === Blaze.View.prototype
? elementOrView
: Blaze.getView(elementOrView);
while (view && view.templateInstance === undefined) {
view = view.originalParentView || view.parentView;
}
if (!view) {
return;
}
return Tracker.nonreactive(() => view.templateInstance());
};

Related

Adding components to existing Ractive instance

I have a somewhat weird question.
In Ractive we can do something like this.
App’s template:
<h1>My app</h1><SubComponent/>
And generally have SubComponent’s template access data from App.
Is there any way to have the same behaviour without mentioning SubComponent in the template?
Something like:
const App = new Ractive({el: ‘#myapp’, …………});
const SubComponent = Ractive.extend({ append: true, ………… });
const example = new SubComponent()
example.render(App.el)
But, with example’s template being able to access App’s data, maybe also giving App the chance to find it by using App.findComponent().
What I’m trying to do, is having unpredictable (= I can’t include them inside templates by default) nested components.
Hopefully this makes sense.
Any idea on how to do it?
I don't know if this will work out for you..
But you can just put a div with a known ID in
<h1>My app</h1><div id="subcomp" />
Then mount your dynamic SubComponent under there.
You can use can event like oncomplete - which will guarentee than div#subcomp has been added to dom.
oncomplete: function() {
this.mycompRef = new SubComponent( { el: '#subcomp' });
// You can skip findComponent and use mycompRef
}

Displaying dynamic content in Meteor using Dynamic Templates

I've read through the (somewhat sparse) documentation on Dynamic Templates but am still having trouble displaying dynamic content on a user dashboard based on a particular field.
My Meteor.users collection includes a status field and I want to return different content based on this status.
So, for example , if the user has a status of ‘current’, they would see the 'currentUser' template.
I’ve been using a dynamic template helper (but have also considered using template helper arguments which may still be the way to go) but it isn’t showing a different template for users with different statuses.
{{> Template.dynamic template=userStatus}}
And the helper returns a string to align with the required template as required
userStatus: function () {
if (Meteor.users.find({_id:Meteor.userId(), status: 'active'})){
return 'isCurrent'
}
else if (Meteor.users.find({_id:Meteor.userId(), status: ‘isIdle'})) {
return 'isIdle'
} else {
return ‘genericContent'
}
}
There may be much better ways to go about this but it seems a pretty common use case.
The few examples I've seen use Sessions or a click event but I’d rather use the cursor if possible. Does this mean what I’m missing is the re-computation to make it properly reactive? Or something else incredibly obvious that I’ve overlooked.
There is a shortcut for getting the current user object, Meteor.user(). I suggest you get this object and then check the value of the status.
userStatus: function () {
if(Meteor.user()) {
if (Meteor.user().status === 'active') {
return 'currentUserTemplate'; // this should be the template name
} else if (Meteor.user().status === 'isIdle') {
return 'idleUserTemplate'; // this should be the template name
}
} else {
return ‘notLoggedInTemplate'; // this should be the template name
}
}
Ended up using this approach discussed on the Meteor forums which seems a bit cleaner.
{{> Template.dynamic template=getTemplateName}}
And the helper then becomes:
getTemplateName: function() {
return "statusTemplate" + Meteor.user().status;
},
Which means you can then use template names based on the status:
<template name="statusTemplateActive">
Content for active users
</template>
(though keep in mind that Template helpers don't like hyphens and the data context needs to be set correctly)

(AngularJS) Only one less file for entire Website

I am a beginner with AngularJS and I have a little problem, I installed grunt-contrib-less to support less files instead css but now I have to declare all less styles that will be compiled into only one css file.
But my problem is normally when I'm using less, I write some code for a specific page, and here I have to write the style code for all pages. This is confusing and not really maintanable so is there a best practice to organize less styles?
I tought that there may be multiple solution:
Apply a class to body tag and change it with I don't know what
(controller, services or other)
(Import LESS file only for one page)
Generate multiple css file depending which style is compiled (but I can't do this because I can't configure grunt correctly)
Do this with DOM manipulation (but it don't find it beautifull because I think Angular must have a good way to solve that problem)
Could you explain me how to have different style for differents views ? I don't want to have the same style for all links in all views and without create hundreds classes I don't know how to do that.
Use directive
and add whatever variables/code/logic you want to add
HTML template(directive) of style can be added to your view and after compile you will get different ui for all your views
for reference read
angular directive
I solve my problem by adding specific class on body tag depending the route.
I put a variable in rootScope called 'pageStyle' with correspond to the classname that I want. This variable is updated automatically when route change (see run function). There is an event when the route change ($stateChangeSuccess or $routeChangeSuccess depending if you are using ngRoute or -angularui-routeur).
In my case i would like to add the name of the route but you can do it with the controller name or something else.
Here is an example
This is the routes :
angular
.module('frontApp', [])
.config(['$stateProvider', '$urlRouterProvider', function ($stateProvider, $urlRouterProvider, $mdThemingProvider) {
$urlRouterProvider.otherwise('/');
$stateProvider
.state('home', {
url: '/',
templateUrl: '../views/home.html',
controller: function ($scope) {
$scope.msg = 'Xavier';
}
})
.state('form', {
url: '/form',
templateUrl: '../views/form.html',
controller: 'FormCtrl'
});
}])
And in the run function you will see the event bound to adapt the class when route change :
.run(function($rootScope) {
$rootScope.pageStyle = '';
// Watch state and set controller name in pageStyle variable when state change
$rootScope.$on('$stateChangeSuccess', function(event, toState) {
event.preventDefault();
if (toState && toState.name && typeof toState.name === 'string'){
$rootScope.pageStyle = toState.name;
} else {
$rootScope.pageStyle = '';
}
});
});
Extra informations :
Note that the event called when route change is different if you are using ngroute. use "$routeChangeSuccess" if you use ngRoute and "$stateChangeSuccess" if you choose to use angular-ui-routeur
If you want to add the controller name instead the route name simply use the follow and replace 'ctrl' with you controller suffixe:
if (toState && toState.controller && typeof toState.controller !== 'function'){
$rootScope.pageStyle = toState.controller.toLowerCase().replace('ctrl','');
}
Hope it help someone else

How can I access DOM Elements declared within a template outside Template.myTemplate.events, Template.myTemplate.rendered etc

I have a template with few elements (input, radioButton etc). If I want to access to these DOM elements within mytemplate I can either access them within events
Template.myForm.events({
'click #submitButton' : function (event, template) {
//template variable here gives me access to the
//current template instance, so I can get to any
//DOM element within this template.
}
})
OR within
Template.myForm.rendered = function () {
//within this function I have access to "this" which points to template instance
}
I was wondering if there is a way to access the DOM Elements that a declared within a template outside of these event functions and rendered callback?
Thanks in advance
You can but you need to reference the template instance.
The reason for this is a single template can be used multiple times. In this case a single easy to use way to access the template would not know which instance it would belong to. This is why you need to use a reference, such as done in the example below.
You have to store the instance somewhere when it is rendered:
TheTemplateInstance = null;
Template.myForm.rendered = function() {
TheTemplateInstance = this;
}
Then you can use TheTemplateInstance anywhere you want, provided the template is on the DOM.
If you use myForm many times then it will only have access to the one created last.
Also You did not give a use case for your intentions. But there are several better ways to do most things with a template:
JQuery modding something when some variable changes (the most common use case where helpers aren't useful)
Template.myForm.rendered = function() {
var self = this;
this.autorun(function() {
var xx = something.findOne();
self.$("something").autoform() //Some jquery call
});
}
and helpers:
Template.myForm.helpers({
someName: function() {
return Session.get("name");
}
});
You can then use {{someName}} in your template's html where it can change when you use Session.set("name", "a new value");

Render a Backbone.js collection

I am a Backbone.js n00b and trying to get my head around it. I know how to render a model using a view and the built-in underscore.js templating engine. Now I'm trying to render a collection and that's where I get stuck. There is no server here, so I'm not fetching anything remotely, just a simple HTML page with some JavaScript.
ContinentModel = Backbone.Model.extend({});
ContinentsCollection = Backbone.Collection.extend({
model: ContinentModel,
initialize: function () {
this.continentsView = new ContinentsView;
this.bind("reset", this.continentsView.render);
}
});
ContinentsView = Backbone.View.extend({
el: '#continents',
template: _.template($('#continents-template').html()),
render: function() {
var renderedContent = this.template(this.collection.toJSON());
$(this.el).html(renderedContent);
return this;
}
});
$(function() {
var continentsCollection = new ContinentsCollection();
continentsCollection.reset([{name: "Asia"}, {name: "Africa"}]);
});
It breaks on the template attribute line in the view but I'm not sure that's where I need to look. Am I supposed to render a collection or do I miss the point completely here (maybe collections are just grouping objects and I shouldn't look at it as a list I can render)?
Thanks for helping...
The problem is that when you define ContinentsView, the template is evaluated and it uses $('#continents-template') - but the DOM is not ready yet, so it does not find the template.
To solve it, simply move the template assignment in the initialize function:
ContinentsView = Backbone.View.extend({
el: '#continents',
initialize: function() {
this.template = _.template($('#continents-template').html());
}
...
Regarding collections, yes, they are grouping objects, specifically sets of models.
You should make the code so the models (and collections) do NOT know about the views, only the views know about models.
ContinentModel = Backbone.Model.extend({});
ContinentsCollection = Backbone.Collection.extend({
model: ContinentModel,
// no reference to any view here
});
ContinentsView = Backbone.View.extend({
el: '#continents',
initialize: function() {
this.template = _.template($('#continents-template').html());
// in the view, listen for events on the model / collection
this.collection.bind("reset", this.render, this);
},
render: function() {
var renderedContent = this.template(this.collection.toJSON());
$(this.el).html(renderedContent);
return this;
}
});
$(function() {
var continentsCollection = new ContinentsCollection();
continentsCollection.reset([{name: "Asia"}, {name: "Africa"}]);
// initialize the view and pass the collection
var continentsView = new ContinentsView({collection: continentsCollection});
});
It is also worth noting there are additional complexities that quickly rear their heads when rendering a collection in a view. For instance, the view generally needs to be re-rendered when models are added or removed from the collection. It isn't rocket science to implement your own solution, but it is probably worth looking into existing solutions since there are quite a few tried and tested ones out there.
Backbone.CollectionView is a robust collection view class that handles selecting models in response to mouse clicks, reordering the collection based on drag and drop, filtering visible models, etc.
Several popular frameworks built on top of backbone also provide simple collection view classes, like Backbone.Marionette, Chaplin, and Layout Manager.
Even though Backbone itself does not provide any structure for rendering a collection, it is a non-trivial problem and lots of people have different opinions on how it should be done. Luckily it is such a common need that there are quite a few good options already in the eco system.

Resources