Commit e7fbe334 authored by Harald Schubert's avatar Harald Schubert Committed by Pascal Hartig

TodoMVC using SAPUI5

parent e7272037
html,
body {
margin: 0;
padding: 0;
}
button {
margin: 0;
padding: 0;
border: 0;
background: none;
font-size: 100%;
vertical-align: baseline;
font-family: inherit;
color: inherit;
-webkit-appearance: none;
-moz-appearance: none;
-ms-appearance: none;
-o-appearance: none;
appearance: none;
}
body {
font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
line-height: 1.4em;
background: #eaeaea url('../img/bg.png');
color: #4d4d4d;
width: 550px;
margin: 0 auto;
-webkit-font-smoothing: antialiased;
-moz-font-smoothing: antialiased;
-ms-font-smoothing: antialiased;
-o-font-smoothing: antialiased;
font-smoothing: antialiased;
}
.sapUiTv, .sapUiBtnS {
font: inherit;
font-size: inherit;
}
#todoapp {
background: #fff;
background: rgba(255, 255, 255, 0.9);
margin: 130px 0 40px 0;
border: 1px solid #ccc;
position: relative;
border-top-left-radius: 2px;
border-top-right-radius: 2px;
box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.2),
0 25px 50px 0 rgba(0, 0, 0, 0.15);
}
#todoapp:before {
content: '';
border-left: 1px solid #f5d6d6;
border-right: 1px solid #f5d6d6;
width: 2px;
position: absolute;
top: 0;
left: 40px;
height: 100%;
}
#todoapp input::-webkit-input-placeholder {
font-style: italic;
}
#todoapp input:-moz-placeholder {
font-style: italic;
color: #a9a9a9;
}
#todoapp h1 {
position: absolute;
top: -120px;
width: 100%;
font-size: 70px;
font-weight: bold;
text-align: center;
color: #b3b3b3;
color: rgba(255, 255, 255, 0.3);
text-shadow: -1px -1px rgba(0, 0, 0, 0.2);
-webkit-text-rendering: optimizeLegibility;
-moz-text-rendering: optimizeLegibility;
-ms-text-rendering: optimizeLegibility;
-o-text-rendering: optimizeLegibility;
text-rendering: optimizeLegibility;
}
#header {
padding-top: 15px;
border-radius: inherit;
}
#header:before {
content: '';
position: absolute;
top: 0;
right: 0;
left: 0;
height: 15px;
z-index: 2;
border-bottom: 1px solid #6c615c;
background: #8d7d77;
background: -webkit-gradient(linear, left top, left bottom, from(rgba(132, 110, 100, 0.8)),to(rgba(101, 84, 76, 0.8)));
background: -webkit-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
background: -moz-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
background: -o-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
background: -ms-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
background: linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#9d8b83', EndColorStr='#847670');
border-top-left-radius: 1px;
border-top-right-radius: 1px;
}
#main {
position: relative;
z-index: 2;
border-top: 1px dotted #adadad;
}
#new-todo,
.sapUiTfBrd.sapUiTfRo.todo,
.sapUiTfBrd.sapUiTfStd.todo {
position: relative;
margin: 0;
margin-right: 153px;
width: 100%;
font-size: 24px;
font-family: inherit;
line-height: 1.4em;
background-color: transparent;
border: 0;
outline: none;
color: #4D4D4D;
padding: 6px;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
-o-box-sizing: border-box;
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
-moz-font-smoothing: antialiased;
-ms-font-smoothing: antialiased;
-o-font-smoothing: antialiased;
font-smoothing: antialiased;
}
.sapUiTfBrd.sapUiTfStd.todo {
border: 1px solid #999;
box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
}
.sapUiTfBrd.sapUiTfRo.todo[data-completed="true"] {
color: #a9a9a9;
text-decoration: line-through;
}
#toggle-all {
display: block;
outline: none;
}
#toggle-all input {
z-index: 3;
position: absolute;
text-align: center;
top: 9px;
left: -15px;
width: 65px;
height: 41px;
-webkit-transform: rotate(90deg);
/* transform: rotate(90deg); */
-webkit-appearance: none;
appearance: none;
}
#toggle-all input:before {
content: '»';
font-size: 28px;
color: #d9d9d9;
padding: 0 25px 7px;
}
#toggle-all input:checked:before {
color: #737373;
}
#new-todo {
padding: 15px 15px 16px 60px;
border: none;
background: rgba(0, 0, 0, 0.02);
z-index: 2;
box-shadow: none;
}
#todo-list {
margin: 0;
padding: 0;
list-style: none;
}
#todo-list li {
position: relative;
font-size: 24px;
border-top: 1px dotted #ccc;
}
#todo-list input[type='checkbox'] {
text-align: center;
width: 40px;
/* auto, since non-WebKit browsers doesn't support input styling */
height: auto;
position: absolute;
top: 0;
bottom: 0;
margin: auto 0;
-webkit-appearance: none;
/*-moz-appearance: none;*/
-ms-appearance: none;
-o-appearance: none;
appearance: none;
}
#todo-list input[type='checkbox']:after {
content: '✔';
line-height: 62px;
font-size: 20px;
color: #d9d9d9;
text-shadow: 0 -1px 0 #bfbfbf;
}
#todo-list input[type='checkbox']:checked:after {
color: #85ada7;
text-shadow: 0 1px 0 #669991;
bottom: 1px;
position: relative;
}
#todo-list input:not([type='checkbox']) {
word-break: break-word;
padding: 15px;
margin-left: 45px;
display: block;
line-height: 1.2em;
-webkit-transition: color 0.4s;
-moz-transition: color 0.4s;
-ms-transition: color 0.4s;
-o-transition: color 0.4s;
transition: color 0.4s;
}
#todo-list li .destroy {
outline: none;
background-color: transparent;
display: none;
position: absolute;
top: 0;
right: 10px;
bottom: 0;
width: 40px;
height: 40px;
margin: auto 0;
font-size: 22px;
color: #a88a8a;
-webkit-transition: all 0.2s;
-moz-transition: all 0.2s;
-ms-transition: all 0.2s;
-o-transition: all 0.2s;
transition: all 0.2s;
}
#todo-list li .destroy:hover {
text-shadow: 0 0 1px #000,
0 0 10px rgba(199, 107, 107, 0.8);
-webkit-transform: scale(1.3);
-moz-transform: scale(1.3);
-ms-transform: scale(1.3);
-o-transform: scale(1.3);
transform: scale(1.3);
}
#todo-list li .destroy:after {
content: '✖';
}
#todo-list li:hover .destroy {
display: block;
}
#todo-list .sapUiRrNoData,
#todo-list .sapUiRrPtb,
#todo-list .sapUiRrFtr {
display: none;
}
#footer {
color: #777;
padding: 0 15px;
position: absolute;
right: 0;
bottom: -31px;
left: 0;
height: 20px;
z-index: -1;
text-align: center;
}
#footer:before {
content: '';
position: absolute;
right: 0;
bottom: 31px;
left: 0;
height: 50px;
z-index: -1;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3),
0 6px 0 -3px rgba(255, 255, 255, 0.8),
0 7px 1px -3px rgba(0, 0, 0, 0.3),
0 43px 0 -6px rgba(255, 255, 255, 0.8),
0 44px 2px -6px rgba(0, 0, 0, 0.2);
}
#footer #AllTodos, #footer #ActiveTodos, #footer #CompletedTodos {
color: #83756f;
}
#footer #clear-completed {
color: inherit;
}
#footer .sapUiHLayoutChildWrapper:nth-of-type(1) {
float: left;
text-align: left;
}
#todo-count {
vertical-align: 1px;
}
#todo-count:first-letter {
font-weight: bold;
}
#filters {
margin: 0;
padding: 0 2px 0 0;
position: absolute;
right: 0;
left: 0;
outline: none;
}
#filters .sapUiBtnStd,
#filters .sapUiBtnFoc {
background-color: transparent;
font-weight: normal;
outline: none;
padding-right: 3px;
margin-top: -1px;
}
#filters .sapUiSegButtonSelected.sapUiBtnStd,
#filters .sapUiSegButtonSelected.sapUiBtnAct.sapUiBtnFoc {
font-weight: bold;
}
#footer .sapUiHLayoutChildWrapper:nth-of-type(3) {
float: right;
}
#clear-completed {
float: right;
position: relative;
line-height: 20px;
text-decoration: none;
background: rgba(0, 0, 0, 0.1);
font-size: 11px;
padding: 0 10px;
border-radius: 3px;
box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.2);
height: 20px;
outline: none;
}
@-moz-document url-prefix() {
#clear-completed {
top: -22px;
}
}
#clear-completed:hover {
background: rgba(0, 0, 0, 0.15);
box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.3);
}
#info {
margin: 65px auto 0;
color: #a6a6a6;
font-size: 12px;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.7);
text-align: center;
}
#info a {
color: inherit;
}
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>SAPUI5 • TodoMVC</title>
<script
src="https://sapui5.netweaver.ondemand.com/resources/sap-ui-core.js"
id="sap-ui-bootstrap"
data-sap-ui-libs="sap.ui.commons,sap.ui.ux3,sap.ui.table"
data-sap-ui-theme="base">
</script>
<link href="css/base.css" rel="stylesheet" type="text/css">
<script src="js/app.js"></script>
</head>
<body role="application">
<section id="todoapp">
<header id="header">
<h1>todos</h1>
</header>
<section id="main"></section>
</section>
<footer id="info">
<p>Double-click to edit a todo</p>
<p>Written by Harald Schubert. Based on SAP UI5.</p>
<p>Part of <a href="http://todomvc.com/">TodoMVC</a>.</p>
</footer>
</body>
</html>
/*global jQuery, sap */
/*jshint unused:false */
(function () {
'use strict';
var oRootView;
jQuery.sap.registerModulePath('todo', 'js/todo');
// build the application root view and place on page
oRootView = sap.ui.view({
type: sap.ui.core.mvc.ViewType.JS,
id: 'todoView',
viewName: 'todo.Todo'
});
oRootView.placeAt('main');
})();
/*global jQuery, sap */
/*jshint unused:false */
/*
* A text field that supports placeholder values and autofocus
*/
(function () {
'use strict';
jQuery.sap.declare('todo.SmartTextField');
jQuery.sap.require('sap.ui.core.Core', 'sap.ui.commons.TextField');
sap.ui.commons.TextField.extend('todo.SmartTextField', {
metadata: {
properties: {
placeholder: 'string',
autofocus: 'boolean',
strongediting: 'boolean'
}
},
renderer: {
renderInnerAttributes: function (oRm, oTextField) {
if (oTextField.getProperty('placeholder')) {
oRm.writeAttributeEscaped('placeholder', oTextField
.getPlaceholder());
}
}
},
onAfterRendering: function (e) {
var $domRef = this.$();
if (this.getProperty('autofocus')) {
$domRef.focus();
}
if (this.getProperty('strongediting')) {
this.setEditable(false);
this.attachBrowserEvent('dblclick', function (e) {
var $domRef = this.$();
this.setEditable(true);
$domRef.get(0).selectionStart = $domRef.get(0).selectionEnd = $domRef.val().length;
});
this.attachChange(function () {
this.setEditable(false);
});
}
},
onfocusout: function (e) {
if (this.getProperty('strongediting')) {
if (!this.getEditable()) {
return;
}
this.setEditable(false);
this.getModel().updateBindings(true);
}
}
});
})();
/*global jQuery, sap, todo */
/*jshint unused:false */
/*
* Performs no UI logic and has therefore has no references to UI controls.
* Accesses and modifies model and relies on data binding to inform UI about changes.
*/
(function () {
'use strict';
jQuery.sap.require('todo.TodoPersistency');
sap.ui.controller('todo.Todo', {
// Stores todos permanently via HTML5 localStorage
store: new todo.TodoPersistency('todos'),
// Stores todos for the duration of the session
model: null,
// Retrieve todos from store and initialize model
onInit: function () {
var data = null;
if (this.store.isEmpty()) {
data = this.store.set({
todos: []
}).get();
} else {
data = this.store.get();
}
this.model = new sap.ui.model.json.JSONModel(data);
this.getView().setModel(this.model);
},
// Create a new todo
createTodo: function (todo) {
todo = todo.trim();
if (todo.length === 0) {
return;
}
this.model.setProperty('/todos/', this.model.getProperty('/todos/')
.push({
id: jQuery.sap.uid(),
done: false,
text: todo
}));
this.store.set(this.model.getData());
this.model.updateBindings(true);
},
// Clear todo
clearTodo: function (todo) {
var todos = this.model.getProperty('/todos/');
for (var i = todos.length - 1; i >= 0; i--) {
if (todos[i].id === todo.getProperty('id')) {
todos.splice(i, 1);
}
}
this.model.setProperty('/todos/', todos);
this.store.set(this.model.getData());
this.model.updateBindings(true);
},
// Clear all completed todos
clearCompletedTodos: function () {
var todos = this.model.getProperty('/todos/');
for (var i = todos.length - 1; i >= 0; i--) {
if (todos[i].done === true) {
todos.splice(i, 1);
}
}
this.model.setProperty('/todos/', todos);
this.store.set(this.model.getData());
this.model.updateBindings(true);
},
// Complete / reopen all todos
toggleAll: function () {
var todos = this.model.getProperty('/todos/');
var hasOpenTodos = todos.some(function (element, index, array) {
return element.done === false;
});
todos.forEach(function (todo) {
todo.done = hasOpenTodos;
});
this.store.set(this.model.getData());
this.model.updateBindings(true);
},
// Complete / reopen a todo
todoToggled: function (todo) {
this.store.set(this.model.getData());
this.model.updateBindings(true);
},
// Rename a todo
todoRenamed: function (todo) {
var text = todo.getProperty('text').trim();
if (text.length === 0) {
this.clearTodo(todo);
} else {
todo.getModel().setProperty(todo.getPath() + '/text', text);
this.store.set(this.model.getData());
this.model.updateBindings(true);
}
},
// Change model filter based on selection
todosSelected: function (selectionMode) {
if (selectionMode === 'AllTodos') {
this.getView().changeSelection([]);
} else if (selectionMode === 'ActiveTodos') {
this.getView().changeSelection(
[new sap.ui.model.Filter('done',
sap.ui.model.FilterOperator.EQ, false)]);
} else if (selectionMode === 'CompletedTodos') {
this.getView().changeSelection(
[new sap.ui.model.Filter('done',
sap.ui.model.FilterOperator.EQ, true)]);
}
}
});
})();
/*global jQuery, sap, todo, $ */
/*jshint unused:false */
/*
* Does all UI-related things like creating controls, data binding configuration,
* setting up callbacks, etc. Does not perform any business logic.
*/
(function () {
'use strict';
jQuery.sap.require('todo.SmartTextField');
jQuery.sap.require('todo.formatters');
sap.ui.jsview('todo.Todo', {
getControllerName: function () {
return 'todo.Todo';
},
controls: [],
repeater: false,
createContent: function (oController) {
var toggleAll, newTodo, todosRepeater, completedDataTemplate, todoTemplate, todoCount,
todosSelection, clearCompleted, todosFooter;
// Toggle button to mark all todos as completed / open
toggleAll = new sap.ui.commons.CheckBox({
id: 'toggle-all',
checked: {
path: '/todos/',
formatter: todo.formatters.allCompletedTodosFormatter
},
visible: {
path: '/todos/',
formatter: todo.formatters.isArrayNonEmptyFormatter
}
}).attachChange(function () {
oController.toggleAll();
});
this.controls.push(toggleAll);
// Text field for entering a new todo
newTodo = new todo.SmartTextField('new-todo', {
placeholder: 'What needs to be done?',
// Don't autofocus in case of MSIE and Opera as both hide placeholder on focus
autofocus: !$.browser.msie && !$.browser.opera
}).attachChange(function () {
oController.createTodo(this.getProperty('value'));
this.setValue('');
}).addStyleClass('create-todo');
this.controls.push(newTodo);
// Row repeater that will hold our todos
todosRepeater = new sap.ui.commons.RowRepeater('todo-list', {
design: sap.ui.commons.RowRepeaterDesign.Transparent,
numberOfRows: 100
});
this.repeater = todosRepeater;
// Completed flag that is later bound to the done status of a todo
// We attach this to each text field and write it to the DOM as a data-*
// attribute; this way, we can refer to it in our stylesheet
completedDataTemplate = new sap.ui.core.CustomData({
key: 'completed',
value: {
path: 'done',
formatter: todo.formatters.booleanToStringFormatter
},
writeToDom: true
});
// A template used by the row repeater to render a todo
todoTemplate = new sap.ui.commons.layout.HorizontalLayout({
content: [new sap.ui.commons.CheckBox({
checked: '{done}'
}).attachChange(function () {
oController.todoToggled(this.getBindingContext());
}), new todo.SmartTextField({
value: '{text}',
strongediting: true
}).attachBrowserEvent('dblclick', function (e) {
$('.destroy').css('display', 'none');
}).attachChange(function () {
oController.todoRenamed(this.getBindingContext());
}).addStyleClass('todo').addCustomData(completedDataTemplate),
new sap.ui.commons.Button({
lite: true,
text: ''
}).addStyleClass('destroy').attachPress(function () {
oController.clearTodo(this.getBindingContext());
})]
});
// Helper function to rebind the aggregation with different filters
todosRepeater.rebindAggregation = function (filters) {
this.unbindRows();
this.bindRows('/todos/', todoTemplate, null, filters);
};
// Initially, we don't filter any todos
todosRepeater.rebindAggregation([]);
this.controls.push(todosRepeater);
// Counts open todos
todoCount = new sap.ui.commons.TextView('todo-count', {
text: {
path: '/todos/',
formatter: todo.formatters.openTodoCountFormatter
}
});
// Allows selecting what todos to show
todosSelection = new sap.ui.commons.SegmentedButton('filters', {
id: 'TodosSelection',
buttons: [new sap.ui.commons.Button({
id: 'AllTodos',
lite: true,
text: 'All'
}), new sap.ui.commons.Button({
id: 'ActiveTodos',
lite: true,
text: 'Active'
}), new sap.ui.commons.Button({
id: 'CompletedTodos',
lite: true,
text: 'Completed'
})]
}).attachSelect(function (e) {
oController.todosSelected(e.getParameters().selectedButtonId);
});
todosSelection.setSelectedButton('AllTodos');
// Button to clear all completed todos
clearCompleted = new sap.ui.commons.Button({
id: 'clear-completed',
lite: true,
text: {
path: '/todos/',
formatter: todo.formatters.completedTodoCountFormatter
},
visible: {
path: '/todos/',
formatter: todo.formatters.hasCompletedTodosFormatter
}
}).attachPress(function () {
oController.clearCompletedTodos();
});
todosFooter = new sap.ui.commons.layout.HorizontalLayout('footer', {
content: [todoCount, todosSelection, clearCompleted],
visible: {
path: '/todos/',
formatter: todo.formatters.isArrayNonEmptyFormatter
}
});
this.controls.push(todosFooter);
return this.controls;
},
changeSelection: function (filters) {
this.repeater.rebindAggregation(filters);
}
});
})();
/*global jQuery, todo */
/*jshint unused:false */
(function () {
'use strict';
jQuery.sap.declare('todo.TodoPersistency');
todo.TodoPersistency = function (aName) {
this.name = aName;
};
todo.TodoPersistency.prototype = function () {
var storage = window.localStorage;
return {
get: function () {
return JSON.parse(storage.getItem(this.name));
},
set: function (data) {
storage.setItem(this.name, JSON.stringify(data));
return this; // for method chaining
},
remove: function () {
storage.removeItem(this.name);
return this; // for method chaining
},
isEmpty: function () {
return !(this.get());
}
};
}();
})();
/*global jQuery, todo */
/*jshint unused:false */
/*
* Formatters used for data binding.
*/
(function () {
'use strict';
jQuery.sap.declare('todo.formatters');
todo.formatters = {
// Returns whether all todos are completed
allCompletedTodosFormatter: function (aTodos) {
return !(aTodos.some(function (element, index, array) {
return element.done === false;
}));
},
// Converts booleans to strings
booleanToStringFormatter: function (value) {
if (value === true) {
return 'true';
}
return 'false';
},
// Counts the number of closed todos
completedTodoCountFormatter: function (aTodos) {
var numberOfCompletedItems = 0;
aTodos.forEach(function (todo) {
if (todo.done === true) {
numberOfCompletedItems++;
}
});
return 'Clear completed (' + numberOfCompletedItems + ')';
},
// Returns whether a completed todo is available
hasCompletedTodosFormatter: function (aTodos) {
return aTodos.some(function (element, index, array) {
return element.done === true;
});
},
// Returns whether a an array has elements
isArrayNonEmptyFormatter: function (aTodos) {
return aTodos.length > 0;
},
// Counts the number of open todos
openTodoCountFormatter: function (aTodos) {
var numberOfOpenItems = 0;
aTodos.forEach(function (todo) {
if (todo.done === false) {
numberOfOpenItems++;
}
});
return numberOfOpenItems === 1 ? '1 item left' : numberOfOpenItems + ' items left';
}
};
})();
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