├── .gitignore ├── History.txt ├── License.txt ├── Manifest.txt ├── README ├── README.txt ├── Rakefile ├── config ├── hoe.rb └── requirements.rb ├── examples ├── crud_components │ ├── html │ │ ├── address_view_edit.xhtml │ │ ├── addresses.xhtml │ │ ├── images │ │ │ ├── destroy.gif │ │ │ ├── edit.gif │ │ │ ├── field-error-marker.gif │ │ │ ├── sort-asc.png │ │ │ ├── sort-desc.png │ │ │ └── sortable.png │ │ └── style │ │ │ └── trellis.css │ └── source │ │ ├── crud_components.rb │ │ └── domain.rb ├── examples.txt ├── filters │ └── source │ │ └── filters.rb ├── flickr │ └── source │ │ ├── flickr.rb │ │ └── spec │ │ └── flickr_spec.rb ├── guest_book │ └── source │ │ └── guest_book.rb ├── hangman │ ├── html │ │ ├── game_over.xhtml │ │ ├── guess.xhtml │ │ ├── images │ │ │ ├── a_disabled.png │ │ │ ├── a_enabled.png │ │ │ ├── b_disabled.png │ │ │ ├── b_enabled.png │ │ │ ├── c_disabled.png │ │ │ ├── c_enabled.png │ │ │ ├── d_disabled.png │ │ │ ├── d_enabled.png │ │ │ ├── e_disabled.png │ │ │ ├── e_enabled.png │ │ │ ├── f_disabled.png │ │ │ ├── f_enabled.png │ │ │ ├── g_disabled.png │ │ │ ├── g_enabled.png │ │ │ ├── h_disabled.png │ │ │ ├── h_enabled.png │ │ │ ├── i_disabled.png │ │ │ ├── i_enabled.png │ │ │ ├── j_disabled.png │ │ │ ├── j_enabled.png │ │ │ ├── k_disabled.png │ │ │ ├── k_enabled.png │ │ │ ├── l_disabled.png │ │ │ ├── l_enabled.png │ │ │ ├── m_disabled.png │ │ │ ├── m_enabled.png │ │ │ ├── n_disabled.png │ │ │ ├── n_enabled.png │ │ │ ├── o_disabled.png │ │ │ ├── o_enabled.png │ │ │ ├── p_disabled.png │ │ │ ├── p_enabled.png │ │ │ ├── q_disabled.png │ │ │ ├── q_enabled.png │ │ │ ├── r_disabled.png │ │ │ ├── r_enabled.png │ │ │ ├── s_disabled.png │ │ │ ├── s_enabled.png │ │ │ ├── t_disabled.png │ │ │ ├── t_enabled.png │ │ │ ├── u_disabled.png │ │ │ ├── u_enabled.png │ │ │ ├── v_disabled.png │ │ │ ├── v_enabled.png │ │ │ ├── w_disabled.png │ │ │ ├── w_enabled.png │ │ │ ├── x_disabled.png │ │ │ ├── x_enabled.png │ │ │ ├── y_disabled.png │ │ │ ├── y_enabled.png │ │ │ ├── z_disabled.png │ │ │ └── z_enabled.png │ │ ├── resources │ │ │ └── word_list.txt │ │ ├── start.xhtml │ │ └── style │ │ │ └── hangman.css │ └── source │ │ └── hangman.rb ├── hilo │ ├── html │ │ ├── game_over.xhtml │ │ ├── guess.xhtml │ │ └── start.xhtml │ └── source │ │ └── hilo.rb ├── routing │ └── source │ │ └── routing.rb ├── sessions │ └── source │ │ └── sessions.rb ├── simplest │ └── source │ │ └── simplest.rb └── stateful_counters │ ├── html │ ├── counters.xhtml │ └── style │ │ └── main.css │ └── source │ └── stateful_counters.rb ├── lib ├── trellis.rb └── trellis │ ├── component_library │ ├── core_components.rb │ ├── grid.rb │ ├── jquery.rb │ └── object_editor.rb │ ├── logging.rb │ ├── trellis.rb │ ├── utils.rb │ └── version.rb ├── script ├── console ├── destroy ├── generate └── txt2html ├── setup.rb ├── tasks ├── deployment.rake ├── environment.rake ├── rspec.rake └── website.rake └── test ├── application_spec.rb ├── component_spec.rb ├── core_extensions_spec.rb ├── default_router_spec.rb ├── filters_spec.rb ├── fixtures ├── application_fixtures.rb ├── component_fixtures.rb └── filters_fixtures.rb ├── page_spec.rb ├── renderer_spec.rb ├── router_spec.rb ├── spec.opts └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | nbproject 2 | .DS_Store 3 | Thumbs.db 4 | **/.svn 5 | website 6 | coverage/* -------------------------------------------------------------------------------- /History.txt: -------------------------------------------------------------------------------- 1 | == 0.1.2 2 | * 1 major enhancement 3 | * big refactor fest #1 - removed a great deal of dup code 4 | 5 | == 0.1.1 6 | * 2 major enhancement 7 | * before, around and after filters 8 | * refactoring (clean up) of call! (rack specification) method 9 | 10 | == 0.1.0 11 | * 1 major enhancement 12 | * fix logger for rack 1.0.1 13 | 14 | == 0.0.9 15 | * 1 major enhancement: 16 | * added missing nokogiri gem from the dependencies 17 | 18 | == 0.0.8 19 | * 1 major enhancement: 20 | * implemented automatic route sorting 21 | 22 | == 0.0.7 23 | * 3 major enhancements: 24 | * ripped out rexml/hpricot replaced with nokogiri 25 | * implemented page get method to override default page rendering 26 | * added trellis namespace xmlns method for markaby 27 | * clean all examples to add trellis namespace 28 | 29 | == 0.0.6 30 | * 2 major enhancement: 31 | * added dup.call for thread safety 32 | * added ability to select Rack::Session middleware implementation 33 | between cookie, pool and memcached 34 | 35 | == 0.0.5 36 | * 2 major enhancements: 37 | * template can now use erb (erubis using Processing Instructions (PI)) 38 | * added check for nil Rack::Session when using Cookie Store 39 | 40 | == 0.0.4 41 | * 1 major enhancement: 42 | * tests for Renderer 43 | * more routing tests 44 | * request query params now available to page as instance variables 45 | * cleaned up dependencies 46 | 47 | == 0.0.3 48 | * 1 major enhancement: 49 | * stand-in pages 50 | 51 | == 0.0.2 2009-09-19 52 | * 5 major enhancements: 53 | * code reloading 54 | * template reloading 55 | * event subsystem revamped 56 | * custom routing 57 | * more tests! 58 | 59 | == 0.0.1 2009-08-29 60 | 61 | * 1 major enhancement: 62 | * initial release 63 | -------------------------------------------------------------------------------- /License.txt: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright &169;2001-2010 Integrallis Software, LLC. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Manifest.txt: -------------------------------------------------------------------------------- 1 | History.txt 2 | License.txt 3 | Manifest.txt 4 | README 5 | README.txt 6 | Rakefile 7 | config/hoe.rb 8 | config/requirements.rb 9 | examples/crud_components/html/address_view_edit.xhtml 10 | examples/crud_components/html/addresses.xhtml 11 | examples/crud_components/html/images/destroy.gif 12 | examples/crud_components/html/images/edit.gif 13 | examples/crud_components/html/images/field-error-marker.gif 14 | examples/crud_components/html/images/sort-asc.png 15 | examples/crud_components/html/images/sort-desc.png 16 | examples/crud_components/html/images/sortable.png 17 | examples/crud_components/html/style/trellis.css 18 | examples/crud_components/source/crud_components.rb 19 | examples/crud_components/source/domain.rb 20 | examples/examples.txt 21 | examples/filters/source/filters.rb 22 | examples/flickr/source/flickr.rb 23 | examples/guest_book/source/guest_book.rb 24 | examples/hangman/html/game_over.xhtml 25 | examples/hangman/html/guess.xhtml 26 | examples/hangman/html/images/a_disabled.png 27 | examples/hangman/html/images/a_enabled.png 28 | examples/hangman/html/images/b_disabled.png 29 | examples/hangman/html/images/b_enabled.png 30 | examples/hangman/html/images/c_disabled.png 31 | examples/hangman/html/images/c_enabled.png 32 | examples/hangman/html/images/d_disabled.png 33 | examples/hangman/html/images/d_enabled.png 34 | examples/hangman/html/images/e_disabled.png 35 | examples/hangman/html/images/e_enabled.png 36 | examples/hangman/html/images/f_disabled.png 37 | examples/hangman/html/images/f_enabled.png 38 | examples/hangman/html/images/g_disabled.png 39 | examples/hangman/html/images/g_enabled.png 40 | examples/hangman/html/images/h_disabled.png 41 | examples/hangman/html/images/h_enabled.png 42 | examples/hangman/html/images/i_disabled.png 43 | examples/hangman/html/images/i_enabled.png 44 | examples/hangman/html/images/j_disabled.png 45 | examples/hangman/html/images/j_enabled.png 46 | examples/hangman/html/images/k_disabled.png 47 | examples/hangman/html/images/k_enabled.png 48 | examples/hangman/html/images/l_disabled.png 49 | examples/hangman/html/images/l_enabled.png 50 | examples/hangman/html/images/m_disabled.png 51 | examples/hangman/html/images/m_enabled.png 52 | examples/hangman/html/images/n_disabled.png 53 | examples/hangman/html/images/n_enabled.png 54 | examples/hangman/html/images/o_disabled.png 55 | examples/hangman/html/images/o_enabled.png 56 | examples/hangman/html/images/p_disabled.png 57 | examples/hangman/html/images/p_enabled.png 58 | examples/hangman/html/images/q_disabled.png 59 | examples/hangman/html/images/q_enabled.png 60 | examples/hangman/html/images/r_disabled.png 61 | examples/hangman/html/images/r_enabled.png 62 | examples/hangman/html/images/s_disabled.png 63 | examples/hangman/html/images/s_enabled.png 64 | examples/hangman/html/images/t_disabled.png 65 | examples/hangman/html/images/t_enabled.png 66 | examples/hangman/html/images/u_disabled.png 67 | examples/hangman/html/images/u_enabled.png 68 | examples/hangman/html/images/v_disabled.png 69 | examples/hangman/html/images/v_enabled.png 70 | examples/hangman/html/images/w_disabled.png 71 | examples/hangman/html/images/w_enabled.png 72 | examples/hangman/html/images/x_disabled.png 73 | examples/hangman/html/images/x_enabled.png 74 | examples/hangman/html/images/y_disabled.png 75 | examples/hangman/html/images/y_enabled.png 76 | examples/hangman/html/images/z_disabled.png 77 | examples/hangman/html/images/z_enabled.png 78 | examples/hangman/html/resources/word_list.txt 79 | examples/hangman/html/start.xhtml 80 | examples/hangman/html/style/hangman.css 81 | examples/hangman/source/hangman.rb 82 | examples/hilo/html/game_over.xhtml 83 | examples/hilo/html/guess.xhtml 84 | examples/hilo/html/start.xhtml 85 | examples/hilo/source/hilo.rb 86 | examples/routing/source/routing.rb 87 | examples/sessions/source/sessions.rb 88 | examples/simplest/source/simplest.rb 89 | examples/stateful_counters/html/counters.xhtml 90 | examples/stateful_counters/html/style/main.css 91 | examples/stateful_counters/source/stateful_counters.rb 92 | lib/trellis.rb 93 | lib/trellis/component_library/core_components.rb 94 | lib/trellis/component_library/grid.rb 95 | lib/trellis/component_library/jquery.rb 96 | lib/trellis/component_library/object_editor.rb 97 | lib/trellis/logging.rb 98 | lib/trellis/trellis.rb 99 | lib/trellis/utils.rb 100 | lib/trellis/version.rb 101 | script/console 102 | script/destroy 103 | script/generate 104 | script/txt2html 105 | setup.rb 106 | tasks/deployment.rake 107 | tasks/environment.rake 108 | tasks/rspec.rake 109 | tasks/website.rake 110 | test/application_spec.rb 111 | test/component_spec.rb 112 | test/core_extensions_spec.rb 113 | test/default_router_spec.rb 114 | test/filters_spec.rb 115 | test/fixtures/application_fixtures.rb 116 | test/fixtures/component_fixtures.rb 117 | test/fixtures/filters_fixtures.rb 118 | test/page_spec.rb 119 | test/renderer_spec.rb 120 | test/router_spec.rb 121 | test/spec.opts 122 | test/spec_helper.rb 123 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | = Project: Trellis 2 | 3 | Trellis is a component-based Web Application Framework written in Ruby and based on Rack. 4 | Its main goal is to bring component-driven development to the micro-framework world. 5 | Trellis applications are composed of pages, pages have components, components emit events, 6 | pages and components can listen and handle events. 7 | Trellis aims to be a (close to) zero-configuration framework 8 | 9 | It draws inspiration from: 10 | 11 | Ruby Web Frameworks 12 | =================== 13 | * Rails 14 | * Camping 15 | * Merb 16 | * Iowa 17 | * Sinatra 18 | 19 | Java Web Frameworks 20 | =================== 21 | * Tapesty 22 | * Wicket 23 | 24 | Others 25 | ====== 26 | * Seaside 27 | 28 | == Goals 29 | Accomplished goals are marked with a (*) 30 | - * Pure HTML templates or in-line template creation with Markaby, HAML, erubis, Markdown and Textile 31 | - * To abstract away the request-response nature of web development and replace 32 | with events and listeners 33 | - * Reusable, extensible components including invisible components (behavior 34 | providers), tags (stateless components) or stateful components 35 | - * Easy component composition and markup inheritance 36 | - * Multi-threading support 37 | - * Heavy CoC :-) (Convention Over Configuration) ala Rails 38 | - * No static code generation, no generators, just a Ruby file! 39 | - * Component Libraries as Gems 40 | - Solid Ajax support attached to the event model using a single* massively tested 41 | Ajax framework/toolkit such as JQuery 42 | - Skinnable components a la DotNet. That is the ability to apply a theme to a 43 | page and cascade that to the contained components 44 | - Support for continuations in a componentized fashion (maybe) 45 | - CRUD behaviours for Pages/Components and using Datamapper under the covers 46 | - Web-based debugging and administration of the application similar to what 47 | Seaside provides 48 | 49 | == Development Goals (To-do's) 50 | 51 | - Keep the core framework in a single file 52 | - Keep the core components in another file 53 | - I have not done anything about exception handling (didn't wanted to litter the 54 | code base). Currently I'm always sending an HTTP 200 back or I'm just letting 55 | the app blow chuncks and showing rack's exception page 56 | - Radius usage is really entrenched in the current component implementation need 57 | to clean it up 58 | - Currently Trellis uses the Mongrel Rack Adapter. In the near future the 59 | particular web server would be configurable (one of the reasons to use Rack) 60 | 61 | == Classes 62 | 63 | Trellis::Application:: Base case for all Trellis applications 64 | Trellis::Page:: Base class for all application Pages 65 | Trellis::Renderer:: Renders XML/XHTML tags in the markup using Radius 66 | Trellis::Router:: Encapsulated the base route for a page 67 | Trellis::Component:: Base class for all Trellis components (work in progress) 68 | Trellis::DefaultRouter:: Encapsulates the default routing logic 69 | 70 | == Notes:: 71 | 72 | * Event model is pretty shallow right now. Basically events are just 73 | a convention of how the URLs are interpreted which results on a page method 74 | being invoked. A given URL contains information about the target page, event, 75 | source of the event and possible context values.This information is used to 76 | map to a single method in the page. 77 | * Navigation is a la Tapestry; Page methods can return the instance of a 78 | injected Page or they can return self in which case the same page is 79 | re-rendered. 80 | * Components can register their event handlers with the page. The page wraps the 81 | event handlers of the contained components and dispatches the events to the 82 | component instance. 83 | * Currently the Application is a single object, the multi-threading (which I had 84 | nothing to do with is provided by Rack) happens in the request dispatching. 85 | * Trellis is not a managed framework like Tapestry, in that sense it is more like 86 | Wicket. Pages instances are just created when needed, there is no pooling, 87 | or any of the complexity involved in activating/passivating objects to a pool. 88 | 89 | == Installation 90 | 91 | * gem install trellis 92 | 93 | A Trellis application consists of the Application class; a descendant of 94 | Trellis::Application and one or more pages; descendants of Trellis::Page. The 95 | Application at a minimum needs to declare the starting or home page: 96 | 97 | module Hello 98 | class HelloWorld < Trellis::Application 99 | home :home 100 | end 101 | 102 | class Home < Trellis::Page 103 | template do html { body { h1 "Hello World!" }} end 104 | end 105 | end 106 | 107 | To run the above application simply add the line: 108 | 109 | Hello::HelloWorld.new.start 110 | 111 | That will start the application on Mongrel running on port 3000 by default. To 112 | run on any other port pass the port number to the start method like: 113 | 114 | Hello::HelloWorld.new.start 8282 115 | 116 | == Required Gems 117 | 118 | rack => http://rack.rubyforge.org 119 | mongrel => http://mongrel.rubyforge.org 120 | radius => http://radius.rubyforge.org 121 | builder => http://builder.rubyforge.org 122 | paginator => http://paginator.rubyforge.org 123 | extensions => http://extensions.rubyforge.org 124 | haml => http://haml.hamptoncatlin.com 125 | markaby => http://code.whytheluckystiff.net/markaby 126 | nokogiri => http://nokogiri.org/ 127 | facets => http://facets.rubyforge.org/ 128 | directory_watcher => http://rubyforge.org/projects/codeforpeople 129 | erubis => http://www.kuwata-lab.com/erubis/ 130 | 131 | == LICENSE: 132 | 133 | (The MIT License) 134 | 135 | Copyright &169;2001-2010 Integrallis Software, LLC. 136 | 137 | Permission is hereby granted, free of charge, to any person obtaining 138 | a copy of this software and associated documentation files (the 139 | 'Software'), to deal in the Software without restriction, including 140 | without limitation the rights to use, copy, modify, merge, publish, 141 | distribute, sublicense, and/or sell copies of the Software, and to 142 | permit persons to whom the Software is furnished to do so, subject to 143 | the following conditions: 144 | 145 | The above copyright notice and this permission notice shall be 146 | included in all copies or substantial portions of the Software. 147 | 148 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 149 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 150 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 151 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 152 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 153 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 154 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 155 | 156 | == Contact 157 | 158 | Author:: Brian Sam-Bodden & the Folks at Integrallis 159 | Email:: bsbodden@integrallis.com 160 | Home Page:: http://trellisframework.org 161 | License:: MIT Licence (http://www.opensource.org/licenses/mit-license.html) -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | = Project: Trellis 2 | 3 | Trellis is a component-based Web Application Framework written in Ruby and based on Rack. 4 | Its main goal is to bring component-driven development to the micro-framework world. 5 | Trellis applications are composed of pages, pages have components, components emit events, 6 | pages and components can listen and handle events. 7 | Trellis aims to be a (close to) zero-configuration framework 8 | 9 | It draws inspiration from: 10 | 11 | Ruby Web Frameworks 12 | =================== 13 | * Rails 14 | * Camping 15 | * Merb 16 | * Iowa 17 | * Sinatra 18 | 19 | Java Web Frameworks 20 | =================== 21 | * Tapesty 22 | * Wicket 23 | 24 | Others 25 | ====== 26 | * Seaside 27 | 28 | == Goals 29 | Accomplished goals are marked with a (*) 30 | - * Pure HTML templates or in-line template creation with Markaby, HAML, erubis, Markdown and Textile 31 | - * To abstract away the request-response nature of web development and replace 32 | with events and listeners 33 | - * Reusable, extensible components including invisible components (behavior 34 | providers), tags (stateless components) or stateful components 35 | - * Easy component composition and markup inheritance 36 | - * Multi-threading support 37 | - * Heavy CoC :-) (Convention Over Configuration) ala Rails 38 | - * No static code generation, no generators, just a Ruby file! 39 | - * Component Libraries as Gems 40 | - Solid Ajax support attached to the event model using a single* massively tested 41 | Ajax framework/toolkit such as JQuery 42 | - Skinnable components a la DotNet. That is the ability to apply a theme to a 43 | page and cascade that to the contained components 44 | - Support for continuations in a componentized fashion (maybe) 45 | - CRUD behaviours for Pages/Components and using Datamapper under the covers 46 | - Web-based debugging and administration of the application similar to what 47 | Seaside provides 48 | 49 | == Development Goals (To-do's) 50 | 51 | - Keep the core framework in a single file 52 | - Keep the core components in another file 53 | - I have not done anything about exception handling (didn't wanted to litter the 54 | code base). Currently I'm always sending an HTTP 200 back or I'm just letting 55 | the app blow chuncks and showing rack's exception page 56 | - Radius usage is really entrenched in the current component implementation need 57 | to clean it up 58 | - Currently Trellis uses the Mongrel Rack Adapter. In the near future the 59 | particular web server would be configurable (one of the reasons to use Rack) 60 | 61 | == Classes 62 | 63 | Trellis::Application:: Base case for all Trellis applications 64 | Trellis::Page:: Base class for all application Pages 65 | Trellis::Renderer:: Renders XML/XHTML tags in the markup using Radius 66 | Trellis::Router:: Encapsulated the base route for a page 67 | Trellis::Component:: Base class for all Trellis components (work in progress) 68 | Trellis::DefaultRouter:: Encapsulates the default routing logic 69 | 70 | == Notes:: 71 | 72 | * Event model is pretty shallow right now. Basically events are just 73 | a convention of how the URLs are interpreted which results on a page method 74 | being invoked. A given URL contains information about the target page, event, 75 | source of the event and possible context values.This information is used to 76 | map to a single method in the page. 77 | * Navigation is a la Tapestry; Page methods can return the instance of a 78 | injected Page or they can return self in which case the same page is 79 | re-rendered. 80 | * Components can register their event handlers with the page. The page wraps the 81 | event handlers of the contained components and dispatches the events to the 82 | component instance. 83 | * Currently the Application is a single object, the multi-threading (which I had 84 | nothing to do with is provided by Rack) happens in the request dispatching. 85 | * Trellis is not a managed framework like Tapestry, in that sense it is more like 86 | Wicket. Pages instances are just created when needed, there is no pooling, 87 | or any of the complexity involved in activating/passivating objects to a pool. 88 | 89 | == Installation 90 | 91 | * gem install trellis 92 | 93 | A Trellis application consists of the Application class; a descendant of 94 | Trellis::Application and one or more pages; descendants of Trellis::Page. The 95 | Application at a minimum needs to declare the starting or home page: 96 | 97 | module Hello 98 | class HelloWorld < Trellis::Application 99 | home :home 100 | end 101 | 102 | class Home < Trellis::Page 103 | template do html { body { h1 "Hello World!" }} end 104 | end 105 | end 106 | 107 | To run the above application simply add the line: 108 | 109 | Hello::HelloWorld.new.start 110 | 111 | That will start the application on Mongrel running on port 3000 by default. To 112 | run on any other port pass the port number to the start method like: 113 | 114 | Hello::HelloWorld.new.start 8282 115 | 116 | == Required Gems 117 | 118 | rack => http://rack.rubyforge.org 119 | mongrel => http://mongrel.rubyforge.org 120 | radius => http://radius.rubyforge.org 121 | builder => http://builder.rubyforge.org 122 | paginator => http://paginator.rubyforge.org 123 | extensions => http://extensions.rubyforge.org 124 | haml => http://haml.hamptoncatlin.com 125 | markaby => http://code.whytheluckystiff.net/markaby 126 | nokogiri => http://nokogiri.org/ 127 | facets => http://facets.rubyforge.org/ 128 | directory_watcher => http://rubyforge.org/projects/codeforpeople 129 | erubis => http://www.kuwata-lab.com/erubis/ 130 | 131 | == LICENSE: 132 | 133 | (The MIT License) 134 | 135 | Copyright &169;2001-2010 Integrallis Software, LLC. 136 | 137 | Permission is hereby granted, free of charge, to any person obtaining 138 | a copy of this software and associated documentation files (the 139 | 'Software'), to deal in the Software without restriction, including 140 | without limitation the rights to use, copy, modify, merge, publish, 141 | distribute, sublicense, and/or sell copies of the Software, and to 142 | permit persons to whom the Software is furnished to do so, subject to 143 | the following conditions: 144 | 145 | The above copyright notice and this permission notice shall be 146 | included in all copies or substantial portions of the Software. 147 | 148 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 149 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 150 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 151 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 152 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 153 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 154 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 155 | 156 | == Contact 157 | 158 | Author:: Brian Sam-Bodden & the Folks at Integrallis 159 | Email:: bsbodden@integrallis.com 160 | Home Page:: http://trellisframework.org 161 | License:: MIT Licence (http://www.opensource.org/licenses/mit-license.html) -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'config/requirements' 2 | require 'config/hoe' # setup Hoe + all gem configuration 3 | 4 | Dir['tasks/**/*.rake'].each { |rake| load rake } 5 | 6 | task :default => :spec 7 | 8 | -------------------------------------------------------------------------------- /config/hoe.rb: -------------------------------------------------------------------------------- 1 | require 'trellis/version' 2 | 3 | AUTHOR = 'Brian Sam-Bodden' 4 | EMAIL = "bsbodden@integrallis.com" 5 | DESCRIPTION = "A component based web framework" 6 | GEM_NAME = 'trellis' 7 | RUBYFORGE_PROJECT = 'trellis' 8 | HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org" 9 | DOWNLOAD_PATH = "http://rubyforge.org/projects/#{RUBYFORGE_PROJECT}" 10 | EXTRA_DEPENDENCIES = [ 11 | ['paginator', '>= 1.1.1'], 12 | ['rack', '>= 1.1.0'], 13 | ['radius', '>= 0.6.1'], 14 | ['builder', '>= 2.1.2'], 15 | ['nokogiri', '>= 1.4.1'], 16 | ['extensions', '>= 0.6.0'], 17 | ['haml', '>= 2.2.17'], 18 | ['markaby', '>= 0.5'], 19 | ['RedCloth', '>= 4.2.2'], 20 | ['bluecloth', '>= 2.0.5'], 21 | ['log4r', '>= 1.1.2'], 22 | ['facets', '>= 2.8.1'], 23 | ['directory_watcher', '>= 1.3.1'], 24 | ['eventmachine', '>= 0.12.10'], 25 | ['rack-cache', '>= 0.5.2'], 26 | ['rack-contrib', '>= 0.9.2'], 27 | ['rack-test', '>= 0.5.3'], 28 | ['erubis', '>= 2.6.5'], 29 | ['rspec', '>= 1.2.9'], 30 | ['newgem', '>= 1.5.2'], 31 | ['advisable', '>= 1.0.0'] 32 | ] # An array of rubygem dependencies [name, version] 33 | 34 | @config_file = "~/.rubyforge/user-config.yml" 35 | @config = nil 36 | RUBYFORGE_USERNAME = "bsbodden" 37 | def rubyforge_username 38 | unless @config 39 | begin 40 | @config = YAML.load(File.read(File.expand_path(@config_file))) 41 | rescue 42 | puts <<-EOS 43 | ERROR: No rubyforge config file found: #{@config_file} 44 | Run 'rubyforge setup' to prepare your env for access to Rubyforge 45 | - See http://newgem.rubyforge.org/rubyforge.html for more details 46 | EOS 47 | exit 48 | end 49 | end 50 | RUBYFORGE_USERNAME.replace @config["username"] 51 | end 52 | 53 | 54 | REV = nil 55 | VERS = Trellis::VERSION::STRING + (REV ? ".#{REV}" : "") 56 | RDOC_OPTS = ['--quiet', '--title', 'trellis documentation', 57 | "--opname", "index.html", 58 | "--line-numbers", 59 | "--main", "README", 60 | "--inline-source"] 61 | 62 | class Hoe 63 | def extra_deps 64 | @extra_deps.reject! { |x| Array(x).first == 'hoe' } 65 | @extra_deps 66 | end 67 | end 68 | 69 | # Generate all the Rake tasks 70 | # Run 'rake -T' to see list of generated tasks (from gem root directory) 71 | $hoe = Hoe.spec GEM_NAME do 72 | self.version = VERS 73 | self.developer(AUTHOR, EMAIL) 74 | self.description = DESCRIPTION 75 | self.summary = DESCRIPTION 76 | self.url = HOMEPATH 77 | self.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT 78 | self.test_globs = ["test/**/test_*.rb"] 79 | self.clean_globs |= ['**/.*.sw?', '*.gem', '.config', '**/.DS_Store'] #An array of file patterns to delete on clean. 80 | 81 | # == Optional 82 | self.changes = paragraphs_of("History.txt", 0..1).join("\n\n") 83 | self.extra_deps = EXTRA_DEPENDENCIES 84 | end 85 | 86 | CHANGES = $hoe.paragraphs_of('History.txt', 0..1).join("\\n\\n") 87 | PATH = (RUBYFORGE_PROJECT == GEM_NAME) ? RUBYFORGE_PROJECT : "#{RUBYFORGE_PROJECT}/#{GEM_NAME}" 88 | $hoe.remote_rdoc_dir = File.join(PATH.gsub(/^#{RUBYFORGE_PROJECT}\/?/,''), 'rdoc') 89 | $hoe.rsync_args = '-av --delete --ignore-errors' 90 | $hoe.spec.post_install_message = File.open(File.dirname(__FILE__) + "/../PostInstall.txt").read rescue "" -------------------------------------------------------------------------------- /config/requirements.rb: -------------------------------------------------------------------------------- 1 | require 'fileutils' 2 | include FileUtils 3 | 4 | require 'rubygems' 5 | %w[rake hoe newgem rubigen].each do |req_gem| 6 | begin 7 | require req_gem 8 | rescue LoadError 9 | puts "This Rakefile requires the '#{req_gem}' RubyGem." 10 | puts "Installation: gem install #{req_gem} -y" 11 | exit 12 | end 13 | end 14 | 15 | $:.unshift(File.join(File.dirname(__FILE__), %w[.. lib])) 16 | -------------------------------------------------------------------------------- /examples/crud_components/html/address_view_edit.xhtml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 13 | 14 | Trellis Grid Example 15 | 16 | 17 |

