MarionetteJS + Brunch Part 3 — Views 1/2

Renato Pestana
Marionette.js blog
Published in
8 min readNov 10, 2017

This article is part of a series about MarionetteJS and Brunch.io. While I try to keep the articles as abstract as possible so they wont depend too much on previous ones, I would encourage you guys to read all the previous articles as I go about explaining tons of concepts that may help you understand the articles better.

< Previous
Next >

Hey there, hope everyone is having a great day.

First of all, I want to give a big shout-out to Paul Falgout 🎊 As some of you may have noticed, this articles are now available at MarionetteJS Blog and that’s all thanks to him. Paul gave me the opportunity to be part of this awesome community and I’m very thankful for that, I started this series as a way to improve myself and help people that may have struggled starting with this awesome framework or maybe better explaining some concepts that it weren't so clear to them.

By being part of the blog, I can now reach many more people and being able to help so many of you is something very rewarding. So thanks again for the opportunity Paul and as always I’ll try my utmost best to keep writing awesome articles 🙌

So last article we talked about how to use Mn.Application as our application entry-point and also briefly introduced the concept of a View, but what is it exactly?

Those who are familiar with the MVC Pattern should already know what it is, but for those who don’t the View its basically what you want to use to render HTML to the client (very brief and generic explanation I know, but its essential what it is).

Back in the day, we had a Mn.LayoutView and a Mn.ItemView but since Mnv3 those have been merged into Mn.View so if you came here from an older version of Mn and thought something was missing this is why.

Views have a lot of concepts to learn and I’ll try to cover the more important ones to make them work on a basic level.

Templating

In order to render the page to the client Mn makes use of templates, little pieces of HTML that can be either defined inside the View itself, or we can import them from separated files, witch is extremely useful if we want to reuse a specific template on other Views.

Now, while we can use pure html files as our templates, its not very interesting as most of the time we want to use dynamic data inside it. See the below example:

const UserView = Mn.View.extend({ el: 'body' })

We created a view with the purpose of showing a card listing some user information to the client, using the property el to specify we want to render it on the body tag. For that purpose we also create an html code:

<div class="user_card">
<dl>
<dt>Name</dt>
<dd> <!-- name here --> </dd>
<dt>Age</dt>
<dd> <!-- age here --> </dd>
<dt>Occupation</dt>
<dd> <!-- occupation here --> </dd>
</dl>
</div>

To make this the template of our View, we simply have to use the property template :

const UserView = Mn.View.extends({
el: 'body,
template: require('user_card_template.html')
});
const userView = new UserView().render();

Boom! that’s all you need to render the View with the HTML file, there’s only one problem, we didn’t render any user specific data and that’s the downfall of using pure html files. Do you have a piece of static code that requires no dynamic data whatsoever? No problem then, but if you want to render dynamic data into the template we need to use a Template Engine.

Mn its coupled with a library called underscore witch has its own Template Engine but frankly, I hate it :P I think the syntax its too complex and doesn’t fit well on HTML code, sometime making the entire template unreadable. Since using underscore’s template engine will prevent you from adding a third party one, its mostly used on code examples as you will see throughout Mn official Docs, but there are much better options on the market like Handlebars witch syntax is more user friendly.

However, with the arrival of ES6 we now have something called Template Literals and we can build entire templates using it, deprecating the need of using an external Template Engine, and having the advantage of using native code! So I’ll be using it for the entirety of the series.

Side Note: If you think that Template Literals are too confusing for you and you find Handlebars more attractive, let me know in the comments and I’ll make an extra section on how to set up Handlebars to work with your project ;)

By default Mn will use a Bb.Model as data source for your view, it doesn’t necessary need one as you may not need to have data, but if you have one, Mn will parse it to the template so we can use it directly:

// This is our User template
const tmpl = ({name, age, occupation}) => `
<div class="user_card">
<dl>
<dt>Name</dt>
<dd> ${name} </dd>
<dt>Age</dt>
<dd> ${age} </dd>
<dt>Occupation</dt>
<dd> ${occupation}</dd>
</dl>
</div>
`;
// This is the User model we will be using as DataSource
const dataSource = new Backbone.Model({
name: 'Renato',
age: 25,
occupation: 'developer'
});
// This is our User View
const UserView = Mn.View.extend({
el: 'body,
model: dataSource,
template: tmpl
});
const userView = new UserView().render();

As you can see, I’ve created a function that returns a Template Literal and assigned it to the variable tmpl. I then used the tmpl variable as a template on the View and voila, instant access to the model proprieties without explicitly assign them.

But hey, what if I want to use some additional info that doesn’t come with the model?

Mn has that covered. Like I said, Mn.View will use a Bb.Model as default data source but you can always use a different one.

If you wish to use a custom data source you can make use of the View method templateContext() witch returns a key/value object of whatever you want to pass to the template.

