This commit is contained in:
Kload 2013-07-01 15:56:32 +02:00
commit d6e1a19f16
15 changed files with 4022 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
*.swp

4
README.md Normal file
View file

@ -0,0 +1,4 @@
YunoHost Admin
==============
JS client for YunoHost API

1
home.ms Normal file
View file

@ -0,0 +1 @@
Hello :)

30
index.html Normal file
View file

@ -0,0 +1,30 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="cache-control" content="no-cache" />
<title>YunoHost admin</title>
</head>
<body>
<h1>YunoHost</h1>
<div id="flash">
</div>
<div id="content">
<a href="#/">Greet</a>
</div>
<div id="main">
<a href="#/login">Grset</a>
</div>
<script type="text/javascript" src="js/jquery-1.10.1.min.js"></script>
<script type="text/javascript" src="js/mustache.js"></script>
<script type="text/javascript" src="js/sammy.js"></script>
<script type="text/javascript" src="js/sammy.mustache.js"></script>
<script type="text/javascript" src="js/sammy.json.js"></script>
<script type="text/javascript" src="js/sammy.storage.js"></script>
<script type="text/javascript" src="js/sammy.flash.js"></script>
<script type="text/javascript" src="js/app.js"></script>
</body>
</html>

98
js/app.js Normal file
View file

@ -0,0 +1,98 @@
app = Sammy('#main', function (sam) {
sam.use('Mustache', 'ms');
var store = new Sammy.Store({name: 'storage', type: 'session'});
var flash = function (level, message) {
flashs = store.get('flash');
if (!flashs) { flashs = {'info': [], 'fail': [], 'success': [] } }
flashs[level].push(message);
store.set('flash', flashs);
html = '';
for(lvl in flashs) {
flashs[lvl].forEach( function(msg) {
html += '<div class="'+ lvl +'">'+ msg +'</div>';
});
}
$('#flash').html(html);
}
var api = function (uri, callback, method, data) {
method = typeof method !== 'undefined' ? method : 'GET';
data = typeof data !== 'undefined' ? data : {};
auth = "Basic "+ btoa(store.get('user') +':'+ atob(store.get('password')));
jQuery.ajax({
url: store.get('url') + uri,
type: method,
crossdomain: true,
data: data,
dataType: 'json',
beforeSend: function(req) {
req.setRequestHeader('Authorization', auth);
}
})
.done(function(data) {
console.log(data);
result = data;
})
.fail(function() {
alert('fail');
result = false;
})
.always(function() {
callback(result);
});
}
sam.after(function () {
store.set('flash', {'info': [], 'fail': [], 'success': [] });
});
sam.before({except: {path: '#/login'}}, function (req) {
store.set('path', req.path);
if (!store.get('user')) {
req.redirect('#/login');
return false;
}
});
sam.get('#/', function (c) {
c.render('home.ms').swap();
});
sam.get('#/users/:user', function (c) {
c.swap('');
api('/users/'+ c.params['user'], function(data) {
c.render('user_info.ms', data).swap();
});
});
sam.get('#/login', function (c) {
c.render('login.ms').swap();
});
sam.post('#/login', function (c) {
store.set('url', c.params['url']);
store.set('user', c.params['user']);
store.set('password', btoa(c.params['password']));
api('/users', function(data) {
if (data.error) {
flash('fail', 'Error: '+ data.error);
} else {
flash('success', 'Connected :)');
}
if (store.get('path')) {
c.redirect(store.get('path'));
} else {
c.redirect('#/');
}
});
});
});
$(document).ready(function () {
app.run('#/');
});

6
js/jquery-1.10.1.min.js vendored Normal file

File diff suppressed because one or more lines are too long

536
js/mustache.js Normal file
View file