18 | 19 |

20 | 21 | 22 | -------------------------------------------------------------------------------- /examples/crud_components/html/addresses.xhtml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 13 | 14 | Trellis Grid Example 15 | 16 | 17 |

Addresses

18 |

19 | 20 |

21 | 22 | 23 | -------------------------------------------------------------------------------- /examples/crud_components/html/images/destroy.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/crud_components/html/images/destroy.gif -------------------------------------------------------------------------------- /examples/crud_components/html/images/edit.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/crud_components/html/images/edit.gif -------------------------------------------------------------------------------- /examples/crud_components/html/images/field-error-marker.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/crud_components/html/images/field-error-marker.gif -------------------------------------------------------------------------------- /examples/crud_components/html/images/sort-asc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/crud_components/html/images/sort-asc.png -------------------------------------------------------------------------------- /examples/crud_components/html/images/sort-desc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/crud_components/html/images/sort-desc.png -------------------------------------------------------------------------------- /examples/crud_components/html/images/sortable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/crud_components/html/images/sortable.png -------------------------------------------------------------------------------- /examples/crud_components/html/style/trellis.css: -------------------------------------------------------------------------------- 1 | /* Tapestry styles all start with "t-" */ 2 | DIV.t-error { 3 | border: 1px solid red; 4 | padding: 0px; 5 | margin: 4px 0px; 6 | } 7 | 8 | DIV.t-error DIV { 9 | padding: 2px; 10 | display: block; 11 | margin: 0px; 12 | background-color: red; 13 | color: white; 14 | font-weight: bold; 15 | } 16 | 17 | DIV.t-error UL { 18 | margin: 2px 0px; 19 | background-color: white; 20 | color: red; 21 | } 22 | 23 | DIV.t-error LI { 24 | margin-left: 20px; 25 | } 26 | 27 | HTML>BODY DIV.t-error LI { 28 | margin-left: -20px; 29 | } 30 | 31 | .t-invisible { 32 | display: none; 33 | } 34 | 35 | LABEL.t-error { 36 | color: red; 37 | } 38 | 39 | INPUT.t-error, TEXTAREA.t-error { 40 | border-color: red; 41 | font-style: italic; 42 | color: red; 43 | } 44 | 45 | IMG.t-error-icon { 46 | margin-left: 4px; 47 | } 48 | 49 | IMG.t-sort-icon { 50 | margin-left: 4px; 51 | } 52 | 53 | DIV.t-exception-message { 54 | font-style: italic; 55 | font-size: 12pt; 56 | border: thin dotted silver; 57 | margin: 5px 0px; 58 | padding: 3px; 59 | } 60 | 61 | DIV.t-exception-report, DIV.t-env-data { 62 | font-family: "Trebuchet MS", Arial, sans-serif; 63 | } 64 | 65 | DIV.t-exception-report LI { 66 | margin-left: -40px; 67 | } 68 | 69 | DIV.t-exception-report DT, DIV.t-env-data DT { 70 | color: green; 71 | padding-left: 2px; 72 | background-color: #FFFFCF; 73 | } 74 | 75 | DIV.t-exception-report LI { 76 | list-style: none; 77 | } 78 | 79 | SPAN.t-exception-class-name { 80 | display: block; 81 | margin-top: 15px; 82 | font-size: 12pt; 83 | background-color: #E1E1E1; 84 | color: blue; 85 | padding: 2px 3px; 86 | font-weight: bold; 87 | } 88 | 89 | UL.t-stack-trace LI { 90 | font-family: Monaco, Times, monospace; 91 | font-size: 10pt; 92 | margin-left: -25px; 93 | list-style: square; 94 | } 95 | 96 | LI.t-usercode-frame { 97 | font-weight: bold; 98 | color: blue; 99 | } 100 | 101 | H1.t-exception-report { 102 | font-family: "Trebuchet MS", Arial, sans-serif; 103 | color: red; 104 | } 105 | 106 | DIV.t-exception-report DT:after { 107 | content: ":"; 108 | } 109 | 110 | DIV.t-exception-report DD, DIV.t-env-data DD { 111 | margin-left: 10px; 112 | } 113 | 114 | TABLE.t-data-table { 115 | border-collapse: collapse; 116 | margin: 0px; 117 | padding: 2px; 118 | } 119 | 120 | TABLE.t-data-table TH { 121 | background-color: black; 122 | color: white; 123 | } 124 | 125 | TABLE.t-data-table TD { 126 | border: 1px solid silver; 127 | margin: 0px; 128 | } 129 | 130 | DIV.t-beaneditor, DIV.t-beandisplay { 131 | display: block; 132 | background: #ffc; 133 | border: 2px outset brown; 134 | padding: 2px; 135 | font-family: "Trebuchet MS", Arial, sans-serif; 136 | } 137 | 138 | DIV.t-beandisplay { 139 | background: #CCBE99; 140 | border: 2px outset black; 141 | width: auto; 142 | } 143 | 144 | DIV.t-beaneditor-row { 145 | padding: 4px 0px 2px 0px; 146 | } 147 | 148 | DIV.t-beaneditor-row LABEL:after { 149 | content: ":"; 150 | } 151 | 152 | DIV.t-beandisplay-row { 153 | clear: both; 154 | } 155 | 156 | DIV.t-beaneditor-row LABEL, DIV.t-beandisplay DIV.t-beandisplay-label { 157 | width: 250px; 158 | display: block; 159 | float: left; 160 | text-align: right; 161 | clear: left; 162 | padding-right: 3px; 163 | vertical-align: middle; 164 | } 165 | 166 | INPUT.t-number { 167 | text-align: right; 168 | } 169 | 170 | DIV.t-beandisplay DIV.t-beandisplay-label { 171 | padding-right: 5px; 172 | } 173 | 174 | TABLE.t-data-grid THEAD TR { 175 | color: white; 176 | background-color: #809FFF; 177 | } 178 | 179 | TABLE.t-data-grid THEAD TR TH { 180 | text-align: left; 181 | padding: 3px; 182 | white-space: nowrap; 183 | border-right: 1px solid silver; 184 | border-bottom: 1px solid silver; 185 | } 186 | 187 | TABLE.t-data-grid { 188 | border-collapse: collapse; 189 | border-left: 1px solid silver; 190 | } 191 | 192 | TABLE.t-data-grid TBODY TR TD { 193 | border-right: 1px solid silver; 194 | border-bottom: 1px solid silver; 195 | padding: 2px; 196 | } 197 | 198 | DIV.t-data-grid { 199 | font-family: "Trebuchet MS", Arial, sans-serif; 200 | } 201 | 202 | DIV.t-data-grid-pager { 203 | margin: 8px 0px; 204 | } 205 | 206 | DIV.t-data-grid-pager A, DIV.t-data-grid-pager SPAN.current { 207 | text-decoration: none; 208 | color: black; 209 | padding: 2px 5px; 210 | font-size: medium; 211 | border: 1px solid silver; 212 | margin-right: 5px; 213 | } 214 | 215 | DIV.t-data-grid-pager A:hover { 216 | border: 1px solid black; 217 | } 218 | 219 | DIV.t-data-grid-pager SPAN.current { 220 | color: white; 221 | background-color: #809FFF; 222 | } 223 | 224 | TABLE.t-data-grid TR TH A { 225 | color: white; 226 | } 227 | 228 | IMG { 229 | border: none; 230 | } 231 | 232 | DIV.t-env-data-section { 233 | padding-left: 5px; 234 | } 235 | 236 | DIV.t-env-data DD, DIV.t-exception-report DD { 237 | margin-left: 25px; 238 | margin-bottom: 10px; 239 | } 240 | 241 | DIV.t-env-data LI { 242 | margin-left: -25px; 243 | } 244 | 245 | DIV.t-env-data-section { 246 | font-size: 12pt; 247 | background-color: #E1E1E1; 248 | color: blue; 249 | padding: 2px 3px; 250 | font-weight: bold; 251 | } 252 | 253 | TABLE.t-location-outer { 254 | padding: 5px; 255 | border-collapse: collapse; 256 | border: 1px solid black; 257 | width: 100%; 258 | } 259 | 260 | TD.t-location-line { 261 | width: 40px; 262 | text-align: right; 263 | padding: 0px; 264 | background-color: #E1E1E1; 265 | padding-right: 3px; 266 | border-right: 1px solid black; 267 | } 268 | 269 | TD.t-location-content { 270 | border-top: 1px solid silver; 271 | border-right: 1px solid black; 272 | white-space: pre; 273 | } 274 | 275 | TD.t-location-current { 276 | background-color: #FFFFCF; 277 | } 278 | 279 | TD.t-location-content-first { 280 | border-top: 1px solid black; 281 | } 282 | 283 | DIV.t-palette { 284 | display: inline; 285 | } 286 | 287 | DIV.t-palette SELECT { 288 | margin-bottom: 2px; 289 | width: 200px; 290 | } 291 | 292 | DIV.t-palette-title { 293 | color: white; 294 | background-color: #809FFF; 295 | text-align: center; 296 | font-weight: bold; 297 | margin-bottom: 3px; 298 | display: block; 299 | } 300 | 301 | DIV.t-palette-available { 302 | float: left; 303 | } 304 | 305 | DIV.t-palette-controls { 306 | margin: 5px 5px; 307 | float: left; 308 | text-align: center; 309 | } 310 | 311 | DIV.t-palette-controls BUTTON { 312 | display: block; 313 | margin-bottom: 3px; 314 | } 315 | 316 | DIV.t-palette-controls BUTTON[disabled] IMG { 317 | filter: alpha( opacity = 25 ); 318 | -moz-opacity: .25; 319 | } 320 | 321 | DIV.t-palette-selected { 322 | float: left; 323 | clear: right; 324 | } 325 | 326 | DIV.t-palette-spacer { 327 | clear: left; 328 | } 329 | 330 | IMG.t-calendar-trigger { 331 | padding-left: 3px; 332 | cursor: pointer; 333 | } 334 | 335 | DIV.t-autocomplete-menu UL { 336 | border: 2px outset #cc9933; 337 | background-color: #cc9933; 338 | padding: 4px 6px; 339 | overflow: auto; 340 | } 341 | 342 | DIV.t-autocomplete-menu LI { 343 | color: white; 344 | list-style-type: none; 345 | padding: 0px; 346 | margin: 0px; 347 | border-bottom: 1px solid black; 348 | cursor: pointer; 349 | } 350 | 351 | DIV.t-autocomplete-menu LI.selected { 352 | color: black; 353 | font-weight: bold; 354 | } 355 | 356 | DIV.t-error-popup SPAN { 357 | background: transparent url( 'error-bevel-left.gif' ) no-repeat; 358 | display: block; 359 | line-height: 28px; 360 | margin-left: 0px; 361 | padding: 0px 5px 10px 22px; 362 | } 363 | 364 | HTML>BODY DIV.t-error-popup SPAN { 365 | background: transparent url( 'error-bevel-left.png' ) no-repeat; 366 | } 367 | 368 | DIV.t-error-popup { 369 | background: transparent url( 'error-bevel-right.gif' ) no-repeat scroll top right; 370 | cursor: pointer; 371 | color: #FFF; 372 | display: block; 373 | float: left; 374 | font: normal 12px arial, sans-serif; 375 | height: 39px; 376 | margin-right: 6px; 377 | padding-right: 29px; 378 | text-decoration: none; 379 | } 380 | 381 | HTML>BODY DIV.t-error-popup { 382 | background: transparent url( 'error-bevel-right.png' ) no-repeat scroll top right; 383 | } 384 | 385 | UL.t-data-list LI { 386 | list-style-type: square; 387 | } 388 | 389 | /* The console is used to show debugging messages. */ 390 | DIV.t-console { 391 | position: absolute; 392 | z-index: 1; 393 | top: 2px; 394 | left: 2px; 395 | } 396 | 397 | DIV.t-console DIV { 398 | font-weight: bold; 399 | cursor: pointer; 400 | padding: 0px 2px; 401 | } 402 | 403 | DIV.t-console DIV.t-err { 404 | background-color: red; 405 | color: white; 406 | } 407 | 408 | DIV.t-console DIV.t-warn { 409 | background-color: yellow; 410 | color: black; 411 | } 412 | 413 | DIV.t-console DIV.t-debug { 414 | background-color: silver; 415 | color: black; 416 | } -------------------------------------------------------------------------------- /examples/crud_components/source/crud_components.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'trellis' 3 | 4 | include Trellis 5 | 6 | module ObjectEditor 7 | 8 | class Address 9 | attr_accessor :aid, :first_name, :last_name, :street_1, :street_2, :city, :state, :zip, :email, :phone 10 | 11 | def initialize(aid, first_name, last_name, street_1, street_2, city, state, zip, email, phone) 12 | @aid, @first_name, @last_name, @street_1, @street_2, @city, @state, @zip, @email, @phone = aid, first_name, last_name, street_1, street_2, city, state, zip, email, phone 13 | @@_addresses ||= {} 14 | @@_addresses[self.aid] = self 15 | end 16 | 17 | def self.addresses 18 | @@_addresses 19 | end 20 | 21 | def to_s 22 | "#{@aid}: #{@first_name} #{@last_name}" 23 | end 24 | 25 | Address.new(1, "Richard", "Jeni", "10544 E Meadowhill Dr.", "", "Scottsdale", "AZ", "85255", "rjenni@platypus.com", "555-2715777") 26 | Address.new(2, "Mitch", "Hedberg", "123 Broadway", "", "New York", "NY", "20231", "mitch@hedberg.com", "555-5551234") 27 | Address.new(3, "Sam", "Kenison", "125 Fifth Ave.", "", "New York", "NY", "20123", "sam@kenison.com", "555-5558675") 28 | Address.new(4, "Richard", "Prior", "321 Unfunny Street", "", "New York", "NY", "20202", "richard@prior.com", "555-2675877") 29 | Address.new(5, "George", "Carlin", "999 Union Street", "", "New York", "NY", "20231", "george@carlin.com", "555-5592734") 30 | Address.new(6, "Bill", "Hicks", "125 Fifth Ave.", "", "New York", "NY", "20786", "bill@hicks.com", "555-5556775") 31 | Address.new(7, "Rodney", "Dangerfield", "13884 E. Kalil Dr.", "", "Scottsdale", "AZ", "85259", "rodney@dangerfield.com", "555-2715777") 32 | Address.new(8, "Andy", "Kauffman", "789 Main Street", "", "New York", "NY", "20231", "andy@kauffman.com", "555-5534634") 33 | Address.new(9, "Lenny", "Bruce", "456 Sixth Ave.", "", "New York", "NY", "45637", "lenny@bruce.com", "555-5837475") 34 | Address.new(10, "Redd", "Foxx", "314 Rebel Force Dr.", "", "New Albany", "NY", "20212", "redd@foxx.com", "555-5557475") 35 | end 36 | 37 | class CrudComponentsExample < Application 38 | home :addresses 39 | 40 | map_static ['/images', '/style', '/favicon.ico'] 41 | end 42 | 43 | class Addresses < Page 44 | pages :address_view_edit 45 | 46 | def before_load 47 | @grid_addresses.columns(:first_name, :last_name, :street_1, :city, :state, :zip, :email, :phone) 48 | @grid_addresses.sort_by :all 49 | @grid_addresses.add_command(:name => "edit", :context => "aid", :image => "/images/edit.gif") 50 | @all_addresses = Address.addresses.values 51 | end 52 | 53 | def on_edit_from_addresses id 54 | @address_view_edit.object_editor_address.model = Address.addresses[id.to_i] 55 | @address_view_edit 56 | end 57 | 58 | end 59 | 60 | class AddressViewEdit < Page 61 | 62 | pages :addresses 63 | 64 | def before_load 65 | @object_editor_address.fields(:first_name, :last_name, :street_1, :city, :state, :zip, :email, :phone) 66 | @object_editor_address.submit_text = (@model && @model.aid ? "Save" : "Create") 67 | @object_editor_address.on_submit { |model| 68 | logger.info "saving the object #{model.to_s}, responding to an event in #{self}" 69 | @addresses #navigate back to the addresses page 70 | } 71 | end 72 | end 73 | 74 | web_app = CrudComponentsExample.new 75 | web_app.start 3019 if __FILE__ == $PROGRAM_NAME 76 | end 77 | -------------------------------------------------------------------------------- /examples/crud_components/source/domain.rb: -------------------------------------------------------------------------------- 1 | # 2 | # To change this template, choose Tools | Templates 3 | # and open the template in the editor. 4 | 5 | 6 | puts "Hello World" 7 | -------------------------------------------------------------------------------- /examples/examples.txt: -------------------------------------------------------------------------------- 1 | == Trellis examples: 2 | 3 | simplest 4 | ======== 5 | see http://tapestry.apache.org/tapestry5/tutorial1/first.html 6 | 7 | The simplest possible application to prove that Trellis is generating dynamic 8 | HTML. The date and time in the middle of the page changes with every page 9 | refresh proving that this is a live application. 10 | 11 | hilo 12 | ==== 13 | see http://tapestry.apache.org/tapestry5/tutorial1/hilo.html 14 | 15 | In the game, the computer selects a number between 1 and 10. You try and guess 16 | the number, clicking links. At the end, the computer tells you how many guesses 17 | you required. 18 | 19 | hangman 20 | ======= 21 | see http://rifers.org/examples#D.10 22 | 23 | Hangman is a simple word game in which the system selects a secret target word; 24 | the player attempts to guess the word and is only allow 5 incorrect guesses. The 25 | game is over when the word is guessed or the user has exhausted the maximum 26 | number of incorrect guesses. 27 | 28 | guest_book 29 | ========== 30 | see http://wicket.apache.org/exampleguestbook.html 31 | 32 | This application show the basic form handling and page persistent fields. 33 | The GuestBook application allows users to enter a comment. On submission the 34 | page gets re-rendered showing the list of comments entered so far. The list of 35 | comments is kept in the session. 36 | 37 | flickr 38 | ====== 39 | This application uses a custom stateless component that connects to the flickr 40 | image service api and retrieves several interesting pictures. Refresh the page 41 | to get more images 42 | 43 | stateful_counters 44 | ================= 45 | see http://www.seaside.st/about/examples/multicounter 46 | see http://rifers.org/examples#D.9 47 | 48 | Example of a custom stateful component (a simple counter) and how you can build 49 | and embed multiple components in a page 50 | 51 | crud_components 52 | =============== 53 | Shows the work in progress on a simple pageable/sortable data grid and an object 54 | editor 55 | 56 | routing 57 | ======= 58 | Shows examples of custom page routing in Trellis 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /examples/filters/source/filters.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'trellis' 3 | 4 | include Trellis 5 | 6 | module Filters 7 | 8 | class Filters < Application 9 | home :login 10 | persistent :user 11 | 12 | logger.level = DEBUG 13 | 14 | # hardcoded user name and password (yeah don't do this) 15 | USER_NAME = "admin" 16 | PASSWORD = "itsasecret!" 17 | 18 | # helper methods 19 | def admin? 20 | @user 21 | end 22 | 23 | filter :authorized?, :around do |page, &block| 24 | page.application.admin? ? block.call : page.redirect("/not_authorized") 25 | end 26 | 27 | filter :capitalize, :after do |page| 28 | page.answer = "#{page.answer} is #{page.answer.reverse} backwards" 29 | end 30 | 31 | def user 32 | @user = false unless @user 33 | @user 34 | end 35 | 36 | layout :main, %[ 37 | 38 | 39 | 40 | @{@page_name}@ 41 | 42 | 43 |

Trellis Filters Demo

44 | 45 |

Logged in as @{@application.user}@

46 | 47 | 57 | @!{@body}@ 58 | 59 | 60 | ], :format => :eruby 61 | end 62 | 63 | class Login < Page 64 | route '/login' 65 | 66 | def on_submit_from_login 67 | if params[:login_name] == Filters::USER_NAME && params[:login_password] == Filters::PASSWORD 68 | @application.user = Filters::USER_NAME 69 | redirect "/protected" 70 | else 71 | self 72 | end 73 | end 74 | 75 | def on_logout 76 | @application.user = false 77 | self 78 | end 79 | 80 | template %[ 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | ], :format => :html, :layout => :main 89 | end 90 | 91 | class Protected < Page 92 | apply_filter :authorized?, :to => :all 93 | 94 | template %[ 95 |

This page is protected by an around filter

96 |

Shhhhhh!

97 | ], :format => :html, :layout => :main 98 | end 99 | 100 | class ProtectedWithGet < Page 101 | apply_filter :authorized?, :to => :all 102 | 103 | def get 104 | self 105 | end 106 | 107 | template %[ 108 |

This page is also protected by an around filter

109 |

Let's also keep it quiet!

110 | ], :format => :html, :layout => :main 111 | end 112 | 113 | class ProtectedEvent < Page 114 | persistent :answer 115 | apply_filter :authorized?, :to => :on_knock_knock 116 | apply_filter :capitalize, :to => :on_knock_knock 117 | 118 | def initialize 119 | @answer = "blah" 120 | end 121 | 122 | def on_knock_knock 123 | @answer = "who's there?" 124 | self 125 | end 126 | 127 | template %[ 128 |

Only a few chosen can see this

129 |

@{@answer}@

130 | ], :format => :eruby, :layout => :main 131 | end 132 | 133 | class NotAuthorized < Page 134 | template %[ 135 |

You are not authorized to see the page

136 |

Move along stranger!

137 | ], :format => :html, :layout => :main 138 | end 139 | 140 | Filters.new.start if __FILE__ == $PROGRAM_NAME 141 | end 142 | -------------------------------------------------------------------------------- /examples/flickr/source/flickr.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'trellis' 3 | require 'xmlrpc/client' 4 | require 'nokogiri' 5 | require 'builder' 6 | 7 | include Trellis 8 | 9 | module Flickr 10 | 11 | # This component grabs a set of random images from Flickr using the 12 | # interestingness web services (using XML-RCP) 13 | # 14 | # s small square 75x75 15 | # t thumbnail, 100 on longest side 16 | # m small, 240 on longest side 17 | # - medium, 500 on longest side 18 | # b large, 1024 on longest side (only exists for very large original images) 19 | # o original image, either a jpg, gif or png, depending on source format 20 | class FlickrInterestingness < Component 21 | render do |tag| 22 | format = tag.attr['format'] || 's' 23 | displayed = tag.attr['per_page'] || '3' 24 | displayed = displayed.to_i 25 | per_page = displayed * 10 # grab 10x more images 26 | 27 | flickruri = 'http://api.flickr.com/services/xmlrpc/' 28 | server = XMLRPC::Client.new2(flickruri) 29 | flickrkey = '15b43fbd25e10d51e8533d32bf7e1d1a' 30 | details = {:api_key => flickrkey, :per_page => per_page} 31 | xml = server.call("flickr.interestingness.getList", details) 32 | doc = Nokogiri::XML(xml) 33 | builder = Builder::XmlMarkup.new 34 | 35 | chosen = (0..per_page-1).to_a.sort_by{rand}[0..displayed-1] 36 | 37 | index = 0 38 | doc.xpath("//photos/photo").each do |element| 39 | if chosen.include?(index) 40 | server = element["server"] 41 | id = element["id"] 42 | secret = element["secret"] 43 | title = element["title"] 44 | farm = element["farm"] 45 | image_url = "http://farm#{farm}.static.flickr.com/#{server}/#{id}_#{secret}_#{format}.jpg" 46 | 47 | builder.div(:id => "flickr_viewer") { 48 | builder.div(:id => "flickr_image") { 49 | builder.img(:src => image_url, :alt => title) 50 | } 51 | } 52 | end 53 | index = index + 1 54 | end 55 | builder.target! 56 | end 57 | end 58 | 59 | class FlickrApp < Application 60 | home :home 61 | end 62 | 63 | class Home < Page 64 | template %[ 65 | 68 | 69 |