// This is our User template
const tmpl = ({name, age, occupation, additionalField}) => `
...
<dt>Additional Content</dt>
<dd>${additionalField}</dd>
...
`;
// This is the User model we will be using as DataSource
const dataSource = new Backbone.Model({
...
});
// This is our User View
const UserView = Mn.View.extend({
el: 'body,
model: dataSource,
template: tmpl,
templateContext(){
return { additionalField: 'this is an additional field' };
)

});
const userView = new UserView().render();

The information at templateContext() gets merged with the model data and just like that we can easily assign dynamic data to our template.

Regions

Last article we also talked about Regions and how we could use them to layout our page and separate logic concerns, by nesting views inside other views. We can do this by using the View attribute regions witch accepts a key/value object as value:

const RootView = Mn.View.extend({
...
regions: {
headerRegion: '#header',
mainRegion : '#main',
footerRegion: '#footer'
}
...
});

Now (and this is very important), we can render a new view inside a region anywhere on the code, on some function that its called as callback for an event or even at initialize() on a DOM element already rendered. Mn places no restrictions on where you can render your views but you should!

Manipulating the DOM is a heavy process and it can take a big toll on performance if your are rendering view by view. To address this problem Mn provides a simple way to render all your views (and nested-views) at once. How? you just render everything inside the onRender() life-cycle method, its that easy! and by doing so you are already improving your code :D

const RootView = Mn.View.extends({
...
regions: {
headerRegion: '#header',
mainRegion : '#main',
footerRegion: '#footer'
}
onRender(){
this.showChildView('headerRegion', new HeaderView());
this.showChildView('mainRegion', new MainView());
}
...
});

Note: There’s obviously some use-cases in witch you want to render views outside the onRender() method, but make that a last resort, not a pattern.

But hey, maybe you now want to access the View inside that specific Region to trigger some function on it, that could be useful right?

Well, you can do so by using the method getChildView() . By calling this method with the name of the Region as argument, it will return the View inside it and you are free to use it as you normally would with any other View.

const RootView = Mn.View.extends({
...
regions: {
headerRegion: '#header',
mainRegion : '#main',
footerRegion: '#footer'
}
onRender(){
this.showChildView('headerRegion', new HeaderView());
this.showChildView('mainRegion', new MainView());
}
someFunction(){
const mainview = this.getChildView('mainRegion');
mainview.doSomething();
}
...
});

There are many other useful things you can do with Regions, but I’ll leave that for another article, so for now let us focus on Views.

UI

By now we know how to setup Views and how to render them inside Regions, but how do we manipulate DOM elements? You know, fetching values of inputboxes, listening to click events, maybe even dynamically add elements inside other DOM elements.

You probably already noticed that Mn relies on jQuery for DOM manipulation, because of that you can use any jQuery function to retrieve, create and/or manipulate the DOM.

They are trying to deprecate this dependency by creating a DOM API, but since its still on its early stages, I wont use it for the purpose of this articles, well that is until its battle proof ✌️

Maybe you have a form that you want to serialize? You could do something like this:

const formView = Mn.View.extends({ 
...
serializeForm(){
const data = $('form').serialize();
// do something with data
}
})

This would definitely work, but directly manipulate the DOM like this can bring up some problems!

What if there’s more than one form on the DOM? We obviously only want to manipulate the one related to that View, but if we use a jQuery selector we are interacting on the entire DOM, not only the $el of the View 😰. And what if I need to use this element multiple times? Do I keep using jQuery selectors to search for it? You could eventually assign the element to a variable but what if you need it on a different function? Geez, so many problems 😤

Mn provides a simple and elegant way to handle this by allowing to name parts of the template using the ui property.

This provides plenty of benefits:

  • Caches the element, allowing us to use it indefinitely throughout the View without needing to constantly searching for it using a selector (better performance);
  • By using a named property, we can dynamically change the element that the ui references without losing context! So maybe we have an element with an id of userList and we assigned it to ui.list . For some reason we change templates and now we reference a list that has an id of betterUserList . Since we are using ui.list we wont need to change anything else, the property its still the same, only the element its referencing is different. Cool right?
  • Provides a good way to separate concerns. When you see something like this.ui you instantly know it has something to do with DOM manipulation.

Here is an example:

const formView = Mn.View.extends({ 
...

ui: {
formEl: 'form'
}
serializeForm(){
const data = this.getUI('formEl').serialize();
// do something with data
}
})

So, we instantiate our UI by assigning a key/value object to the property ui, the key being the name you want to give to the element, and the value a jQuery selector of the element.

After that, we use the function getUI() with the name of the ui as argument to retrieve the element that it references and we can now manipulate it as any normal jQuery element.

And that’s it for now folks, this article was a little bit overwhelming, tons of new concepts and believe me, there’s so much more to be talked about Views like events, triggers, more UI and Region uses, … but we can talk more about those on future articles. I believe what we learned today is enough for one day, so go get a cup of water and relax for a bit, take your time to review what we talked about and maybe do some practice of your own, any questions feel free to ask.

Next article we will talk about special types of Views and what’s their use so stay tuned and thanks for reading as always.

Peace ✌️

--

--