Commit 29732c99 authored by Mike Czepiel's avatar Mike Czepiel Committed by Sindre Sorhus

Close GH-251: Montage.

parent 00580880
......@@ -166,6 +166,9 @@
<li>
<a href="http://todomvc.meteor.com" data-source="http://meteor.com" data-content="Meteor is an ultra-simple environment for building modern websites.A Meteor application is a mix of JavaScript that runs inside a client web browser, JavaScript that runs on the Meteor server inside a Node.js container, and all the supporting HTML fragments, CSS rules, and static assets. Meteor automates the packaging and transmission of these different components. And, it is quite flexible about how you choose to structure those components in your file tree.">Meteor</a>
</li>
<li>
<a href="labs/architecture-examples/montage/" data-source="https://github.com/Motorola-Mobility/montage" data-content="Montage simplifies the development of rich HTML5 applications by providing modular components, real-time two-way data binding, CommonJS dependency management, and many more conveniences.">Montage</a>
</li>
<li>
<a href="labs/architecture-examples/socketstream/README.md" data-source="http://www.socketstream.org" data-content="SocketStream is a fast, modular Node.js web framework dedicated to building realtime single-page apps">SocketStream + jQuery</a>
</li>
......
montage-todomvc
==============
This is a Montage implementation of the TodoMVC application.
TL;DR: The unbundled application code included is for reference only; it is not being executed.
The [source code for this application is available](https://github.com/mczepiel/montage-todomvc) but
in an effort to not include an unminified version of Montage itself in TodoMVC we have run the
application through our build tool, [mop](https://github.com/Motorola-Mobility/montage/tree/master/tools/mop).
Montage applications are authored as CommonJS modules using our own XHR-based dependency management,
enabling us to write pure-HTML templates and boiler-plate-free JavaScript.
Mopping bundles the framework, application code, and HTML templates together into a handful of files
formatted using AMD-style dependency management.
While it would be technically possible to bundle the framework and manually wrap unminified
application code in an AMD-compatible manner for the purposes of this example, it would
not give an accurate impression of how to author Montage applications.
In the future we hope to aid debugging deployed applications with sourcemaps.
[Montage Framework Project](https://github.com/Motorola-Mobility/montage)
#montage-todomvc .visible {display: block;}
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
var Montage = require("montage").Montage;
exports.Todo = Montage.create(Montage, {
initWithTitle: {
value: function(title) {
this.title = title;
return this;
}
},
title: {
value: null
},
completed: {
value: false
}
});
<!doctype html>
<html lang=en id=montage-todomvc> <head> <meta charset=utf-8>
<meta http-equiv=X-UA-Compatible content="IE=edge,chrome=1">
<title>Montage • TodoMVC</title>
<link rel=stylesheet href="../../../assets/base.css">
<link rel=stylesheet href="assets/app.css">
<script src=bundle-0-d67ccde.js data-montage="packages/montage@4763f06/" data-montage-hash=4763f06 data-application-hash=1156f40></script>
<script type="text/montage-serialization">{"owner":{"prototype":"montage/ui/loader.reel"}}</script>
</head>
<body></body>
</html>
\ No newline at end of file
var Montage = require("montage").Montage,
Converter = require("montage/core/converter/converter").Converter;
exports.ItemCountConverter = Montage.create(Converter, {
convert: {
value: function(itemCount) {
return (1 === itemCount) ? "item" : "items";
}
}
});
#main,
#footer,
#clear-completed-container {display: none;}
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Main</title>
<link rel="stylesheet" type="text/css" href="main.css">
<script type="text/montage-serialization">
{
"owner": {
"properties": {
"element": {"#": "mainComponent"},
"newTodoForm": {"#": "newTodoForm"},
"newTodoInput": {"#": "new-todo"}
}
},
"todoRepetition": {
"prototype": "montage/ui/repetition.reel",
"properties": {
"element": {"#": "todo-list"}
},
"bindings": {
"contentController": {"<-": "@owner.todoListController"}
}
},
"todoView": {
"prototype": "ui/todo-view.reel",
"properties": {
"element": {"#": "todoView"}
},
"bindings": {
"todo": {"<-": "@todoRepetition.objectAtCurrentIteration"}
}
},
"main": {
"prototype": "montage/ui/dynamic-element.reel",
"properties": {
"element": {"#": "main"}
},
"bindings": {
"classList.visible": {"<-": "@owner.todoListController.organizedObjects.0"}
}
},
"footer": {
"prototype": "montage/ui/dynamic-element.reel",
"properties": {
"element": {"#": "footer"}
},
"bindings": {
"classList.visible": {"<-": "@owner.todoListController.organizedObjects.0"}
}
},
"toggleAllCheckbox": {
"prototype": "montage/ui/native/input-checkbox.reel",
"properties": {
"element": {"#": "toggle-all"}
},
"bindings": {
"checked": {"<->": "@owner.allCompleted"}
}
},
"todoCount": {
"prototype": "montage/ui/dynamic-text.reel",
"properties": {
"element": {"#": "todo-count"}
},
"bindings": {
"value": {"<-": "@owner.todosLeft.count()"}
}
},
"itemCountConverter": {
"prototype": "ui/main.reel/item-count-converter"
},
"todoCountWording": {
"prototype": "montage/ui/dynamic-text.reel",
"properties": {
"element": {"#": "todo-count-wording"}
},
"bindings": {
"value": {"<-": "@owner.todosLeft.count()", "converter": {"@": "itemCountConverter"}}
}
},
"completedCount": {
"prototype": "montage/ui/dynamic-text.reel",
"properties": {
"element": {"#": "completed-count"}
},
"bindings": {
"value": {"<-": "@owner.completedTodos.count()"}
}
},
"clearCompletedContainer": {
"prototype": "montage/ui/dynamic-element.reel",
"properties": {
"element": {"#": "clear-completed-container"}
},
"bindings": {
"classList.visible": {"<-": "@owner.completedTodos.count()"}
}
},
"clearCompletedButton": {
"prototype": "montage/ui/native/button.reel",
"properties": {
"element": {"#": "clear-completed"}
},
"listeners": [
{
"type": "action",
"listener": {"@": "owner"},
"capture": false
}
]
}
}
</script>
</head>
<body>
<div data-montage-id="mainComponent">
<section id="todoapp">
<header id="header">
<h1>todos</h1>
<form data-montage-id="newTodoForm">
<input data-montage-id="new-todo" id="new-todo" placeholder="What needs to be done?" autofocus>
</form>
</header>
<section data-montage-id="main" id="main">
<input data-montage-id="toggle-all" id="toggle-all" type="checkbox">
<label for="toggle-all">Mark all as complete</label>
<ul data-montage-id="todo-list" id="todo-list">
<li data-montage-id="todoView"></li>
</ul>
</section>
<footer data-montage-id="footer" id="footer">
<span id="todo-count"><strong data-montage-id="todo-count">0</strong> <span data-montage-id="todo-count-wording">items</span> left</span>
<div data-montage-id="clear-completed-container" id="clear-completed-container">
<button data-montage-id="clear-completed" id="clear-completed">Clear completed (<span data-montage-id="completed-count">0</span>)</button>
</div>
</footer>
</section>
<footer id="info">
<p>Double-click to edit a todo</p>
<p>Created with <a href="http://github.com/motorola-mobility/montage">Montage</a> </p>
<p>Source available at <a href="http://github.com/mczepiel/montage-todomvc">Montage-TodoMVC</a> </p>
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
</footer>
</div>
</body>
</html>
var Montage = require("montage").Montage,
Component = require("montage/ui/component").Component,
ArrayController = require("montage/ui/controller/array-controller").ArrayController,
Todo = require("core/todo").Todo,
Serializer = require("montage/core/serializer").Serializer,
Deserializer = require("montage/core/deserializer").Deserializer,
LOCAL_STORAGE_KEY = "todos-montage";
exports.Main = Montage.create(Component, {
newTodoForm: {
value: null
},
newTodoInput: {
value: null
},
todoListController: {
serializable: false,
value: null
},
didCreate: {
value: function() {
this.todoListController = ArrayController.create();
this.load();
}
},
load: {
value: function() {
if (localStorage) {
var todoSerialization = localStorage.getItem(LOCAL_STORAGE_KEY);
if (todoSerialization) {
var deserializer = Deserializer.create(),
self = this;
try {
deserializer.initWithStringAndRequire(todoSerialization, require).deserializeObject(function(todos) {
self.todoListController.initWithContent(todos);
}, require);
} catch(e) {
console.error("Could not load saved tasks.");
console.debug("Could not deserialize", todoSerialization);
console.log(e.stack);
}
}
}
}
},
save: {
value: function() {
if (localStorage) {
var todos = this.todoListController.content,
serializer = Serializer.create().initWithRequire(require);
localStorage.setItem(LOCAL_STORAGE_KEY, serializer.serializeObject(todos));
}
}
},
prepareForDraw: {
value: function() {
this.newTodoForm.identifier = "newTodoForm";
this.newTodoForm.addEventListener("submit", this, false);
this.addEventListener("destroyTodo", this, true);
window.addEventListener("beforeunload", this, true);
}
},
captureDestroyTodo: {
value: function(evt) {
this.destroyTodo(evt.detail.todo);
}
},
handleNewTodoFormSubmit: {
value: function(evt) {
evt.preventDefault();
var title = this.newTodoInput.value.trim();
if ("" === title) {
return;
}
this.createTodo(title);
this.newTodoInput.value = null;
}
},
createTodo: {
value: function(title) {
var todo = Todo.create().initWithTitle(title);
this.todoListController.addObjects(todo);
return todo;
}
},
destroyTodo: {
value: function(todo) {
this.todoListController.removeObjects(todo);
return todo;
}
},
allCompleted: {
dependencies: ["todoListController.organizedObjects.completed"],
get: function() {
return this.todoListController.organizedObjects.getProperty("completed").all();
},
set: function(value) {
this.todoListController.organizedObjects.forEach(function(member) {
member.completed = value;
});
}
},
todosLeft: {
dependencies: ["todoListController.organizedObjects.completed"],
get: function() {
if (this.todoListController.organizedObjects) {
var todos = this.todoListController.organizedObjects;
return todos.filter(function(member) {
return !member.completed;
});
}
}
},
completedTodos: {
dependencies: ["todoListController.organizedObjects.completed"],
get: function() {
if (this.todoListController.organizedObjects) {
var todos = this.todoListController.organizedObjects;
return todos.filter(function(member) {
return member.completed;
});
}
}
},
handleClearCompletedButtonAction: {
value: function(evt) {
var completedTodos = this.todoListController.organizedObjects.filter(function(todo) {
return todo.completed;
});
if (completedTodos.length > 0) {
this.todoListController.removeObjects.apply(this.todoListController, completedTodos);
}
}
},
captureBeforeunload: {
value: function() {
this.save();
}
}
});
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>TodoView</title>
<script type="text/montage-serialization">
{
"owner": {
"properties": {
"element": {"#": "todoView"},
"editInput": {"@": "editInput"}
}
},
"todoTitle": {
"prototype": "montage/ui/dynamic-text.reel",
"properties": {
"element": {"#": "todoTitle"}
},
"bindings": {
"value": {"<-": "@owner.todo.title"}
}
},
"todoCompletedCheckbox": {
"prototype": "montage/ui/native/input-checkbox.reel",
"properties": {
"element": {"#": "todoCompletedCheckbox"}
},
"bindings": {
"checked": {"<->": "@owner.todo.completed"}
}
},
"destroyButton": {
"prototype": "montage/ui/native/button.reel",
"properties": {
"element": {"#": "destroyButton"}
},
"listeners": [
{
"type": "action",
"listener": {"@": "owner"},
"capture": true
}
]
},
"editInput": {
"prototype": "montage/ui/native/input-text.reel",
"properties": {
"element": {"#": "edit-input"}
},
"bindings": {
"value": {"<-": "@owner.todo.title"}
}
}
}
</script>
</head>
<body>
<li data-montage-id="todoView">
<div class="view">
<input data-montage-id="todoCompletedCheckbox" class="toggle" type="checkbox">
<label data-montage-id="todoTitle"></label>
<button data-montage-id="destroyButton" class="destroy"></button>
</div>
<form data-montage-id="edit">
<input data-montage-id="edit-input" class="edit" value="Rule the web">
</form>
</li>
</body>
</html>
var Montage = require("montage").Montage,
Component = require("montage/ui/component").Component;
exports.TodoView = Montage.create(Component, {
todo: {
value: null
},
editInput: {
value: null
},
didCreate: {
value: function() {
Object.defineBinding(this, "isCompleted", {
boundObject: this,
boundObjectPropertyPath: "todo.completed",
oneway: true
});
}
},
prepareForDraw: {
value: function() {
this.element.addEventListener("dblclick", this, false);
this.element.addEventListener("blur", this, true);
this.element.addEventListener("submit", this, false);
}
},
captureDestroyButtonAction: {
value: function() {
this.dispatchDestroy();
}
},
dispatchDestroy: {
value: function() {
this.dispatchEventNamed("destroyTodo", true, true, {todo: this.todo})
}
},
handleDblclick: {
value: function(evt) {
this.isEditing = true;
}
},
_isEditing: {
value: false
},
isEditing: {
get: function() {
return this._isEditing;
},
set: function(value) {
if (value === this._isEditing) {
return;
}
this._isEditing = value;
this.needsDraw = true;
}
},
_isCompleted: {
value: false
},
isCompleted: {
get: function() {
return this._isCompleted;
},
set: function(value) {
if (value === this._isCompleted) {
return;
}
this._isCompleted = value;
this.needsDraw = true;
}
},
captureBlur: {
value: function(evt) {
if (this.isEditing && this.editInput.element === evt.target) {
this._submitTitle();
}
}
},
handleSubmit: {
value: function(evt) {
if (this.isEditing) {
evt.preventDefault();
this._submitTitle();
}
}
},
_submitTitle: {
value: function() {
var title = this.editInput.value.trim();
if ("" === title) {
this.dispatchDestroy();
} else {
this.todo.title = title;
}
this.isEditing = false;
}
},
draw: {
value: function() {
if (this.isEditing) {
this.element.classList.add("editing");
this.editInput.element.select();
} else {
this.element.classList.remove("editing");
this.editInput.element.blur();
}
if (this.isCompleted) {
this.element.classList.add("completed");
} else {
this.element.classList.remove("completed");
}
}
}
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment