├── .gitignore
├── Gruntfile.js
├── LICENSE
├── README.md
├── admin_bundle.go
├── bundle.sh
├── index.html
├── package.json
├── src
├── base.css
├── js
│ ├── eventListener.js
│ ├── json-client.js
│ ├── main.js
│ └── syncModel.js
├── jsx
│ ├── app.jsx
│ ├── channels.jsx
│ ├── databases.jsx
│ ├── documents.jsx
│ ├── editor.jsx
│ ├── helpers.jsx
│ ├── page.jsx
│ ├── sync.jsx
│ └── users.jsx
├── logo.png
└── vendor
│ ├── codemirror-compressed.js
│ ├── codemirror.css
│ ├── davis.js
│ ├── react.js
│ └── zepto.min.js
└── tests
└── test_data.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | assets/
3 | tmp/
4 | watchChanged.json
5 | pkg/
6 |
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | module.exports = function(grunt) {
2 | var watchChanged = {}
3 | if (grunt.file.exists('watchChanged.json')) {
4 | watchChanged = grunt.file.readJSON('watchChanged.json')
5 | }
6 | grunt.initConfig({
7 | pkg: grunt.file.readJSON('package.json'),
8 | babel : {
9 | // for jshint only
10 | options : {
11 | only : "*.jsx"
12 | },
13 | dist : {
14 | "tmp/babel.js" : "src/jsx/*"
15 | }
16 | },
17 | jshint: {
18 | changed : [],
19 | js: ['Gruntfile.js', 'src/js/*.js', 'tests/*.js'],
20 | jsx : ['tmp/jsx/*.js'],
21 | options: {
22 | "browser": true,
23 | "globals": {
24 | "React" : true,
25 | "CodeMirror" : true,
26 | "confirm" : true
27 | },
28 | "node" : true,
29 | "asi" : true,
30 | "globalstrict": false,
31 | "quotmark": false,
32 | "smarttabs": true,
33 | "trailing": false,
34 | "undef": true,
35 | "unused": false
36 | }
37 | },
38 | node_tap: {
39 | all: {
40 | options: {
41 | outputType: 'failures', // tap, failures, stats
42 | outputTo: 'console' // or file
43 | // outputFilePath: '/tmp/out.log' // path for output file,
44 | // only makes sense with outputTo 'file'
45 | },
46 | files: {
47 | 'tests': ['tests/*.js']
48 | }
49 | },
50 | changed: {
51 | options: {
52 | outputType: 'tap', // tap, failures, stats
53 | outputTo: 'console' // or file
54 | // outputFilePath: '/tmp/out.log' // path for output file,
55 | // only makes sense with outputTo 'file'
56 | },
57 | files: {
58 | 'tests': watchChanged.node_tap || []
59 | }
60 | }
61 | },
62 | copy: {
63 | assets: {
64 | files: [
65 | // includes files within path
66 | {expand: true, cwd: 'src/', src: ['*'], dest: 'assets/', filter: 'isFile'},
67 |
68 | // includes files within path and its sub-directories
69 | {expand: true, cwd: 'src/vendor', src: ['**'], dest: 'assets/vendor'}
70 |
71 | // makes all src relative to cwd
72 | // {expand: true, cwd: 'path/', src: ['**'], dest: 'dest/'},
73 |
74 | // flattens results to a single level
75 | // {expand: true, flatten: true, src: ['path/**'], dest: 'dest/', filter: 'isFile'}
76 | ]
77 | }
78 | },
79 | browserify: {
80 | options: {
81 | debug : true,
82 | transform: [ require('babelify').configure({sourceMap : true}) ]
83 | },
84 | app: {
85 | src: 'src/js/main.js',
86 | dest: 'assets/bundle.js'
87 | }
88 | },
89 | uglify: {
90 | options: {
91 | mangle: false,
92 | compress : {
93 | unused : false
94 | },
95 | beautify : {
96 | ascii_only : true
97 | }
98 | },
99 | assets: {
100 | files: {
101 | 'assets/bundle.min.js': ['assets/bundle.js'],
102 | 'assets/vendor.min.js': ['src/vendor/*.js']
103 | }
104 | }
105 | },
106 | imageEmbed: {
107 | dist: {
108 | src: [ "src/base.css" ],
109 | dest: "assets/base.css",
110 | options: {
111 | deleteAfterEncoding : false
112 | }
113 | }
114 | },
115 | staticinline: {
116 | main: {
117 | files: {
118 | 'assets/index.html': 'index.html',
119 | }
120 | }
121 | },
122 | watch: {
123 | scripts: {
124 | files: ['Gruntfile.js', 'src/js/*.js'],
125 | tasks: ['jshint:changed', 'default'],
126 | options: {
127 | spawn: false,
128 | },
129 | },
130 | jsx: {
131 | files: ['src/jsx/*.jsx'],
132 | tasks: ['jsxhint', 'default'],
133 | options: {
134 | spawn: false,
135 | },
136 | },
137 | other : {
138 | files: ['index.html','src/**/*.css', 'src/vendor/**/*'],
139 | tasks: ['default'],
140 | options: {
141 | spawn: false,
142 | },
143 | },
144 | tests : {
145 | files: ['tests/*.js'],
146 | tasks: ['jshint:js', 'node_tap:changed', 'default'],
147 | options: {
148 | interrupt: true,
149 | },
150 | }
151 | }
152 | })
153 | grunt.loadNpmTasks('grunt-newer');
154 | grunt.loadNpmTasks('grunt-browserify')
155 | grunt.loadNpmTasks('grunt-babel');
156 | grunt.loadNpmTasks('grunt-contrib-jshint');
157 | grunt.loadNpmTasks('grunt-contrib-watch');
158 | grunt.loadNpmTasks('grunt-contrib-copy');
159 | grunt.loadNpmTasks('grunt-contrib-uglify');
160 | grunt.loadNpmTasks('grunt-node-tap');
161 | grunt.loadNpmTasks('grunt-static-inline');
162 | grunt.loadNpmTasks("grunt-image-embed");
163 |
164 | grunt.registerTask('jsxhint', ['babel', 'jshint:jsx']);
165 | grunt.registerTask('default', ['jshint:js', 'jsxhint', 'node_tap:all', 'copy:assets', 'browserify', 'imageEmbed','uglify', 'staticinline']);
166 |
167 | grunt.event.on('watch', function(action, filepath) {
168 | // for (var key in require.cache) {delete require.cache[key];}
169 | grunt.config('jshint.changed', [filepath]);
170 | grunt.file.write("watchChanged.json", JSON.stringify({
171 | node_tap : [filepath]
172 | }))
173 | grunt.config('node_tap.changed.files.tests', [filepath]);
174 | });
175 | };
176 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright 2017 Couchbase, Inc.
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | **NOTE:** development on this has stopped and moved to the [dev branch](https://github.com/couchbaselabs/sync_gateway_admin_ui/tree/dev) which is a re-write from scratch!
2 |
3 | # Developer Console for Couchbase Sync Gateway
4 |
5 | This is not a standalone project -- it's a submodule of the [Couchbase Sync Gateway][SG]. We are keeping it in a separate repository so that its Git commits and Github issues are separated from the main gateway's, for clarity. In addition, this project is currently in an early development and experimental phase. The resulting dashboard will change drastically in the near future.
6 |
7 | This project contains the Web assets for the Sync Gateway's admin console. To use this interface, launch a Sync Gateway server and visit [http://localhost:4985/_admin/](http://localhost:4985/_admin/) in your browser. (This port is bound to localhost-only by default, so if you want to connect to it from a remote device you may need to create a tunnel or change your gateway config.)
8 |
9 | ## What can you do with it?
10 |
11 | * View and edit your Sync Function code and see what it will do *before* you deploy it
12 | * Browse through all databases and their documents
13 | * View the JSON contents of any document, plus its channel assignments and any channel access it grants
14 | * View the internal `_sync` metadata of any document (useful mostly for troubleshooting the Sync Gateway)
15 |
16 | ## Known Issues
17 |
18 | Currently it tries to load the last 1000 changes into the brower's memory. If you have more than 1000 documents in your database it will only look at the 1000 most recent. In the future we will make this configurable.
19 |
20 | ## Developing / Contributing
21 |
22 | **NOTE:** To use the existing admin UI you don't need to do anything with this repository; it's already built into the Sync Gateway. You only need to follow these instructions if you want to make changes to the admin UI.
23 |
24 | Before you can work on this code, you need [node.js][NODEJS] installed locally. Once you have that, run these commands.
25 |
26 | ```bash
27 | cd src/github.com/couchbaselabs/sync_gateway_admin_ui
28 | npm install -g grunt-cli # you might need to sudo this
29 | npm install -g tap # ditto
30 | npm install
31 | grunt
32 | ```
33 |
34 | You'll need to run `grunt` every time you change code files. You can also run it continuously with `grunt watch`.
35 |
36 | To point Sync Gateway at the development bundle created by `grunt`, add this line to your Sync Gateway config file at the top level:
37 |
38 | ```
39 | "adminUI" : "src/github.com/couchbaselabs/sync_gateway_admin_ui/assets/index.html",
40 | ```
41 |
42 | ## Building for Release
43 |
44 | To release this code for consumption by Sync Gateway's build process, it needs to be packaged as Go code:
45 |
46 | ```bash
47 | go get github.com/jteeuwen/go-bindata
48 | grunt
49 | ./bundle.sh
50 | ```
51 |
52 | [SG]: https://github.com/couchbase/sync_gateway
53 | [NODEJS]: http://nodejs.org
54 |
--------------------------------------------------------------------------------
/bundle.sh:
--------------------------------------------------------------------------------
1 | #/bin/sh
2 | go-bindata -pkg sync_gateway_admin_ui -o admin_bundle.go assets/ && \
3 | gofmt admin_bundle.go > gofmt_admin_bundle.go && \
4 | mv gofmt_admin_bundle.go admin_bundle.go
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
The Sync Function determines application-specific behavior regarding who can see and modify which documents. The code you write here can validate updates, route documents to channels, and grant access privileges to users and groups on a per-channel basis. For more information see the Sync Function API documentation.
19 |
)
89 | }
90 | })
91 |
--------------------------------------------------------------------------------
/src/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/couchbaselabs/sync_gateway_admin_ui/93c74bac9ddc2979ab895a37087c225c998b03bf/src/logo.png
--------------------------------------------------------------------------------
/src/vendor/codemirror.css:
--------------------------------------------------------------------------------
1 | /* BASICS */
2 |
3 | .CodeMirror {
4 | /* Set height, width, borders, and global font properties here */
5 | font-family: monospace;
6 | height: 300px;
7 | }
8 | .CodeMirror-scroll {
9 | /* Set scrolling behaviour here */
10 | overflow: auto;
11 | }
12 |
13 | /* PADDING */
14 |
15 | .CodeMirror-lines {
16 | padding: 4px 0; /* Vertical padding around content */
17 | }
18 | .CodeMirror pre {
19 | padding: 0 4px; /* Horizontal padding of content */
20 | }
21 |
22 | .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
23 | background-color: white; /* The little square between H and V scrollbars */
24 | }
25 |
26 | /* GUTTER */
27 |
28 | .CodeMirror-gutters {
29 | border-right: 1px solid #ddd;
30 | background-color: #f7f7f7;
31 | white-space: nowrap;
32 | }
33 | .CodeMirror-linenumbers {}
34 | .CodeMirror-linenumber {
35 | padding: 0 3px 0 5px;
36 | min-width: 20px;
37 | text-align: right;
38 | color: #999;
39 | }
40 |
41 | /* CURSOR */
42 |
43 | .CodeMirror div.CodeMirror-cursor {
44 | border-left: 1px solid black;
45 | z-index: 3;
46 | }
47 | /* Shown when moving in bi-directional text */
48 | .CodeMirror div.CodeMirror-secondarycursor {
49 | border-left: 1px solid silver;
50 | }
51 | .CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor {
52 | width: auto;
53 | border: 0;
54 | background: #7e7;
55 | z-index: 1;
56 | }
57 | /* Can style cursor different in overwrite (non-insert) mode */
58 | .CodeMirror div.CodeMirror-cursor.CodeMirror-overwrite {}
59 |
60 | .cm-tab { display: inline-block; }
61 |
62 | /* DEFAULT THEME */
63 |
64 | .cm-s-default .cm-keyword {color: #708;}
65 | .cm-s-default .cm-atom {color: #219;}
66 | .cm-s-default .cm-number {color: #164;}
67 | .cm-s-default .cm-def {color: #00f;}
68 | .cm-s-default .cm-variable {color: black;}
69 | .cm-s-default .cm-variable-2 {color: #05a;}
70 | .cm-s-default .cm-variable-3 {color: #085;}
71 | .cm-s-default .cm-property {color: black;}
72 | .cm-s-default .cm-operator {color: black;}
73 | .cm-s-default .cm-comment {color: #a50;}
74 | .cm-s-default .cm-string {color: #a11;}
75 | .cm-s-default .cm-string-2 {color: #f50;}
76 | .cm-s-default .cm-meta {color: #555;}
77 | .cm-s-default .cm-qualifier {color: #555;}
78 | .cm-s-default .cm-builtin {color: #30a;}
79 | .cm-s-default .cm-bracket {color: #997;}
80 | .cm-s-default .cm-tag {color: #170;}
81 | .cm-s-default .cm-attribute {color: #00c;}
82 | .cm-s-default .cm-header {color: blue;}
83 | .cm-s-default .cm-quote {color: #090;}
84 | .cm-s-default .cm-hr {color: #999;}
85 | .cm-s-default .cm-link {color: #00c;}
86 |
87 | .cm-negative {color: #d44;}
88 | .cm-positive {color: #292;}
89 | .cm-header, .cm-strong {font-weight: bold;}
90 | .cm-em {font-style: italic;}
91 | .cm-link {text-decoration: underline;}
92 |
93 | .cm-s-default .cm-error {color: #f00;}
94 | .cm-invalidchar {color: #f00;}
95 |
96 | div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
97 | div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
98 | .CodeMirror-activeline-background {background: #e8f2ff;}
99 |
100 | /* STOP */
101 |
102 | /* The rest of this file contains styles related to the mechanics of
103 | the editor. You probably shouldn't touch them. */
104 |
105 | .CodeMirror {
106 | line-height: 1;
107 | position: relative;
108 | overflow: hidden;
109 | background: white;
110 | color: black;
111 | }
112 |
113 | .CodeMirror-scroll {
114 | /* 30px is the magic margin used to hide the element's real scrollbars */
115 | /* See overflow: hidden in .CodeMirror */
116 | margin-bottom: -30px; margin-right: -30px;
117 | padding-bottom: 30px; padding-right: 30px;
118 | height: 100%;
119 | outline: none; /* Prevent dragging from highlighting the element */
120 | position: relative;
121 | -moz-box-sizing: content-box;
122 | box-sizing: content-box;
123 | }
124 | .CodeMirror-sizer {
125 | position: relative;
126 | }
127 |
128 | /* The fake, visible scrollbars. Used to force redraw during scrolling
129 | before actuall scrolling happens, thus preventing shaking and
130 | flickering artifacts. */
131 | .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
132 | position: absolute;
133 | z-index: 6;
134 | display: none;
135 | }
136 | .CodeMirror-vscrollbar {
137 | right: 0; top: 0;
138 | overflow-x: hidden;
139 | overflow-y: scroll;
140 | }
141 | .CodeMirror-hscrollbar {
142 | bottom: 0; left: 0;
143 | overflow-y: hidden;
144 | overflow-x: scroll;
145 | }
146 | .CodeMirror-scrollbar-filler {
147 | right: 0; bottom: 0;
148 | }
149 | .CodeMirror-gutter-filler {
150 | left: 0; bottom: 0;
151 | }
152 |
153 | .CodeMirror-gutters {
154 | position: absolute; left: 0; top: 0;
155 | padding-bottom: 30px;
156 | z-index: 3;
157 | }
158 | .CodeMirror-gutter {
159 | white-space: normal;
160 | height: 100%;
161 | -moz-box-sizing: content-box;
162 | box-sizing: content-box;
163 | padding-bottom: 30px;
164 | margin-bottom: -32px;
165 | display: inline-block;
166 | /* Hack to make IE7 behave */
167 | *zoom:1;
168 | *display:inline;
169 | }
170 | .CodeMirror-gutter-elt {
171 | position: absolute;
172 | cursor: default;
173 | z-index: 4;
174 | }
175 |
176 | .CodeMirror-lines {
177 | cursor: text;
178 | }
179 | .CodeMirror pre {
180 | /* Reset some styles that the rest of the page might have set */
181 | -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
182 | border-width: 0;
183 | background: transparent;
184 | font-family: inherit;
185 | font-size: inherit;
186 | margin: 0;
187 | white-space: pre;
188 | word-wrap: normal;
189 | line-height: inherit;
190 | color: inherit;
191 | z-index: 2;
192 | position: relative;
193 | overflow: visible;
194 | }
195 | .CodeMirror-wrap pre {
196 | word-wrap: break-word;
197 | white-space: pre-wrap;
198 | word-break: normal;
199 | }
200 | .CodeMirror-code pre {
201 | border-right: 30px solid transparent;
202 | width: -webkit-fit-content;
203 | width: -moz-fit-content;
204 | width: fit-content;
205 | }
206 | .CodeMirror-wrap .CodeMirror-code pre {
207 | border-right: none;
208 | width: auto;
209 | }
210 | .CodeMirror-linebackground {
211 | position: absolute;
212 | left: 0; right: 0; top: 0; bottom: 0;
213 | z-index: 0;
214 | }
215 |
216 | .CodeMirror-linewidget {
217 | position: relative;
218 | z-index: 2;
219 | overflow: auto;
220 | }
221 |
222 | .CodeMirror-widget {}
223 |
224 | .CodeMirror-wrap .CodeMirror-scroll {
225 | overflow-x: hidden;
226 | }
227 |
228 | .CodeMirror-measure {
229 | position: absolute;
230 | width: 100%;
231 | height: 0;
232 | overflow: hidden;
233 | visibility: hidden;
234 | }
235 | .CodeMirror-measure pre { position: static; }
236 |
237 | .CodeMirror div.CodeMirror-cursor {
238 | position: absolute;
239 | visibility: hidden;
240 | border-right: none;
241 | width: 0;
242 | }
243 | .CodeMirror-focused div.CodeMirror-cursor {
244 | visibility: visible;
245 | }
246 |
247 | .CodeMirror-selected { background: #d9d9d9; }
248 | .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
249 |
250 | .cm-searching {
251 | background: #ffa;
252 | background: rgba(255, 255, 0, .4);
253 | }
254 |
255 | /* IE7 hack to prevent it from returning funny offsetTops on the spans */
256 | .CodeMirror span { *vertical-align: text-bottom; }
257 |
258 | @media print {
259 | /* Hide the cursor when printing */
260 | .CodeMirror div.CodeMirror-cursor {
261 | visibility: hidden;
262 | }
263 | }
264 |
--------------------------------------------------------------------------------
/src/vendor/davis.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Davis - http://davisjs.com - JavaScript Routing - 0.9.9
3 | * Copyright (C) 2011 Oliver Nightingale
4 | * MIT Licensed
5 | */
6 | ;
7 | /**
8 | * Convinience method for instantiating a new Davis app and configuring it to use the passed
9 | * routes and subscriptions.
10 | *
11 | * @param {Function} config A function that will be run with a newly created Davis.App as its context,
12 | * should be used to set up app routes, subscriptions and settings etc.
13 | * @namespace
14 | * @returns {Davis.App}
15 | */
16 | Davis = function (config) {
17 | var app = new Davis.App
18 | config && config.call(app)
19 | Davis.$(function () { app.start() })
20 | return app
21 | };
22 |
23 | /**
24 | * Stores the DOM library that Davis will use. Can be overriden to use libraries other than jQuery.
25 | */
26 | if (window.jQuery) {
27 | Davis.$ = jQuery
28 | } else {
29 | Davis.$ = null
30 | };
31 |
32 | /**
33 | * Checks whether Davis is supported in the current browser
34 | *
35 | * @returns {Boolean}
36 | */
37 | Davis.supported = function () {
38 | return (typeof window.history.pushState == 'function')
39 | }
40 |
41 | /*!
42 | * A function that does nothing, used as a default param for any callbacks.
43 | *
44 | * @private
45 | * @returns {Function}
46 | */
47 | Davis.noop = function () {}
48 |
49 | /**
50 | * Method to extend the Davis library with an extension.
51 | *
52 | * An extension is just a function that will modify the Davis framework in some way,
53 | * for example changing how the routing works or adjusting where Davis thinks it is supported.
54 | *
55 | * Example:
56 | * Davis.extend(Davis.hashBasedRouting)
57 | *
58 | * @param {Function} extension the function that will extend Davis
59 | *
60 | */
61 | Davis.extend = function (extension) {
62 | extension(Davis)
63 | }
64 |
65 | /*!
66 | * the version
67 | */
68 | Davis.version = "0.9.9";/*!
69 | * Davis - utils
70 | * Copyright (C) 2011 Oliver Nightingale
71 | * MIT Licensed
72 | */
73 |
74 | /*!
75 | * A module that provides wrappers around modern JavaScript so that native implementations are used
76 | * whereever possible and JavaScript implementations are used in those browsers that do not natively
77 | * support them.
78 | */
79 | Davis.utils = (function () {
80 |
81 | /*!
82 | * A wrapper around native Array.prototype.every.
83 | *
84 | * Falls back to a pure JavaScript implementation in browsers that do not support Array.prototype.every.
85 | * For more details see the full docs on MDC https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/every
86 | *
87 | * @private
88 | * @param {array} the array to loop through
89 | * @param {fn} the function to that performs the every check
90 | * @param {thisp} an optional param that will be set as fn's this value
91 | * @returns {Array}
92 | */
93 | if (Array.prototype.every) {
94 | var every = function (array, fn) {
95 | return array.every(fn, arguments[2])
96 | }
97 | } else {
98 | var every = function (array, fn) {
99 | if (array === void 0 || array === null) throw new TypeError();
100 | var t = Object(array);
101 | var len = t.length >>> 0;
102 | if (typeof fn !== "function") throw new TypeError();
103 |
104 | var thisp = arguments[2];
105 | for (var i = 0; i < len; i++) {
106 | if (i in t && !fn.call(thisp, t[i], i, t)) return false;
107 | }
108 |
109 | return true;
110 | }
111 | };
112 |
113 | /*!
114 | * A wrapper around native Array.prototype.forEach.
115 | *
116 | * Falls back to a pure JavaScript implementation in browsers that do not support Array.prototype.forEach.
117 | * For more details see the full docs on MDC https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/forEach
118 | *
119 | * @private
120 | * @param {array} the array to loop through
121 | * @param {fn} the function to apply to every element of the array
122 | * @param {thisp} an optional param that will be set as fn's this value
123 | * @returns {Array}
124 | */
125 | if (Array.prototype.forEach) {
126 | var forEach = function (array, fn) {
127 | return array.forEach(fn, arguments[2])
128 | }
129 | } else {
130 | var forEach = function (array, fn) {
131 | if (array === void 0 || array === null) throw new TypeError();
132 | var t = Object(array);
133 | var len = t.length >>> 0;
134 | if (typeof fn !== "function") throw new TypeError();
135 |
136 |
137 | var thisp = arguments[2];
138 | for (var i = 0; i < len; i++) {
139 | if (i in t) fn.call(thisp, t[i], i, t);
140 | }
141 | };
142 | };
143 |
144 | /*!
145 | * A wrapper around native Array.prototype.filter.
146 | * Falls back to a pure JavaScript implementation in browsers that do not support Array.prototype.filter.
147 | * For more details see the full docs on MDC https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/filter
148 | *
149 | * @private
150 | * @param {array} the array to filter
151 | * @param {fn} the function to do the filtering
152 | * @param {thisp} an optional param that will be set as fn's this value
153 | * @returns {Array}
154 | */
155 | if (Array.prototype.filter) {
156 | var filter = function (array, fn) {
157 | return array.filter(fn, arguments[2])
158 | }
159 | } else {
160 | var filter = function(array, fn) {
161 | if (array === void 0 || array === null) throw new TypeError();
162 | var t = Object(array);
163 | var len = t.length >>> 0;
164 | if (typeof fn !== "function") throw new TypeError();
165 |
166 |
167 | var res = [];
168 | var thisp = arguments[2];
169 | for (var i = 0; i < len; i++) {
170 | if (i in t) {
171 | var val = t[i]; // in case fn mutates this
172 | if (fn.call(thisp, val, i, t)) res.push(val);
173 | }
174 | }
175 |
176 | return res;
177 | };
178 | };
179 |
180 | /*!
181 | * A wrapper around native Array.prototype.map.
182 | * Falls back to a pure JavaScript implementation in browsers that do not support Array.prototype.map.
183 | * For more details see the full docs on MDC https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/map
184 | *
185 | * @private
186 | * @param {array} the array to map
187 | * @param {fn} the function to do the mapping
188 | * @param {thisp} an optional param that will be set as fn's this value
189 | * @returns {Array}
190 | */
191 |
192 | if (Array.prototype.map) {
193 | var map = function (array, fn) {
194 | return array.map(fn, arguments[2])
195 | }
196 | } else {
197 | var map = function(array, fn) {
198 | if (array === void 0 || array === null)
199 | throw new TypeError();
200 |
201 | var t = Object(array);
202 | var len = t.length >>> 0;
203 | if (typeof fn !== "function")
204 | throw new TypeError();
205 |
206 | var res = new Array(len);
207 | var thisp = arguments[2];
208 | for (var i = 0; i < len; i++) {
209 | if (i in t)
210 | res[i] = fn.call(thisp, t[i], i, t);
211 | }
212 |
213 | return res;
214 | };
215 | };
216 |
217 | /*!
218 | * A convinience function for converting arguments to a proper array
219 | *
220 | * @private
221 | * @param {args} a functions arguments
222 | * @param {start} an integer at which to start converting the arguments to an array
223 | * @returns {Array}
224 | */
225 | var toArray = function (args, start) {
226 | return Array.prototype.slice.call(args, start || 0)
227 | }
228 |
229 | /*!
230 | * Exposing the public interface to the Utils module
231 | * @private
232 | */
233 | return {
234 | every: every,
235 | forEach: forEach,
236 | filter: filter,
237 | toArray: toArray,
238 | map: map
239 | }
240 | })()
241 |
242 | /*!
243 | * Davis - listener
244 | * Copyright (C) 2011 Oliver Nightingale
245 | * MIT Licensed
246 | */
247 |
248 | /**
249 | * A module to bind to link clicks and form submits and turn what would normally be http requests
250 | * into instances of Davis.Request. These request objects are then pushed onto the history stack
251 | * using the Davis.history module.
252 | *
253 | * This module uses Davis.$, which by defualt is jQuery for its event binding and event object normalization.
254 | * To use Davis with any, or no, JavaScript framework be sure to provide support for all the methods called
255 | * on Davis.$.
256 | *
257 | * @module
258 | */
259 | Davis.listener = function () {
260 |
261 | /*!
262 | * Methods to check whether an element has an href or action that is local to this page
263 | * @private
264 | */
265 | var originChecks = {
266 | A: function (elem) {
267 | return elem.host !== window.location.host || elem.protocol !== window.location.protocol
268 | },
269 |
270 | FORM: function (elem) {
271 | var a = document.createElement('a')
272 | a.href = elem.action
273 | return this.A(a)
274 | }
275 | }
276 |
277 | /*!
278 | * Checks whether the target of a click or submit event has an href or action that is local to the
279 | * current page. Only links or targets with local hrefs or actions will be handled by davis, all
280 | * others will be ignored.
281 | * @private
282 | */
283 | var differentOrigin = function (elem) {
284 | if (!originChecks[elem.nodeName.toUpperCase()]) return true // the elem is neither a link or a form
285 | return originChecks[elem.nodeName.toUpperCase()](elem)
286 | }
287 |
288 | var hasModifier = function (event) {
289 | return (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey)
290 | }
291 |
292 | /*!
293 | * A handler that creates a new Davis.Request and pushes it onto the history stack using Davis.history.
294 | *
295 | * @param {Function} targetExtractor a function that will be called with the event target jQuery object and should return an object with path, title and method.
296 | * @private
297 | */
298 | var handler = function (targetExtractor) {
299 | return function (event) {
300 | if (hasModifier(event)) return true
301 | if (differentOrigin(this)) return true
302 |
303 | var request = new Davis.Request (targetExtractor.call(Davis.$(this)));
304 | Davis.location.assign(request)
305 | event.stopPropagation()
306 | event.preventDefault()
307 | return false;
308 | };
309 | };
310 |
311 | /*!
312 | * A handler specialized for click events. Gets the request details from a link elem
313 | * @private
314 | */
315 | var clickHandler = handler(function () {
316 | var self = this
317 |
318 | return {
319 | method: 'get',
320 | fullPath: this.prop('href'),
321 | title: this.attr('title'),
322 | delegateToServer: function () {
323 | window.location = self.prop('href')
324 | }
325 | };
326 | });
327 |
328 | /*!
329 | * A handler specialized for submit events. Gets the request details from a form elem
330 | * @private
331 | */
332 | var submitHandler = handler(function () {
333 | var self = this
334 | return {
335 | method: this.attr('method'),
336 | fullPath: (this.serialize() ? [this.prop('action'), this.serialize()].join("?") : this.prop('action')),
337 | title: this.attr('title'),
338 | delegateToServer: function () {
339 | self.submit()
340 | }
341 | };
342 | });
343 |
344 | /**
345 | * Binds to both link clicks and form submits using jQuery's deleagate.
346 | *
347 | * Will catch all current and future links and forms. Uses the apps settings for the selector to use for links and forms
348 | *
349 | * @see Davis.App.settings
350 | * @memberOf listener
351 | */
352 | this.listen = function () {
353 | Davis.$(document).delegate(this.settings.formSelector, 'submit', submitHandler)
354 | Davis.$(document).delegate(this.settings.linkSelector, 'click', clickHandler)
355 | }
356 |
357 | /**
358 | * Unbinds all click and submit handlers that were attatched with listen.
359 | *
360 | * Will efectivley stop the current app from processing any requests and all links and forms will have their default
361 | * behaviour restored.
362 | *
363 | * @see Davis.App.settings
364 | * @memberOf listener
365 | */
366 | this.unlisten = function () {
367 | Davis.$(document).undelegate(this.settings.linkSelector, 'click', clickHandler)
368 | Davis.$(document).undelegate(this.settings.formSelector, 'submit', submitHandler)
369 | }
370 | }
371 | /*!
372 | * Davis - event
373 | * Copyright (C) 2011 Oliver Nightingale
374 | * MIT Licensed
375 | */
376 |
377 | /**
378 | * A plugin that adds basic event capabilities to a Davis app, it is included by default.
379 | *
380 | * @module
381 | */
382 | Davis.event = function () {
383 |
384 | /*!
385 | * callback storage
386 | */
387 | var callbacks = {}
388 |
389 | /**
390 | * Binds a callback to a named event.
391 | *
392 | * The following events are triggered internally by Davis and can be bound to
393 | *
394 | * * start : Triggered when the application is started
395 | * * lookupRoute : Triggered before looking up a route. The request being looked up is passed as an argument
396 | * * runRoute : Triggered before running a route. The request and route being run are passed as arguments
397 | * * routeNotFound : Triggered if no route for the current request can be found. The current request is passed as an arugment
398 | * * requestHalted : Triggered when a before filter halts the current request. The current request is passed as an argument
399 | * * unsupported : Triggered when starting a Davis app in a browser that doesn't support html5 pushState
400 | *
401 | * Example
402 | *
403 | * app.bind('runRoute', function () {
404 | * console.log('about to run a route')
405 | * })
406 | *
407 | * @param {String} event event name
408 | * @param {Function} fn callback
409 | * @memberOf event
410 | */
411 | this.bind = function (event, fn) {
412 | (callbacks[event] = callbacks[event] || []).push(fn);
413 | return this;
414 | };
415 |
416 | /**
417 | * Triggers an event with the given arguments.
418 | *
419 | * @param {String} event event name
420 | * @param {Mixed} ...
421 | * @memberOf event
422 | */
423 | this.trigger = function (event) {
424 | var args = Davis.utils.toArray(arguments, 1),
425 | handlers = callbacks[event];
426 |
427 | if (!handlers) return this
428 |
429 | for (var i = 0, len = handlers.length; i < len; ++i) {
430 | handlers[i].apply(this, args)
431 | }
432 |
433 | return this;
434 | };
435 | }
436 | /*!
437 | * Davis - logger
438 | * Copyright (C) 2011 Oliver Nightingale
439 | * MIT Licensed
440 | */
441 |
442 | /**
443 | * A plugin for enhancing the standard logging available through the console object.
444 | * Automatically included in all Davis apps.
445 | *
446 | * Generates log messages of varying severity in the format
447 | *
448 | * `[Sun Jan 23 2011 16:15:21 GMT+0000 (GMT)] `
449 | *
450 | * @module
451 | */
452 | Davis.logger = function () {
453 |
454 | /*!
455 | * Generating the timestamp portion of the log message
456 | * @private
457 | */
458 | function timestamp(){
459 | return "[" + Date() + "]";
460 | }
461 |
462 | /*!
463 | * Pushing the timestamp onto the front of the arguments to log
464 | * @private
465 | */
466 | function prepArgs(args) {
467 | var a = Davis.utils.toArray(args)
468 | a.unshift(timestamp())
469 | return a.join(' ');
470 | }
471 |
472 | var logType = function (logLevel) {
473 | return function () {
474 | if (window.console) console[logLevel](prepArgs(arguments));
475 | }
476 | }
477 |
478 |
479 | /**
480 | * Prints an error message to the console if the console is available.
481 | *
482 | * @params {String} All arguments are combined and logged to the console.
483 | * @memberOf logger
484 | */
485 | var error = logType('error')
486 |
487 | /**
488 | * Prints an info message to the console if the console is available.
489 | *
490 | * @params {String} All arguments are combined and logged to the console.
491 | * @memberOf logger
492 | */
493 | var info = logType('info')
494 |
495 | /**
496 | * Prints a warning message to the console if the console is available.
497 | *
498 | * @params {String} All arguments are combined and logged to the console.
499 | * @memberOf logger
500 | */
501 | var warn = logType('warn')
502 |
503 | /*!
504 | * Exposes the public methods of the module
505 | * @private
506 | */
507 | this.logger = {
508 | error: error,
509 | info: info,
510 | warn: warn
511 | }
512 | }/*!
513 | * Davis - Route
514 | * Copyright (C) 2011 Oliver Nightingale
515 | * MIT Licensed
516 | */
517 |
518 | Davis.Route = (function () {
519 |
520 | var pathNameRegex = /:([\w\d]+)/g;
521 | var pathNameReplacement = "([^\/]+)";
522 |
523 | var splatNameRegex = /\*([\w\d]+)/g;
524 | var splatNameReplacement = "(.*)";
525 |
526 | var nameRegex = /[:|\*]([\w\d]+)/g
527 |
528 | /**
529 | * Davis.Routes are the main part of a Davis application. They consist of an HTTP method, a path
530 | * and a callback function. When a link or a form that Davis has bound to are clicked or submitted
531 | * a request is pushed on the history stack and a route that matches the path and method of the
532 | * generated request is run.
533 | *
534 | * The path for the route can consist of placeholders for attributes, these will then be available
535 | * on the request. Simple variables should be prefixed with a colan, and for splat style params use
536 | * an asterisk.
537 | *
538 | * Inside the callback function 'this' is bound to the request.
539 | *
540 | * Example:
541 | *
542 | * var route = new Davis.Route ('get', '/foo/:id', function (req) {
543 | * var id = req.params['id']
544 | * // do something interesting!
545 | * })
546 | *
547 | * var route = new Davis.Route ('get', '/foo/*splat', function (req) {
548 | * var id = req.params['splat']
549 | * // splat will contain everything after the /foo/ in the path.
550 | * })
551 | *
552 | * You can include any number of route level 'middleware' when defining routes. These middlewares are
553 | * run in order and need to explicitly call the next handler in the stack. Using route middleware allows
554 | * you to share common logic between routes and is also a good place to load any data or do any async calls
555 | * keeping your main handler simple and focused.
556 | *
557 | * Example:
558 | *
559 | * var loadUser = function (req, next) {
560 | * $.get('/users/current', function (user) {
561 | * req.user = user
562 | * next(req)
563 | * })
564 | * }
565 | *
566 | * var route = new Davis.Route ('get', '/foo/:id', loadUser, function (req) {
567 | * renderUser(req.user)
568 | * })
569 | *
570 | * @constructor
571 | * @param {String} method This should be one of either 'get', 'post', 'put', 'delete', 'before', 'after' or 'state'
572 | * @param {String} path This string can contain place holders for variables, e.g. '/user/:id' or '/user/*splat'
573 | * @param {Function} callback One or more callbacks that will be called in order when a request matching both the path and method is triggered.
574 | */
575 | var Route = function (method, path, handlers) {
576 | var convertPathToRegExp = function () {
577 | if (!(path instanceof RegExp)) {
578 | var str = path
579 | .replace(pathNameRegex, pathNameReplacement)
580 | .replace(splatNameRegex, splatNameReplacement);
581 |
582 | // Most browsers will reset this to zero after a replace call. IE will
583 | // set it to the index of the last matched character.
584 | path.lastIndex = 0;
585 |
586 | return new RegExp("^" + str + "$", "gi");
587 | } else {
588 | return path;
589 | };
590 | };
591 |
592 | var convertMethodToRegExp = function () {
593 | if (!(method instanceof RegExp)) {
594 | return new RegExp("^" + method + "$", "i");
595 | } else {
596 | return method
597 | };
598 | }
599 |
600 | var capturePathParamNames = function () {
601 | var names = [], a;
602 | while ((a = nameRegex.exec(path))) names.push(a[1]);
603 | return names;
604 | };
605 |
606 | this.paramNames = capturePathParamNames();
607 | this.path = convertPathToRegExp();
608 | this.method = convertMethodToRegExp();
609 |
610 | if (typeof handlers === 'function') {
611 | this.handlers = [handlers]
612 | } else {
613 | this.handlers = handlers;
614 | }
615 | }
616 |
617 | /**
618 | * Tests whether or not a route matches a particular request.
619 | *
620 | * Example:
621 | *
622 | * route.match('get', '/foo/12')
623 | *
624 | * @param {String} method the method to match against
625 | * @param {String} path the path to match against
626 | * @returns {Boolean}
627 | */
628 | Route.prototype.match = function (method, path) {
629 | this.reset();
630 | return (this.method.test(method)) && (this.path.test(path))
631 | }
632 |
633 | /**
634 | * Resets the RegExps for method and path
635 | */
636 | Route.prototype.reset = function () {
637 | this.method.lastIndex = 0;
638 | this.path.lastIndex = 0;
639 | }
640 |
641 | /**
642 | * Runs the callback associated with a particular route against the passed request.
643 | *
644 | * Any named params in the request path are extracted, as per the routes path, and
645 | * added onto the requests params object.
646 | *
647 | * Example:
648 | *
649 | * route.run(request)
650 | *
651 | * @params {Davis.Request} request
652 | * @returns {Object} whatever the routes callback returns
653 | */
654 | Route.prototype.run = function (request) {
655 | this.reset();
656 | var matches = this.path.exec(request.path);
657 | if (matches) {
658 | matches.shift();
659 | for (var i=0; i < matches.length; i++) {
660 | request.params[this.paramNames[i]] = matches[i];
661 | };
662 | };
663 |
664 | var handlers = Davis.utils.map(this.handlers, function (handler, i) {
665 | return function (req) {
666 | return handler.call(req, req, handlers[i+1])
667 | }
668 | })
669 |
670 | return handlers[0](request)
671 | }
672 |
673 | /**
674 | * Converts the route to a string representation of itself by combining the method and path
675 | * attributes.
676 | *
677 | * @returns {String} string representation of the route
678 | */
679 | Route.prototype.toString = function () {
680 | return [this.method, this.path].join(' ');
681 | }
682 |
683 | /*!
684 | * exposing the constructor
685 | * @private
686 | */
687 | return Route;
688 | })()
689 | /*!
690 | * Davis - router
691 | * Copyright (C) 2011 Oliver Nightingale
692 | * MIT Licensed
693 | */
694 |
695 | /**
696 | * A decorator that adds convinience methods to a Davis.App for easily creating instances
697 | * of Davis.Route and looking up routes for a particular request.
698 | *
699 | * Provides get, post put and delete method shortcuts for creating instances of Davis.Routes
700 | * with the corresponding method. This allows simple REST styled routing for a client side
701 | * JavaScript application.
702 | *
703 | * ### Example
704 | *
705 | * app.get('/foo/:id', function (req) {
706 | * // get the foo with id = req.params['id']
707 | * })
708 | *
709 | * app.post('/foo', function (req) {
710 | * // create a new instance of foo with req.params
711 | * })
712 | *
713 | * app.put('/foo/:id', function (req) {
714 | * // update the instance of foo with id = req.params['id']
715 | * })
716 | *
717 | * app.del('/foo/:id', function (req) {
718 | * // delete the instance of foo with id = req.params['id']
719 | * })
720 | *
721 | * As well as providing convinience methods for creating instances of Davis.Routes the router
722 | * also provides methods for creating special instances of routes called filters. Before filters
723 | * run before any matching route is run, and after filters run after any matched route has run.
724 | * A before filter can return false to halt the running of any matched routes or other before filters.
725 | *
726 | * A filter can take an optional path to match on, or without a path will match every request.
727 | *
728 | * ### Example
729 | *
730 | * app.before('/foo/:id', function (req) {
731 | * // will only run before request matching '/foo/:id'
732 | * })
733 | *
734 | * app.before(function (req) {
735 | * // will run before all routes
736 | * })
737 | *
738 | * app.after('/foo/:id', function (req) {
739 | * // will only run after routes matching '/foo/:id'
740 | * })
741 | *
742 | * app.after(function (req) {
743 | * // will run after all routes
744 | * })
745 | *
746 | * Another special kind of route, called state routes, are also generated using the router. State routes
747 | * are for requests that will not change the current page location. Instead the page location will remain
748 | * the same but the current state of the page has changed. This allows for states which the server will not
749 | * be expected to know about and support.
750 | *
751 | * ### Example
752 | *
753 | * app.state('/foo/:id', function (req) {
754 | * // will run when the app transitions into the '/foo/:id' state.
755 | * })
756 | *
757 | * Using the `trans` method an app can transition to these kind of states without changing the url location.
758 | *
759 | * For convinience routes can be defined within a common base scope, this is useful for keeping your route
760 | * definitions simpler and DRYer. A scope can either cover the whole app, or just a subset of the routes.
761 | *
762 | * ### Example
763 | *
764 | * app.scope('/foo', function () {
765 | * this.get('/:id', function () {
766 | * // will run for routes that match '/foo/:id'
767 | * })
768 | * })
769 | *
770 | * @module
771 | */
772 | Davis.router = function () {
773 |
774 | /**
775 | * Low level method for adding routes to your application.
776 | *
777 | * If called with just a method will return a partially applied function that can create routes with
778 | * that method. This is used internally to provide shortcuts for get, post, put, delete and state
779 | * routes.
780 | *
781 | * You normally want to use the higher level methods such as get and post, but this can be useful for extending
782 | * Davis to work with other kinds of requests.
783 | *
784 | * Example:
785 | *
786 | * app.route('get', '/foo', function (req) {
787 | * // will run when a get request is made to '/foo'
788 | * })
789 | *
790 | * app.patch = app.route('patch') // will return a function that can be used to handle requests with method of patch.
791 | * app.patch('/bar', function (req) {
792 | * // will run when a patch request is made to '/bar'
793 | * })
794 | *
795 | * @param {String} method The method for this route.
796 | * @param {String} path The path for this route.
797 | * @param {Function} handler The handler for this route, will be called with the request that triggered the route.
798 | * @returns {Davis.Route} the route that has just been created and added to the route list.
799 | * @memberOf router
800 | */
801 | this.route = function (method, path) {
802 | var createRoute = function (path) {
803 | var handlers = Davis.utils.toArray(arguments, 1),
804 | scope = scopePaths.join(''),
805 | fullPath, route
806 |
807 | (typeof path == 'string') ? fullPath = scope + path : fullPath = path
808 |
809 | route = new Davis.Route (method, fullPath, handlers)
810 |
811 | routeCollection.push(route)
812 | return route
813 | }
814 |
815 | return (arguments.length == 1) ? createRoute : createRoute.apply(this, Davis.utils.toArray(arguments, 1))
816 | }
817 |
818 | /**
819 | * A convinience wrapper around `app.route` for creating get routes.
820 | *
821 | * @param {String} path The path for this route.
822 | * @param {Function} handler The handler for this route, will be called with the request that triggered the route.
823 | * @returns {Davis.Route} the route that has just been created and added to the route list.
824 | * @see Davis.router.route
825 | * @memberOf router
826 | */
827 | this.get = this.route('get')
828 |
829 | /**
830 | * A convinience wrapper around `app.route` for creating post routes.
831 | *
832 | * @param {String} path The path for this route.
833 | * @param {Function} handler The handler for this route, will be called with the request that triggered the route.
834 | * @returns {Davis.Route} the route that has just been created and added to the route list.
835 | * @see Davis.router.route
836 | * @memberOf router
837 | */
838 | this.post = this.route('post')
839 |
840 | /**
841 | * A convinience wrapper around `app.route` for creating put routes.
842 | *
843 | * @param {String} path The path for this route.
844 | * @param {Function} handler The handler for this route, will be called with the request that triggered the route.
845 | * @returns {Davis.Route} the route that has just been created and added to the route list.
846 | * @see Davis.router.route
847 | * @memberOf router
848 | */
849 | this.put = this.route('put')
850 |
851 | /**
852 | * A convinience wrapper around `app.route` for creating delete routes.
853 | *
854 | * delete is a reserved word in javascript so use the `del` method when creating a Davis.Route with a method of delete.
855 | *
856 | * @param {String} path The path for this route.
857 | * @param {Function} handler The handler for this route, will be called with the request that triggered the route.
858 | * @returns {Davis.Route} the route that has just been created and added to the route list.
859 | * @see Davis.router.route
860 | * @memberOf router
861 | */
862 | this.del = this.route('delete')
863 |
864 | /**
865 | * Adds a state route into the apps route collection.
866 | *
867 | * These special kind of routes are not triggered by clicking links or submitting forms, instead they
868 | * are triggered manually by calling `trans`.
869 | *
870 | * Routes added using the state method act in the same way as other routes except that they generate
871 | * a route that is listening for requests that will not change the page location.
872 | *
873 | * Example:
874 | *
875 | * app.state('/foo/:id', function (req) {
876 | * // will run when the app transitions into the '/foo/:id' state.
877 | * })
878 | *
879 | * @param {String} path The path for this route, this will never be seen in the url bar.
880 | * @param {Function} handler The handler for this route, will be called with the request that triggered the route
881 | * @memberOf router
882 | *
883 | */
884 | this.state = this.route('state');
885 |
886 | /**
887 | * Modifies the scope of the router.
888 | *
889 | * If you have many routes that share a common path prefix you can use scope to reduce repeating
890 | * that path prefix.
891 | *
892 | * You can use `scope` in two ways, firstly you can set the scope for the whole app by calling scope
893 | * before defining routes. You can also provide a function to the scope method, and the scope will
894 | * only apply to those routes defined within this function. It is also possible to nest scopes within
895 | * other scopes.
896 | *
897 | * Example
898 | *
899 | * // using scope with a function
900 | * app.scope('/foo', function () {
901 | * this.get('/bar', function (req) {
902 | * // this route will have a path of '/foo/bar'
903 | * })
904 | * })
905 | *
906 | * // setting a global scope for the rest of the application
907 | * app.scope('/bar')
908 | *
909 | * // using scope with a function
910 | * app.scope('/foo', function () {
911 | * this.scope('/bar', function () {
912 | * this.get('/baz', function (req) {
913 | * // this route will have a path of '/foo/bar/baz'
914 | * })
915 | * })
916 | * })
917 | *
918 | * @memberOf router
919 | * @param {String} path The prefix to use as the scope
920 | * @param {Function} fn A function that will be executed with the router as its context and the path
921 | * as a prefix
922 | *
923 | */
924 | this.scope = function (path, fn) {
925 | scopePaths.push(path)
926 | if (arguments.length == 1) return
927 |
928 | fn.call(this, this)
929 | scopePaths.pop()
930 | }
931 |
932 | /**
933 | * Transitions the app into the state identified by the passed path parameter.
934 | *
935 | * This allows the app to enter states without changing the page path through a link click or form submit.
936 | * If there are handlers registered for this state, added by the `state` method, they will be triggered.
937 | *
938 | * This method generates a request with a method of 'state', in all other ways this request is identical
939 | * to those that are generated when clicking links etc.
940 | *
941 | * States transitioned to using this method will not be able to be revisited directly with a page load as
942 | * there is no url that represents the state.
943 | *
944 | * An optional second parameter can be passed which will be available to any handlers in the requests
945 | * params object.
946 | *
947 | * Example
948 | *
949 | * app.trans('/foo/1')
950 | *
951 | * app.trans('/foo/1', {
952 | * "bar": "baz"
953 | * })
954 | *
955 | *
956 | * @param {String} path The path that represents this state. This will not be seen in the url bar.
957 | * @param {Object} data Any additional data that should be sent with the request as params.
958 | * @memberOf router
959 | */
960 | this.trans = function (path, data) {
961 | if (data) {
962 | var fullPath = [path, decodeURIComponent(Davis.$.param(data))].join('?')
963 | } else {
964 | var fullPath = path
965 | };
966 |
967 | var req = new Davis.Request({
968 | method: 'state',
969 | fullPath: fullPath,
970 | title: ''
971 | })
972 |
973 | Davis.location.assign(req)
974 | }
975 |
976 | /*!
977 | * Generating convinience methods for creating filters using Davis.Routes and methods to
978 | * lookup filters.
979 | */
980 | this.filter = function (filterName) {
981 | return function () {
982 | var method = /.+/;
983 |
984 | if (arguments.length == 1) {
985 | var path = /.+/;
986 | var handler = arguments[0];
987 | } else if (arguments.length == 2) {
988 | var path = scopePaths.join('') + arguments[0];
989 | var handler = arguments[1];
990 | };
991 |
992 | var route = new Davis.Route (method, path, handler)
993 | filterCollection[filterName].push(route);
994 | return route
995 | }
996 | }
997 |
998 | this.lookupFilter = function (filterType) {
999 | return function (method, path) {
1000 | return Davis.utils.filter(filterCollection[filterType], function (route) {
1001 | return route.match(method, path)
1002 | });
1003 | }
1004 | }
1005 |
1006 | /**
1007 | * A convinience wrapper around `app.filter` for creating before filters.
1008 | *
1009 | * @param {String} path The optionl path for this filter.
1010 | * @param {Function} handler The handler for this filter, will be called with the request that triggered the route.
1011 | * @returns {Davis.Route} the route that has just been created and added to the route list.
1012 | * @memberOf router
1013 | */
1014 | this.before = this.filter('before')
1015 |
1016 | /**
1017 | * A convinience wrapper around `app.filter` for creating after filters.
1018 | *
1019 | * @param {String} path The optionl path for this filter.
1020 | * @param {Function} handler The handler for this filter, will be called with the request that triggered the route.
1021 | * @returns {Davis.Route} the route that has just been created and added to the route list.
1022 | * @memberOf router
1023 | */
1024 | this.after = this.filter('after')
1025 |
1026 | /**
1027 | * A convinience wrapper around `app.lookupFilter` for looking up before filters.
1028 | *
1029 | * @param {String} path The optionl path for this filter.
1030 | * @param {Function} handler The handler for this filter, will be called with the request that triggered the route.
1031 | * @returns {Davis.Route} the route that has just been created and added to the route list.
1032 | * @memberOf router
1033 | */
1034 | this.lookupBeforeFilter = this.lookupFilter('before')
1035 |
1036 | /**
1037 | * A convinience wrapper around `app.lookupFilter` for looking up after filters.
1038 | *
1039 | * @param {String} path The optionl path for this filter.
1040 | * @param {Function} handler The handler for this filter, will be called with the request that triggered the route.
1041 | * @returns {Davis.Route} the route that has just been created and added to the route list.
1042 | * @memberOf router
1043 | */
1044 | this.lookupAfterFilter = this.lookupFilter('after')
1045 |
1046 | /*!
1047 | * collections of routes and filters
1048 | * @private
1049 | */
1050 | var routeCollection = [];
1051 | var filterCollection = {
1052 | before: [],
1053 | after: []
1054 | };
1055 | var scopePaths = []
1056 |
1057 | /**
1058 | * Looks for the first route that matches the method and path from a request.
1059 | * Will only find and return the first matched route.
1060 | *
1061 | * @param {String} method the method to use when looking up a route
1062 | * @param {String} path the path to use when looking up a route
1063 | * @returns {Davis.Route} route
1064 | * @memberOf router
1065 | */
1066 | this.lookupRoute = function (method, path) {
1067 | return Davis.utils.filter(routeCollection, function (route) {
1068 | return route.match(method, path)
1069 | })[0];
1070 | };
1071 | }
1072 | /*!
1073 | * Davis - history
1074 | * Copyright (C) 2011 Oliver Nightingale
1075 | * MIT Licensed
1076 | */
1077 |
1078 | /**
1079 | * A module to normalize and enhance the window.pushState method and window.onpopstate event.
1080 | *
1081 | * Adds the ability to bind to whenever a new state is pushed onto the history stack and normalizes
1082 | * both of these events into an onChange event.
1083 | *
1084 | * @module
1085 | */
1086 | Davis.history = (function () {
1087 |
1088 | /*!
1089 | * storage for the push state handlers
1090 | * @private
1091 | */
1092 | var pushStateHandlers = [];
1093 |
1094 | /*!
1095 | * keep track of whether or not webkit like browsers have fired their initial
1096 | * page load popstate
1097 | * @private
1098 | */
1099 | var popped = false
1100 |
1101 | /*!
1102 | * Add a handler to the push state event. This event is not a native event but is fired
1103 | * every time a call to pushState is called.
1104 | *
1105 | * @param {Function} handler
1106 | * @private
1107 | */
1108 | function onPushState(handler) {
1109 | pushStateHandlers.push(handler);
1110 | };
1111 |
1112 | /*!
1113 | * Simple wrapper for the native onpopstate event.
1114 | *
1115 | * @param {Function} handler
1116 | * @private
1117 | */
1118 | function onPopState(handler) {
1119 | window.addEventListener('popstate', handler, true);
1120 | };
1121 |
1122 | /*!
1123 | * returns a handler that wraps the native event given onpopstate.
1124 | * When the page first loads or going back to a time in the history that was not added
1125 | * by pushState the event.state object will be null. This generates a request for the current
1126 | * location in those cases
1127 | *
1128 | * @param {Function} handler
1129 | * @private
1130 | */
1131 | function wrapped(handler) {
1132 | return function (event) {
1133 | if (event.state && event.state._davis) {
1134 | handler(new Davis.Request(event.state._davis))
1135 | } else {
1136 | if (popped) handler(Davis.Request.forPageLoad())
1137 | };
1138 | popped = true
1139 | }
1140 | }
1141 |
1142 | /*!
1143 | * provide a wrapper for any data that is going to be pushed into the history stack. All
1144 | * data is wrapped in a "_davis" namespace.
1145 | * @private
1146 | */
1147 | function wrapStateData(data) {
1148 | return {"_davis": data}
1149 | }
1150 |
1151 | /**
1152 | * Bind to the history on change event.
1153 | *
1154 | * This is not a native event but is fired any time a new state is pushed onto the history stack,
1155 | * the current history is replaced or a state is popped off the history stack.
1156 | * The handler function will be called with a request param which is an instance of Davis.Request.
1157 | *
1158 | * @param {Function} handler a function that will be called on push and pop state.
1159 | * @see Davis.Request
1160 | * @memberOf history
1161 | */
1162 | function onChange(handler) {
1163 | onPushState(handler);
1164 | onPopState(wrapped(handler));
1165 | };
1166 |
1167 | /*!
1168 | * returns a function for manipulating the history state and optionally calling any associated
1169 | * pushStateHandlers
1170 | *
1171 | * @param {String} methodName the name of the method to manipulate the history state with.
1172 | * @private
1173 | */
1174 | function changeStateWith (methodName) {
1175 | return function (request, opts) {
1176 | popped = true
1177 | history[methodName](wrapStateData(request.toJSON()), request.title, request.location());
1178 | if (opts && opts.silent) return
1179 | Davis.utils.forEach(pushStateHandlers, function (handler) {
1180 | handler(request);
1181 | });
1182 | }
1183 | }
1184 |
1185 | /**
1186 | * Pushes a request onto the history stack.
1187 | *
1188 | * This is used internally by Davis to push a new request
1189 | * resulting from either a form submit or a link click onto the history stack, it will also trigger
1190 | * the onpushstate event.
1191 | *
1192 | * An instance of Davis.Request is expected to be passed, however any object that has a title
1193 | * and a path property will also be accepted.
1194 | *
1195 | * @param {Davis.Request} request the location to be assinged as the current location.
1196 | * @memberOf history
1197 | */
1198 | var assign = changeStateWith('pushState')
1199 |
1200 | /**
1201 | * Replace the current state on the history stack.
1202 | *
1203 | * This is used internally by Davis when performing a redirect. This will trigger an onpushstate event.
1204 | *
1205 | * An instance of Davis.Request is expected to be passed, however any object that has a title
1206 | * and a path property will also be accepted.
1207 | *
1208 | * @param {Davis.Request} request the location to replace the current location with.
1209 | * @memberOf history
1210 | */
1211 | var replace = changeStateWith('replaceState')
1212 |
1213 | /**
1214 | * Returns the current location for the application.
1215 | *
1216 | * Davis.location delegates to this method for getting the apps current location.
1217 | *
1218 | * @memberOf history
1219 | */
1220 | function current() {
1221 | return window.location.pathname + (window.location.search ? window.location.search : '')
1222 | }
1223 |
1224 | /*!
1225 | * Exposing the public methods of this module
1226 | * @private
1227 | */
1228 | return {
1229 | onChange: onChange,
1230 | current: current,
1231 | assign: assign,
1232 | replace: replace
1233 | }
1234 | })()
1235 | /*!
1236 | * Davis - location
1237 | * Copyright (C) 2011 Oliver Nightingale
1238 | * MIT Licensed
1239 | */
1240 |
1241 | /**
1242 | * A module that acts as a delegator to any locationDelegate implementation. This abstracts the details of
1243 | * what is being used for the apps routing away from the rest of the library. This allows any kind of routing
1244 | * To be used with Davis as long as it can respond appropriatly to the given delegate methods.
1245 | *
1246 | * A routing module must respond to the following methods
1247 | *
1248 | * * __current__ : Should return the current location for the app
1249 | * * __assign__ : Should set the current location of the app based on the location of the passed request.
1250 | * * __replace__ : Should at least change the current location to the location of the passed request, for full compatibility it should not add any extra items in the history stack.
1251 | * * __onChange__ : Should add calbacks that will be fired whenever the location is changed.
1252 | *
1253 | * @module
1254 | *
1255 | */
1256 | Davis.location = (function () {
1257 |
1258 | /*!
1259 | * By default the Davis uses the Davis.history module for its routing, this gives HTML5 based pushState routing
1260 | * which is preferrable over location.hash based routing.
1261 | */
1262 | var locationDelegate = Davis.history
1263 |
1264 | /**
1265 | * Sets the current location delegate.
1266 | *
1267 | * The passed delegate will be used for all Davis apps. The delegate
1268 | * must respond to the following four methods `current`, `assign`, `replace` & `onChange`.
1269 | *
1270 | * @param {Object} the location delegate to use.
1271 | * @memberOf location
1272 | */
1273 | function setLocationDelegate(delegate) {
1274 | locationDelegate = delegate
1275 | }
1276 |
1277 | /**
1278 | * Delegates to the locationDelegate.current method.
1279 | *
1280 | * This should return the current location of the app.
1281 | *
1282 | * @memberOf location
1283 | */
1284 | function current() {
1285 | return locationDelegate.current()
1286 | }
1287 |
1288 | /*!
1289 | * Creates a function which sends the location delegate the passed message name.
1290 | * It handles converting a string path to an actual request
1291 | *
1292 | * @returns {Function} a function that calls the location delegate with the supplied method name
1293 | * @memberOf location
1294 | * @private
1295 | */
1296 | function sendLocationDelegate (methodName) {
1297 | return function (req, opts) {
1298 | if (typeof req == 'string') req = new Davis.Request (req)
1299 | locationDelegate[methodName](req, opts)
1300 | }
1301 | }
1302 |
1303 | /**
1304 | * Delegates to the locationDelegate.assign method.
1305 | *
1306 | * This should set the current location for the app to that of the passed request object.
1307 | *
1308 | * Can take either a Davis.Request or a string representing the path of the request to assign.
1309 | *
1310 | *
1311 | *
1312 | * @param {Request} req the request to replace the current location with, either a string or a Davis.Request.
1313 | * @param {Object} opts the optional options object that will be passed to the location delegate
1314 | * @see Davis.Request
1315 | * @memberOf location
1316 | */
1317 | var assign = sendLocationDelegate('assign')
1318 |
1319 | /**
1320 | * Delegates to the locationDelegate.replace method.
1321 | *
1322 | * This should replace the current location with that of the passed request.
1323 | * Ideally it should not create a new entry in the browsers history.
1324 | *
1325 | * Can take either a Davis.Request or a string representing the path of the request to assign.
1326 | *
1327 | * @param {Request} req the request to replace the current location with, either a string or a Davis.Request.
1328 | * @param {Object} opts the optional options object that will be passed to the location delegate
1329 | * @see Davis.Request
1330 | * @memberOf location
1331 | */
1332 | var replace = sendLocationDelegate('replace')
1333 |
1334 | /**
1335 | * Delegates to the locationDelegate.onChange method.
1336 | *
1337 | * This should add a callback that will be called any time the location changes.
1338 | * The handler function will be called with a request param which is an instance of Davis.Request.
1339 | *
1340 | * @param {Function} handler callback function to be called on location chnage.
1341 | * @see Davis.Request
1342 | * @memberOf location
1343 | *
1344 | */
1345 | function onChange(handler) {
1346 | locationDelegate.onChange(handler)
1347 | }
1348 |
1349 | /*!
1350 | * Exposing the public methods of this module
1351 | * @private
1352 | */
1353 | return {
1354 | setLocationDelegate: setLocationDelegate,
1355 | current: current,
1356 | assign: assign,
1357 | replace: replace,
1358 | onChange: onChange
1359 | }
1360 | })()
1361 | /*!
1362 | * Davis - Request
1363 | * Copyright (C) 2011 Oliver Nightingale
1364 | * MIT Licensed
1365 | */
1366 |
1367 | Davis.Request = (function () {
1368 |
1369 | /**
1370 | * Davis.Requests are created from click and submit events. Davis.Requests are passed to Davis.Routes
1371 | * and are stored in the history stack. They are instantiated by the Davis.listener module.
1372 | *
1373 | * A request will have a params object which will contain all query params and form params, any named
1374 | * params in a routes path will also be added to the requests params object. Also included is support
1375 | * for rails style nested form params.
1376 | *
1377 | * By default the request method will be taken from the method attribute for forms or will be defaulted
1378 | * to 'get' for links, however there is support for using a hidden field called _method in your forms
1379 | * to set the correct reqeust method.
1380 | *
1381 | * Simple get requests can be created by just passing a path when initializing a request, to set the method
1382 | * or title you have to pass in an object.
1383 | *
1384 | * Each request will have a timestamp property to make it easier to determine if the application is moving
1385 | * forward or backward through the history stack.
1386 | *
1387 | * Example
1388 | *
1389 | * var request = new Davis.Request ("/foo/12")
1390 | *
1391 | * var request = new Davis.Request ("/foo/12", {title: 'foo', method: 'POST'})
1392 | *
1393 | * var request = new Davis.Request({
1394 | * title: "foo",
1395 | * fullPath: "/foo/12",
1396 | * method: "get"
1397 | * })
1398 | *
1399 | * @constructor
1400 | * @param {String} fullPath
1401 | * @param {Object} opts An optional object with a title or method proprty
1402 | *
1403 | */
1404 | var Request = function (fullPath, opts) {
1405 | if (typeof fullPath == 'object') {
1406 | opts = fullPath
1407 | fullPath = opts.fullPath
1408 | delete opts.fullPath
1409 | }
1410 |
1411 | var raw = Davis.$.extend({}, {
1412 | title: "",
1413 | fullPath: fullPath,
1414 | method: "get",
1415 | timestamp: +new Date ()
1416 | }, opts)
1417 |
1418 | raw.fullPath = raw.fullPath.replace(/\+/g, '%20')
1419 |
1420 | var self = this;
1421 | this.raw = raw;
1422 | this.params = {};
1423 | this.title = raw.title;
1424 | this.queryString = raw.fullPath.split("?")[1];
1425 | this.timestamp = raw.timestamp;
1426 | this._staleCallback = function () {};
1427 |
1428 | if (this.queryString) {
1429 | Davis.utils.forEach(this.queryString.split("&"), function (keyval) {
1430 | var paramName = decodeURIComponent(keyval.split("=")[0]),
1431 | paramValue = keyval.split("=")[1],
1432 | nestedParamRegex = /^(\w+)\[(\w+)?\](\[\])?/,
1433 | nested;
1434 | if (nested = nestedParamRegex.exec(paramName)) {
1435 | var paramParent = nested[1];
1436 | var paramName = nested[2];
1437 | var isArray = !!nested[3];
1438 | var parentParams = self.params[paramParent] || {};
1439 |
1440 | if (isArray) {
1441 | parentParams[paramName] = parentParams[paramName] || [];
1442 | parentParams[paramName].push(decodeURIComponent(paramValue));
1443 | self.params[paramParent] = parentParams;
1444 | } else if (!paramName && !isArray) {
1445 | parentParams = self.params[paramParent] || []
1446 | parentParams.push(decodeURIComponent(paramValue))
1447 | self.params[paramParent] = parentParams
1448 | } else {
1449 | parentParams[paramName] = decodeURIComponent(paramValue);
1450 | self.params[paramParent] = parentParams;
1451 | }
1452 | } else {
1453 | self.params[paramName] = decodeURIComponent(paramValue);
1454 | };
1455 |
1456 | });
1457 | };
1458 |
1459 | raw.fullPath = raw.fullPath.replace(/^https?:\/\/.+?\//, '/');
1460 |
1461 | this.method = (this.params._method || raw.method).toLowerCase();
1462 |
1463 | this.path = raw.fullPath
1464 | .replace(/\?(.|[\r\n])+$/, "") // Remove the query string
1465 | .replace(/^https?:\/\/[^\/]+/, ""); // Remove the protocol and host parts
1466 |
1467 | this.fullPath = raw.fullPath;
1468 |
1469 | this.delegateToServer = raw.delegateToServer || Davis.noop;
1470 | this.isForPageLoad = raw.forPageLoad || false;
1471 |
1472 | if (Request.prev) Request.prev.makeStale(this);
1473 | Request.prev = this;
1474 |
1475 | };
1476 |
1477 | /**
1478 | * Redirects the current request to a new location.
1479 | *
1480 | * Calling redirect on an instance of Davis.Request will create a new request using the path and
1481 | * title of the current request. Redirected requests always have a method of 'get'.
1482 | *
1483 | * The request created will replace the current request in the history stack. Redirect is most
1484 | * often useful inside a handler for a form submit. After succesfully handling the form the app
1485 | * can redirect to another path. This means that the current form will not be re-submitted if
1486 | * navigating through the history with the back or forward buttons because the request that the
1487 | * submit generated has been replaced in the history stack.
1488 | *
1489 | * Example
1490 | *
1491 | * this.post('/foo', function (req) {
1492 | * processFormRequest(req.params) // do something with the form request
1493 | * req.redirect('/bar');
1494 | * })
1495 | *
1496 | * @param {String} path The path to redirect the current request to
1497 | * @param {Object} opts The optional options object that will be passed through to the location
1498 | * @memberOf Request
1499 | */
1500 | Request.prototype.redirect = function (path, opts) {
1501 | Davis.location.replace(new Request ({
1502 | method: 'get',
1503 | fullPath: path,
1504 | title: this.title
1505 | }), opts);
1506 | };
1507 |
1508 | /**
1509 | * Adds a callback to be called when the request is stale.
1510 | * A request becomes stale when it is no longer the current request, this normally occurs when a
1511 | * new request is triggered. A request can be marked as stale manually if required. The callback
1512 | * passed to whenStale will be called with the new request that is making the current request stale.
1513 | *
1514 | * Use the whenStale callback to 'teardown' the objects required for the current route, this gives
1515 | * a chance for views to hide themselves and unbind any event handlers etc.
1516 | *
1517 | * Example
1518 | *
1519 | * this.get('/foo', function (req) {
1520 | * var fooView = new FooView ()
1521 | * fooView.render() // display the foo view
1522 | * req.whenStale(function (nextReq) {
1523 | * fooView.remove() // stop displaying foo view and unbind any events
1524 | * })
1525 | * })
1526 | *
1527 | * @param {Function} callback A single callback that will be called when the request becomes stale.
1528 | * @memberOf Request
1529 | *
1530 | */
1531 | Request.prototype.whenStale = function (callback) {
1532 | this._staleCallback = callback;
1533 | }
1534 |
1535 | /**
1536 | * Mark the request as stale.
1537 | *
1538 | * This will cause the whenStale callback to be called.
1539 | *
1540 | * @param {Davis.Request} req The next request that has been recieved.
1541 | * @memberOf Request
1542 | */
1543 | Request.prototype.makeStale = function (req) {
1544 | this._staleCallback.call(req, req);
1545 | }
1546 |
1547 | /**
1548 | * Returns the location or path that should be pushed onto the history stack.
1549 | *
1550 | * For get requests this will be the same as the path, for post, put, delete and state requests this will
1551 | * be blank as no location should be pushed onto the history stack.
1552 | *
1553 | * @returns {String} string The location that the url bar should display and should be pushed onto the history stack for this request.
1554 | * @memberOf Request
1555 | */
1556 | Request.prototype.location = function () {
1557 | return (this.method === 'get') ? decodeURI(this.fullPath) : ''
1558 | }
1559 |
1560 | /**
1561 | * Converts the request to a string representation of itself by combining the method and fullPath
1562 | * attributes.
1563 | *
1564 | * @returns {String} string representation of the request
1565 | * @memberOf Request
1566 | */
1567 | Request.prototype.toString = function () {
1568 | return [this.method.toUpperCase(), this.path].join(" ")
1569 | };
1570 |
1571 | /**
1572 | * Converts the request to a plain object which can be converted to a JSON string.
1573 | *
1574 | * Used when pushing a request onto the history stack.
1575 | *
1576 | * @returns {Object} a plain object representation of the request.
1577 | * @memberOf Request
1578 | */
1579 | Request.prototype.toJSON = function () {
1580 | return {
1581 | title: this.raw.title,
1582 | fullPath: this.raw.fullPath,
1583 | method: this.raw.method,
1584 | timestamp: this.raw.timestamp
1585 | }
1586 | }
1587 |
1588 | /**
1589 | * Creates a new request for the page on page load.
1590 | *
1591 | * This is required because usually requests are generated from clicking links or submitting forms
1592 | * however this doesn't happen on a page load but should still be considered a request that the
1593 | * JavaScript app should handle.
1594 | *
1595 | * @returns {Davis.Request} A request representing the current page loading.
1596 | * @memberOf Request
1597 | */
1598 | Request.forPageLoad = function () {
1599 | return new this ({
1600 | method: 'get',
1601 | // fullPath: window.location.pathname,
1602 | fullPath: Davis.location.current(),
1603 | title: document.title,
1604 | forPageLoad: true
1605 | });
1606 | }
1607 |
1608 | /*!
1609 | * Stores the last request
1610 | * @private
1611 | */
1612 | Request.prev = null
1613 |
1614 | return Request
1615 |
1616 | })()
1617 | /*!
1618 | * Davis - App
1619 | * Copyright (C) 2011 Oliver Nightingale
1620 | * MIT Licensed
1621 | */
1622 |
1623 | Davis.App = (function () {
1624 |
1625 | /**
1626 | * Constructor for Davis.App
1627 | *
1628 | * @constructor
1629 | * @returns {Davis.App}
1630 | */
1631 | function App() {
1632 | this.running = false;
1633 | this.boundToInternalEvents = false;
1634 |
1635 | this.use(Davis.listener)
1636 | this.use(Davis.event)
1637 | this.use(Davis.router)
1638 | this.use(Davis.logger)
1639 | };
1640 |
1641 | /**
1642 | * A convinience function for changing the apps default settings.
1643 | *
1644 | * Should be used before starting the app to ensure any new settings
1645 | * are picked up and used.
1646 | *
1647 | * Example:
1648 | *
1649 | * app.configure(function (config) {
1650 | * config.linkSelector = 'a.davis'
1651 | * config.formSelector = 'form.davis'
1652 | * })
1653 | *
1654 | * @param {Function} config This function will be executed with the context bound to the apps setting object, this will also be passed as the first argument to the function.
1655 | */
1656 | App.prototype.configure = function(config) {
1657 | config.call(this.settings, this.settings);
1658 | };
1659 |
1660 | /**
1661 | * Method to include a plugin in this app.
1662 | *
1663 | * A plugin is just a function that will be evaluated in the context of the app.
1664 | *
1665 | * Example:
1666 | * app.use(Davis.title)
1667 | *
1668 | * @param {Function} plugin The plugin to use
1669 | *
1670 | */
1671 | App.prototype.use = function(plugin) {
1672 | plugin.apply(this, Davis.utils.toArray(arguments, 1))
1673 | };
1674 |
1675 | /**
1676 | * Method to add helper properties to all requests in the application.
1677 | *
1678 | * Helpers will be added to the Davis.Request.prototype. Care should be taken not to override any existing Davis.Request
1679 | * methods.
1680 | *
1681 | * @param {Object} helpers An object containing helpers to mixin to the request
1682 | */
1683 | App.prototype.helpers = function(helpers) {
1684 | for (property in helpers) {
1685 | if (helpers.hasOwnProperty(property)) Davis.Request.prototype[property] = helpers[property]
1686 | }
1687 | };
1688 |
1689 | /**
1690 | * Settings for the app. These may be overriden directly or by using the configure
1691 | * convinience method.
1692 | *
1693 | * `linkSelector` is the jquery selector for all the links on the page that you want
1694 | * Davis to respond to. These links will not trigger a normal http request.
1695 | *
1696 | * `formSelector` is similar to link selector but for all the forms that davis will bind to
1697 | *
1698 | * `throwErrors` decides whether or not any errors will be caugth by Davis. If this is set to true
1699 | * errors will be thrown so that the request will not be handled by JavaScript, the server will have
1700 | * to provide a response. When set to false errors in a route will be caught and the server will not
1701 | * receive the request.
1702 | *
1703 | * `handleRouteNotFound` determines whether or not Davis should handle requests when there is no matching
1704 | * route. If set to false Davis will allow the request to be passed to your server to handle if no matching
1705 | * route can be found.
1706 | *
1707 | * `generateRequestOnPageLoad` determines whether a request should be generated for the initial page load.
1708 | * by default this is set to false. A Davis.Request will not be generated with the path of the current
1709 | * page. Setting this to true will cause a request to be passed to your app for the inital page load.
1710 | *
1711 | * @see #configure
1712 | */
1713 |
1714 | App.prototype.settings = {
1715 | linkSelector: 'a',
1716 | formSelector: 'form',
1717 | throwErrors: true,
1718 | handleRouteNotFound: false,
1719 | generateRequestOnPageLoad: false
1720 | };
1721 |
1722 | /**
1723 | * Starts the app's routing.
1724 | *
1725 | * Apps created using the convinience Davis() function are automatically started.
1726 | *
1727 | * Starting the app binds all links and forms, so clicks and submits
1728 | * create Davis requests that will be pushed onto the browsers history stack. Browser history change
1729 | * events will be picked up and the request that caused the change will be matched against the apps
1730 | * routes and filters.
1731 | */
1732 | App.prototype.start = function(){
1733 | var self = this;
1734 |
1735 | if (this.running) return
1736 |
1737 | if (!Davis.supported()) {
1738 | this.trigger('unsupported')
1739 | return
1740 | };
1741 |
1742 | var runFilterWith = function (request) {
1743 | return function (filter) {
1744 | var result = filter.run(request, request);
1745 | return (typeof result === "undefined" || result);
1746 | }
1747 | }
1748 |
1749 | var beforeFiltersPass = function (request) {
1750 | return Davis.utils.every(
1751 | self.lookupBeforeFilter(request.method, request.path),
1752 | runFilterWith(request)
1753 | )
1754 | }
1755 |
1756 | var handleRequest = function (request) {
1757 | if (beforeFiltersPass(request)) {
1758 | self.trigger('lookupRoute', request)
1759 | var route = self.lookupRoute(request.method, request.path);
1760 | if (route) {
1761 | self.trigger('runRoute', request, route);
1762 |
1763 | try {
1764 | route.run(request)
1765 | self.trigger('routeComplete', request, route)
1766 | } catch (error) {
1767 | self.trigger('routeError', request, route, error)
1768 | }
1769 |
1770 | Davis.utils.every(
1771 | self.lookupAfterFilter(request.method, request.path),
1772 | runFilterWith(request)
1773 | );
1774 |
1775 | } else {
1776 | self.trigger('routeNotFound', request);
1777 | }
1778 | } else {
1779 | self.trigger('requestHalted', request)
1780 | }
1781 | }
1782 |
1783 | var bindToInternalEvents = function () {
1784 | self
1785 | .bind('runRoute', function (request) {
1786 | self.logger.info("runRoute: " + request.toString());
1787 | })
1788 | .bind('routeNotFound', function (request) {
1789 | if (!self.settings.handleRouteNotFound && !request.isForPageLoad) {
1790 | self.stop()
1791 | request.delegateToServer()
1792 | };
1793 | self.logger.warn("routeNotFound: " + request.toString());
1794 | })
1795 | .bind('start', function () {
1796 | self.logger.info("application started")
1797 | })
1798 | .bind('stop', function () {
1799 | self.logger.info("application stopped")
1800 | })
1801 | .bind('routeError', function (request, route, error) {
1802 | if (self.settings.throwErrors) throw(error)
1803 | self.logger.error(error.message, error.stack)
1804 | });
1805 |
1806 | Davis.location.onChange(function (req) {
1807 | handleRequest(req)
1808 | });
1809 |
1810 | self.boundToInternalEvents = true
1811 | }
1812 |
1813 | if (!this.boundToInternalEvents) bindToInternalEvents()
1814 |
1815 | this.listen();
1816 | this.trigger('start')
1817 | this.running = true;
1818 |
1819 | if (this.settings.generateRequestOnPageLoad) handleRequest(Davis.Request.forPageLoad())
1820 |
1821 | };
1822 |
1823 | /**
1824 | * Stops the app's routing.
1825 | *
1826 | * Stops the app listening to clicks and submits on all forms and links found using the current
1827 | * apps settings.
1828 | */
1829 | App.prototype.stop = function() {
1830 | this.unlisten();
1831 | this.trigger('stop')
1832 | this.running = false
1833 | };
1834 |
1835 | return App;
1836 | })()
1837 |
--------------------------------------------------------------------------------
/src/vendor/zepto.min.js:
--------------------------------------------------------------------------------
1 | /* Zepto v1.0-1-ga3cab6c - polyfill zepto detect event ajax form fx - zeptojs.com/license */
2 | (function(a){String.prototype.trim===a&&(String.prototype.trim=function(){return this.replace(/^\s+|\s+$/g,"")}),Array.prototype.reduce===a&&(Array.prototype.reduce=function(b){if(this===void 0||this===null)throw new TypeError;var c=Object(this),d=c.length>>>0,e=0,f;if(typeof b!="function")throw new TypeError;if(d==0&&arguments.length==1)throw new TypeError;if(arguments.length>=2)f=arguments[1];else do{if(e in c){f=c[e++];break}if(++e>=d)throw new TypeError}while(!0);while(e0?c.fn.concat.apply([],a):a}function O(a){return a.replace(/::/g,"/").replace(/([A-Z]+)([A-Z][a-z])/g,"$1_$2").replace(/([a-z\d])([A-Z])/g,"$1_$2").replace(/_/g,"-").toLowerCase()}function P(a){return a in j?j[a]:j[a]=new RegExp("(^|\\s)"+a+"(\\s|$)")}function Q(a,b){return typeof b=="number"&&!l[O(a)]?b+"px":b}function R(a){var b,c;return i[a]||(b=h.createElement(a),h.body.appendChild(b),c=k(b,"").getPropertyValue("display"),b.parentNode.removeChild(b),c=="none"&&(c="block"),i[a]=c),i[a]}function S(a){return"children"in a?f.call(a.children):c.map(a.childNodes,function(a){if(a.nodeType==1)return a})}function T(c,d,e){for(b in d)e&&(J(d[b])||K(d[b]))?(J(d[b])&&!J(c[b])&&(c[b]={}),K(d[b])&&!K(c[b])&&(c[b]=[]),T(c[b],d[b],e)):d[b]!==a&&(c[b]=d[b])}function U(b,d){return d===a?c(b):c(b).filter(d)}function V(a,b,c,d){return F(b)?b.call(a,c,d):b}function W(a,b,c){c==null?a.removeAttribute(b):a.setAttribute(b,c)}function X(b,c){var d=b.className,e=d&&d.baseVal!==a;if(c===a)return e?d.baseVal:d;e?d.baseVal=c:b.className=c}function Y(a){var b;try{return a?a=="true"||(a=="false"?!1:a=="null"?null:isNaN(b=Number(a))?/^[\[\{]/.test(a)?c.parseJSON(a):a:b):a}catch(d){return a}}function Z(a,b){b(a);for(var c in a.childNodes)Z(a.childNodes[c],b)}var a,b,c,d,e=[],f=e.slice,g=e.filter,h=window.document,i={},j={},k=h.defaultView.getComputedStyle,l={"column-count":1,columns:1,"font-weight":1,"line-height":1,opacity:1,"z-index":1,zoom:1},m=/^\s*<(\w+|!)[^>]*>/,n=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,o=/^(?:body|html)$/i,p=["val","css","html","text","data","width","height","offset"],q=["after","prepend","before","append"],r=h.createElement("table"),s=h.createElement("tr"),t={tr:h.createElement("tbody"),tbody:r,thead:r,tfoot:r,td:s,th:s,"*":h.createElement("div")},u=/complete|loaded|interactive/,v=/^\.([\w-]+)$/,w=/^#([\w-]*)$/,x=/^[\w-]+$/,y={},z=y.toString,A={},B,C,D=h.createElement("div");return A.matches=function(a,b){if(!a||a.nodeType!==1)return!1;var c=a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.matchesSelector;if(c)return c.call(a,b);var d,e=a.parentNode,f=!e;return f&&(e=D).appendChild(a),d=~A.qsa(e,b).indexOf(a),f&&D.removeChild(a),d},B=function(a){return a.replace(/-+(.)?/g,function(a,b){return b?b.toUpperCase():""})},C=function(a){return g.call(a,function(b,c){return a.indexOf(b)==c})},A.fragment=function(b,d,e){b.replace&&(b=b.replace(n,"<$1>$2>")),d===a&&(d=m.test(b)&&RegExp.$1),d in t||(d="*");var g,h,i=t[d];return i.innerHTML=""+b,h=c.each(f.call(i.childNodes),function(){i.removeChild(this)}),J(e)&&(g=c(h),c.each(e,function(a,b){p.indexOf(a)>-1?g[a](b):g.attr(a,b)})),h},A.Z=function(a,b){return a=a||[],a.__proto__=c.fn,a.selector=b||"",a},A.isZ=function(a){return a instanceof A.Z},A.init=function(b,d){if(!b)return A.Z();if(F(b))return c(h).ready(b);if(A.isZ(b))return b;var e;if(K(b))e=M(b);else if(I(b))e=[J(b)?c.extend({},b):b],b=null;else if(m.test(b))e=A.fragment(b.trim(),RegExp.$1,d),b=null;else{if(d!==a)return c(d).find(b);e=A.qsa(h,b)}return A.Z(e,b)},c=function(a,b){return A.init(a,b)},c.extend=function(a){var b,c=f.call(arguments,1);return typeof a=="boolean"&&(b=a,a=c.shift()),c.forEach(function(c){T(a,c,b)}),a},A.qsa=function(a,b){var c;return H(a)&&w.test(b)?(c=a.getElementById(RegExp.$1))?[c]:[]:a.nodeType!==1&&a.nodeType!==9?[]:f.call(v.test(b)?a.getElementsByClassName(RegExp.$1):x.test(b)?a.getElementsByTagName(b):a.querySelectorAll(b))},c.contains=function(a,b){return a!==b&&a.contains(b)},c.type=E,c.isFunction=F,c.isWindow=G,c.isArray=K,c.isPlainObject=J,c.isEmptyObject=function(a){var b;for(b in a)return!1;return!0},c.inArray=function(a,b,c){return e.indexOf.call(b,a,c)},c.camelCase=B,c.trim=function(a){return a.trim()},c.uuid=0,c.support={},c.expr={},c.map=function(a,b){var c,d=[],e,f;if(L(a))for(e=0;e=0?b:b+this.length]},toArray:function(){return this.get()},size:function(){return this.length},remove:function(){return this.each(function(){this.parentNode!=null&&this.parentNode.removeChild(this)})},each:function(a){return e.every.call(this,function(b,c){return a.call(b,c,b)!==!1}),this},filter:function(a){return F(a)?this.not(this.not(a)):c(g.call(this,function(b){return A.matches(b,a)}))},add:function(a,b){return c(C(this.concat(c(a,b))))},is:function(a){return this.length>0&&A.matches(this[0],a)},not:function(b){var d=[];if(F(b)&&b.call!==a)this.each(function(a){b.call(this,a)||d.push(this)});else{var e=typeof b=="string"?this.filter(b):L(b)&&F(b.item)?f.call(b):c(b);this.forEach(function(a){e.indexOf(a)<0&&d.push(a)})}return c(d)},has:function(a){return this.filter(function(){return I(a)?c.contains(this,a):c(this).find(a).size()})},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){var a=this[0];return a&&!I(a)?a:c(a)},last:function(){var a=this[this.length-1];return a&&!I(a)?a:c(a)},find:function(a){var b,d=this;return typeof a=="object"?b=c(a).filter(function(){var a=this;return e.some.call(d,function(b){return c.contains(b,a)})}):this.length==1?b=c(A.qsa(this[0],a)):b=this.map(function(){return A.qsa(this,a)}),b},closest:function(a,b){var d=this[0],e=!1;typeof a=="object"&&(e=c(a));while(d&&!(e?e.indexOf(d)>=0:A.matches(d,a)))d=d!==b&&!H(d)&&d.parentNode;return c(d)},parents:function(a){var b=[],d=this;while(d.length>0)d=c.map(d,function(a){if((a=a.parentNode)&&!H(a)&&b.indexOf(a)<0)return b.push(a),a});return U(b,a)},parent:function(a){return U(C(this.pluck("parentNode")),a)},children:function(a){return U(this.map(function(){return S(this)}),a)},contents:function(){return this.map(function(){return f.call(this.childNodes)})},siblings:function(a){return U(this.map(function(a,b){return g.call(S(b.parentNode),function(a){return a!==b})}),a)},empty:function(){return this.each(function(){this.innerHTML=""})},pluck:function(a){return c.map(this,function(b){return b[a]})},show:function(){return this.each(function(){this.style.display=="none"&&(this.style.display=null),k(this,"").getPropertyValue("display")=="none"&&(this.style.display=R(this.nodeName))})},replaceWith:function(a){return this.before(a).remove()},wrap:function(a){var b=F(a);if(this[0]&&!b)var d=c(a).get(0),e=d.parentNode||this.length>1;return this.each(function(f){c(this).wrapAll(b?a.call(this,f):e?d.cloneNode(!0):d)})},wrapAll:function(a){if(this[0]){c(this[0]).before(a=c(a));var b;while((b=a.children()).length)a=b.first();c(a).append(this)}return this},wrapInner:function(a){var b=F(a);return this.each(function(d){var e=c(this),f=e.contents(),g=b?a.call(this,d):a;f.length?f.wrapAll(g):e.append(g)})},unwrap:function(){return this.parent().each(function(){c(this).replaceWith(c(this).children())}),this},clone:function(){return this.map(function(){return this.cloneNode(!0)})},hide:function(){return this.css("display","none")},toggle:function(b){return this.each(function(){var d=c(this);(b===a?d.css("display")=="none":b)?d.show():d.hide()})},prev:function(a){return c(this.pluck("previousElementSibling")).filter(a||"*")},next:function(a){return c(this.pluck("nextElementSibling")).filter(a||"*")},html:function(b){return b===a?this.length>0?this[0].innerHTML:null:this.each(function(a){var d=this.innerHTML;c(this).empty().append(V(this,b,a,d))})},text:function(b){return b===a?this.length>0?this[0].textContent:null:this.each(function(){this.textContent=b})},attr:function(c,d){var e;return typeof c=="string"&&d===a?this.length==0||this[0].nodeType!==1?a:c=="value"&&this[0].nodeName=="INPUT"?this.val():!(e=this[0].getAttribute(c))&&c in this[0]?this[0][c]:e:this.each(function(a){if(this.nodeType!==1)return;if(I(c))for(b in c)W(this,b,c[b]);else W(this,c,V(this,d,a,this.getAttribute(c)))})},removeAttr:function(a){return this.each(function(){this.nodeType===1&&W(this,a)})},prop:function(b,c){return c===a?this[0]&&this[0][b]:this.each(function(a){this[b]=V(this,c,a,this[b])})},data:function(b,c){var d=this.attr("data-"+O(b),c);return d!==null?Y(d):a},val:function(b){return b===a?this[0]&&(this[0].multiple?c(this[0]).find("option").filter(function(a){return this.selected}).pluck("value"):this[0].value):this.each(function(a){this.value=V(this,b,a,this.value)})},offset:function(a){if(a)return this.each(function(b){var d=c(this),e=V(this,a,b,d.offset()),f=d.offsetParent().offset(),g={top:e.top-f.top,left:e.left-f.left};d.css("position")=="static"&&(g.position="relative"),d.css(g)});if(this.length==0)return null;var b=this[0].getBoundingClientRect();return{left:b.left+window.pageXOffset,top:b.top+window.pageYOffset,width:Math.round(b.width),height:Math.round(b.height)}},css:function(a,c){if(arguments.length<2&&typeof a=="string")return this[0]&&(this[0].style[B(a)]||k(this[0],"").getPropertyValue(a));var d="";if(E(a)=="string")!c&&c!==0?this.each(function(){this.style.removeProperty(O(a))}):d=O(a)+":"+Q(a,c);else for(b in a)!a[b]&&a[b]!==0?this.each(function(){this.style.removeProperty(O(b))}):d+=O(b)+":"+Q(b,a[b])+";";return this.each(function(){this.style.cssText+=";"+d})},index:function(a){return a?this.indexOf(c(a)[0]):this.parent().children().indexOf(this[0])},hasClass:function(a){return e.some.call(this,function(a){return this.test(X(a))},P(a))},addClass:function(a){return this.each(function(b){d=[];var e=X(this),f=V(this,a,b,e);f.split(/\s+/g).forEach(function(a){c(this).hasClass(a)||d.push(a)},this),d.length&&X(this,e+(e?" ":"")+d.join(" "))})},removeClass:function(b){return this.each(function(c){if(b===a)return X(this,"");d=X(this),V(this,b,c,d).split(/\s+/g).forEach(function(a){d=d.replace(P(a)," ")}),X(this,d.trim())})},toggleClass:function(b,d){return this.each(function(e){var f=c(this),g=V(this,b,e,X(this));g.split(/\s+/g).forEach(function(b){(d===a?!f.hasClass(b):d)?f.addClass(b):f.removeClass(b)})})},scrollTop:function(){if(!this.length)return;return"scrollTop"in this[0]?this[0].scrollTop:this[0].scrollY},position:function(){if(!this.length)return;var a=this[0],b=this.offsetParent(),d=this.offset(),e=o.test(b[0].nodeName)?{top:0,left:0}:b.offset();return d.top-=parseFloat(c(a).css("margin-top"))||0,d.left-=parseFloat(c(a).css("margin-left"))||0,e.top+=parseFloat(c(b[0]).css("border-top-width"))||0,e.left+=parseFloat(c(b[0]).css("border-left-width"))||0,{top:d.top-e.top,left:d.left-e.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||h.body;while(a&&!o.test(a.nodeName)&&c(a).css("position")=="static")a=a.offsetParent;return a})}},c.fn.detach=c.fn.remove,["width","height"].forEach(function(b){c.fn[b]=function(d){var e,f=this[0],g=b.replace(/./,function(a){return a[0].toUpperCase()});return d===a?G(f)?f["inner"+g]:H(f)?f.documentElement["offset"+g]:(e=this.offset())&&e[b]:this.each(function(a){f=c(this),f.css(b,V(this,d,a,f[b]()))})}}),q.forEach(function(a,b){var d=b%2;c.fn[a]=function(){var a,e=c.map(arguments,function(b){return a=E(b),a=="object"||a=="array"||b==null?b:A.fragment(b)}),f,g=this.length>1;return e.length<1?this:this.each(function(a,h){f=d?h:h.parentNode,h=b==0?h.nextSibling:b==1?h.firstChild:b==2?h:null,e.forEach(function(a){if(g)a=a.cloneNode(!0);else if(!f)return c(a).remove();Z(f.insertBefore(a,h),function(a){a.nodeName!=null&&a.nodeName.toUpperCase()==="SCRIPT"&&(!a.type||a.type==="text/javascript")&&!a.src&&window.eval.call(window,a.innerHTML)})})})},c.fn[d?a+"To":"insert"+(b?"Before":"After")]=function(b){return c(b)[a](this),this}}),A.Z.prototype=c.fn,A.uniq=C,A.deserializeValue=Y,c.zepto=A,c}();window.Zepto=Zepto,"$"in window||(window.$=Zepto),function(a){function b(a){var b=this.os={},c=this.browser={},d=a.match(/WebKit\/([\d.]+)/),e=a.match(/(Android)\s+([\d.]+)/),f=a.match(/(iPad).*OS\s([\d_]+)/),g=!f&&a.match(/(iPhone\sOS)\s([\d_]+)/),h=a.match(/(webOS|hpwOS)[\s\/]([\d.]+)/),i=h&&a.match(/TouchPad/),j=a.match(/Kindle\/([\d.]+)/),k=a.match(/Silk\/([\d._]+)/),l=a.match(/(BlackBerry).*Version\/([\d.]+)/),m=a.match(/(BB10).*Version\/([\d.]+)/),n=a.match(/(RIM\sTablet\sOS)\s([\d.]+)/),o=a.match(/PlayBook/),p=a.match(/Chrome\/([\d.]+)/)||a.match(/CriOS\/([\d.]+)/),q=a.match(/Firefox\/([\d.]+)/);if(c.webkit=!!d)c.version=d[1];e&&(b.android=!0,b.version=e[2]),g&&(b.ios=b.iphone=!0,b.version=g[2].replace(/_/g,".")),f&&(b.ios=b.ipad=!0,b.version=f[2].replace(/_/g,".")),h&&(b.webos=!0,b.version=h[2]),i&&(b.touchpad=!0),l&&(b.blackberry=!0,b.version=l[2]),m&&(b.bb10=!0,b.version=m[2]),n&&(b.rimtabletos=!0,b.version=n[2]),o&&(c.playbook=!0),j&&(b.kindle=!0,b.version=j[1]),k&&(c.silk=!0,c.version=k[1]),!k&&b.android&&a.match(/Kindle Fire/)&&(c.silk=!0),p&&(c.chrome=!0,c.version=p[1]),q&&(c.firefox=!0,c.version=q[1]),b.tablet=!!(f||o||e&&!a.match(/Mobile/)||q&&a.match(/Tablet/)),b.phone=!b.tablet&&!!(e||g||h||l||m||p&&a.match(/Android/)||p&&a.match(/CriOS\/([\d.]+)/)||q&&a.match(/Mobile/))}b.call(a,navigator.userAgent),a.__detect=b}(Zepto),function(a){function g(a){return a._zid||(a._zid=d++)}function h(a,b,d,e){b=i(b);if(b.ns)var f=j(b.ns);return(c[g(a)]||[]).filter(function(a){return a&&(!b.e||a.e==b.e)&&(!b.ns||f.test(a.ns))&&(!d||g(a.fn)===g(d))&&(!e||a.sel==e)})}function i(a){var b=(""+a).split(".");return{e:b[0],ns:b.slice(1).sort().join(" ")}}function j(a){return new RegExp("(?:^| )"+a.replace(" "," .* ?")+"(?: |$)")}function k(b,c,d){a.type(b)!="string"?a.each(b,d):b.split(/\s/).forEach(function(a){d(a,c)})}function l(a,b){return a.del&&(a.e=="focus"||a.e=="blur")||!!b}function m(a){return f[a]||a}function n(b,d,e,h,j,n){var o=g(b),p=c[o]||(c[o]=[]);k(d,e,function(c,d){var e=i(c);e.fn=d,e.sel=h,e.e in f&&(d=function(b){var c=b.relatedTarget;if(!c||c!==this&&!a.contains(this,c))return e.fn.apply(this,arguments)}),e.del=j&&j(d,c);var g=e.del||d;e.proxy=function(a){var c=g.apply(b,[a].concat(a.data));return c===!1&&(a.preventDefault(),a.stopPropagation()),c},e.i=p.length,p.push(e),b.addEventListener(m(e.e),e.proxy,l(e,n))})}function o(a,b,d,e,f){var i=g(a);k(b||"",d,function(b,d){h(a,b,d,e).forEach(function(b){delete c[i][b.i],a.removeEventListener(m(b.e),b.proxy,l(b,f))})})}function t(b){var c,d={originalEvent:b};for(c in b)!r.test(c)&&b[c]!==undefined&&(d[c]=b[c]);return a.each(s,function(a,c){d[a]=function(){return this[c]=p,b[a].apply(b,arguments)},d[c]=q}),d}function u(a){if(!("defaultPrevented"in a)){a.defaultPrevented=!1;var b=a.preventDefault;a.preventDefault=function(){this.defaultPrevented=!0,b.call(this)}}}var b=a.zepto.qsa,c={},d=1,e={},f={mouseenter:"mouseover",mouseleave:"mouseout"};e.click=e.mousedown=e.mouseup=e.mousemove="MouseEvents",a.event={add:n,remove:o},a.proxy=function(b,c){if(a.isFunction(b)){var d=function(){return b.apply(c,arguments)};return d._zid=g(b),d}if(typeof c=="string")return a.proxy(b[c],b);throw new TypeError("expected function")},a.fn.bind=function(a,b){return this.each(function(){n(this,a,b)})},a.fn.unbind=function(a,b){return this.each(function(){o(this,a,b)})},a.fn.one=function(a,b){return this.each(function(c,d){n(this,a,b,null,function(a,b){return function(){var c=a.apply(d,arguments);return o(d,b,a),c}})})};var p=function(){return!0},q=function(){return!1},r=/^([A-Z]|layer[XY]$)/,s={preventDefault:"isDefaultPrevented",stopImmediatePropagation:"isImmediatePropagationStopped",stopPropagation:"isPropagationStopped"};a.fn.delegate=function(b,c,d){return this.each(function(e,f){n(f,c,d,b,function(c){return function(d){var e,g=a(d.target).closest(b,f).get(0);if(g)return e=a.extend(t(d),{currentTarget:g,liveFired:f}),c.apply(g,[e].concat([].slice.call(arguments,1)))}})})},a.fn.undelegate=function(a,b,c){return this.each(function(){o(this,b,c,a)})},a.fn.live=function(b,c){return a(document.body).delegate(this.selector,b,c),this},a.fn.die=function(b,c){return a(document.body).undelegate(this.selector,b,c),this},a.fn.on=function(b,c,d){return!c||a.isFunction(c)?this.bind(b,c||d):this.delegate(c,b,d)},a.fn.off=function(b,c,d){return!c||a.isFunction(c)?this.unbind(b,c||d):this.undelegate(c,b,d)},a.fn.trigger=function(b,c){if(typeof b=="string"||a.isPlainObject(b))b=a.Event(b);return u(b),b.data=c,this.each(function(){"dispatchEvent"in this&&this.dispatchEvent(b)})},a.fn.triggerHandler=function(b,c){var d,e;return this.each(function(f,g){d=t(typeof b=="string"?a.Event(b):b),d.data=c,d.target=g,a.each(h(g,b.type||b),function(a,b){e=b.proxy(d);if(d.isImmediatePropagationStopped())return!1})}),e},"focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select keydown keypress keyup error".split(" ").forEach(function(b){a.fn[b]=function(a){return a?this.bind(b,a):this.trigger(b)}}),["focus","blur"].forEach(function(b){a.fn[b]=function(a){return a?this.bind(b,a):this.each(function(){try{this[b]()}catch(a){}}),this}}),a.Event=function(a,b){typeof a!="string"&&(b=a,a=b.type);var c=document.createEvent(e[a]||"Events"),d=!0;if(b)for(var f in b)f=="bubbles"?d=!!b[f]:c[f]=b[f];return c.initEvent(a,d,!0,null,null,null,null,null,null,null,null,null,null,null,null),c.isDefaultPrevented=function(){return this.defaultPrevented},c}}(Zepto),function($){function triggerAndReturn(a,b,c){var d=$.Event(b);return $(a).trigger(d,c),!d.defaultPrevented}function triggerGlobal(a,b,c,d){if(a.global)return triggerAndReturn(b||document,c,d)}function ajaxStart(a){a.global&&$.active++===0&&triggerGlobal(a,null,"ajaxStart")}function ajaxStop(a){a.global&&!--$.active&&triggerGlobal(a,null,"ajaxStop")}function ajaxBeforeSend(a,b){var c=b.context;if(b.beforeSend.call(c,a,b)===!1||triggerGlobal(b,c,"ajaxBeforeSend",[a,b])===!1)return!1;triggerGlobal(b,c,"ajaxSend",[a,b])}function ajaxSuccess(a,b,c){var d=c.context,e="success";c.success.call(d,a,e,b),triggerGlobal(c,d,"ajaxSuccess",[b,c,a]),ajaxComplete(e,b,c)}function ajaxError(a,b,c,d){var e=d.context;d.error.call(e,c,b,a),triggerGlobal(d,e,"ajaxError",[c,d,a]),ajaxComplete(b,c,d)}function ajaxComplete(a,b,c){var d=c.context;c.complete.call(d,b,a),triggerGlobal(c,d,"ajaxComplete",[b,c]),ajaxStop(c)}function empty(){}function mimeToDataType(a){return a&&(a=a.split(";",2)[0]),a&&(a==htmlType?"html":a==jsonType?"json":scriptTypeRE.test(a)?"script":xmlTypeRE.test(a)&&"xml")||"text"}function appendQuery(a,b){return(a+"&"+b).replace(/[&?]{1,2}/,"?")}function serializeData(a){a.processData&&a.data&&$.type(a.data)!="string"&&(a.data=$.param(a.data,a.traditional)),a.data&&(!a.type||a.type.toUpperCase()=="GET")&&(a.url=appendQuery(a.url,a.data))}function parseArguments(a,b,c,d){var e=!$.isFunction(b);return{url:a,data:e?b:undefined,success:e?$.isFunction(c)?c:undefined:b,dataType:e?d||c:c}}function serialize(a,b,c,d){var e,f=$.isArray(b);$.each(b,function(b,g){e=$.type(g),d&&(b=c?d:d+"["+(f?"":b)+"]"),!d&&f?a.add(g.name,g.value):e=="array"||!c&&e=="object"?serialize(a,g,c,b):a.add(b,g)})}var jsonpID=0,document=window.document,key,name,rscript=/