HMVC is a JavaScript library that lets you isolate and decouple your code, which took inspiration from the hierarchical MVC pattern and AngularJS directives.
HMVC is a JavaScript library that lets you isolate and decouple your code, which took inspiration from the hierarchical MVC pattern and AngularJS directives.
HMVC is mostly suitable for apps that need to modify parts of the DOM to generate user-specific content on the client-side, and will let them scale better: with HMVC, you can keep client-related informations on the client and customize views there rather then the server; on the latter you will be able to cache contents more aggressively and live a - definitely - happier life.
<html>
<body>
<scream>
This test needs to scream!
</scream>
<script src="my-minified-scripts.js" ></script>
<script type="text/javascript">
hmvc.component('scream', function(element){
return {
template: '<h1>{{ text }}</h1>',
run: function() {
return {
text: element.text()
};
}
};
});
hmvc.run();
</script>
</body>
</html>
The example above will transform all the "scream" DOM
elements in H1
s.
HMVC can be used with or without a frontend JS framework (like Angular / Ember / BackBone) as its target is to be able to provide a way to shift tasks on the frontend, by creating decoupled and clean components: therefore, no matter what you are using, HMVC will be completely separate from your "main" framework (which can even be Rails, Express or Symfony2...)
You can simply include the component via bower.
After you included hmvc, you will then have the hmvc
variable available in the global JS scope, so that you
can play and start with your first component.
Components basically need to return an object with 2
simple properties, a run
function and a template
:
hmvc.component('greet', function(element){
var username = myAwesomeCookieLibrary.getCookieCoontent('username');
return {
run: function() {
return {
username: username
}
},
template: "<h3>Hello {{ username }},</h3>"
}
});
Now you simply have to add to your DOM:
<greet />
and upon calling hmvc.run()
your DOM will now look like:
<h3>Hello Barack,</h3>
Components can be specified exactly like AngularJS directives, so you can use:
<greet />
)<div greet />
)data-*
attributes (<div data-greet />
)The recommended way to write your own components
is by using the data-*
style.
In any case, all of the following markups are identical to HMVC:
<greet />
<p greet />
<header data-greet />
HMVC has a very simple DI implementation that works well when you want things to be as classy as possible, without declaring too many global JS variables.
You can declare your own services with:
myAwesomeService = {
doSomething: function() {
alert("THAT'S HELLA COOL!");
}
}
hmvc.service('myAwesomeService', myAwesomeService);
and then reuse the service in a component:
hmvc.component('something', function(element, myAwesomeService){
myAwesomeService.doSomething();
// ...
}, ["myAwesomeService"]);
Please note you'll have to declare the service "twice"
Yes, I know.
HMVC components usually have templates, which means that if you
return objects with a template
property from your component:
hmvc.component('greet', function(element){
var username = myAwesomeCookieLibrary.getCookieCoontent('username');
return {
run: function() {
return {
username: username
}
},
template: "<h3>Hello {{ username }},</h3>"
}
});
HMVC will try to render that template using the return value of
the run
function.
You can run components without templates (so that there will be no
output but you will only run the run
function), even though this
might not be a very popular use case for HMVC.
Templates are plain old JavaScript strings, so you can simply declare them elsewhere (maybe even using Jade and so on) and then pass them to the component:
hmvc.component('greet', function(element){
var username = myAwesomeCookieLibrary.getCookieCoontent('username');
return {
run: function() {
return {
username: username
}
},
template: templates.get('greet.html') // custom templating engine stuff!
}
});
HMVC also has the ability to render components that need to wait until a promise is resolved:
hmvc.component('fetch-remote-page', function(element){
return {
template: 'HTTP: {{ res.statusCode }}',
run: function() {
return http('https://example.org/hello').then(function(res){
return {
res: res
};
})
}
};
});
Soooo much fun!
Upon creating HMVC, we tried to downsize it and avoid unnecessary dependencies, inspired by this.
Not that jQuery is shit - because it's not - it's more to keep things as lightweight and lean as possible.
You might have noticed that when you declare a component, it takes
an element
as first argument:
hmvc.component('greet', function(element){
// ...
});
This element
is the "HMVC representation" of the HTML element
on which the component is running (ie. <div data-greet />
).
Even though you can use jQuery to do DOM manipulation and stuff, you might want to look at the native methods that HMVC offers on top of "its" elements, which are pretty self-explanatory:
original
(returns the original HTML element)text
html
attr
(acts as both setter and getter)hasClass
addClass
removeClass
parent
The test suite is written with CasperJS:
npm install -g casperjs
casperjs test/hmvc.js
Automated tests are setup through travis-ci even though
there seems to be an obscure problem with running the
tests there (locally it's all good, on travis it seems
there is some glitch with phantom 1.9.X
, as the JS
doesnt seem to be executed by the headless browser).
:*-(
HMVC is an established pattern in most server-side frameworks: it consists into defining small sub-MVC cycles in your application.
If you, for example, need to render a webpage with a main content and a sidebar, your controller would look like:
class ArticleController
def renderArticle(id, articleRepository)
return templating.render('article/detail', {
article: articleRepository.get(id)
});
end
end
and in your view you will have something like:
<div class="main">
<h1>
{{ article.title }}
</h1>
<div>
{{ article.content }}
</div>
</div>
{{ app.renderComponent('sidebar/render', { context: 'article/detail' }) }}
As you might understand, the renderComponent
function will
then boot a new controller that will return the view to be
included as a sidebar:
class SidebarController
def render(context)
return templating.render('sidebar/' + context);
end
end
I was quite surprised that the JS ecosystem didn't have anything on HMVC until the rise of angular directives :-) and after playing with AngularJS for a few months and seeing how you can't really mix it with a server-side framework in an easy way, I decided to give it a shot.
Simple as that!
I love Angular and yes, you can use it coupled wiith Rails
or another SS framework, but it wasn't meant for this -- ie.
try using the history.pushState()
in your app.
For those who care, this software is licensed under the MIT license.
HMVC has been succesfully adopted by FaceBook, Google, StackOverflow and The Red Cross.
The team behind FaceBook's React.js has been caught on camera upon hearing about HMVC:
Jokes apart, I'd like to get your feedback!