├── .editorconfig
├── .gitignore
├── README.md
├── bower.json
├── img
├── by-moot.png
├── company.png
├── riotjslogo.png
├── riotjslogo@2x.png
├── tipiirai.jpg
└── wallpaper.jpg
├── index.html
├── make.js
├── package.json
├── src
├── api
│ ├── admin.js
│ ├── backend.js
│ ├── promise.js
│ ├── spa.js
│ ├── test-data.js
│ └── user.js
├── ext
│ ├── graph.html
│ └── graph.js
└── ui
│ ├── README.md
│ ├── customer.js
│ ├── customers.js
│ ├── login.js
│ ├── search.js
│ ├── stats.js
│ ├── user.js
│ ├── util.js
│ └── view-switch.js
├── style
├── css3.styl
├── global.styl
├── index.styl
├── login.styl
├── media.styl
├── search.styl
├── vars.styl
└── view.styl
└── test
├── index.js
└── test.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent coding styles
2 | # between different editors and IDEs.
3 | # http://editorconfig.org
4 |
5 | root = true
6 |
7 | [*]
8 | charset = utf-8
9 | end_of_line = lf
10 | indent_size = 2
11 | indent_style = space
12 | insert_final_newline = true
13 | trim_trailing_whitespace = true
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .*
2 | !.editorconfig
3 | !.jshintrc
4 | !.gitignore
5 | !.gitmodules
6 | !.travis.yml
7 | *~
8 |
9 | dist
10 | node_modules
11 | bower_components
12 | Thumbs.db
13 | ehthumbs.db
14 | Desktop.ini
15 | $RECYCLE.BIN/
16 | .DS_Store
17 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | 
3 |
4 | ## Riot.js demo application
5 |
6 | This is a demo single-page demo application made with Riot.js. It's a generic skeleton for an administration panel that shows the core concepts of modular client side development.
7 |
8 | ### Features
9 | - actually something useful, just fork it and extend it to your needs
10 | - plain and minimal UI
11 | - authentication and session management
12 | - small: ~10kb minified, including Riot (4kb gzipped)
13 | - optional caching of server side requests
14 |
15 |
16 | ### Modular architecture
17 | - one global variable `admin` to build modules
18 | - modules can be included in no specific order (*.js)
19 | - modules can be renamed/removed/modified on without breaking the app
20 | - backend agnostic
21 | - easy to maintain and extend, good for multiple developers
22 |
23 |
24 | ### Extendable
25 | - Real API: try for example `admin().load("customers")` on console
26 | - Promise based server calls for more fluent error handling
27 | - Use app name to extend instead of a framework name: `admin(callback)`
28 |
29 |
30 | ### Planned
31 | - real backend (firebase, hosted server, to be decided...)
32 | - realtime channel
33 | - tests for both UI and API layer
34 | - documented API, including events
35 |
36 |
37 | ## Installation
38 |
39 | Hit following commands to run the administration panel on the console
40 |
41 | ``` sh
42 | bower install
43 | npm install
44 | ./make.js gen
45 | open index.html
46 | ./make.js watch
47 | ```
48 |
49 | You should be able to modify JS and Stylus files and the concatenation and pre-compilation is automatically taken care of. Check make.js for more other targets than `watch`.
50 |
51 |
52 | [Live version](https://muut.com/riotjs/demo/) •
53 | [Riot website](https://muut.com/riotjs/) •
54 | [Building modular applications with Riot.js](https://muut.com/riotjs/docs/) •
55 | [Original Riot blog post](https://muut.com/blog/technology/riotjs-the-1kb-mvp-framework.html)
56 |
57 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "riotjs-admin",
3 | "version": "0.0.2",
4 | "homepage": "https://muut.com/riotjs/demo/",
5 | "authors": [ "Tero Piirainen" ],
6 | "description": "Administration panel – A Riot.js demo (The 1kb MVP library)",
7 | "main": "index.html",
8 | "keywords": [ "administration", "panel", "MVP", "MVC", "control panel"],
9 | "license": "MIT",
10 | "ignore": [
11 | "**/.*",
12 | "node_modules",
13 | "bower_components"
14 | ],
15 | "dependencies": {
16 | "jquery": "~1.11.0",
17 | "riotjs": "latest"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/img/by-moot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/muut/riotjs-admin/124c708c7aab66e411f12635cc9037fcd3ae142e/img/by-moot.png
--------------------------------------------------------------------------------
/img/company.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/muut/riotjs-admin/124c708c7aab66e411f12635cc9037fcd3ae142e/img/company.png
--------------------------------------------------------------------------------
/img/riotjslogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/muut/riotjs-admin/124c708c7aab66e411f12635cc9037fcd3ae142e/img/riotjslogo.png
--------------------------------------------------------------------------------
/img/riotjslogo@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/muut/riotjs-admin/124c708c7aab66e411f12635cc9037fcd3ae142e/img/riotjslogo@2x.png
--------------------------------------------------------------------------------
/img/tipiirai.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/muut/riotjs-admin/124c708c7aab66e411f12635cc9037fcd3ae142e/img/tipiirai.jpg
--------------------------------------------------------------------------------
/img/wallpaper.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/muut/riotjs-admin/124c708c7aab66e411f12635cc9037fcd3ae142e/img/wallpaper.jpg
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Admin Demo | Riot.js – The 1kb MVP library
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
33 |
34 |
35 |
36 |
37 |
49 |
50 |
51 |
52 |
Customers
53 |
54 |
55 | Users
56 |
57 |
58 | Posts
59 |
60 |
61 |
62 |
63 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
81 |
82 |
83 |
84 |
85 |
93 |
94 |
95 |
100 |
101 |
102 |
118 |
119 |
120 |
123 |
124 |
125 |
128 |
129 |
130 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
169 |
170 |
171 |
172 |
173 |
--------------------------------------------------------------------------------
/make.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | // grunt is messy, shelljs is not (https://github.com/arturadib/shelljs)
4 |
5 | require('shelljs/make');
6 |
7 | var gaze = require('gaze'),
8 | stylus = require('stylus'),
9 | header = ";(function(top) {",
10 | footer = '})(typeof top == "object" ? window : exports);';
11 |
12 |
13 | // initialize repository
14 | function init() {
15 | mkdir("-p", "dist");
16 | }
17 |
18 | // Make a single file out of everything
19 | function concat() {
20 |
21 | init();
22 |
23 | // riot.js
24 | // var js = cat("bower_components/riotjs/riot.js");
25 | var js = cat("../riotjs/riot.js");
26 |
27 | // api
28 | js += header + cat("src/api/*.js") + footer;
29 |
30 | // ui
31 | js+= cat(["src/ext/*.js", "src/ui/*.js"]);
32 |
33 | // dist
34 | js.to("dist/admin.js");
35 |
36 | }
37 |
38 | // Test the API on server side (node.js)
39 | target.test = function() {
40 |
41 | init();
42 |
43 | // generate API files
44 | (header + cat("src/api/*.js") + footer).to("dist/api.js");
45 |
46 | // run tests
47 | require("./test/index.js");
48 | }
49 |
50 |
51 | target.lint = function() {
52 | exec("jshint src");
53 | }
54 |
55 | // return target.test();
56 |
57 | // Function to compile a single stylus file
58 | function styl(source, target) {
59 | var dir = source.split("/").slice(0, -1).join("/");
60 |
61 | stylus(cat(source), { compress: true }).include(dir).render(function(err, css) {
62 | if (err) throw err;
63 | css.to(target);
64 | });
65 |
66 | }
67 |
68 | // concat target
69 | target.concat = concat;
70 |
71 |
72 | // generate application
73 | target.gen = function() {
74 | concat();
75 | styl("style/index.styl", "dist/style.css");
76 | cp("-f", "bower_components/jquery/dist/jquery.min.js", "dist");
77 | };
78 |
79 | // watch for changes: ./make.js watch
80 | target.watch = function() {
81 |
82 | // scripts
83 | gaze("src/**/*.js", function() {
84 | this.on('all', function(e, file) {
85 | concat();
86 | });
87 | });
88 |
89 | // styles
90 | gaze("style/*.styl", function() {
91 | this.on('changed', function(e, file) {
92 | styl("style/index.styl", "dist/style.css");
93 | });
94 | });
95 |
96 | };
97 |
98 |
99 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "riot-admin",
3 | "description": "Administration panel – A Riot.js demo (The 1kb MVP library)",
4 | "version": "1.0.0",
5 | "homepage": "https://muut.com/riotjs/demo/",
6 | "repository": {
7 | "type": "git",
8 | "url": "https://github.com/muut/riotjs-admin.git"
9 | },
10 | "licenses": [
11 | {
12 | "type": "MIT",
13 | "url": "http://opensource.org/licenses/mit-license.php"
14 | }
15 | ],
16 | "devDependencies": {
17 | "stylus": "latest",
18 | "lodash": "latest",
19 | "jshint": "latest",
20 | "shelljs": "latest",
21 | "gaze": "latest"
22 | }
23 |
24 | }
--------------------------------------------------------------------------------
/src/api/admin.js:
--------------------------------------------------------------------------------
1 |
2 | // The admin API
3 | function Admin(conf) {
4 |
5 | var self = riot.observable(this),
6 | backend = new Backend(conf);
7 |
8 | $.extend(self, conf);
9 |
10 | // load a given page from the server
11 | self.load = function(page, fn) {
12 |
13 | self.trigger("before:load", page);
14 |
15 | self.one("load", fn);
16 |
17 | backend.call("load", page, function(view) {
18 | self.trigger("load", view);
19 | });
20 |
21 | };
22 |
23 | // ... other API methods goes here
24 |
25 | // same as load("search")
26 | self.search = function(query, fn) {
27 | return backend.call("search", query, fn);
28 | };
29 |
30 |
31 | // initialization
32 | backend.call("init", conf.page).always(function(data) {
33 | self.user = new User(self, data ? data.user : {}, backend);
34 | self.trigger("ready");
35 |
36 | }).done(function(data) {
37 | self.trigger("load", data.view);
38 |
39 | }).fail(function() {
40 |
41 | // failed because
42 | self.user.one("login", function(data) {
43 | $.extend(self.user, data.user);
44 | self.trigger("load", data.view);
45 |
46 | });
47 |
48 | });
49 |
50 | // on each "page" load
51 | self.on("load", function(view) {
52 | self.trigger("load:" + view.type, view.data, view.path);
53 | self.page = view.type;
54 | });
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/src/api/backend.js:
--------------------------------------------------------------------------------
1 |
2 | // Fake backend to simulate a real thing
3 | function Backend(conf) {
4 |
5 | var self = this,
6 | cache = {},
7 | storage = top.localStorage || { sessionId: conf.sessionId },
8 | debug = conf.debug && typeof console != 'undefined';
9 |
10 |
11 | // underlying implementation for `call` can change
12 | self.call = function(method, arg, fn) {
13 |
14 | var ret = test_data[method](arg, storage.sessionId),
15 | promise = new Promise(fn);
16 |
17 | // debug message
18 | if (debug) console.info("->", method, arg);
19 |
20 | // configurable caching for the "load" method
21 | if (conf.cache && method == 'load') {
22 | if (cache[arg]) return promise.done(cache[arg]);
23 | cache[arg] = ret;
24 | }
25 |
26 | // session management
27 | if (ret.sessionId) storage.sessionId = ret.sessionId;
28 | else if (method == 'logout') storage.removeItem("sessionId");
29 |
30 | // fake delay for the call
31 | setTimeout(function() {
32 | if (debug) console.info("<-", ret);
33 |
34 | promise.always(ret);
35 | promise[ret === false ? 'fail' : 'done'](ret);
36 |
37 | }, 400);
38 |
39 | // given callback
40 | promise.done(fn);
41 |
42 | return promise;
43 |
44 | };
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/src/api/promise.js:
--------------------------------------------------------------------------------
1 |
2 | // A generic promiese interface by using riot.observable
3 |
4 | function Promise(fn) {
5 | var self = riot.observable(this);
6 |
7 | $.map(['done', 'fail', 'always'], function(name) {
8 | self[name] = function(arg) {
9 | return self[$.isFunction(arg) ? 'on' : 'trigger'](name, arg);
10 | };
11 | });
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/src/api/spa.js:
--------------------------------------------------------------------------------
1 |
2 | // The ability to split your single-page application (SPA) into loosely-coupled modules
3 |
4 | var instance;
5 |
6 | top.admin = riot.observable(function(arg) {
7 |
8 | // admin() --> return instance
9 | if (!arg) return instance;
10 |
11 | // admin(fn) --> add a new module
12 | if ($.isFunction(arg)) {
13 | top.admin.on("ready", arg);
14 |
15 | // admin(conf) --> initialize the application
16 | } else {
17 |
18 | instance = new Admin(arg);
19 |
20 | instance.on("ready", function() {
21 | top.admin.trigger("ready", instance);
22 | });
23 |
24 | }
25 |
26 | });
27 |
28 |
--------------------------------------------------------------------------------
/src/api/test-data.js:
--------------------------------------------------------------------------------
1 |
2 | /*jshint multistr:true */
3 |
4 | // Fake server responses (aka. "fixtures"),
5 |
6 | var customers = $.map([
7 | 'Acme, inc.',
8 | 'Widget Corp',
9 | '123 Warehousing',
10 | 'Demo Company',
11 | 'Smith and Co.',
12 | 'Foo Bars',
13 | 'ABC Telecom',
14 | 'Fake Brothers',
15 | 'QWERTY Logistics',
16 | 'Demo, inc.',
17 | 'Sample Company',
18 | 'Sample, inc',
19 | 'Acme Corp',
20 | 'Allied Biscuit',
21 | 'Ankh-Sto Associates',
22 | 'Extensive Enterprise',
23 | 'Galaxy Corp',
24 | 'Globo-Chem',
25 | 'Mr. Sparkle',
26 | 'Globex Corporation',
27 | 'LexCorp',
28 | 'LuthorCorp',
29 | 'Praxis Corporation',
30 | 'Sombra Corporation',
31 | 'Sto Plains Holdings'
32 |
33 | ], function(name, i) {
34 | return { name: name, id: i + 1, val: 100 - (i * 4) + (5 * Math.random()) };
35 |
36 | });
37 |
38 | function customer(id) {
39 | return $.extend(customers[id - 1], {
40 |
41 | img: 'img/company.png',
42 | joined: (+new Date() - 100000),
43 | email: 'demo@company.it',
44 |
45 | desc: 'Elit hoodie pickled, literally church-key whatever High Life skateboard \
46 | tofu actually reprehenderit. Id slow-carb asymmetrical accusamus \
47 | Portland, flannel tempor proident odio esse quis.',
48 |
49 | invoices: $.map([200, 350, 150, 600], function(total, i) {
50 | return { id: i + 1, total: total, time: (+new Date() - 1234567890 * i) };
51 | }),
52 |
53 | users: users
54 |
55 | });
56 |
57 | }
58 |
59 | var users = $.map([
60 | 'Cheryll Egli',
61 | 'Dominque Larocca',
62 | 'Judie Flaugher',
63 | 'Leonard Fason',
64 | 'Lia Monteith',
65 | 'Lindsy Woolard',
66 | 'Rosanna Broadhead',
67 | 'Sharyl Finlayson',
68 | 'Spencer Zeller',
69 | 'Zelda Fazenbaker'
70 |
71 | ], function(name, i) {
72 | return { name: name, id: i + 1, img: 'img/tipiirai.jpg' };
73 |
74 | });
75 |
76 | function user(id) {
77 |
78 | return $.extend(users[id - 1], {
79 | username: 'dominique2',
80 | email: 'demo.user@riotjs.com',
81 | joined: (+new Date() - 100000),
82 |
83 | desc: 'Elit hoodie pickled, literally church-key whatever High Life skateboard \
84 | tofu actually reprehenderit. Id slow-carb asymmetrical accusamus \
85 | Portland, flannel tempor proident odio esse quis.'
86 | });
87 |
88 | }
89 |
90 | function graph(multiplier) {
91 | var arr = [];
92 |
93 | for (var i = 0; i < 30; i++) {
94 | arr[i] = Math.random() * multiplier * i;
95 | }
96 |
97 | return arr;
98 | }
99 |
100 |
101 | var test_data = {
102 |
103 | // load new "page"
104 | load: function(path) {
105 |
106 | var els = path.split("/"),
107 | page = els[0];
108 |
109 | return {
110 | path: path,
111 | type: page || "stats",
112 | data: page == "stats" ? [ graph(100), graph(100), graph(200) ] :
113 | page == "customers" ? customers :
114 | page == "customer" ? customer(els[1]) :
115 | page == "user" ? user(els[1]) : []
116 | };
117 |
118 | },
119 |
120 | // init
121 | init: function(page, sessionId) {
122 |
123 | return !sessionId ? false : {
124 | user: {
125 | email: "joe@riotjs.com",
126 | name: "Joe Rebellous",
127 | username: "riot"
128 | },
129 | sessionId: sessionId,
130 | view: test_data.load(page || 'stats')
131 | };
132 |
133 | },
134 |
135 | search: function(query) {
136 | return users.filter( function(user){
137 | return user.name.toLowerCase().match(query.toLowerCase());
138 | });
139 | },
140 |
141 | login: function(params) {
142 | return test_data.init(params.page, params.username == 'riot');
143 | },
144 |
145 | logout: function() {
146 | return true;
147 | }
148 |
149 | };
150 |
--------------------------------------------------------------------------------
/src/api/user.js:
--------------------------------------------------------------------------------
1 |
2 | // Current user (logged in or anonymous)
3 | function User(app, data, backend) {
4 |
5 | var self = riot.observable(this);
6 |
7 | $.extend(self, data);
8 |
9 | self.login = function(params, fn) {
10 |
11 | self.one("login", fn);
12 |
13 | return backend.call("login", params, function(data) {
14 | self.trigger("login", data);
15 | });
16 |
17 | };
18 |
19 | self.logout = function(fn) {
20 |
21 | self.one("logout", fn);
22 |
23 | return backend.call("logout", {}, function(data) {
24 | self.trigger("logout");
25 | });
26 |
27 | };
28 |
29 | }
--------------------------------------------------------------------------------
/src/ext/graph.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Graph
8 |
9 |
10 |
11 |
28 |
29 |
30 |
31 | Forums
32 |
33 |
34 | Users
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/src/ext/graph.js:
--------------------------------------------------------------------------------
1 |
2 | // A minimalistic line graph tool (use with extreme care, not tested)
3 | $.fn.graph2 = function(data, color) {
4 |
5 | var graph = this.attr("width", this.parent().width() - 10),
6 | padd = 35,
7 | c = graph[0].getContext("2d"),
8 | max = Math.max.apply(0, data),
9 | width = graph.width(),
10 | height = graph.height(),
11 | len = data.length;
12 |
13 | // re-render? -> clear
14 | c.clearRect (0, 0, width, height);
15 |
16 | function getX(val) {
17 | return ((width - padd) / len) * val + (padd * 1.5);
18 | }
19 |
20 | function getY(val) {
21 | return height - (((height - padd) / max) * val) - padd;
22 | }
23 |
24 | c.strokeStyle = "#999";
25 | c.font = "12px " + $("body").css("fontFamily");
26 | c.fillStyle = "#666";
27 | c.textAlign = "center";
28 |
29 | // axises
30 | c.lineWidth = 0.5;
31 | c.beginPath();
32 | c.moveTo(padd, 0);
33 | c.lineTo(padd, height - padd);
34 | c.lineTo(width, height - padd);
35 | c.stroke();
36 |
37 | // x labels
38 | for(var i = 0; i < len; i++) {
39 | c.fillText(i, getX(i), height - padd + 20);
40 | }
41 |
42 | // y labels
43 | c.textAlign = "right";
44 | c.textBaseline = "middle";
45 |
46 | var steps = Math.round(max / 6 / 100) * 100;
47 |
48 | for(i = 0; i < max; i += steps) {
49 | c.fillText(i, padd - 10, getY(i));
50 | }
51 |
52 | // lines
53 | c.lineWidth = 1;
54 | c.beginPath();
55 | c.moveTo(getX(0), getY(data[0]));
56 |
57 | for(i = 1; i < len; i ++) {
58 | c.lineTo(getX(i), getY(data[i]));
59 | }
60 |
61 | c.strokeStyle = color;
62 | c.stroke();
63 |
64 | };
65 |
66 |
--------------------------------------------------------------------------------
/src/ui/README.md:
--------------------------------------------------------------------------------
1 |
2 | The UI is a list of loosely-coupled presenter modules.
--------------------------------------------------------------------------------
/src/ui/customer.js:
--------------------------------------------------------------------------------
1 |
2 | // Presenter for single user
3 | admin(function(app) {
4 |
5 | var root = $("#customer-page"),
6 | tmpl = $("#customer-tmpl").html(),
7 | user_tmpl = $("#user-link-tmpl").html(),
8 | invoice_tmpl = $("#invoice-tmpl").html();
9 |
10 | app.on("load:customer", function(data) {
11 |
12 | data.joined = util.timeformat(data.joined);
13 | root.html(riot.render(tmpl, data));
14 |
15 | // users
16 | var list = $("#user-list", root);
17 |
18 | $.each(data.users, function(i, el) {
19 | list.append(riot.render(user_tmpl, el));
20 | });
21 |
22 | // invoices
23 | list = $("#invoice-list ul", root);
24 |
25 | $.each(data.invoices, function(i, el) {
26 | el.time = util.timeformat(el.time);
27 | list.append(riot.render(invoice_tmpl, el));
28 | });
29 |
30 | });
31 |
32 | });
--------------------------------------------------------------------------------
/src/ui/customers.js:
--------------------------------------------------------------------------------
1 |
2 | // Presenter for customer list
3 | admin(function(app) {
4 |
5 | var root = $("#bars", app.root),
6 | tmpl = $("#bars-tmpl").html();
7 |
8 | app.on("load:customers", function(view) {
9 |
10 | var max;
11 |
12 | // clear existing data
13 | root.empty();
14 |
15 | // add new ones
16 | $.each(view, function(i, entry) {
17 |
18 | // first one is the largest
19 | if (!i) max = entry.val;
20 |
21 | entry.width = Math.round(entry.val / max * 100);
22 |
23 | root.append(riot.render(tmpl, entry));
24 |
25 | });
26 |
27 | });
28 |
29 | });
--------------------------------------------------------------------------------
/src/ui/login.js:
--------------------------------------------------------------------------------
1 |
2 | // Login and logout features
3 |
4 | admin(function(app) {
5 |
6 | var user = app.user,
7 | loading = "is-loading";
8 |
9 | // login
10 | $("#login").submit(function(e) {
11 | e.preventDefault();
12 |
13 | var el = $(this).addClass("is-loading");
14 |
15 | user.login({
16 | username: this.username.value,
17 | password: this.password.value,
18 | page: app.page
19 |
20 | }).fail(function() {
21 | console.info("login failed");
22 |
23 | }).done(function() {
24 | el.removeClass("is-loading");
25 |
26 | });
27 |
28 | });
29 |
30 | // logout
31 | $("#logout").click(function(e) {
32 | e.preventDefault();
33 | var el = $(this).addClass("is-loading");
34 |
35 | user.logout(function() {
36 | el.removeClass("is-loading");
37 | });
38 |
39 | });
40 |
41 | function toggle(is_logged) {
42 | app.root.toggleClass("is-logged", is_logged).toggleClass("is-not-logged", !is_logged);
43 | }
44 |
45 | user.on("login logout", function(type) {
46 | toggle(type == 'login');
47 | });
48 |
49 | toggle(!!user.username);
50 |
51 | });
--------------------------------------------------------------------------------
/src/ui/search.js:
--------------------------------------------------------------------------------
1 |
2 | // Search dropdown
3 | admin(function(app) {
4 |
5 | var form = $("#search"),
6 | tmpl = $("#result-tmpl").html(),
7 | results = $("#results");
8 |
9 | form.on('keyup', function(e) {
10 |
11 | e.preventDefault();
12 |
13 | var form = $(this),
14 | val = $.trim(this.q.value);
15 |
16 | if (!val) return;
17 |
18 | form.addClass("is-loading");
19 |
20 | app.search(val, function(arr) {
21 | form.removeClass("is-loading");
22 | results.empty().show();
23 |
24 | $.each(arr, function(i, res) {
25 | results.append(riot.render(tmpl, res));
26 | });
27 |
28 | });
29 |
30 | $(document).one("click keypress", function() {
31 | results.hide();
32 | });
33 |
34 | });
35 |
36 | });
37 |
--------------------------------------------------------------------------------
/src/ui/stats.js:
--------------------------------------------------------------------------------
1 |
2 | // Presenter for stats (the line graphs, see ext/graph.js)
3 | admin(function(app) {
4 |
5 | var canvas = $("canvas", app.root),
6 | colors = ['#be0000', '#4cbe00', '#1fadc5'];
7 |
8 | app.on("load:stats", function(stats) {
9 |
10 | $.each(stats, function(i, data) {
11 | canvas.eq(i).graph2(data, colors[i]);
12 | });
13 |
14 | });
15 |
16 | });
--------------------------------------------------------------------------------
/src/ui/user.js:
--------------------------------------------------------------------------------
1 |
2 | // Presenter for single user
3 | admin(function(app) {
4 |
5 | var root = $("#user-page"),
6 | tmpl = $("#user-tmpl").html();
7 |
8 | app.on("load:user", function(data) {
9 | data.joined = util.timeformat(data.joined);
10 | root.html(riot.render(tmpl, data));
11 |
12 | // not real banning feature on this demo
13 | $("button", root).click(function() {
14 | $(this).text("User is banned!");
15 | });
16 |
17 | });
18 |
19 | });
--------------------------------------------------------------------------------
/src/ui/util.js:
--------------------------------------------------------------------------------
1 |
2 | // List of utility functions for the UI
3 | var util = {
4 |
5 | // date formatting goes to presenter layer, not inside model
6 | timeformat: function(time) {
7 | var d = new Date(time);
8 | return d.getFullYear() + "/" + (d.getMonth() + 1) + "/" + d.getDate();
9 | }
10 |
11 | };
12 |
--------------------------------------------------------------------------------
/src/ui/view-switch.js:
--------------------------------------------------------------------------------
1 |
2 | /*
3 | Handle view switching, aka. "routing"
4 | The transition effect is done with CSS
5 | */
6 | admin(function(app) {
7 |
8 | var is_active = "is-active";
9 |
10 | // 1. select elements from the page to call riot.route(path)
11 | app.root.on("click", "[href^='#/']", function(e) {
12 |
13 | e.preventDefault();
14 |
15 | var link = $(this);
16 |
17 | // no action
18 | if (link.hasClass(is_active)) return;
19 |
20 | // loading indicator
21 | link.addClass("is-loading");
22 |
23 | // Riot changes the URL, notifies listeners and takes care of the back button
24 | riot.route(link.attr("href"));
25 |
26 | });
27 |
28 |
29 | // 2. listen to route clicks and back button
30 | riot.route(function(path) {
31 |
32 | // Call API method to load stuff from server
33 | app.load(path.slice(2));
34 |
35 | });
36 |
37 | // 3. Set "is-active" class name for the active page and navi element
38 | app.on("before:load", function() {
39 |
40 | // remove existing class
41 | $("." + is_active).removeClass(is_active);
42 |
43 |
44 | }).on("load", function(view) {
45 |
46 | // set a new class
47 | $("#" + view.type + "-page").add("#" + view.type + "-nav").addClass(is_active);
48 |
49 | // remove loading indicator
50 | $("nav .is-loading").removeClass("is-loading");
51 |
52 | });
53 |
54 | });
55 |
56 |
--------------------------------------------------------------------------------
/style/css3.styl:
--------------------------------------------------------------------------------
1 |
2 | /* Avoid using vendor prefixes */
3 |
4 | vendor-prefixes = webkit moz w3c
5 |
6 | vendor(prop, args, only = null, ignore = null)
7 | for prefix in vendor-prefixes
8 | unless (only and !(prefix in only)) or (ignore and prefix in ignore)
9 | if prefix == w3c
10 | {prop}: args
11 | else
12 | {'-' + prefix + '-' + prop}: args
13 |
14 | opacity(args...)
15 | opacity: args
16 | filter: 'progid:DXImageTransform.Microsoft.Alpha(Opacity=%s)' % round(args[0] * 100)
17 |
18 | background-size()
19 | vendor('background-size', arguments)
20 |
21 | transform()
22 | vendor('transform', arguments)
23 |
24 | border-image()
25 | vendor('border-image', arguments)
26 |
27 | transition()
28 | vendor('transition', arguments)
29 |
30 | box-sizing()
31 | vendor('box-sizing', arguments)
32 |
33 | border-radius()
34 | vendor('border-radius', arguments)
35 |
--------------------------------------------------------------------------------
/style/global.styl:
--------------------------------------------------------------------------------
1 |
2 | /* Global stuff: body, links, inputs, header, #main, footer */
3 |
4 | // quick reset
5 | *
6 | border 0
7 | margin 0
8 | line-height 1
9 | box-sizing border-box
10 |
11 |
12 | body
13 | font-family "Myriad Pro", "Lucida Grande", "Lucida Sans Unicode"
14 | min-width 400px
15 |
16 | template
17 | display none
18 |
19 | a
20 | color blue
21 | text-decoration none
22 | cursor pointer
23 |
24 | &:hover
25 | text-decoration underline
26 |
27 | input
28 | border 1px solid #c
29 | padding u
30 | border-radius 3px
31 | font-size 100%
32 |
33 | button
34 | padding uh u2
35 | background-color blue
36 | color #f
37 | font-size 100%
38 | cursor pointer
39 | border-radius 5em
40 | outline 0
41 |
42 | &:hover
43 | background-color darken(blue, 5)
44 |
45 | &:active
46 | background-color darken(blue, 15)
47 |
48 |
49 | header
50 | background-color #3
51 | padding u
52 | margin-bottom u2
53 | color #e
54 | text-transform uppercase
55 | letter-spacing 2px
56 | font-size 110%
57 | display none
58 |
59 | header div, #main
60 | max-width 800px
61 | margin 0 auto
62 |
63 | header a
64 | font-size 80%
65 | margin-left 1em
66 | color #e
67 | transition color .1s
68 |
69 | &.is-loading
70 | color #5
71 |
72 | &.is-active
73 | color #9
74 | text-decoration none
75 | cursor default
76 |
77 | nav
78 | margin-left 2em
79 | display inline-block
80 | width 54%
81 |
82 | footer
83 | background-color rgba(#313f4c, .8)
84 | width 100%
85 | position fixed
86 | bottom 0
87 | font-size 80%
88 | color #9
89 | padding .6em 0
90 | letter-spacing 5px
91 | text-align center
92 | display none
93 |
94 | a
95 | color #d
96 | letter-spacing 0
97 |
--------------------------------------------------------------------------------
/style/index.styl:
--------------------------------------------------------------------------------
1 |
2 | @import "css3"
3 | @import "vars"
4 | @import "global"
5 | @import "login"
6 | @import "view"
7 | @import "search"
8 | @import "media"
9 |
--------------------------------------------------------------------------------
/style/login.styl:
--------------------------------------------------------------------------------
1 |
2 | /* Initial login screen */
3 |
4 | #logo
5 | background-image url("../img/riotjslogo.png")
6 | width 220px
7 | height 83px
8 | position absolute
9 | top u2
10 | left u2
11 | display none
12 |
13 | #login
14 | padding 2em
15 | background-color rgba(#0, .5)
16 | color #e
17 | margin 150px auto 50px
18 | border-radius 4px
19 | display none
20 | transition opacity .3s
21 |
22 | label
23 | display block
24 | margin-bottom u2
25 | font-weight 100
26 | color #c
27 | text-align left
28 |
29 | span
30 | display block
31 | margin-bottom uh
32 |
33 | button
34 | width 60%
35 | font-size 110%
36 | padding u 0
37 |
38 | input
39 | width 100%
40 | border none
41 | background-color rgba(#f, .5)
42 | box-shadow 0 0 5px #4 inset
43 |
44 | &:focus
45 | background-color #f
46 |
47 | h3
48 | font-family "avenir next", "helvetica neue", "verdana"
49 | font-weight 100
50 | font-size 250%
51 | margin-bottom u
52 |
53 | &.is-loading
54 | opacity .5
55 |
56 |
57 | .is-not-logged
58 | background #f url(../img/wallpaper.jpg) 0 0 no-repeat
59 | text-align center
60 | background-size cover
61 |
62 | #login
63 | display inline-block
64 |
65 | #logo, footer
66 | display block
67 |
68 |
69 | .is-logged
70 | background-image none
71 |
72 | header, .page
73 | display block
74 |
75 | #login
76 | display none
77 |
--------------------------------------------------------------------------------
/style/media.styl:
--------------------------------------------------------------------------------
1 |
2 | // logo
3 | @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi)
4 | #logo
5 | background-image url("../img/riotjslogo@2x.png")
6 | -webkit-background-size 100%
7 |
8 | // login backgrond image
9 | @media (max-width: 1150px)
10 | body.is-not-logged
11 | background-size 150% 1600px
12 |
13 | @media (max-width: 800px)
14 | #search
15 | display block
16 | margin-bottom u2
17 |
18 | navi
19 | margin-left -(uh)
20 | width 100%
21 |
22 | header a
23 | font-size 12px
24 |
25 | #logout
26 | position absolute
27 | top 20px
28 | right 15px
29 | margin 0
30 |
31 | #bars
32 | em
33 | display block
34 | margin-bottom -(uh)
--------------------------------------------------------------------------------
/style/search.styl:
--------------------------------------------------------------------------------
1 |
2 | /* search dropdown */
3 |
4 | #search
5 | display inline-block
6 | position relative
7 |
8 | input
9 | padding .3em 1em
10 | border-radius 3em
11 | font-size 85%
12 | background-color #6
13 | transition background-color .4s
14 | border-color #6
15 |
16 | &:focus
17 | outline none
18 | background-color #f
19 |
20 | &.is-loading
21 | opacity .5
22 | transition opacity .2s
23 |
24 | #results
25 | position absolute
26 | top 110%
27 | left 0
28 | background-color #f
29 | width 100%
30 | box-shadow 0 0 6px #c
31 | border-radius 4px
32 | display none
33 | z-index 9
34 |
35 | a
36 | font-size 80%
37 | color #3
38 | text-transform none
39 | letter-spacing 0
40 | display block
41 | margin .5em
42 | text-decoration none
43 |
44 | &:hover
45 | background-color blue
46 | color #f
47 |
48 | > *
49 | display inline-block
50 | vertical-align middle
51 | img
52 | width 2.4em
53 | margin-right uh
54 |
55 | span
56 | margin-top uh
57 |
--------------------------------------------------------------------------------
/style/vars.styl:
--------------------------------------------------------------------------------
1 |
2 | /* CSS variables for colors and sizing */
3 |
4 | mootred = #ff003f
5 | red = #be0000
6 | green = #4cbe00
7 | blue = #1fadc5
8 | blue2 = #6cb7c4
9 | pink = #ff57a8
10 |
11 |
12 | // size units
13 | u = 0.6em
14 | u2 = 1.2em
15 | uh = 0.3em
--------------------------------------------------------------------------------
/style/view.styl:
--------------------------------------------------------------------------------
1 |
2 | /*
3 | All the different views of this application.
4 | Might make sense to separate into individual files when things start growing.
5 | */
6 |
7 | // CSS based fading for view switch
8 | #main
9 | position relative
10 |
11 | // pages are absolutely positioned so that they can fade-in/out
12 | .page
13 | position absolute
14 | top 0
15 | left 0
16 | opacity 0
17 | transition opacity .1s
18 | width 100%
19 | z-index -1
20 | padding 15px
21 | display none
22 |
23 | &.is-active
24 | opacity 1
25 | z-index 1
26 |
27 |
28 | // statistical bars
29 | #bars
30 | a
31 | display block
32 | padding uh
33 | text-decoration none
34 |
35 | > *
36 | display inline-block
37 | vertical-align middle
38 |
39 | &:hover
40 | background-color #f9
41 | color #3
42 |
43 | em
44 | width 20%
45 | white-space nowrap
46 | padding-right u
47 | font-size 90%
48 | font-style normal
49 |
50 | span
51 | width 78%
52 |
53 | strong
54 | height 4px
55 | background-color #7
56 | display block
57 | border-radius 4px
58 |
59 | #stats-page
60 | h3
61 | font-weight normal
62 | font-size 100%
63 | margin 0 0 -1.1em 3em
64 |
65 | canvas
66 | height 200px
67 | margin-bottom 25px
68 |
69 | #user-page, #customer-page
70 | margin-top u2
71 |
72 | img
73 | margin-right u2
74 | border-radius 4px
75 |
76 | .details
77 | display inline-block
78 | max-width 530px
79 | vertical-align top
80 | padding-top uh
81 |
82 | h1
83 | font-weight normal
84 |
85 | .meta
86 | margin-bottom u
87 | color #7
88 | margin uh 0 u
89 |
90 | span
91 | margin 0 uh
92 | color #c
93 |
94 | .desc
95 | color #5
96 | font-size 120%
97 | line-height 1.2
98 | margin-bottom u2
99 |
100 | #customer-page
101 | img
102 | max-width 180px
103 |
104 | #user-list
105 | margin-bottom u2
106 |
107 | img
108 | max-width 60px
109 | margin 0 u uh 0
110 |
111 | #invoice-list
112 | max-width 200px
113 |
114 | ul
115 | list-style-type none
116 | padding 0
117 |
118 | li
119 | padding uh 0
120 |
121 | strong
122 | float right
123 |
124 |
--------------------------------------------------------------------------------
/test/index.js:
--------------------------------------------------------------------------------
1 |
2 | /*
3 | Shows you how to test your API (model) on the server.
4 | This makes a seamless integration to the development workflow.
5 | */
6 |
7 | // required stuff for tests
8 | require("./test.js")
9 |
10 | // the admin interface
11 | var admin = require("../dist/api.js").admin;
12 |
13 | // run the tests
14 | admin(function(app) {
15 |
16 | it("Should have proper initial values", function() {
17 | assert.equal(app.page, "stats");
18 | assert.equal(app.user.username, "riot");
19 | });
20 |
21 | it("Should have an initial view", function() {
22 | app.one("load", function(view) {
23 | assert.equal(view.type, "stats");
24 | })
25 | })
26 |
27 | });
28 |
29 | // initialize
30 | admin({ page: "stats", debug: false, cache: false, sessionId: 'abc' })
31 |
--------------------------------------------------------------------------------
/test/test.js:
--------------------------------------------------------------------------------
1 |
2 | // BDD style testing
3 | require("../bower_components/riotjs/bdd.js");
4 |
5 | // jQuery object with Riot functions
6 | global.$ = global.riot = require("../bower_components/riotjs/riot.js");
7 |
8 | // $ utility functions used by the API (from lodash)
9 | var _ = require("lodash");
10 |
11 | _.each(['map', 'isFunction', 'extend'], function(name) {
12 | $[name] = _[name];
13 | });
14 |
--------------------------------------------------------------------------------