hyper-router 1.0.alpha1.5 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA256:
3
- metadata.gz: 6ea9c2324b2513fc2c1e52621e49f5e78cebaa1d42cc5bec7f4b1acc82793b0f
4
- data.tar.gz: 04dab8146079a46ce5f3d9b9912f960f06de8ee07f77933375f9287f1b91f18f
2
+ SHA1:
3
+ metadata.gz: 42539f6d1b24ec189cee24a5fa1bef54ab185b20
4
+ data.tar.gz: 7cde99e5959c6c002100afc45f37c1bee5296e8f
5
5
  SHA512:
6
- metadata.gz: 03272c353f3f21cd38c437aa130b0c0bd4ecd1f750868c3643ff4851323895c4bb1657e20bcb40a5e4ca9e8d38c27afa2e379660740994d2e62d5bd4b673b2ff
7
- data.tar.gz: 545c37f9186f8d24400d70edd0e8fb36878ecdd5707645f13f231d9f3576ff9e7299afb4521d3e5a2efad141132539a6b2725439f53124a9850d409a074d6038
6
+ metadata.gz: ee4d5bee2df7fed2d2ee91cb622cd639105d2e5990f3b81ac9045192e4499a6ca41cc37dfbd7744ce9a660de536b7ca8ac4c62dea32681a3c777ea0933edaa03
7
+ data.tar.gz: 94fea592a8cb1bee3c186dc5ce3d3acfd14b556bc456e355d56e257807cafb9766bb36e1a3cf4673484a13fcd71e21a0b5a39baf0153a1fe5438b8d5bcf4616f
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 catprintlabs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
@@ -0,0 +1,393 @@
1
+ ## HyperRouter
2
+
3
+ HyperRouter allows you write and use the React Router in Ruby through Opal.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'hyper-router'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ ## Usage
18
+
19
+ This is simply a DSL wrapper on [react-router](....)
20
+
21
+ ### DSL
22
+
23
+ The following DSL:
24
+
25
+ ```ruby
26
+ route("/", mounts: App, index: Home) do
27
+ route("about")
28
+ route("inbox") do
29
+ redirect('messages/:id').to { | params | "/messages/#{params[:id]}" }
30
+ end
31
+ route(mounts: Inbox) do
32
+ route("messages/:id")
33
+ end
34
+ end
35
+ ```
36
+
37
+ Is equivalent to this route configuration:
38
+
39
+ ```javascript
40
+ const routes = {
41
+ path: '/',
42
+ component: App,
43
+ indexRoute: { component: Dashboard },
44
+ childRoutes: [
45
+ { path: 'about', component: About },
46
+ {
47
+ path: 'inbox',
48
+ component: Inbox,
49
+ childRoutes: [{
50
+ path: 'messages/:id',
51
+ onEnter: ({ params }, replace) => replace(`/messages/${params.id}`)
52
+ }]
53
+ },
54
+ {
55
+ component: Inbox,
56
+ childRoutes: [{
57
+ path: 'messages/:id', component: Message
58
+ }]
59
+ }
60
+ ]
61
+ }
62
+ ```
63
+
64
+ The basic dsl syntax is designed with the following in mind:
65
+
66
+ 1. Most routes have a path so that is the assumed first argument.
67
+ 2. Use `mounts` rather than component (reads better?)
68
+ 3. Convention over configuration, given a path, the component name can be derived.
69
+ 4. Redirect takes the path, and a block (similar to the JSX DSL)
70
+ 5. The first param to route can be skipped per the documentation
71
+ 6. Use standard ruby lower case method names instead of caps (reserve those for components)
72
+
73
+ The above example does not cover all the possible syntax, here are the other methods and options:
74
+
75
+ #### enter / leave / change transition hooks
76
+
77
+ for adding an onEnter or onLeave hook you would say:
78
+
79
+ ```ruby
80
+ route("foo").on(:leave) { | t | ... }.on(:enter) { | t |.... }
81
+ ```
82
+ which follows the react.rb event handler convention.
83
+
84
+ A `TransitionContext` object will be passed to the handler, which has the following methods:
85
+
86
+ | method | available on | description |
87
+ |-----------|------------------|-----------------|
88
+ | `next_state` | `:change`, `:enter` | returns the next state object |
89
+ | `prev_state` | `:change` | returns the previous state object |
90
+ | `replace` | `:change`, `:enter` | pass `replace` a new path |
91
+ | `promise` | `:change`, `:enter` | returns a new promise. multiple calls returns the same promise |
92
+
93
+ If you return a promise from the `:change` or `:enter` hooks, the transition will wait till the promise is resolved before proceeding. For simplicity you can call the promise method, but you can also use some other method to define the promise.
94
+
95
+ The hooks can also be specified as proc values to the `:on_leave`, `:on_enter`, `:on_change` options.
96
+
97
+ #### multiple component mounting
98
+
99
+ The `mounts` option can accept a single component, or a hash which will generate a `components` (plural) react-router prop, as in:
100
+
101
+ `route("groups", mounts: {main: Groups, sidebar: GroupsSidebar})` which is equivalent to:
102
+
103
+ `{path: "groups", components: {main: Groups, sidebar: GroupsSidebar}}` (json) or
104
+
105
+ `<Route path="groups" components={{main: Groups, sidebar: GroupsSidebar}} />` JSX
106
+
107
+ #### The `mounts` option can also take a `Proc` or be specified as a block
108
+
109
+ The proc is passed a TransitionContext (see **Hooks** above) and may either return a react component to be mounted, or return a promise. If a promise is returned the transition will wait till the promise is either resolved with a component, or rejected.
110
+
111
+ `route("courses/:courseId", mounts: -> () { Course }`
112
+
113
+ is the same as:
114
+
115
+ ```jsx
116
+ <Route path="courses/:courseId" getComponent={(nextState, cb) => {cb(null, Course)}} />
117
+ ```
118
+
119
+ Also instead of a proc, a block can be specified with the `mounts` method:
120
+
121
+ `route("courses/:courseId").mounts { Course }`
122
+
123
+ Which generates the same route as the above.
124
+
125
+ More interesting would be something like this:
126
+
127
+ ```ruby
128
+ route("courses/:id").mounts do | ct |
129
+ HTTP.get("validate-user-access/courses/#{ct.next_state[:id]}").then { Course }
130
+ end
131
+ ```
132
+
133
+ *Note that the above works because of promise chaining.*
134
+
135
+ You can use the `mount` method multiple times with different arguments as an alternative to passing the the `mount` option a hash:
136
+
137
+ `route("foo").mount(:baz) { Comp1 }.mount(:bar) { Comp2 }.mount(:bomb)`
138
+
139
+ Note that if no block is given (as in `:bomb` above) the component name will be inferred from the argument (`Bomb` in this case.)
140
+
141
+ #### The index component can be specified as a proc
142
+
143
+ Same deal as mount...
144
+
145
+ `route("foo", index: -> { MyIndex })`
146
+
147
+ #### The index method
148
+
149
+ Instead of specifying the index component as a param to the parent route, it can be specified as a child using the
150
+ index method:
151
+
152
+ ```ruby
153
+ route("/", mounts: About, index: Home) do
154
+ index(mounts: MyIndex)
155
+ route("about")
156
+ route("privacy-policy")
157
+ end
158
+ ```
159
+
160
+ This is useful because the index method has all the features of a route except that it does not take a path or children.
161
+
162
+ #### The `redirect` options
163
+
164
+ with static arguments:
165
+
166
+ `redirect("/from/path/spec", to: "/to/path/spec", query: {q1: 123, q2: :abc})`
167
+
168
+ the `:to` and `:query` options can be Procs which will receive the current state.
169
+
170
+ Or you can specify the `:to` an `:query` options with blocks:
171
+
172
+ `redirect("/from/path/spec/:id").to { |curr_state| "/to/path/spec/#{current_state[:id]}"}.query { {q1: 12} }`
173
+
174
+ #### The `index_redirect` method
175
+
176
+ just like `redirect` without the first arg: `index_redirect(to: ... query: ...)`
177
+
178
+ ### The Router Component
179
+
180
+ A router is defined as a subclass of `React::Router` which is itself a `React::Component::Base`.
181
+
182
+ ```ruby
183
+ class Components::Router < React::Router
184
+ def routes # define your routes (there is no render method)
185
+ route("/", mounts: About, index: Home) do
186
+ route("about")
187
+ route("inbox") do
188
+ redirect('messages/:id').to { | params | "/messages/#{params[:id]}" }
189
+ end
190
+ route(mounts: Inbox) do
191
+ route("messages/:id")
192
+ end
193
+ end
194
+ end
195
+ end
196
+ ```
197
+ #### Mounting your Router
198
+ You will mount this component the usual way (i.e. via `render_component`, `Element#render`, `react_render`, etc) or even by mounting it within a higher level application component.
199
+
200
+ ```ruby
201
+ class Components::App < React::Component::Base
202
+ render(DIV) do
203
+ Application::Nav()
204
+ MAIN do
205
+ Router()
206
+ end
207
+ end
208
+ end
209
+ ```
210
+
211
+ #### navigating
212
+
213
+ Create links to your routes with `Router::Link`
214
+ ```ruby
215
+ #Application::Nav
216
+ LI.nav_link { TestRouter::Link("/") { "Home" } }
217
+ LI.nav_link { TestRouter::Link("/about") { "About" } }
218
+ params.messsages.each do |msg|
219
+ LI.nav_link { TestRouter::Link("/inbox/messages/#{msg.id}") { msg.title } }
220
+ end
221
+ ```
222
+
223
+ Additionally, you can manipulate the history with by passing JS as so
224
+ ```ruby
225
+ # app/views/components/app_links.rb
226
+ class Components::AppLinks
227
+ class << self
228
+ if RUBY_ENGINE == 'opal'
229
+ def inbox
230
+ `window.ReactRouter.browserHistory.push('/inbox');`
231
+ end
232
+ def message(id)
233
+ `window.ReactRouter.browserHistory.push('/messages/#{id}');`
234
+ end
235
+ end
236
+ end
237
+ end
238
+ ```
239
+
240
+
241
+ #### Other router hooks:
242
+
243
+ There are several other methods that can be redefined to modify the routers behavior
244
+
245
+ #### history
246
+
247
+ ```ruby
248
+ class Router < React::Router
249
+ def history
250
+ ... return a history object
251
+ end
252
+ end
253
+ ```
254
+
255
+ The two standard history objects are predefined as `browser_history` and `hash_history` so you can say:
256
+
257
+ ```ruby
258
+ ...
259
+ def history
260
+ browser_history
261
+ end
262
+ ```
263
+
264
+ or just
265
+
266
+ ```ruby
267
+ ...
268
+ alias_method :history :browser_history
269
+ ```
270
+
271
+ #### create_element
272
+
273
+ `create_element` (if defined) is passed the component that the router will render, and its params. Use it to intercept, inspect and/or modify the component behavior.
274
+
275
+ `create_element` can return any of these values:
276
+
277
+ + Any falsy value: indicating that rendering should continue with no modification to behavior.
278
+ + A `React::Element`, or a native `React.Element` which will be used for rendering.
279
+ + Any truthy value: indicating that a new Element should be created using the (probably modified) params
280
+
281
+ ```ruby
282
+ class Router < React::Router
283
+ def create_element(component, component_params)
284
+ # add the param :time_stamp to each element as its rendered
285
+ React.create_element(component, component_params.merge(time_stamp: Time.now))
286
+ end
287
+ end
288
+ ```
289
+
290
+ The above could be simplified to:
291
+
292
+ ```ruby
293
+ ...
294
+ def create_element(component, component_params)
295
+ component_params[:time_stamp] = Time.now
296
+ end
297
+ ```
298
+
299
+ Just make sure that you return a truthy value otherwise it will ignore any changes to component or params.
300
+
301
+ Or if you just wanted some kind of logging:
302
+
303
+ ```ruby
304
+ ...
305
+ def create_element(component, component_params)
306
+ puts "[#{Time.now}] Rendering: #{component.name}" # puts returns nil, so we are jake mate
307
+ end
308
+ ```
309
+
310
+ The component_params will always contain the following keys as native js objects, and they must stay native js objects:
311
+
312
+ + `children`
313
+ + `history`
314
+ + `location`
315
+ + `params`
316
+ + `route`
317
+ + `route_params`
318
+ + `routes`
319
+
320
+ We will try to get more fancy with a later version of reactrb-router ;-)
321
+
322
+ #### `stringify_query(params_hash)` <- needs work
323
+
324
+ The method used to convert an object from <Link>s or calls to transitionTo to a URL query string.
325
+
326
+ ```ruby
327
+ class Router < React::Router
328
+ def stringify_query(params_hash)
329
+ # who knows doc is a little unclear on this one...is it being passed the full params_hash or just
330
+ # the query portion.... we shall see...
331
+ end
332
+ end
333
+ ```
334
+
335
+ #### `parse_query_string(string)` <- needs work
336
+
337
+ The method used to convert a query string into the route components's param hash
338
+
339
+ #### `on_error(data)`
340
+
341
+ While the router is matching, errors may bubble up, here is your opportunity to catch and deal with them. Typically these will come when promises are rejected (see the DSL above for returning promises to handle async behaviors.)
342
+
343
+ #### `on_update`
344
+
345
+ Called whenever the router updates its state in response to URL changes.
346
+
347
+ #### `render`
348
+
349
+ A `Router` default `render` looks like this:
350
+
351
+ ```ruby
352
+ def render
353
+ # Router.router renders the native router component
354
+ Router.router(build_params)
355
+ end
356
+ ```
357
+
358
+
359
+ This is primarily for integrating with other libraries that need to participate in rendering before the route components are rendered. It defaults to render={(props) => <RouterContext {...props} />}.
360
+
361
+ Ensure that you render a <RouterContext> at the end of the line, passing all the props passed to render.
362
+
363
+ ### React::Router::Component
364
+
365
+ The class React::Router::Component is a subclass of React::Component::Base that predefines the params that the router will be passing in to your component. This includes
366
+
367
+ `params.location`
368
+
369
+ The current location.
370
+
371
+ `params.params`
372
+
373
+ The dynamic segments of the URL.
374
+
375
+ `params.route`
376
+
377
+ The route that rendered this component.
378
+
379
+ `params.route_params`
380
+
381
+ The subset of `params.params` that were directly specified in this component's route. For example, if the route's path is `users/:user_id` and the URL is /users/123/portfolios/345 then `params.route_params` will be `{user_id: '123'}`, and `params.params` will be `{user_id: '123', portfolio_id: 345}`.
382
+
383
+ ## Development
384
+
385
+ `bundle exec rake` runs test suite
386
+
387
+ ## Contributing
388
+
389
+ 1. Fork it ( https://github.com/ruby-hyperloop/reactrb-router/tree/hyper-router/fork )
390
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
391
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
392
+ 4. Push to the branch (`git push origin my-new-feature`)
393
+ 5. Create a new Pull Request
data/Rakefile CHANGED
@@ -1,11 +1,32 @@
1
1
  require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
3
2
 
4
- RSpec::Core::RakeTask.new(:spec)
3
+ require 'bundler'
4
+ Bundler.require
5
+ Bundler::GemHelper.install_tasks
5
6
 
6
- namespace :spec do
7
- task :prepare do
8
- end
7
+
8
+ require 'rspec/core/rake_task'
9
+ require 'opal/rspec/rake_task'
10
+
11
+ RSpec::Core::RakeTask.new('ruby:rspec') do |s|
12
+ s.rspec_opts = "--tag ruby"
13
+ end
14
+ Opal::RSpec::RakeTask.new('opal:rspec') do |s|
15
+ s.append_path 'spec/vendor'
16
+ s.index_path = 'spec/index.html.erb'
17
+ end
18
+
19
+ task :test do
20
+ Rake::Task['ruby:rspec'].invoke
21
+ Rake::Task['opal:rspec'].invoke
22
+ end
23
+
24
+ require 'generators/reactive_ruby/test_app/test_app_generator'
25
+ desc "Generates a dummy app for testing"
26
+ task :test_app do
27
+ ReactiveRuby::TestAppGenerator.start
28
+ puts "Setting up test app database..."
29
+ system("bundle exec rake db:drop db:create db:migrate > #{File::NULL}")
9
30
  end
10
31
 
11
- task :default => :spec
32
+ task default: [ :test ]