Some Interesting Pictures...

70 |

Default

71 |
72 | 73 |
74 |

Parameterized

75 |
76 | 77 |
78 | 79 | 80 | ], :format => :html 81 | end 82 | 83 | web_app = FlickrApp.new 84 | web_app.start 3006 if __FILE__ == $PROGRAM_NAME 85 | end -------------------------------------------------------------------------------- /examples/flickr/source/spec/flickr_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'spec' 3 | require 'rack' 4 | require 'rack/test' 5 | require File.dirname(__FILE__) + '/../flickr.rb' 6 | 7 | describe Flickr::FlickrApp do 8 | include Rack::Test::Methods 9 | 10 | def app 11 | Flickr::FlickrApp.new 12 | end 13 | 14 | it "should have a home page declared" do 15 | Flickr::FlickrApp.homepage.should == :home 16 | end 17 | 18 | it "should return an OK HTTP status" do 19 | get "/" 20 | last_response.status.should be(200) 21 | end 22 | 23 | it "should render the flickr component" do 24 | get '/' 25 | last_response.body.should include(%[
]) 26 | end 27 | 28 | it "should render the default number of images in the default format" do 29 | get '/' 30 | body = Nokogiri::HTML(last_response.body) 31 | images = body.xpath("//div[@id='test1']//div[@id='flickr_image']/img") 32 | 33 | images.should have(3).images 34 | images.each { |image| image['src'].should match(/\Ahttp.*_s.jpg\Z/) } 35 | end 36 | 37 | it "should render a requested number of images in the requested format" do 38 | get '/' 39 | body = Nokogiri::HTML(last_response.body) 40 | images = body.xpath("//div[@id='test2']//div[@id='flickr_image']/img") 41 | 42 | images.should have(2).images 43 | images.each { |image| image['src'].should match(/\Ahttp.*_t.jpg\Z/) } 44 | end 45 | 46 | end 47 | -------------------------------------------------------------------------------- /examples/guest_book/source/guest_book.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'trellis' 3 | 4 | include Trellis 5 | 6 | module GuestBookApp 7 | 8 | class Comment 9 | attr_accessor :text 10 | attr_accessor :time_stamp 11 | 12 | def initialize(text, time_stamp=Time.now) 13 | @text, @time_stamp = text, time_stamp 14 | end 15 | end 16 | 17 | class GuestBookApp < Application 18 | home :guest_book 19 | end 20 | 21 | class GuestBook < Page 22 | persistent :comments 23 | 24 | def initialize 25 | @comments = Array.new 26 | super 27 | end 28 | 29 | def on_submit_from_comment 30 | text = params[:comment_text] 31 | @comments << Comment.new(text) 32 | logger.info "there are #{@comments.size.to_s} comments" 33 | self 34 | end 35 | 36 | template(%[ 37 | !!! XML 38 | !!! Strict 39 | %html{ :xmlns => "http://www.w3.org/1999/xhtml", 40 | "xmlns:trellis" => "http://trellisframework.org/schema/trellis_1_0_0.xsd" } 41 | %head 42 | %title 43 | Trellis Guest Book 44 | %body 45 | %trellis:form{ :tid => "comment", :method => "post" } 46 | Add your comment here: 47 | %p 48 | %trellis:text_area{ :keep_contents => "no", :tid => "text", :rows => "15", :cols => "60" } 49 | %p 50 | %trellis:submit{ :tid => "add", :value => "Submit" } 51 | %p 52 | %trellis:each{ :value => "comment", :source => "comments" } 53 | %p 54 | %trellis:value{ :name => "comment.time_stamp" } 55 | %br 56 | %trellis:value{ :name => "comment.text" } 57 | %br 58 | ], :format => :haml) 59 | end 60 | 61 | GuestBookApp.new.start 3001 if __FILE__ == $PROGRAM_NAME 62 | end 63 | -------------------------------------------------------------------------------- /examples/hangman/html/game_over.xhtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | Game Over! 10 | 11 | 12 | 13 |

Bad luck. You failed to guess that the word was education. 14 |

Better luck next time!

15 |
16 | 17 | 18 |

Congratulations! You guessed that the word was education 19 | with 2 remaining guesses.

20 |
21 | 22 |

[Play Again].

23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /examples/hangman/html/guess.xhtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | Trellis Hangman 10 | 11 | 12 |

n guesses remaining

13 |

Word: ________

14 | 15 |
16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 |
28 | 29 | 30 |
31 | 32 |
33 |
34 | 35 |
36 |
37 | 38 |
39 |
40 | 41 |
42 |
43 | 44 |
45 | 46 |
47 |
48 | 49 | 50 | -------------------------------------------------------------------------------- /examples/hangman/html/images/a_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/hangman/html/images/a_disabled.png -------------------------------------------------------------------------------- /examples/hangman/html/images/a_enabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/hangman/html/images/a_enabled.png -------------------------------------------------------------------------------- /examples/hangman/html/images/b_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/hangman/html/images/b_disabled.png -------------------------------------------------------------------------------- /examples/hangman/html/images/b_enabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/hangman/html/images/b_enabled.png -------------------------------------------------------------------------------- /examples/hangman/html/images/c_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/hangman/html/images/c_disabled.png -------------------------------------------------------------------------------- /examples/hangman/html/images/c_enabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/hangman/html/images/c_enabled.png -------------------------------------------------------------------------------- /examples/hangman/html/images/d_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/hangman/html/images/d_disabled.png -------------------------------------------------------------------------------- /examples/hangman/html/images/d_enabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/hangman/html/images/d_enabled.png -------------------------------------------------------------------------------- /examples/hangman/html/images/e_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/hangman/html/images/e_disabled.png -------------------------------------------------------------------------------- /examples/hangman/html/images/e_enabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/hangman/html/images/e_enabled.png -------------------------------------------------------------------------------- /examples/hangman/html/images/f_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/hangman/html/images/f_disabled.png -------------------------------------------------------------------------------- /examples/hangman/html/images/f_enabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/hangman/html/images/f_enabled.png -------------------------------------------------------------------------------- /examples/hangman/html/images/g_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/hangman/html/images/g_disabled.png -------------------------------------------------------------------------------- /examples/hangman/html/images/g_enabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/hangman/html/images/g_enabled.png -------------------------------------------------------------------------------- /examples/hangman/html/images/h_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/hangman/html/images/h_disabled.png -------------------------------------------------------------------------------- /examples/hangman/html/images/h_enabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/hangman/html/images/h_enabled.png -------------------------------------------------------------------------------- /examples/hangman/html/images/i_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/hangman/html/images/i_disabled.png -------------------------------------------------------------------------------- /examples/hangman/html/images/i_enabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/hangman/html/images/i_enabled.png -------------------------------------------------------------------------------- /examples/hangman/html/images/j_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/hangman/html/images/j_disabled.png -------------------------------------------------------------------------------- /examples/hangman/html/images/j_enabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/hangman/html/images/j_enabled.png -------------------------------------------------------------------------------- /examples/hangman/html/images/k_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/hangman/html/images/k_disabled.png -------------------------------------------------------------------------------- /examples/hangman/html/images/k_enabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/hangman/html/images/k_enabled.png -------------------------------------------------------------------------------- /examples/hangman/html/images/l_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/hangman/html/images/l_disabled.png -------------------------------------------------------------------------------- /examples/hangman/html/images/l_enabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/hangman/html/images/l_enabled.png -------------------------------------------------------------------------------- /examples/hangman/html/images/m_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/hangman/html/images/m_disabled.png -------------------------------------------------------------------------------- /examples/hangman/html/images/m_enabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/hangman/html/images/m_enabled.png -------------------------------------------------------------------------------- /examples/hangman/html/images/n_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/hangman/html/images/n_disabled.png -------------------------------------------------------------------------------- /examples/hangman/html/images/n_enabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/hangman/html/images/n_enabled.png -------------------------------------------------------------------------------- /examples/hangman/html/images/o_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/hangman/html/images/o_disabled.png -------------------------------------------------------------------------------- /examples/hangman/html/images/o_enabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/hangman/html/images/o_enabled.png -------------------------------------------------------------------------------- /examples/hangman/html/images/p_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/hangman/html/images/p_disabled.png -------------------------------------------------------------------------------- /examples/hangman/html/images/p_enabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/hangman/html/images/p_enabled.png -------------------------------------------------------------------------------- /examples/hangman/html/images/q_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/hangman/html/images/q_disabled.png -------------------------------------------------------------------------------- /examples/hangman/html/images/q_enabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/hangman/html/images/q_enabled.png -------------------------------------------------------------------------------- /examples/hangman/html/images/r_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/hangman/html/images/r_disabled.png -------------------------------------------------------------------------------- /examples/hangman/html/images/r_enabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/hangman/html/images/r_enabled.png -------------------------------------------------------------------------------- /examples/hangman/html/images/s_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/hangman/html/images/s_disabled.png -------------------------------------------------------------------------------- /examples/hangman/html/images/s_enabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/hangman/html/images/s_enabled.png -------------------------------------------------------------------------------- /examples/hangman/html/images/t_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/hangman/html/images/t_disabled.png -------------------------------------------------------------------------------- /examples/hangman/html/images/t_enabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/hangman/html/images/t_enabled.png -------------------------------------------------------------------------------- /examples/hangman/html/images/u_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/hangman/html/images/u_disabled.png -------------------------------------------------------------------------------- /examples/hangman/html/images/u_enabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/hangman/html/images/u_enabled.png -------------------------------------------------------------------------------- /examples/hangman/html/images/v_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/hangman/html/images/v_disabled.png -------------------------------------------------------------------------------- /examples/hangman/html/images/v_enabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/hangman/html/images/v_enabled.png -------------------------------------------------------------------------------- /examples/hangman/html/images/w_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/hangman/html/images/w_disabled.png -------------------------------------------------------------------------------- /examples/hangman/html/images/w_enabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/hangman/html/images/w_enabled.png -------------------------------------------------------------------------------- /examples/hangman/html/images/x_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/hangman/html/images/x_disabled.png -------------------------------------------------------------------------------- /examples/hangman/html/images/x_enabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/hangman/html/images/x_enabled.png -------------------------------------------------------------------------------- /examples/hangman/html/images/y_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/hangman/html/images/y_disabled.png -------------------------------------------------------------------------------- /examples/hangman/html/images/y_enabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/hangman/html/images/y_enabled.png -------------------------------------------------------------------------------- /examples/hangman/html/images/z_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/hangman/html/images/z_disabled.png -------------------------------------------------------------------------------- /examples/hangman/html/images/z_enabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bsbodden/trellis/8d5a9532c9dd0fd4df058e86d0aabe7a3b809fe3/examples/hangman/html/images/z_enabled.png -------------------------------------------------------------------------------- /examples/hangman/html/resources/word_list.txt: -------------------------------------------------------------------------------- 1 | tapestry 2 | trellis 3 | peanut 4 | pinball 5 | helmet 6 | stereo 7 | citadel 8 | gargoyle 9 | cranium 10 | axiom 11 | media 12 | virus 13 | orbit 14 | wizard 15 | golden 16 | revenge 17 | bowling 18 | annual 19 | mercy 20 | anatomy 21 | zanzibar 22 | pepper 23 | connect 24 | monster 25 | impress 26 | doctor 27 | horror 28 | cooking 29 | virtual 30 | power 31 | freight 32 | formula 33 | board 34 | eclipse 35 | biology -------------------------------------------------------------------------------- /examples/hangman/html/start.xhtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | Trellis Hangman 10 | 11 | 12 |

Trellis Hangman

13 | 14 |

Are you ready to play? ...

15 | 16 |

17 | Start playing 18 |

