Browse files

Merge branch 'dev'

  • Loading branch information...
2 parents 00b507b + 255616f commit 8f7c13e805721422a36c06b77c02d419e9967e61 @caolan caolan committed Dec 11, 2011
Showing with 1,178 additions and 7,197 deletions.
  1. +0 −1 docs/CNAME
  2. +0 −193 docs/build_docs.js
  3. +0 −55 docs/community.md
  4. +0 −270 docs/css/kanso.css
  5. +0 −1 docs/docs.md
  6. +0 −48 docs/download.md
  7. +0 −185 docs/guides/adding_pretty_links.md
  8. +0 −895 docs/guides/getting_started.md
  9. +0 −25 docs/guides/how_kanso_processes_requests.md
  10. BIN docs/guides/images/adding_forms1.png
  11. BIN docs/guides/images/adding_forms2.png
  12. BIN docs/guides/images/adding_forms3.png
  13. BIN docs/guides/images/deployment1.png
  14. BIN docs/guides/images/deployment2.png
  15. BIN docs/guides/images/describing_the_data1.png
  16. BIN docs/guides/images/describing_the_data2.png
  17. BIN docs/guides/images/describing_the_data3.png
  18. BIN docs/guides/images/describing_the_data4.png
  19. BIN docs/guides/images/describing_the_data5.png
  20. BIN docs/guides/images/describing_the_data6.png
  21. BIN docs/guides/images/embedded_types1.png
  22. BIN docs/guides/images/embedded_types2.png
  23. BIN docs/guides/images/embedded_types3.png
  24. BIN docs/guides/images/embedded_types4.png
  25. BIN docs/guides/images/embedded_types5.png
  26. BIN docs/guides/images/querying_the_data1.png
  27. BIN docs/guides/images/querying_the_data2.png
  28. BIN docs/guides/images/rendering_pages1.png
  29. BIN docs/guides/images/rendering_pages2.png
  30. BIN docs/guides/images/rendering_pages3.png
  31. BIN docs/guides/images/request_flow.png
  32. +0 −2,689 docs/guides/images/request_flow.svg
  33. BIN docs/guides/images/users_and_permissions1.png
  34. BIN docs/guides/images/users_and_permissions2.png
  35. +0 −42 docs/guides/index.md
  36. 0 docs/guides/kanso_events.md
  37. 0 docs/guides/selecting_docs_advanced.md
  38. +0 −452 docs/guides/selecting_docs_basic.md
  39. +0 −75 docs/guides/sharing_code_with_views.md
  40. +0 −221 docs/guides/working_with_data.md
  41. +0 −57 docs/guides/writing_guides.md
  42. BIN docs/images/kanso.png
  43. +0 −109 docs/images/kanso.svg
  44. BIN docs/images/kanso_small.png
  45. BIN docs/images/topbar_bg.png
  46. +0 −105 docs/index.md
  47. +0 −1,315 docs/showdown.js
  48. +0 −66 docs/templates/base.html
  49. +0 −2 docs/tutorial.md
  50. +1 −1 package.json
  51. +4 −3 project/kanso.json
  52. +6 −6 project/templates/base.html
  53. +11 −5 scripts/autocomp.js
  54. +8 −1 scripts/install_autocomp.js
  55. +69 −0 src/kanso/commands/createdb.js
  56. +69 −0 src/kanso/commands/deletedb.js
  57. +0 −202 src/kanso/commands/fetch.js
  58. +4 −1 src/kanso/commands/index.js
  59. +339 −71 src/kanso/commands/install.js
  60. +125 −0 src/kanso/commands/listdb.js
  61. +96 −63 src/kanso/commands/push.js
  62. +104 −0 src/kanso/commands/replicate.js
  63. +51 −23 src/kanso/commands/upload.js
  64. +61 −1 src/kanso/couchdb.js
  65. +5 −2 src/kanso/logger.js
  66. +9 −3 src/kanso/repository.js
  67. +133 −9 src/kanso/utils.js
  68. +83 −0 test/test-lib-utils.js