@ -0,0 +1,536 @@
/*!
* mustache.js - Logic-less {{mustache}} templates with JavaScript
* http://github.com/janl/mustache.js
*/
/*global define: false*/
(function (root, factory) {
if (typeof exports === "object" && exports) {
factory(exports); // CommonJS
} else {
var mustache = {};
factory(mustache);
if (typeof define === "function" && define.amd) {
define(mustache); // AMD
} else {
root.Mustache = mustache; // <script>
}
}
}(this, function (mustache) {
var whiteRe = /\s*/;
var spaceRe = /\s+/;
var nonSpaceRe = /\S/;
var eqRe = /\s*=/;
var curlyRe = /\s*\}/;
var tagRe = /#|\^|\/|>|\{|&|=|!/;
// Workaround for https://issues.apache.org/jira/browse/COUCHDB-577
// See https://github.com/janl/mustache.js/issues/189
var RegExp_test = RegExp.prototype.test;
function testRegExp(re, string) {
return RegExp_test.call(re, string);
}
function isWhitespace(string) {
return !testRegExp(nonSpaceRe, string);
}
var Object_toString = Object.prototype.toString;
var isArray = Array.isArray || function (obj) {
return Object_toString.call(obj) === '[object Array]';
};
function escapeRegExp(string) {
return string.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&");
}
var entityMap = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': '&quot;',
"'": '&#39;',
"/": '&#x2F;'
};
function escapeHtml(string) {
return String(string).replace(/[&<>"'\/]/g, function (s) {
return entityMap[s];
});
}
function Scanner(string) {
this.string = string;
this.tail = string;
this.pos = 0;
}
/**
* Returns `true` if the tail is empty (end of string).
*/
Scanner.prototype.eos = function () {
return this.tail === "";
};
/**
* Tries to match the given regular expression at the current position.
* Returns the matched text if it can match, the empty string otherwise.
*/
Scanner.prototype.scan = function (re) {
var match = this.tail.match(re);
if (match && match.index === 0) {
this.tail = this.tail.substring(match[0].length);
this.pos += match[0].length;
return match[0];
}
return "";
};
/**
* Skips all text until the given regular expression can be matched. Returns
* the skipped string, which is the entire tail if no match can be made.
*/
Scanner.prototype.scanUntil = function (re) {
var match, pos = this.tail.search(re);
switch (pos) {
case -1:
match = this.tail;
this.pos += this.tail.length;
this.tail = "";
break;
case 0:
match = "";
break;
default:
match = this.tail.substring(0, pos);
this.tail = this.tail.substring(pos);
this.pos += pos;
}
return match;
};
function Context(view, parent) {
this.view = view || {};
this.parent = parent;
this._cache = {};
}
Context.make = function (view) {
return (view instanceof Context) ? view : new Context(view);
};
Context.prototype.push = function (view) {
return new Context(view, this);
};
Context.prototype.lookup = function (name) {
var value = this._cache[name];
if (!value) {
if (name == '.') {
value = this.view;
} else {
var context = this;
while (context) {
if (name.indexOf('.') > 0) {
value = context.view;
var names = name.split('.'), i = 0;
while (value && i < names.length) {
value = value[names[i++]];
}
} else {
value = context.view[name];
}
if (value != null) break;
context = context.parent;
}
}
this._cache[name] = value;
}
if (typeof value === 'function') value = value.call(this.view);
return value;
};
function Writer() {
this.clearCache();
}
Writer.prototype.clearCache = function () {
this._cache = {};
this._partialCache = {};
};
Writer.prototype.compile = function (template, tags) {
var fn = this._cache[template];
if (!fn) {
var tokens = mustache.parse(template, tags);
fn = this._cache[template] = this.compileTokens(tokens, template);
}
return fn;
};
Writer.prototype.compilePartial = function (name, template, tags) {
var fn = this.compile(template, tags);
this._partialCache[name] = fn;
return fn;
};
Writer.prototype.getPartial = function (name) {
if (!(name in this._partialCache) && this._loadPartial) {
this.compilePartial(name, this._loadPartial(name));
}
return this._partialCache[name];
};
Writer.prototype.compileTokens = function (tokens, template) {
var self = this;
return function (view, partials) {
if (partials) {
if (typeof partials === 'function') {
self._loadPartial = partials;
} else {
for (var name in partials) {
self.compilePartial(name, partials[name]);
}
}
}
return renderTokens(tokens, self, Context.make(view), template);
};
};
Writer.prototype.render = function (template, view, partials) {
return this.compile(template)(view, partials);
};
/**
* Low-level function that renders the given `tokens` using the given `writer`
* and `context`. The `template` string is only needed for templates that use
* higher-order sections to extract the portion of the original template that
* was contained in that section.
*/
function renderTokens(tokens, writer, context, template) {
var buffer = '';
var token, tokenValue, value;
for (var i = 0, len = tokens.length; i < len; ++i) {
token = tokens[i];
tokenValue = token[1];
switch (token[0]) {
case '#':
value = context.lookup(tokenValue);
if (typeof value === 'object') {
if (isArray(value)) {
for (var j = 0, jlen = value.length; j < jlen; ++j) {
buffer += renderTokens(token[4], writer, context.push(value[j]), template);
}
} else if (value) {
buffer += renderTokens(token[4], writer, context.push(value), template);
}
} else if (typeof value === 'function') {
var text = template == null ? null : template.slice(token[3], token[5]);
value = value.call(context.view, text, function (template) {
return writer.render(template, context);
});
if (value != null) buffer += value;
} else if (value) {
buffer += renderTokens(token[4], writer, context, template);
}
break;
case '^':
value = context.lookup(tokenValue);
// Use JavaScript's definition of falsy. Include empty arrays.
// See https://github.com/janl/mustache.js/issues/186
if (!value || (isArray(value) && value.length === 0)) {
buffer += renderTokens(token[4], writer, context, template);
}
break;
case '>':
value = writer.getPartial(tokenValue);
if (typeof value === 'function') buffer += value(context);
break;
case '&':
value = context.lookup(tokenValue);
if (value != null) buffer += value;
break;
case 'name':
value = context.lookup(tokenValue);
if (value != null) buffer += mustache.escape(value);
break;
case 'text':
buffer += tokenValue;
break;
}
}
return buffer;
}
/**
* Forms the given array of `tokens` into a nested tree structure where
* tokens that represent a section have two additional items: 1) an array of
* all tokens that appear in that section and 2) the index in the original
* template that represents the end of that section.
*/
function nestTokens(tokens) {
var tree = [];
var collector = tree;
var sections = [];
var token;
for (var i = 0, len = tokens.length; i < len; ++i) {
token = tokens[i];
switch (token[0]) {
case '#':
case '^':
sections.push(token);
collector.push(token);
collector = token[4] = [];
break;
case '/':
var section = sections.pop();
section[5] = token[2];
collector = sections.length > 0 ? sections[sections.length - 1][4] : tree;
break;
default:
collector.push(token);
}
}
return tree;
}
/**
* Combines the values of consecutive text tokens in the given `tokens` array
* to a single token.
*/
function squashTokens(tokens) {
var squashedTokens = [];
var token, lastToken;
for (var i = 0, len = tokens.length; i < len; ++i) {
token = tokens[i];
if (token) {
if (token[0] === 'text' && lastToken && lastToken[0] === 'text') {
lastToken[1] += token[1];
lastToken[3] = token[3];
} else {
lastToken = token;
squashedTokens.push(token);
}
}
}
return squashedTokens;
}
function escapeTags(tags) {
return [
new RegExp(escapeRegExp(tags[0]) + "\\s*"),
new RegExp("\\s*" + escapeRegExp(tags[1]))
];
}
/**
* Breaks up the given `template` string into a tree of token objects. If
* `tags` is given here it must be an array with two string values: the
* opening and closing tags used in the template (e.g. ["<%", "%>"]). Of
* course, the default is to use mustaches (i.e. Mustache.tags).
*/
function parseTemplate(template, tags) {
template = template || '';
tags = tags || mustache.tags;
if (typeof tags === 'string') tags = tags.split(spaceRe);
if (tags.length !== 2) throw new Error('Invalid tags: ' + tags.join(', '));
var tagRes = escapeTags(tags);
var scanner = new Scanner(template);
var sections = []; // Stack to hold section tokens
var tokens = []; // Buffer to hold the tokens
var spaces = []; // Indices of whitespace tokens on the current line
var hasTag = false; // Is there a {{tag}} on the current line?
var nonSpace = false; // Is there a non-space char on the current line?
// Strips all whitespace tokens array for the current line
// if there was a {{#tag}} on it and otherwise only space.
function stripSpace() {
if (hasTag && !nonSpace) {
while (spaces.length) {
delete tokens[spaces.pop()];
}
} else {
spaces = [];
}
hasTag = false;
nonSpace = false;
}
var start, type, value, chr, token;
while (!scanner.eos()) {
start = scanner.pos;
// Match any text between tags.
value = scanner.scanUntil(tagRes[0]);
if (value) {
for (var i = 0, len = value.length; i < len; ++i) {
chr = value.charAt(i);
if (isWhitespace(chr)) {
spaces.push(tokens.length);
} else {
nonSpace = true;
}
tokens.push(['text', chr, start, start + 1]);
start += 1;
// Check for whitespace on the current line.
if (chr == '\n') stripSpace();
}
}
// Match the opening tag.
if (!scanner.scan(tagRes[0])) break;
hasTag = true;
// Get the tag type.
type = scanner.scan(tagRe) || 'name';
scanner.scan(whiteRe);
// Get the tag value.
if (type === '=') {
value = scanner.scanUntil(eqRe);
scanner.scan(eqRe);
scanner.scanUntil(tagRes[1]);
} else if (type === '{') {
value = scanner.scanUntil(new RegExp('\\s*' + escapeRegExp('}' + tags[1])));
scanner.scan(curlyRe);
scanner.scanUntil(tagRes[1]);
type = '&';
} else {
value = scanner.scanUntil(tagRes[1]);
}
// Match the closing tag.
if (!scanner.scan(tagRes[1])) throw new Error('Unclosed tag at ' + scanner.pos);
token = [type, value, start, scanner.pos];
tokens.push(token);
if (type === '#' || type === '^') {
sections.push(token);
} else if (type === '/') {
// Check section nesting.
if (sections.length === 0) throw new Error('Unopened section "' + value + '" at ' + start);
var openSection = sections.pop();
if (openSection[1] !== value) throw new Error('Unclosed section "' + openSection[1] + '" at ' + start);
} else if (type === 'name' || type === '{' || type === '&') {
nonSpace = true;
} else if (type === '=') {
// Set the tags for the next time around.
tags = value.split(spaceRe);
if (tags.length !== 2) throw new Error('Invalid tags at ' + start + ': ' + tags.join(', '));
tagRes = escapeTags(tags);
}
}
// Make sure there are no open sections when we're done.
var openSection = sections.pop();
if (openSection) throw new Error('Unclosed section "' + openSection[1] + '" at ' + scanner.pos);
tokens = squashTokens(tokens);
return nestTokens(tokens);
}
mustache.name = "mustache.js";
mustache.version = "0.7.2";
mustache.tags = ["{{", "}}"];
mustache.Scanner = Scanner;
mustache.Context = Context;
mustache.Writer = Writer;
mustache.parse = parseTemplate;
// Export the escaping function so that the user may override it.
// See https://github.com/janl/mustache.js/issues/244
mustache.escape = escapeHtml;
// All Mustache.* functions use this writer.
var defaultWriter = new Writer();
/**
* Clears all cached templates and partials in the default writer.
*/
mustache.clearCache = function () {
return defaultWriter.clearCache();
};
/**
* Compiles the given `template` to a reusable function using the default
* writer.
*/
mustache.compile = function (template, tags) {
return defaultWriter.compile(template, tags);
};
/**
* Compiles the partial with the given `name` and `template` to a reusable
* function using the default writer.
*/
mustache.compilePartial = function (name, template, tags) {
return defaultWriter.compilePartial(name, template, tags);
};
/**
* Compiles the given array of tokens (the output of a parse) to a reusable
* function using the default writer.
*/
mustache.compileTokens = function (tokens, template) {
return defaultWriter.compileTokens(tokens, template);
};
/**
* Renders the `template` with the given `view` and `partials` using the
* default writer.
*/
mustache.render = function (template, view, partials) {
return defaultWriter.render(template, view, partials);
};
// This is here for backwards compatibility with 0.4.x.
mustache.to_html = function (template, view, partials, send) {
var result = mustache.render(template, view, partials);
if (typeof send === "function") {
send(result);
} else {
return result;
}
};
}));