19 | 20 | 21 | -------------------------------------------------------------------------------- /examples/hangman/html/style/hangman.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: white; 3 | color: #555555; 4 | font-family: 'Lucida Sans', 'Helvetica', 'Sans-serif', 'sans'; 5 | font-size: 9pt; 6 | line-height: 1.8em; 7 | padding: 1em; 8 | margin: 1em; 9 | } 10 | 11 | form { 12 | padding: 0; 13 | margin: 0; 14 | display: inline; 15 | } 16 | 17 | .letter { 18 | width: 30px; 19 | height: 30px; 20 | margin: 2px; 21 | float: left; 22 | } 23 | 24 | a { 25 | color: black; 26 | } 27 | 28 | -------------------------------------------------------------------------------- /examples/hangman/source/hangman.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'trellis' 3 | 4 | include Trellis 5 | 6 | module Hangman 7 | class HangmanGame < Application 8 | home :start 9 | 10 | map_static ['/images', '/style', '/favicon.ico'] 11 | end 12 | 13 | class Start < Page 14 | pages :guess 15 | 16 | @@words = Array.new 17 | File.open("#{File.dirname($0)}/../html/resources/word_list.txt", "r").each do |word| 18 | @@words << word.strip 19 | end 20 | 21 | def on_select 22 | @guess.set_target_word = @@words[rand(@@words.size)] 23 | @guess 24 | end 25 | end 26 | 27 | class Guess < Page 28 | pages :game_over 29 | persistent :target, :guesses_left, :letters, :guessed, :win 30 | 31 | def on_select_from_link(value) 32 | next_page = self 33 | @guessed[value] = true 34 | 35 | if @target.include?(value) 36 | @letters.each_index { |index| @letters[index] = value if @target[index] == value } 37 | else 38 | @guesses_left = @guesses_left - 1 39 | end 40 | 41 | @win = !@letters.include?('_') 42 | 43 | if @win || @guesses_left == 0 44 | next_page = @game_over 45 | @game_over.target = @target 46 | @game_over.win = @win 47 | @game_over.guesses_left = @guesses_left 48 | end 49 | 50 | next_page 51 | end 52 | 53 | def set_target_word=(value) 54 | @win = false 55 | @guesses_left = 5 56 | @target = Array.new 57 | value.scan(/.{1}/).each { |char| @target << char } 58 | @letters = Array.new(@target.length, '_') 59 | @guessed = Hash.new(false) 60 | ('a'..'z').each { |letter| @guessed[letter] = false } 61 | end 62 | end 63 | 64 | class GameOver < Page 65 | persistent :target, :win, :guesses_left 66 | end 67 | 68 | web_app = HangmanGame.new 69 | web_app.start 3010 if __FILE__ == $PROGRAM_NAME 70 | end 71 | -------------------------------------------------------------------------------- /examples/hilo/html/game_over.xhtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | Game Over! 9 | 10 | 11 |

Game Over

12 | 13 |

You guessed the secret number in n guesses!

14 |

[Play Again]

15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /examples/hilo/html/guess.xhtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | Guess A Number 9 | 10 | 11 |

Make a guess between one and ten:

12 |

Too low or two high message

13 | 14 | 15 | 16 | guess 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /examples/hilo/html/start.xhtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | Hi/Lo Game Start Page 9 | 10 | 11 |

Hi/Lo Guess

12 | 13 |

I'm thinking of a number between one and ten ...

14 | 15 |

16 | Start guessing 17 |

18 | 19 | 20 | -------------------------------------------------------------------------------- /examples/hilo/source/hilo.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'trellis' 3 | 4 | include Trellis 5 | 6 | module HiLo 7 | 8 | class HiLoGame < Application 9 | home :start 10 | logger.level = DEBUG 11 | end 12 | 13 | class Start < Page 14 | pages :guess 15 | 16 | def on_select 17 | @guess.initialize_target 18 | end 19 | end 20 | 21 | class Guess < Page 22 | pages :game_over 23 | persistent :target, :message, :count 24 | 25 | def on_select_from_link(value) 26 | guess_val = value.to_i 27 | next_page = self 28 | @count = @count + 1 29 | if guess_val == @target 30 | @game_over.count = @count 31 | next_page = @game_over 32 | else 33 | @message = "Guess #{guess_val} is too #{guess_val < @target ? 'low' : 'high'}" 34 | end 35 | 36 | next_page 37 | end 38 | 39 | def initialize_target 40 | @target, @count, @message = rand(9) + 1, 0, '' 41 | self 42 | end 43 | end 44 | 45 | class GameOver < Page 46 | persistent :count 47 | end 48 | 49 | web_app = HiLoGame.new 50 | web_app.start 3001 if __FILE__ == $PROGRAM_NAME 51 | end 52 | -------------------------------------------------------------------------------- /examples/routing/source/routing.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'trellis' 3 | 4 | include Trellis 5 | 6 | module Routing 7 | 8 | class Routing < Application 9 | home :home 10 | logger.level = DEBUG 11 | end 12 | 13 | class Home < Trellis::Page 14 | template do html { body { text %[this is the home page] }} end 15 | end 16 | 17 | class RoutedDifferently < Trellis::Page 18 | route '/whoa' 19 | 20 | template do html { body { text %[whoa!] }} end 21 | end 22 | 23 | class RoutedDifferentlyWithParams < Trellis::Page 24 | route '/day_of_the_year/:year/:month/:day' 25 | 26 | def parse_date 27 | Date.parse("#{@month}/#{@day}/#{@year}").strftime("%e %B, %Y") 28 | end 29 | 30 | def on_select 31 | self 32 | end 33 | 34 | template %[ 35 | 38 |

@{parse_date}@ is the @{Date.parse(@month + '/' + @day + '/' + @year).yday.ordinalize.to_s}@ day of the year!

39 |
40 | Refresh 41 | 42 | ], :format => :eruby 43 | end 44 | 45 | Routing.new.start if __FILE__ == $PROGRAM_NAME 46 | end 47 | 48 | -------------------------------------------------------------------------------- /examples/sessions/source/sessions.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'trellis' 3 | 4 | include Trellis 5 | 6 | module Sessions 7 | 8 | class Sessions < Application 9 | home :start 10 | 11 | session :pool, { :key => 'rack.session', :path => '/', :expire_after => 2592000 } 12 | end 13 | 14 | class Start < Page 15 | persistent :results 16 | 17 | def on_select 18 | logger.info "processing on_select" 19 | @results = rand(9) 20 | self 21 | end 22 | 23 | template do # using Markaby 24 | xhtml_strict { 25 | head { title "Simplest Trellis Application" } 26 | body { 27 | h1 "Persistent Fields via Session" 28 | p "This application uses Rack::Session::Pool for in-memory, cookie backed HTTP sessions" 29 | p { 30 | text "The value is: " 31 | text %[${results}.] 32 | } 33 | p { 34 | text %[[Click Me]] 35 | } 36 | } 37 | } 38 | end 39 | end 40 | 41 | Sessions.new.start 3012 if __FILE__ == $PROGRAM_NAME 42 | end 43 | -------------------------------------------------------------------------------- /examples/simplest/source/simplest.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'trellis' 3 | 4 | include Trellis 5 | 6 | module Simplest 7 | 8 | class Simplest < Application 9 | home :start 10 | end 11 | 12 | class Start < Page 13 | attr_accessor :current_time 14 | 15 | def initialize 16 | @current_time = Time.now 17 | end 18 | 19 | template do # using Markaby 20 | thtml { 21 | head { title "Simplest Trellis Application" } 22 | body { 23 | h1 "Simplest Trellis Application" 24 | p "One Application, One Page, Two Components" 25 | p "The time below should change after each request." 26 | p { 27 | text "The current time is: " 28 | text %[${currentTime}.] 29 | } 30 | p { 31 | text %[[refresh]] 32 | } 33 | } 34 | } 35 | end 36 | end 37 | 38 | Simplest.new.start if __FILE__ == $PROGRAM_NAME 39 | end 40 | -------------------------------------------------------------------------------- /examples/stateful_counters/html/counters.xhtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | Trellis: Multi Counter 8 | 9 | 10 | 11 |
12 |
13 |
14 |
15 |

Multi Counter

16 |

The multicounter show multiple instances of a stateful component. It displays several instances of the same counter component.

17 |

18 | 19 |

20 |
21 |
22 | 23 |

1

24 | ++ -- 25 |
26 |

1

27 | ++ -- 28 |
29 |

1

30 | ++ -- 31 |
32 |

0

33 | ++ -- 34 |
35 |

0

