nice 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/README.md +115 -0
- data/Rakefile +38 -0
- data/lib/assets/javascripts/event_dispatcher.coffee +34 -0
- data/lib/assets/javascripts/nice_imp_jquery.js.coffee +40 -0
- data/lib/assets/javascripts/nice_jquery.js +15 -0
- data/lib/nice.rb +5 -0
- data/lib/nice/engine.rb +4 -0
- data/lib/nice/html_parser.rb +126 -0
- data/lib/nice/js/caller.rb +29 -0
- data/lib/nice/logic.rb +61 -0
- data/lib/nice/middleware.rb +84 -0
- data/lib/nice/version.rb +3 -0
- data/lib/tasks/nice_tasks.rake +4 -0
- data/test/dummy/README.rdoc +261 -0
- data/test/dummy/Rakefile +7 -0
- data/test/dummy/app/assets/javascripts/application.js +15 -0
- data/test/dummy/app/assets/stylesheets/application.css +13 -0
- data/test/dummy/app/controllers/application_controller.rb +3 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/config/application.rb +56 -0
- data/test/dummy/config/boot.rb +10 -0
- data/test/dummy/config/database.yml +25 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +37 -0
- data/test/dummy/config/environments/production.rb +67 -0
- data/test/dummy/config/environments/test.rb +37 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/inflections.rb +15 -0
- data/test/dummy/config/initializers/mime_types.rb +5 -0
- data/test/dummy/config/initializers/secret_token.rb +7 -0
- data/test/dummy/config/initializers/session_store.rb +8 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +5 -0
- data/test/dummy/config/routes.rb +58 -0
- data/test/dummy/db/test.sqlite3 +0 -0
- data/test/dummy/log/test.log +8 -0
- data/test/dummy/public/404.html +26 -0
- data/test/dummy/public/422.html +26 -0
- data/test/dummy/public/500.html +25 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/script/rails +6 -0
- data/test/nice_test.rb +7 -0
- data/test/nizzaTestApp/Gemfile +45 -0
- data/test/nizzaTestApp/Gemfile.lock +130 -0
- data/test/nizzaTestApp/README.rdoc +261 -0
- data/test/nizzaTestApp/Rakefile +7 -0
- data/test/nizzaTestApp/app/assets/images/rails.png +0 -0
- data/test/nizzaTestApp/app/assets/javascripts/application.js +16 -0
- data/test/nizzaTestApp/app/assets/javascripts/books.js.coffee +3 -0
- data/test/nizzaTestApp/app/assets/javascripts/nice.js.coffee +3 -0
- data/test/nizzaTestApp/app/assets/stylesheets/application.css +23 -0
- data/test/nizzaTestApp/app/assets/stylesheets/books.css.scss +3 -0
- data/test/nizzaTestApp/app/assets/stylesheets/nice.css.scss +34 -0
- data/test/nizzaTestApp/app/assets/stylesheets/scaffolds.css.scss +46 -0
- data/test/nizzaTestApp/app/controllers/application_controller.rb +3 -0
- data/test/nizzaTestApp/app/controllers/books_controller.rb +83 -0
- data/test/nizzaTestApp/app/controllers/nice_controller.rb +9 -0
- data/test/nizzaTestApp/app/helpers/application_helper.rb +2 -0
- data/test/nizzaTestApp/app/helpers/books_helper.rb +2 -0
- data/test/nizzaTestApp/app/helpers/nice_helper.rb +2 -0
- data/test/nizzaTestApp/app/models/book.rb +3 -0
- data/test/nizzaTestApp/app/views/books/_form.html.erb +26 -0
- data/test/nizzaTestApp/app/views/books/edit.html.erb +4 -0
- data/test/nizzaTestApp/app/views/books/index.html.erb +38 -0
- data/test/nizzaTestApp/app/views/books/new.html.erb +4 -0
- data/test/nizzaTestApp/app/views/books/show.html.erb +27 -0
- data/test/nizzaTestApp/app/views/layouts/application.html.haml +11 -0
- data/test/nizzaTestApp/app/views/layouts/nice.html.haml +16 -0
- data/test/nizzaTestApp/app/views/nice/many.html.haml +6 -0
- data/test/nizzaTestApp/app/views/nice/simple.html.haml +7 -0
- data/test/nizzaTestApp/config.ru +4 -0
- data/test/nizzaTestApp/config/application.rb +65 -0
- data/test/nizzaTestApp/config/boot.rb +6 -0
- data/test/nizzaTestApp/config/database.yml +25 -0
- data/test/nizzaTestApp/config/environment.rb +5 -0
- data/test/nizzaTestApp/config/environments/development.rb +37 -0
- data/test/nizzaTestApp/config/environments/production.rb +67 -0
- data/test/nizzaTestApp/config/environments/test.rb +37 -0
- data/test/nizzaTestApp/config/initializers/backtrace_silencers.rb +7 -0
- data/test/nizzaTestApp/config/initializers/inflections.rb +15 -0
- data/test/nizzaTestApp/config/initializers/mime_types.rb +5 -0
- data/test/nizzaTestApp/config/initializers/secret_token.rb +7 -0
- data/test/nizzaTestApp/config/initializers/session_store.rb +8 -0
- data/test/nizzaTestApp/config/initializers/wrap_parameters.rb +14 -0
- data/test/nizzaTestApp/config/locales/en.yml +5 -0
- data/test/nizzaTestApp/config/routes.rb +64 -0
- data/test/nizzaTestApp/db/development.sqlite3 +0 -0
- data/test/nizzaTestApp/db/migrate/20120424181757_create_books.rb +11 -0
- data/test/nizzaTestApp/db/schema.rb +24 -0
- data/test/nizzaTestApp/db/seeds.rb +7 -0
- data/test/nizzaTestApp/doc/README_FOR_APP +2 -0
- data/test/nizzaTestApp/log/development.log +18129 -0
- data/test/nizzaTestApp/public/404.html +26 -0
- data/test/nizzaTestApp/public/422.html +26 -0
- data/test/nizzaTestApp/public/500.html +25 -0
- data/test/nizzaTestApp/public/favicon.ico +0 -0
- data/test/nizzaTestApp/public/html_a.html +19 -0
- data/test/nizzaTestApp/public/robots.txt +5 -0
- data/test/nizzaTestApp/script/rails +6 -0
- data/test/nizzaTestApp/test/fixtures/books.yml +11 -0
- data/test/nizzaTestApp/test/functional/books_controller_test.rb +49 -0
- data/test/nizzaTestApp/test/functional/nice_controller_test.rb +14 -0
- data/test/nizzaTestApp/test/performance/browsing_test.rb +12 -0
- data/test/nizzaTestApp/test/test_helper.rb +13 -0
- data/test/nizzaTestApp/test/unit/book_test.rb +7 -0
- data/test/nizzaTestApp/test/unit/helpers/books_helper_test.rb +4 -0
- data/test/nizzaTestApp/test/unit/helpers/nice_helper_test.rb +4 -0
- data/test/nizzaTestApp/tmp/cache/assets/C0B/BC0/sprockets%2F101e06a7a14c184823c19103629096d2 +0 -0
- data/test/nizzaTestApp/tmp/cache/assets/C1E/9B0/sprockets%2F7b5417470164315b3893a6761f235fd1 +0 -0
- data/test/nizzaTestApp/tmp/cache/assets/C46/5D0/sprockets%2F41a35570f1490d1582cf6349724a611c +0 -0
- data/test/nizzaTestApp/tmp/cache/assets/C53/A30/sprockets%2F54110227445e976ce96491b91152ad5f +0 -0
- data/test/nizzaTestApp/tmp/cache/assets/C7B/CC0/sprockets%2F85d74df12791bf3446221275a72ac812 +0 -0
- data/test/nizzaTestApp/tmp/cache/assets/C7F/E00/sprockets%2Ff0913862ad164458ad09cf05236f2286 +0 -0
- data/test/nizzaTestApp/tmp/cache/assets/CA1/570/sprockets%2F96de018b14a15866fd23113417cbc730 +0 -0
- data/test/nizzaTestApp/tmp/cache/assets/CA9/020/sprockets%2F9c6b44b42095db99107072f3023c45dd +0 -0
- data/test/nizzaTestApp/tmp/cache/assets/CAC/730/sprockets%2F892c8ec11684bd01347124824bf35af5 +0 -0
- data/test/nizzaTestApp/tmp/cache/assets/CBF/5E0/sprockets%2F68c66e476c73987d8b6f2ae8160102e2 +0 -0
- data/test/nizzaTestApp/tmp/cache/assets/CD3/460/sprockets%2Fd584d62c054619a28c1c0cff1910521f +0 -0
- data/test/nizzaTestApp/tmp/cache/assets/CD7/6F0/sprockets%2Fbd3936370d0f952ada5774e2230046ed +0 -0
- data/test/nizzaTestApp/tmp/cache/assets/CD8/370/sprockets%2F357970feca3ac29060c1e3861e2c0953 +0 -0
- data/test/nizzaTestApp/tmp/cache/assets/CDB/590/sprockets%2Ff3a3730475dbf19c4a286b5a4f772161 +0 -0
- data/test/nizzaTestApp/tmp/cache/assets/CDD/0B0/sprockets%2Fb351b5f1236c183f9961725ca13ec3e9 +0 -0
- data/test/nizzaTestApp/tmp/cache/assets/CE3/600/sprockets%2F683a4e08e292483284fad185a85e4a3a +0 -0
- data/test/nizzaTestApp/tmp/cache/assets/CE4/FC0/sprockets%2F919f9138b534aa39c3fa50c87e8420a6 +0 -0
- data/test/nizzaTestApp/tmp/cache/assets/CE6/960/sprockets%2F7555284c4d8a6b95e4d2c10e7d02684e +0 -0
- data/test/nizzaTestApp/tmp/cache/assets/CEF/1E0/sprockets%2Fc4f9a95bee5399474db38395336d10d2 +0 -0
- data/test/nizzaTestApp/tmp/cache/assets/CF0/DA0/sprockets%2Fd7d5b37686831d37c4dd75e645f5e016 +0 -0
- data/test/nizzaTestApp/tmp/cache/assets/CF4/140/sprockets%2F22a3157d204c1f8e417a25f01a2dbe45 +0 -0
- data/test/nizzaTestApp/tmp/cache/assets/CF6/C20/sprockets%2F7847096fc8a875a9b941beb70e91284d +0 -0
- data/test/nizzaTestApp/tmp/cache/assets/D05/840/sprockets%2F0ac43b191b0098c9942ebe5e47406ea2 +0 -0
- data/test/nizzaTestApp/tmp/cache/assets/D0B/710/sprockets%2F66190523b03af056e8dca69cd9fd2242 +0 -0
- data/test/nizzaTestApp/tmp/cache/assets/D0E/F80/sprockets%2F8c2b061e379a23e7c4d207adcf992462 +0 -0
- data/test/nizzaTestApp/tmp/cache/assets/D0F/3E0/sprockets%2F6654975eaa53a225d976278fcb6baa00 +0 -0
- data/test/nizzaTestApp/tmp/cache/assets/D10/A10/sprockets%2F9fe3a3308aaa4e98688d77b9b22111a4 +0 -0
- data/test/nizzaTestApp/tmp/cache/assets/D1A/E10/sprockets%2Ff2d34568efb5305d6990b1502d8c6ff7 +0 -0
- data/test/nizzaTestApp/tmp/cache/assets/D1C/C90/sprockets%2F8eb2c6f59556a9aa92dbf132856910c9 +0 -0
- data/test/nizzaTestApp/tmp/cache/assets/D2C/6F0/sprockets%2F1852beb3a2ad67b2cc634f05e142435e +0 -0
- data/test/nizzaTestApp/tmp/cache/assets/D2D/200/sprockets%2F164554b0cd8a0c5bbc610521a87cf19e +0 -0
- data/test/nizzaTestApp/tmp/cache/assets/D2D/D50/sprockets%2Fd585a06e2ee6203ccb04c8b84150d14d +0 -0
- data/test/nizzaTestApp/tmp/cache/assets/D30/9B0/sprockets%2Fa365b8a67c4a6a210d178c2c7dc812f0 +0 -0
- data/test/nizzaTestApp/tmp/cache/assets/D32/A10/sprockets%2F13fe41fee1fe35b49d145bcc06610705 +0 -0
- data/test/nizzaTestApp/tmp/cache/assets/D39/F10/sprockets%2F04aab6c01e8d12d151f54e8c6ee79715 +0 -0
- data/test/nizzaTestApp/tmp/cache/assets/D3B/B40/sprockets%2Fd50a18a28a8aefa85142997ae7a22f24 +0 -0
- data/test/nizzaTestApp/tmp/cache/assets/D3C/990/sprockets%2F47cc6b7ccafd54046c3881f18a16e162 +0 -0
- data/test/nizzaTestApp/tmp/cache/assets/D3D/DC0/sprockets%2F24a8e58314bc87244de540eee302f3ef +0 -0
- data/test/nizzaTestApp/tmp/cache/assets/D46/330/sprockets%2F50ff63775b3d84d16b93348bedd0d5d4 +0 -0
- data/test/nizzaTestApp/tmp/cache/assets/D4E/1B0/sprockets%2Ff7cbd26ba1d28d48de824f0e94586655 +0 -0
- data/test/nizzaTestApp/tmp/cache/assets/D51/0C0/sprockets%2F7ae1c78c809fa25eef9e8f5052a99332 +0 -0
- data/test/nizzaTestApp/tmp/cache/assets/D55/F00/sprockets%2F3df7a51021aa28af9f436ab24dc2c430 +0 -0
- data/test/nizzaTestApp/tmp/cache/assets/D58/170/sprockets%2F1d1519f0daa88f114d10945d0a0ee5bb +0 -0
- data/test/nizzaTestApp/tmp/cache/assets/D5A/EA0/sprockets%2Fd771ace226fc8215a3572e0aa35bb0d6 +0 -0
- data/test/nizzaTestApp/tmp/cache/assets/D71/EB0/sprockets%2F9a57785b6dbd04ace339105c79ca5ad6 +0 -0
- data/test/nizzaTestApp/tmp/cache/assets/D73/4C0/sprockets%2Fac8c9d97edb15b84cb35a184822952da +0 -0
- data/test/nizzaTestApp/tmp/cache/assets/D76/710/sprockets%2Ff9dabce89a305adf72963094731ef53a +0 -0
- data/test/nizzaTestApp/tmp/cache/assets/D78/6E0/sprockets%2F66c9f9ea0228a2bad83467d8a416fe4f +0 -0
- data/test/nizzaTestApp/tmp/cache/assets/D7B/A50/sprockets%2Fb1b95d84f048a6e4ce984cd1e29f50d6 +0 -0
- data/test/nizzaTestApp/tmp/cache/assets/DB7/CB0/sprockets%2F2402f5cbeb259c1b7a03f6a0ff074adb +0 -0
- data/test/nizzaTestApp/tmp/cache/assets/DDC/400/sprockets%2Fcffd775d018f68ce5dba1ee0d951a994 +0 -0
- data/test/nizzaTestApp/tmp/cache/assets/DDE/620/sprockets%2Ff6f9a7fedaea753da63ea72347d8c385 +0 -0
- data/test/nizzaTestApp/tmp/cache/assets/E04/890/sprockets%2F2f5173deea6c795b8fdde723bb4b63af +0 -0
- data/test/nizzaTestApp/tmp/cache/assets/E07/B70/sprockets%2Fd8c8bcaa8828e657a5fd4b9cacb92a17 +0 -0
- data/test/nizzaTestApp/tmp/cache/assets/E19/2A0/sprockets%2F10fcfbe6ebae11a40c8eac41939a1b9a +0 -0
- data/test/nizzaTestApp/tmp/cache/assets/E25/4C0/sprockets%2Fde2fd9fd11c04a582cdbbe3d84a35ae6 +0 -0
- data/test/nizzaTestApp/tmp/cache/assets/E5F/FD0/sprockets%2F43fe06bae4e63b9cecaf84e1d8b0cdf7 +0 -0
- data/test/nizzaTestApp/tmp/cache/assets/E64/EC0/sprockets%2Fea602649ea9f2e7adceb8c2f7d3cc5fc +0 -0
- data/test/nizzaTestApp/tmp/cache/sass/279c98e2d4f2491dbaea6ced015d162cb89b00e3/books.css.scssc +0 -0
- data/test/nizzaTestApp/tmp/cache/sass/279c98e2d4f2491dbaea6ced015d162cb89b00e3/scaffolds.css.scssc +0 -0
- data/test/nizzaTestApp/tmp/cache/sass/40d1259310c37828c7b403e05d7a941ef3d84db5/nice.css.scssc +0 -0
- data/test/nizzaTestApp/tmp/cache/sass/40d1259310c37828c7b403e05d7a941ef3d84db5/scaffolds.css.scssc +0 -0
- data/test/nizzaTestApp/tmp/cache/sass/fa4fcc5ec3c4b006233db2677ec61c27ca744dda/nice.css.scssc +0 -0
- data/test/test_helper.rb +15 -0
- metadata +433 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2012 YOURNAME
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
# Nice \[franz. nis\]
|
2
|
+
|
3
|
+
## What?
|
4
|
+
|
5
|
+
*Nice* is a light-weight engine which puts some *magic* into JS/AJAX driven Rails pages with the aim to ease the development of rich and interactive restful web applications.
|
6
|
+
The idea is about state defintions as they are known in Adobe Flex for a while which represent one visiual status displayed to the user. In response to an event (typically user interaction), this state can change.
|
7
|
+
Think of page states as of one visual view presented to the user - or for RESTful applications, one state is tied to one route.
|
8
|
+
While plain HTML requires a page refresh to update the visual presentation, Javascript introduced a possiblity to define a transitition to the next state by adding or removing elements on the fly. Although this is an enhancement with regards to user experience, but the source code gets messed up easily, because the view will be generated both on the server and dynamically in the browser. Furthermore rendering code will be written twice and we all know that this leads to incoherence. Finally, the idea of *Nice* is to put the view generation entirely on the backend side (where it should be for several reasons!) and let the state transitions happen auto-magically. Instead of coding the changes between different states you will simply code your states by annotating which page elements belong to which state and *Nice* generates the glue code to transit between states - no matter how many states you have. Specially if you consider having multiple states you have to write a lot of code in plain javascript to handle all possible state changes, *Nice* can handle this easily and consistently.
|
9
|
+
|
10
|
+
The whole framework is aimed to be non-intrusive as much as possible, so the way you regulary write your rails apps won't change dramatically.
|
11
|
+
|
12
|
+
## How to use
|
13
|
+
|
14
|
+
### Requirements
|
15
|
+
|
16
|
+
This gem was tested with **Rails 3.2** but should also work with Rails 3.1 which introduced the *Asset-Pipeline* used by this gem.
|
17
|
+
Furthermore, the current version uses **JQuery** to manipulate the DOM tree, so you should have the appropriate files in place. Although, it is easily possible to replace this by other frameworks (see *Contribution Section*)
|
18
|
+
|
19
|
+
### Install
|
20
|
+
|
21
|
+
1. Add Gem dependency to your Gemfile
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
#.Gemfile
|
25
|
+
|
26
|
+
gem 'nice'
|
27
|
+
```
|
28
|
+
|
29
|
+
2. Run
|
30
|
+
|
31
|
+
```
|
32
|
+
bundle install
|
33
|
+
```
|
34
|
+
|
35
|
+
3. Add Middleware
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
#config/application.rb
|
39
|
+
|
40
|
+
config.middleware.use Nice::Middleware
|
41
|
+
```
|
42
|
+
|
43
|
+
4. Require Gem javascript in your applicaton.js manifest
|
44
|
+
|
45
|
+
```js
|
46
|
+
//app/assets/javascripts/application.js
|
47
|
+
|
48
|
+
//= require nice_jquery
|
49
|
+
```
|
50
|
+
|
51
|
+
### Basic Usage
|
52
|
+
|
53
|
+
The idea is to combine all views of one controller into one layout file exactly as it was already possible with rails and make heavy use of **yield()** and **content_for** tags to include view specific content.
|
54
|
+
The convention of rails is to put a file named after your controller inside the *app/views/layouts/* folder. Such a file could look like this
|
55
|
+
|
56
|
+
```haml
|
57
|
+
-# app/views/layouts/books.html.haml
|
58
|
+
|
59
|
+
- content_for :content do
|
60
|
+
%div
|
61
|
+
.one{"data-state" => "get_books"}
|
62
|
+
%h1 Only visible in state index
|
63
|
+
= yield(:container1)
|
64
|
+
.two{"data-state" => "get_books_show"}
|
65
|
+
%h1 Only visible in state show
|
66
|
+
= yield(:container1)
|
67
|
+
|
68
|
+
= render :template => 'layouts/application'
|
69
|
+
```
|
70
|
+
|
71
|
+
```haml
|
72
|
+
-# app/views/layouts/application.html.haml
|
73
|
+
|
74
|
+
\#{content_for?(:content) ? yield(:content) : yield}
|
75
|
+
```
|
76
|
+
|
77
|
+
- The *content_for* tag will make sure the following context gets rendered in the application layout file \(see [Rails Guide](http://guides.rubyonrails.org/layouts_and_rendering.html#using-nested-layouts) for a deeper understanding of nested layouts\).
|
78
|
+
- *.one{"data-state" => "get_books"}* is [HAML](http://haml.info/) code to generate a DIV block with a HTML5 attribute **data-state**. This is the key part: The value of the **data-state** attribute *marks the state in which the annotated element should be included*. This attribute can also hold a list of *space-separated* state names.
|
79
|
+
- *= yield(:container1)* is a placeholder where content from the view file will be inserted at runtime.
|
80
|
+
- The last line is part of the nexted layout design and just makes sure that this *books* layout page gets rendered inside the application layout.
|
81
|
+
|
82
|
+
There is one golden rule when programming with this state engine:
|
83
|
+
|
84
|
+
**All elements bounded to one or more states \(meaning that they are annotated with a *data-state* attribute\) must live in the *layout* file but not in a *view*!**
|
85
|
+
|
86
|
+
This restriction is imposed by the way how the middleware calculates reference points for elements but should normally not effect your workflow - just keep it in mind.
|
87
|
+
|
88
|
+
All links in your application should now use the ```:remote => :true``` attribute to ensure the requests will be sent using javascript by default.
|
89
|
+
|
90
|
+
### Features
|
91
|
+
|
92
|
+
*Nice* is still in early stages and there is truly a lot to do. If you feel intrested and want to contribute, please don't hestitate to start work on one of the following features or enhance existing ones.
|
93
|
+
|
94
|
+
- state annotation via HTML5 data attribute **data-state**
|
95
|
+
- elements can belong to more than one state annotated by space separated list
|
96
|
+
- naming convention for state names follows the appropriate REST route: method_controller_action
|
97
|
+
- if an elements exist in the current and the following state it can be optionally left untouched with the **data-state-update** property set to *no*. Default is true.
|
98
|
+
- automated Browser history management
|
99
|
+
- javascript code for DOM manipulation is separated and can be replaced to use other frameworks easily \(just remove *nice_jquery* requirement in application.js manifest and put your own methods in place. Hava a look in *Nice-GEM/lib/assets/javascripts/dom_jquery.js.coffee* and *Nice-GEM/lib/assets/javascripts/dom_jquery.js.coffee* for the signature to implement\)
|
100
|
+
|
101
|
+
## Behind the scenes
|
102
|
+
|
103
|
+
Nice is a middleware which processes all HTML and JS requests by either removing non-state specific content from the rendered page or generates JS code to manipulate the DOM tree client side.
|
104
|
+
|
105
|
+
## Roadmap / Contribute
|
106
|
+
|
107
|
+
- better example application
|
108
|
+
- test cases
|
109
|
+
- customization of state names
|
110
|
+
- customization of HTML5 attribute names
|
111
|
+
- add more js events which can be catched by application
|
112
|
+
- preloading of states (for elements which do not require updated backend data)
|
113
|
+
|
114
|
+
# License
|
115
|
+
This project rocks and uses MIT-LICENSE.
|
data/Rakefile
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
begin
|
3
|
+
require 'bundler/setup'
|
4
|
+
rescue LoadError
|
5
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
6
|
+
end
|
7
|
+
begin
|
8
|
+
require 'rdoc/task'
|
9
|
+
rescue LoadError
|
10
|
+
require 'rdoc/rdoc'
|
11
|
+
require 'rake/rdoctask'
|
12
|
+
RDoc::Task = Rake::RDocTask
|
13
|
+
end
|
14
|
+
|
15
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
16
|
+
rdoc.rdoc_dir = 'rdoc'
|
17
|
+
rdoc.title = 'Nice'
|
18
|
+
rdoc.options << '--line-numbers'
|
19
|
+
rdoc.rdoc_files.include('README.rdoc')
|
20
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
|
25
|
+
|
26
|
+
Bundler::GemHelper.install_tasks
|
27
|
+
|
28
|
+
require 'rake/testtask'
|
29
|
+
|
30
|
+
Rake::TestTask.new(:test) do |t|
|
31
|
+
t.libs << 'lib'
|
32
|
+
t.libs << 'test'
|
33
|
+
t.pattern = 'test/**/*_test.rb'
|
34
|
+
t.verbose = false
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
task :default => :test
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class this.NiceEventDispatcher
|
2
|
+
|
3
|
+
# state transition cache
|
4
|
+
@state_cache
|
5
|
+
|
6
|
+
# gets called by JS response and generates JS events either dispatched immediatly
|
7
|
+
# or later by another transition event
|
8
|
+
@dispatch_event: (event_name, attrs, cached = false, transition_id = null) ->
|
9
|
+
# create event
|
10
|
+
evt = document.createEvent("Event")
|
11
|
+
evt.initEvent(event_name,true,true);
|
12
|
+
|
13
|
+
# add remaining arguments as event properties
|
14
|
+
evt[key] = value for key, value of attrs
|
15
|
+
|
16
|
+
if cached == true # is this state preloading?
|
17
|
+
if !NiceEventDispatcher.state_cache?[transition_id]?
|
18
|
+
NiceEventDispatcher.state_cache = new Object;
|
19
|
+
NiceEventDispatcher.state_cache[transition_id] = []
|
20
|
+
document.addEventListener "nice.state.CachedTransitionEvent", NiceEventDispatcher.dispatch_cached_transition_events, false
|
21
|
+
|
22
|
+
NiceEventDispatcher.state_cache[transition_id].push evt
|
23
|
+
else # dispatch
|
24
|
+
document.dispatchEvent(evt)
|
25
|
+
|
26
|
+
# launch cached state transition
|
27
|
+
@dispatch_cached_transition_events: (event) ->
|
28
|
+
if NiceEventDispatcher.state_cache?[event.transition_id]?
|
29
|
+
|
30
|
+
# call stored events
|
31
|
+
document.dispatchEvent(evt) for evt in NiceEventDispatcher.state_cache[event.transition_id]
|
32
|
+
|
33
|
+
# clean/reset
|
34
|
+
NiceEventDispatcher.state_cache[event.transition_id] = null
|
@@ -0,0 +1,40 @@
|
|
1
|
+
## default event handler
|
2
|
+
class NiceJquery
|
3
|
+
|
4
|
+
# insert element after referencing node
|
5
|
+
@insert_after: (event) ->
|
6
|
+
$(event.ref_node).after(event.new_node)
|
7
|
+
|
8
|
+
# insert element at first position inside referencing node
|
9
|
+
@insert_inside: (event) ->
|
10
|
+
$(event.ref_node).prepend(event.new_node)
|
11
|
+
|
12
|
+
# remove all elements which are not of current state and all elements
|
13
|
+
# which are of current state and secondly annotated to be always updated.
|
14
|
+
@remove_state_elements: (event) ->
|
15
|
+
$("[data-state]").not("[data-state~='#{event.curr_state}']").remove()
|
16
|
+
$("[data-state~='#{event.curr_state}'][data-state-update!='no']").remove()
|
17
|
+
|
18
|
+
|
19
|
+
# Browser History Stuff
|
20
|
+
@move_to_url: (event) ->
|
21
|
+
history.pushState(null,event.title,event.url)
|
22
|
+
|
23
|
+
@insert_or_update_back_listener: (event) ->
|
24
|
+
# remove current existing back-binders
|
25
|
+
$(window).unbind('popstate')
|
26
|
+
$(window).bind('popstate', ->
|
27
|
+
xmlHttp = null
|
28
|
+
xmlHttp = new XMLHttpRequest()
|
29
|
+
xmlHttp.open('GET', event.url, false)
|
30
|
+
xmlHttp.send(null)
|
31
|
+
eval(xmlHttp.responseText)
|
32
|
+
)
|
33
|
+
|
34
|
+
|
35
|
+
## add event listener
|
36
|
+
document.addEventListener "nice.dom.InsertAfterEvent", NiceJquery.insert_after, false
|
37
|
+
document.addEventListener "nice.dom.InsertInsideEvent", NiceJquery.insert_inside, false
|
38
|
+
document.addEventListener "nice.dom.RemoveStateEvent", NiceJquery.remove_state_elements, false
|
39
|
+
document.addEventListener "nice.hist.ChangeURLEvent", NiceJquery.move_to_url, false
|
40
|
+
document.addEventListener "nice.hist.PopHistoryEvent", NiceJquery.insert_or_update_back_listener, false
|
@@ -0,0 +1,15 @@
|
|
1
|
+
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
2
|
+
// listed below.
|
3
|
+
//
|
4
|
+
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
|
5
|
+
// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
|
6
|
+
//
|
7
|
+
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
8
|
+
// the compiled file.
|
9
|
+
//
|
10
|
+
// WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
|
11
|
+
// GO AFTER THE REQUIRES BELOW.
|
12
|
+
//
|
13
|
+
|
14
|
+
//= require event_dispatcher
|
15
|
+
//= require nice_imp_jquery
|
data/lib/nice.rb
ADDED
data/lib/nice/engine.rb
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
require 'yaml'
|
3
|
+
require 'nice/js/caller'
|
4
|
+
|
5
|
+
module Nice
|
6
|
+
class HtmlParser
|
7
|
+
|
8
|
+
def self.remove_elements_not_of_state(state, doc)
|
9
|
+
|
10
|
+
doc.css("[data-state]").each do |node|
|
11
|
+
if !node.attribute('data-state').value.split(" ").include?(state) then
|
12
|
+
node.remove
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
doc
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.add_elements_of_current_state doc, curr_state
|
20
|
+
|
21
|
+
# get all nodes of the current state
|
22
|
+
curr_state_nodes = doc.css("[data-state~='#{curr_state}']")
|
23
|
+
|
24
|
+
# get reference nodes in DOM tree for current nodes and generate js insert statements
|
25
|
+
stack = curr_state_nodes.reverse.each_with_index.map do |curr_node,index|
|
26
|
+
|
27
|
+
if curr_node.has_attribute?("data-state-update") &&
|
28
|
+
curr_node.attribute("data-state-update").value == "no" then
|
29
|
+
next
|
30
|
+
end
|
31
|
+
|
32
|
+
ref_id = self.ref_node_uid(curr_state,curr_state_nodes.count - index)
|
33
|
+
ref_node_name = "[data-state-uid~=\'#{ref_id}\']"
|
34
|
+
ref_node = doc.css(ref_node_name)
|
35
|
+
|
36
|
+
next if ref_node == nil
|
37
|
+
|
38
|
+
#get index
|
39
|
+
idx = ref_node.attribute("data-state-uid").value.split(" ").find_index(ref_id)
|
40
|
+
|
41
|
+
ref_node_method = ref_node.attribute('data-state-insert-method').value.split(" ")[idx]
|
42
|
+
|
43
|
+
if ref_node_method == "insert"
|
44
|
+
js_text = Nice::Js::Caller.generate_js_insert_after curr_node, ref_node_name
|
45
|
+
else
|
46
|
+
js_text = Nice::Js::Caller.generate_js_insert_inside curr_node, ref_node_name
|
47
|
+
end
|
48
|
+
|
49
|
+
# remove unuseful chars which will break the js parser
|
50
|
+
js_text = js_text.gsub(/(\r\n|\n|\r|\t|\s\s)/,'')
|
51
|
+
end
|
52
|
+
|
53
|
+
stack
|
54
|
+
end
|
55
|
+
|
56
|
+
# generates referencing data attributes in all preceiding or parent nodes of
|
57
|
+
# state bound elements which are used later to insert elements correctly
|
58
|
+
# This method supports referencing by more than one state bounded node
|
59
|
+
def self.annotate_referencing_nodes doc
|
60
|
+
|
61
|
+
per_state_counter = {}
|
62
|
+
doc.css("[data-state]").each do |curr_node|
|
63
|
+
|
64
|
+
# each node can have multiple state references which need to be
|
65
|
+
# treated separately
|
66
|
+
states = curr_node.attribute("data-state").value.split(" ")
|
67
|
+
|
68
|
+
states.each do |state|
|
69
|
+
# increase counter per state
|
70
|
+
per_state_counter[state] ||= 0
|
71
|
+
idx = per_state_counter[state] += 1
|
72
|
+
|
73
|
+
# try using preceding element if one exists otherwise use parent.
|
74
|
+
# the referencing node must not be an state bound element otherwise
|
75
|
+
# we can not be sure the element is always present.
|
76
|
+
prev_node = curr_node.previous_element
|
77
|
+
|
78
|
+
while prev_node && prev_node.has_attribute?("data-state")
|
79
|
+
prev_node = prev_node.previous_element
|
80
|
+
end
|
81
|
+
|
82
|
+
if prev_node && !prev_node.has_attribute?("data-state")
|
83
|
+
node = prev_node
|
84
|
+
method = "insert"
|
85
|
+
else
|
86
|
+
par_node = curr_node.parent
|
87
|
+
|
88
|
+
while par_node && par_node.has_attribute?("data-state")
|
89
|
+
par_node = par_node.parent
|
90
|
+
end
|
91
|
+
|
92
|
+
if par_node && !par_node.has_attribute?("data-state")
|
93
|
+
node = curr_node.parent
|
94
|
+
method = "append"
|
95
|
+
else
|
96
|
+
raise "No reference could be created for node #{curr_node}. Make sure this node \
|
97
|
+
is preceeded or sourrounded at least by one non state bound element."
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
next if node == nil
|
103
|
+
|
104
|
+
# add reference to the found element
|
105
|
+
a = node.has_attribute?('data-state-uid') ? [node.attribute('data-state-uid').value] : []
|
106
|
+
a += [self.ref_node_uid(state,idx)]
|
107
|
+
node['data-state-uid'] = a.join(" ")
|
108
|
+
|
109
|
+
m = node.has_attribute?('data-state-insert-method') ? [node.attribute('data-state-insert-method').value] : []
|
110
|
+
m += [method]
|
111
|
+
node['data-state-insert-method'] = m.join(" ")
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
doc
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
private
|
120
|
+
|
121
|
+
def self.ref_node_uid node_uid, num
|
122
|
+
"#{node_uid}_ref_#{num}"
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Nice
|
2
|
+
module Js
|
3
|
+
class Caller
|
4
|
+
|
5
|
+
# DOM Manipulation
|
6
|
+
def self.generate_js_insert_after new_node, reference_node_ref
|
7
|
+
"NiceEventDispatcher.dispatch_event(\'nice.dom.InsertAfterEvent\',{new_node:\'#{new_node}\', ref_node:\"#{reference_node_ref}\"});"
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.generate_js_insert_inside new_node, reference_node_ref
|
11
|
+
"NiceEventDispatcher.dispatch_event(\'nice.dom.InsertInsideEvent\',{new_node:\'#{new_node}\', ref_node:\"#{reference_node_ref}\"});"
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.generate_js_remove curr_state
|
15
|
+
"NiceEventDispatcher.dispatch_event(\'nice.dom.RemoveStateEvent\',{curr_state:\'#{curr_state}\'},true,'get_many_nice__get_simple_nice');"
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
# History Manipulation
|
20
|
+
def self.move_to_url url, title
|
21
|
+
"NiceEventDispatcher.dispatch_event(\'nice.hist.ChangeURLEvent\',{url:\'#{url}\', title:\'#{title}\'});"
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.insert_or_update_back_listener url
|
25
|
+
"NiceEventDispatcher.dispatch_event(\'nice.hist.PopHistoryEvent\',{url:\'#{url}\'});"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/nice/logic.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'nice/html_parser'
|
2
|
+
require 'nice/js/caller'
|
3
|
+
require 'uri'
|
4
|
+
|
5
|
+
module Nice
|
6
|
+
|
7
|
+
# The nice state engine is tighthly integrated with restful routes. Each route can be a state
|
8
|
+
# and states are only identified by their corresponding route. In effect, no state names
|
9
|
+
# must be created and no parameters indicating the state transitions are needed as this
|
10
|
+
# information is already contained in the http header.
|
11
|
+
|
12
|
+
## case 1: this is the first call of the component -> no previous state
|
13
|
+
|
14
|
+
# => remove elements not belonging to the start state, generate UID for reference
|
15
|
+
# nodes ({ref_element}_ref) and respond with HTML
|
16
|
+
|
17
|
+
## case 2: this is an ordinary call of another state
|
18
|
+
|
19
|
+
# => respond with JS with assignments to remove elements not included
|
20
|
+
# in current state and assignments to insert elements of current state.
|
21
|
+
|
22
|
+
|
23
|
+
## case 3: curr_state == prev_state
|
24
|
+
|
25
|
+
# => respond with JS which either first removes all elements of the current state and
|
26
|
+
# later inserts new content OR directly replaces elements
|
27
|
+
|
28
|
+
class Logic
|
29
|
+
|
30
|
+
def self.run current_method, current_path, referer, doc
|
31
|
+
|
32
|
+
current_state = current_method.downcase + current_path.gsub("/", "_")
|
33
|
+
p "current: #{current_state}"
|
34
|
+
|
35
|
+
referenced_doc = Nice::HtmlParser.annotate_referencing_nodes doc
|
36
|
+
|
37
|
+
cleaned_doc = Nice::HtmlParser.remove_elements_not_of_state current_state, referenced_doc
|
38
|
+
|
39
|
+
# case 1
|
40
|
+
if referer == nil then
|
41
|
+
|
42
|
+
cleaned_doc.to_html
|
43
|
+
|
44
|
+
# case 2
|
45
|
+
else
|
46
|
+
js_stack = ["// remove elements not present in the following state"]
|
47
|
+
js_stack << Nice::Js::Caller.generate_js_remove(current_state)
|
48
|
+
|
49
|
+
js_stack << "// add new elements"
|
50
|
+
js_stack += Nice::HtmlParser.add_elements_of_current_state(cleaned_doc,current_state).compact
|
51
|
+
|
52
|
+
js_stack << "// add browser history scripts"
|
53
|
+
js_stack << Nice::Js::Caller.move_to_url(current_path,"title")
|
54
|
+
js_stack << Nice::Js::Caller.insert_or_update_back_listener(referer)
|
55
|
+
|
56
|
+
js_stack.join("\n")
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|