8
js/sammy-latest.min.js vendored Normal file

File diff suppressed because one or more lines are too long

106
js/sammy.flash.js Normal file
View file

@ -0,0 +1,106 @@
(function (factory) {
if (typeof define === 'function' && define.amd) {
define(['jquery', 'sammy'], factory);
} else {
(window.Sammy = window.Sammy || {}).Flash = factory(window.jQuery, window.Sammy);
}
}(function ($, Sammy) {
Sammy.FlashHash = function() {
this.now = {};
};
$.extend(Sammy.FlashHash.prototype , {
// @return [String] this Flash, rendered as an <ul>.
toHTML: function() {
var result = this._renderUL();
this.clear();
return result;
},
clear: function() {
this._clearHash(this);
this._clearHash(this.now);
},
// Callback on redirect.
// @api private
_onRedirect: function() {
this._clearHash(this.now);
},
// clear out all flash keys
// @api private
_clearHash: function(hash) {
var key;
for (key in hash) {
if (key !== 'now' && hash.hasOwnProperty(key)) {
delete hash[key];
}
}
},
_renderUL: function() {
return '<ul class="flash">' +
this._renderLIs(this) +
this._renderLIs(this.now) +
'</ul>';
},
_renderLIs: function(hash) {
var result = '',
key;
for (key in hash) {
if (hash[key] && key !== 'now' && hash.hasOwnProperty(key)) {
result = result + '<li class="' + key + '">' + hash[key] + '</li>';
}
}
Sammy.log('rendered flash: ' + result);
return result;
}
});
// Sammy.Flash is a plugin for storing and sending status messages to the client. It's API and use
// is similar to Ruby on Rails' `flash` explained here:
// [http://apidock.com/rails/ActionController/Flash](http://apidock.com/rails/ActionController/Flash)
Sammy.Flash = function(app) {
app.flash = new Sammy.FlashHash();
// *Helper* flash(key, value) get or set a flash message that will
// be erased on the next render (but not on redirect).
//
// @param [String] key, the Flash key
// @param [String] value, the new value; optional
// @return [Sammy.FlashHash, String, null] if a key was given, the value for that key; else, the Flash
app.helper('flash', function(key, value) {
if (arguments.length === 0) {
return this.app.flash;
} else if (arguments.length === 2) {
this.app.flash[key] = value;
}
return this.app.flash[key];
});
// *Helper* flashNow(key, value) get or set a flash message that
// will be erased on the next render or redirect.
//
// @param [String] key, the Flash key
// @param [String] value, the new value; optional
// @return [String, null] the value for the given key
app.helper('flashNow', function(key, value) {
if (arguments.length === 0) {
return this.app.flash.now;
}else if (arguments.length === 2) {
this.app.flash.now[key] = value;
}
return this.app.flash.now[key];
});
app.bind('redirect', function() {
this.app.flash._onRedirect();
});
};
return Sammy.Flash;
}));