36 | ++ -- 37 |
38 | 39 |
40 | 41 |
42 | 43 |
44 |
45 | Reset all counters 46 |
47 |
48 |
49 |
50 | 51 | 52 | -------------------------------------------------------------------------------- /examples/stateful_counters/html/style/main.css: -------------------------------------------------------------------------------- 1 | /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 2 | * Design : Samuel Morello * 3 | * CSS : Lukas Renggli * 4 | * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 5 | 6 | /* ---- html ---- */ 7 | body, 8 | table, tr, td, th { 9 | color: #002842; 10 | font-size: 12px; 11 | line-height: 1.5; 12 | background-color: #fff; 13 | font-family: Verdana, Arial, Helvetica, sans-serif; 14 | } 15 | body, div, span, table, tr, td, th, img, form { 16 | border: 0; 17 | margin: 0; 18 | padding: 0; 19 | } 20 | table { 21 | border-spacing: 0; 22 | border-collapse: collapse; 23 | } 24 | th, td { 25 | text-align: left; 26 | vertical-align: top; 27 | } 28 | caption, th { 29 | text-align: left; 30 | } 31 | pre { 32 | margin-left: 2em; 33 | } 34 | p, ol, ul, h1, h2, h3, h4, h5, h6 { 35 | margin-bottom: 15px; 36 | } 37 | td, th { 38 | padding: 2px; 39 | } 40 | ul, ul li { 41 | margin: 0; 42 | padding: 0; 43 | list-style: none; 44 | } 45 | 46 | /* ---- columns ---- */ 47 | .columns { 48 | width: 840px; 49 | margin: 0 auto; 50 | overflow: hidden; 51 | text-align: left; 52 | } 53 | .columns div.single, 54 | .columns div.double, 55 | .columns div.triple, 56 | .columns div.quadruple { 57 | float: left; 58 | padding: 0 10px 32767px 10px; 59 | margin-bottom: -32767px; 60 | } 61 | .columns div.single { 62 | width: 190px; 63 | } 64 | .columns div.double { 65 | width: 400px; 66 | } 67 | .columns div.triple { 68 | width: 610px; 69 | } 70 | .columns div.quadruple { 71 | width: 840px; 72 | padding-left: 0px; 73 | padding-right: 0px; 74 | } 75 | .columns div.clear { 76 | clear: both; 77 | } 78 | 79 | /* ---- head ---- */ 80 | #head { 81 | height: 60px; 82 | text-align: center; 83 | background-repeat: repeat-x; 84 | background-image: url(head.png); 85 | } 86 | #head .columns { 87 | overflow: visible; 88 | } 89 | #head .search { 90 | float: right; 91 | width: 210px; 92 | height: 36px; 93 | text-align: center; 94 | background-repeat: no-repeat; 95 | background-position: top center; 96 | background-image: url(search.png); 97 | } 98 | #head .search input { 99 | width: 190px; 100 | margin-top: 6px; 101 | } 102 | #head .search div { 103 | z-index: 100; 104 | background-color: #fbfffa; 105 | border: 1px solid #93bdd5; 106 | } 107 | #head .search div a { 108 | color: #002842; 109 | text-decoration: none; 110 | } 111 | #head .search div ul, 112 | #head .search div ul li { 113 | cursor: pointer; 114 | text-align: left; 115 | line-height: 1.7; 116 | } 117 | #head .search div ul li { 118 | padding-left: 4px; 119 | } 120 | #head .search div ul li.selected { 121 | background-color: #93bdd5; 122 | } 123 | 124 | /* ---- home ---- */ 125 | #home { 126 | height: 180px; 127 | } 128 | 129 | /* ---- logo ---- */ 130 | #logo { 131 | height: 142px; 132 | } 133 | 134 | /* ---- children ---- */ 135 | #children h1 { 136 | visibility: hidden; 137 | } 138 | #children ul { 139 | margin-left: 5px; 140 | } 141 | #children ul li { 142 | margin-left: 16px; 143 | } 144 | #children ul li a.active { 145 | display: block; 146 | margin-left: -16px; 147 | padding-left: 16px; 148 | background-repeat: no-repeat; 149 | background-position: top left; 150 | background-image: url(list-star.gif); 151 | } 152 | 153 | /* ---- foot ---- */ 154 | #foot { 155 | font-size: 90%; 156 | padding-top: 10px; 157 | text-align: center; 158 | background-color: #eaede9; 159 | background-repeat: no-repeat; 160 | background-position: top center; 161 | background-image: url(foot.png); 162 | } 163 | #foot div { 164 | text-align: left; 165 | } 166 | #foot a { 167 | color: #0471b0; 168 | } 169 | #foot h1 { 170 | margin-top: 0; 171 | color: #002842; 172 | font-size: 175%; 173 | font-style: italic; 174 | font-weight: normal; 175 | text-transform: lowercase; 176 | } 177 | 178 | #navigation ul li { 179 | margin-left: 16px; 180 | } 181 | #navigation ul li a.active { 182 | display: block; 183 | margin-left: -16px; 184 | padding-left: 16px; 185 | background-repeat: no-repeat; 186 | background-position: center left; 187 | background-image: url(list-star.gif); 188 | } 189 | 190 | #manage ul li { 191 | float: left; 192 | display: inline; 193 | padding-right: 1em; 194 | } 195 | 196 | #powered { 197 | color: #858e8d; 198 | font-size: 90%; 199 | margin-top: 60px; 200 | margin-left: -5px; 201 | } 202 | #powered a { 203 | color: #858e8d; 204 | } 205 | 206 | /* ---- align ---- */ 207 | html, body, #wrap { 208 | height: 100%; 209 | min-height: 100%; 210 | } 211 | html>body #wrap { 212 | height: auto; 213 | } 214 | #wrap { 215 | width: 100%; 216 | position: absolute; 217 | } 218 | #desk { 219 | height: auto; 220 | padding-bottom: 169px; 221 | } 222 | #foot { 223 | bottom: 0; 224 | width: 100%; 225 | height: 150px; 226 | overflow: visible; 227 | position: absolute; 228 | } 229 | 230 | /* ---- desk ---- */ 231 | #desk { 232 | text-align: center; 233 | } 234 | #desk div.columns { 235 | text-align: left; 236 | } 237 | #desk a { 238 | color: #008aff; 239 | } 240 | 241 | /* ---- body ---- */ 242 | #body h1, 243 | #body h2, 244 | #body h3 { 245 | color: #01598d; 246 | font-weight: normal; 247 | } 248 | #body h1 a { 249 | color: #01598d; 250 | text-decoration: none; 251 | } 252 | #body h1 a:hover { 253 | text-decoration: underline; 254 | } 255 | #body h1.heading { 256 | font-size: 225%; 257 | margin-bottom: 10px; 258 | } 259 | #body h1 { 260 | font-size: 175%; 261 | } 262 | #body h2 { 263 | font-size: 150%; 264 | } 265 | #body h3 { 266 | font-size: 125%; 267 | } 268 | #body h4, #body h5, #body h6 { 269 | font-size: 100%; 270 | } 271 | #body div.single, 272 | #body div.double, 273 | #body div.triple { 274 | background-repeat: no-repeat; 275 | background-image: url(column.png); 276 | } 277 | 278 | #body ul.hfeed, 279 | #body ul.hfeed li { 280 | font-size: 92%; 281 | padding-left: 0; 282 | line-height: 1.25; 283 | padding-bottom: 1em; 284 | background-image: none; 285 | } 286 | #body ul.hfeed li .published { 287 | font-style: italic; 288 | margin-left: 0.25em; 289 | } 290 | 291 | /* ---- content ---- */ 292 | #body ul li, 293 | #content ul li { 294 | padding-left: 8px; 295 | background-repeat: no-repeat; 296 | background-position: left 0.4em; 297 | background-image: url(list-dash.gif); 298 | } 299 | 300 | /* ---- templates ---- */ 301 | #entry h1 { 302 | display: none; 303 | } 304 | #download { 305 | overflow: hidden; 306 | } 307 | #download h1 a.internal { 308 | display: block; 309 | height: 55px; 310 | margin-left: -9px; 311 | text-indent: 32767px; 312 | background-image: url(download.png); 313 | background-repeat: no-repeat; 314 | outline: none; 315 | } 316 | #download a.opensource { 317 | float: left; 318 | width: 75px; 319 | height: 70px; 320 | outline: none; 321 | display: block; 322 | text-indent: -32767px; 323 | background-image: url(opensource.png); 324 | background-repeat: no-repeat; 325 | background-position: left center; 326 | 327 | } 328 | 329 | /* ---- pier ---- */ 330 | #desk a.broken { 331 | color: #d32417; 332 | } 333 | #desk a.protected { 334 | color: #aaaaaa; 335 | } 336 | 337 | /* ---- magritte ---- */ 338 | #desk form table { 339 | width: 100%; 340 | } 341 | #desk form table th { 342 | width: 20%; 343 | text-align: right; 344 | vertical-align: top; 345 | } 346 | #desk form table td { 347 | width: 80%; 348 | } 349 | #desk ul.errors, 350 | #desk form table th.error, 351 | #desk form table td.error { 352 | color: #d32417; 353 | } 354 | #desk form table td.required:after { 355 | content: "*"; 356 | color: #d32417; 357 | position: absolute; 358 | } 359 | #desk form table input { 360 | margin-right: 0.5em; 361 | } 362 | #desk form table input.text, 363 | #desk form table input.password { 364 | width: 80%; 365 | } 366 | #desk form table textarea { 367 | width: 100%; 368 | height: 200px; 369 | } 370 | #desk form .buttons { 371 | text-align: right; 372 | padding-top: 1ex; 373 | padding-bottom: 1ex; 374 | } 375 | #desk form .buttons input { 376 | margin-left: 0.5em; 377 | } 378 | 379 | /* ---- trellis ---- */ 380 | #desk .trellis { 381 | margin: 1em; 382 | padding: 1em; 383 | border: 1px solid #93bdd5; 384 | background-color: #fbfffa; 385 | } 386 | #desk .trellis h1, 387 | #desk .trellis h2, 388 | #desk .trellis h3 { 389 | margin: 0; 390 | padding: 0; 391 | color: black; 392 | margin-bottom: 0.5em; 393 | } 394 | #desk .trellis hr { 395 | height: 1px; 396 | border-top: none; 397 | border-left: none; 398 | border-right: none; 399 | border-bottom: 1px solid #93bdd5; 400 | margin-top: 0.5em; 401 | margin-bottom: 0.5em; 402 | } 403 | #desk .trellis .dialog-buttons { 404 | margin-top: 0.5em; 405 | } 406 | 407 | /* ---- lightbox ---- */ 408 | #lightbox { 409 | width: 600px; 410 | background-color: #fbfffa; 411 | border: 4px solid #93bdd5; 412 | } 413 | #lightbox .head { 414 | padding: 4px; 415 | background-color: #93bdd5; 416 | } 417 | #lightbox .head h1 { 418 | margin: 0; 419 | padding: 0; 420 | float: left; 421 | display: block; 422 | color: #002842; 423 | font-size: 150%; 424 | font-weight: normal; 425 | } 426 | #lightbox .head a { 427 | float: right; 428 | outline: none; 429 | display: block; 430 | color: #002842; 431 | font-size: 150%; 432 | font-weight: bold; 433 | margin-right: 4px; 434 | text-decoration: none; 435 | } 436 | #lightbox .body { 437 | margin-bottom: -5px; 438 | } 439 | #lightbox .body img { 440 | width: 600px; 441 | } -------------------------------------------------------------------------------- /examples/stateful_counters/source/stateful_counters.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'trellis' 3 | 4 | include Trellis 5 | 6 | # http://seaside.st/about/examples/multicounter 7 | # This example shows 8 | # - a custom stateful component that can be use repeatedly in a page 9 | module StatefulCounters 10 | 11 | class Counter < Component 12 | is_stateful 13 | 14 | tag_name "counter" 15 | 16 | field :value, :persistent => true 17 | 18 | def initialize 19 | reset 20 | end 21 | 22 | render do |tag| 23 | tid = tag.attr['tid'] 24 | page = tag.globals.page 25 | counter = page.send("counter_#{tid}") 26 | value = counter.value 27 | href_add = DefaultRouter.to_uri(:page => page.class.name, 28 | :event => 'add', 29 | :source => "counter_#{tid}") 30 | href_subtract = DefaultRouter.to_uri(:page => page.class.name, 31 | :event => 'subtract', 32 | :source => "counter_#{tid}") 33 | builder = Builder::XmlMarkup.new 34 | builder.div(:id => tid) { 35 | builder.h1(value) 36 | builder.a("++", :href => href_add) 37 | builder.text(" ") 38 | builder.a("--", :href => href_subtract) 39 | } 40 | end 41 | 42 | def on_add 43 | @value = @value + 1 44 | end 45 | 46 | def on_subtract 47 | @value = @value - 1 48 | end 49 | 50 | def reset 51 | @value = 0 52 | end 53 | end 54 | 55 | class CountersApp < Application 56 | home :counters 57 | 58 | map_static ['/images', '/style', '/favicon.ico'] 59 | end 60 | 61 | class Counters < Page 62 | def on_select_from_reset 63 | # reset all counters on the page 64 | @counter_one.reset 65 | @counter_two.reset 66 | @counter_three.reset 67 | self 68 | end 69 | end 70 | 71 | web_app = CountersApp.new 72 | web_app.start 3005 if __FILE__ == $PROGRAM_NAME 73 | end -------------------------------------------------------------------------------- /lib/trellis.rb: -------------------------------------------------------------------------------- 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/trellis' -------------------------------------------------------------------------------- /lib/trellis/component_library/core_components.rb: -------------------------------------------------------------------------------- 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/trellis' 28 | require 'paginator' 29 | 30 | module Trellis 31 | module CoreComponents 32 | 33 | # Component that triggers an action on the server with a subsequent full 34 | # page refresh 35 | # 36 | class ActionLink < Trellis::Component 37 | render do |tag| 38 | source = tag.attr['tid'] 39 | context = tag.attr['context'] 40 | path = (tag.globals.page.path.nil? || tag.globals.page.path.empty?) ? nil : tag.globals.page.path 41 | class_name = tag.globals.page.class.name 42 | target_page = tag.attr['page'] || path || class_name 43 | 44 | if context 45 | value = tag.locals.instance_eval(context) || tag.globals.instance_eval(context) 46 | end 47 | 48 | if source 49 | if context 50 | id = "#{source}_#{value}" 51 | href = DefaultRouter.to_uri(:page => target_page, 52 | :event => 'select', 53 | :source => source, 54 | :value => value) 55 | else 56 | id = "#{source}" 57 | href = DefaultRouter.to_uri(:page => target_page, 58 | :event => 'select', 59 | :source => source) 60 | end 61 | else 62 | if context 63 | id = "action_link_#{value}" 64 | href = DefaultRouter.to_uri(:page => target_page, 65 | :event => 'select', 66 | :value => value) 67 | else 68 | id = "action_link" 69 | href = DefaultRouter.to_uri(:page => target_page, 70 | :event => 'select') 71 | end 72 | end 73 | 74 | builder = Builder::XmlMarkup.new 75 | text = builder.a('${contents}', "href" => href, "id" => id) 76 | text.replace_ant_style_property('contents', tag.expand) 77 | end 78 | end 79 | 80 | # 81 | # 82 | # 83 | class Loop < Trellis::Component 84 | render do |tag| 85 | value_name = tag.attr['value'] 86 | value = "#{value_name}=" 87 | # make value available by name to the page 88 | source = tag.attr['source'] 89 | start, finish = source.split('..') 90 | content = '' 91 | (start..finish).each do |n| 92 | tag.locals.send(value.to_sym, n) 93 | content << tag.expand 94 | end 95 | content 96 | end 97 | end 98 | 99 | # 100 | # 101 | # 102 | class Each < Trellis::Component 103 | render do |tag| 104 | value_name = tag.attr['value'] 105 | value = "#{value_name}=" 106 | # make value available by name to the page 107 | source = tag.attr['source'] 108 | begin 109 | iterator = tag.locals.instance_eval(source) || tag.globals.instance_eval(source) 110 | rescue NoMethodError 111 | iterator = [] 112 | end 113 | content = '' 114 | iterator.each do |n| 115 | tag.locals.send(value.to_sym, n) 116 | content << tag.expand 117 | end if iterator 118 | content 119 | end 120 | end 121 | 122 | # 123 | # 124 | # 125 | class Value < Trellis::Component 126 | render do |tag| 127 | name = tag.attr['name'] 128 | unless name.include?('.') 129 | value = tag.locals.send(name.to_sym) || tag.globals.send(name.to_sym) 130 | else 131 | value = tag.locals.instance_eval(name) || tag.globals.instance_eval(name) 132 | end 133 | value 134 | end 135 | end 136 | 137 | class Eval < Trellis::Component 138 | render do |tag| 139 | expression = tag.attr['expression'] 140 | tag.locals.instance_eval(expression) || tag.globals.instance_eval(expression) if expression 141 | end 142 | end 143 | 144 | # 145 | # page link should take parameters for pages that have a custom route 146 | # 147 | class PageLink < Trellis::Component 148 | render do |tag| 149 | url_root = tag.globals.page.class.url_root 150 | page_name = tag.attr['tpage'] 151 | id = tag.attr['tid'] || page_name 152 | href = DefaultRouter.to_uri(:url_root => url_root, :page => page_name) 153 | contents = tag.expand 154 | builder = Builder::XmlMarkup.new 155 | builder.a(contents, "href" => href, "id" => "page_link_#{id}") 156 | end 157 | end 158 | 159 | # 160 | # 161 | # 162 | class Img < Trellis::Component 163 | render do |tag| 164 | attrs = tag.attr.exclude_keys('src', 'alt') 165 | 166 | # resolve the ${} variables 167 | attrs['src'] = Utils.expand_properties_in_tag(tag.attr['src'], tag) 168 | attrs['alt'] = Utils.expand_properties_in_tag(tag.attr['alt'], tag) 169 | 170 | builder = Builder::XmlMarkup.new 171 | builder.img(attrs) 172 | end 173 | end 174 | 175 | # 176 | # 177 | # 178 | class Remove < Trellis::Component 179 | render do |tag| 180 | # do nothing 181 | end 182 | end 183 | 184 | # 185 | # 186 | # 187 | class If < Trellis::Component 188 | render do |tag| 189 | # resolve the ${} variables 190 | test = Utils.expand_properties_in_tag(tag.attr['test'], tag) 191 | value = tag.locals.instance_eval(test) || tag.globals.instance_eval(test) 192 | result = !value ? false : value 193 | tag.expand if result 194 | end 195 | end 196 | 197 | # 198 | # 199 | # 200 | class Unless < Trellis::Component 201 | render do |tag| 202 | # resolve the ${} variables 203 | test = Utils.expand_properties_in_tag(tag.attr['test'], tag) 204 | value = tag.locals.instance_eval(test) || tag.globals.instance_eval(test) 205 | result = !value ? false : value 206 | tag.expand unless result 207 | end 208 | end 209 | 210 | # 211 | # 212 | # 213 | class Button < Trellis::Component 214 | render do |tag| 215 | attrs = tag.attr.exclude_keys('tid', 'name', 'type') 216 | attrs['name'] = "#{tag.attr['tid']}" 217 | contents = tag.expand 218 | builder = Builder::XmlMarkup.new 219 | builder.button(contents, attrs) 220 | end 221 | end 222 | 223 | # 224 | # 225 | # 226 | class Form < Trellis::Component 227 | render do |tag| 228 | url_root = tag.globals.page.class.url_root 229 | form_name = tag.attr['tid'] 230 | on_behalf = tag.attr['on_behalf'] 231 | method = tag.attr['method'] || 'GET' 232 | tag.locals.form_name = form_name 233 | value = tag.attr['value'] 234 | 235 | if value 236 | eval_value = tag.locals.instance_eval(value) || tag.globals.instance_eval(value) 237 | end 238 | 239 | path = (tag.globals.page.path.nil? || tag.globals.page.path.empty?) ? nil : tag.globals.page.path 240 | class_name = tag.globals.page.class.name 241 | target_page = path || class_name 242 | 243 | href = Trellis::DefaultRouter.to_uri(:url_root => url_root, 244 | :page => target_page, 245 | :event => "submit", 246 | :source => "#{(on_behalf ? on_behalf : form_name)}", 247 | :value => eval_value) 248 | 249 | attrs = tag.attr.exclude_keys('tid', 'on_behalf', 'method') 250 | attrs["name"] = form_name 251 | attrs["action"] = href 252 | attrs["method"] = method 253 | 254 | builder = Builder::XmlMarkup.new 255 | builder.form(attrs) do |form| 256 | form << tag.expand 257 | end 258 | end 259 | end 260 | 261 | # 262 | # 263 | # 264 | class Submit < Trellis::Component 265 | tag_name "submit" 266 | 267 | contained_in "form" 268 | 269 | render do |tag| 270 | attrs = tag.attr.exclude_keys('tid', 'name', 'type') 271 | attrs['name'] = "#{tag.locals.form_name}_#{tag.attr['tid']}" 272 | attrs['type'] = 'submit' 273 | builder = Builder::XmlMarkup.new 274 | builder.input(attrs) 275 | end 276 | end 277 | 278 | # 279 | # 280 | # 281 | class CheckBox < Trellis::Component 282 | tag_name "check_box" 283 | 284 | contained_in "form" 285 | 286 | render do |tag| 287 | attrs = tag.attr.exclude_keys('tid', 'name', 'type') 288 | attrs['name'] = "#{tag.locals.form_name}_#{tag.attr['tid']}" 289 | attrs['type'] = 'checkbox' 290 | builder = Builder::XmlMarkup.new 291 | builder.input(attrs) 292 | end 293 | end 294 | 295 | # 296 | # 297 | # 298 | class TextField < Trellis::Component 299 | tag_name "text_field" 300 | 301 | contained_in "form" 302 | 303 | render do |tag| 304 | attrs = tag.attr.exclude_keys('tid', 'name', 'type', 'value') 305 | attrs['name'] = "#{tag.locals.form_name}_#{tag.attr['tid']}" 306 | value = tag.attr['value'] 307 | literal = tag.attr['literal'] =~ /^(y|yes|true)$/ 308 | if value 309 | if !literal 310 | resolved_value = '' 311 | unless value.include?('.') 312 | resolved_value = tag.locals.send(value.to_sym) || tag.globals.send(value.to_sym) 313 | else 314 | target_name, method = value.split('.') 315 | target = tag.locals.send(target_name.to_sym) || tag.globals.send(target_name.to_sym) 316 | resolved_value = target.send(method.to_sym) if target 317 | end 318 | attrs['value'] = resolved_value if resolved_value 319 | else 320 | attrs['value'] = value 321 | end 322 | end 323 | 324 | attrs['type'] = 'text' 325 | builder = Builder::XmlMarkup.new 326 | builder.input(attrs) 327 | end 328 | end 329 | 330 | # 331 | # 332 | # 333 | class TextArea < Trellis::Component 334 | tag_name "text_area" 335 | 336 | contained_in "form" 337 | 338 | render do |tag| 339 | attrs = tag.attr.exclude_keys('tid', 'name', 'keep_contents') 340 | attrs['name'] = "#{tag.locals.form_name}_#{tag.attr['tid']}" 341 | keep_contents = tag.attr['keep_contents'] 342 | if keep_contents 343 | contents = keep_contents =~ /^(y|yes|true)$/ ? tag.expand : '' 344 | else 345 | contents = '' 346 | end 347 | builder = Builder::XmlMarkup.new 348 | builder.textarea(contents, attrs) 349 | end 350 | end 351 | 352 | # 353 | # 354 | # 355 | class Password < Trellis::Component 356 | tag_name "password" 357 | 358 | contained_in "form" 359 | 360 | render do |tag| 361 | attrs = tag.attr.exclude_keys('tid', 'name', 'type') 362 | attrs['name'] = "#{tag.locals.form_name}_#{tag.attr['tid']}" 363 | attrs['type'] = 'password' 364 | builder = Builder::XmlMarkup.new 365 | builder.input(attrs) 366 | end 367 | end 368 | 369 | # 370 | # 371 | # 372 | class Hidden < Trellis::Component 373 | tag_name "hidden" 374 | 375 | contained_in "form" 376 | 377 | render do |tag| 378 | attrs = tag.attr.exclude_keys('tid', 'name', 'type', 'value') 379 | attrs['name'] = "#{tag.locals.form_name}_#{tag.attr['tid']}" 380 | value = tag.attr['value'] 381 | literal = tag.attr['literal'] =~ /^(y|yes|true)$/ 382 | if value 383 | if !literal 384 | resolved_value = '' 385 | unless value.include?('.') 386 | resolved_value = tag.globals.send(value.to_sym) 387 | else 388 | target_name, method = value.split('.') 389 | target = tag.globals.send(target_name.to_sym) 390 | resolved_value = target.send(method.to_sym) 391 | end 392 | attrs['value'] = resolved_value 393 | else 394 | attrs['value'] = value 395 | end 396 | end 397 | 398 | attrs['type'] = 'hidden' 399 | builder = Builder::XmlMarkup.new 400 | builder.input(attrs) 401 | end 402 | end 403 | 404 | # 405 | # 406 | # 407 | class Select < Trellis::Component 408 | tag_name "select" 409 | 410 | contained_in "form" 411 | 412 | render do |tag| 413 | attrs = tag.attr.exclude_keys('tid', 'select_if', 'selected_value', 'source') 414 | attrs['name'] = "#{tag.locals.form_name}_#{tag.attr['tid']}" 415 | expression = tag.attr['source'] # something we can iterate over 416 | selected = Utils.evaluate_tag_attribute('select_if', tag) 417 | selected_value = Utils.evaluate_tag_attribute('selected_value', tag) 418 | value_accessor = Utils.expand_properties_in_tag(tag.attr['value'], tag) 419 | 420 | builder = Builder::XmlMarkup.new 421 | builder.select(attrs) do 422 | if expression.include? '..' 423 | start, finish = expression.split('..') 424 | (start..finish).each do |item| 425 | value = value_accessor ? item.instance_eval(value_accessor) : item 426 | unless item == selected 427 | builder.option item, :value => value 428 | else 429 | builder.option item, selected => (selected_value.nil? ? 'yes' : selected_value), :value => value 430 | end 431 | end 432 | else 433 | source = Utils.evaluate_tag_attribute('source', tag) 434 | if source.respond_to? :each 435 | source.each do |item| 436 | value = value_accessor ? item.instance_eval(value_accessor) : item 437 | unless item == selected 438 | builder.option item, :value => value 439 | else 440 | builder.option item, :selected => (selected_value.nil? ? 'yes' : selected_value), :value => value 441 | end 442 | end 443 | elsif source.respond_to? :each_pair 444 | source.each_pair do |name,value| 445 | unless value == selected 446 | builder.option name, :value => value 447 | else 448 | builder.option name, :selected => (selected_value.nil? ? value : selected_value), :value => value 449 | end 450 | end 451 | end 452 | end 453 | end 454 | end 455 | end 456 | 457 | 458 | # Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object 459 | # assigned to the template (identified by +object+). The text of label will default to the attribute name unless you specify 460 | # it explicitly. Additional options on the label tag can be passed as a hash with +options+. These options will be tagged 461 | # onto the HTML as an HTML element attribute as in the example shown. 462 | # 463 | # ==== Examples 464 | # label(:post, :title) 465 | # #=> 466 | # 467 | # label(:post, :title, "A short title") 468 | # #=> 469 | # 470 | # label(:post, :title, "A short title", :class => "title_label") 471 | # #=> 472 | class Label < Trellis::Component 473 | render do |tag| 474 | target = "#{tag.locals.form_name}_#{tag.attr['for']}" 475 | contents = tag.expand 476 | builder = Builder::XmlMarkup.new 477 | builder.label(contents, :for => target) 478 | end 479 | end 480 | 481 | # Creates a file upload field. If you are using file uploads then you will also need 482 | # to set the multipart option for the form tag: 483 | # 484 | # <%= form_tag { :action => "post" }, { :multipart => true } %> 485 | # <%= file_field_tag "file" %> 486 | # <%= submit_tag %> 487 | # <%= end_form_tag %> 488 | # 489 | # The specified URL will then be passed a File object containing the selected file, or if the field 490 | # was left blank, a StringIO object. 491 | # 492 | # ==== Options 493 | # * Creates standard HTML attributes for the tag. 494 | # * :disabled - If set to true, the user will not be able to use this input. 495 | # 496 | # ==== Examples 497 | # file_field_tag 'attachment' 498 | # # => 499 | # 500 | # file_field_tag 'avatar', :class => 'profile-input' 501 | # # => 502 | # 503 | # file_field_tag 'picture', :disabled => true 504 | # # => 505 | # 506 | # file_field_tag 'resume', :value => '~/resume.doc' 507 | # # => 508 | # 509 | # file_field_tag 'user_pic', :accept => 'image/png,image/gif,image/jpeg' 510 | # # => 511 | # 512 | # file_field_tag 'file', :accept => 'text/html', :class => 'upload', :value => 'index.html' 513 | # # => 514 | class File < Trellis::Component 515 | 516 | render do |tag| 517 | 518 | end 519 | end 520 | 521 | # TODO: Need radio button group and a standalone radio button 522 | 523 | class Div < Trellis::Component 524 | render do |tag| 525 | attrs = tag.attr.exclude_keys('id', 'title') 526 | attrs['id'] = Utils.expand_properties_in_tag(tag.attr['id'], tag) if tag.attr['id'] 527 | attrs['title'] = Utils.expand_properties_in_tag(tag.attr['title'], tag) if tag.attr['title'] 528 | builder = Builder::XmlMarkup.new 529 | builder.div(attrs) { |div| 530 | div << tag.expand 531 | } 532 | end 533 | end 534 | 535 | end 536 | end -------------------------------------------------------------------------------- /lib/trellis/component_library/grid.rb: -------------------------------------------------------------------------------- 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/trellis' 28 | require 'paginator' 29 | 30 | module Trellis 31 | module CoreLibrary 32 | 33 | # 34 | # 35 | # 36 | class Grid < Trellis::Component 37 | is_stateful 38 | 39 | tag_name "grid" 40 | 41 | attr_accessor :source 42 | field :page_position, :persistent => true 43 | attr_reader :properties 44 | attr_reader :commands 45 | attr_reader :counter_method 46 | attr_reader :counter_method_arguments 47 | attr_reader :retrieve_block 48 | attr_reader :sort_properties 49 | field :sorted_by, :persistent => true 50 | field :sort_direction, :persistent => true, :default_value => :ascending 51 | 52 | def initialize 53 | @properties = [] 54 | @sort_properties = [] 55 | @commands = [] 56 | end 57 | 58 | # must be called before rendering 59 | def columns(*syms) 60 | syms.each do |sym| 61 | @properties << sym 62 | end 63 | end 64 | 65 | def add_command(options=[]) 66 | # extract options 67 | name = options[:name] if options 68 | page = options[:page] if options 69 | context = options[:context] if options 70 | image = options[:image] if options 71 | 72 | @commands << lambda do |tag, object| 73 | tid = tag.attr['tid'] 74 | value = object.send(context.to_sym) 75 | url_root = tag.globals.page.class.url_root 76 | page = tag.globals.page.class.name unless page 77 | href = Trellis::DefaultRouter.to_uri(:url_root => url_root, 78 | :page => page, 79 | :event => name, 80 | :source => tid, 81 | :value => value) 82 | 83 | %{ 84 | 85 | 86 | 87 | } 88 | end 89 | end 90 | 91 | def sort_by_all_except(*syms) 92 | @sort_properties = @properties.reject { |property| syms.includes?(property)} 93 | end 94 | 95 | def sort_by(*syms) 96 | unless syms.first == :all 97 | @sort_properties = syms 98 | else 99 | @sort_properties = @properties 100 | end 101 | end 102 | 103 | def size_accessor(symbol, args) 104 | @counter_method, @counter_method_arguments = symbol, args 105 | end 106 | 107 | def retrieve_method(&block) 108 | @retrieve_block = block 109 | end 110 | 111 | # event handlers 112 | 113 | def on_page(page) 114 | @page_position = page.to_i # must use the cohersion built in capabilities 115 | end 116 | 117 | def on_sort(property) 118 | to_sym = property.to_sym 119 | if @sort_properties.include?(to_sym) 120 | if @sorted_by != to_sym 121 | @sorted_by = to_sym 122 | else 123 | @sort_direction = @sort_direction == :ascending ? :descending : :ascending 124 | end 125 | end 126 | end 127 | 128 | render do |tag| 129 | url_root = tag.globals.page.class.url_root 130 | # get the page object 131 | page = tag.globals.page 132 | # get the tag properties 133 | tid = tag.attr['tid'] 134 | rows_per_page = tag.attr['rows_per_page'] || 3 135 | # get the instance of the component from the page 136 | grid = page.send("grid_#{tid}") 137 | # get the properties or columns 138 | properties = grid.properties 139 | # get the sort properties 140 | sort_properties = grid.sort_properties 141 | # current page 142 | current_page = grid.page_position || 1 143 | # sort information 144 | sorted_by = grid.sorted_by 145 | sort_direction = grid.sort_direction 146 | commands = grid.commands 147 | # get the source from either the tag or the component instance itself 148 | source = tag.attr['source'] || grid.source 149 | # get the array or collection that we can iterate over 150 | iterator = tag.globals.send(source.to_sym) 151 | 152 | builder = Builder::XmlMarkup.new 153 | 154 | # build the table 155 | builder.div(:class => "t-data-grid") { 156 | 157 | # configure pagination 158 | #TODO need to implement page ranging 159 | range = tag.attr['page_range'] || 5 160 | available_rows = 0 161 | unless grid.counter_method 162 | available_rows = iterator.length 163 | else 164 | unless grid.counter_method_arguments 165 | available_rows = iterator.send(grid.counter_method) 166 | else 167 | available_rows = iterator.send(grid.counter_method, grid.counter_method_arguments) 168 | end 169 | end 170 | 171 | # configure the pager 172 | pager = Paginator.new(available_rows, rows_per_page) do |offset, per_page| 173 | rows = nil 174 | unless grid.retrieve_method 175 | if sorted_by 176 | iterator = iterator.sort_by { |row| row.send(sorted_by) } 177 | iterator.reverse! if sort_direction == :descending 178 | end 179 | rows = iterator[offset, per_page] 180 | else 181 | rows = grid.retrieve_method.call 182 | end 183 | rows 184 | end 185 | 186 | # get the current page from the pager 187 | rows = pager.page(current_page) 188 | 189 | # render the pager control if necessary 190 | unless pager.number_of_pages < 2 191 | builder.div(:class => "t-data-grid-pager") { 192 | # loop over the pages 193 | (1..pager.number_of_pages).each do |page_num| 194 | if page_num == current_page 195 | builder.span("#{page_num}", :class => "current") 196 | else 197 | href = Trellis::DefaultRouter.to_uri(:url_root => url_root, 198 | :page => page.class.name, 199 | :event => "page", 200 | :source => "grid_#{tid}", 201 | :value => "#{page_num}") 202 | builder.a("#{page_num}", :href => href, :title => "Go to page #{page_num}") 203 | end 204 | end 205 | } 206 | end 207 | 208 | # build the html 209 | builder.table(:class => "t-data-grid") { 210 | # header 211 | builder.thead { 212 | builder.tr { 213 | properties.each_index { |index| 214 | property = properties[index] 215 | field_name = property.to_s.humanize 216 | 217 | if properties.length == index + 1 218 | css_class = "#{field_name} t-last" 219 | elsif index == 0 220 | css_class = "#{field_name} t-first" 221 | else 222 | css_class = "#{field_name}" 223 | end 224 | 225 | sort_image = 'sortable.png' 226 | if property == sorted_by 227 | if sort_direction == :ascending 228 | sort_image = 'sort-desc.png' 229 | elsif sort_direction == :descending 230 | sort_image = 'sort-asc.png' 231 | end 232 | end 233 | 234 | unless sort_properties.include?(property) 235 | builder.th(field_name, :class => css_class) 236 | else 237 | builder.th(:class => css_class) { 238 | href = Trellis::DefaultRouter.to_uri(:url_root => url_root, 239 | :page => page.class.name, 240 | :event => "sort", 241 | :source => "grid_#{tid}", 242 | :value => "#{property}") 243 | builder.a(field_name, :href => href) 244 | builder.a(:href => href) { 245 | builder.img(:alt => "[Sortable]", 246 | :class => "t-sort-icon", 247 | :id => "#{property}:sort", 248 | :src => "#{url_root}/images/#{sort_image}", 249 | :name => "#{property}:sort") 250 | } 251 | } 252 | end 253 | } 254 | # add columns for commands 255 | if commands && !commands.empty? 256 | (1..commands.size).each do 257 | builder.th 258 | end 259 | end 260 | } 261 | } 262 | 263 | # body 264 | builder.tbody { 265 | # data 266 | index = 0 267 | rows.each do |item| 268 | if (rows.last_item_number - rows.first_item_number) == index + 1 269 | css_class = "t-last" 270 | elsif index == 0 271 | css_class = "t-first" 272 | else 273 | css_class = nil 274 | end 275 | index = index + 1 276 | 277 | if css_class 278 | builder.tr(:class => css_class) { 279 | properties.each { |property| 280 | field_value = item.send(property) 281 | builder.td("#{field_value}", :class => "#{property}") 282 | } 283 | # add columns for commands 284 | if commands && !commands.empty? 285 | commands.each { |command| 286 | builder.td { |td| td << command.call(tag, item)} 287 | } 288 | end 289 | } 290 | else 291 | builder.tr { 292 | properties.each { |property| 293 | field_value = item.send(property) 294 | builder.td("#{field_value}", :class => "#{property}") 295 | } 296 | # add columns for commands 297 | if commands && !commands.empty? 298 | commands.each { |command| 299 | builder.td { |td| td << command.call(tag, item)} 300 | } 301 | end 302 | } 303 | end 304 | end if rows 305 | } 306 | } 307 | } 308 | end 309 | end 310 | end 311 | end 312 | -------------------------------------------------------------------------------- /lib/trellis/component_library/jquery.rb: -------------------------------------------------------------------------------- 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/trellis' 28 | require 'trellis/core_components' 29 | 30 | module Trellis 31 | # ---------------------------------------------------------------------------- 32 | # Trellis components based on JQueryUI and other JQuery Widgets 33 | # See http://jqueryui.com 34 | # ---------------------------------------------------------------------------- 35 | module JQueryComponents 36 | 37 | end 38 | end -------------------------------------------------------------------------------- /lib/trellis/component_library/object_editor.rb: -------------------------------------------------------------------------------- 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/trellis' 28 | require 'paginator' 29 | 30 | module Trellis 31 | module CoreLibrary 32 | # 33 | # 34 | # 35 | class ObjectEditor < Trellis::Component 36 | is_stateful 37 | 38 | depends_on :form, :submit 39 | 40 | field :model, :persistent => true 41 | attr_reader :retrieve_block 42 | attr_reader :properties 43 | attr_accessor :submit_text 44 | attr_reader :submit_block 45 | 46 | def initialize 47 | @properties = [] 48 | end 49 | 50 | # must be called before rendering 51 | def fields(*syms) 52 | syms.each do |sym| 53 | @properties << sym 54 | end 55 | end 56 | 57 | def on_submit(&block) 58 | # populate the object with values from the session 59 | if page.params 60 | properties.each do |property| 61 | value = page.params[property.to_sym] 62 | @model.instance_variable_set("@#{property}".to_sym, value) 63 | end 64 | end 65 | if block_given? 66 | @submit_block = block 67 | else 68 | @submit_block.call(@model) 69 | end 70 | end 71 | 72 | render do |tag| 73 | url_root = tag.globals.page.class.url_root 74 | # get the page object 75 | page = tag.globals.page 76 | # get the tag properties 77 | tid = tag.attr['tid'] 78 | # get the instance of the component from the page 79 | editor = page.send("object_editor_#{tid}") 80 | # get the properties or columns 81 | properties = editor.properties 82 | # get the source from either the tag or the component instance itself 83 | source = tag.attr['model'] || editor.model 84 | submit_text = tag.attr['submit_text'] || editor.submit_text || "Submit" 85 | 86 | builder = Builder::XmlMarkup.new 87 | 88 | # the editor encloses a form 89 | form = tag.render("form", "tid" => "form_#{tid}", "method" => "post", "on_behalf" => "object_editor#{tid}") do 90 | builder.div(:class => "t-beaneditor") { 91 | properties.each { |property| 92 | field_name = property.to_s.humanize 93 | field_value = source.send(property) 94 | builder.div(:class => "t-beaneditor-row") { 95 | builder.label(field_name, :for => property, :id => "#{property}:label") 96 | builder.input(:id => property, :name => property, :type => "text", :value => field_value) 97 | builder.img(:alt => "[Error]", :class => "t-error-icon t-invisible", :id => "#{property}:icon", :src => "#{url_root}/images/field-error-marker.gif", :name => "#{property}:icon") 98 | } 99 | } if source 100 | builder.div(:class => "t-beaneditor-row") { 101 | builder << tag.render("submit", "tid" => "submit", "name" => "whats_the_name", "value" => submit_text) 102 | } 103 | } 104 | end 105 | form 106 | end 107 | 108 | end 109 | end 110 | end 111 | -------------------------------------------------------------------------------- /lib/trellis/logging.rb: -------------------------------------------------------------------------------- 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 'log4r' 28 | require 'log4r/yamlconfigurator' 29 | require 'log4r/outputter/datefileoutputter' 30 | 31 | class Log4r::Logger #:nodoc: 32 | # Rack CommonLogger middleware expects a << method 33 | def <<(text) 34 | info(text.delete!("\n")) 35 | end 36 | 37 | def write(text) 38 | info(text.delete!("\n")) 39 | end 40 | end 41 | 42 | module Logging #:nodoc: all 43 | include Log4r 44 | 45 | def self.logger 46 | @@logger 47 | end 48 | 49 | def Logging.included(recipient) 50 | cfg = YamlConfigurator 51 | cfg['HOME'] = '.' 52 | begin 53 | cfg.load_yaml_file('logging.yaml') #TODO make this configurable 54 | logger = Logger['trellis'] 55 | rescue 56 | logger = Logger.new 'trellis' 57 | formatter = PatternFormatter.new(:pattern => '%d %l: %m ', :date_pattern => '%y%m%d %H:%M:%S') 58 | logger.add Log4r::StdoutOutputter.new('stdout', :formatter=> formatter) 59 | end 60 | logger.level = INFO 61 | recipient.instance_variable_set(:@logger, logger) 62 | recipient.class.send(:define_method, :logger) { @logger } 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /lib/trellis/utils.rb: -------------------------------------------------------------------------------- 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 | class Object #:nodoc: 28 | def meta_def(m,&b) #:nodoc: 29 | metaclass.send(:define_method,m,&b) 30 | end 31 | 32 | def metaclass 33 | class< 1 167 | end 168 | end 169 | 170 | class Hash #:nodoc: 171 | def keys_to_symbols 172 | self.each_pair do |key, value| 173 | self["#{key}".to_sym] = value if key 174 | end 175 | end 176 | 177 | def each_pair_except(*exceptions) 178 | _each_pair_except(exceptions) 179 | end 180 | 181 | def exclude_keys(*exceptions) 182 | result = Hash.new 183 | self._each_pair_except(exceptions) do |key, value| 184 | result[key] = value 185 | end 186 | result 187 | end 188 | 189 | protected 190 | 191 | def _each_pair_except(exceptions) 192 | self.each_pair do |key, value| 193 | unless exceptions.include?(key) then 194 | yield [key, value] 195 | end 196 | end 197 | end 198 | end 199 | 200 | class File 201 | def self.find(dir, filename="*.*", subdirs=true) 202 | Dir[ subdirs ? File.join(dir.split(/\\/), "**", filename) : File.join(dir.split(/\\/), filename) ] 203 | end 204 | 205 | def self.find_first(dir, filename, subdirs=false) 206 | find(dir, filename, subdirs).first 207 | end 208 | 209 | end 210 | 211 | 212 | # class Radius::TagBinding #:nodoc: 213 | # 214 | # end 215 | 216 | module Utils 217 | 218 | # TODO open the tag ==> TagContext?? class 219 | def self.expand_properties_in_tag(text, tag) 220 | # resolve the ${} variables 221 | result = text #TODO not tested! 222 | result = text.gsub(/\$\{.*?\}/) do |match| 223 | name = match.split(/\$\{|\}/)[1] 224 | unless name.include?('.') 225 | tag.locals.send(name.to_sym) || tag.globals.send(name.to_sym) 226 | else 227 | target_name, method = name.split('.') 228 | target = tag.locals.send(target_name.to_sym) || tag.globals.send(target_name.to_sym) 229 | target.send(method.to_sym) if target 230 | end 231 | end if text 232 | result 233 | end 234 | 235 | # TODO open the tag ==> TagContext?? class 236 | def self.evaluate_tag_attribute(attribute_name, tag) 237 | result = nil 238 | source = tag.attr[attribute_name] 239 | if source 240 | source = expand_properties_in_tag(source, tag) 241 | begin 242 | local = tag.locals.instance_eval(source) 243 | rescue 244 | # log that they try to get a value that doesn't exist/can't be reached 245 | end 246 | begin 247 | global = tag.globals.instance_eval(source) 248 | rescue 249 | # same as above 250 | end 251 | 252 | if local 253 | result = local 254 | elsif global 255 | result = global 256 | end 257 | end 258 | result 259 | end 260 | end 261 | 262 | module Markaby 263 | 264 | def self.build(*args, &block) 265 | Markaby::Builder.new(*args, &block).to_s 266 | end 267 | 268 | class Builder 269 | 270 | def thtml(&block) 271 | tag!(:html, 272 | :xmlns => "http://www.w3.org/1999/xhtml", 273 | "xml:lang" => "en", 274 | :lang => "en", 275 | "xmlns:trellis" => "http://trellisframework.org/schema/trellis_1_0_0.xsd", &block) 276 | end 277 | 278 | def bluecloth(body) 279 | thtml { body { text "#{BlueCloth.new(body).to_html}" } } 280 | end 281 | 282 | def render_body 283 | text %[@!{@body}@] 284 | end 285 | 286 | end 287 | end 288 | 289 | -------------------------------------------------------------------------------- /lib/trellis/version.rb: -------------------------------------------------------------------------------- 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 | module Trellis 28 | module VERSION #:nodoc: 29 | MAJOR = 0 30 | MINOR = 1 31 | TINY = 1 32 | 33 | STRING = [MAJOR, MINOR, TINY].join('.') 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /script/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # File: script/console 3 | irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb' 4 | 5 | libs = " -r irb/completion" 6 | # Perhaps use a console_lib to store any extra methods I may want available in the cosole 7 | # libs << " -r #{File.dirname(__FILE__) + '/../lib/console_lib/console_logger.rb'}" 8 | libs << " -r #{File.dirname(__FILE__) + '/../lib/trellis.rb'}" 9 | puts "Loading trellis gem" 10 | exec "#{irb} #{libs} --simple-prompt" -------------------------------------------------------------------------------- /script/destroy: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..')) 3 | 4 | begin 5 | require 'rubigen' 6 | rescue LoadError 7 | require 'rubygems' 8 | require 'rubigen' 9 | end 10 | require 'rubigen/scripts/destroy' 11 | 12 | ARGV.shift if ['--help', '-h'].include?(ARGV[0]) 13 | RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit] 14 | RubiGen::Scripts::Destroy.new.run(ARGV) 15 | -------------------------------------------------------------------------------- /script/generate: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..')) 3 | 4 | begin 5 | require 'rubigen' 6 | rescue LoadError 7 | require 'rubygems' 8 | require 'rubigen' 9 | end 10 | require 'rubigen/scripts/generate' 11 | 12 | ARGV.shift if ['--help', '-h'].include?(ARGV[0]) 13 | RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit] 14 | RubiGen::Scripts::Generate.new.run(ARGV) 15 | -------------------------------------------------------------------------------- /script/txt2html: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | GEM_NAME = 'trellis' # what ppl will type to install your gem 4 | RUBYFORGE_PROJECT = 'trellis' 5 | 6 | require 'rubygems' 7 | begin 8 | require 'newgem' 9 | require 'rubyforge' 10 | rescue LoadError 11 | puts "\n\nGenerating the website requires the newgem RubyGem" 12 | puts "Install: gem install newgem\n\n" 13 | exit(1) 14 | end 15 | require 'redcloth' 16 | require 'syntax/convertors/html' 17 | require 'erb' 18 | require File.dirname(__FILE__) + "/../lib/#{GEM_NAME}/version.rb" 19 | 20 | version = Trellis::VERSION::STRING 21 | download = "http://rubyforge.org/projects/#{RUBYFORGE_PROJECT}" 22 | 23 | def rubyforge_project_id 24 | RubyForge.new.autoconfig["group_ids"][RUBYFORGE_PROJECT] 25 | end 26 | 27 | class Fixnum 28 | def ordinal 29 | # teens 30 | return 'th' if (10..19).include?(self % 100) 31 | # others 32 | case self % 10 33 | when 1: return 'st' 34 | when 2: return 'nd' 35 | when 3: return 'rd' 36 | else return 'th' 37 | end 38 | end 39 | end 40 | 41 | class Time 42 | def pretty 43 | return "#{mday}#{mday.ordinal} #{strftime('%B')} #{year}" 44 | end 45 | end 46 | 47 | def convert_syntax(syntax, source) 48 | return Syntax::Convertors::HTML.for_syntax(syntax).convert(source).gsub(%r!^
|
$!,'') 49 | end 50 | 51 | if ARGV.length >= 1 52 | src, template = ARGV 53 | template ||= File.join(File.dirname(__FILE__), '/../website/template.html.erb') 54 | else 55 | puts("Usage: #{File.split($0).last} source.txt [template.html.erb] > output.html") 56 | exit! 57 | end 58 | 59 | template = ERB.new(File.open(template).read) 60 | 61 | title = nil 62 | body = nil 63 | File.open(src) do |fsrc| 64 | title_text = fsrc.readline 65 | body_text_template = fsrc.read 66 | body_text = ERB.new(body_text_template).result(binding) 67 | syntax_items = [] 68 | body_text.gsub!(%r!<(pre|code)[^>]*?syntax=['"]([^'"]+)[^>]*>(.*?)!m){ 69 | ident = syntax_items.length 70 | element, syntax, source = $1, $2, $3 71 | syntax_items << "<#{element} class='syntax'>#{convert_syntax(syntax, source)}" 72 | "syntax-temp-#{ident}" 73 | } 74 | title = RedCloth.new(title_text).to_html.gsub(%r!<.*?>!,'').strip 75 | body = RedCloth.new(body_text).to_html 76 | body.gsub!(%r!(?:
)?syntax-temp-(\d+)(?:
)?!){ syntax_items[$1.to_i] } 77 | end 78 | stat = File.stat(src) 79 | created = stat.ctime 80 | modified = stat.mtime 81 | 82 | $stdout << template.result(binding) 83 | -------------------------------------------------------------------------------- /tasks/deployment.rake: -------------------------------------------------------------------------------- 1 | desc 'Release the website and new gem version' 2 | task :deploy => [:check_version, :website, :release] do 3 | puts "Remember to create SVN tag:" 4 | puts "svn copy svn+ssh://#{rubyforge_username}@rubyforge.org/var/svn/#{PATH}/trunk " + 5 | "svn+ssh://#{rubyforge_username}@rubyforge.org/var/svn/#{PATH}/tags/REL-#{VERS} " 6 | puts "Suggested comment:" 7 | puts "Tagging release #{CHANGES}" 8 | end 9 | 10 | desc 'Runs tasks website_generate and install_gem as a local deployment of the gem' 11 | task :local_deploy => [:website_generate, :install_gem] 12 | 13 | task :check_version do 14 | unless ENV['VERSION'] 15 | puts 'Must pass a VERSION=x.y.z release version' 16 | exit 17 | end 18 | unless ENV['VERSION'] == VERS 19 | puts "Please update your version.rb to match the release version, currently #{VERS}" 20 | exit 21 | end 22 | end 23 | 24 | desc 'Install the package as a gem, without generating documentation(ri/rdoc)' 25 | task :install_gem_no_doc => [:clean, :package] do 26 | sh "#{'sudo ' unless Hoe::WINDOZE }gem install pkg/*.gem --no-rdoc --no-ri" 27 | end 28 | 29 | namespace :manifest do 30 | desc 'Recreate Manifest.txt to include ALL files' 31 | task :refresh do 32 | `rake check_manifest | patch -p0 > Manifest.txt` 33 | end 34 | end -------------------------------------------------------------------------------- /tasks/environment.rake: -------------------------------------------------------------------------------- 1 | task :ruby_env do 2 | RUBY_APP = if RUBY_PLATFORM =~ /java/ 3 | "jruby" 4 | else 5 | "ruby" 6 | end unless defined? RUBY_APP 7 | end 8 | -------------------------------------------------------------------------------- /tasks/rspec.rake: -------------------------------------------------------------------------------- 1 | begin 2 | require 'spec' 3 | rescue LoadError 4 | require 'rubygems' 5 | require 'spec' 6 | end 7 | begin 8 | require 'spec/rake/spectask' 9 | rescue LoadError 10 | puts <<-EOS 11 | To use rspec for testing you must install rspec gem: 12 | gem install rspec 13 | EOS 14 | exit(0) 15 | end 16 | 17 | desc "Run the specs under spec" 18 | Spec::Rake::SpecTask.new do |t| 19 | t.spec_opts = ['--options', "test/spec.opts"] 20 | t.spec_files = FileList['test/**/*_spec.rb'] 21 | t.rcov = true 22 | end -------------------------------------------------------------------------------- /tasks/website.rake: -------------------------------------------------------------------------------- 1 | desc 'Generate website files' 2 | task :website_generate => :ruby_env do 3 | (Dir['website/**/*.txt'] - Dir['website/version*.txt']).each do |txt| 4 | sh %{ #{RUBY_APP} script/txt2html #{txt} > #{txt.gsub(/txt$/,'html')} } 5 | end 6 | end 7 | 8 | desc 'Upload website files to rubyforge' 9 | task :website_upload do 10 | host = "#{rubyforge_username}@rubyforge.org" 11 | remote_dir = "/var/www/gforge-projects/#{PATH}/" 12 | local_dir = 'website' 13 | sh %{rsync -aCv #{local_dir}/ #{host}:#{remote_dir}} 14 | end 15 | 16 | desc 'Generate and upload website files' 17 | task :website => [:website_generate, :website_upload, :publish_docs] 18 | -------------------------------------------------------------------------------- /test/application_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/spec_helper.rb' 2 | 3 | require "rack" 4 | require 'rack/test' 5 | require_fixtures 'application_fixtures' 6 | 7 | describe Trellis::Application, " when declared" do 8 | before do 9 | @homepage = TestApp::MyApp.instance_eval { @homepage } 10 | @pages = TestApp::MyApp.instance_eval { @pages } 11 | @static_routes = TestApp::MyApp.instance_eval { @static_routes } 12 | end 13 | 14 | it "should contain a contain the symbol for its home page" do 15 | @homepage.should == :home 16 | end 17 | 18 | it "should contain any declared static routes" do 19 | images_route = @static_routes.select { |item| item[:urls].include?('/images') } 20 | images_route.should_not be_empty 21 | style_route = @static_routes.select { |item| item[:urls].include?('/style') } 22 | style_route.should_not be_empty 23 | favicon_route = @static_routes.select { |item| item[:urls].include?('/favicon.ico') } 24 | favicon_route.should_not be_empty 25 | jquery_route = @static_routes.select { |item| item[:urls].include?('/jquery') && item[:root].include?('./js') } 26 | jquery_route.should_not be_empty 27 | end 28 | 29 | it "should include Rack::Utils" do 30 | TestApp::MyApp.included_modules.should include(Rack::Utils) 31 | end 32 | 33 | end 34 | 35 | describe Trellis::Application, " when requesting the root url with a GET" do 36 | include Rack::Test::Methods 37 | 38 | def app 39 | TestApp::MyApp.new 40 | end 41 | 42 | it "should return an OK HTTP status" do 43 | get "/" 44 | last_response.status.should be(200) 45 | end 46 | 47 | it "should reply with the home page" do 48 | get "/" 49 | last_response.body.should == %[\n\n \n

Hello World!

\n \n\n] 50 | end 51 | end 52 | 53 | describe Trellis::Application, " requesting a route" do 54 | include Rack::Test::Methods 55 | 56 | def app 57 | TestApp::MyApp.new 58 | end 59 | 60 | it "should return a 404 (not found) for an unmatch request" do 61 | get "/blowup" 62 | last_response.status.should be(404) 63 | end 64 | 65 | it "should return the page contents of the first page matching the route" do 66 | get "/whoa" 67 | last_response.body.should == "\n\n whoa!\n\n" 68 | end 69 | 70 | it "should support a single named parameter" do 71 | get "/hello/brian" 72 | last_response.body.should include("\n

Hello

\n \n brian\n ") 73 | get "/hello/anne" 74 | last_response.body.should include("\n

Hello

\n \n anne\n ") 75 | end 76 | 77 | it "should support multiple named parameters" do 78 | get '/report/2009/05/31' 79 | last_response.body.should include("

Report for

05/31/2009") 80 | end 81 | 82 | it "should support optional parameters" do 83 | get '/foobar/hello/world' 84 | last_response.body.should include("hello-world") 85 | get '/foobar/hello' 86 | last_response.body.should include("hello-") 87 | get '/foobar' 88 | last_response.body.should include("-") 89 | end 90 | 91 | it "should support a wildcard parameters" do 92 | get '/splat/goodbye/cruel/world' 93 | last_response.body.should include("goodbye/cruel/world") 94 | end 95 | 96 | it "should supports mixing multiple splats" do 97 | get '/splats/bar/foo/bling/baz/boom' 98 | last_response.body.should include("barblingbaz/boom") 99 | 100 | get '/splats/bar/foo/baz' 101 | last_response.status.should be(404) 102 | end 103 | 104 | it "should supports mixing named and wildcard params" do 105 | get '/mixed/afoo/bar/baz' 106 | last_response.body.should include("bar/baz-afoo") 107 | end 108 | 109 | it "should merge named params and query string params" do 110 | get "/hello/Bean?salutation=Mr.%20" 111 | last_response.body.should include("

Hello

\n Mr. \n Bean") 112 | end 113 | 114 | it "should match a dot ('.') as part of a named param" do 115 | get "/foobar/user@example.com/thebar" 116 | last_response.body.should include("user@example.com-thebar") 117 | end 118 | 119 | it "should match a literal dot ('.') outside of named params" do 120 | get "/downloads/logo.gif" 121 | last_response.body.should include("logo-gif") 122 | end 123 | 124 | it "should redirect to a custom route when handling an event returning a custom routed page" do 125 | post "/admin/login/events/submit.login" 126 | redirect = last_response.headers['Location'] 127 | redirect.should eql('/admin/result') 128 | get redirect 129 | last_response.body.should include('

PostRedirectPage

') 130 | end 131 | 132 | end 133 | 134 | describe Trellis::Application do 135 | include Rack::Test::Methods 136 | 137 | def app 138 | TestApp::MyApp.new 139 | end 140 | 141 | it "should have access to any persistent fields" do 142 | get "/application_data_page" 143 | last_response.body.should == "\n\n \n

\n \n\n" 144 | end 145 | 146 | it "should be able to modify any persistent fields" do 147 | env = Hash.new 148 | env["rack.session"] = Hash.new 149 | get "/application_data_page/events/save", {}, env 150 | redirect = last_response.headers['Location'] 151 | redirect.should eql('/application_data_page') 152 | get redirect, {}, env 153 | last_response.body.should == "\n\n \n

here's a value

\n \n\n" 154 | end 155 | 156 | it "should have access to any application public methods" do 157 | get "/application_method_page" 158 | last_response.body.should == "\n\n \n

Zaphod Beeblebrox

\n \n\n" 159 | end 160 | 161 | end 162 | 163 | describe Trellis::Application, " with declared partial views" do 164 | include Rack::Test::Methods 165 | 166 | def app 167 | TestApp::MyApp.new 168 | end 169 | 170 | it "should render a view defined in markaby" do 171 | get "/partial_markaby" 172 | last_response.body.should include("

This content was generated by Markaby

") 173 | end 174 | 175 | it "should render a view defined in haml" do 176 | get "/partial_haml" 177 | last_response.body.should include("

This content was generated by HAML

") 178 | end 179 | 180 | it "should render a view defined in textile" do 181 | get "/partial_textile" 182 | last_response.body.should include("

This content was generated by Textile

") 183 | end 184 | 185 | it "should render a view defined in markdown" do 186 | get "/partial_markdown" 187 | last_response.body.should include("

This content was generated by Markdown

") 188 | end 189 | 190 | it "should render a view defined in eruby" do 191 | get "/partial_eruby" 192 | last_response.body.should include("

This content was generated by The Amazing ERubis

") 193 | end 194 | 195 | it "should render a view defined in eruby and have access to the surrounding context" do 196 | get "/partial_eruby_loop" 197 | last_response.body.join.should include("
  • ichi
  • ni
  • san
  • shi
  • go
  • rokku
  • hichi
  • hachi
  • kyu
  • jyu
") 198 | end 199 | 200 | end 201 | 202 | describe Trellis::Application, " with declared layout" do 203 | include Rack::Test::Methods 204 | 205 | def app 206 | TestApp::MyApp.new 207 | end 208 | 209 | it "should render a page with its corresponding layout" do 210 | get "/with_layout_static" 211 | last_response.body.should include("

\n

Hello Arizona!

") 212 | end 213 | 214 | it "should render a page with its corresponding layout" do 215 | get "/with_layout_variable" 216 | last_response.body.should include("p>\n

Hello Arizona!

") 217 | end 218 | 219 | it "should render any embedded trellis components" do 220 | get "/markaby_template_with_components" 221 | last_response.body.should include("

Vulgar Vogons

") 222 | end 223 | 224 | it "should render and eruby template and layout" do 225 | get '/eruby_template_and_layout' 226 | last_response.body.join.should include("
  • one
  • two
  • tres
  • cuatro
") 227 | end 228 | end 229 | -------------------------------------------------------------------------------- /test/component_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/spec_helper.rb' 2 | 3 | require "rack" 4 | require 'rack/test' 5 | require_fixtures 'component_fixtures' 6 | 7 | describe Trellis::Component, " in an application" do 8 | include Rack::Test::Methods 9 | 10 | def app 11 | TestComponents::ApplicationWithComponents.new 12 | end 13 | 14 | it "should return its intended content" do 15 | get '/' 16 | last_response.body.should include("hello from simple component") 17 | end 18 | 19 | it "should render each instance of a component in the template" do 20 | get '/counters' 21 | counter_one_markup = %[] 22 | counter_two_markup = %[] 23 | counter_three_markup = %[
0++--1] 58 | counter_two_markup = %[
0] 59 | counter_three_markup = %[
0] 60 | 61 | last_response.body.should include(counter_one_markup) 62 | last_response.body.should include(counter_two_markup) 63 | last_response.body.should include(counter_three_markup) 64 | end 65 | 66 | it "should be able to provide a style link contribution to the page" do 67 | get '/page_with_contributions' 68 | last_response.body.should include(%[]) 69 | end 70 | 71 | it "should be able to provide a script link contribution to the page" do 72 | get '/page_with_contributions' 73 | last_response.body.should include(%[]) 89 | last_response.body.should include(%[]) 90 | end 91 | 92 | it "should be able to provide a script contribution per class" do 93 | get '/page_with_contributions' 94 | last_response.body.should include(%[alert('hello just once');]) 95 | end 96 | 97 | it "should be able to provide a dom modification block" do 98 | get '/page_with_contributions' 99 | last_response.body.scan(%[]).should have_exactly(1).match 100 | end 101 | 102 | end 103 | -------------------------------------------------------------------------------- /test/core_extensions_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/spec_helper.rb' 2 | 3 | # replace_ant_style_property 4 | describe String, " when calling replace_ant_style_property" do 5 | 6 | it "should return a string with the property replaced" do 7 | source = "Good ${greeting}" 8 | result = source.replace_ant_style_property("greeting", "Morning!") 9 | result.should eql("Good Morning!") 10 | end 11 | 12 | it "should return an unmodified string when the source doesn't contain the specified ant style property" do 13 | source = "Good Morning!" 14 | result = source.replace_ant_style_property("chunky", "bacon!") 15 | result.should eql("Good Morning!") 16 | end 17 | end 18 | 19 | # replace_ant_style_properties 20 | describe String, " when calling replace_ant_style_properties" do 21 | 22 | it "should return a string with any given properties replaced" do 23 | source = "Good ${greeting}, ${state_of_world} World" 24 | result = source.replace_ant_style_properties({"greeting" => "Bye!", "state_of_world" => "Cruel"}) 25 | result.should eql("Good Bye!, Cruel World") 26 | end 27 | 28 | it "should return an unmodified string when the source doesn't contain any of the specified ant style properties" do 29 | source = "Good Morning!" 30 | result = source.replace_ant_style_properties({"greeting" => "Bye!", "state_of_world" => "Cruel"}) 31 | result.should eql("Good Morning!") 32 | end 33 | end 34 | 35 | # underscore_class_name 36 | describe Class, " when calling underscore_class_name" do 37 | 38 | it "should return a valid underscore ruby identifier string" do 39 | class SomeClass; end 40 | sym = SomeClass.underscore_class_name 41 | sym.should eql("some_class") 42 | end 43 | 44 | it "should return a valid underscore ruby identifier string sans the module" do 45 | module Boo 46 | class SomeOtherClass; end; 47 | end 48 | sym = Boo::SomeOtherClass.underscore_class_name 49 | sym.should eql("some_other_class") 50 | end 51 | 52 | it "should return a valid underscore ruby identifier string for an annonymous class" do 53 | class TheParent; end 54 | TheParent.create_child "AnnonymousChild" 55 | sym = AnnonymousChild.underscore_class_name 56 | sym.should eql("annonymous_child") 57 | end 58 | end 59 | 60 | # class_to_sym 61 | describe Class, " when calling class_to_sym" do 62 | 63 | it "should return a valid underscore ruby identifier as a symbol" do 64 | class SomeClass; end 65 | sym = SomeClass.class_to_sym 66 | sym.should eql(:some_class) 67 | end 68 | 69 | it "should return a valid underscore ruby identifier as a symbol sans the module" do 70 | module Boo 71 | class SomeOtherClass; end; 72 | end 73 | sym = Boo::SomeOtherClass.class_to_sym 74 | sym.should eql(:some_other_class) 75 | end 76 | 77 | it "should return a valid underscore ruby identifier as a symbol for an annonymous class" do 78 | class TheParent; end 79 | TheParent.create_child "AnotherAnnonymousChild" 80 | sym = AnotherAnnonymousChild.class_to_sym 81 | sym.should eql(:another_annonymous_child) 82 | end 83 | end 84 | 85 | # attr_array tests 86 | describe "when calling attr_array", :shared => true do 87 | 88 | it "should create an empty array" do 89 | elements = @target.instance_eval { @elements } 90 | elements.should_not be_nil 91 | elements.should be_empty 92 | end 93 | end 94 | 95 | describe Class, " when calling attr_array with default options" do 96 | 97 | before :each do 98 | @target = Class.new 99 | @target.attr_array(:elements) 100 | end 101 | 102 | it_should_behave_like "when calling attr_array" 103 | 104 | it "should create an accessor method for the array" do 105 | @target.should respond_to(:elements) 106 | end 107 | 108 | end 109 | 110 | describe Class, " when calling attr_array with options {:create_accessor => false}" do 111 | 112 | before :each do 113 | @target = Class.new 114 | @target.attr_array(:elements, :create_accessor => false) 115 | end 116 | 117 | it_should_behave_like "when calling attr_array" 118 | 119 | it "should not create an accessor method for the array" do 120 | @target.should_not respond_to(:elements) 121 | end 122 | end 123 | 124 | describe Class do 125 | 126 | it "should add class attribute accessors for each symbol passed when calling class_attr_accessor" do 127 | class Foo; end 128 | Foo.class_attr_accessor(:bar) 129 | Foo.should respond_to(:bar) 130 | Foo.should respond_to(:bar=) 131 | end 132 | 133 | it "should add class attribute readers for each symbol passed when calling class_attr_reader" do 134 | class Bar; end 135 | Bar.class_attr_reader(:foo) 136 | Bar.should respond_to(:foo) 137 | Bar.should_not respond_to(:foo=) 138 | end 139 | 140 | it "should add class attribute readers for each symbol passed when calling class_attr_writer" do 141 | class FooBar; end 142 | FooBar.class_attr_writer(:bar) 143 | FooBar.should_not respond_to(:bar) 144 | FooBar.should respond_to(:bar=) 145 | end 146 | 147 | it "should add instance attribute accessors for each symbol passed when calling instance_attr_accessor" do 148 | class Snafu; end 149 | Snafu.instance_attr_accessor(:bar) 150 | instance = Snafu.new 151 | instance.should respond_to(:bar) 152 | instance.should respond_to(:bar=) 153 | end 154 | 155 | end 156 | 157 | describe Object do 158 | before do 159 | class Ini 160 | def mini 161 | "myni" 162 | end 163 | end 164 | @instance = Ini.new 165 | end 166 | 167 | it "should invoke a method if available when calling call_if_provided" do 168 | @instance.should respond_to(:call_if_provided) 169 | @instance.call_if_provided(:mini).should eql("myni") 170 | end 171 | 172 | it "should not invoke a method if not available when calling call_if_provided" do 173 | @instance.should respond_to(:call_if_provided) 174 | @instance.call_if_provided(:moe).should be_nil 175 | end 176 | end 177 | -------------------------------------------------------------------------------- /test/default_router_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/spec_helper.rb' 2 | 3 | describe Trellis::DefaultRouter, " when processing a request" do 4 | before do 5 | @regexp = Trellis::DefaultRouter::ROUTE_REGEX 6 | @router = Trellis::DefaultRouter.new 7 | end 8 | 9 | it "should extract the page" do 10 | value, source, event, destination = "/some_page".match(@regexp).to_a.reverse 11 | value.should be_nil 12 | source.should be_nil 13 | event.should be_nil 14 | destination.should_not be_nil 15 | destination.should == "some_page" 16 | end 17 | 18 | it "should extract the page and event" do 19 | value, source, event, destination = "/some_page/events/event".match(@regexp).to_a.reverse 20 | value.should be_nil 21 | source.should be_nil 22 | event.should == "event" 23 | destination.should_not be_nil 24 | destination.should == "some_page" 25 | end 26 | 27 | it "should extract the page, event and source" do 28 | value, source, event, destination = "/some_page/events/event.source".match(@regexp).to_a.reverse 29 | value.should be_nil 30 | source.should == "source" 31 | event.should == "event" 32 | destination.should_not be_nil 33 | destination.should == "some_page" 34 | end 35 | 36 | it "should extract the page, event and source and value" do 37 | value, source, event, destination = "/some_page/events/event.source/123".match(@regexp).to_a.reverse 38 | value.should == "123" 39 | source.should == "source" 40 | event.should == "event" 41 | destination.should_not be_nil 42 | destination.should == "some_page" 43 | end 44 | 45 | it "should extract the page, event and value" do 46 | value, source, event, destination = "/some_page/events/event/123".match(@regexp).to_a.reverse 47 | value.should == "123" 48 | source.should be_nil 49 | event.should == "event" 50 | destination.should_not be_nil 51 | destination.should == "some_page" 52 | end 53 | 54 | it "should build a path given page" do 55 | page = TestApp::Home.new 56 | path = Trellis::DefaultRouter.to_uri(:page => page) 57 | path.should == "/home" 58 | end 59 | 60 | it "should build a path given page and an event" do 61 | page = TestApp::Home.new 62 | path = Trellis::DefaultRouter.to_uri(:page => page, :event => 'event') 63 | path.should == "/home/events/event" 64 | end 65 | 66 | it "should build a path given page, an event and a source" do 67 | page = TestApp::Home.new 68 | path = Trellis::DefaultRouter.to_uri(:page => page, :event => 'event', :source => 'source') 69 | path.should == "/home/events/event.source" 70 | end 71 | 72 | it "should build a path given page, an event, a source and a value" do 73 | page = TestApp::Home.new 74 | path = Trellis::DefaultRouter.to_uri(:page => page, :event => 'event', :source => 'source', :value => 'value') 75 | path.should == "/home/events/event.source/value" 76 | end 77 | 78 | it "should match valid patterns" do 79 | page_request = mock('request', :path_info => '/page') 80 | page_event_request = mock('request', :path_info => '/page/events/event') 81 | page_event_source_request = mock('request', :path_info => '/page/events/event.source') 82 | page_event_value_request = mock('request', :path_info => '/page/events/event/value') 83 | page_event_source_value_request = mock('request', :path_info => '/page/events/event.source/value') 84 | 85 | @router.matches?(page_request).should be_true 86 | @router.matches?(page_event_request).should be_true 87 | @router.matches?(page_event_source_request).should be_true 88 | @router.matches?(page_event_value_request).should be_true 89 | @router.matches?(page_event_source_value_request).should be_true 90 | end 91 | end 92 | -------------------------------------------------------------------------------- /test/filters_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/spec_helper.rb' 2 | 3 | require "rack" 4 | require "rack/test" 5 | require_fixtures 'filters_fixtures' 6 | 7 | describe Trellis::Page, " with applied filters " do 8 | include Rack::Test::Methods 9 | 10 | before do 11 | @app = FiltersApp::FiltersApp.new 12 | end 13 | 14 | def app 15 | @app 16 | end 17 | 18 | it "should be redirected by an around filter to a specific destination" do 19 | get "/protected_one" 20 | redirect = last_response.headers['Location'] 21 | redirect.should == '/not_authorized' 22 | end 23 | 24 | it "should be allowed to render by an around filter" do 25 | @app.allow = true 26 | get "/protected_one" 27 | last_response.body.should == %[\n