View
1 docs/CNAME
@@ -1 +0,0 @@
-kansojs.org
View
193 docs/build_docs.js
@@ -1,193 +0,0 @@
-var scrawl = require('../deps/scrawl'),
- Showdown = require('../deps/showdown'),
- async = require('../deps/async'),
- utils = require('../lib/utils'),
- templates = require('../packages/kanso-templates/build/templates'),
- dust = require('../deps/dustjs/lib/dust'),
- path = require('path'),
- fs = require('fs');
-
-
-var output_dir = __dirname + '/../www';
-var template_dir = __dirname + '/templates';
-
-
-function get_named_public_apis(comments) {
- return comments.filter(function (c) {
- return c.api === 'public' && c.name;
- });
-}
-
-function get_module_comment(comments) {
- return comments.filter(function (c) {
- return c.module;
- })[0];
-};
-
-function render_module(module) {
- var html = '<h2 id="' + module.name + '">' + module.name + '</h2>';
- var c = get_module_comment(module.comments);
- if (c) {
- html += c.description_html;
- }
- var items = get_named_public_apis(module.comments);
- return html + items.map(function (i) {
- return render_item(module, i);
- }).join('');
-}
-
-function render_item(module, item) {
- var html = '<h3 id="' + item_id(module, item) + '">' +
- item.name + '</h3>';
- html += item.description_html;
- if (item.params) {
- html += '<h4>Parameters</h4>';
- html += '<table class="params">';
- html += item.params.map(function (p) {
- return '<tr>' +
- '<td class="name">' + (p.name || '') + '</td>' +
- '<td class="type">' + (p.type || '') + '</td>' +
- '<td class="description">' + (p.description || '') + '</td>' +
- '</tr>';
- }).join('');
- html += '</table>';
- }
- if (item.returns) {
- html += '<div class="returns">' +
- '<strong>Returns: </strong>' +
- '<span class="type">' + item.returns + '</span>' +
- '</div>';
- }
- return html;
-}
-
-function item_id(module, item) {
- return module.name + '.' + item.name.replace(/\(.*$/, '');
-}
-
-function create_page(infile, outfile, nav, title, rootURL, callback) {
- if (!callback) {
- callback = rootURL;
- rootURL = '.';
- }
- fs.readFile(infile, function (err, content) {
- if (err) {
- return callback(err);
- }
- var converter = new Showdown.converter();
- var html = converter.makeHtml(content.toString());
- if (!title && title !== '') {
- title = content.toString().replace(/^\s*# /, '').replace(/\n.*/g, '');
- }
- var navobj = {};
- navobj[nav] = true;
- var context = {
- content: html,
- rootURL: rootURL,
- nav: navobj,
- title: title
- };
- dust.render('base.html', context, function (err, result) {
- if (err) {
- return callback(err);
- }
- fs.writeFile(outfile, result, callback);
- });
- });
-}
-
-function create_guides(dir, outdir, callback) {
- utils.find(dir, /\.md$/, function (err, files) {
- async.forEach(files, function (f, cb) {
- var name = path.basename(f, '.md');
- create_page(
- f,
- outdir + '/' + name + '.html',
- 'guides',
- null,
- '..',
- callback
- );
- });
- });
-}
-
-function load_templates(path, callback) {
- templates.find(path, function (err, paths) {
- if (err) {
- return callback(err);
- }
- async.forEach(paths, function (p, cb) {
- fs.readFile(p, function (err, content) {
- if (err) {
- return callback(err);
- }
- var rel = utils.relpath(p, path);
- dust.compileFn(content.toString(), rel);
- cb();
- });
- }, callback);
- });
-}
-
-
-async.parallel({
- load_templates: async.apply(load_templates, template_dir),
- ensureDir: async.apply(utils.ensureDir, output_dir)
-},
-function (err, results) {
- if (err) {
- console.error(err);
- return console.error(err.stack);
- }
- var modules = results.parseModules;
- async.parallel([
- async.apply(
- create_page,
- __dirname + '/index.md',
- output_dir + '/index.html',
- 'about',
- ''
- ),
- async.apply(
- create_page,
- __dirname + '/download.md',
- output_dir + '/download.html',
- 'download',
- ''
- ),
- async.apply(
- create_page,
- __dirname + '/community.md',
- output_dir + '/community.html',
- 'community',
- 'Community'
- ),
- async.apply(
- create_page,
- __dirname + '/docs.md',
- output_dir + '/docs.html',
- 'api',
- 'API'
- ),
- async.apply(
- create_page,
- __dirname + '/tutorial.md',
- output_dir + '/tutorial.html',
- 'guides',
- 'Guides'
- ),
- async.apply(
- create_guides,
- __dirname + '/guides',
- output_dir + '/guides'
- )
- ],
- function (err) {
- if (err) {
- console.error(err);
- return console.error(err.stack);
- }
- console.log('OK');
- });
-});
View
55 docs/community.md
@@ -1,55 +0,0 @@
-# Community
-
-
-## Support and Discussion
-
-If you want to get involved or ask questions, these are good places to start:
-
-* __IRC:__ #kansojs on FreeNode
-* __Mailing List:__ [http://groups.google.com/group/kanso][mailinglist]
-
-
-## Reporting Issues
-
-Feature requests and bug reports should be added to the GitHub issues list
-
-* __GitHub Issues:__ [https://github.com/caolan/kanso/issues][issues]
-
-
-## Contributors
-
-Here is a list of contributors that have had patches accepted and released
-in a version of Kanso. If you'd like to appear on this list, there are plenty
-of [issues in GitHub labelled 'easy'][easyissues] for newcomers to the project!
-
-<ul id="contributors">
-
-</ul>
-
-<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"></script>
-<script>
- function kanso_contributors(result) {
- var cs = result.contributors || [];
- for (var i = 0, len = cs.length; i < len; i++) {
- var c = cs[i];
- var html = '<li>' +
- '<a href="https://github.com/' + c.login + '">' +
- '<img src="http://gravatar.com/avatar/' + c.gravatar_id + '?size=48" />' +
- '</a>' +
- '<a href="https://github.com/' + c.login + '">' +
- '<span class="name">' + (c.name || c.login) + '</span>' +
- '</a>' +
- '<span class="location">' + (c.location || '') + '</span>' +
- '</li>';
- $('#contributors').append(html);
- }
- };
-</script>
-
-<script src="https://github.com/api/v2/json/repos/show/caolan/kanso/contributors?callback=kanso_contributors"></script>
-
-
-
-[mailinglist]: http://groups.google.com/group/kanso "Kanso Mailing List"
-[issues]: https://github.com/caolan/kanso/issues "GitHub Issues"
-[easyissues]: https://github.com/caolan/kanso/issues?labels=easy&sort=created&direction=desc&state=open&page=1 "Easy Issues in GitHub"
View
270 docs/css/kanso.css
@@ -1,270 +0,0 @@
-body {
- font-family: Arial, sans-serif;
- margin: 0;
- padding: 0;
- font-size: 13px;
- line-height: 1.618em;
- color: #292626;
-}
-#topbar {
- height: 20px;
- border-top: 2px solid #ccc;
- background-color: white;
- background-image: url(../images/topbar_bg.png);
- background-position: bottom left;
- background-repeat: repeat-x;
- padding: 7px 14px;
-}
-#logo {
- text-decoration: none;
- position: relative;
- top: 1px;
-}
-#logo img {
- border: none;
-}
-#site {
- text-decoration: none;
- font-family: 'Helvetica Neue', 'Helvetica', Arial, FreeSans, sans-serif;
- font-size: 14.5px;
- border-left: 1px solid #ddd;
- height: 20px;
- margin: 0 0 0 10px;
- padding: 0 0 0 14px;
- color: #808080;
- font-weight: bold;
-}
-#rightmenu {
- display: block;
- position: absolute;
- right: 0;
- top: 0;
- padding: 0;
- margin: 0;
-}
-
-#topmenu,
-#sessionmenu {
- font-size: 13px;
- display: block;
- list-style-type: none;
- margin: 0;
- padding: 0;
- height: 36px;
- font-family: 'Helvetica Neue', 'Helvetica', Arial, FreeSans, sans-serif;
- padding: 0 14px;
- margin-left: 14px;
- float: right;
-}
-#topmenu li,
-#sessionmenu li {
- display: block;
- list-style-type: none;
- margin: 0;
- padding: 0;
- float: left;
-}
-#topmenu li a,
-#sessionmenu li a {
- display: block;
- color: #808080;
- text-decoration: none;
- margin: 0;
- padding: 8px 9px 4px 9px;
- border-top: 2px solid #ccc;
-}
-
-@-moz-document url-prefix() {
- #topmenu li a,
- #sessionmenu li a {
- padding: 6px 9px;
- }
-}
-
-#topmenu li a:hover,
-#sessionmenu li a:hover {
- color: black;
- border-top: 2px solid #999;
- background-color: #eee;
- background-image: url(../images/topbar_bg.png);
- background-position: bottom left;
- background-repeat: repeat-x;
-}
-#topmenu li.active a,
-#sessionmenu li.active a {
- color: black;
- border-top: 2px solid red;
- background: none;
-}
-#content {
- padding: 14px 28px;
- width: 586px;
-}
-h1, h2, h3 {
- font-family: 'Helvetica Neue', 'Helvetica', Arial, FreeSans, sans-serif;
- font-weight: bold;
- padding: 0;
-}
-h1 {
- font-size: 2.618em;
- margin: 34px 0 34px 0;
- color: #292626;
-}
-h2 {
- font-size: 1.618em;
- margin: 34px 0 21px 0;
- color: #191616;
-}
-h3 {
- font-size: 1.12em;
- margin: 21px 0 12px 0;
- color: #090606;
-}
-
-#contributors {
- margin: 2.618em 0;
- padding: 0;
- list-style-type: none;
-}
-
-#contributors li {
- list-style-type: none;
- margin: 0;
- padding: 0;
- height: 58px;
- display: block;
- position: relative;
-}
-
-#contributors li a img {
- border: none;
- position: absolute;
- top: 0;
- left: 0;
-}
-
-#contributors li a .name {
- position: absolute;
- top: 0;
- left: 58px;
-}
-
-#contributors li .location {
- position: absolute;
- top: 18px;
- left: 58px;
- color: #666;
- font-size: 0.9em;
-}
-
-pre {
- font-family: 'Monospace', monospace !important;
- color: black;
- background: white;
- font-size: 12px;
- border: dashed 1px #D6CDA0;
- border-left: 0.382em solid #D6CDA0;
- padding: 1.618em 2.618em;
- margin: 2.618em 0;
- box-shadow: 0 2px 7px #DFDFDF;
- -moz-box-shadow: 0 2px 7px #DFDFDF;
- -webkit-box-shadow: 0 2px 7px #DFDFDF;
-}
-code {
- background-color: white !important;
- padding: 0 !important;
- margin: 0 !important;
-}
-
-#content img {
- box-shadow: 0 2px 10px #DFDFDF;
- -moz-box-shadow: 0 2px 10px #DFDFDF;
- -webkit-box-shadow: 0 2px 10px #DFDFDF;
- margin: 1em 0;
-}
-
-#content .author img {
- box-shadow: none;
- -moz-box-shadow: none;
- -webkit-box-shadow: none;
- margin: 0;
-}
-
-img.diagram {
- border: none !important;
- box-shadow: none !important;
- -moz-box-shadow: none !important;
- -webkit-box-shadow: none !important;
- margin: 4.618em 1.618em !important;
-}
-
-
-#contributors img {
- margin: 0;
-}
-
-.params, .returns, .options, .constructor {
- font-size: 0.8em;
- margin-bottom: 1.618em;
-}
-
-.constructor {
- font-weight: bold;
-}
-
-table.params,
-table.options {
- text-align: left;
-}
-
-table.params th,
-table.params td,
-table.options th,
-table.options td {
- vertical-align: top;
- padding: 0.5em 1em;
-}
-
-table.params .type,
-table.options .type {
- font-style: italic;
-}
-
-.returns strong {
- font-size: 1.25em;
- margin-right: 1em;
-}
-
-.extra_note {
- margin: 4.618em 0;
-}
-.author {
- font-style: italic;
- margin: -0.618em 0 1.618em 0;
-}
-
-div.next {
- margin: 4.236em 0;
- font-size: 1.2em;
-}
-a.call {
- background: red;
- padding: 0.618em 2.618em;
- color: white;
- text-decoration: none;
- font-weight: bold;
- border-bottom: 1px solid #B40406;
- border-right: 1px solid #B40406;
- text-shadow: 0px -1px -1px #B40406;
- -moz-text-shadow: 0px -1px -1px #B40406;
- -webkit-text-shadow: 0px -1px -1px #B40406;
-}
-a.call:hover {
- background: #DD0003;
-}
-div.next a.call {
- margin-right: 7px;
-}
-div.next a.watch {
- margin-left: 7px;
-}
View
1 docs/docs.md
@@ -1 +0,0 @@
-This page has moved, please visit: [api/index.html](api/index.html)
View
48 docs/download.md
@@ -1,48 +0,0 @@
-# Installing Kanso
-
-
-## Requirements
-
-To use Kanso you'll need to install the latest _stable_ version of node.js,
-which can be found at [http://nodejs.org](http://nodejs.org).
-
-### Why does Kanso need node.js?
-
-Node.js is used for the command-line tools only. With Kanso, the end result is
-a pure [CouchApp](http://couchapp.org) you can host using CouchDB alone.
-Using node to write the associated tools allows us to do some powerful things
-by interpreting the JavaScript of your application.
-
-
-## Install using Git
-
-This is the preferred method of installing Kanso. First, clone the repository
-from GitHub:
-
-<pre><code class="no-highlight">git clone git://github.com/caolan/kanso.git
-cd kanso</code></pre>
-
-Then you'll need to fetch its dependencies using git submodules:
-
- git submodule init
- git submodule update
-
-Next, install using make:
-
- make && sudo make install
-
-
-## Using NPM
-
-If you already have node.js installed, and you're using npm
-(Node Package Manager), then you can install by simply doing the following:
-
-<pre><code class="no-highlight">sudo npm install -g kanso</code></pre>
-
-
-## CouchDB version
-
-As of release 0.0.7, Kanso **only supports CouchDB 1.1.0 or higher**. This is due
-to a number of fixes in CouchDB 1.1.0 for CommonJS modules which make Kanso
-faster and helps to keep the framework code clean. If you're running an older
-version of CouchDB you'll want to upgrade it before continuing.
View
185 docs/guides/adding_pretty_links.md
@@ -1,185 +0,0 @@
-# Adding pretty links.
-
-In the getting started guide the links use the doc_id for reference.
-This can be improved by using a readable link. Since this is also used in bookmarks and by search engines,
-that can improve the site experience.
-For this you need to change the type a bit, add a view, a list and change the rewrites and the blogposts template.
-
-## Change the blogpost type
-
-First you need to add a slug to the blogpost type.
-
-<pre><code class="javascript">exports.blogpost = new Type('blogpost', {
- permissions: {
- add: permissions.hasRole('_admin'),
- update: permissions.loggedIn(),
- remove: permissions.hasRole('_admin')
- },
- fields: {
- created: fields.createdTime(),
- title: fields.string({
- permissions: {
- update: permissions.hasRole('_admin')
- }
- }),
- slug: fields.string({
- permissions: {
- update: permissions.hasRole('_admin')
- }
- }),
- text: fields.string({
- widget: widgets.textarea({cols: 40, rows: 10}),
- permissions: {
- update: permissions.hasRole('_admin')
- }
- }),
- comments: fields.embedList({
- type: exports.comment,
- required: false
- })
- }
-});</code></pre>
-
-Now fill the slug for the blog posts you created earlier.
-You can use a '-' or '_' for seperator instead of a space.
-Also only use lowercase here.
-
-## Add a view by slug
-
-Next you need to create a new view for a lookup by slug.
-This will be used later on to reference the documents instead of by id.
-In the <code>lib/views.js</code> add the following:
-
-<pre><code class="javascript">exports.blogposts_by_slug = {
- map: function (doc) {
- if (doc.type === 'blogpost') {
- emit([doc.slug], null);
- }
- }
-};</code></pre>
-
-You can check your view by opening the following url: <code>http://localhost:5984/myblog/_design/myblog/_rewrite/_db/_design/myblog/_view/blogposts_by_slug</code>
-Don't worry, this is just a peek in the details and won't be used in your application.
-
-## Add the list for the lookup
-
-Now you have added the view, the next step is to replace the show function.
-The show function is used to present a single document and parse that through the presentation template.
-If you want to lookup the document by slug, then you can't use the show, but should use a list instead.
-The list will parse a view and present that using the presentation template.
-In this case the list will only process the first document of the view.
-
-For this add the following code to the <code>lists.js</code> file:
-
-<pre><code>exports.blogpost = function (head, req) {
-
- start({code: 200, headers: {'Content-Type': 'text/html'}});
-
- // fetch row and set blogpost doc.
- var row = [];
- if (row = getRow()) {
- //log('Found row, set the doc.');
- doc = row.doc
- }
- else {
- //log('Doc not found.');
- return {
- title: '404 - Not Found',
- content: templates.render('404.html', req, {})
- };
- }
-
- // generate the markup for the blog post
- var content = templates.render('blogpost.html', req, doc);
-
- return {title: 'MyBlog', content: content};
-
-};</code></pre>
-
-Now that you have added the list, you can remove the blogpost from the <code>shows.js</code>.
-
-## Presentation (and performance)
-
-Because you pass the document to the render function instead of the rows,
-you don't need to change the blogpost.html as you can see above.
-This will also show that you can mix these two types of collecting data
-as long as you are aware of the difference.
-In the case where performance is a huge difference, you can also use the slug as
-the doc_id. But you need to have a lot of documents before you will notice that.
-By then Couchbase Server 3.0 might be released and you can distribute the views over multiple nodes ;-).
-
-## Changing the blogposts view.
-
-Now that the base for referencing the pages by slug is set up you can change the blogpost.html page.
-For this change the <code>blogposts_by_created</code> view to include the slug.
-The slug needs to be added to the view, but since you can't pass multiple values to the view data directly,
-you have to add a level to the data.
-
-<pre><code class="javascript"> exports.blogposts_by_created = {
- map: function (doc) {
- if (doc.type === 'blogpost') {
- emit(doc.created, {"title": doc.title, "slug": doc.slug});
- }
- }
-};</code></pre>
-
-## Changing the blogposts presentation
-
-Since the view above has been changed, you need to make a similar change in the blogposts.html file
-and then replace the id with the new slug.
-You can see that the <code>{value}</code> is moved a level and then the <code>{id}</code> is replaced with a <code>{slug}</code>
-
-<pre><code>&lt;h1&gt;My Blog&lt;/h1&gt;
-
-{?rows}
- &lt;ul&gt;
- {#rows}
- {#value}
- &lt;li&gt;&lt;a href="{baseURL}/blogpost/{slug}"&gt;{title}&lt;/a&gt;&lt;/li&gt;
- {/value}
- {/rows}
- &lt;/ul&gt;
- {:else}
- &lt;p&gt;No blog posts&lt;/p&gt;
-{/rows}</code></pre>
-
-## rewrite
-
-Now add a rewrite to end up at the list instead of the view.
-Since you no longer use the reference by doc_id, remove that line at the same time.
-For this change the <code>lib/rewrites.js</code> file:
-
-<pre><code class="javascript">module.exports = [
- {from: '/static/*', to: 'static/*'},
- {from: '/', to: '_list/homepage/blogposts_by_created'},
- {from: '/add', to: '_update/add_blogpost', method: 'POST'},
- {from: '/add', to: '_show/add_blogpost'},
- {from: '/blogpost/:slug', to: '_list/blogpost/blogposts_by_slug', query: {
- limit: '1',
- key: [':slug'],
- include_docs: 'true'
- }},
- {from: '*', to: '_show/not_found'}
-];</code></pre>
-
-This might need some explanation.
-Above you have added the line for the list and that will use the new view for data.
-To make sure you get the right document from the view, you need to add a query.
-This query will select on the key, select only the first row and also add the doc to the result using
-<code>include_docs: 'true'</code>.
-This addition will make sure that you don't need to put all the data you need in the view.
-Since that will just double the filesize of your application.
-In case you need optimal performance, then you might need to change the view to include the data that you need directly.
-
-In the key selection you can also see the usage of the braces, that's needed to make sure that couchdb get's a proper json structure.
-So don't forget to add that. You will also see this same usage in the view to reflect this call.
-
-## Push and test.
-
-Now push the app to your local database:
-
-<pre><code class="no-highlight">kanso push http://user@localhost:5984/dbname</code></pre>
-
-And test the app.
-
-You can also find these additions to the myblog app on [github](https://github.com/smhoekstra/myblog)
View
895 docs/guides/getting_started.md
@@ -1,895 +0,0 @@
-# Getting started
-
-## Table of Contents
-
-<ul class="toc">
- <li><a href="#installation">Installation</a></li>
- <li><a href="#starting_a_project">Starting a project</a></li>
- <li><a href="#deployment">Deployment</a></li>
- <li><a href="#describing_the_data">Describing the data</a></li>
- <li><a href="#querying_the_data">Querying the data</a></li>
- <li><a href="#rendering_pages">Rendering pages</a></li>
- <li><a href="#users_and_permissions">Users and permissions</a></li>
- <li><a href="#embedded_types">Embedded types</a></li>
- <li><a href="#adding_forms">Adding forms</a></li>
- <li><a href="#going_it_alone">Going it alone</a></li>
-</ul>
-
-
-<h2 id="installation">Installation</h2>
-
-Install the most recent _stable_ version of
-[node](http://nodejs.org/#download), then clone
-[kanso](https://github.com/caolan/kanso) from GitHub.
-Fetch the relevant submodules by doing the following in the cloned directory:
-
-<pre><code class="no-highlight">git submodule init
-git submodule update</code></pre>
-
-You are then ready to install:
-
-<pre><code class="no-highlight">make && sudo make install</code></pre>
-
-
-### Using NPM
-
-If you already have node.js installed, and you're using npm
-(Node Package Manager), then you can install by simply doing the following:
-
-<pre><code class="no-highlight">sudo npm install -g kanso</code></pre>
-
-
-### Why does Kanso need node.js?
-
-Node.js is used for the command-line tools only. With Kanso, the end result is
-a pure [CouchApp](http://couchapp.org) you can host using CouchDB alone.
-Using node to write the associated tools allows us to do some powerful things
-by interpreting the JavaScript of your application.
-
-
-### CouchDB version
-
-As of release 0.0.7, Kanso **only supports CouchDB 1.1.0 or higher**. This is due
-to a number of fixes in CouchDB 1.1.0 for CommonJS modules which make Kanso
-faster and helps to keep the framework code clean. If you're running an older
-version of CouchDB you'll want to upgrade it before continuing with this guide.
-
-
-<h2 id="starting_a_project">Starting a project</h2>
-
-For this tutorial we'll be making a fairly typical blog, with posts,
-comments and users. Some familiarity with CouchDB is expected, but I'll
-try to explain the concepts as we go.
-
-To create a new project skeleton, enter the following command:
-
-<pre><code class="no-highlight">kanso create myblog</code></pre>
-
-This creates a number of files and directories representing a basic
-project structure. We'll look more closely at the generated files later,
-but for a brief overview, the directories fall into the following categories:
-
-<pre><code class="no-highlight">myblog
- |- lib CommonJS modules which define your app
- |- static Static files such as jQuery and CSS
- |- templates HTML templates used by the app
- |- kanso.json Project configuration</code></pre>
-
-This structure is a merely a guide and you are free to place files
-wherever it makes sense, provided you update the configuration settings in
-kanso.json.
-
-
-<h2 id="deployment">Deployment</h2>
-
-It might seem early in the tutorial to start talking about deployment, but
-since your app needs to be hosted by CouchDB to run, we're going to cover it
-now.
-
-The first thing to mention is that <em>Kanso apps are just CouchApps</em>. This
-means they can be hosted directly from your CouchDB instance, without any additional
-tier or services.
-
-For the rest of this tutorial we're going to assume you have CouchDB running
-on <a href="http://localhost:5984">http://localhost:5984</a>
-(the default settings). If you haven't got a local copy of CouchDB running,
-go do that now.
-
-
-### Pushing
-
-The <code>push</code> command uploads your app to a CouchDB database. You can find
-help on any of the commands used in this tutorial by typing <code>kanso help</code>.
-
-Let's push the new project to your CouchDB instance, and check that everthing
-works. To do this, enter the following command from your project directory:
-
-<pre><code class="no-highlight">kanso push http://localhost:5984/myblog</code></pre>
-
-<img src="images/deployment1.png" alt="pushing myblog" />
-
-This creates a new database called 'myblog' and uploads your app to it.
-If you now visit [http://localhost:5984/myblog/\_design/myblog/\_rewrite/](http://localhost:5984/myblog/_design/myblog/_rewrite/),
-you should see the following welcome page:
-
-<img src="images/deployment2.png" alt="pushing myblog" />
-
-This is the location to use when testing your app. Don't worry about the
-ugly URL, we can fix this later using
-<a href="http://wiki.apache.org/couchdb/Virtual_Hosts">virtual hosts</a>.
-
-
-<h2 id="describing_the_data">Describing the data</h2>
-
-Now we've got our new project up and running, let's think about the data
-structures this project requires. Because CouchDB is schemaless, we can make
-lots of changes as we go without having to worry about doing ALTER TABLE.
-
-The only problem is, it can quickly get complicated when manually validating
-documents and checking permissions. Thankfully, kanso provides a powerful
-document validation and permissions tool in the form of Type definitions.
-
-
-### Creating types
-
-In the skeleton project we created earlier, there's a file called
-<code>lib/types.js</code>. This is the conventional place to export types.
-Remember, the files in the <code>lib</code> directory are
-<a href="http://wiki.commonjs.org/wiki/Modules/1.1.1">CommonJS modules</a>.
-This means anything added to the <code>exports</code> object in this file is
-made visible to other modules.
-
-Let's create a type for describing blog posts. Add the following to
-<code>lib/types.js</code>.
-
-<pre><code class="javascript">var Type = require('kanso/types').Type,
- fields = require('kanso/fields'),
- widgets = require('kanso/widgets');
-
-
-exports.blogpost = new Type('blogpost', {
- fields: {
- created: fields.createdTime(),
- title: fields.string(),
- text: fields.string({
- widget: widgets.textarea({cols: 40, rows: 10})
- })
- }
-});
-</code></pre>
-
-That should be fairly self-explanatory.
-The first argument to the <code>Type</code> constructor is the type name
-which must be unique, the second argument is an object describing fields
-and other options.
-
-Now we've added a basic type, let's push these changes to the server:
-
-<img src="images/deployment1.png" alt="pushing myblog" />
-
-<h3>The Admin App</h3>
-
-As a useful way to play with data types, kanso provides a basic admin app.
-You can push this app to the same database you pushed the 'myblog' project to
-by using the following command:
-
-<pre><code class="no-highlight">kanso pushadmin http://localhost:5984/myblog</code></pre>
-
-<img src="images/describing_the_data1.png" alt="push the admin app" />
-
-If you now visit
-[http://localhost:5984/myblog/\_design/admin/\_rewrite/](http://localhost:5984/myblog/_design/admin/_rewrite/)
-you should see the following page:
-
-<img src="images/describing_the_data2.png" alt="admin app - apps list" />
-
-Because you can have multiple apps running on a single database you will be
-presented with a list of available applications. Click the 'myblog' app.
-
-<img src="images/describing_the_data3.png" alt="admin app - app overview" />
-
-Here, we are presented with a list of types. Currently, we just have the one
-'blogpost' type. Clicking on this will show you a list of existing blog posts:
-
-<img src="images/describing_the_data4.png" alt="admin app - type list" />
-
-Of course, we haven't added any yet. Let's try adding one now. Click the
-'Add blogpost' link.
-
-<img src="images/describing_the_data5.png" alt="admin app - adding a type" />
-
-As you can see, the admin app has read the field settings we defined earlier
-and presented us with a sensible form for adding blog posts. You'll notice the
-<code>createdTime</code> field is missing. This is because it's automatically
-populated with the current time.
-
-Create a blog post by filling out the form with some test data and clicking
-the create button.
-
-<img src="images/describing_the_data6.png" alt="admin app - type added" />
-
-As you can see, kanso has automatically populated the 'type' and 'created'
-fields. The 'type' field is automatically added, and reserved for use by
-kanso so we can identify the definition to validate against.
-
-
-<h2 id="querying_the_data">Querying the data</h2>
-
-In CouchDB, you query your documents using map / reduce functions called
-'views'. If you've never written a CouchDB view before, you can read up on
-it in the <a href="http://guide.couchdb.org/draft/views.html">Definitive Guide</a>.
-
-Adding a view to a kanso app couldn't be simpler. Just open up the
-<code>lib/views.js</code> file and add the following example view:
-
-<pre><code class="javascript">exports.blogposts_by_created = {
- map: function (doc) {
- if (doc.type === 'blogpost') {
- emit(doc.created, doc.title);
- }
- }
-};</code></pre>
-
-**Push the app**, then revisit the admin interface at
-[http://localhost:5984/myblog/\_design/admin/\_rewrite/myblog](http://localhost:5984/myblog/_design/admin/_rewrite/myblog)
- you'll see the view we created now appears:
-
-<img src="images/querying_the_data1.png" alt="admin app - now showing a view" />
-
-Clicking on the view shows the current results. You should see the document
-we created earlier keyed by its created at timestamp:
-
-<img src="images/querying_the_data2.png" alt="admin app - view results" />
-
-Clicking on a row will take you to its corresponding document.
-
-
-<h2 id="rendering_pages">Rendering pages</h2>
-
-In CouchDB, rendering custom representations of your data is achieved using
-[List and Show functions](http://wiki.apache.org/couchdb/Formatting_with_Show_and_List).
-Kanso is no different, although it will also run these functions client-side
-wherever possible, giving users a more responsive interface while also
-providing a fallback for search-engines.
-
-Writing code in this way can be very efficient and shows where you might be
-missing non-js support. However, at first it might seem a little odd. Work
-through the examples and bear in mind that all list and show functions could
-be run client-side or server-side at any time!
-
-
-### List functions
-
-List functions format the results of a view. We're going to use a list
-function to show a list of blog posts on the homepage, ordered by the
-date they were created. Add the following to <code>lib/lists.js</code>:
-
-<pre><code class="javascript">var templates = require('kanso/templates');
-
-
-exports.homepage = function (head, req) {
-
- start({code: 200, headers: {'Content-Type': 'text/html'}});
-
- // fetch all the rows
- var row, rows = [];
- while (row = getRow()) {
- rows.push(row);
- }
-
- // generate the markup for a list of blog posts
- var content = templates.render('blogposts.html', req, {
- rows: rows
- });
-
- if (req.client) {
- // being run client-side, update the current page
- $('#content').html(content);
- document.title = 'MyBlog';
- }
- else {
- // being run server-side, return a complete rendered page
- return templates.render('base.html', req, {
- content: content,
- title: 'MyBlog'
- });
- }
-
-};</code></pre>
-
-For simplicity, we're pre-fetching all the rows at once, on large views
-you'll want to return content for each row before fetching the next.
-
-You'll notice this list function detects if its running client-side by
-checking the client property on the request object. When run client-side,
-it will update the DOM instead of returning a new HTML document.
-
-Since this is such a common pattern when writing Kanso apps, you can use a
-convenient short-hand of returning an object with title and content properties,
-Kanso will then do the appropriate thing for the current environment:
-
-<pre><code class="javascript">var templates = require('kanso/templates');
-
-
-exports.homepage = function (head, req) {
-
- start({code: 200, headers: {'Content-Type': 'text/html'}});
-
- // fetch all the rows
- var row, rows = [];
- while (row = getRow()) {
- rows.push(row);
- }
-
- // generate the markup for a list of blog posts
- var content = templates.render('blogposts.html', req, {
- rows: rows
- });
-
- return {title: 'MyBlog', content: content};
-
-};</code></pre>
-
-We referenced two templates in the first example: 'base.html' and
-'blogposts.html'. The first was created as part of the project skeleton,
-but the second we need to create ourselves.
-Create a new template at <code>templates/blogposts.html</code> with the
-following content:
-
-<pre><code>&lt;h1&gt;My Blog&lt;/h1&gt;
-
-{?rows}
- &lt;ul&gt;
- {#rows}
- &lt;li&gt;&lt;a href="{baseURL}/{id}"&gt;{value}&lt;/a&gt;&lt;/li&gt;
- {/rows}
- &lt;/ul&gt;
- {:else}
- &lt;p&gt;No blog posts&lt;/p&gt;
-{/rows}</code></pre>
-
-For more information on the template format used by kanso, see the
-<a href="http://akdubya.github.com/dustjs/">Dust website</a>. Basically,
-this will render a list of blog post titles.
-
-One thing worth noting is the use of {baseURL} in the template. This variable
-is automatically made available to templates by kanso, and refers to the
-current 'root' level URL. That would currently mean
-'/myblog/\_design/myblog/\_rewrite', but that may change if you use
-a virtual host in the future. For now, be sure to prefix all
-<strong>application</strong> URLs with the base URL.
-
-
-### Rewrites
-
-Next, we need to hook this view up to a URL. CouchDB uses
-<a href="http://wiki.apache.org/couchdb/Rewriting_urls">rewrites</a>
-to point arbitrary URLs at CouchDB functions and again, kanso is no exception.
-Let's change the root URL from the welcome page to our new list of blog posts.
-Edit <code>lib/rewrites.js</code> to look like the following:
-
-<pre><code class="javascript">module.exports = [
- {from: '/static/*', to: 'static/*'},
- {from: '/', to: '_list/homepage/blogposts_by_created'},
- {from: '*', to: '_show/not_found'}
-];</code></pre>
-
-This uses our new list function in combination with the view query we created
-earlier. <strong>Push the app</strong>, then open it in your browser:
-<a href="http://localhost:5984/myblog/_design/myblog/_rewrite/">http://localhost:5984/myblog/\_design/myblog/\_rewrite/</a>.
-
-<img src="images/rendering_pages1.png" alt="The new homepage" />
-
-Of course, clicking on the blog post will give a 404 Not Found error, since
-we've not implemented the view for a single blog post yet. Let's do that now.
-
-
-### Show functions
-
-Show functions are like list functions, only they display a single document
-rather than the results of a view. We're going to create a show function
-for our blog posts, open up <code>lib/shows.js</code> and take a look.
-Currently, it will contain the welcome page we recently unhooked from our
-rewrite rules. You can now remove that, and replace it with the following:
-
-<pre><code class="javascript">var templates = require('kanso/templates');
-
-
-exports.blogpost = function (doc, req) {
- return {
- title: doc.title,
- content: templates.render('blogpost.html', req, doc)
- };
-};</code></pre>
-
-You'll notice we're using the short-hand method used in the list function
-earlier, by returning an object with title and content properties.
-Next we need to add the <code>blogpost.html</code> template:
-
-<pre><code>&lt;h1&gt;{title}&lt;/h1&gt;
-
-&lt;p&gt;{text}&lt;/p&gt;</code></pre>
-
-And update the rewrite rules:
-
-<pre><code class="javascript">module.exports = [
- {from: '/static/*', to: 'static/*'},
- {from: '/', to: '_list/homepage/blogposts_by_created'},
- {from: '/:id', to: '_show/blogpost/:id'},
- {from: '*', to: '_show/not_found'}
-];</code></pre>
-
-__Push the app__ and try clicking the blog post link now.
-You should see a page similar to the following:
-
-<img src="images/rendering_pages2.png" alt="Single blog post page" />
-
-If you check your JavaScript console in Chrome or Firebug, you should notice
-that clicking on the blog post link on the homepage causes the next request to
-be handled client-side:
-
-<img src="images/rendering_pages3.png" alt="Single blog post page" />
-
-However, turning off JavaScript should provide you with the same experience.
-Just with your CouchDB server doing the work of rending pages.
-
-As your sites become more complex you may want to make additional requests
-to the server when a list or show function is run client-side. This means
-we could show all blogposts with the same tag in a side-bar, or perhaps show
-links to related posts. These features would then be available only to people
-with JavaScript turned on, and browsers without JavaScript will fall-back to
-the core content you see now.
-
-
-<h2 id="users_and_permissions">Users and permissions</h2>
-
-Currently, anyone can add a blog post to the site, even if they're not
-logged-in. This probably isn't ideal, so let's look at protecting the ability
-to add blog posts.
-
-CouchDB provides its own users and roles system, which defines who has
-access to which database. Kanso can also use this to authorize or
-reject changes to the database on a document-level. The great thing about
-this is that our authentication system is already set-up and ready to go!
-
-
-### validate\_doc\_update
-
-CouchDB authorizes changes to a document using the
-<a href="http://guide.couchdb.org/draft/validation.html">validate\_doc\_update</a>
-function defined in your app. This function accepts the new document,
-the previous version of the document (if available), and the user's details as
-arguments and throws an Error if the update should be rejected.
-
-Kanso also supports this system as the underlying way to authorize
-changes. However, this method is quite low-level. The simplicity of this approach
-is both its attraction and its downfall. With complex applications comprising of
-many types, each with it's own validation and authorization requirements, your
-validate\_doc\_update function quickly becomes unwieldy.
-
-Kanso allows you to mix-and-match, by providing powerful tools
-on a type or even field-level, while still
-supporting the simplicity of the validate\_doc\_update approach.
-
-If you look at <code>lib/validate.js</code> in the project skeleton we
-generated, you'll notice kanso has already added a
-validate\_doc\_update function which just calls the kanso function
-<code>types.validate_doc_update</code>. This runs the permissions
-defined in your app's type and field definitions, so it's
-important not to remove this line if you're using this feature.
-
-<pre><code class="javascript">var types = require('kanso/types'),
-app_types = require('./types');
-
-
-module.exports = function (newDoc, oldDoc, userCtx) {
- types.validate_doc_update(app_types, newDoc, oldDoc, userCtx);
-};</code></pre>
-
-If you also want to add permissions directly into this validate\_doc\_update
-function, you can do that too.
-
-
-### End of the admin-party
-
-By default, CouchDB has no users and everyone has the special "\_admin" role.
-This makes playing around with CouchDB (and Kanso) easy when you first
-install, but obviously isn't very secure. If you haven't done so already,
-put an end to the
-[admin party](http://guide.couchdb.org/draft/security.html#party)
-by visiting the [Futon admin interface](http://localhost:5984/_utils/)
-and adding a new admin user:
-
-<img src="images/users_and_permissions2.png" alt="fixing admin party" />
-
-
-### Type-level permissions
-
-Currently, we want to protect the ability to add, remove and update the
-blogpost type. To do this we simply update our type definition to include a
-permissions section:
-
-<pre><code class="javascript">var Type = require('kanso/types').Type,
- fields = require('kanso/fields'),
- widgets = require('kanso/widgets'),
- permissions = require('kanso/permissions');
-
-
-exports.blogpost = new Type('blogpost', {
- permissions: {
- add: permissions.hasRole('_admin'),
- update: permissions.hasRole('_admin'),
- remove: permissions.hasRole('_admin')
- },
- fields: {
- created: fields.createdTime(),
- title: fields.string(),
- text: fields.string({
- widget: widgets.textarea({cols: 40, rows: 10})
- })
- }
-});</code></pre>
-
-__Push the app__ (you may now be prompted for a username and
-password), if you then __logout__ and go to the [blogposts page](http://localhost:5984/myblog/_design/admin/_rewrite/myblog/types/blogpost)
-in the admin app. When you try to delete a blogpost, you should see the following:
-
-<img src="images/users_and_permissions1.png" alt="admin app - role required" />
-
-The same should happen when editing or adding new blog posts. Logging back in
-as an admin user should allow you to complete all of these operations again.
-
-A short note on pushing as a user: if you include a username or both a username
-and password in the database URL, these credentials will be used to push the app.
-
-<pre><code class="no-highlight">kanso push http://user:password@localhost:5984/dbname
-kanso push http://user@localhost:5984/dbname</code></pre>
-
-You may not want to expose the password in your shell history so I'd
-recommend just putting the username in the URL, and kanso will know to
-prompt you for a password before attempting to push.
-
-
-<h2 id="embedded_types">Embedded types</h2>
-
-Now we move on to one of Kanso's more advanced features: Embedded types.
-
-Beacuse it often makes sense to 'de-normalize' your data in CouchDB you'll
-often find yourself embedding one document directly into another.
-Any Type system that's going to work properly will also need to support this
-technique.
-
-This can get quite tricky once you consider the permissions and validation
-requirements involved. Consider a situation where you'd like to embed
-comments on a blog post document. One user may be allowed to edit a blogpost,
-while another may only be allowed to comment on it. However, both users need to be able to
-update the document itself, since the comments are embedded within the blogpost.
-
-Using embedded types, Kanso helps you handle these situations much more easily
-and define exactly which parts of a document can be edited by a user.
-
-Whether or not embedding one type within another document is a good idea
-often depends on the specific circumstances. Comments on blog posts for
-example, may be best added separately to the blog post document if you
-expect there to be a lot of commenting activity, otherwise you run the
-risk of conflicts, and CouchDB will have to write the whole list of
-comments every time you add a new one.
-
-For the sake of this example however, we're going to assume comments are
-very infrequent and that we prefer to make them available on the blog post
-document itself.
-
-
-### Creating the comment type
-
-Before we can embed comments within the blogpost type, we need to define
-the comment type itself. Add the following to <code>lib/types.js</code>,
-__BEFORE__ the blogpost type:
-
-<pre><code class="javascript">exports.comment = new Type('comment', {
- permissions: {
- add: permissions.loggedIn(),
- update: permissions.usernameMatchesField('creator'),
- remove: permissions.usernameMatchesField('creator')
- },
- fields: {
- creator: fields.creator(),
- text: fields.string({
- widget: widgets.textarea({cols: 40, rows: 10})
- })
- }
-});</code></pre>
-
-You'll notice we're using some new permissions for this type,
-first <code>loggedIn</code> simply checks the user is logged in (not
-anonymous) before being allowed to add a comment. Then,
-<code>usernameMatchesField</code> checks that the current user is the same
-as the user defined in the creator field. This stops users from editing or
-deleting other users comments.
-
-The <code>creator</code> field is also new, and set's itself to the current
-user's name when a new comment is added. After creation, this field becomes
-uneditable.
-
-
-### Embedding a type
-
-To embed a list of comments in a blogpost, update the blogpost type definition
-to include the following comments field:
-
-<pre><code type="javascript">exports.blogpost = new Type('blogpost', {
- permissions: {
- add: permissions.hasRole('_admin'),
- update: permissions.loggedIn(),
- remove: permissions.hasRole('_admin')
- },
- fields: {
- created: fields.createdTime(),
- title: fields.string({
- permissions: {
- update: permissions.hasRole('_admin')
- }
- }),
- text: fields.string({
- widget: widgets.textarea({cols: 40, rows: 10}),
- permissions: {
- update: permissions.hasRole('_admin')
- }
- }),
- comments: fields.embedList({
- type: exports.comment,
- required: false
- })
- }
-});</code></pre>
-
-You'll notice we've changed the permissions a bit. Previously the type had add,
-update and remove permissions at the top-level which all checked for the '\_admin'
-role. That meant you couldn't create, delete or make any changes to a blogpost
-without being a CouchDB admin.
-
-Since we're embedding comments within the blogpost document, we need to give update
-access to any logged-in user, so they can add comments directly into it. However,
-we still don't want them to change the title or text of the blogpost, so we've
-added field-level permissions to protect those.
-
-The <code>embedList</code> field will use the permissions of the comment Type
-definition, meaning any logged-in user can add a comment, but you can only edit or
-delete comments you created.
-
-This is an example of the flexibility of the Kanso Types and permissions system.
-De-normalizing your data often means you'll need some fine-grained permissions
-to protect specific parts of a document. Because Kanso was written with CouchDB in
-mind, we can design specifically for these kinds of problems.
-
-__Push the app__, and visit the admin page for adding blogposts:
-<a href="http://localhost:5984/myblog/_design/admin/_rewrite/myblog/types/blogpost/add">http://localhost:5984/myblog/\_design/admin/\_rewrite/myblog/types/blogpost/add</a>
-
-<img src="images/embedded_types1.png" alt="new comments button" />
-
-There is now an add a comment button, clicking on this will open a modal
-dialog for embedding a comment.
-
-<img src="images/embedded_types2.png" alt="adding comments" />
-
-Continue to add a new blog post with some example comments, rembering that
-you'll need to be logged in as an admin user to add new blog posts.
-
-<img src="images/embedded_types3.png" alt="adding new blog post with comments" />
-
-
-### Updating templates
-
-Let's update the templates to handle the newly embedded comments.
-Edit <code>templates/blogpost.html</code> to look like the following:
-
-<pre><code>&lt;h1&gt;{title}&lt;/h1&gt;
-
-&lt;p&gt;{text}&lt;/p&gt;
-
-{?comments}
- &lt;h2&gt;Comments&lt;/h2&gt;
-
- &lt;ul&gt;
- {#comments}
- &lt;li&gt;
- &lt;strong&gt;{creator} says: &lt;/strong&gt;
- {text}
- &lt;/li&gt;
- {/comments}
- &lt;/ul&gt;
-{/comments}</code></pre>
-
-__Push the app__, and visit it's home page:
-<a href="http://localhost:5984/myblog/_design/myblog/_rewrite/">http://localhost:5984/myblog/\_design/myblog/\_rewrite/</a>
-
-<img src="images/embedded_types4.png" alt="app homepage" />
-
-You should see the new blog post you created listed on the homepage.
-Clicking on it should show it using the new template, complete with the
-comments you added earlier:
-
-<img src="images/embedded_types5.png" alt="showing blog post with comments" />
-
-
-<h2 id="adding_forms">Adding forms</h2>
-
-So far, we've been using the Kanso admin app to add documents, but let's
-assume we want to write our own interface and forms. Kanso provides some useful
-form-rendering code (also used by the admin app) which allows you to quickly
-and easily create forms based on Type definitions.
-
-First, we're going to create a form for adding new blog posts.
-Add the following to the end of <code>templates/blogposts.html</code>:
-
- <p><a href="{baseURL}/add">Add new</a></p>
-
-Then update <code>lib/rewrites.js</code> to look like this:
-
-<pre><code class="javascript">module.exports = [
- {from: '/static/*', to: 'static/*'},
- {from: '/', to: '_list/homepage/blogposts_by_created'},
- {from: '/add', to: '_show/add_blogpost'},
- {from: '/:id', to: '_show/blogpost/:id'},
- {from: '*', to: '_show/not_found'}
-];</code></pre>
-
-Let's create a new show function to display the form. Add the following to
-<code>lib/shows.js</code>:
-
-<pre><code class="javascript">exports.add_blogpost = function (doc, req) {
- // render the markup for a blog post form
- var content = templates.render('blogpost_form.html', req, {
- form_title: 'Add new blogpost'
- });
-
- return {title: 'Add new blogpost', content: content};
-};</code></pre>
-
-And add a new template for the page at <code>templates/blogpost_form.html</code>.
-
- <h1>{form_title}</h1>
-
-__Push the app__, and visit it's home page:
-<a href="http://localhost:5984/myblog/_design/myblog/_rewrite/">http://localhost:5984/myblog/\_design/myblog/\_rewrite/</a>
-
-<img src="images/adding_forms1.png" alt="Check the add new link" />
-
-Clicking 'Add new' should display the following page:
-
-<img src="images/adding_forms2.png" alt="Check the add new rewrite" />
-
-So far so good. Let's add a form to the page. In <code>lib/shows.js</code>
-add the following new modules to the requires at the top:
-
-<pre><code class="javascript">var templates = require('kanso/templates'),
- forms = require('kanso/forms'),
- types = require('./types');</code></pre>
-
-Then update <code>exports.add_blogpost</code> to look like this:
-
-<pre><code class="javascript">exports.add_blogpost = function (doc, req) {
- var form = new forms.Form(types.blogpost, null, {
- exclude: ['created', 'comments']
- });
-
- // render the markup for a blog post form
- var content = templates.render('blogpost_form.html', req, {
- form_title: 'Add new blogpost',
- form: form.toHTML(req)
- });
-
- return {title: 'Add new blogpost', content: content};
-};</code></pre>
-
-You'll notice we're using the new <code>forms</code> module to construct a
-form object using the blogpost Type definition we created earlier. We are
-excluding the created and comments fields, and just displaying the fields we need.
-
-Next we render the form as HTML using <code>form.toHTML(req)</code>, and pass it
-to the template. In <code>templates/blogpost_form.html</code> we need to add the
-following:
-
- <h1>{form_title}</h1>
-
- <form method="POST" action="">
- <table>
- {form|s}
- </table>
- <input type="submit" value="Add" />
- </form>
-
-__Push the app__, and visit the add blogpost page:
-<a href="http://localhost:5984/myblog/_design/myblog/_rewrite/add">http://localhost:5984/myblog/\_design/myblog/\_rewrite/add</a>
-
-<img src="images/adding_forms3.png" alt="Viewing the new form" />
-
-Great, but if you try submitting the form, nothing happens! Thats because we need
-to add an <a href="http://wiki.apache.org/couchdb/Document_Update_Handlers">update function</a> for saving changes to a document.
-
-<h3>Update functions</h3>
-
-Add the following to <code>lib/updates.js</code>:
-
-<pre><code class="javascript">var templates = require('kanso/templates'),
- forms = require('kanso/forms'),
- utils = require('kanso/utils'),
- types = require('./types');
-
-
-exports.add_blogpost = function (doc, req) {
- var form = new forms.Form(types.blogpost, null, {
- exclude: ['created', 'comments']
- });
-
- // parse the request data and check validation and permission functions
- form.validate(req);
-
- if (form.isValid()) {
- // the form is valid, save the document and redirect to the new page
- return [form.values, utils.redirect(req, '/' + form.values._id)];
- }
- else {
- // the form is not valid, so render it again with error messages
- var content = templates.render('blogpost_form.html', req, {
- form_title: 'Add new blogpost',
- form: form.toHTML(req)
- });
- // return null as the first argument so the document isn't saved
- return [null, {content: content, title: 'Add new blogpost'}];
- }
-};</code></pre>
-
-Update functions expect an array as the return value, the first argument is the
-document to create or update, the second argument is the response to send to the
-client. This function should look similar to the show function we created for
-displaying the add blog post form, only with the addition of form validation.
-
-After creating the form, we call <code>form.validate</code> with the request
-object. This will populate the form with the request data and any relevant error
-messages if the data is invalid.
-
-We then check the validity of the form and either reject or accept the new document.
-Note, that this is mostly to provide a nice response to the user and is not
-required for security. Ultimately, an unacceptable document would be rejected by
-validate\_doc\_update, even if this function were to accept it.
-
-Next, we need to hook this update function up to a URL using rewrites. Edit
-<code>lib/rewrites.js</code> to look like the following:
-
-<pre><code class="javascript">module.exports = [
- {from: '/static/*', to: 'static/*'},
- {from: '/', to: '_list/homepage/blogposts_by_created'},
- {from: '/add', to: '_update/add_blogpost', method: 'POST'},
- {from: '/add', to: '_show/add_blogpost'},
- {from: '/:id', to: '_show/blogpost/:id'},
- {from: '*', to: '_show/not_found'}
-];</code></pre>
-
-We've added a new rewrite for <code>/add</code>, only this time we specify a HTTP
-method to match. This means when we do <code>GET /add</code> we'll run the show
-function, and when we do <code>POST /add</code> we'll run the update function.
-
-Let's try submitting the form now. Be sure to login as an admin user using either
-futon or the Kanso admin app (on the same hostname you're using with the app, no
-good logging in on localhost if your accessing the app on 127.0.0.1).
-
-__Push the app__, and visit the add blogpost page:
-<a href="http://localhost:5984/myblog/_design/myblog/_rewrite/add">http://localhost:5984/myblog/\_design/myblog/\_rewrite/add</a>
-
-After submitting the form you should be redirected to the list of blogposts and
-you should see your new blogpost added to the end.
-
-
-<h2 id="going_it_alone">Going it alone</h2>
-
-Congratulations on finishing your first (basic) Kanso app! It's time for you
-to strike out on your own, and explore the possibilities of a whole new way
-to write web-apps. If you create something cool, please share it on the
-<a href="https://github.com/caolan/kanso/wiki/Sites-using-Kanso">Kanso wiki</a>.
-
-You can <strong>report issues</strong> using the
-<a href="https://github.com/caolan/kanso/issues">GitHub issues page</a>.
-
-Don't forget to check out the <a href="../api/index.html">Docs</a> and other
-available <a href="index.html">Guides</a>.
View
25 docs/guides/how_kanso_processes_requests.md
@@ -1,25 +0,0 @@
-# How Kanso Processes Requests
-
-Kanso makes it easy to re-use code between the client and server wherever
-possible, but to be used effectively you must understand how requests are
-processed.
-
-<img src="images/request_flow.png" alt="Kanso request flow" class="diagram" />
-
-1. The browser makes a request to <code>/foo</code>
-2. CouchDB looks up the show / list / update function that <code>/foo</code>
- rewrites to.
-3. This function is executed server-side by CouchDB, rendering a HTML page
- containing the document or view.
-4. The browser receives the pre-rendered version of the page from CouchDB.
- If JavaScript is not enabled, the process stops here until a link is
- clicked or a form is submitted, causing a new request to the CouchDB server.
-5. If JavaScript is enabled, Kanso looks up the show / list / update function
- for <code>/foo</code> and executes it again, client-side. This allows the
- application developer to enhance the page by making additional requests and
- generally doing things CouchDB wouldn't normally allow.
-6. Kanso binds event handlers for every form submit and link click
- (even ones yet to exist). If the target URL is part of your app, Kanso
- will look up the new show / list / update function and execute it client-side
- without a round-trip to the server (apart from fetching relevent documents
- or views).
View
BIN docs/guides/images/adding_forms1.png
Deleted file not rendered
View
BIN docs/guides/images/adding_forms2.png
Deleted file not rendered
View
BIN docs/guides/images/adding_forms3.png
Deleted file not rendered
View
BIN docs/guides/images/deployment1.png
Deleted file not rendered
View
BIN docs/guides/images/deployment2.png
Deleted file not rendered
View
BIN docs/guides/images/describing_the_data1.png
Deleted file not rendered
View
BIN docs/guides/images/describing_the_data2.png
Deleted file not rendered
View
BIN docs/guides/images/describing_the_data3.png
Deleted file not rendered
View
BIN docs/guides/images/describing_the_data4.png
Deleted file not rendered
View
BIN docs/guides/images/describing_the_data5.png
Deleted file not rendered
View
BIN docs/guides/images/describing_the_data6.png
Deleted file not rendered
View
BIN docs/guides/images/embedded_types1.png
Deleted file not rendered
View
BIN docs/guides/images/embedded_types2.png
Deleted file not rendered
View
BIN docs/guides/images/embedded_types3.png
Deleted file not rendered
View
BIN docs/guides/images/embedded_types4.png
Deleted file not rendered
View
BIN docs/guides/images/embedded_types5.png
Deleted file not rendered
View
BIN docs/guides/images/querying_the_data1.png
Deleted file not rendered
View
BIN docs/guides/images/querying_the_data2.png
Deleted file not rendered
View
BIN docs/guides/images/rendering_pages1.png
Deleted file not rendered
View
BIN docs/guides/images/rendering_pages2.png
Deleted file not rendered
View
BIN docs/guides/images/rendering_pages3.png
Deleted file not rendered
View
BIN docs/guides/images/request_flow.png
Deleted file not rendered
View
2,689 docs/guides/images/request_flow.svg
@@ -1,2689 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<!-- Created with Inkscape (http://www.inkscape.org/) -->
-
-<svg
- xmlns:dc="http://purl.org/dc/elements/1.1/"
- xmlns:cc="http://creativecommons.org/ns#"
- xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
- xmlns:svg="http://www.w3.org/2000/svg"
- xmlns="http://www.w3.org/2000/svg"
- xmlns:xlink="http://www.w3.org/1999/xlink"
- xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
- xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
- width="744.09448819"
- height="1052.3622047"
- id="svg2"
- version="1.1"
- inkscape:version="0.48.0 r9654"
- sodipodi:docname="request_flow.svg">
- <defs
- id="defs4">
- <marker
- inkscape:stockid="TriangleOutM"
- orient="auto"
- refY="0.0"
- refX="0.0"
- id="TriangleOutM"
- style="overflow:visible">
- <path
- id="path5014"
- d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
- style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none"
- transform="scale(0.4)" />
- </marker>
- <marker
- inkscape:stockid="EmptyTriangleOutM"
- orient="auto"
- refY="0.0"
- refX="0.0"
- id="EmptyTriangleOutM"
- style="overflow:visible">
- <path
- id="path5032"
- d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
- style="fill-rule:evenodd;fill:#FFFFFF;stroke:#000000;stroke-width:1.0pt;marker-start:none"
- transform="scale(0.4) translate(-4.5,0)" />
- </marker>
- <marker
- inkscape:stockid="TriangleOutS"
- orient="auto"
- refY="0.0"
- refX="0.0"
- id="TriangleOutS"
- style="overflow:visible">
- <path
- id="path5017"
- d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
- style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none"
- transform="scale(0.2)" />
- </marker>
- <marker
- inkscape:stockid="TriangleOutL"
- orient="auto"
- refY="0.0"
- refX="0.0"
- id="TriangleOutL"
- style="overflow:visible">
- <path
- id="path5011"
- d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
- style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none"
- transform="scale(0.8)" />
- </marker>
- <marker
- inkscape:stockid="Arrow2Lend"
- orient="auto"
- refY="0.0"
- refX="0.0"
- id="Arrow2Lend"
- style="overflow:visible;">
- <path
- id="path4889"
- style="font-size:12.0;fill-rule:evenodd;stroke-width:0.62500000;stroke-linejoin:round;"
- d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
- transform="scale(1.1) rotate(180) translate(1,0)" />
- </marker>
- <marker
- inkscape:stockid="Arrow1Lend"
- orient="auto"
- refY="0.0"
- refX="0.0"
- id="Arrow1Lend"
- style="overflow:visible;">
- <path
- id="path4871"
- d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
- style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none;"
- transform="scale(0.8) rotate(180) translate(12.5,0)" />
- </marker>
- <linearGradient
- gradientTransform="matrix(1,0,0,1.05,0.71428573,0.27489207)"
- gradientUnits="userSpaceOnUse"
- xlink:href="#linearGradient3247"
- id="linearGradient3253"
- y2="24.533754"
- x2="155.71428"
- y1="34.03484"
- x1="155.71428" />
- <linearGradient
- id="linearGradient3247">
- <stop
- offset="0"
- style="stop-color:#e1e1e1;stop-opacity:1"
- id="stop3249" />
- <stop
- offset="0.25"
- style="stop-color:#f0f0f0;stop-opacity:1"
- id="stop3809" />
- <stop
- offset="0.5"
- style="stop-color:#fafafa;stop-opacity:1"
- id="stop3807" />
- <stop
- offset="1"
- style="stop-color:#ffffff;stop-opacity:1"
- id="stop3251" />
- </linearGradient>
- <linearGradient
- gradientTransform="matrix(1,0,0,1.05,0.71428573,0.27489207)"
- gradientUnits="userSpaceOnUse"
- xlink:href="#linearGradient3247-6"
- id="linearGradient3253-1"
- y2="24.533754"
- x2="155.71428"
- y1="34.03484"
- x1="155.71428" />
- <linearGradient
- id="linearGradient3247-6">
- <stop
- offset="0"
- style="stop-color:#e1e1e1;stop-opacity:1"
- id="stop3249-3" />
- <stop
- offset="0.25"
- style="stop-color:#f0f0f0;stop-opacity:1"
- id="stop3809-8" />
- <stop
- offset="0.5"
- style="stop-color:#fafafa;stop-opacity:1"
- id="stop3807-5" />
- <stop
- offset="1"
- style="stop-color:#ffffff;stop-opacity:1"
- id="stop3251-6" />
- </linearGradient>
- <marker
- inkscape:stockid="TriangleOutM"
- orient="auto"
- refY="0"
- refX="0"
- id="TriangleOutM-3"
- style="overflow:visible">
- <path
- inkscape:connector-curvature="0"
- id="path5014-8"
- d="m 5.77,0 -8.65,5 0,-10 8.65,5 z"
- style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;marker-start:none"
- transform="scale(0.4,0.4)" />
- </marker>
- </defs>
- <sodipodi:namedview
- id="base"
- pagecolor="#ffffff"
- bordercolor="#666666"
- borderopacity="1.0"
- inkscape:pageopacity="0.0"
- inkscape:pageshadow="2"
- inkscape:zoom="0.98994949"
- inkscape:cx="457.04285"
- inkscape:cy="865.71036"
- inkscape:document-units="px"
- inkscape:current-layer="layer1"
- showgrid="false"
- showguides="true"
- inkscape:guide-bbox="true"
- inkscape:window-width="1440"
- inkscape:window-height="821"
- inkscape:window-x="0"
- inkscape:window-y="25"
- inkscape:window-maximized="1" />
- <metadata
- id="metadata7">
- <rdf:RDF>
- <cc:Work
- rdf:about="">
- <dc:format>image/svg+xml</dc:format>
- <dc:type
- rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
- <dc:title></dc:title>
- </cc:Work>
- </rdf:RDF>
- </metadata>
- <g
- inkscape:label="Layer 1"
- inkscape:groupmode="layer"
- id="layer1">
- <path
- style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-end:none"
- d="m 346.42858,370.93361 247.85713,0 0,-138.57143"
- id="path7413"
- inkscape:connector-curvature="0"
- sodipodi:nodetypes="ccc" />
- <g
- id="g3032"
- transform="matrix(0.23387391,0,0,0.23387391,236.76214,87.065911)">
- <polygon
- id="polygon2986"
- points="201.51,10.92 20.542,10.92 20.542,48.685 6.383,48.685 6.383,126.065 215.396,126.065 215.396,48.685 201.51,48.685 "
- style="fill:#ff0000" />
- <path
- id="path2988"
- d="M 2.605,127.709 V 44.906 h 14.16 V 7.142 h 188.522 v 37.764 h 13.885 v 82.803 H 2.605 l 0,0 z m 5.905,-2.973 203.994,0.179 -0.884,-72.453 H 197.73 V 14.696 H 24.317 v 37.766 h -14.16 l -1.381,69.877 -0.266,2.397 z"
- inkscape:connector-curvature="0"
- style="fill:#eb1019" />
- <polygon
- id="polygon2990"
- points="55.305,0.438 29.972,6.829 29.972,23.848 36.422,32.574 55.382,26.136 "
- style="fill:#ffffff" />
- <polygon
- id="polygon2992"
- points="33.813,126.69 33.899,93.641 76.686,103.309 187.977,92.94 188.003,126.69 "
- style="fill:#bd101d;stroke:#bd101d" />
- <polygon
- id="polygon2994"
- points="161.239,69.688 149.609,64.863 125.424,48.352 96.529,57.145 33.871,52.401 33.871,74.63 73.602,86.029 187.13,75.945 187.13,52.758 "
- style="fill:#bd101d;stroke:#bd101d" />
- <polygon
- id="polygon2996"
- points="58.873,82.808 68.02,120.713 72.766,122.681 76.123,136.549 102.592,136.549 89.863,83.941 125.821,60.593 145.377,77.117 150.128,75.256 151.931,80.169 187.13,77.01 184.075,69.212 164.271,64.369 128.412,35.152 89.34,49.973 77.334,34.173 95.421,15.785 112.66,15.785 119.461,21.515 119.461,15.785 133.01,15.785 129.737,8.181 90.051,1.897 53.414,25.361 16.749,37.652 9.587,45.228 9.587,65.542 19.699,69.846 19.699,58.113 24.002,61.485 24.002,53.469 33.871,53.467 52.137,80.971 "
- style="fill:#ffffff" />
- <g
- id="g2998">
- <path
- id="path3000"
- d="m 34.008,171.487 c -1.599,0.827 -5.126,1.709 -9.59,1.709 -11.794,0 -18.959,-7.44 -18.959,-18.739 0,-12.235 8.487,-19.621 19.841,-19.621 4.464,0 7.661,0.938 9.039,1.653 l -1.488,5.401 c -1.764,-0.771 -4.188,-1.433 -7.275,-1.433 -7.551,0 -13.007,4.74 -13.007,13.668 0,8.157 4.795,13.394 12.952,13.394 2.756,0 5.622,-0.552 7.385,-1.378 l 1.102,5.346 z"
- inkscape:connector-curvature="0" />
-
- <path
- id="path3002"
- d="m 63.271,158.922 c 0,9.865 -6.944,14.274 -13.779,14.274 -7.606,0 -13.448,-5.236 -13.448,-13.834 0,-8.818 5.787,-14.22 13.889,-14.22 7.991,0.001 13.338,5.623 13.338,13.78 z m -20.228,0.275 c 0,5.181 2.535,9.094 6.669,9.094 3.858,0 6.559,-3.803 6.559,-9.204 0,-4.188 -1.874,-8.983 -6.503,-8.983 -4.796,0 -6.725,4.628 -6.725,9.093 z"
- inkscape:connector-curvature="0" />
-
- <path
- id="path3004"
- d="m 91.942,164.543 c 0,3.197 0.11,5.842 0.221,8.047 H 86.21 l -0.331,-4.078 h -0.11 c -1.157,1.929 -3.803,4.685 -8.598,4.685 -4.905,0 -9.37,-2.921 -9.37,-11.685 v -15.763 h 6.779 v 14.605 c 0,4.464 1.433,7.33 5.015,7.33 2.701,0 4.464,-1.929 5.181,-3.638 0.221,-0.606 0.386,-1.322 0.386,-2.094 v -16.204 h 6.779 v 18.795 z"
- inkscape:connector-curvature="0" />
-
- <path
- id="path3006"
- d="m 118.002,171.763 c -1.434,0.662 -4.244,1.434 -7.606,1.434 -8.377,0 -13.833,-5.401 -13.833,-13.724 0,-8.047 5.511,-14.33 14.936,-14.33 2.48,0 5.016,0.551 6.559,1.268 l -1.212,5.07 c -1.103,-0.496 -2.7,-1.047 -5.126,-1.047 -5.181,0 -8.267,3.803 -8.212,8.708 0,5.512 3.583,8.653 8.212,8.653 2.37,0 4.023,-0.496 5.346,-1.047 l 0.936,5.015 z"
- inkscape:connector-curvature="0" />
-
- <path
- id="path3008"
- d="m 121.904,133.458 h 6.834 v 15.983 h 0.111 c 0.826,-1.268 1.983,-2.37 3.361,-3.086 1.322,-0.771 2.922,-1.213 4.63,-1.213 4.574,0 9.37,3.031 9.37,11.629 v 15.818 h -6.779 v -15.046 c 0,-3.913 -1.434,-6.835 -5.182,-6.835 -2.645,0 -4.52,1.764 -5.235,3.803 -0.221,0.552 -0.276,1.269 -0.276,2.04 v 16.038 h -6.834 v -39.131 z"
- inkscape:connector-curvature="0" />
-
- <path
- id="path3010"
- d="m 152.742,135.938 c 2.977,-0.496 6.779,-0.771 10.803,-0.771 6.944,0 11.739,1.433 15.156,4.299 3.583,2.921 5.787,7.33 5.787,13.669 0,6.613 -2.26,11.573 -5.787,14.771 -3.692,3.362 -9.59,5.07 -16.81,5.07 -3.969,0 -6.944,-0.221 -9.149,-0.496 v -36.542 z m 6.779,31.471 c 0.937,0.165 2.37,0.165 3.748,0.165 8.818,0.056 14.054,-4.795 14.054,-14.22 0.056,-8.212 -4.685,-12.896 -13.172,-12.896 -2.149,0 -3.693,0.165 -4.63,0.386 v 26.565 z"
- inkscape:connector-curvature="0" />
-
- <path
- id="path3012"
- d="m 189.605,135.938 c 2.149,-0.44 6.062,-0.771 9.865,-0.771 5.016,0 8.103,0.606 10.638,2.37 2.314,1.378 3.857,3.803 3.857,6.944 0,3.417 -2.149,6.559 -6.173,8.047 v 0.11 c 3.914,0.992 7.496,4.078 7.496,9.204 0,3.307 -1.433,5.897 -3.582,7.661 -2.646,2.314 -7,3.472 -13.779,3.472 -3.748,0 -6.613,-0.275 -8.322,-0.496 v -36.541 z m 6.724,14.661 h 3.473 c 4.685,0 7.33,-2.204 7.33,-5.346 0,-3.473 -2.646,-5.07 -6.944,-5.07 -1.984,0 -3.142,0.11 -3.858,0.275 v 10.141 z m 0,17.086 c 0.882,0.165 2.04,0.165 3.583,0.165 4.354,0 8.212,-1.653 8.212,-6.228 0,-4.299 -3.748,-6.062 -8.433,-6.062 h -3.362 v 12.125 z"
- inkscape:connector-curvature="0" />
-
-</g>
- <g
- id="g3014">
- <path
- id="path3016"
- d="m 152.609,183.135 c 0,-1.57 -0.027,-2.921 -0.11,-4.161 h 2.122 l 0.083,2.618 h 0.109 c 0.606,-1.791 2.067,-2.921 3.693,-2.921 0.275,0 0.468,0.027 0.688,0.082 v 2.288 c -0.248,-0.056 -0.496,-0.083 -0.826,-0.083 -1.709,0 -2.922,1.295 -3.252,3.114 -0.056,0.33 -0.11,0.716 -0.11,1.13 v 7.109 h -2.397 v -9.176 z"
- inkscape:connector-curvature="0"
- style="fill:#ff0000" />
-
- <path
- id="path3018"
- d="m 165.289,186.084 c 0.055,3.279 2.149,4.629 4.574,4.629 1.736,0 2.783,-0.303 3.692,-0.688 l 0.414,1.736 c -0.854,0.386 -2.315,0.854 -4.438,0.854 -4.105,0 -6.559,-2.728 -6.559,-6.751 0,-4.023 2.37,-7.192 6.256,-7.192 4.354,0 5.512,3.83 5.512,6.283 0,0.496 -0.056,0.882 -0.083,1.13 h -9.368 z m 7.109,-1.736 c 0.027,-1.543 -0.634,-3.941 -3.362,-3.941 -2.452,0 -3.527,2.26 -3.72,3.941 h 7.082 z"
- inkscape:connector-curvature="0"
- style="fill:#ff0000" />
-
- <path
- id="path3020"
- d="m 180.262,172.746 h 2.425 v 19.565 h -2.425 v -19.565 z"
- inkscape:connector-curvature="0"
- style="fill:#ff0000" />
-
- <path
- id="path3022"
- d="m 198.469,189.115 c 0,1.157 0.056,2.287 0.221,3.196 h -2.204 l -0.193,-1.681 h -0.083 c -0.744,1.047 -2.177,1.983 -4.078,1.983 -2.7,0 -4.078,-1.901 -4.078,-3.83 0,-3.225 2.865,-4.988 8.019,-4.96 v -0.276 c 0,-1.102 -0.303,-3.086 -3.031,-3.086 -1.24,0 -2.535,0.386 -3.472,0.992 l -0.552,-1.599 c 1.103,-0.717 2.701,-1.185 4.382,-1.185 4.078,0 5.07,2.783 5.07,5.456 v 4.99 z m -2.342,-3.61 c -2.646,-0.055 -5.649,0.413 -5.649,3.004 0,1.57 1.047,2.314 2.287,2.314 1.736,0 2.839,-1.103 3.225,-2.232 0.082,-0.247 0.138,-0.523 0.138,-0.771 v -2.315 z"
- inkscape:connector-curvature="0"
- style="fill:#ff0000" />
-
- <path
- id="path3024"
- d="m 206.228,178.974 1.901,2.866 c 0.496,0.744 0.909,1.433 1.351,2.177 h 0.082 c 0.441,-0.799 0.882,-1.488 1.323,-2.204 l 1.874,-2.839 h 2.618 l -4.548,6.448 4.686,6.89 h -2.756 l -1.957,-3.004 c -0.523,-0.771 -0.964,-1.516 -1.433,-2.314 h -0.056 c -0.44,0.826 -0.909,1.516 -1.405,2.314 l -1.929,3.004 h -2.673 l 4.739,-6.807 -4.519,-6.531 h 2.702 z"
- inkscape:connector-curvature="0"
- style="fill:#ff0000" />
-
-</g>
- </g>
- <g
- id="Firefox"
- transform="matrix(0.36505738,0,0,0.36505738,239.30396,245.73473)">
- <g
- id="Globe">
- <ellipse
- id="ellipse3088"
- ry="59.375"
- rx="59.335999"
- cy="59.375"
- cx="63.755001"
- sodipodi:cx="63.755001"
- sodipodi:cy="59.375"
- sodipodi:rx="59.335999"
- sodipodi:ry="59.375"
- style="fill:#110070"
- d="m 123.091,59.375 c 0,32.791907 -26.565632,59.375 -59.335999,59.375 C 30.984634,118.75 4.4190025,92.166907 4.4190025,59.375 4.4190025,26.583093 30.984634,0 63.755001,0 96.525368,0 123.091,26.583093 123.091,59.375 z" />
-
- <radialGradient
- gradientUnits="userSpaceOnUse"
- fy="7.2266002"
- fx="64.568802"
- r="104.221"
- cy="7.2266002"
- cx="64.568802"
- id="XMLID_61_">
- <stop
- id="stop3091"
- style="stop-color:#67C5D5"
- offset="0" />
-
- <stop
- id="stop3093"
- style="stop-color:#66C2D3"
- offset="0.1604" />
-
- <stop
- id="stop3095"
- style="stop-color:#62B9CE"
- offset="0.2795" />
-
- <stop
- id="stop3097"
- style="stop-color:#5CA8C6"
- offset="0.3852" />
-
- <stop
- id="stop3099"
- style="stop-color:#5392BA"
- offset="0.4832" />
-
- <stop
- id="stop3101"
- style="stop-color:#4874AA"
- offset="0.5759" />
-
- <stop
- id="stop3103"
- style="stop-color:#3A5097"
- offset="0.6646" />
-
- <stop
- id="stop3105"
- style="stop-color:#2A2781"
- offset="0.7485" />
-
- <stop
- id="stop3107"
- style="stop-color:#1B006D"
- offset="0.8146" />
-
- <stop
- id="stop3109"
- style="stop-color:#596AAD"
- offset="1" />
-
- </radialGradient>
-
- <ellipse
- id="ellipse3111"
- ry="58.254002"
- rx="58.215"
- cy="59.375"
- cx="63.755001"
- sodipodi:cx="63.755001"
- sodipodi:cy="59.375"
- sodipodi:rx="58.215"
- sodipodi:ry="58.254002"
- style="fill:url(#XMLID_61_)"
- d="m 121.97,59.375 c 0,32.172797 -26.063742,58.254 -58.214999,58.254 C 31.603744,117.629 5.5400009,91.547797 5.5400009,59.375 5.5400009,27.202203 31.603744,1.1209984 63.755001,1.1209984 95.906258,1.1209984 121.97,27.202203 121.97,59.375 z" />
-
- <radialGradient
- gradientUnits="userSpaceOnUse"
- fy="14.1748"
- fx="64.551804"
- r="52.231701"
- cy="14.1748"
- cx="64.551804"
- id="XMLID_62_">
- <stop
- id="stop3114"
- style="stop-color:#0F80BC"
- offset="0" />
-
- <stop
- id="stop3116"
- style="stop-color:#0A5F9E"
- offset="0.3376" />
-
- <stop
- id="stop3118"
- style="stop-color:#00145A"
- offset="1" />
-
- </radialGradient>
-
- <path
- id="path3120"
- d="m 55.405,89.446 c -0.072,-0.316 -0.857,-1.233 -1.458,-1.438 -0.599,-0.205 -1.188,0.884 -1.574,-0.924 -1.266,0.726 -1.812,-0.2 -1.812,-0.2 0,0 -1.365,0.08 -1.629,1.751 -0.65,0.769 -2.316,3.566 -1.431,0.657 0.891,-2.927 -0.94,-0.23 -1.11,1.24 -0.318,0.653 -0.855,0.719 -0.517,-2.218 -1.173,-0.405 -1.772,-3.127 -1.129,-3.179 -0.747,-1.041 -0.16,-2.633 0.462,-2.548 0.622,0.08 1.69,-1.36 1.657,-1.694 -0.033,-0.333 0.482,-0.095 0.45,-2.336 -0.033,-2.247 0.847,-2.803 0.919,-0.924 0.076,1.872 1.194,-0.613 1.079,-1.566 -0.116,-0.949 0.112,-3.094 0.793,-1.826 -0.793,-3.137 -0.167,-3.261 0.309,-1.173 1.193,0.094 1.234,0.73 1.234,0.73 1.321,-0.325 1.954,0.67 2.056,1.228 1.224,-1.445 2.46,0.113 2.46,0.113 0,0 0.853,-0.283 0.445,1.2 -0.41,1.478 1.772,0.678 1.251,-0.384 1.073,-0.543 2.666,1.102 3.168,2.224 0.502,1.127 -0.86,2.723 0.202,3.109 1.068,0.384 0.6,2.242 0.6,2.242 0,0 2.232,1.238 0.683,2.301 l -0.528,1.265 c 0,0 1.01,1.127 1.354,0.456 0.344,-0.672 0.466,0.683 0.466,0.683 -0.129,-0.24 0.248,1.396 1.64,1.74 1.398,0.341 2.283,2.55 0.731,2.578 C 64.63,92.578 64.51,92.536 64.091,93.439 63.674,94.338 61.7,94.37 60.678,93.206 59.591,93.574 57.657,93.1 58.226,92.23 57.288,92.777 56.402,91.308 57.135,90.653 57.868,90 56.723,89.154 56.338,90.09 c -0.381,0.935 -2.404,1.396 -2.263,-0.195 0.042,-0.472 1.12,-0.748 1.134,-0.354 0.197,0.455 0.277,-0.036 0.196,-0.095 z"
- inkscape:connector-curvature="0"
- style="fill:url(#XMLID_62_)" />
-
- <g
- id="g3122">
- <radialGradient
- gradientUnits="userSpaceOnUse"
- fy="14.1758"
- fx="64.553703"
- r="52.228901"
- cy="14.1758"
- cx="64.553703"
- id="XMLID_63_">
- <stop
- id="stop3125"
- style="stop-color:#0F80BC"
- offset="0" />
-
- <stop
- id="stop3127"
- style="stop-color:#0A5F9E"
- offset="0.3376" />
-
- <stop
- id="stop3129"
- style="stop-color:#00145A"
- offset="1" />
-
- </radialGradient>
-
- <path
- id="path3131"
- d="m 103.541,43.916 c 0.309,0.306 0.917,0.238 1.254,0.144 0.339,-0.097 0.291,-0.337 0.583,-0.431 0.287,-0.097 1.397,0.431 1.107,1.584 -0.673,0.094 -0.917,0.912 -0.917,0.912 0,0 -0.673,0.287 -1.013,0.144 -0.338,-0.144 -0.338,-0.097 -0.821,-0.05 -0.483,0.05 -1.643,-0.622 -0.387,-0.622 1.257,0 -0.143,-0.577 -0.675,-0.577 -0.53,0 -0.097,-0.528 0.532,-0.384 -0.436,-0.72 -0.195,-1.251 0.337,-0.72 z"
- inkscape:connector-curvature="0"
- style="fill:url(#XMLID_63_)" />
-
- <radialGradient
- gradientUnits="userSpaceOnUse"
- fy="14.1724"
- fx="64.548798"
- r="52.237301"
- cy="14.1724"
- cx="64.548798"
- id="XMLID_64_">
- <stop
- id="stop3134"
- style="stop-color:#0F80BC"
- offset="0" />
-
- <stop
- id="stop3136"
- style="stop-color:#0A5F9E"
- offset="0.3376" />
-
- <stop
- id="stop3138"
- style="stop-color:#00145A"
- offset="1" />
-
- </radialGradient>
-
- <path
- id="path3140"
- d="m 99.188,46.524 c -0.337,0.334 0.049,1.344 0.483,1.294 0.433,-0.047 1.11,-0.238 1.11,-0.238 0,0 0.337,0.672 0,0.866 -0.337,0.19 -1.319,0.106 -0.047,0.575 1.304,0.479 1.64,0.334 1.737,0.094 0.097,-0.238 0.53,-1.054 0.773,-1.006 0.193,-0.431 -0.676,-0.815 -0.192,-1.105 0.479,-0.288 0.096,-1.343 0.096,-1.343 0,0 -0.677,0.047 -0.966,0.528 -0.29,0.48 -0.674,-0.047 -0.964,-0.047 -0.049,0.287 -0.243,0.527 -0.243,0.527 l -0.193,-0.434 c -10e-4,-10e-4 -1.354,-0.047 -1.594,0.289 z"
- inkscape:connector-curvature="0"
- style="fill:url(#XMLID_64_)" />
-
- </g>
-
- <radialGradient
- gradientUnits="userSpaceOnUse"
- fy="19.1201"
- fx="65.405296"
- r="57.382702"
- cy="19.1201"
- cx="65.405296"