2135
js/sammy.js Normal file

File diff suppressed because it is too large Load diff

368
js/sammy.json.js Normal file
View file

@ -0,0 +1,368 @@
(function (factory) {
if (typeof define === 'function' && define.amd) {
define(['jquery', 'sammy'], factory);
} else {
(window.Sammy = window.Sammy || {}).JSON = factory(window.jQuery, window.Sammy);
}
}(function ($, Sammy) {
// json2.js - only included if native json does not exist
// http://www.json.org/js.html
if (!window.JSON) {
window.JSON = {};
}
(function () {
function f(n) {
// Format integers to have at least two digits.
return n < 10 ? '0' + n : n;
}
if (typeof Date.prototype.toJSON !== 'function') {
Date.prototype.toJSON = function (key) {
return this.getUTCFullYear() + '-' +
f(this.getUTCMonth() + 1) + '-' +
f(this.getUTCDate()) + 'T' +
f(this.getUTCHours()) + ':' +
f(this.getUTCMinutes()) + ':' +
f(this.getUTCSeconds()) + 'Z';
};
String.prototype.toJSON =
Number.prototype.toJSON =
Boolean.prototype.toJSON = function (key) {
return this.valueOf();
};
}
var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
gap,
indent,
meta = { // table of character substitutions
'\b': '\\b',
'\t': '\\t',
'\n': '\\n',
'\f': '\\f',
'\r': '\\r',
'"' : '\\"',
'\\': '\\\\'
},
rep;
function quote(string) {
// If the string contains no control characters, no quote characters, and no
// backslash characters, then we can safely slap some quotes around it.
// Otherwise we must also replace the offending characters with safe escape
// sequences.
escapable.lastIndex = 0;
return escapable.test(string) ?
'"' + string.replace(escapable, function (a) {
var c = meta[a];
return typeof c === 'string' ? c :
'\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
}) + '"' :
'"' + string + '"';
}
function str(key, holder) {
// Produce a string from holder[key].
var i, // The loop counter.
k, // The member key.
v, // The member value.
length,
mind = gap,
partial,
value = holder[key];
// If the value has a toJSON method, call it to obtain a replacement value.
if (value && typeof value === 'object' &&
typeof value.toJSON === 'function') {
value = value.toJSON(key);
}
// If we were called with a replacer function, then call the replacer to
// obtain a replacement value.
if (typeof rep === 'function') {
value = rep.call(holder, key, value);
}
// What happens next depends on the value's type.
switch (typeof value) {
case 'string':
return quote(value);
case 'number':
// JSON numbers must be finite. Encode non-finite numbers as null.
return isFinite(value) ? String(value) : 'null';
case 'boolean':
case 'null':
// If the value is a boolean or null, convert it to a string. Note:
// typeof null does not produce 'null'. The case is included here in
// the remote chance that this gets fixed someday.
return String(value);
// If the type is 'object', we might be dealing with an object or an array or
// null.
case 'object':
// Due to a specification blunder in ECMAScript, typeof null is 'object',
// so watch out for that case.
if (!value) {
return 'null';
}
// Make an array to hold the partial results of stringifying this object value.
gap += indent;
partial = [];
// Is the value an array?
if (Object.prototype.toString.apply(value) === '[object Array]') {
// The value is an array. Stringify every element. Use null as a placeholder
// for non-JSON values.
length = value.length;
for (i = 0; i < length; i += 1) {
partial[i] = str(i, value) || 'null';
}
// Join all of the elements together, separated with commas, and wrap them in
// brackets.
v = partial.length === 0 ? '[]' :
gap ? '[\n' + gap +
partial.join(',\n' + gap) + '\n' +
mind + ']' :
'[' + partial.join(',') + ']';
gap = mind;
return v;
}
// If the replacer is an array, use it to select the members to be stringified.
if (rep && typeof rep === 'object') {
length = rep.length;
for (i = 0; i < length; i += 1) {
k = rep[i];
if (typeof k === 'string') {
v = str(k, value);
if (v) {
partial.push(quote(k) + (gap ? ': ' : ':') + v);
}
}
}
} else {
// Otherwise, iterate through all of the keys in the object.
for (k in value) {
if (Object.hasOwnProperty.call(value, k)) {
v = str(k, value);
if (v) {
partial.push(quote(k) + (gap ? ': ' : ':') + v);
}
}
}
}
// Join all of the member texts together, separated with commas,
// and wrap them in braces.
v = partial.length === 0 ? '{}' :
gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
mind + '}' : '{' + partial.join(',') + '}';
gap = mind;
return v;
}
}
// If the JSON object does not yet have a stringify method, give it one.
if (typeof JSON.stringify !== 'function') {
JSON.stringify = function (value, replacer, space) {
// The stringify method takes a value and an optional replacer, and an optional
// space parameter, and returns a JSON text. The replacer can be a function
// that can replace values, or an array of strings that will select the keys.
// A default replacer method can be provided. Use of the space parameter can
// produce text that is more easily readable.
var i;
gap = '';
indent = '';
// If the space parameter is a number, make an indent string containing that
// many spaces.
if (typeof space === 'number') {
for (i = 0; i < space; i += 1) {
indent += ' ';
}
// If the space parameter is a string, it will be used as the indent string.
} else if (typeof space === 'string') {
indent = space;
}
// If there is a replacer, it must be a function or an array.
// Otherwise, throw an error.
rep = replacer;
if (replacer && typeof replacer !== 'function' &&
(typeof replacer !== 'object' ||
typeof replacer.length !== 'number')) {
throw new Error('JSON.stringify');
}
// Make a fake root object containing our value under the key of ''.
// Return the result of stringifying the value.
return str('', {'': value});
};
}
// If the JSON object does not yet have a parse method, give it one.
if (typeof JSON.parse !== 'function') {
JSON.parse = function (text, reviver) {
// The parse method takes a text and an optional reviver function, and returns
// a JavaScript value if the text is a valid JSON text.
var j;
function walk(holder, key) {
// The walk method is used to recursively walk the resulting structure so
// that modifications can be made.
var k, v, value = holder[key];
if (value && typeof value === 'object') {
for (k in value) {
if (Object.hasOwnProperty.call(value, k)) {
v = walk(value, k);
if (v !== undefined) {
value[k] = v;
} else {
delete value[k];
}
}
}
}
return reviver.call(holder, key, value);
}
// Parsing happens in four stages. In the first stage, we replace certain
// Unicode characters with escape sequences. JavaScript handles many characters
// incorrectly, either silently deleting them, or treating them as line endings.
cx.lastIndex = 0;
if (cx.test(text)) {
text = text.replace(cx, function (a) {
return '\\u' +
('0000' + a.charCodeAt(0).toString(16)).slice(-4);
});
}
// In the second stage, we run the text against regular expressions that look
// for non-JSON patterns. We are especially concerned with '()' and 'new'
// because they can cause invocation, and '=' because it can cause mutation.
// But just to be safe, we want to reject all unexpected forms.
// We split the second stage into 4 regexp operations in order to work around
// crippling inefficiencies in IE's and Safari's regexp engines. First we
// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
// replace all simple value tokens with ']' characters. Third, we delete all
// open brackets that follow a colon or comma or that begin the text. Finally,
// we look to see that the remaining characters are only whitespace or ']' or
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
if (/^[\],:{}\s]*$/.
test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
// In the third stage we use the eval function to compile the text into a
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
// in JavaScript: it can begin a block or an object literal. We wrap the text
// in parens to eliminate the ambiguity.
j = eval('(' + text + ')');
// In the optional fourth stage, we recursively walk the new structure, passing
// each name/value pair to a reviver function for possible transformation.
return typeof reviver === 'function' ?
walk({'': j}, '') : j;
}
// If the text is not JSON parseable, then a SyntaxError is thrown.
throw new SyntaxError('JSON.parse');
};
}
}());
// Sammy.JSON is a simple wrapper around Douglas Crockford's ever-useful json2.js
// (http://www.json.org/js.html]) Sammy.JSON includes the top level JSON object if
// it doesn't already exist (a.k.a. does not override the native implementation that
// some browsers include). It also adds a <tt>json()</tt> helper to a Sammy app when
// included.
Sammy.JSON = function(app) {
app.helpers({
// json is a polymorphic function that translates objects aback and forth
// from JSON to JS. If given a string, it will parse into JS, if given a JS
// object it will stringify into JSON.
//
// ### Example
//
// var app = $.sammy(function() {
// this.use(Sammy.JSON);
//
// this.get('#/', function() {
// this.json({user_id: 123}); //=> "{\"user_id\":\"123\"}"
// this.json("{\"user_id\":\"123\"}"); //=> [object Object]
// this.json("{\"user_id\":\"123\"}").user_id; //=> "123"
// });
// })
//
//
json: function(object) {
if (typeof object == 'string') {
return JSON.parse(object);
} else {
return JSON.stringify(object);
}
}
});
}
return Sammy.JSON;
}));

