trellis 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (292) hide show
  1. data/.git/COMMIT_EDITMSG +1 -0
  2. data/.git/HEAD +1 -0
  3. data/.git/config +9 -0
  4. data/.git/description +1 -0
  5. data/.git/hooks/applypatch-msg.sample +15 -0
  6. data/.git/hooks/commit-msg.sample +24 -0
  7. data/.git/hooks/post-commit.sample +8 -0
  8. data/.git/hooks/post-receive.sample +15 -0
  9. data/.git/hooks/post-update.sample +8 -0
  10. data/.git/hooks/pre-applypatch.sample +14 -0
  11. data/.git/hooks/pre-commit.sample +18 -0
  12. data/.git/hooks/pre-rebase.sample +169 -0
  13. data/.git/hooks/prepare-commit-msg.sample +36 -0
  14. data/.git/hooks/update.sample +107 -0
  15. data/.git/index +0 -0
  16. data/.git/info/exclude +6 -0
  17. data/.git/logs/HEAD +1 -0
  18. data/.git/logs/refs/heads/master +1 -0
  19. data/.git/logs/refs/remotes/origin/master +1 -0
  20. data/.git/objects/01/2368d4dd3162ed570e6c1423ecb07a6a35f8c0 +0 -0
  21. data/.git/objects/02/238c88c522b88e71529495460cc5c2c8abe110 +0 -0
  22. data/.git/objects/07/03d3b123189f1da72d3889d9340e681841c96d +0 -0
  23. data/.git/objects/0b/3935fdbf3ce8d37e6cb2a6ec94edaccb1c58dc +0 -0
  24. data/.git/objects/0f/ecd1ad35f280c874ce070b7a97a4c1f86ace54 +0 -0
  25. data/.git/objects/12/61911b063b7e6883c5e9db7ab4396183f6f2b5 +0 -0
  26. data/.git/objects/13/0b7e2d214ec198d07aeed5b462597670ce85ad +0 -0
  27. data/.git/objects/13/214d40f02b63f1233fd7881b1df9bb75d9c0a7 +0 -0
  28. data/.git/objects/14/7522bdc96af0b578935d81c1a7347b40573940 +0 -0
  29. data/.git/objects/16/a7fde706fe92b3548254d29722f5ca311ce63b +2 -0
  30. data/.git/objects/16/fe580663cc29d141bf416dff5bf21bf4ea42a3 +0 -0
  31. data/.git/objects/17/2d55806918bafb4e701515edb27147a4ef6498 +0 -0
  32. data/.git/objects/17/686ef1882ed6c3c07b781fab18739b1c5e48bd +0 -0
  33. data/.git/objects/17/824053c41a2cb523303e20cac42f66f9ca2134 +0 -0
  34. data/.git/objects/18/68448c5f88bc73b2fef6b8f3aa75b663013a3a +1 -0
  35. data/.git/objects/1a/6d9051dc4c7db10bdcf5e17c5cb717b072d30a +0 -0
  36. data/.git/objects/1a/8bdb445fb5668dcd1309cb9de5dedf846c822f +0 -0
  37. data/.git/objects/1b/6fa67b70358fdb82eabcd7ef3bc46d518d0426 +0 -0
  38. data/.git/objects/1c/f6510ed08a274fb3292d40557cebb3e7090767 +0 -0
  39. data/.git/objects/1d/e661ce11d5335e5dd7968ef5698e2eb164eb52 +0 -0
  40. data/.git/objects/1e/301ee672d6040e734af6d0a5491185129d7b67 +0 -0
  41. data/.git/objects/21/2201d46200f13d23d8199bd4a7ecc9165205b3 +0 -0
  42. data/.git/objects/22/f687370babdf38d224d2a9ff039707e8d0aac1 +0 -0
  43. data/.git/objects/24/202d2de95dc31f5f1802e629a16ad45b723267 +0 -0
  44. data/.git/objects/25/4c5b6b8d211d8b9f07310fd342fd7b078a81af +0 -0
  45. data/.git/objects/25/5832653fde9e9748855347fe7bce013aa0a6c7 +0 -0
  46. data/.git/objects/25/806945c89b4b5adb8c8676c308e4653361109c +5 -0
  47. data/.git/objects/26/cb8deaca28bf275769cbb71b88e53a44adb9ca +0 -0
  48. data/.git/objects/2e/1c13fb5f154f89cccd4c645540a2ec8ee0f57a +0 -0
  49. data/.git/objects/2f/437425b2c085bb529379e409d6b4bc7f9f9fba +0 -0
  50. data/.git/objects/2f/7cf4a25937b71f3de52e70c7a47cb2f7da07ea +0 -0
  51. data/.git/objects/31/31bdbbac6e6e7e24fd6ea84332461f67e6e5b6 +0 -0
  52. data/.git/objects/32/02e02de478263912b729283eabc7cdab8a8037 +0 -0
  53. data/.git/objects/32/839ed9baa8c800d9033655d1ee995499825fb5 +0 -0
  54. data/.git/objects/34/2e479c61796125b0e025b483401ec12f3c5824 +2 -0
  55. data/.git/objects/36/7acb017aebab3f627a5208c25885c21fa7fdbb +0 -0
  56. data/.git/objects/38/12910d2e2a8e6eeb79a399d20751675d3fbe77 +0 -0
  57. data/.git/objects/38/3fe1e1a07537db6e0f39c6005b7b4e40fa5ccd +0 -0
  58. data/.git/objects/39/5ef328bc94df9191f4d80a77503a5be9f1f673 +0 -0
  59. data/.git/objects/39/dc76dc096fdf737e9f4f7062b78cbf4ab9628f +0 -0
  60. data/.git/objects/3b/09eb196140b471556ed4c3987c0bc739733617 +0 -0
  61. data/.git/objects/3b/9f97f1bf55834832a92bab1ab900918dc49570 +0 -0
  62. data/.git/objects/40/5ffd3925b39dd905638ec24640dd8efa27cc1b +0 -0
  63. data/.git/objects/42/4a5f37c6fe3a7cac54b0f85688c1cce7da9cdf +0 -0
  64. data/.git/objects/44/9e23363d4e20da6a67be7ab78fc610b4e9852a +0 -0
  65. data/.git/objects/48/90bb5d241cecaa30b28148c920b833056c7508 +0 -0
  66. data/.git/objects/4c/558c43676410a2c0a7d5c7b94fca18923a9af1 +0 -0
  67. data/.git/objects/4f/bf572175dd5245104679ff8f676f6a34466c29 +0 -0
  68. data/.git/objects/53/8583edf00c9f656b32149a0e69e030371737c5 +0 -0
  69. data/.git/objects/5b/8a2c4bf7bb56ae199414096dbda21fd4b8cd63 +0 -0
  70. data/.git/objects/5c/9fe8ff998d56dc076657ef88ee9f784ec160f9 +0 -0
  71. data/.git/objects/5f/8441f9d3507587940f31af04dd9fd5911d2812 +0 -0
  72. data/.git/objects/61/4f2752550e63b793e31dcf98b5ea26c32eb0be +0 -0
  73. data/.git/objects/65/801b0fabdfd673861dbb2ac134d438ab2cc5e4 +3 -0
  74. data/.git/objects/66/2746dffaddbcd311f4ac070b5b9bddcd8894a7 +0 -0
  75. data/.git/objects/69/1ed3b65603a0d0dabfb66720e7163664be78f5 +0 -0
  76. data/.git/objects/69/3dc3b6037df8c1c8dfda916d649e7975481124 +0 -0
  77. data/.git/objects/69/67538427b3a371b33c01c72726ac7e67a04c39 +0 -0
  78. data/.git/objects/69/6cd6c3508e2adcc68829497884ff3d4b774dc2 +0 -0
  79. data/.git/objects/69/6ee93917040308ccd0bfc245d63c1cbeb23dae +0 -0
  80. data/.git/objects/6a/3ac317746a8a2cfc88f3580bbd2b03dc0d50b5 +0 -0
  81. data/.git/objects/6d/6ba435839a65b96cb3f7584a40dd3767a0990f +0 -0
  82. data/.git/objects/6f/32c190a651a82caeefeb55eb797fc5daf05426 +0 -0
  83. data/.git/objects/71/fecbce1bec36eaf4de655763d4bb7ceffc991d +0 -0
  84. data/.git/objects/72/01d78a9e90347fd2f7790f9e8665587791d853 +0 -0
  85. data/.git/objects/73/6c97d6a551363ba4a4956408ba228fcab7f91f +0 -0
  86. data/.git/objects/75/3264271e935e18f4eada49aa1818c6fb9e6abb +0 -0
  87. data/.git/objects/75/cc7d390b4a565495cbdc5a08d4479e7e8dbfeb +0 -0
  88. data/.git/objects/76/7c0c96f68b9ccec34ae538045a63bbe4f97d3e +0 -0
  89. data/.git/objects/7b/47fe9b1ab9f2692b9706054de706430089ecd4 +0 -0
  90. data/.git/objects/7c/73144ef6847d1bc2315aee0925c0e5cf5c09d1 +0 -0
  91. data/.git/objects/7d/9c177c155386606aa1024d5cb020a097eea64a +0 -0
  92. data/.git/objects/7d/e9a3296007c09ba9b7bc1802e1c5d9ad1d5dbb +0 -0
  93. data/.git/objects/7e/b73d408147c8d84881109189a9724292abcb8d +0 -0
  94. data/.git/objects/82/0e3bf5449a8f6b8eb637a653dcb2dc387b393c +0 -0
  95. data/.git/objects/83/a10ce96763cb30d0b81425e9372f89c52f4c9f +0 -0
  96. data/.git/objects/83/bdb801e78444d59225f83cf2f336f114cd23cf +0 -0
  97. data/.git/objects/85/24e583e822abc18b1ffdd0aa356c9c079b5548 +0 -0
  98. data/.git/objects/88/b46cc8de5237897d4ba5023d3efdc83fec4f7c +0 -0
  99. data/.git/objects/89/5eea66474f48a2cbf643ad0ef266fd26f9d2a3 +0 -0
  100. data/.git/objects/8a/539c5fc35d5b770d67241d2ccb6c6855df6a43 +0 -0
  101. data/.git/objects/8a/82cdbfb20cc58fdffe4e13b70b55977deb92c6 +0 -0
  102. data/.git/objects/8b/2b537442e4ac8719ffa71b3ae0c707133df06f +0 -0
  103. data/.git/objects/8c/2a45f0c81284a081f8d7980e9a54210a9dd52c +0 -0
  104. data/.git/objects/8e/10355b4ab312450031966107ef6a65b13833ee +0 -0
  105. data/.git/objects/90/f6c76767ae725f15fa6790dcb6afd1719fa0e2 +0 -0
  106. data/.git/objects/92/92b696aba9840c089241c68d4c6ca14be38a64 +2 -0
  107. data/.git/objects/93/e03faa6906892434fa9f2ad230c4ececc64055 +0 -0
  108. data/.git/objects/93/eca2e28d80fda45d327fdfc6c39ae690af41cd +0 -0
  109. data/.git/objects/95/92fe63bd5403654730cdec76cbf116ef83bbff +0 -0
  110. data/.git/objects/96/bc6dd471c1c188a2eb4308bcc3ca4e6c8faac8 +0 -0
  111. data/.git/objects/96/d855221a9ff4fba57d2b70680b57bd5115cfde +0 -0
  112. data/.git/objects/97/4ec92b2b9eb01a73fb57dc78d463cafdda69a7 +0 -0
  113. data/.git/objects/97/b7ec01ebe089832a0972d20b0a40d54456985c +2 -0
  114. data/.git/objects/97/fa6aaae4cfc7654feb39971e7aee5c41ed1679 +1 -0
  115. data/.git/objects/98/18351c6685a1df5d23686e6346d11d261cc6f0 +0 -0
  116. data/.git/objects/98/3d3b7e55cbbce2f2a52c6e6d830a5dca7b4d42 +0 -0
  117. data/.git/objects/a2/e551049a5388112c21e7f3a4a720bf720601fa +0 -0
  118. data/.git/objects/a3/7066bea4fae90d34761a3ebb3f8a348d94eba1 +0 -0
  119. data/.git/objects/a6/c99619145d1b9b75aa15ca04fbe4db0b2ee79b +0 -0
  120. data/.git/objects/aa/e2234d0a8c1485096c7a7de039eceb4e019e13 +0 -0
  121. data/.git/objects/b0/3b52f4269c9719e02ecd6caabd8aff5adf3aac +0 -0
  122. data/.git/objects/b1/50dbbd89f825302a9e82d8fdd66f238b8e6fc4 +0 -0
  123. data/.git/objects/b4/545fbe393aaa18f1c48de4d9f68f64ebf09ddc +0 -0
  124. data/.git/objects/b6/8691404e088692929d40b988b993f815d76756 +0 -0
  125. data/.git/objects/b7/daa3d844ef5f5cd917084e48edd82e67f8f476 +0 -0
  126. data/.git/objects/bf/4232281dba5a0f17e8b5c16b9cbd0f52274fa8 +0 -0
  127. data/.git/objects/bf/9b84d10f19188beffe5ee99e6f5af035ba77d3 +2 -0
  128. data/.git/objects/c0/06e664b306145de804f74ab27550d62a92f58a +1 -0
  129. data/.git/objects/c2/2806b16775f66453012d085d21fb8698521ea9 +0 -0
  130. data/.git/objects/c2/7f6559350f7adb19d43742b55b2f91d07b6550 +0 -0
  131. data/.git/objects/c3/833df21ed674a77374fd40ed931f900134d38b +0 -0
  132. data/.git/objects/c3/b9da021eb76abad0b17ad6c4be75595e41a7e6 +0 -0
  133. data/.git/objects/c5/ca6b7e3561ea88aba1712aabb984d1167787f1 +0 -0
  134. data/.git/objects/c6/39f65e1f43c75b7f57c5ab28623d295dfb2afd +0 -0
  135. data/.git/objects/c8/6f05fa5539f8e69e94850d1cd917919ff581be +0 -0
  136. data/.git/objects/cb/5e499c8dcc6f2901014a08e74f04b7a4bd6596 +0 -0
  137. data/.git/objects/cc/1289d25eeee6ba31b4e406372cb9abf969fc6f +0 -0
  138. data/.git/objects/cc/784e1b49111c42fa7eae10ad0e788186e672d1 +0 -0
  139. data/.git/objects/cc/bcae254dad515d7ef01a461980544e4483c41e +0 -0
  140. data/.git/objects/ce/bfadad3a7c17148998e04275e6d0d1cf1b2e1e +0 -0
  141. data/.git/objects/ce/f3335522abdc47a2c94be70455dcfaa9a51a27 +0 -0
  142. data/.git/objects/cf/6add7ea568d3d90d6a1f8afb0898b0119b14ff +0 -0
  143. data/.git/objects/d0/719abd196033fec0e4cbe2af68a26ca435389c +0 -0
  144. data/.git/objects/d1/ce63ff0eaec4dba419e5547f99195dfb9b5b20 +0 -0
  145. data/.git/objects/d2/c3feb3c74b892cca0049c0aced6ce786a784c8 +0 -0
  146. data/.git/objects/d7/8a91da13413419da71b539a3c6c7df4d8a2458 +0 -0
  147. data/.git/objects/d8/c7bed2b1357d54b6935536a8a51a3c73d535b4 +0 -0
  148. data/.git/objects/da/c3a1277ccb4806011e33eb32c5609cc45c46f2 +0 -0
  149. data/.git/objects/dc/3f920f4b937d0e2ac512f0bb31d477624c46d5 +0 -0
  150. data/.git/objects/dc/6c389abf5bd0d7b9be2a6bb6af90738a410e59 +0 -0
  151. data/.git/objects/df/07c423309f39c4d5570b0fad65dff23af654ae +0 -0
  152. data/.git/objects/e2/93a5c9e2257b9f3fcb8fa5d5c8147a2f07ba1b +0 -0
  153. data/.git/objects/e4/8464df56bf487e96e21ea99487330266dae3c9 +0 -0
  154. data/.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 +0 -0
  155. data/.git/objects/e8/0c13a1d1f62756fb9f1e4fb9f7512e5b8c5816 +0 -0
  156. data/.git/objects/e9/b8ade2125b2615ddc4c8043940c40ed36215c9 +0 -0
  157. data/.git/objects/ed/59a7599a81ef7ea7c230c3332345a3484febf8 +0 -0
  158. data/.git/objects/f0/d2a74226877e9a28af814d0d3727e4a3487439 +0 -0
  159. data/.git/objects/f0/f3ba7264f1d8e60eb93a907092af98fdc248e4 +0 -0
  160. data/.git/objects/f1/cdcc7ff2dc6298318523b2c4e425f515a58bf7 +3 -0
  161. data/.git/objects/f4/45c96f406f4d2f9839b3fe2fe8bc7f943ab469 +0 -0
  162. data/.git/objects/f7/eef7ce0a650d6f0c85dc4982f481da7bd44362 +0 -0
  163. data/.git/objects/f9/0abf96c5059ce1ba115f73bc9cf33e4663b3e1 +0 -0
  164. data/.git/objects/f9/cf7ae572eaebbd63f809f16a5ef14b9c679790 +0 -0
  165. data/.git/objects/fc/300df53fe2c9467210e2fd4c2f34e1c9c7c89a +0 -0
  166. data/.git/objects/fc/45546596ab8debc4c9f49a7abc7fc14a7bcc7f +4 -0
  167. data/.git/objects/fc/68d89167f69614eda17bd56dfccb4355d818e6 +0 -0
  168. data/.git/objects/fc/e4c02bb132e73b3cb957da182db7b75a47c242 +0 -0
  169. data/.git/objects/fe/be283da7f326344013f1fc847c12c8f29ab6ef +0 -0
  170. data/.git/refs/heads/master +1 -0
  171. data/.git/refs/remotes/origin/master +1 -0
  172. data/.gitignore +5 -0
  173. data/History.txt +4 -0
  174. data/License.txt +22 -0
  175. data/Manifest.txt +291 -0
  176. data/README +156 -0
  177. data/README.txt +156 -0
  178. data/Rakefile +4 -0
  179. data/config/hoe.rb +83 -0
  180. data/config/requirements.rb +15 -0
  181. data/examples/crud_components/html/address_view_edit.xhtml +19 -0
  182. data/examples/crud_components/html/addresses.xhtml +20 -0
  183. data/examples/crud_components/html/images/destroy.gif +0 -0
  184. data/examples/crud_components/html/images/edit.gif +0 -0
  185. data/examples/crud_components/html/images/field-error-marker.gif +0 -0
  186. data/examples/crud_components/html/images/sort-asc.png +0 -0
  187. data/examples/crud_components/html/images/sort-desc.png +0 -0
  188. data/examples/crud_components/html/images/sortable.png +0 -0
  189. data/examples/crud_components/html/style/trellis.css +416 -0
  190. data/examples/crud_components/source/crud_components.rb +77 -0
  191. data/examples/crud_components/source/domain.rb +6 -0
  192. data/examples/examples.txt +65 -0
  193. data/examples/flickr/source/flickr.rb +87 -0
  194. data/examples/guest_book/source/guest_book.rb +61 -0
  195. data/examples/hangman/html/game_over.xhtml +23 -0
  196. data/examples/hangman/html/guess.xhtml +47 -0
  197. data/examples/hangman/html/images/a_disabled.png +0 -0
  198. data/examples/hangman/html/images/a_enabled.png +0 -0
  199. data/examples/hangman/html/images/b_disabled.png +0 -0
  200. data/examples/hangman/html/images/b_enabled.png +0 -0
  201. data/examples/hangman/html/images/c_disabled.png +0 -0
  202. data/examples/hangman/html/images/c_enabled.png +0 -0
  203. data/examples/hangman/html/images/d_disabled.png +0 -0
  204. data/examples/hangman/html/images/d_enabled.png +0 -0
  205. data/examples/hangman/html/images/e_disabled.png +0 -0
  206. data/examples/hangman/html/images/e_enabled.png +0 -0
  207. data/examples/hangman/html/images/f_disabled.png +0 -0
  208. data/examples/hangman/html/images/f_enabled.png +0 -0
  209. data/examples/hangman/html/images/g_disabled.png +0 -0
  210. data/examples/hangman/html/images/g_enabled.png +0 -0
  211. data/examples/hangman/html/images/h_disabled.png +0 -0
  212. data/examples/hangman/html/images/h_enabled.png +0 -0
  213. data/examples/hangman/html/images/i_disabled.png +0 -0
  214. data/examples/hangman/html/images/i_enabled.png +0 -0
  215. data/examples/hangman/html/images/j_disabled.png +0 -0
  216. data/examples/hangman/html/images/j_enabled.png +0 -0
  217. data/examples/hangman/html/images/k_disabled.png +0 -0
  218. data/examples/hangman/html/images/k_enabled.png +0 -0
  219. data/examples/hangman/html/images/l_disabled.png +0 -0
  220. data/examples/hangman/html/images/l_enabled.png +0 -0
  221. data/examples/hangman/html/images/m_disabled.png +0 -0
  222. data/examples/hangman/html/images/m_enabled.png +0 -0
  223. data/examples/hangman/html/images/n_disabled.png +0 -0
  224. data/examples/hangman/html/images/n_enabled.png +0 -0
  225. data/examples/hangman/html/images/o_disabled.png +0 -0
  226. data/examples/hangman/html/images/o_enabled.png +0 -0
  227. data/examples/hangman/html/images/p_disabled.png +0 -0
  228. data/examples/hangman/html/images/p_enabled.png +0 -0
  229. data/examples/hangman/html/images/q_disabled.png +0 -0
  230. data/examples/hangman/html/images/q_enabled.png +0 -0
  231. data/examples/hangman/html/images/r_disabled.png +0 -0
  232. data/examples/hangman/html/images/r_enabled.png +0 -0
  233. data/examples/hangman/html/images/s_disabled.png +0 -0
  234. data/examples/hangman/html/images/s_enabled.png +0 -0
  235. data/examples/hangman/html/images/t_disabled.png +0 -0
  236. data/examples/hangman/html/images/t_enabled.png +0 -0
  237. data/examples/hangman/html/images/u_disabled.png +0 -0
  238. data/examples/hangman/html/images/u_enabled.png +0 -0
  239. data/examples/hangman/html/images/v_disabled.png +0 -0
  240. data/examples/hangman/html/images/v_enabled.png +0 -0
  241. data/examples/hangman/html/images/w_disabled.png +0 -0
  242. data/examples/hangman/html/images/w_enabled.png +0 -0
  243. data/examples/hangman/html/images/x_disabled.png +0 -0
  244. data/examples/hangman/html/images/x_enabled.png +0 -0
  245. data/examples/hangman/html/images/y_disabled.png +0 -0
  246. data/examples/hangman/html/images/y_enabled.png +0 -0
  247. data/examples/hangman/html/images/z_disabled.png +0 -0
  248. data/examples/hangman/html/images/z_enabled.png +0 -0
  249. data/examples/hangman/html/resources/word_list.txt +35 -0
  250. data/examples/hangman/html/start.xhtml +18 -0
  251. data/examples/hangman/html/style/hangman.css +27 -0
  252. data/examples/hangman/source/hangman.rb +71 -0
  253. data/examples/hilo/html/game_over.xhtml +15 -0
  254. data/examples/hilo/html/guess.xhtml +18 -0
  255. data/examples/hilo/html/start.xhtml +17 -0
  256. data/examples/hilo/source/hilo.rb +51 -0
  257. data/examples/simplest/html/start.xhtml +0 -0
  258. data/examples/simplest/source/simplest.rb +39 -0
  259. data/examples/stateful_counters/html/counters.xhtml +49 -0
  260. data/examples/stateful_counters/html/style/main.css +441 -0
  261. data/examples/stateful_counters/source/stateful_counters.rb +68 -0
  262. data/lib/trellis/core_components.rb +870 -0
  263. data/lib/trellis/jquery.rb +38 -0
  264. data/lib/trellis/logging.rb +60 -0
  265. data/lib/trellis/trellis.rb +701 -0
  266. data/lib/trellis/utils.rb +209 -0
  267. data/lib/trellis/version.rb +35 -0
  268. data/lib/trellis.rb +27 -0
  269. data/script/console +10 -0
  270. data/script/destroy +14 -0
  271. data/script/generate +14 -0
  272. data/script/txt2html +82 -0
  273. data/setup.rb +1585 -0
  274. data/tasks/deployment.rake +34 -0
  275. data/tasks/environment.rake +7 -0
  276. data/tasks/rspec.rake +21 -0
  277. data/tasks/website.rake +17 -0
  278. data/test/application_spec.rb +79 -0
  279. data/test/component_spec.rb +88 -0
  280. data/test/core_extensions_spec.rb +108 -0
  281. data/test/fixtures/application_spec_applications.rb +51 -0
  282. data/test/page_spec.rb +46 -0
  283. data/test/renderer_spec.rb +12 -0
  284. data/test/router_spec.rb +52 -0
  285. data/test/spec.opts +1 -0
  286. data/test/spec_helper.rb +16 -0
  287. data/website/index.html +124 -0
  288. data/website/index.txt +119 -0
  289. data/website/javascripts/rounded_corners_lite.inc.js +285 -0
  290. data/website/stylesheets/screen.css +138 -0
  291. data/website/template.html.erb +48 -0
  292. metadata +512 -0