protected one

\n] 28 | end 29 | 30 | it "should be redirected by an around filter to a specific destination when using the get method" do 31 | get "/protected_two" 32 | redirect = last_response.headers['Location'] 33 | redirect.should == '/not_authorized' 34 | end 35 | 36 | it "should be allowed to process the get method by an around filter" do 37 | @app.allow = true 38 | get "/protected_two" 39 | last_response.body.should == %[\n

protected two

\n] 40 | end 41 | 42 | it "should apply filters only to the specified methods" do 43 | get "/protected_three" 44 | last_response.body.should == %[\n

blah

\n] 45 | get "/protected_three/events/knock_knock" 46 | redirect = last_response.headers['Location'] 47 | redirect.should == '/not_authorized' 48 | end 49 | 50 | it "should allow to daisy chain filters" do 51 | @app.allow = true 52 | env = Hash.new 53 | env["rack.session"] = Hash.new 54 | get "/protected_three/events/knock_knock", {}, env 55 | redirect = last_response.headers['Location'] 56 | redirect.should eql('/protected_three') 57 | get redirect, {}, env 58 | last_response.body.should include("\n

?ereht s'ohw

\n") 59 | end 60 | 61 | end -------------------------------------------------------------------------------- /test/fixtures/application_fixtures.rb: -------------------------------------------------------------------------------- 1 | module TestApp 2 | class MyApp < Trellis::Application 3 | home :home 4 | persistent :my_field 5 | 6 | map_static ['/images', '/style', '/favicon.ico'] 7 | map_static ['/jquery'], "./js" 8 | 9 | MY_CONSTANT_1 = "it's just us, chickens!" 10 | 11 | def application_method 12 | "Zaphod Beeblebrox" 13 | end 14 | 15 | partial :partial_markaby do p "This content was generated by Markaby" end 16 | partial :partial_haml, %[%p This content was generated by HAML], :format => :haml 17 | partial :partial_textile, %[This content was generated by *Textile*], :format => :textile 18 | partial :partial_markdown, %[# This content was generated by Markdown], :format => :markdown 19 | partial :partial_eruby, %[

This content was generated by @{@my_variable}@

], :format => :eruby 20 | partial :loop_body, %[
  • @{@i}@
  • ], :format => :eruby 21 | 22 | layout :main, %[

    @!{@body}@

    ], :format => :eruby 23 | layout(:other) { thtml { body { render_body }}} 24 | end 25 | 26 | class Home < Trellis::Page 27 | pages :other 28 | 29 | template do html { body { h1 "Hello World!" }} end 30 | 31 | def on_event1 32 | self 33 | end 34 | 35 | def on_event2 36 | "just some text" 37 | end 38 | 39 | def on_event3 40 | @other 41 | end 42 | 43 | def on_event4(value) 44 | "the value is #{value}" 45 | end 46 | end 47 | 48 | class PageWithGetPlainText < Trellis::Page 49 | pages :other 50 | 51 | def get 52 | "some content" 53 | end 54 | end 55 | 56 | class PageWithGetRedirect < Trellis::Page 57 | pages :other 58 | 59 | def get 60 | @other 61 | end 62 | end 63 | 64 | class PageWithGetSame < Trellis::Page 65 | 66 | def get 67 | self 68 | end 69 | 70 | template do html { body { p "Vera, what has become of you?" }} end 71 | end 72 | 73 | class Other < Trellis::Page 74 | template do html { body { p "Goodbye Cruel World " }} end 75 | end 76 | 77 | class BeforeLoad < Trellis::Page 78 | attr_reader :some_value 79 | 80 | def before_load 81 | @some_value = "8675309" 82 | end 83 | 84 | template do thtml { body { text %[] }} end 85 | end 86 | 87 | class AfterLoad < Trellis::Page 88 | attr_reader :some_value 89 | 90 | def after_load 91 | @some_value = "chunky bacon!" 92 | end 93 | 94 | template do thtml { body { text %[] }} end 95 | end 96 | 97 | class BeforeRender < Trellis::Page 98 | attr_reader :some_value 99 | 100 | def before_render 101 | @some_value = "8675309" 102 | end 103 | 104 | template do thtml { body { text %[] }} end 105 | end 106 | 107 | class AfterRender < Trellis::Page 108 | 109 | def after_render 110 | @application.my_field = "changed in after_render method" 111 | end 112 | 113 | template do thtml { body { p { "hey!"} }} end 114 | end 115 | 116 | class RoutedDifferently < Trellis::Page 117 | route '/whoa' 118 | 119 | template do html { body { text %[whoa!] }} end 120 | end 121 | 122 | class RoutedDifferentlyWithAParam < Trellis::Page 123 | route '/hello/:name' 124 | 125 | template do 126 | thtml { 127 | body { 128 | h2 "Hello" 129 | text %[] 130 | } 131 | } 132 | end 133 | end 134 | 135 | class RoutedDifferentlyWithParams < Trellis::Page 136 | route '/report/:year/:month/:day' 137 | 138 | template do 139 | thtml { 140 | body { 141 | h2 "Report for" 142 | text %[] 143 | text '/' 144 | text %[] 145 | text '/' 146 | text %[] 147 | } 148 | } 149 | end 150 | end 151 | 152 | class RoutedWithOptionalParams < Trellis::Page 153 | route '/foobar/?:foo?/?:bar?' 154 | 155 | template do 156 | thtml { 157 | body { 158 | text %[] 159 | text '-' 160 | text %[] 161 | } 162 | } 163 | end 164 | end 165 | 166 | class RoutedWithSingleWildcard < Trellis::Page 167 | route '/splat/*' 168 | 169 | template do 170 | thtml { 171 | body { 172 | text %[] 173 | } 174 | } 175 | end 176 | end 177 | 178 | class RoutedWithMultipleWildcards < Trellis::Page 179 | route '/splats/*/foo/*/*' 180 | 181 | template do 182 | thtml { 183 | body { 184 | text %[] 185 | } 186 | } 187 | end 188 | end 189 | 190 | class RoutedWithMixedParams < Trellis::Page 191 | route '/mixed/:foo/*' 192 | 193 | template do 194 | thtml { 195 | body { 196 | text %[] 197 | text '-' 198 | text %[] 199 | } 200 | } 201 | end 202 | end 203 | 204 | class RoutedWithTwoParams < Trellis::Page 205 | route '/foobar/:foo/:bar' 206 | 207 | template do 208 | thtml { 209 | body { 210 | text %[] 211 | text '-' 212 | text %[] 213 | } 214 | } 215 | end 216 | end 217 | 218 | class RoutedWithImplicitDot < Trellis::Page 219 | route '/downloads/:file.:ext' 220 | 221 | template do 222 | thtml { 223 | body { 224 | text %[] 225 | text '-' 226 | text %[] 227 | } 228 | } 229 | end 230 | end 231 | 232 | class SamplePage < Trellis::Page 233 | attr_accessor :value 234 | template do thtml { body { text %[] }} end 235 | end 236 | 237 | class AnotherSamplePage < Trellis::Page 238 | template do thtml { body { text %[] }} end 239 | end 240 | 241 | class HamlPage < Trellis::Page 242 | template %[!!! XML 243 | !!! Strict 244 | %html{ :xmlns => "http://www.w3.org/1999/xhtml" } 245 | %head 246 | %meta{ :content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type" } 247 | %title 248 | This is a HAML page 249 | %body 250 | %h1 251 | Page Title 252 | %p 253 | HAML rocks!], :format => :haml 254 | end 255 | 256 | class TextilePage < Trellis::Page 257 | template %[A *simple* example.], :format => :textile 258 | end 259 | 260 | class MarkDownPage < Trellis::Page 261 | template %[# This is the Title\n## This is the SubTitle\n**This is some text**], :format => :markdown 262 | end 263 | 264 | class HTMLPage < Trellis::Page 265 | template "

    This is just HTML

    " 266 | end 267 | 268 | class ERubyPage < Trellis::Page 269 | attr_reader :list 270 | 271 | def initialize 272 | self 273 | @list = ["Hey", "bud", "let's", "party!"] 274 | end 275 | 276 | template %[
    • @{item}@
    ], :format => :eruby 277 | end 278 | 279 | class ConstantAccessPage < Trellis::Page 280 | def greet 281 | "helloooo, la la la" 282 | end 283 | 284 | template %[

    @{TestApp::MyApp::MY_CONSTANT_1}@

    ], :format => :eruby 285 | end 286 | 287 | class MethodAccessPage < Trellis::Page 288 | def greet 289 | "helloooo, la la la" 290 | end 291 | 292 | template %[

    @{greet}@

    ], :format => :eruby 293 | end 294 | 295 | class ApplicationDataPage < Trellis::Page 296 | 297 | def on_save 298 | @application.my_field = "here's a value" 299 | self 300 | end 301 | 302 | template %[

    @{@application.my_field}@

    ], :format => :eruby 303 | end 304 | 305 | class ApplicationMethodPage < Trellis::Page 306 | template %[

    @{application_method()}@

    ], :format => :eruby 307 | end 308 | 309 | class PageWithGetAndExplicitRedirect < Trellis::Page 310 | route '/explicit_redirect' 311 | 312 | def get 313 | redirect "/hello/Ford%20Prefect" 314 | end 315 | end 316 | 317 | class PartialMarkabyPage < Trellis::Page 318 | route '/partial_markaby' 319 | template %[@!{render_partial(:partial_markaby)}@], :format => :eruby 320 | end 321 | 322 | class PartialHamlPage < Trellis::Page 323 | route '/partial_haml' 324 | template %[@!{render_partial(:partial_haml)}@], :format => :eruby 325 | end 326 | 327 | class PartialTextilePage < Trellis::Page 328 | route '/partial_textile' 329 | template %[@!{render_partial(:partial_textile)}@], :format => :eruby 330 | end 331 | 332 | class PartialMarkdownPage < Trellis::Page 333 | route '/partial_markdown' 334 | template %[@!{render_partial(:partial_markdown)}@], :format => :eruby 335 | end 336 | 337 | class PartialErubyPage < Trellis::Page 338 | route '/partial_eruby' 339 | 340 | def get 341 | @my_variable = 'The Amazing ERubis' 342 | self 343 | end 344 | 345 | template %[@!{render_partial(:partial_eruby)}@], :format => :eruby 346 | end 347 | 348 | class PartialErubyLoopPage < Trellis::Page 349 | route '/partial_eruby_loop' 350 | 351 | def get 352 | @elements = ['ichi', 'ni', 'san', 'shi', 'go', 'rokku', 'hichi', 'hachi', 'kyu', 'jyu'] 353 | self 354 | end 355 | 356 | template %[ 357 | 358 |
      359 | 360 | @!{ render_partial(:loop_body, {:i, element}) }@ 361 | 362 |
    363 | 364 | ], :format => :eruby 365 | end 366 | 367 | class PageWithLayoutStatic < Trellis::Page 368 | route '/with_layout_static' 369 | template %[

    Hello Arizona!

    ], :format => :eruby, :layout => :main 370 | end 371 | 372 | class PageWithLayoutDynamic < Trellis::Page 373 | route '/with_layout_variable' 374 | 375 | def get 376 | @my_value = "Hello Arizona!" 377 | self 378 | end 379 | 380 | template %[

    @{@my_value}@

    ], :format => :eruby, :layout => :main 381 | end 382 | 383 | class MarkabyTemplateWithComponents < Trellis::Page 384 | attr_reader :some_value 385 | 386 | def get 387 | @some_value = "Vulgar Vogons" 388 | self 389 | end 390 | 391 | template(nil, :layout => :other) do p { text %[] } end 392 | end 393 | 394 | class ErubyTemplateAndLayout < Trellis::Page 395 | def get 396 | @elements = ['one', 'two', 'tres', 'cuatro'] 397 | self 398 | end 399 | 400 | template %[ 401 |
      402 | 403 | @!{ render_partial(:loop_body, {:i, element}) }@ 404 | 405 |
    406 | ], :format => :eruby, :layout => :main 407 | end 408 | 409 | class PostOriginPage < Trellis::Page 410 | route '/admin/login' 411 | pages :post_redirect_page 412 | 413 | def on_submit_from_login 414 | redirect '/admin/result' 415 | end 416 | 417 | template %[

    PostOriginPage

    ] 418 | end 419 | 420 | class PostRedirectPage < Trellis::Page 421 | route '/admin/result' 422 | template %[

    PostRedirectPage

    ] 423 | end 424 | 425 | end 426 | -------------------------------------------------------------------------------- /test/fixtures/component_fixtures.rb: -------------------------------------------------------------------------------- 1 | module TestComponents 2 | 3 | class SimpleComponent < Trellis::Component 4 | tag_name "simple_component" 5 | 6 | render do |tag| 7 | "hello from simple component" 8 | end 9 | end 10 | 11 | class Counter < Trellis::Component 12 | is_stateful 13 | 14 | tag_name "counter" 15 | 16 | field :value, :persistent => true 17 | 18 | def initialize 19 | reset 20 | end 21 | 22 | render do |tag| 23 | tid = tag.attr['tid'] 24 | page = tag.globals.page 25 | counter = page.send("counter_#{tid}") 26 | value = counter.value 27 | href_add = Trellis::DefaultRouter.to_uri(:page => page.class.name, 28 | :event => 'add', 29 | :source => "counter_#{tid}") 30 | href_subtract = Trellis::DefaultRouter.to_uri(:page => page.class.name, 31 | :event => 'subtract', 32 | :source => "counter_#{tid}") 33 | builder = Builder::XmlMarkup.new 34 | builder.div(:id => tid) { 35 | builder.text!(value.to_s) 36 | builder.a("++", :href => href_add) 37 | builder.a("--", :href => href_subtract) 38 | } 39 | end 40 | 41 | def on_add 42 | @value = @value + 1 43 | end 44 | 45 | def on_subtract 46 | @value = @value - 1 47 | end 48 | 49 | def reset 50 | @value = 0 51 | end 52 | end 53 | 54 | class Contributions < Trellis::Component 55 | #tag_name "contributions" 56 | 57 | # ----------------------- 58 | # component contributions 59 | # ----------------------- 60 | page_contribution :style_link, "/someplace/my_styles.css" 61 | page_contribution :script_link, "/someplace/my_script.js" 62 | page_contribution :style, %[html { color:#555555; background-color:#303030; }], :scope => :class 63 | page_contribution :style, %[/* just a comment */], :scope => :instance 64 | page_contribution :script, %[alert('hello from ${tid}');], :scope => :instance 65 | page_contribution :script, %[alert('hello just once');], :scope => :class 66 | 67 | page_contribution(:dom) { 68 | at_css("body")['class'] = 'new_class' 69 | } 70 | 71 | render do |tag| 72 | "hear ye, hear ye!" 73 | end 74 | end 75 | 76 | class ApplicationWithComponents < Trellis::Application 77 | home :page_with_simple_component 78 | end 79 | 80 | class PageWithSimpleComponent < Trellis::Page 81 | template do thtml { body { text %[] }} end 82 | end 83 | 84 | class PageWithStatefulComponent < Trellis::Page 85 | route '/counters' 86 | 87 | template do 88 | thtml { 89 | body { 90 | text %[ 91 | 92 |
    93 | 94 |
    95 | 96 | ] 97 | } 98 | } 99 | end 100 | end 101 | 102 | class PageWithContributions < Trellis::Page 103 | template do 104 | thtml { 105 | head { 106 | title "counters" 107 | } 108 | body { 109 | text %[] 110 | } 111 | } 112 | end 113 | end 114 | end 115 | -------------------------------------------------------------------------------- /test/fixtures/filters_fixtures.rb: -------------------------------------------------------------------------------- 1 | module FiltersApp 2 | class FiltersApp < Trellis::Application 3 | attr_accessor :allow 4 | home :protected_one 5 | 6 | filter :authorized?, :around do |page, &block| 7 | page.application.allow ? block.call : page.redirect("/not_authorized") 8 | end 9 | 10 | filter :capitalize, :after do |page| 11 | page.answer = page.answer.reverse 12 | end 13 | end 14 | 15 | class ProtectedOne < Trellis::Page 16 | apply_filter :authorized?, :to => :all 17 | template %[

    protected one

    ], :format => :html 18 | end 19 | 20 | class ProtectedTwo < Trellis::Page 21 | apply_filter :authorized?, :to => :all 22 | def get; self; end 23 | template %[

    protected two

    ], :format => :html 24 | end 25 | 26 | class ProtectedThree < Trellis::Page 27 | persistent :answer 28 | apply_filter :authorized?, :to => :on_knock_knock 29 | apply_filter :capitalize, :to => :on_knock_knock 30 | 31 | def initialize; @answer = "blah"; end 32 | 33 | def on_knock_knock 34 | @answer = "who's there?" 35 | self 36 | end 37 | 38 | template %[

    @{@answer}@

    ], :format => :eruby 39 | end 40 | 41 | class NotAuthorized < Trellis::Page 42 | template %[not authorized], :format => :html 43 | end 44 | end -------------------------------------------------------------------------------- /test/page_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/spec_helper.rb' 2 | 3 | require "rack" 4 | require "rack/test" 5 | require_fixtures 'application_fixtures' 6 | 7 | describe Trellis::Page, " when sending an event to a page" do 8 | include Rack::Test::Methods 9 | 10 | def app 11 | TestApp::MyApp.new 12 | end 13 | 14 | it "should redirect to the receiving page if the event handler returns self" do 15 | get "/home/events/event1" 16 | redirect = last_response.headers['Location'] 17 | redirect.should eql('/home') 18 | end 19 | 20 | it "should return a response as a string if the event handler returns a String" do 21 | get "/home/events/event2" 22 | last_response.body.should == "just some text" 23 | end 24 | 25 | it "should redirect to the injected page as a response if the event handler returns an injected page" do 26 | get "/home/events/event3" 27 | redirect = last_response.headers['Location'] 28 | redirect.should eql('/other') 29 | end 30 | 31 | it "should be able to pass a value as the last element or the URL" do 32 | get "/home/events/event4/quo%20vadis" 33 | last_response.body.should == "the value is quo vadis" 34 | end 35 | 36 | end 37 | 38 | describe Trellis::Page, " when calling inject_dependent_pages on an instance of Page" do 39 | it "should contain instances of the injected pages" do 40 | homepage = TestApp::Home.new 41 | homepage.inject_dependent_pages 42 | injected_page = homepage.instance_eval { @other } 43 | injected_page.should be_an_instance_of(TestApp::Other) 44 | end 45 | end 46 | 47 | describe Trellis::Page, " when created with a custom route" do 48 | it "should contain an instance of Router" do 49 | router = TestApp::RoutedDifferently.router 50 | router.should_not be_nil 51 | router.should be_an_instance_of(Trellis::Router) 52 | end 53 | end 54 | 55 | describe Trellis::Page, " when provided with a #get method" do 56 | include Rack::Test::Methods 57 | 58 | def app 59 | TestApp::MyApp.new 60 | end 61 | 62 | it "should redirect to the page returned" do 63 | get "/page_with_get_redirect" 64 | redirect = last_response.headers['Location'] 65 | redirect.should eql('/other') 66 | end 67 | 68 | it "should return a response as a string if the ==get== methood returns a String" do 69 | get "/page_with_get_plain_text" 70 | last_response.body.should == "some content" 71 | end 72 | 73 | it "should render the result of the ==get== method if it is the same page" do 74 | get "/page_with_get_same" 75 | last_response.body.should include("

    Vera, what has become of you?

    ") 76 | end 77 | end 78 | 79 | describe Trellis::Page, " when given a template" do 80 | include Rack::Test::Methods 81 | 82 | def app 83 | TestApp::MyApp.new 84 | end 85 | 86 | it "should rendered it correctly if it is in HAML format" do 87 | get "/haml_page" 88 | last_response.body.should include(%[\n This is a HAML page\n \n \n \n

    \n Page Title\n

    \n

    \n HAML rocks!\n

    ]) 89 | end 90 | 91 | it "should rendered it correctly if it is in Textile format" do 92 | get "/textile_page" 93 | last_response.body.should == "\n

    A simple example.

    \n" 94 | end 95 | 96 | it "should rendered it correctly if it is in Markdown format" do 97 | get "/mark_down_page" 98 | last_response.body.should include(%[body>

    This is the Title

    \n\n

    This is the SubTitle

    \n\n

    This is some text]) 99 | end 100 | 101 | it "should rendered it correctly if it is in ERuby format" do 102 | get "/e_ruby_page" 103 | last_response.body.should include("

  • Hey
  • ") 104 | last_response.body.should include("
  • bud
  • ") 105 | last_response.body.should include("
  • let's
  • ") 106 | last_response.body.should include("
  • party!
  • ") 107 | end 108 | 109 | it "should rendered it correctly if it is in HTML format" do 110 | get "/html_page" 111 | last_response.body.should include("

    This is just HTML

    ") 112 | end 113 | end 114 | 115 | describe Trellis::Page do 116 | include Rack::Test::Methods 117 | 118 | before do 119 | @application = TestApp::MyApp.new 120 | end 121 | 122 | def app 123 | @application 124 | end 125 | 126 | it "should have access to application constants in ERuby format" do 127 | get "/constant_access_page" 128 | last_response.body.should include("

    it's just us, chickens!

    ") 129 | end 130 | 131 | it "should have access to application methods in ERuby format" do 132 | get "/method_access_page" 133 | last_response.body.should include("

    helloooo, la la la

    ") 134 | end 135 | 136 | it "should invoke the before_load method if provided by the page" do 137 | get "/before_load" 138 | last_response.body.should include("8675309") 139 | end 140 | 141 | it "should invoke the after_load method if provided by the page" do 142 | get "/after_load" 143 | last_response.body.should include("chunky bacon!") 144 | end 145 | 146 | it "should invoke the before_render method if provided by the page" do 147 | get "/before_render" 148 | last_response.body.should include("8675309") 149 | end 150 | 151 | it "should invoke the after_render method if provided by the page" do 152 | env = Hash.new 153 | env["rack.session"] = Hash.new 154 | get "/after_render", {}, env 155 | env["rack.session"][:my_field].should include("changed in after_render method") 156 | end 157 | 158 | it "should redirect if giving an explicit redirect" do 159 | get '/explicit_redirect' 160 | redirect = last_response.headers['Location'] 161 | redirect.should eql('/hello/Ford%20Prefect') 162 | get redirect 163 | last_response.body.should include("

    Hello

    \n \n Ford%20Prefect") 164 | end 165 | end 166 | 167 | -------------------------------------------------------------------------------- /test/renderer_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/spec_helper.rb' 2 | 3 | require "rack" 4 | require_fixtures 'application_fixtures' 5 | 6 | describe Trellis::Renderer do 7 | 8 | it "should render a given page template" do 9 | page = TestApp::Home.new 10 | renderer = Trellis::Renderer.new(page) 11 | result = renderer.render 12 | result.should == "\n\n \n

    Hello World!

    \n \n\n" 13 | end 14 | 15 | it "should have access to page instance variables" do 16 | page = TestApp::SamplePage.new 17 | page.value = "chunky bacon" 18 | renderer = Trellis::Renderer.new(page) 19 | result = renderer.render 20 | result.should include("\n chunky bacon\n ") 21 | end 22 | 23 | it "should have access to the page name" do 24 | page = TestApp::AnotherSamplePage.new 25 | renderer = Trellis::Renderer.new(page) 26 | result = renderer.render 27 | result.should include("\n TestApp::AnotherSamplePage\n ") 28 | end 29 | 30 | end 31 | 32 | -------------------------------------------------------------------------------- /test/router_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/spec_helper.rb' 2 | 3 | require_fixtures 'application_fixtures' 4 | 5 | describe Trellis::Router, " when constructed" do 6 | 7 | it "with a page parameter should route to that page " do 8 | @router = Trellis::Router.new(:page => TestApp::Home) 9 | @router.route().destination.should == TestApp::Home 10 | end 11 | 12 | it "with a simple path it should match that path" do 13 | request = mock('request', :path_info => '/hello') 14 | @router = Trellis::Router.new(:path => '/hello') 15 | @router.matches?(request).should be_true 16 | end 17 | 18 | it "with a path containing a single named parameter should match valid variations of that path" do 19 | request_brian = mock('request', :path_info => '/hello/brian') 20 | request_michael = mock('request', :path_info => '/hello/michael') 21 | @router = Trellis::Router.new(:path => '/hello/:name') 22 | @router.matches?(request_brian).should be_true 23 | @router.matches?(request_michael).should be_true 24 | end 25 | 26 | it "with a path containing multiple named parameters should match valid variations of that path" do 27 | request_xmas = mock('request', :path_info => '/hello/2009/12/25') 28 | request_labor_day = mock('request', :path_info => '/hello/2009/09/07') 29 | @router = Trellis::Router.new(:path => '/hello/:year/:month/:day') 30 | @router.matches?(request_xmas).should be_true 31 | @router.matches?(request_labor_day).should be_true 32 | end 33 | 34 | it "with a path containing a single named parameters should collect the parameter" do 35 | @router = Trellis::Router.new(:path => '/hello/:name') 36 | @router.keys.should include('name') 37 | end 38 | 39 | it "with a path containing multiple parameters should collect all parameters" do 40 | @router = Trellis::Router.new(:path => '/hello/:year/:month/:day') 41 | @router.keys.should include('year', 'month', 'day') 42 | end 43 | 44 | it "with a path containing multiple optional parameters should collect all parameters" do 45 | @router = Trellis::Router.new(:path => '/?:foo?/?:bar?') 46 | @router.keys.should include('foo', 'bar') 47 | end 48 | 49 | it "with a path containing a single wildcard param it should capture the values in splat" do 50 | @router = Trellis::Router.new(:path => '/*') 51 | @router.keys.should include('splat') 52 | end 53 | 54 | it "should be able to be sorted by 'matchability'" do 55 | route_1 = Trellis::Router.new(:path => '/hello/:one/:two') 56 | route_2 = Trellis::Router.new(:path => '/hello/jim/:last') 57 | route_3 = Trellis::Router.new(:path => '/hello/*') 58 | route_4 = Trellis::Router.new(:path => '/hello/*/:foo/*') 59 | 60 | routes = [ route_1, route_2, route_3, route_4 ].sort {|a,b| b.score <=> a.score } 61 | 62 | routes[0].should be(route_4) 63 | routes[1].should be(route_2) 64 | routes[2].should be(route_1) 65 | routes[3].should be(route_3) 66 | end 67 | 68 | it "a catch all route should always be last when sorted with other routes" do 69 | route_1 = Trellis::Router.new(:path => '/admin/login') 70 | route_2 = Trellis::Router.new(:path => '/*') 71 | route_3 = Trellis::Router.new(:path => '/admin/pages') 72 | route_4 = Trellis::Router.new(:path => '/admin/new/page') 73 | route_5 = Trellis::Router.new(:path => '/admin/page/:id') 74 | route_6 = Trellis::Router.new(:path => '/admin/page/:id/delete') 75 | 76 | routes = [ route_1, route_2, route_3, route_4, route_5, route_6 ].sort {|a,b| b.score <=> a.score } 77 | 78 | routes[0].should be(route_6) 79 | routes[1].should be(route_4) 80 | routes[2].should be(route_5) 81 | routes[3].should be(route_3) 82 | routes[4].should be(route_1) 83 | routes[5].should be(route_2) 84 | end 85 | 86 | end 87 | -------------------------------------------------------------------------------- /test/spec.opts: -------------------------------------------------------------------------------- 1 | --colour -------------------------------------------------------------------------------- /test/spec_helper.rb: -------------------------------------------------------------------------------- 1 | begin 2 | require 'rubygems' 3 | require 'spec' 4 | rescue LoadError 5 | require 'rubygems' 6 | gem 'rspec' 7 | require 'spec' 8 | end 9 | 10 | $:.unshift(File.dirname(__FILE__) + '/../lib') 11 | require 'trellis' 12 | 13 | FIXTURES = File.expand_path(File.join(File.dirname(__FILE__), 'fixtures')) unless defined?(FIXTURES) 14 | def require_fixtures(path) 15 | require File.expand_path(File.join(FIXTURES, path)) 16 | end --------------------------------------------------------------------------------