Warning :: This version of Mast in this repository is no longer actively developed and maintained. My recent efforts in this area have instead been devoted instead to building a similar paradigm on top of Vue.js. If you're interested in contributing, let me know!
~ @mikermcneil, Oct 19, 2017
Please visit the wiki for examples and API documentation.
Mast is a set of conventions built on top of Backbone.js. Aside from some other nice features, it is especially good for putting together single-page apps, since it specializes in allowing you to do so without writing any JavaScript. The JavaScript you do write, to add interactivity, uses Backbone/jQuery.
Mast brings some of the more important contributions from frameworks like Knockout and Angular and allows you to take advantage of them using Backbone Models, Collections, and Views, with an eye towards practical, efficient development for medium to large teams. It's not designed to replace frameworks like Angular-- rather it exists to let you do the same thing in Backbone.
Is Backbone better than Angular? I dunno-- that's a complex question. Angular is really, really neat. But sometimes Backbone makes more sense, based on your requirements. And that's one of the reasons this library exists.
Nope! Mast is a client-side library for the browser, whereas Sails is a client-agnostic, API-oriented framework which is backend-only. They're completely unrelated. You can use Mast with Rails, Java, C#, Cordova, no server, whatever you want! And of course, you can use Sails with any front-end library you like, or with a native iOS app, or as a raw API, or even from things like household lamps.
But we're the same folks who built Sails, hence the nautical name.
Historical note: The early versions of Sails were very different, and at one point, Sails was even bundled with Mast included. We learned many lessons about separation of concerns between the client and the server since then. The projects have been separate for a long time.
Add the mast.withDependencies.min.js
file to your project, and make sure you bring it in with a <script>
tag before the <script>
tags loading your components (or anything else using Mast). This file contains all of Mast's dependencies, so you don't need to have separate Backbone, jQuery and Underscore files in your application -- it's all there for you.
In order for Mast to work its magic on your page, you need to raise
it. Paste this after all your other scripts:
<script type="text/javascript">
Mast.raise();
</script>
Templates are HTML blobs that are designed to represent a piece of your user interface. They should be designed to be standalone, since you might want to put them in various places, and you want them to look right no matter where they end up.
The simplest way to create a template is by wrapping it in a <script type="text/template"></script>
tag. Templates should always have an id-- otherwise there's no way to look them up. We'll define our template's id by setting the script tag's data-id
attribute. Let's just look at an example and it will probably make sense:
<script data-id="ContactForm" type="text/template">
<form action="/contact" method="POST">
<label>What's your email?</label>
<input type="text" name="email"/>
<input type="submit"/>
</form>
</script>
Regions are similar to view partials, in the sense that they let you reference your templates and bring down copies where you need them.
In other words, a region is just a container where you can summon one or more of your templates.
The contents of a region can be modified/replaced using JavaScript. This is important for doing cool stuff later. But the best thing about regions is that you can use them to put together single-page apps WITHOUT JavaScript; using only HTML/CSS.
Regions give you a canvas to work on whichever template you want, in the proper context. Just by changing some HTML, you can bring different templates (or multiple templates) into your region, allowing you to build your CSS/HTML as if everything was interactive, long before it actually is. And since regions can contain other regions, you can get as in-depth as you need to.
Here are three examples where regions make your life a lot easier:
Let's say we're implementing a simple single-page website with a header and footer that stays the same, but a main content section that changes depending on what navigation item is clicked. This seems easy, but can be sort of a mess. Here's how your <body>
might look:
<header>
<ul>
<li>Home</li>
<li>About</li>
<li>Contact</li>
</ul>
</header>
<section>
<!-- this is where the home, about, and contact sections are supposed to go -->
</section>
<footer>
<span>© 2028 Balderdash</span>
</footer>
So we could implement the Home page, but how are we going to do the others? Here's what we can do with regions:
<header>
<ul>
<li>Home</li>
<li>About</li>
<li>Contact</li>
</ul>
</header>
<region template="Home"></region>
<footer>
<span>© 2028 Balderdash</span>
</footer>
<script data-id="Home" type="text/template">
<section>
<h1>Home stuff goes here</h1>
</section>
</script>
<script data-id="About" type="text/template">
<section>
<h1>About stuff goes here</h1>
</section>
</script>
<script data-id="News" type="text/template">
<section>
<h1>Portfolio stuff goes here</h1>
</section>
</script>
Those three blocks at the bottom are our templates, and the region is where our middle section used to be. Notice how its template
attribute is set to Home
? This means that the Home
template will be displayed. If you change it to About
, the About
template will be displayed. And so on.
Later on, you can tie these changes to interactive events using JavaScript, but for now, we're done! The HTML/CSS is done and looks great.
Let's say we need to implement a table of emails, with a search box that changes contents of the table as you type to match the criteria (i.e. start typing "Summ" and the user sees 3 or 4 emails about her vacation plans this summer)
This sounds a little tough, but let's give it a shot:
<h1>MastMail</h1>
<label>Search:</label>
<input type="text" class="search-box" />
<div>
<h2>Inbox</h2>
<region template="Email" count="6"></region>
</div>
<!-- This template represents just ONE email in our list -->
<script data-id="Email" type="text/template">
<div>
<strong>From:</strong> <span>Mike</span><br/>
<strong>Subject:</strong> <span>This vacation will BE EPIC</span><br/>
</div>
</script>
This time, we used the count
attribute. This allows us to inject more than one copy of the template, which lets us see what our CSS/HTML implementation of the email list looks like with more or fewer items (and even when it's empty). This addresses a few important edge cases: what our list looks like with too many items (overflow), with too few items (sticky height), or no items ("Sorry, no emails found." state). These oh-so-important details are ofttimes overlooked when "oh-shit" mode kicks in. No longer! Onward.
So I think we should be feeling pretty good, since we've implemented all of the HTML/CSS for the single-page website and type-as-you-go search. What more could we possibly ask for? Well, maybe there are parts of your application that you want to re-use in more than one section, but that aren't in every section like, for instance, the header & footer in the first example.
Let's say we're still working on the single-page website from before. It's an event photographer's website, and one of the navigation items now goes to a "Portfolio" section. Maybe you already have a contact form in the "Home" section, but you realize that visitors should also have quick access to it while they're mesmerized by the pretty pictures.
You don't need to copy/paste the form code into the "Portfolio" section; just stick the code into a new template, and add regions to the places where you want it to be.
So your "Home" template now looks like this:
<script data-id="Home" type="text/template">
<h1>Home Page Stuff</h1>
<p>Blah blah blah</p>
//Your contact form will show up here.
<region template="Contact"></region>
</script>
And the "Portfolio" template like this:
<script data-id="Portfolio" type="text/template">
<img src="https://github.com/balderdashy/mast/raw/master/purdy_picture_1"/>
<img src="https://github.com/balderdashy/mast/raw/master/purdy_picture_2"/>
<img src="https://github.com/balderdashy/mast/raw/master/purdy_picture_3"/>
//Your contact form will show up here, too!
<region template="Contact"></region>
</script>
Defining components gives you the power to make things more interactive. A really cool thing about Mast, though, is that it has shortcuts to let you do some of this without writing JavaScript. Here's a quick walkthrough of how to set up components and add some simple interactivity to your app.
To create a component, make a JavaScript file in your app, and give it the same name as the template you want it to work for. Then, paste in the following bit of code, replacing NameofTemplate
with the template you're using:
Mast.define('NameOfTemplate', function() {
return {
//DO COOL STUFF IN HERE!
};
});
Now that you've created the component, it's time to make your app more awesome!
To switch out the content of a region, you will first need to give that region a data-id
. The <region>
tag should now look something like this: <region data-id="content" template="Home"></region>
.
Then, create a component for the template that contains that region.
Inside the function, you can define the navigation paths. First, you put a string that has the name of the path, starting with #
. Follow that with a :
, and then a string that has the region's data-id@TemplateName
.
So it will look like: '#path' : 'data-id@TemplateName'
It's pretty simple when you see it. Here is how we would set up navigation for our example website:
Mast.define('Body', function() {
return {
'#' : 'content@Home',
'#about' : 'content@About',
'#news' : 'content@News'
};
});
To navigate to the News section, for example, you can now use <a href="#news">News</a>
.
Mast has some simple shorthand for adding, removing, and toggling classes on jQuery events. Here's an example of how that works:
Let's go back to Example 2, where we implemented a list of emails. What if, when the user clicks on an email, you want to make it look different to show that it's been selected? Then, if they click it again, you want it to go back to normal.
You could do that in regular jQuery, but Mast speeds up the process:
Once you've styled up the .selected
class for your email div, create your component:
Mast.define('Email', function() {
return {
};
});
Then add your events
:
Mast.define('Email', function() {
return {
events : {
// jQuery events go here
}
};
});
The events can be any jQuery events. In this case, we'll use a .click()
event.
In Mast, we lay out events similarly to how we set up the navigation before. First, you include a string that has the name of the event, followed by the name of the element that triggers the event. In this case, it would be 'click div'
. Then :
, and then a string that tells it what to do. The shorthand to toggle a class is !
, so we would put '! .selected'
.
Altogether, it looks like:
Mast.define('Email', function() {
return {
events : {
// Toggle .selected class when the email div is clicked
'click div' : '! .selected'
}
};
});
Secret Code | Action |
---|---|
'+ .class-name' |
Add class |
'- .class-name' |
Remove class |
'! .class-name' |
Toggle class |
'!!! message' |
Alert |
'>>> message' |
Log a message to the console |
<script>
tagsMast, like Backbone, supports Underscore templates by default (basically the same thing as EJS, JST).
Aside from the shortcut method we've been discussing up until now (the <script>
tags), you can load templates as strings or underscore-compatible functions (if you want to precompile them).
You can hook these templates up to Mast in one of two ways:
Set Mast.templates.Foo = myTemplate
, where Foo
is the desired id of your template e,g,
Mast.templates.header = '<header>The header</header>';
Mast.templates.footer = '<footer>The footer</footer>';
Pass in a collection of templates when you call Mast.raise()
, e.g.:
Mast.raise({
header: '<header>The header</header>',
footer: '<footer>The footer</footer>'
});
This flexibility is crucial so that, regardless how the project is set up, you can count on Mast to work. If you're using AMD (e.g. RequireJS), for example, you might bring in your templates with the RequireJS template plugin. At the end of the day, you'll stuff your templates into Mast using one of the two techniques above.
If you're already using Sails on the backend, here is a quick way to get started with some fancier template loading:
Save the Mast.dev.js
file in assets/linker/js
In views/layout.ejs
paste the following after <!--SCRIPTS END-->
:
<script type="text/javascript">
// Modify JST templates to eliminate the nasty parts of the file path
_.each(JST, function (fn, path) {
var id = path.match(/\/([^\/]+)\..+$/)[1];
Mast.templates[id] = fn;
});
Mast.raise();
</script>
This makes the templates a bit easier to set up. Now when you create a file in linker/templates
, that template's name becomes the ID with which you would reference it in a region. (e.g. Home.html
would be brought in with <region template="Home"></region>
)
--
Copyright © 2012-2013 Balderdash Design Co.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.