126
js/sammy.mustache.js Normal file
View file

@ -0,0 +1,126 @@
(function (factory) {
if (typeof define === 'function' && define.amd) {
define(['jquery', 'sammy', 'mustache'], factory);
} else {
(window.Sammy = window.Sammy || {}).Mustache = factory(window.jQuery, window.Sammy, window.Mustache);
}
}(function ($, Sammy, Mustache) {
// <tt>Sammy.Mustache</tt> provides a quick way of using mustache style templates in your app.
// The plugin wraps the awesome mustache.js lib created and maintained by Jan Lehnardt
// at http://github.com/janl/mustache.js
//
// Note: As of Sammy 0.7 the Mustache lib is not included in the templates source. Please download
// mustache.js and include it before Sammy.Mustache.
//
// Mustache is a clever templating system that relys on double brackets {{}} for interpolation.
// For full details on syntax check out the original Ruby implementation created by Chris Wanstrath at
// http://github.com/defunkt/mustache
//
// By default using Sammy.Mustache in your app adds the <tt>mustache()</tt> method to the EventContext
// prototype. However, just like <tt>Sammy.Template</tt> you can change the default name of the method
// by passing a second argument (e.g. you could use the ms() as the method alias so that all the template
// files could be in the form file.ms instead of file.mustache)
//
// ### Example #1
//
// The template (mytemplate.ms):
//
// <h1>{{title}}<h1>
//
// Hey, {{name}}! Welcome to Mustache!
//
// The app:
//
// var app = $.sammy(function() {
// // include the plugin and alias mustache() to ms()
// this.use('Mustache', 'ms');
//
// this.get('#/hello/:name', function() {
// // set local vars
// this.title = 'Hello!'
// this.name = this.params.name;
// // render the template and pass it through mustache
// this.partial('mytemplate.ms');
// });
// });
//
// $(function() {
// app.run()
// });
//
// If I go to #/hello/AQ in the browser, Sammy will render this to the <tt>body</tt>:
//
// <h1>Hello!</h1>
//
// Hey, AQ! Welcome to Mustache!
//
//
// ### Example #2 - Mustache partials
//
// The template (mytemplate.ms)
//
// Hey, {{name}}! {{>hello_friend}}
//
//
// The partial (mypartial.ms)
//
// Say hello to your friend {{friend}}!
//
// The app:
//
// var app = $.sammy(function() {
// // include the plugin and alias mustache() to ms()
// this.use('Mustache', 'ms');
//
// this.get('#/hello/:name/to/:friend', function(context) {
// // fetch mustache-partial first
// this.load('mypartial.ms')
// .then(function(partial) {
// // set local vars
// context.partials = {hello_friend: partial};
// context.name = context.params.name;
// context.friend = context.params.friend;
//
// // render the template and pass it through mustache
// context.partial('mytemplate.ms');
// });
// });
// });
//
// $(function() {
// app.run()
// });
//
// If I go to #/hello/AQ/to/dP in the browser, Sammy will render this to the <tt>body</tt>:
//
// Hey, AQ! Say hello to your friend dP!
//
// Note: You need to include the mustache.js file before this plugin.
//
Sammy.Mustache = function(app, method_alias) {
// *Helper* Uses Mustache.js to parse a template and interpolate and work with the passed data
//
// ### Arguments
//
// * `template` A String template. {{}} Tags are evaluated and interpolated by Mustache.js
// * `data` An Object containing the replacement values for the template.
// data is extended with the <tt>EventContext</tt> allowing you to call its methods within the template.
// * `partials` An Object containing one or more partials (String templates
// that are called from the main template).
//
var mustache = function(template, data, partials) {
data = $.extend({}, this, data);
partials = $.extend({}, data.partials, partials);
return Mustache.to_html(template, data, partials);
};
// set the default method name/extension
if (!method_alias) { method_alias = 'mustache'; }
app.helper(method_alias, mustache);
};
return Sammy.Mustache;
}));

