nice 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (175) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.md +115 -0
  3. data/Rakefile +38 -0
  4. data/lib/assets/javascripts/event_dispatcher.coffee +34 -0
  5. data/lib/assets/javascripts/nice_imp_jquery.js.coffee +40 -0
  6. data/lib/assets/javascripts/nice_jquery.js +15 -0
  7. data/lib/nice.rb +5 -0
  8. data/lib/nice/engine.rb +4 -0
  9. data/lib/nice/html_parser.rb +126 -0
  10. data/lib/nice/js/caller.rb +29 -0
  11. data/lib/nice/logic.rb +61 -0
  12. data/lib/nice/middleware.rb +84 -0
  13. data/lib/nice/version.rb +3 -0
  14. data/lib/tasks/nice_tasks.rake +4 -0
  15. data/test/dummy/README.rdoc +261 -0
  16. data/test/dummy/Rakefile +7 -0
  17. data/test/dummy/app/assets/javascripts/application.js +15 -0
  18. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  19. data/test/dummy/app/controllers/application_controller.rb +3 -0
  20. data/test/dummy/app/helpers/application_helper.rb +2 -0
  21. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  22. data/test/dummy/config.ru +4 -0
  23. data/test/dummy/config/application.rb +56 -0
  24. data/test/dummy/config/boot.rb +10 -0
  25. data/test/dummy/config/database.yml +25 -0
  26. data/test/dummy/config/environment.rb +5 -0
  27. data/test/dummy/config/environments/development.rb +37 -0
  28. data/test/dummy/config/environments/production.rb +67 -0
  29. data/test/dummy/config/environments/test.rb +37 -0
  30. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  31. data/test/dummy/config/initializers/inflections.rb +15 -0
  32. data/test/dummy/config/initializers/mime_types.rb +5 -0
  33. data/test/dummy/config/initializers/secret_token.rb +7 -0
  34. data/test/dummy/config/initializers/session_store.rb +8 -0
  35. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  36. data/test/dummy/config/locales/en.yml +5 -0
  37. data/test/dummy/config/routes.rb +58 -0
  38. data/test/dummy/db/test.sqlite3 +0 -0
  39. data/test/dummy/log/test.log +8 -0
  40. data/test/dummy/public/404.html +26 -0
  41. data/test/dummy/public/422.html +26 -0
  42. data/test/dummy/public/500.html +25 -0
  43. data/test/dummy/public/favicon.ico +0 -0
  44. data/test/dummy/script/rails +6 -0
  45. data/test/nice_test.rb +7 -0
  46. data/test/nizzaTestApp/Gemfile +45 -0
  47. data/test/nizzaTestApp/Gemfile.lock +130 -0
  48. data/test/nizzaTestApp/README.rdoc +261 -0
  49. data/test/nizzaTestApp/Rakefile +7 -0
  50. data/test/nizzaTestApp/app/assets/images/rails.png +0 -0
  51. data/test/nizzaTestApp/app/assets/javascripts/application.js +16 -0
  52. data/test/nizzaTestApp/app/assets/javascripts/books.js.coffee +3 -0
  53. data/test/nizzaTestApp/app/assets/javascripts/nice.js.coffee +3 -0
  54. data/test/nizzaTestApp/app/assets/stylesheets/application.css +23 -0
  55. data/test/nizzaTestApp/app/assets/stylesheets/books.css.scss +3 -0
  56. data/test/nizzaTestApp/app/assets/stylesheets/nice.css.scss +34 -0
  57. data/test/nizzaTestApp/app/assets/stylesheets/scaffolds.css.scss +46 -0
  58. data/test/nizzaTestApp/app/controllers/application_controller.rb +3 -0
  59. data/test/nizzaTestApp/app/controllers/books_controller.rb +83 -0
  60. data/test/nizzaTestApp/app/controllers/nice_controller.rb +9 -0
  61. data/test/nizzaTestApp/app/helpers/application_helper.rb +2 -0
  62. data/test/nizzaTestApp/app/helpers/books_helper.rb +2 -0
  63. data/test/nizzaTestApp/app/helpers/nice_helper.rb +2 -0
  64. data/test/nizzaTestApp/app/models/book.rb +3 -0
  65. data/test/nizzaTestApp/app/views/books/_form.html.erb +26 -0
  66. data/test/nizzaTestApp/app/views/books/edit.html.erb +4 -0
  67. data/test/nizzaTestApp/app/views/books/index.html.erb +38 -0
  68. data/test/nizzaTestApp/app/views/books/new.html.erb +4 -0
  69. data/test/nizzaTestApp/app/views/books/show.html.erb +27 -0
  70. data/test/nizzaTestApp/app/views/layouts/application.html.haml +11 -0
  71. data/test/nizzaTestApp/app/views/layouts/nice.html.haml +16 -0
  72. data/test/nizzaTestApp/app/views/nice/many.html.haml +6 -0
  73. data/test/nizzaTestApp/app/views/nice/simple.html.haml +7 -0
  74. data/test/nizzaTestApp/config.ru +4 -0
  75. data/test/nizzaTestApp/config/application.rb +65 -0
  76. data/test/nizzaTestApp/config/boot.rb +6 -0
  77. data/test/nizzaTestApp/config/database.yml +25 -0
  78. data/test/nizzaTestApp/config/environment.rb +5 -0
  79. data/test/nizzaTestApp/config/environments/development.rb +37 -0
  80. data/test/nizzaTestApp/config/environments/production.rb +67 -0
  81. data/test/nizzaTestApp/config/environments/test.rb +37 -0
  82. data/test/nizzaTestApp/config/initializers/backtrace_silencers.rb +7 -0
  83. data/test/nizzaTestApp/config/initializers/inflections.rb +15 -0
  84. data/test/nizzaTestApp/config/initializers/mime_types.rb +5 -0
  85. data/test/nizzaTestApp/config/initializers/secret_token.rb +7 -0
  86. data/test/nizzaTestApp/config/initializers/session_store.rb +8 -0
  87. data/test/nizzaTestApp/config/initializers/wrap_parameters.rb +14 -0
  88. data/test/nizzaTestApp/config/locales/en.yml +5 -0
  89. data/test/nizzaTestApp/config/routes.rb +64 -0
  90. data/test/nizzaTestApp/db/development.sqlite3 +0 -0
  91. data/test/nizzaTestApp/db/migrate/20120424181757_create_books.rb +11 -0
  92. data/test/nizzaTestApp/db/schema.rb +24 -0
  93. data/test/nizzaTestApp/db/seeds.rb +7 -0
  94. data/test/nizzaTestApp/doc/README_FOR_APP +2 -0
  95. data/test/nizzaTestApp/log/development.log +18129 -0
  96. data/test/nizzaTestApp/public/404.html +26 -0
  97. data/test/nizzaTestApp/public/422.html +26 -0
  98. data/test/nizzaTestApp/public/500.html +25 -0
  99. data/test/nizzaTestApp/public/favicon.ico +0 -0
  100. data/test/nizzaTestApp/public/html_a.html +19 -0
  101. data/test/nizzaTestApp/public/robots.txt +5 -0
  102. data/test/nizzaTestApp/script/rails +6 -0
  103. data/test/nizzaTestApp/test/fixtures/books.yml +11 -0
  104. data/test/nizzaTestApp/test/functional/books_controller_test.rb +49 -0
  105. data/test/nizzaTestApp/test/functional/nice_controller_test.rb +14 -0
  106. data/test/nizzaTestApp/test/performance/browsing_test.rb +12 -0
  107. data/test/nizzaTestApp/test/test_helper.rb +13 -0
  108. data/test/nizzaTestApp/test/unit/book_test.rb +7 -0
  109. data/test/nizzaTestApp/test/unit/helpers/books_helper_test.rb +4 -0
  110. data/test/nizzaTestApp/test/unit/helpers/nice_helper_test.rb +4 -0
  111. data/test/nizzaTestApp/tmp/cache/assets/C0B/BC0/sprockets%2F101e06a7a14c184823c19103629096d2 +0 -0
  112. data/test/nizzaTestApp/tmp/cache/assets/C1E/9B0/sprockets%2F7b5417470164315b3893a6761f235fd1 +0 -0
  113. data/test/nizzaTestApp/tmp/cache/assets/C46/5D0/sprockets%2F41a35570f1490d1582cf6349724a611c +0 -0
  114. data/test/nizzaTestApp/tmp/cache/assets/C53/A30/sprockets%2F54110227445e976ce96491b91152ad5f +0 -0
  115. data/test/nizzaTestApp/tmp/cache/assets/C7B/CC0/sprockets%2F85d74df12791bf3446221275a72ac812 +0 -0
  116. data/test/nizzaTestApp/tmp/cache/assets/C7F/E00/sprockets%2Ff0913862ad164458ad09cf05236f2286 +0 -0
  117. data/test/nizzaTestApp/tmp/cache/assets/CA1/570/sprockets%2F96de018b14a15866fd23113417cbc730 +0 -0
  118. data/test/nizzaTestApp/tmp/cache/assets/CA9/020/sprockets%2F9c6b44b42095db99107072f3023c45dd +0 -0
  119. data/test/nizzaTestApp/tmp/cache/assets/CAC/730/sprockets%2F892c8ec11684bd01347124824bf35af5 +0 -0
  120. data/test/nizzaTestApp/tmp/cache/assets/CBF/5E0/sprockets%2F68c66e476c73987d8b6f2ae8160102e2 +0 -0
  121. data/test/nizzaTestApp/tmp/cache/assets/CD3/460/sprockets%2Fd584d62c054619a28c1c0cff1910521f +0 -0
  122. data/test/nizzaTestApp/tmp/cache/assets/CD7/6F0/sprockets%2Fbd3936370d0f952ada5774e2230046ed +0 -0
  123. data/test/nizzaTestApp/tmp/cache/assets/CD8/370/sprockets%2F357970feca3ac29060c1e3861e2c0953 +0 -0
  124. data/test/nizzaTestApp/tmp/cache/assets/CDB/590/sprockets%2Ff3a3730475dbf19c4a286b5a4f772161 +0 -0
  125. data/test/nizzaTestApp/tmp/cache/assets/CDD/0B0/sprockets%2Fb351b5f1236c183f9961725ca13ec3e9 +0 -0
  126. data/test/nizzaTestApp/tmp/cache/assets/CE3/600/sprockets%2F683a4e08e292483284fad185a85e4a3a +0 -0
  127. data/test/nizzaTestApp/tmp/cache/assets/CE4/FC0/sprockets%2F919f9138b534aa39c3fa50c87e8420a6 +0 -0
  128. data/test/nizzaTestApp/tmp/cache/assets/CE6/960/sprockets%2F7555284c4d8a6b95e4d2c10e7d02684e +0 -0
  129. data/test/nizzaTestApp/tmp/cache/assets/CEF/1E0/sprockets%2Fc4f9a95bee5399474db38395336d10d2 +0 -0
  130. data/test/nizzaTestApp/tmp/cache/assets/CF0/DA0/sprockets%2Fd7d5b37686831d37c4dd75e645f5e016 +0 -0
  131. data/test/nizzaTestApp/tmp/cache/assets/CF4/140/sprockets%2F22a3157d204c1f8e417a25f01a2dbe45 +0 -0
  132. data/test/nizzaTestApp/tmp/cache/assets/CF6/C20/sprockets%2F7847096fc8a875a9b941beb70e91284d +0 -0
  133. data/test/nizzaTestApp/tmp/cache/assets/D05/840/sprockets%2F0ac43b191b0098c9942ebe5e47406ea2 +0 -0
  134. data/test/nizzaTestApp/tmp/cache/assets/D0B/710/sprockets%2F66190523b03af056e8dca69cd9fd2242 +0 -0
  135. data/test/nizzaTestApp/tmp/cache/assets/D0E/F80/sprockets%2F8c2b061e379a23e7c4d207adcf992462 +0 -0
  136. data/test/nizzaTestApp/tmp/cache/assets/D0F/3E0/sprockets%2F6654975eaa53a225d976278fcb6baa00 +0 -0
  137. data/test/nizzaTestApp/tmp/cache/assets/D10/A10/sprockets%2F9fe3a3308aaa4e98688d77b9b22111a4 +0 -0
  138. data/test/nizzaTestApp/tmp/cache/assets/D1A/E10/sprockets%2Ff2d34568efb5305d6990b1502d8c6ff7 +0 -0
  139. data/test/nizzaTestApp/tmp/cache/assets/D1C/C90/sprockets%2F8eb2c6f59556a9aa92dbf132856910c9 +0 -0
  140. data/test/nizzaTestApp/tmp/cache/assets/D2C/6F0/sprockets%2F1852beb3a2ad67b2cc634f05e142435e +0 -0
  141. data/test/nizzaTestApp/tmp/cache/assets/D2D/200/sprockets%2F164554b0cd8a0c5bbc610521a87cf19e +0 -0
  142. data/test/nizzaTestApp/tmp/cache/assets/D2D/D50/sprockets%2Fd585a06e2ee6203ccb04c8b84150d14d +0 -0
  143. data/test/nizzaTestApp/tmp/cache/assets/D30/9B0/sprockets%2Fa365b8a67c4a6a210d178c2c7dc812f0 +0 -0
  144. data/test/nizzaTestApp/tmp/cache/assets/D32/A10/sprockets%2F13fe41fee1fe35b49d145bcc06610705 +0 -0
  145. data/test/nizzaTestApp/tmp/cache/assets/D39/F10/sprockets%2F04aab6c01e8d12d151f54e8c6ee79715 +0 -0
  146. data/test/nizzaTestApp/tmp/cache/assets/D3B/B40/sprockets%2Fd50a18a28a8aefa85142997ae7a22f24 +0 -0
  147. data/test/nizzaTestApp/tmp/cache/assets/D3C/990/sprockets%2F47cc6b7ccafd54046c3881f18a16e162 +0 -0
  148. data/test/nizzaTestApp/tmp/cache/assets/D3D/DC0/sprockets%2F24a8e58314bc87244de540eee302f3ef +0 -0
  149. data/test/nizzaTestApp/tmp/cache/assets/D46/330/sprockets%2F50ff63775b3d84d16b93348bedd0d5d4 +0 -0
  150. data/test/nizzaTestApp/tmp/cache/assets/D4E/1B0/sprockets%2Ff7cbd26ba1d28d48de824f0e94586655 +0 -0
  151. data/test/nizzaTestApp/tmp/cache/assets/D51/0C0/sprockets%2F7ae1c78c809fa25eef9e8f5052a99332 +0 -0
  152. data/test/nizzaTestApp/tmp/cache/assets/D55/F00/sprockets%2F3df7a51021aa28af9f436ab24dc2c430 +0 -0
  153. data/test/nizzaTestApp/tmp/cache/assets/D58/170/sprockets%2F1d1519f0daa88f114d10945d0a0ee5bb +0 -0
  154. data/test/nizzaTestApp/tmp/cache/assets/D5A/EA0/sprockets%2Fd771ace226fc8215a3572e0aa35bb0d6 +0 -0
  155. data/test/nizzaTestApp/tmp/cache/assets/D71/EB0/sprockets%2F9a57785b6dbd04ace339105c79ca5ad6 +0 -0
  156. data/test/nizzaTestApp/tmp/cache/assets/D73/4C0/sprockets%2Fac8c9d97edb15b84cb35a184822952da +0 -0
  157. data/test/nizzaTestApp/tmp/cache/assets/D76/710/sprockets%2Ff9dabce89a305adf72963094731ef53a +0 -0
  158. data/test/nizzaTestApp/tmp/cache/assets/D78/6E0/sprockets%2F66c9f9ea0228a2bad83467d8a416fe4f +0 -0
  159. data/test/nizzaTestApp/tmp/cache/assets/D7B/A50/sprockets%2Fb1b95d84f048a6e4ce984cd1e29f50d6 +0 -0
  160. data/test/nizzaTestApp/tmp/cache/assets/DB7/CB0/sprockets%2F2402f5cbeb259c1b7a03f6a0ff074adb +0 -0
  161. data/test/nizzaTestApp/tmp/cache/assets/DDC/400/sprockets%2Fcffd775d018f68ce5dba1ee0d951a994 +0 -0
  162. data/test/nizzaTestApp/tmp/cache/assets/DDE/620/sprockets%2Ff6f9a7fedaea753da63ea72347d8c385 +0 -0
  163. data/test/nizzaTestApp/tmp/cache/assets/E04/890/sprockets%2F2f5173deea6c795b8fdde723bb4b63af +0 -0
  164. data/test/nizzaTestApp/tmp/cache/assets/E07/B70/sprockets%2Fd8c8bcaa8828e657a5fd4b9cacb92a17 +0 -0
  165. data/test/nizzaTestApp/tmp/cache/assets/E19/2A0/sprockets%2F10fcfbe6ebae11a40c8eac41939a1b9a +0 -0
  166. data/test/nizzaTestApp/tmp/cache/assets/E25/4C0/sprockets%2Fde2fd9fd11c04a582cdbbe3d84a35ae6 +0 -0
  167. data/test/nizzaTestApp/tmp/cache/assets/E5F/FD0/sprockets%2F43fe06bae4e63b9cecaf84e1d8b0cdf7 +0 -0
  168. data/test/nizzaTestApp/tmp/cache/assets/E64/EC0/sprockets%2Fea602649ea9f2e7adceb8c2f7d3cc5fc +0 -0
  169. data/test/nizzaTestApp/tmp/cache/sass/279c98e2d4f2491dbaea6ced015d162cb89b00e3/books.css.scssc +0 -0
  170. data/test/nizzaTestApp/tmp/cache/sass/279c98e2d4f2491dbaea6ced015d162cb89b00e3/scaffolds.css.scssc +0 -0
  171. data/test/nizzaTestApp/tmp/cache/sass/40d1259310c37828c7b403e05d7a941ef3d84db5/nice.css.scssc +0 -0
  172. data/test/nizzaTestApp/tmp/cache/sass/40d1259310c37828c7b403e05d7a941ef3d84db5/scaffolds.css.scssc +0 -0
  173. data/test/nizzaTestApp/tmp/cache/sass/fa4fcc5ec3c4b006233db2677ec61c27ca744dda/nice.css.scssc +0 -0
  174. data/test/test_helper.rb +15 -0
  175. metadata +433 -0
@@ -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.
@@ -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.
@@ -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
@@ -0,0 +1,5 @@
1
+ require 'nice/middleware'
2
+ require 'nice/engine'
3
+
4
+ module Nice
5
+ end
@@ -0,0 +1,4 @@
1
+ module Nice
2
+ class Engine < ::Rails::Engine
3
+ end
4
+ end
@@ -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
@@ -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