|
| 1 | +A Single Page Application (SPA) usually is loaded once and handles all user |
| 2 | +interactions dynamically by calling backend APIs. This gives the user the |
| 3 | +ofted desired "app-feeling". Rails View Layer only offers the static request/response |
| 4 | +mode to render content. matestack fixes that without adding the complexity a |
| 5 | +SPA written in JavaScript usually brings with it. matestacks app instance simply |
| 6 | +performs dynamic transitions between pages. You only have to follow the basic |
| 7 | +structure to get this running: |
| 8 | + |
| 9 | +## 1. Setup your matestack pages |
| 10 | + |
| 11 | +First of all we need at least two matestack pages: |
| 12 | + |
| 13 | +Your routes: |
| 14 | + |
| 15 | +config/routes.rb |
| 16 | + |
| 17 | +```ruby |
| 18 | +Rails.application.routes.draw do |
| 19 | + get '/page1', to:'my_app#first_page', as: 'first_page' |
| 20 | + get '/page2', to:'my_app#second_page', as: 'second_page' |
| 21 | +end |
| 22 | +``` |
| 23 | + |
| 24 | +Your controller actions: |
| 25 | + |
| 26 | +`app/controllers/my_app_controller.rb` |
| 27 | + |
| 28 | +```ruby |
| 29 | +class MyAppController < ApplicationController |
| 30 | + |
| 31 | + include Matestack::Ui::Core::ApplicationHelper |
| 32 | + |
| 33 | + def first_page |
| 34 | + responder_for(Pages::MyApp::MyExamplePage) |
| 35 | + end |
| 36 | + |
| 37 | + def second_page |
| 38 | + responder_for(Pages::MyApp::MySecondExamplePage) |
| 39 | + end |
| 40 | + |
| 41 | +end |
| 42 | +``` |
| 43 | + |
| 44 | +*Note: As you see, controller action names don't have to match the page class name.* |
| 45 | + |
| 46 | +Now we define our matestack pages: |
| 47 | + |
| 48 | +`app/matestack/pages/my_app/my_example_page.rb` |
| 49 | + |
| 50 | +```ruby |
| 51 | +class Pages::MyApp::MyExamplePage < Matestack::Ui::Page |
| 52 | + |
| 53 | + def response |
| 54 | + components { |
| 55 | + div do |
| 56 | + span do |
| 57 | + plain "This is Page 1!" |
| 58 | + end |
| 59 | + end |
| 60 | + } |
| 61 | + end |
| 62 | + |
| 63 | +end |
| 64 | +``` |
| 65 | + |
| 66 | +and |
| 67 | + |
| 68 | +`app/matestack/pages/my_app/my_second_example_page.rb` |
| 69 | + |
| 70 | +```ruby |
| 71 | +class Pages::MyApp::MySecondExamplePage < Matestack::Ui::Page |
| 72 | + |
| 73 | + def response |
| 74 | + components { |
| 75 | + div do |
| 76 | + span do |
| 77 | + plain "This is Page 2!" |
| 78 | + end |
| 79 | + end |
| 80 | + } |
| 81 | + end |
| 82 | + |
| 83 | +end |
| 84 | +``` |
| 85 | + |
| 86 | +*Note: the page class name has to match your file name and the apps name has to match the namespace of your matestack page* |
| 87 | + |
| 88 | +- Pages::**MyApp**::MyExamplePage --> `app/matestack/pages/my_app/my_example_page.rb` |
| 89 | +- Pages::**MyApp**::MySecondExamplePage `app/matestack/pages/my_app/my_second_example_page.rb` |
| 90 | + |
| 91 | +## 2. Add transition links to your app |
| 92 | + |
| 93 | +Now we have to add transition links to your matestack app. |
| 94 | + |
| 95 | +`app/matestack/apps/my_app.rb` |
| 96 | + |
| 97 | +```ruby |
| 98 | +class Apps::MyApp < Matestack::Ui::App |
| 99 | + |
| 100 | + def response |
| 101 | + components { |
| 102 | + header do |
| 103 | + heading size: 1, text: "My App" |
| 104 | + end |
| 105 | + |
| 106 | + nav do |
| 107 | + |
| 108 | + # perform a dynamic transition to a different page |
| 109 | + # the links get the class "active" when their path is active |
| 110 | + transition path: :first_page_path, text: "First Page", class: "nav-link" |
| 111 | + |
| 112 | + # or with a button for example |
| 113 | + transition path: :second_page_path, class: "nav-link" do |
| 114 | + button do |
| 115 | + plain "Second Page" |
| 116 | + end |
| 117 | + end |
| 118 | + |
| 119 | + end |
| 120 | + |
| 121 | + main do |
| 122 | + page_content |
| 123 | + end |
| 124 | + } |
| 125 | + end |
| 126 | + |
| 127 | +end |
| 128 | +``` |
| 129 | + |
| 130 | +Clicking on the transition links will perform dynamic transition and change the |
| 131 | +page_content without a full page reload. |
| 132 | + |
| 133 | +You can see this in action when navigating through this guides. The links on the |
| 134 | +sidebar of these docs are transition components. |
| 135 | + |
| 136 | +## Recap |
| 137 | + |
| 138 | +We've implemented a dynamic SPA with a few lines of Ruby. No JavaScript was required. |
| 139 | +On the next section, we will learn how to add some custom UI Sugar in order to perform |
| 140 | +a smooth page transition! |
| 141 | + |
| 142 | +### Handle Page Transition Events |
| 143 | + |
| 144 | +On the previous section we learned, how to implement dynamic page transitions in pure |
| 145 | +Ruby. matestack wants to cover the basic dynamic behavior of your Web-App with |
| 146 | +minimum complexity. You should be able to add custom UI Goodies on top of this |
| 147 | +solid structure though. To give you maximum flexibility, you can use classic JavaScript |
| 148 | +and a Vue.js EventBus for that. |
| 149 | + |
| 150 | +We now want to add a nice loading transition when the user navigates between pages. |
| 151 | + |
| 152 | +matestack emits vue.js events on specific UI interactions. If you receive these events, |
| 153 | +you can do all kinds of DOM-Manipulations. **Let's see, how |
| 154 | +page transition effects as on this documentation app are implemented, for example.** |
| 155 | + |
| 156 | +(We're using Material Design Light, you could use whatever you want) |
| 157 | + |
| 158 | +`app/matestack/apps/my_app.rb` |
| 159 | + |
| 160 | +```ruby |
| 161 | +class Apps::MyApp < Matestack::Ui::App |
| 162 | + |
| 163 | + def response |
| 164 | + components { |
| 165 | + #... |
| 166 | + main do |
| 167 | + div class: "loading" do |
| 168 | + div id: "spinner", class: "mdl-spinner mdl-js-spinner is-active" |
| 169 | + end |
| 170 | + div id: "page_content" do |
| 171 | + page_content |
| 172 | + end |
| 173 | + partial :alert_bar #optional |
| 174 | + end |
| 175 | + #... |
| 176 | + } |
| 177 | + end |
| 178 | + |
| 179 | + #optional |
| 180 | + def alert_bar |
| 181 | + partial { |
| 182 | + div id: "alert_bar", class: "mdl-js-snackbar mdl-snackbar mdl-snackbar--alert" do |
| 183 | + div class: "mdl-snackbar__text" |
| 184 | + div class: "mdl-snackbar__action" |
| 185 | + end |
| 186 | + } |
| 187 | + end |
| 188 | + |
| 189 | +end |
| 190 | +``` |
| 191 | + |
| 192 | +Now let's add some custom JavaScript behavior (just pick what you want to use on your project and adapt to your DOM): |
| 193 | + |
| 194 | +**Note 1: All the DOM Manipulations below may depend on your custom DOM structure/CSS Framework** |
| 195 | + |
| 196 | +**Note 2: In v0.7.3 we aditionally will offer a component handling most of these effects, making it easier to apply a nice page transition to your component with less JS** |
| 197 | + |
| 198 | +`app/assets/javascripts/application.js` |
| 199 | + |
| 200 | +```javascript |
| 201 | +//Transition Start - DOM Manipulation (depends on your DOM/CSS Framework!) |
| 202 | +MatestackUiCore.matestackEventHub.$on('page_loading', function(url){ |
| 203 | + //hide old content |
| 204 | + document.querySelector('#page_content').style.opacity = 0; |
| 205 | + setTimeout(function () { |
| 206 | + //show loading spinner |
| 207 | + document.querySelector('#spinner').style.display = "inline-block"; |
| 208 | + }, 300); |
| 209 | +}); |
| 210 | + |
| 211 | +//Transition End - DOM Manipulation (depends on your DOM/CSS Framework!) |
| 212 | +MatestackUiCore.matestackEventHub.$on('page_loaded', function(url){ |
| 213 | + setTimeout(function () { |
| 214 | + //hide loading spinner |
| 215 | + document.querySelector('#spinner').style.display = "none"; |
| 216 | + //show new content |
| 217 | + document.querySelector('#page_content').style.opacity = 1; |
| 218 | + }, 500); |
| 219 | +}); |
| 220 | + |
| 221 | +//Transition End Scroll Top (depends on your DOM/CSS Framework!) |
| 222 | +MatestackUiCore.matestackEventHub.$on('page_loaded', function(url){ |
| 223 | + //scroll top in order to display new content at top position |
| 224 | + document.getElementsByTagName("main")[0].scrollTop = 0 |
| 225 | +}) |
| 226 | + |
| 227 | +//Transition End Material Snackbar API Call (depends on your DOM/CSS Framework!) |
| 228 | +MatestackUiCore.matestackEventHub.$on('page_loaded', function(url){ |
| 229 | + setTimeout(function () { |
| 230 | + var noticebarContainer = document.querySelector('#notice_bar'); |
| 231 | + var data = {message: 'loaded: ' + url, timeout: 1500}; |
| 232 | + noticebarContainer.MaterialSnackbar.showSnackbar(data) |
| 233 | + }, 500); |
| 234 | +}) |
| 235 | + |
| 236 | +//Transition Error DOM Manipulation and Material Snackbar API Call (depends on your DOM/CSS Framework!) |
| 237 | +MatestackUiCore.matestackEventHub.$on('page_loading_error', function(error){ |
| 238 | + setTimeout(function () { |
| 239 | + //hide loading spinner |
| 240 | + document.querySelector('#spinner').style.display = "none"; |
| 241 | + //show old content again |
| 242 | + document.querySelector('#page_content').style.opacity = 1; |
| 243 | + //call Material Snackbar API |
| 244 | + var alertbarContainer = document.querySelector('#alert_bar'); |
| 245 | + var data = {message: 'error loading: ' + error.config.url, timeout: 3000}; |
| 246 | + alertbarContainer.MaterialSnackbar.showSnackbar(data) |
| 247 | + }, 500); |
| 248 | +}) |
| 249 | + |
| 250 | + |
| 251 | +``` |
| 252 | + |
| 253 | +and a some CSS: |
| 254 | + |
| 255 | +`app/assets/stylesheets/application.css` |
| 256 | + |
| 257 | +```css |
| 258 | +#page_content{ |
| 259 | + transition:opacity 0.2s linear; |
| 260 | +} |
| 261 | +#spinner { |
| 262 | + display: none; //init hide |
| 263 | +} |
| 264 | +``` |
0 commit comments