583
js/sammy.storage.js Normal file
View file

@ -0,0 +1,583 @@
(function (factory) {
if (typeof define === 'function' && define.amd) {
define(['jquery', 'sammy'], factory);
} else {
(window.Sammy = window.Sammy || {}).Storage = factory(window.jQuery, window.Sammy);
}
}(function ($, Sammy) {
// Sammy.Store is an abstract adapter class that wraps the multitude of in
// browser data storage into a single common set of methods for storing and
// retreiving data. The JSON library is used (through the inclusion of the
// Sammy.JSON) plugin, to automatically convert objects back and forth from
// stored strings.
//
// Sammy.Store can be used directly, but within a Sammy.Application it is much
// easier to use the `Sammy.Storage` plugin and its helper methods.
//
// Sammy.Store also supports the KVO pattern, by firing DOM/jQuery Events when
// a key is set.
//
// ### Example
//
// // create a new store named 'mystore', tied to the #main element, using HTML5 localStorage
// // Note: localStorage only works on browsers that support it
// var store = new Sammy.Store({name: 'mystore', element: '#element', type: 'local'});
// store.set('foo', 'bar');
// store.get('foo'); //=> 'bar'
// store.set('json', {obj: 'this is an obj'});
// store.get('json'); //=> {obj: 'this is an obj'}
// store.keys(); //=> ['foo','json']
// store.clear('foo');
// store.keys(); //=> ['json']
// store.clearAll();
// store.keys(); //=> []
//
// ### Arguments
//
// The constructor takes a single argument which is a Object containing these possible options.
//
// * `name` The name/namespace of this store. Stores are unique by name/type. (default 'store')
// * `element` A selector for the element that the store is bound to. (default 'body')
// * `type` The type of storage/proxy to use (default 'memory')
//
// Extra options are passed to the storage constructor.
// Sammy.Store supports the following methods of storage:
//
// * `memory` Basic object storage
// * `data` jQuery.data DOM Storage
// * `cookie` Access to document.cookie. Limited to 2K
// * `local` HTML5 DOM localStorage, browswer support is currently limited.
// * `session` HTML5 DOM sessionStorage, browswer support is currently limited.
//
Sammy.Store = function(options) {
var store = this;
this.options = options || {};
this.name = this.options.name || 'store';
this.element = this.options.element || 'body';
this.$element = $(this.element);
if ($.isArray(this.options.type)) {
$.each(this.options.type, function(i, type) {
if (Sammy.Store.isAvailable(type)) {
store.type = type;
return false;
}
});
} else {
this.type = this.options.type || 'memory';
}
this.meta_key = this.options.meta_key || '__keys__';
this.storage = new Sammy.Store[Sammy.Store.stores[this.type]](this.name, this.element, this.options);
};
Sammy.Store.stores = {
'memory': 'Memory',
'data': 'Data',
'local': 'LocalStorage',
'session': 'SessionStorage',
'cookie': 'Cookie'
};
$.extend(Sammy.Store.prototype, {
// Checks for the availability of the current storage type in the current browser/config.
isAvailable: function() {
if ($.isFunction(this.storage.isAvailable)) {
return this.storage.isAvailable();
} else {
return true;
}
},
// Checks for the existance of `key` in the current store. Returns a boolean.
exists: function(key) {
return this.storage.exists(key);
},
// Sets the value of `key` with `value`. If `value` is an
// object, it is turned to and stored as a string with `JSON.stringify`.
// It also tries to conform to the KVO pattern triggering jQuery events on the
// element that the store is bound to.
//
// ### Example
//
// var store = new Sammy.Store({name: 'kvo'});
// $('body').bind('set-kvo-foo', function(e, data) {
// Sammy.log(data.key + ' changed to ' + data.value);
// });
// store.set('foo', 'bar'); // logged: foo changed to bar
//
set: function(key, value) {
var string_value = (typeof value == 'string') ? value : JSON.stringify(value);
key = key.toString();
this.storage.set(key, string_value);
if (key != this.meta_key) {
this._addKey(key);
this.$element.trigger('set-' + this.name, {key: key, value: value});
this.$element.trigger('set-' + this.name + '-' + key, {key: key, value: value});
}
// always return the original value
return value;
},
// Returns the set value at `key`, parsing with `JSON.parse` and
// turning into an object if possible
get: function(key) {
var value = this.storage.get(key);
if (typeof value == 'undefined' || value == null || value == '') {
return value;
}
try {
return JSON.parse(value);
} catch(e) {
return value;
}
},
// Removes the value at `key` from the current store
clear: function(key) {
this._removeKey(key);
return this.storage.clear(key);
},
// Clears all the values for the current store.
clearAll: function() {
var self = this;
this.each(function(key, value) {
self.clear(key);
});
},
// Returns the all the keys set for the current store as an array.
// Internally Sammy.Store keeps this array in a 'meta_key' for easy access.
keys: function() {
return this.get(this.meta_key) || [];
},
// Iterates over each key value pair passing them to the `callback` function
//
// ### Example
//
// store.each(function(key, value) {
// Sammy.log('key', key, 'value', value);
// });
//
each: function(callback) {
var i = 0,
keys = this.keys(),
returned;
for (i; i < keys.length; i++) {
returned = callback(keys[i], this.get(keys[i]));
if (returned === false) { return false; }
}
},
// Filters the store by a filter function that takes a key value.
// Returns an array of arrays where the first element of each array
// is the key and the second is the value of that key.
//
// ### Example
//
// var store = new Sammy.Store;
// store.set('one', 'two');
// store.set('two', 'three');
// store.set('1', 'two');
// var returned = store.filter(function(key, value) {
// // only return
// return value === 'two';
// });
// // returned => [['one', 'two'], ['1', 'two']];
//
filter: function(callback) {
var found = [];
this.each(function(key, value) {
if (callback(key, value)) {
found.push([key, value]);
}
return true;
});
return found;
},
// Works exactly like filter except only returns the first matching key
// value pair instead of all of them
first: function(callback) {
var found = false;
this.each(function(key, value) {
if (callback(key, value)) {
found = [key, value];
return false;
}
});
return found;
},
// Returns the value at `key` if set, otherwise, runs the callback
// and sets the value to the value returned in the callback.
//
// ### Example
//
// var store = new Sammy.Store;
// store.exists('foo'); //=> false
// store.fetch('foo', function() {
// return 'bar!';
// }); //=> 'bar!'
// store.get('foo') //=> 'bar!'
// store.fetch('foo', function() {
// return 'baz!';
// }); //=> 'bar!
//
fetch: function(key, callback) {
if (!this.exists(key)) {
return this.set(key, callback.apply(this));
} else {
return this.get(key);
}
},
// loads the response of a request to `path` into `key`.
//
// ### Example
//
// In /mytemplate.tpl:
//
// My Template
//
// In app.js:
//
// var store = new Sammy.Store;
// store.load('mytemplate', '/mytemplate.tpl', function() {
// s.get('mytemplate') //=> My Template
// });
//
load: function(key, path, callback) {
var s = this;
$.get(path, function(response) {
s.set(key, response);
if (callback) { callback.apply(this, [response]); }
});
},
_addKey: function(key) {
var keys = this.keys();
if ($.inArray(key, keys) == -1) { keys.push(key); }
this.set(this.meta_key, keys);
},
_removeKey: function(key) {
var keys = this.keys();
var index = $.inArray(key, keys);
if (index != -1) { keys.splice(index, 1); }
this.set(this.meta_key, keys);
}
});
// Tests if the type of storage is available/works in the current browser/config.
// Especially useful for testing the availability of the awesome, but not widely
// supported HTML5 DOM storage
Sammy.Store.isAvailable = function(type) {
try {
return Sammy.Store[Sammy.Store.stores[type]].prototype.isAvailable();
} catch(e) {
return false;
}
};
// Memory ('memory') is the basic/default store. It stores data in a global
// JS object. Data is lost on refresh.
Sammy.Store.Memory = function(name, element) {
this.name = name;
this.element = element;
this.namespace = [this.element, this.name].join('.');
Sammy.Store.Memory.store = Sammy.Store.Memory.store || {};
Sammy.Store.Memory.store[this.namespace] = Sammy.Store.Memory.store[this.namespace] || {};
this.store = Sammy.Store.Memory.store[this.namespace];
};
$.extend(Sammy.Store.Memory.prototype, {
isAvailable: function() { return true; },
exists: function(key) {
return (typeof this.store[key] != "undefined");
},
set: function(key, value) {
return this.store[key] = value;
},
get: function(key) {
return this.store[key];
},
clear: function(key) {
delete this.store[key];
}
});
// Data ('data') stores objects using the jQuery.data() methods. This has the advantadge
// of scoping the data to the specific element. Like the 'memory' store its data
// will only last for the length of the current request (data is lost on refresh/etc).
Sammy.Store.Data = function(name, element) {
this.name = name;
this.element = element;
this.$element = $(element);
};
$.extend(Sammy.Store.Data.prototype, {
isAvailable: function() { return true; },
exists: function(key) {
return !!this.$element.data(this._key(key));
},
set: function(key, value) {
return this.$element.data(this._key(key), value);
},
get: function(key) {
return this.$element.data(this._key(key));
},
clear: function(key) {
this.$element.removeData(this._key(key));
},
_key: function(key) {
return ['store', this.name, key].join('.');
}
});
// LocalStorage ('local') makes use of HTML5 DOM Storage, and the window.localStorage
// object. The great advantage of this method is that data will persist beyond
// the current request. It can be considered a pretty awesome replacement for
// cookies accessed via JS. The great disadvantage, though, is its only available
// on the latest and greatest browsers.
//
// For more info on DOM Storage:
// https://developer.mozilla.org/en/DOM/Storage
// http://www.w3.org/TR/2009/WD-webstorage-20091222/
//
Sammy.Store.LocalStorage = function(name, element) {
this.name = name;
this.element = element;
};
$.extend(Sammy.Store.LocalStorage.prototype, {
isAvailable: function() {
return ('localStorage' in window) && (window.location.protocol != 'file:');
},
exists: function(key) {
return (this.get(key) != null);
},
set: function(key, value) {
return window.localStorage.setItem(this._key(key), value);
},
get: function(key) {
return window.localStorage.getItem(this._key(key));
},
clear: function(key) {
window.localStorage.removeItem(this._key(key));;
},
_key: function(key) {
return ['store', this.element, this.name, key].join('.');
}
});
// .SessionStorage ('session') is similar to LocalStorage (part of the same API)
// and shares similar browser support/availability. The difference is that
// SessionStorage is only persistant through the current 'session' which is defined
// as the length that the current window is open. This means that data will survive
// refreshes but not close/open or multiple windows/tabs. For more info, check out
// the `LocalStorage` documentation and links.
Sammy.Store.SessionStorage = function(name, element) {
this.name = name;
this.element = element;
};
$.extend(Sammy.Store.SessionStorage.prototype, {
isAvailable: function() {
return ('sessionStorage' in window) &&
(window.location.protocol != 'file:') &&
($.isFunction(window.sessionStorage.setItem));
},
exists: function(key) {
return (this.get(key) != null);
},
set: function(key, value) {
return window.sessionStorage.setItem(this._key(key), value);
},
get: function(key) {
var value = window.sessionStorage.getItem(this._key(key));
if (value && typeof value.value != "undefined") { value = value.value }
return value;
},
clear: function(key) {
window.sessionStorage.removeItem(this._key(key));;
},
_key: function(key) {
return ['store', this.element, this.name, key].join('.');
}
});
// .Cookie ('cookie') storage uses browser cookies to store data. JavaScript
// has access to a single document.cookie variable, which is limited to 2Kb in
// size. Cookies are also considered 'unsecure' as the data can be read easily
// by other sites/JS. Cookies do have the advantage, though, of being widely
// supported and persistent through refresh and close/open. Where available,
// HTML5 DOM Storage like LocalStorage and SessionStorage should be used.
//
// .Cookie can also take additional options:
//
// * `expires_in` Number of seconds to keep the cookie alive (default 2 weeks).
// * `path` The path to activate the current cookie for (default '/').
//
// For more information about document.cookie, check out the pre-eminint article
// by ppk: http://www.quirksmode.org/js/cookies.html
//
Sammy.Store.Cookie = function(name, element, options) {
this.name = name;
this.element = element;
this.options = options || {};
this.path = this.options.path || '/';
// set the expires in seconds or default 14 days
this.expires_in = this.options.expires_in || (14 * 24 * 60 * 60);
};
$.extend(Sammy.Store.Cookie.prototype, {
isAvailable: function() {
return ('cookie' in document) && (window.location.protocol != 'file:');
},
exists: function(key) {
return (this.get(key) != null);
},
set: function(key, value) {
return this._setCookie(key, value);
},
get: function(key) {
return this._getCookie(key);
},
clear: function(key) {
this._setCookie(key, "", -1);
},
_key: function(key) {
return ['store', this.element, this.name, key].join('.');
},
_getCookie: function(key) {
var escaped = this._key(key).replace(/(\.|\*|\(|\)|\[|\])/g, '\\$1');
var match = document.cookie.match("(^|;\\s)" + escaped + "=([^;]*)(;|$)");
return (match ? match[2] : null);
},
_setCookie: function(key, value, expires) {
if (!expires) { expires = (this.expires_in * 1000) }
var date = new Date();
date.setTime(date.getTime() + expires);
var set_cookie = [
this._key(key), "=", value,
"; expires=", date.toGMTString(),
"; path=", this.path
].join('');
document.cookie = set_cookie;
}
});
// Sammy.Storage is a plugin that provides shortcuts for creating and using
// Sammy.Store objects. Once included it provides the `store()` app level
// and helper methods. Depends on Sammy.JSON (or json2.js).
Sammy.Storage = function(app) {
this.use(Sammy.JSON);
this.stores = this.stores || {};
// `store()` creates and looks up existing `Sammy.Store` objects
// for the current application. The first time used for a given `'name'`
// initializes a `Sammy.Store` and also creates a helper under the store's
// name.
//
// ### Example
//
// var app = $.sammy(function() {
// this.use(Sammy.Storage);
//
// // initializes the store on app creation.
// this.store('mystore', {type: 'cookie'});
//
// this.get('#/', function() {
// // returns the Sammy.Store object
// this.store('mystore');
// // sets 'foo' to 'bar' using the shortcut/helper
// // equivilent to this.store('mystore').set('foo', 'bar');
// this.mystore('foo', 'bar');
// // returns 'bar'
// // equivilent to this.store('mystore').get('foo');
// this.mystore('foo');
// // returns 'baz!'
// // equivilent to:
// // this.store('mystore').fetch('foo!', function() {
// // return 'baz!';
// // })
// this.mystore('foo!', function() {
// return 'baz!';
// });
//
// this.clearMystore();
// // equivilent to:
// // this.store('mystore').clearAll()
// });
//
// });
//
// ### Arguments
//
// * `name` The name of the store and helper. the name must be unique per application.
// * `options` A JS object of options that can be passed to the Store constuctor on initialization.
//
this.store = function(name, options) {
// if the store has not been initialized
if (typeof this.stores[name] == 'undefined') {
// create initialize the store
var clear_method_name = "clear" + name.substr(0,1).toUpperCase() + name.substr(1);
this.stores[name] = new Sammy.Store($.extend({
name: name,
element: this.element_selector
}, options || {}));
// app.name()
this[name] = function(key, value) {
if (typeof value == 'undefined') {
return this.stores[name].get(key);
} else if ($.isFunction(value)) {
return this.stores[name].fetch(key, value);
} else {
return this.stores[name].set(key, value)
}
};
// app.clearName();
this[clear_method_name] = function() {
return this.stores[name].clearAll();
}
// context.name()
this.helper(name, function() {
return this.app[name].apply(this.app, arguments);
});
// context.clearName();
this.helper(clear_method_name, function() {
return this.app[clear_method_name]();
});
}
return this.stores[name];
};
this.helpers({
store: function() {
return this.app.store.apply(this.app, arguments);
}
});
};
// Sammy.Session is an additional plugin for creating a common 'session' store
// for the given app. It is a very simple wrapper around `Sammy.Storage`
// that provides a simple fallback mechanism for trying to provide the best
// possible storage type for the session. This means, `LocalStorage`
// if available, otherwise `Cookie`, otherwise `Memory`.
// It provides the `session()` helper through `Sammy.Storage#store()`.
//
// See the `Sammy.Storage` plugin for full documentation.
//
Sammy.Session = function(app, options) {
this.use(Sammy.Storage);
// check for local storage, then cookie storage, then just use memory
this.store('session', $.extend({type: ['local', 'cookie', 'memory']}, options));
};
// Sammy.Cache provides helpers for caching data within the lifecycle of a
// Sammy app. The plugin provides two main methods on `Sammy.Application`,
// `cache` and `clearCache`. Each app has its own cache store so that
// you dont have to worry about collisions. As of 0.5 the original Sammy.Cache module
// has been deprecated in favor of this one based on Sammy.Storage. The exposed
// API is almost identical, but Sammy.Storage provides additional backends including
// HTML5 Storage. `Sammy.Cache` will try to use these backends when available
// (in this order) `LocalStorage`, `SessionStorage`, and `Memory`
Sammy.Cache = function(app, options) {
this.use(Sammy.Storage);
// set cache_partials to true
this.cache_partials = true;
// check for local storage, then session storage, then just use memory
this.store('cache', $.extend({type: ['local', 'session', 'memory']}, options));
};
return Sammy.Storage;
}));

15
login.ms Normal file
View file

@ -0,0 +1,15 @@
<form action="/index.html#/login" id="form" method="post">
<p>
<label>Url:</label>
<input type="text" name="url" />
</p>
<p>
<label>Login:</label>
<input type="text" name="user" />
</p>
<p>
<label>Password:</label>
<input type="password" name="password" />
</p>
<input id="submit" type="submit" value="Gogogo" />
</form>

5
user_info.ms Normal file
View file

@ -0,0 +1,5 @@
<ul>
<li>Fullname: {{Fullname}}</li>
<li>Username: {{Username}}</li>
<li>Mail: {{Mail}}</li>
</ul>