|
| 1 | +--- |
| 2 | +layout: blog |
| 3 | +title: "Meteor UI Pattern: Keeping App State on the URL" |
| 4 | +category: blog |
| 5 | +summery: "In this article, we talk about the importance of keeping app state in the URL. Then, we show few ways to do that for your Meteor app." |
| 6 | +--- |
| 7 | + |
| 8 | +Traditionally in the web, the URL is the key component and everything is built around it. Everyone understands the concept of the URL and how it behaves. |
| 9 | + |
| 10 | +If you’ve forgotten what the URL does, here it is :) |
| 11 | + |
| 12 | +> A URL identifies a unique page or some content on the web. Even on a private app like Gmail, the URL plays the same role. |
| 13 | +
|
| 14 | +## URL and Single Page Apps (SPAs) |
| 15 | + |
| 16 | +After the success of single page apps, it was a bit complex to handle and maintain the URL as content changed. With SPAs, we route and display different views inside the browser. That means that we change the state of the app without reloading the page. |
| 17 | + |
| 18 | +Because of that, some developers have forgotten to maintain the state in the URL and have created a unique URL for every view. That's where client side routers and standards like "[push state](http://diveintohtml5.info/history.html)" help us to maintain the state of the app inside the URL easily. |
| 19 | + |
| 20 | +## Meteor and URLs |
| 21 | + |
| 22 | +In this article, I will show you few ways to use URLs to manage the state of Meteor apps. If you think carefully, a URL is a global state manager that just works. I will show you some examples as we go that are based on the [flow router](https://github.com/meteorhacks/flow-router). |
| 23 | + |
| 24 | +Let's discover a few patterns: |
| 25 | + |
| 26 | +### 1. Pages |
| 27 | + |
| 28 | +This is a basic use of the router and the classic use case. It's a set of pages with a unique URL. See the following demo: |
| 29 | + |
| 30 | +<iframe width="640" height="480" src="https://www.youtube.com/embed/pAfiHah2e7E" frameborder="0" allowfullscreen="1"> |
| 31 | +</iframe> |
| 32 | + |
| 33 | +Here's the code of our router: |
| 34 | + |
| 35 | +~~~js |
| 36 | +FlowRouter.route('/:section/:lesson', { |
| 37 | + action: function() { |
| 38 | + FlowLayout.render("myapp"); |
| 39 | + } |
| 40 | +}); |
| 41 | +~~~ |
| 42 | + |
| 43 | +This is the code for templates. |
| 44 | +(Here we directly render templates using flow layout. In a real app, you would have a complex UI arrangement.) |
| 45 | + |
| 46 | +**Blaze Template** |
| 47 | +<script src="https://gist.github.com/arunoda/74a9641220d5020bf566.js"></script> |
| 48 | + |
| 49 | +**Template Helpers** |
| 50 | + |
| 51 | +~~~js |
| 52 | +Template.myapp.helpers({ |
| 53 | + pageContent: function() { |
| 54 | + var section = FlowRouter.getParam("section"); |
| 55 | + var lesson = FlowRouter.getParam("lesson"); |
| 56 | + |
| 57 | + return Posts.findOne({section: section, lesson: lesson}); |
| 58 | + } |
| 59 | +}); |
| 60 | +~~~ |
| 61 | + |
| 62 | +### 2. Single View, Dynamic Content |
| 63 | + |
| 64 | +Here we don't change the path of the app, but the content is changing dynamically. One good example is [Kadira](https://kadira.io/)'s date-time navigation. |
| 65 | + |
| 66 | +See: |
| 67 | + |
| 68 | +<iframe width="640" height="480" src="https://www.youtube.com/embed/wGcLYrU1Vg0" frameborder="0" allowfullscreen="1"> |
| 69 | +</iframe> |
| 70 | + |
| 71 | +In the above example, you are looking at the same set of charts, but the data is changing as you navigate. At any time, you can copy the URL and use it as a permalink to the charts you see on the screen. Here we use a query param to keep the date in the URL. |
| 72 | + |
| 73 | +This is what happens when the user clicks the prev button: |
| 74 | + |
| 75 | +~~~js |
| 76 | +Template.dateNavigation.event({ |
| 77 | + "click #prev": function() { |
| 78 | + var currentDate = FlowRouter.getQueryParam('date'); |
| 79 | + var prevDate = TimeUtils.getPrevDate(currentDate); |
| 80 | + FlowRouter.setQueryParams({date: prevDate}); |
| 81 | + } |
| 82 | +}); |
| 83 | +~~~ |
| 84 | + |
| 85 | +This will change the query string `date` to the new one. Here's our template for one of the charts in the screen: |
| 86 | + |
| 87 | +~~~js |
| 88 | +Template.cpuChart.onCreated(function() { |
| 89 | + var instance = Template.instance(); |
| 90 | + instance.chartData = new ReactiveVar(); |
| 91 | + instance.autorun(function() { |
| 92 | + var currentDate = FlowRouter.getQueryParam('date'); |
| 93 | + Meteor.call('getCPUChartData', function(err, data) { |
| 94 | + instance.chartData.set(data); |
| 95 | + }); |
| 96 | + }); |
| 97 | +}); |
| 98 | + |
| 99 | +Template.cpuChart.helpers({ |
| 100 | + chartData: function() { |
| 101 | + return Template.instance().chartData.get(); |
| 102 | + } |
| 103 | +}); |
| 104 | +~~~ |
| 105 | + |
| 106 | +It's watching the query params and getting the data according to the current query param. Here, the flow router's API plays a huge role in efficiently notifying the change of the query param. |
| 107 | + |
| 108 | +### 3. Opening Modals |
| 109 | + |
| 110 | +This is also very similar to the previous pattern, but we are using it for a different purpose. Watch the following video: |
| 111 | + |
| 112 | +<iframe width="640" height="480" src="https://www.youtube.com/embed/PDqvvGqo3CY" frameborder="0" allowfullscreen="1"> |
| 113 | +</iframe> |
| 114 | + |
| 115 | +When we click on the alerts button, it will open up a new modal window. Rather than opening the modal when the event fires, we set a new query param in the URL, as shown below: |
| 116 | + |
| 117 | +~~~js |
| 118 | +Template.dateNavigation.event({ |
| 119 | + "click #alertsButton": function() { |
| 120 | + FlowRouter.setQueryParams({action: "alerts"}); |
| 121 | + } |
| 122 | +}); |
| 123 | +~~~ |
| 124 | + |
| 125 | +Then we reactively watch that query param inside an autorun. If that query param exists, we'll show the popup; once it’s closed, we'll remove the query param. |
| 126 | + |
| 127 | +~~~js |
| 128 | +Template.appView.onRendered(function() { |
| 129 | + var instance = Template.instance(); |
| 130 | + instance.autorun(function() { |
| 131 | + var action = FlowRouter.getQueryParam("action"); |
| 132 | + if(action == "alerts") { |
| 133 | + var alertsModal = $('#alertsModal'); |
| 134 | + alertsModal.modal("show"); |
| 135 | + alertsModal.one("hidden.bs.modal", function() { |
| 136 | + // to remove the action query param |
| 137 | + FlowRouter.setQueryParams({action: null}); |
| 138 | + }); |
| 139 | + } |
| 140 | + }); |
| 141 | +}); |
| 142 | +~~~ |
| 143 | + |
| 144 | +By using this method, we can reload the page and still be able to see the modal window. |
| 145 | + |
| 146 | +## I Think You Get the Idea |
| 147 | + |
| 148 | +Using URLs, we can easily decouple the actions and states of our app. Most importantly, you can persist states inside the URL. As a result of that, states of your app now become portable. This means that users can copy the URL and share it with others or bookmark it for later use. |
| 149 | + |
| 150 | +### Exceptions |
| 151 | + |
| 152 | +Even though keeping the app state in the URL is very important, it's not a must. One good example is [Google Inbox](https://inbox.google.com). Watch the following video: |
| 153 | + |
| 154 | +<iframe width="640" height="480" src="https://www.youtube.com/embed/qo1tdCxy13Q" frameborder="0" allowfullscreen="1"> |
| 155 | +</iframe> |
| 156 | + |
| 157 | +Google Inbox does not change the URL when we click on an email. This is not a bug: they did it on purpose. |
| 158 | + |
| 159 | +In Google Inbox, you can mark emails as done or snooze them. Then, they will be removed from the UI. So, providing a unique URL for each individual email is confusing, because emails could disappear from the app at any time. |
| 160 | + |
| 161 | +Facebook's timeline is another example of this scenario. |
| 162 | + |
| 163 | +### React Flux vs. the URL |
| 164 | + |
| 165 | +[React Flux](https://facebook.github.io/flux/docs/overview.html) introduces us a global state manager. I believe they just re-implemented the concept behind the URL. |
| 166 | + |
| 167 | +You could easily achieve flux's features by keeping the state in the router. Flow Router helps to achieve this with its fast and carefully designed reactive API. |
| 168 | + |
| 169 | +## Try Flow Router |
| 170 | + |
| 171 | +I have mentioned flow router in a couple of places in this article. [Flow Router](https://github.com/meteorhacks/flow-router) was just an experiment at MeteorHacks, but it turned into a huge success. Now we are working hard on version 2.0. Version 2.0 basically introduces new APIs and shows more patterns to solve common problems like changing routes based on the user login status. |
| 172 | + |
| 173 | +The current version is also very stable; it is used on [BulletProof Meteor](https://bulletproofmeteor.com) and is used in the next version of [Kadira](https://kadira.io). |
| 174 | + |
| 175 | +If you haven't tried it yet, [try it](https://github.com/meteorhacks/flow-router) today. It's very simple and easy to use. |
0 commit comments