@@ -0,0 +1,701 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ #--
4
+ # Copyright &169;2001-2008 Integrallis Software, LLC.
5
+ # All Rights Reserved.
6
+ #
7
+ # Permission is hereby granted, free of charge, to any person obtaining
8
+ # a copy of this software and associated documentation files (the
9
+ # "Software"), to deal in the Software without restriction, including
10
+ # without limitation the rights to use, copy, modify, merge, publish,
11
+ # distribute, sublicense, and/or sell copies of the Software, and to
12
+ # permit persons to whom the Software is furnished to do so, subject to
13
+ # the following conditions:
14
+ #
15
+ # The above copyright notice and this permission notice shall be
16
+ # included in all copies or substantial portions of the Software.
17
+ #
18
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
+ #++
26
+
27
+ require 'trellis/utils'
28
+ require 'trellis/logging'
29
+ require 'rubygems'
30
+ require 'rack'
31
+ require 'radius'
32
+ require 'builder'
33
+ require 'hpricot'
34
+ require 'rexml/document'
35
+ require 'extensions/string'
36
+ require 'haml'
37
+ require 'markaby'
38
+ require 'redcloth'
39
+ require 'bluecloth'
40
+ require 'english/inflect'
41
+
42
+ module Trellis
43
+
44
+ # -- Application --
45
+ # Represents a Trellis Web Application. An application can define one or more
46
+ # pages and it must define a home page or entry point into the application
47
+ class Application
48
+ include Logging
49
+
50
+ # descendant application classes get a singleton class level instances for
51
+ # holding homepage, dependent pages, static resource routing paths
52
+ def self.inherited(child) #:nodoc:
53
+ child.meta_attr_reader(:homepage)
54
+ child.attr_array(:pages, :create_accessor => false)
55
+ child.attr_array(:static_routes)
56
+ child.meta_def(:logger) { Application.logger }
57
+ super
58
+ end
59
+
60
+ # class method that defines the homepage or entry point of the application
61
+ # the entry point is the URL pattern / where the application is mounted
62
+ def self.home(sym)
63
+ @homepage = sym
64
+ @pages << sym
65
+ end
66
+
67
+ # class method that takes a list of page symbols
68
+ def self.pages(*syms)
69
+ @pages = @pages | syms
70
+ end
71
+
72
+ # define url paths for static resources
73
+ def self.map_static(urls = [], root = File.expand_path("#{File.dirname($0)}/../html/"))
74
+ @static_routes << {:urls => urls, :root => root}
75
+ end
76
+
77
+ # bootstrap the application
78
+ def start(port = 3000)
79
+ Application.logger.info "Starting Trellis Application #{self.class} on port #{port}"
80
+ Rack::Handler::Mongrel.run configured_instance, :Port => port do |server|
81
+ trap(:INT) do
82
+ Application.logger.info "Exiting Trellis Application #{self.class}"
83
+ server.stop
84
+ end
85
+ end
86
+ end
87
+
88
+ def configured_instance
89
+ application = Rack::ShowStatus.new(self)
90
+ application = Rack::ShowExceptions.new(application)
91
+ application = Rack::CommonLogger.new(application, Application.logger)
92
+ application = Rack::Session::Cookie.new(application)
93
+
94
+ # set all static resource paths
95
+ self.class.static_routes.each do |path|
96
+ application = Rack::Static.new(application, path)
97
+ end
98
+ application
99
+ end
100
+
101
+ # implements the rack specification
102
+ def call(env)
103
+ response = Rack::Response.new
104
+ request = Rack::Request.new(env)
105
+ session = env["rack.session"]
106
+
107
+ route = DefaultRouter.new(self).route(request)
108
+
109
+ Application.logger.debug "request received with url_root of #{request.script_name}"
110
+
111
+ page = route.destination.new if route.destination
112
+ if page
113
+ page.class.url_root = request.script_name
114
+ page.inject_dependent_pages
115
+ page.call_if_provided(:before_load)
116
+ page.load_page_session_information(session)
117
+ page.call_if_provided(:after_load)
118
+ page.params = request.params.keys_to_symbols
119
+ page = page.process_event(route.event, route.value, route.source, session) if route.event
120
+
121
+ # prepare the http response
122
+ if request.post? && page.kind_of?(Trellis::Page)
123
+ # redirect after post
124
+ response.status = 302
125
+ response.headers["Location"] = "#{request.script_name}/#{page.class.class_to_sym}"
126
+ else
127
+ response.body = page.kind_of?(Trellis::Page) ? page.render : page
128
+ response.status = 200
129
+ end
130
+ else
131
+ # could find a page with default router, now what?
132
+ end
133
+ response.finish
134
+ end
135
+ end
136
+
137
+ # -- Route --
138
+ # A route object encapsulates an event, the event destination (target), the
139
+ # event source and an optional event value
140
+ class Route
141
+ attr_reader :destination, :event, :source, :value
142
+
143
+ def initialize(destination, event, source, value)
144
+ @destination, @event, @source, @value = destination, event, source, value
145
+ end
146
+ end
147
+
148
+ # -- Router --
149
+ # A Router returns a Route in response to an HTTP request
150
+ class Router
151
+ attr_reader :application
152
+
153
+ def initialize(application)
154
+ @application = application
155
+ end
156
+ end
157
+
158
+ # -- DefaultRouter --
159
+ # The default routing scheme is in the form /page[.event_[source]][/value][?query_params]
160
+ class DefaultRouter < Router
161
+ ROUTE_REGEX = %r{^(?:/)([^.]+)(?:.([^_/]+)(?:_([^/]+))?)?(?:/([\w]+)$)?}
162
+
163
+ def route(request)
164
+ value, source, event, destination = request.path_info.match(ROUTE_REGEX).to_a.reverse
165
+ destination = @application.class.homepage unless destination
166
+ page = Page.get_page(destination.to_sym)
167
+
168
+ Route.new(page, event, source, value)
169
+ end
170
+ end
171
+
172
+ # -- Page --
173
+ # Represents a Web Page in a Trellis Application. A Page can contain multiple
174
+ # components and it defines a template or view either as an external file
175
+ # (xml, xhtml, other) or programmatically using Markaby or HAML
176
+ # A Trellis Page contains listener methods to respond to events trigger by
177
+ # components in the same page or other pages
178
+ class Page
179
+
180
+ @@subclasses = Hash.new
181
+
182
+ attr_accessor :params, :logger
183
+
184
+ def self.inherited(child) #:nodoc:
185
+ @@subclasses[child.class_to_sym] = child
186
+
187
+ child.instance_variable_set(:@name, child.underscore_class_name)
188
+ child.attr_array(:pages, :create_accessor => false)
189
+ child.attr_array(:components)
190
+ child.attr_array(:stateful_components)
191
+ child.attr_array(:persistents)
192
+ child.meta_attr_accessor :url_root
193
+ child.meta_attr_accessor :name
194
+ child.meta_attr_accessor :layout
195
+ child.meta_def(:add_stateful_component) { |tid,clazz| @stateful_components << [tid,clazz] }
196
+
197
+ locate_template child
198
+ super
199
+ end
200
+
201
+ def self.template(body = nil, options = nil, &block)
202
+ format = options[:format] if options
203
+ if block_given?
204
+ mab = Markaby::Builder.new({}, self, &block)
205
+ html = mab.to_s
206
+ else
207
+ case format
208
+ when :haml
209
+ html = Haml::Engine.new(body).render
210
+ when :textile
211
+ html = RedCloth.new(body).to_html
212
+ when :markdown
213
+ html = BlueCloth.new(body).to_html
214
+ else # assume the body is (x)html
215
+ html = body
216
+ end
217
+ end
218
+ @template = Hpricot.XML(html)
219
+ find_components
220
+ end
221
+
222
+ def self.parsed_template
223
+ # try to reload the template if it wasn't found on during inherited
224
+ # since it could have failed if the app was not mounted as root
225
+ unless @template
226
+ Application.logger.debug "parsed template was no loaded, attempting to load..."
227
+ locate_template(self)
228
+ end
229
+ @template
230
+ end
231
+
232
+ def self.pages(*syms)
233
+ @pages = @pages | syms
234
+ end
235
+
236
+ def self.persistent(*fields)
237
+ meta_attr_reader fields
238
+ @persistents = @persistents | fields
239
+ end
240
+
241
+ def self.get_page(sym)
242
+ @@subclasses[sym]
243
+ end
244
+
245
+ def initialize # TODO this is Ugly.... should no do it in initialize since it'll require super in child classes
246
+ self.class.stateful_components.each do |id_component|
247
+ id_component[1].enhance_page(self, id_component[0])
248
+ end
249
+ @logger = Application.logger
250
+ end
251
+
252
+ def process_event(event, value, source, session)
253
+ method = source ? "on_#{event}_from_#{source}" : "on_#{event}"
254
+
255
+ # execute the method passing the value if necessary
256
+ unless value
257
+ method_result = send method.to_sym
258
+ else
259
+ method_result = send method.to_sym, value
260
+ end
261
+
262
+ # determine navigation flow based on the return value of the method call
263
+ if method_result
264
+ if method_result.kind_of?(Trellis::Page)
265
+ page = method_result
266
+ # save the current page persistent information
267
+ if self != method_result
268
+ save_page_session_information(session)
269
+ page.inject_dependent_pages
270
+ page.call_if_provided(:before_load)
271
+ end
272
+
273
+ # save the persistent information before rendering a response
274
+ page.save_page_session_information(session)
275
+ end
276
+ end
277
+
278
+ method_result
279
+ end
280
+
281
+ def load_page_session_information(session)
282
+ load_persistent_fields_data(session)
283
+ load_stateful_components_data(session)
284
+ end
285
+
286
+ def save_page_session_information(session)
287
+ save_persistent_fields_data(session)
288
+ save_stateful_components_data(session)
289
+ end
290
+
291
+ def render
292
+ call_if_provided(:before_render)
293
+ result = Renderer.new(self).render
294
+ call_if_provided(:after_render)
295
+ result
296
+ end
297
+
298
+ # inject an instance of each of the injected pages classes as instance variables
299
+ # of the current page
300
+ def inject_dependent_pages
301
+ self.class.inject_dependent_pages(self)
302
+ end
303
+
304
+ def self.inject_dependent_pages(target)
305
+ @pages.each do |sym|
306
+ clazz = Page.get_page(sym)
307
+ target.instance_variable_set("@#{sym}".to_sym, clazz.new)
308
+ target.meta_def(sym) { instance_variable_get("@#{sym}") }
309
+ end
310
+ end
311
+
312
+ private
313
+
314
+ def self.locate_template(clazz)
315
+ begin
316
+ if clazz.url_root.nil? || clazz.url_root.blank?
317
+ dir = "#{File.dirname($0)}/../html/"
318
+ else
319
+ dir = "#{File.dirname($0)}#{clazz.url_root}/html/".gsub("Rack: ", '')
320
+ end
321
+ base = "#{clazz.underscore_class_name}"
322
+
323
+ Application.logger.debug "looking for template #{base} in #{dir}"
324
+
325
+ [:xhtml, :haml, :textile, :markdown].each do |format|
326
+ filename = "#{base}.#{format}"
327
+ file = File.find_first(dir, filename)
328
+ if file
329
+ Application.logger.debug "found template for page => #{clazz}: #{filename}"
330
+ File.open(file, "r") { |f| clazz.template(f.read, :format => format) }
331
+ break
332
+ end
333
+ end
334
+ rescue Exception => e
335
+ Application.logger.debug "no template found for page => #{clazz}: #{base} : #{e}"
336
+ end
337
+ end
338
+
339
+ def self.find_components
340
+ @components.clear
341
+ classes_processed = []
342
+ doc = REXML::Document.new(@template.to_html)
343
+ # look for component declarations in the template
344
+ doc.elements.each('//trellis:*') do |element|
345
+ # retrieve the component class
346
+ component = Component.get_component(element.name.to_sym)
347
+ # for components that are contained in other components
348
+ # pass the parent information (parent tid)
349
+ unless component.containers.empty?
350
+ parent = nil
351
+ # loop over all the container types until we find the matching parent
352
+ component.containers.each do |container|
353
+ parent = REXML::XPath.first(element, "ancestor::trellis:#{container}")
354
+ break if parent
355
+ end
356
+ element.attributes['parent_tid'] = parent.attributes['tid'] if parent
357
+ end
358
+
359
+ tid = element.attributes['tid']
360
+ unless component
361
+ Application.logger.info "could not find #{element.name} in component hash"
362
+ else
363
+ # add component class to the page component list
364
+ components << component
365
+ add_stateful_component(tid, component) #should I always do this?
366
+ process_component_contributions(component, classes_processed, element.attributes)
367
+ # also process any component dependencies - this should be recursive
368
+ component.dependencies.each do |dependency|
369
+ components << dependency
370
+ process_component_contributions(dependency, classes_processed)
371
+ end
372
+ end
373
+ end
374
+ end
375
+
376
+ def self.process_component_contributions(component, classes_processed, attributes=nil)
377
+ unless classes_processed.include?(component)
378
+ component.add_style_links_to_page(self, attributes)
379
+ component.add_script_links_to_page(self, attributes)
380
+ component.add_class_styles_to_page(self, attributes)
381
+ component.add_class_scripts_to_page(self, attributes)
382
+ component.add_document_modifications_to_page(self)
383
+ end
384
+
385
+ component.add_styles_to_page(self, attributes)
386
+ component.add_scripts_to_page(self, attributes)
387
+
388
+ classes_processed << component unless classes_processed.include?(component)
389
+ end
390
+
391
+ def load_persistent_fields_data(session)
392
+ self.class.persistents.each do |persistent_field|
393
+ field = "@#{persistent_field}".to_sym
394
+ current_value = instance_variable_get(field)
395
+ new_value = session["#{self.class}_#{persistent_field}"]
396
+ if current_value != new_value && new_value != nil
397
+ instance_variable_set(field, new_value)
398
+ end
399
+ end
400
+ end
401
+
402
+ def load_stateful_components_data(session)
403
+ self.instance_variables.each do |instance_variable_name|
404
+ instance_variable = self.instance_variable_get(instance_variable_name.to_sym)
405
+ instance_variable.load_component_session_information(self, instance_variable_name, session) if instance_variable.respond_to?(:load_component_session_information)
406
+ end
407
+ end
408
+
409
+ def save_persistent_fields_data(session)
410
+ self.class.persistents.each do |persistent_field|
411
+ session["#{self.class}_#{persistent_field}"] = instance_variable_get("@#{persistent_field}".to_sym)
412
+ end
413
+ end
414
+
415
+ def save_stateful_components_data(session)
416
+ self.instance_variables.each do |instance_variable_name|
417
+ instance_variable = self.instance_variable_get(instance_variable_name.to_sym)
418
+ instance_variable.save_component_session_information(self, instance_variable_name, session) if instance_variable.respond_to?(:save_component_session_information)
419
+ end
420
+ end
421
+ end # page
422
+
423
+ # -- Renderer --
424
+ # Responsible for processing tags/components in the page templates
425
+ # Uses the Radius context object onto which components registered themselves
426
+ # (the tags that they respond to)
427
+ class Renderer
428
+ include Radius
429
+
430
+ def initialize(page)
431
+ @page = page
432
+ @context = Context.new
433
+
434
+ # add all instance variables in the page as values accesible from the tags
435
+ page.instance_variables.each do |var|
436
+ value = page.instance_variable_get(var)
437
+ unless value.kind_of?(Trellis::Page)
438
+ sym = "#{var}=".split('@').last.to_sym
439
+ @context.globals.send(sym, value)
440
+ end
441
+ end
442
+
443
+ # add the page to the context too
444
+ @context.globals.page = page
445
+
446
+ # register the components contained in the page with the renderer's context
447
+ page.class.components.each do |component|
448
+ component.register_with_tag_context(@context)
449
+ end
450
+
451
+ @parser = Parser.new(@context, :tag_prefix => 'trellis')
452
+ end
453
+
454
+ def render
455
+ @parser.parse(@page.class.parsed_template.to_html)
456
+ end
457
+
458
+ end # renderer
459
+
460
+ # -- Component --
461
+ # The component represents a stateless (tag) or a stateful components. Trellis
462
+ # components can provide contributions to the page. The contributions can be
463
+ # javascript, css stylesheets either at the class level or on a per instance
464
+ # basis. Components contain parameters that can be coerced or casted to a
465
+ # particular type before being handed to the event handling code
466
+ class Component
467
+ # the page instance containing the component
468
+ attr_accessor :page, :logger
469
+
470
+ @@components = {}
471
+
472
+ def initialize()
473
+ @logger = Application.logger
474
+ end
475
+
476
+ def self.inherited(child) #:nodoc:
477
+ # component registration
478
+ @@components[child.class_to_sym] = child
479
+
480
+ child.meta_attr_accessor(:body)
481
+ child.meta_attr_accessor(:cname)
482
+ child.cname = child.underscore_class_name
483
+ child.meta_def(:stateful?) { @stateful }
484
+
485
+ child.attr_array(:fields, :create_accessor => false)
486
+ child.attr_array(:style_links)
487
+ child.attr_array(:script_links)
488
+ child.attr_array(:scripts)
489
+ child.attr_array(:class_scripts)
490
+ child.attr_array(:styles)
491
+ child.attr_array(:class_styles)
492
+ child.attr_array(:persistents)
493
+ child.attr_array(:dependencies)
494
+ child.attr_array(:document_modifications)
495
+ child.attr_array(:containers)
496
+
497
+ Application.logger.debug "registered component for tag #{child.cname} => class #{child}"
498
+ super
499
+ end
500
+
501
+ def self.tag_name(name)
502
+ @cname = name
503
+ end
504
+
505
+ def self.contained_in(*args)
506
+ @containers = @containers | args
507
+ end
508
+
509
+ def self.field(sym, options=nil)
510
+ # extract options
511
+ coherce_to = options[:coherce_to] if options
512
+ default_value = options[:defaults_to] if options
513
+ persistent = options[:persistent] if options
514
+
515
+ @fields << sym
516
+
517
+ # add an instance field to the component
518
+ attr_accessor sym
519
+
520
+ # store in array of persistent fields
521
+ persistents << sym if persistent
522
+
523
+ # castings
524
+ if coherce_to
525
+ meta_def("#{sym}=") do |value|
526
+ Application.logger.debug "casting value #{sym} to #{coherce_to}"
527
+ self.instance_variable_set("@#{sym}", value)
528
+ end
529
+ end
530
+
531
+ send("#{sym}=", default_value) if default_value
532
+ end
533
+
534
+ def self.dom_contribution(&block) #TODO names don't match!
535
+ @document_modifications << block
536
+ end
537
+
538
+ def self.add_style_links_to_page(page, attributes)
539
+ style_links.each do |href|
540
+ href = href.replace_ant_style_properties(attributes) if attributes
541
+ builder = Builder::XmlMarkup.new
542
+ link = builder.link(:rel => "stylesheet", :type => "text/css", :href => href)
543
+ page.parsed_template.at("html/head").containers.last.after("\n#{link}")
544
+ end
545
+ end
546
+
547
+ def self.add_script_links_to_page(page, attributes)
548
+ script_links.each do |src|
549
+ src = src.replace_ant_style_properties(attributes) if attributes
550
+ builder = Builder::XmlMarkup.new
551
+ script = builder.script('', :type => "text/javascript", :src => src)
552
+ page.parsed_template.at("html/head").containers.last.after("\n#{script}")
553
+ end
554
+ end
555
+
556
+ def self.add_class_styles_to_page(page, attributes)
557
+ class_styles.each do |body|
558
+ body = body.replace_ant_style_properties(attributes) if attributes
559
+ builder = Builder::XmlMarkup.new
560
+ style = builder.style(:type => "text/css") do |builder|
561
+ builder << body
562
+ end
563
+ page.parsed_template.at("html/head").containers.last.after("\n#{style}")
564
+ end
565
+ end
566
+
567
+ def self.add_class_scripts_to_page(page, attributes)
568
+ class_scripts.each do |body|
569
+ body = body.replace_ant_style_properties(attributes) if attributes
570
+ builder = Builder::XmlMarkup.new
571
+ script = builder.script(:type => "text/javascript") do |builder|
572
+ builder << body
573
+ end
574
+ page.parsed_template.at("html/body").containers.last.after("\n#{script}")
575
+ end
576
+ end
577
+
578
+ def self.add_styles_to_page(page, attributes)
579
+ styles.each do |body|
580
+ body = body.replace_ant_style_properties(attributes) if attributes
581
+ builder = Builder::XmlMarkup.new
582
+ style = builder.style(:type => "text/css") do |builder|
583
+ builder << body
584
+ end
585
+ page.parsed_template.at("html/head").containers.last.after("\n#{style}")
586
+ end
587
+ end
588
+
589
+ def self.add_scripts_to_page(page, attributes)
590
+ scripts.each do |body|
591
+ body = body.replace_ant_style_properties(attributes) if attributes
592
+ builder = Builder::XmlMarkup.new
593
+ script = builder.script(:type => "text/javascript") do |builder|
594
+ builder << body
595
+ end
596
+ page.parsed_template.at("html/body").containers.last.after("\n#{script}")
597
+ end
598
+ end
599
+
600
+ def self.add_document_modifications_to_page(page)
601
+ document_modifications.each do |block|
602
+ page.parsed_template.instance_eval(&block)
603
+ end
604
+ end
605
+
606
+ def self.page_contribution(sym, contribution, options=nil)
607
+ # add the contribution to the appropriate array of contributions
608
+ # scripts, class_scripts, styles, class_styles, script_links, style_links
609
+ scope = options[:scope] || :class if options
610
+ receiver = sym.to_s.plural
611
+ receiver = "class_#{receiver}" if scope == :class
612
+ instance_variable_get("@#{receiver}").send(:<<, contribution)
613
+ end
614
+
615
+ def self.get_component(sym)
616
+ @@components[sym]
617
+ end
618
+
619
+ def self.render(&body)
620
+ @body = body
621
+ end
622
+
623
+ def self.register_with_tag_context(context)
624
+ Application.logger.debug "registering #{self} with tag context"
625
+ if @containers.empty?
626
+ context.define_tag("#{@cname}", {}, &@body)
627
+ else
628
+ @containers.each do |container|
629
+ Application.logger.debug "=> registering tag name #{container}:#{@cname}"
630
+ context.define_tag("#{container}:#{@cname}", {}, &@body)
631
+ end
632
+ end
633
+ end
634
+
635
+ def self.is_stateful
636
+ instance_variable_set "@stateful".to_sym, true
637
+ end
638
+
639
+ def self.depends_on(*syms)
640
+ syms.each do |sym|
641
+ component = Component.get_component(sym)
642
+ dependencies << component if component
643
+ end
644
+ end
645
+
646
+ def save_component_session_information(page, instance_variable_name, session_data)
647
+ self.class.persistents.each do |field|
648
+ key = "#{page.class}_#{self.class}_#{instance_variable_name}_#{field}"
649
+ session_data[key] = instance_variable_get("@#{field}".to_sym)
650
+ end
651
+ end
652
+
653
+ def load_component_session_information(page, instance_variable_name, session_data)
654
+ self.class.persistents.each do |field|
655
+ field_sym = "@#{field}".to_sym
656
+ current_value = instance_variable_get(field_sym)
657
+ new_value = session_data["#{page.class}_#{self.class}_#{instance_variable_name}_#{field}"]
658
+ if current_value != new_value && new_value != nil
659
+ instance_variable_set(field_sym, new_value)
660
+ end
661
+ end
662
+ end
663
+
664
+ private
665
+
666
+ # - takes a page parameter
667
+ # - adds an instance of the component with the given id to the page
668
+ # - adds a wrapper method that
669
+ # - is named on_event_from_source where source is the specific component id
670
+ # - calls the component instance on_event method passing any params
671
+ # - responds with a redirect to a page or a value
672
+ def self.enhance_page(page, id)
673
+ if @stateful
674
+ cname = @cname
675
+ # create an instance of the component
676
+ component_instance = self.new #TODO maybe this constructor can take the page
677
+ # set the page on the instance of the component
678
+ component_instance.page = page
679
+ # set the component instance by id on the page
680
+ page.instance_variable_set("@#{cname}_#{id}".to_sym, component_instance)
681
+ # add an accessor method to get to the component instance
682
+ page.meta_def("#{cname}_#{id}") do
683
+ self.instance_variable_get("@#{cname}_#{id}".to_sym)
684
+ end
685
+ # create pass-through methods for each event handler in the component (on_something methods)
686
+ self.public_instance_methods.each do |method_name|
687
+ if method_name.starts_with?('on_')
688
+ page.meta_def("#{method_name}_from_#{cname}#{id}") do |*args|
689
+ result = page.instance_variable_get("@#{cname}_#{id}".to_sym).send(method_name.to_sym, *args)
690
+ # if the method returns a page, navigate to that page, otherwise navigate to the source page
691
+ (result && result.kind_of?(String)) ? result : page
692
+ end
693
+ end
694
+ end
695
+ end
696
+ end
697
+ end
698
+
699
+ # load trellis core components
700
+ require 'trellis/core_components'
701
+ end