ruta 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6a8777ca0158cf75be951622603e8213369e4d41
4
+ data.tar.gz: fa3f3d3ce7a1713c25c9928b1493a5a4ed1e7299
5
+ SHA512:
6
+ metadata.gz: ffcd6159486425e1d7f7d827d30fc5767dab0e5a4d9a1ddb8e151f00cc2f259f503f07568d957690c8970ffaed9a97bc6f49dbc53a92dec366f55825e239214f
7
+ data.tar.gz: acd9e9a34d23d88e0cb44bbd525c0917ef50773d6c59e07035855d0e4bbe7817e9f4c17293ff367a4524fdf690491f4e8e50f3418c716ee3005823cce69377d6
data/.gitignore ADDED
@@ -0,0 +1,61 @@
1
+ # Compiled source #
2
+ ###################
3
+ *target
4
+ *.war
5
+ *.ear
6
+ *.com
7
+ *.class
8
+ *.dll
9
+ *.exe
10
+ *.o
11
+ *.so
12
+
13
+ # Packages #
14
+ ############
15
+ # it's better to unpack these files and commit the raw source
16
+ # git has its own built in compression methods
17
+ *.7z
18
+ *.dmg
19
+ *.gz
20
+ *.iso
21
+ *.jar
22
+ *.rar
23
+ *.tar
24
+ *.zip
25
+ *.gem
26
+
27
+ # Logs and databases #
28
+ ######################
29
+ *.log
30
+ *.sql
31
+ *.sqlite
32
+
33
+ # OS generated files #
34
+ ######################
35
+ **/.DS_Store
36
+ **/.DS_Store?
37
+ **/._*
38
+ **/.Spotlight-V100
39
+ **/.Trashes
40
+ **/ehthumbs.db
41
+ **/Thumbs.db
42
+ **/desktop.ini
43
+
44
+ # text/IDE editor generated files #
45
+ ###################################
46
+ **/*~
47
+ **/*#
48
+ **/*.pyo
49
+ **/*.pyc
50
+ **/*.bak
51
+ **/*.swp
52
+ **/.idea
53
+
54
+ # misc files #
55
+ ######################
56
+ #any supporting credentials used during testings goes in a .creds file but is loaded like any other ruby file
57
+ **/*.creds
58
+ #sass cache folder
59
+ .sass-cache
60
+ #files generated by git diff
61
+ **/.orig
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.3
4
+ before_install: gem install bundler -v 1.10.6
@@ -0,0 +1,13 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
4
+
5
+ We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion.
6
+
7
+ Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
8
+
9
+ Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
10
+
11
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
12
+
13
+ This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in ruta.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,39 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ ruta (0.1.0)
5
+ opal (~> 0.8)
6
+ opal-browser (~> 0.2)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ concurrent-ruby (1.0.0)
12
+ hike (1.2.3)
13
+ opal (0.8.1)
14
+ hike (~> 1.2)
15
+ sourcemap (~> 0.1.0)
16
+ sprockets (~> 3.1)
17
+ tilt (>= 1.4)
18
+ opal-browser (0.2.0)
19
+ opal
20
+ paggio
21
+ paggio (0.2.5)
22
+ rack (1.6.4)
23
+ rake (10.4.2)
24
+ sourcemap (0.1.1)
25
+ sprockets (3.5.2)
26
+ concurrent-ruby (~> 1.0)
27
+ rack (> 1, < 3)
28
+ tilt (2.0.1)
29
+
30
+ PLATFORMS
31
+ ruby
32
+
33
+ DEPENDENCIES
34
+ bundler (~> 1.10)
35
+ rake (~> 10.0)
36
+ ruta!
37
+
38
+ BUNDLED WITH
39
+ 1.10.6
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Martin Becker
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
13
+ all 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
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,188 @@
1
+ # Ruta
2
+
3
+ ## Installation
4
+
5
+ Add this line to your application's Gemfile:
6
+
7
+ ```ruby
8
+ gem 'ruta'
9
+ ```
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install ruta
18
+
19
+ ## Usage
20
+
21
+ Making use of this router is pretty simple.
22
+
23
+ ###Contexts
24
+
25
+ The first thing to do is to define your contexts.
26
+
27
+ A context is a composition of on screen component containers, routes and handlers.
28
+
29
+ You can even place another context under a context as a sub-context
30
+
31
+ `component` is used to define a container, you give it a name and then a block and inside of which you
32
+ place a renderable component.
33
+
34
+ you can also use `sub_context` to mount a sub-context at this position, first you give it a name then
35
+ a reference to the context you wish to place here.
36
+
37
+ This initial composition is used to as the default settings for when the context is first rendered
38
+
39
+ ```ruby
40
+ Ruta::Context.define :main do
41
+ component :header do
42
+ Header
43
+ end
44
+
45
+ sub_context :info, :info_view
46
+
47
+ component :footer do
48
+ Footer
49
+ end
50
+ end
51
+
52
+
53
+ Ruta::Context.define :info_view do
54
+ component :hero do
55
+ Hero_Image
56
+ end
57
+
58
+ component :scroller do
59
+ Info_Scroller
60
+ end
61
+
62
+ component :buttons do
63
+ Buttons
64
+ end
65
+ end
66
+
67
+ Ruta::Context.define :sign_up do
68
+ component :sign_up_form do
69
+ Sign_Up_Form
70
+ end
71
+ end
72
+ ```
73
+
74
+ ###Handlers
75
+
76
+ Next you should define some handlers, not every component needs a handler but every component has one
77
+ needs to match names with a corresponding handler.
78
+
79
+ you use the `handle` method to define a handler and you pass into it the name of the element you want this
80
+ handler to drive.
81
+
82
+ The handler has two variables passed to it:
83
+
84
+ 1. `params` a hash containing the named params of the route
85
+ 2. `url` a string containing the url of the route being passed to the handler
86
+
87
+ The handler must return a renderable component.
88
+
89
+ ```ruby
90
+ Ruta::Handlers.define_for :main do
91
+
92
+ end
93
+
94
+ Ruta::Handlers.define_for :info_view do
95
+ handle :scroller do |params,url|
96
+ Info_Scroller.render(page: params[:switch_to])
97
+ end
98
+
99
+ handle :buttons do |params,url|
100
+ Buttons.render(selected: params[:switch_to])
101
+ end
102
+ end
103
+
104
+ Ruta::Handlers.define_for :sign_up do
105
+
106
+ end
107
+ ```
108
+
109
+ ###Routes
110
+
111
+ The last thing to define before your app is ready is the routes that will activate the correct handlers to
112
+ drive onscreen components.
113
+
114
+ Roots that drive on screen components should always be mounted to a context.
115
+ You can state the context using `for_context` then pass in the context you wish to define routes for.
116
+ whilst you can place context's within each other this doesn't actually do anything beyond aesthetics, but
117
+ it's planned that you can mount contexts to routes
118
+
119
+ You map a route by using the `map` command then pass a unique reference (to that context) for the route,
120
+ the route you want to match against and then the driver, one or more handlers; remember handlers are
121
+ executed in the order specified. You can also provide a context that the route will activate.
122
+
123
+ To set the initial context that is presented use `root_to` then provide the context you want to be rendered first
124
+
125
+ ```ruby
126
+ Ruta::Router.define do
127
+ for_context :main do
128
+ for_context :info_view do
129
+ map :i_switch, '/buttons/:switch_to', to: [:scroller,:buttons]
130
+ map :sign_up, '/sign_up', context: :sign_up
131
+ end
132
+ end
133
+
134
+ root_to :main
135
+ end
136
+
137
+ ```
138
+
139
+ ###Renderer
140
+
141
+ The last step is to tell the router how to render your components.
142
+
143
+ You do this using `Ruta::Context.handle_render` and then creating a macro.
144
+ Passed into the macro is the `component` you wish to render and the `element_id` of the components mounting
145
+ point.
146
+
147
+ ```ruby
148
+ Ruta::Context.handle_render do |component,element_id|
149
+ element = if component.class == React::Element
150
+ component
151
+ else
152
+ React.create_element(component)
153
+ end
154
+ React.render element, `document.getElementById(#{element_id})`
155
+ end
156
+ ```
157
+
158
+ ###starting the app
159
+
160
+ The last step is to start the router and thus your app this can be accomplished by using `Ruta.start_app`;
161
+ it's best to wrap this inside of a `$document.ready` block so that the app will only start when the dom is
162
+ fully ready.
163
+ ```ruby
164
+ $document.ready do
165
+ Ruta.start_app
166
+ end
167
+ ```
168
+
169
+ ### Example
170
+
171
+ Here is an [example app](https://github.com/Thermatix/ruta_with_reactrb_example) where you can see the router in action, remember to `bundle install` first before running with `rackup`
172
+
173
+
174
+
175
+ ## Development
176
+
177
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake false` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
178
+
179
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
180
+
181
+ ## Contributing
182
+
183
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/ruta. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](contributor-covenant.org) code of conduct.
184
+
185
+
186
+ ## License
187
+
188
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "ruta"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,153 @@
1
+ # stores contexts
2
+ module Ruta
3
+ class Context
4
+
5
+ # @!attribute [r] elements
6
+ # @return [{ref => { attributes: {},type: Symbol, content: Proc,Symbol}] Hash of all elements defined
7
+
8
+ # @!attribute [r] ref
9
+ # @return [Symbol] this contexts reference in (see #Context#collection)
10
+
11
+ # @!attribute [r,w] handlers
12
+ # @return [{ref => Proc}] list of route handlers attached to this context
13
+
14
+ # @!attribute [r,w] routes
15
+ # @return [{ref => Route}] list of all routes attached to this context
16
+
17
+ attr_reader :elements, :ref
18
+ attr_accessor :handlers, :routes,:sub_contexts
19
+ DOM = ::Kernel.method(:DOM)
20
+
21
+ # @see #Context#handle_render
22
+ def initialize ref,block
23
+ @ref = ref
24
+ @elements = {}
25
+ @handlers = {}
26
+ @routes = {}
27
+ @sub_contexts = []
28
+ instance_exec &block if block
29
+ end
30
+
31
+ # an element of the composition
32
+ #
33
+ # @param [Symbol] id of element to mount element contents to
34
+ # @param [{Symbol => String,Number,Boolean}] list of attributes to attach to tag
35
+ # @yield block containing component to be rendered to page
36
+ # @yieldreturn [Object] a component that will be passed to the renderer to be rendered to the page
37
+ def component id,attribs={}, &block
38
+ self.elements[id] = {
39
+ attributes: attribs,
40
+ type: :element,
41
+ content: block
42
+ }
43
+ end
44
+
45
+ # mount a context as a sub context here
46
+ #
47
+ # @param [Symbol] id of component to mount context to
48
+ # @param [Symbol] ref of context to be mounted
49
+ # @param [{Symbol => String,Number,Boolean}] list of attributes to attach to tag
50
+ def sub_context id,ref,attribs={}
51
+ @sub_contexts << ref
52
+ self.elements[id] = {
53
+ attributes: attribs,
54
+ type: :sub_context,
55
+ content: ref,
56
+ }
57
+ end
58
+
59
+
60
+ class << self
61
+ # @!attribute [r] collection
62
+ # @return [{ref => Context}] Hash of all Contexts created
63
+
64
+ # @!attribute [r] renderer
65
+ # @return [Proc] the renderer used to render and or mount components on to the DOM
66
+
67
+ # @!attribute [r,w] current_context
68
+ # @return [Symbol] The reference to the current context being rendered
69
+
70
+
71
+ attr_reader :collection,:renderer
72
+ attr_accessor :current_context
73
+
74
+ # Used to tell the router how to render components to the DOM
75
+ #
76
+ # @example render a react.rb component to the dom
77
+ # Ruta::Context.handle_render do |object,element_id|
78
+ # React.render React.create_element(object), `document.getElementById(#{element_id})`
79
+ # end
80
+ # @yield [object,element_id] A block of code that gets executed to render a given object to a given element id
81
+ # @yieldparam [Object] object to be rendered
82
+ # @yieldparam [String] ID of page element object will be mounted to
83
+ def handle_render &block
84
+ @renderer = block
85
+ end
86
+
87
+ # used to define a context's page composition
88
+ #
89
+ # @example Define a composition for a context called :main
90
+ # Ruta::Context.define :main do
91
+ # element :header do
92
+ # #some code that returns a component
93
+ # end
94
+ #
95
+ # sub_context :info, :info_view
96
+ #
97
+ # element :footer do
98
+ # #some code that returns a component
99
+ # end
100
+ # end
101
+ # @param [Symbol] ref reference to the context being defined
102
+ # @yield a block that is used to define the composition of a context
103
+ def define ref, &block
104
+ @collection[ref] = new(ref,block)
105
+ end
106
+
107
+ # used to wipe clear an elementt's content
108
+ #
109
+ # @param [String] id of element to be cleared, if no id is provided will clear body tag of content
110
+ def wipe id=nil
111
+ if id
112
+ $document[id].clear
113
+ else
114
+ $document.body.clear
115
+ end
116
+ end
117
+
118
+ # used to render a composition to the screen
119
+ #
120
+ # @param [Symbol] context the context to render
121
+ # @param [String] id of element context is to be rendered to, if no id is provided will default to body tagt
122
+ def render context, id=nil
123
+ this = id ? $document[id]: $document.body
124
+ context_to_render = @collection[context]
125
+ render_context_elements context_to_render,context, this
126
+ render_element_contents context_to_render,context
127
+ end
128
+ private
129
+ def render_context_elements context_to_render,context,this
130
+ context_to_render.elements.each do |element_name,details|
131
+ DOM {
132
+ div(details[:attributes].merge(id: element_name, "data-context" => context))
133
+ }.append_to(this)
134
+ end
135
+ end
136
+
137
+ def render_element_contents context_to_render,context
138
+ @current_context = context
139
+ context_to_render.elements.each do |element_name,details|
140
+ case details[:type]
141
+ when :sub_context
142
+ render details[:content],element_name
143
+ when :element
144
+ @renderer.call(details[:content].call,element_name,context)
145
+ end
146
+ end
147
+ @current_context = :no_context
148
+ end
149
+ end
150
+ @collection = {}
151
+ @current_context = :no_context
152
+ end
153
+ end
@@ -0,0 +1,55 @@
1
+ module Ruta
2
+ class Handlers
3
+
4
+ # create a handle to be excuted when a matching route is hit
5
+ #
6
+ # @param [Symbol] handler_name the unique ident of the handler, it should match the id of an element that you want the component to be rendered to
7
+ # @yield [params,path] a block containing logic for processing any params before passing it to a component to render
8
+ # @yieldparam [{Symbol => String}] params containing a list of params passed into it from the matching route
9
+ # @yieldparam [String] path the non processed url
10
+ # @yieldreturn [Object] a component that will be passed to the renderer to be rendered to the page
11
+ def handle handler_name,&handler
12
+ @handler_name = handler_name
13
+ @context.handlers[@handler_name] = handler
14
+ end
15
+
16
+ # @see #Handlers#define_for
17
+ def initialize context,block
18
+ @context = context
19
+ instance_exec &block
20
+ end
21
+
22
+ # wipe the matching element and render a context
23
+ #
24
+ # @param [Symbol] context context to be mounted to matching element of handler
25
+ # @todo Move these functions to execution context?
26
+ # @return [Proc] returns a proc to be executed later
27
+ def mount context
28
+ handler_name = @handler_name
29
+ proc {
30
+ Context.wipe handler_name
31
+ Context.render context
32
+ }
33
+ end
34
+
35
+ class << self
36
+ # define handlers for a context
37
+ #
38
+ # @example
39
+ # Ruta::Handlers.define_for :main do
40
+ # handler :header do |params,url|
41
+ # some code that process the params and returns a component
42
+ # end
43
+ # handler :header do |params,url|
44
+ # some code that process the params and returns a component
45
+ # end
46
+ # end
47
+ # @param [Symbol] context to define handlers for
48
+ # @yield block containing handlers for a context
49
+ def define_for context, &block
50
+ new(Context.collection.fetch(context){|c_n|raise"Tried to define handlers for #{c_n} before it exists"},
51
+ block)
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,106 @@
1
+ require 'native'
2
+ module Ruta
3
+
4
+ class History
5
+ class << self
6
+
7
+ # push new url to history
8
+ #
9
+ # @param [String] url to be added to history
10
+ # @param [Hash] data to be added to history
11
+ # @param [String] title to be added to history, defaults to ""
12
+ def push(url,data,title="")
13
+ `history.pushState(#{data.to_n}, #{title}, #{url})`
14
+ end
15
+
16
+ # replace current url in history
17
+ #
18
+ # @param [String] url to replace current item in history
19
+ # @param [Hash] data to replace current item in history
20
+ # @param [String] title to replace current item in history, defaults to ""
21
+ def replace(url,data,title=nil)
22
+ `history.replaceState(#{data.to_n}, #{title}, #{url})`
23
+ end
24
+
25
+ # move browser forward
26
+ #
27
+ # @param [Integer] by the amount to go forwards by, defaults to 1
28
+ def forward(by=1)
29
+ `history.go(#{by.to_i})`
30
+ end
31
+
32
+ # move browser backwards
33
+ #
34
+ # @param [Integer] by the amount to go backwards by, defaults to 1
35
+ def back(by=1)
36
+ `history.go(#{-by.to_i})`
37
+ end
38
+
39
+ # navigate browser to remote path
40
+ #
41
+ # @param [String] path to navigate to
42
+ def navigate_to_remote path
43
+ `location.href = #{path}`
44
+ end
45
+
46
+ # get current `thing` from locaction
47
+ #
48
+ # current things supported are:
49
+ # * query
50
+ # * fragment
51
+ # * path
52
+ # * url
53
+ # * uri
54
+ # @param [Symbol] thing to get the current value of from the current location
55
+ def current thing
56
+ case thing
57
+ when :query
58
+ `location.query`
59
+ when :fragment
60
+ ` location.fragment`
61
+ when :path
62
+ `location.pathname`
63
+ when :url
64
+ `location.href`
65
+ when :uri
66
+ `location.uri`
67
+ end
68
+
69
+ end
70
+
71
+ # will turn on a listener for when the forward and backward buttons on the browser are pressed
72
+ def listen_for_pop
73
+ `window.onpopstate = function(event) {
74
+ #{
75
+ Router.find_and_execute(current :path)
76
+ }
77
+ }`
78
+ end
79
+
80
+ # will stop listening for when forward and backward buttons on the browser are pressed
81
+ def stop_listening_for_pop
82
+ `window.onpopstate = nil`
83
+ end
84
+
85
+ # will stop listening for when the page move away from the current page(refresh or back), will present user with dialogue box
86
+ def stop_listening_for_on_before_load
87
+ `window.onbeforeunload = nil`
88
+ end
89
+
90
+
91
+ # will turn on a listener for when the page move away from the current page(refresh or back), will present user with dialogue box
92
+ def listen_for_on_before_load
93
+ `window.onbeforeunload = function (evt) {
94
+ var message = 'Are you sure you want to leave?';
95
+ if (typeof evt == 'undefined') {
96
+ evt = window.event;
97
+ }
98
+ if (evt) {
99
+ evt.returnValue = message;
100
+ }
101
+ return message;
102
+ }`
103
+ end
104
+ end
105
+ end
106
+ end
data/lib/ruta/route.rb ADDED
@@ -0,0 +1,139 @@
1
+ module Ruta
2
+ # this class was taken from vienna and modified
3
+ # https://github.com/opal/vienna/blob/master/opal/vienna/router.rb#L42
4
+ class Route
5
+ DOM = ::Kernel.method(:DOM)
6
+ NAMED = /:(\w+)/
7
+
8
+ SPLAT = /\\\*(\w+)/
9
+
10
+ OPTIONAL = /\\\((.*?)\\\)/
11
+
12
+ # @!attribute [r] regexp
13
+ # @return [Symbol] the regexp used to match against a route
14
+ # @!attribute [r] named
15
+ # @return [Symbol] list of all named paramters
16
+ # @!attribute [r] type
17
+ # @return [Symbol] the type of the route's handler
18
+ # @!attribute [r] handlers
19
+ # @return [Symbol] a list of the handlers this route is suposed to execute
20
+ # @!attribute [r] url
21
+ # @return [Symbol] the raw url to match against
22
+ # @!attribute [r] flags
23
+ # @return [Symbol] any flags this route possesses
24
+
25
+ attr_reader :regexp, :named, :type, :handlers, :url,:flags
26
+
27
+ # @param [String] pattern of url to match against
28
+ # @param [Symbol] context_ref to context route is mounted to
29
+ # @param [{Symbol => String,Number,Boolean}] flags attached to route
30
+ def initialize(pattern, context_ref,flags)
31
+
32
+ if flags[:to]
33
+ @type = :handlers
34
+ @handlers = [flags.delete :to].flatten
35
+ elsif flags[:context]
36
+ @type = :context
37
+ @handlers = flags.delete :context
38
+ else
39
+ @type = :ref_only
40
+ end
41
+ @context_ref = context_ref
42
+ @flags = flags
43
+ @url = pattern
44
+ @named = []
45
+
46
+ pattern = Regexp.escape pattern
47
+ pattern = pattern.gsub OPTIONAL, "(?:$1)?"
48
+
49
+ pattern.gsub(NAMED) { |m| @named << m[1..-1] }
50
+ pattern.gsub(SPLAT) { |m| @named << m[2..-1] }
51
+
52
+ pattern = pattern.gsub NAMED, "([^\\/]*)"
53
+ pattern = pattern.gsub SPLAT, "(.*?)"
54
+
55
+ @regexp = Regexp.new "^#{pattern}$"
56
+
57
+ end
58
+
59
+ #take in params and return a paramaterized route
60
+ #
61
+ # @param [Array<String,Number,Boolean>] params a list of params to replace named params in a route
62
+ # @return [String] url containing named params replaced
63
+ def paramaterize params
64
+ segments = @url.split('/')
65
+ segments.map { |item| item[0] == ':' ? params.shift : item }.join('/')
66
+ end
67
+
68
+ # get route hash and paramaterize url if needed
69
+ #
70
+ # @param [Array<String,Number,Boolean>] params to replace named params in the returned url
71
+ # @return [Symbol => Number,String,Route] hash specificly formatted:
72
+ # {
73
+ # url: of the route with named params replaced,
74
+ # title: the name of page if the url has one,
75
+ # params: a list of all the params used in the route,
76
+ # route: the #Route object
77
+ # }
78
+ def get params=nil
79
+ path = if params
80
+ paramaterize params.dup
81
+ else
82
+ @url
83
+ end
84
+ {
85
+ path: path,
86
+ title: self.flags.fetch(:title){nil},
87
+ params: params_hash(params),
88
+ route: self
89
+ }
90
+ end
91
+
92
+ # match this route against a given path
93
+ #
94
+ # @param [String,Regex] path to match against
95
+ # @return [Hash,false] (see #get) or false if there is no match
96
+ def match(path)
97
+ if match = @regexp.match(path)
98
+ params = {}
99
+ @named.each_with_index { |name, i| params[name] = match[i + 1] } if @type == :handlers
100
+ {
101
+ path: path,
102
+ title: self.flags.fetch(:title){nil},
103
+ params: params,
104
+ route: self
105
+ }
106
+ else
107
+ false
108
+ end
109
+ end
110
+
111
+ # execute's route's associated handlers
112
+ #
113
+ # @param [Symbol => String] params from the route with there respective keys
114
+ # @param [String] path containing params placed into there respective named positions
115
+ def execute_handler params={},path=nil
116
+ case @type
117
+ when :handlers
118
+ @handlers.each do |handler_ident|
119
+ handler = @context_ref.handlers.fetch(handler_ident) {raise "handler #{handler_ident} doesn't exist in context #{@context_ref.ref}"}
120
+ component = handler.(params,path||@url,&:call)
121
+ Context.current_context = @context_ref.ref
122
+ if component.class == Proc
123
+ component.call
124
+ else
125
+ Context.renderer.call(component,handler_ident)
126
+ end
127
+ Context.current_context = :no_context
128
+ end
129
+ when :context
130
+ Context.wipe
131
+ Context.render handlers
132
+ end
133
+ end
134
+
135
+ def params_hash(params)
136
+ Hash[@named.zip(params)]
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,128 @@
1
+ # matches routes to a handeler
2
+ module Ruta
3
+
4
+ class Router
5
+
6
+
7
+ # @todo: ensure sub-context routes are mounted into parent context routes
8
+
9
+ # @!attribute [r,w] current_context
10
+ # @return [Array<Symbol>] current_context a list of contexts, the last being the current
11
+
12
+ attr_accessor :current_context
13
+
14
+ # @see (see #Router#define)
15
+ def initialize block
16
+ @current_context = []
17
+ Context.define(:no_context)
18
+ instance_exec &block
19
+ end
20
+
21
+ # set which Context to map the following routes to
22
+ #
23
+ # @param [Symbol] context to map routes to
24
+ def for_context context
25
+ @current_context << context
26
+ yield
27
+ @current_context.pop
28
+ end
29
+
30
+ # map a route
31
+ # @param [Symbol] ref to map route to for easy future reference
32
+ def map ref,route, options={}
33
+ context = Context.collection[get_context]
34
+ context.routes[ref]= Route.new(route, context,options)
35
+ end
36
+
37
+ # set the root context, this is the initial context that will be renered by the router
38
+ #
39
+ # @note there is only ever one root, calling this multiple times will over right the original root
40
+ # @param [Symbol] reference to context
41
+ def root_to reference
42
+ Router.set_root_to reference
43
+ context = Context.collection[:no_context]
44
+ context.routes[:root]= Route.new('/', context,{ context: reference})
45
+ end
46
+
47
+ private
48
+
49
+ def get_context
50
+ @current_context.last || :no_context
51
+ end
52
+
53
+ class << self
54
+
55
+ # @!attribute [r] current_context
56
+ # @return [Array<Symbol>] current_context the current context that the user is in
57
+
58
+ # @!attribute [r] history
59
+ # @return [Array<Symbol>] history
60
+
61
+ # @!attribute [r] window
62
+ # @return [Array<Symbol>] window
63
+
64
+ # @!attribute [r] root
65
+ # @return [Array<Symbol>] root the initial context of the app
66
+
67
+ attr_reader :current_context, :root
68
+
69
+ # define a router, this can be called multiple times
70
+ # @example defining routes
71
+ # Ruta::Router.define do
72
+ # for_context :main do
73
+ # for_context :info_view do
74
+ # map :i_switch, '/buttons/:switch_to', to: [:scroller,:buttons]
75
+ # map :sign_up, '/sign_up', context: :sign_up
76
+ # end
77
+ # end
78
+ #
79
+ # root_to :main
80
+ # end
81
+ # @note please be aware that placing contexts within other contexts doesn't actully do anything.
82
+ # however it is planed to be able to mount sub contexts to a route and re-direct as neccasary
83
+ # think mounting engines or sub apps in RoR or Padrino
84
+ # @yield Use this block to define any routes
85
+ def define &block
86
+ new block
87
+ end
88
+
89
+ def set_context_to context
90
+ @current_context = context
91
+ end
92
+
93
+ def find_and_execute(path)
94
+ res = find(path)
95
+ if res
96
+ navigate_to res
97
+ else
98
+ raise "no matching route for #{path}"
99
+ end
100
+ end
101
+
102
+ def find path
103
+ Context.collection.each do |con_ref,context|
104
+ context.routes.each do |r_ref,route|
105
+ res = route.match(path)
106
+ return res if res
107
+ end
108
+ end
109
+ false
110
+ end
111
+
112
+ def navigate_to(route)
113
+ route[:route].execute_handler route[:params],route[:path]
114
+ end
115
+
116
+ def route_for context, ref,params=nil
117
+ Context.collection[context].routes[ref].get(params)
118
+ end
119
+
120
+ def set_root_to context
121
+ @root = context
122
+ Router.set_context_to root
123
+ end
124
+
125
+ end
126
+
127
+ end
128
+ end
@@ -0,0 +1,3 @@
1
+ module Ruta
2
+ VERSION = "1.0.0"
3
+ end
data/lib/ruta.rb ADDED
@@ -0,0 +1,62 @@
1
+ # @todo: Ensure sub-context routes are mounted into parent context routes
2
+ # @todo: Allow empty components to be exists and ensure they are only rendered when they are not blank
3
+
4
+ if RUBY_ENGINE == 'opal'
5
+ require 'browser'
6
+ require 'browser/history'
7
+
8
+ require 'ruta/context'
9
+ require 'ruta/handler'
10
+ require 'ruta/history'
11
+ require 'ruta/route'
12
+ require 'ruta/router'
13
+ require 'ruta/version'
14
+
15
+
16
+ module Ruta
17
+ class << self
18
+
19
+
20
+
21
+ # used to retrive a stored url
22
+ #
23
+ # @param [Symbol] context of the stored url, if this is nil it defaults to the current context
24
+ # @param [Symbol] reference to the route
25
+ # @param [Array<String,Number,Boolean>] *params 0 or more params to replace params in the paramterized route
26
+ # @return [String] string containing url with any params given correctly inserted
27
+ def get_url_for context, reference, *params
28
+ Router.route( context || Router.current_context, reference, params)[:path]
29
+ end
30
+
31
+ # used to navigate to a route
32
+ # @param [Symbol] context that route is mounted to
33
+ # @param [Symbol] ref to a route that you wish to navigate to
34
+ # @param [Array<String,Number,Boolean>] *params 0 or more params to replace params in the paramterized route
35
+ # @note you have to use this function as a proc direcly as in the example, if you place this into a callback block and call it there, you will find that the incorrect context is used for the route
36
+ # @return [Proc] A proc that can be used as a callback block for an event
37
+ def navigate_to_ref context, ref,*params
38
+ route = Router.route_for(context,ref,params)
39
+ History.push(route[:path],route[:params],route[:title])
40
+ Router.navigate_to(route)
41
+ end
42
+
43
+ # used to start the app
44
+ # @example app start command placed inside of $document.ready block
45
+ # $document.ready do
46
+ # Ruta.start_app
47
+ # end
48
+ def start_app
49
+ Context.render(Router.current_context)
50
+ History.listen_for_pop
51
+ end
52
+ end
53
+
54
+ end
55
+
56
+
57
+ else
58
+ require "ruta/version"
59
+ require 'opal'
60
+ lib_path = File.dirname(File.expand_path('.', __FILE__))
61
+ Opal.append_path lib_path
62
+ end
data/ruta.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'ruta/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "ruta"
8
+ spec.version = Ruta::VERSION
9
+ spec.authors = ["Martin Becker",'Adam Beynon']
10
+ spec.email = ["mbeckerwork@gmail.com",'adam.beynon@gmail.com']
11
+
12
+ spec.summary = %q{Front end agnostic router built in opal}
13
+ spec.homepage = "https://github.com/Thermatix/ruta"
14
+ spec.license = "MIT"
15
+
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.bindir = "exe"
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_runtime_dependency "opal", "~> 0.8"
23
+ spec.add_runtime_dependency "opal-browser", "~> 0.2"
24
+
25
+ spec.add_development_dependency "bundler", "~> 1.10"
26
+ spec.add_development_dependency "rake", "~> 10.0"
27
+ end
metadata ADDED
@@ -0,0 +1,121 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruta
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Martin Becker
8
+ - Adam Beynon
9
+ autorequire:
10
+ bindir: exe
11
+ cert_chain: []
12
+ date: 2015-12-14 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: opal
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '0.8'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '0.8'
28
+ - !ruby/object:Gem::Dependency
29
+ name: opal-browser
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '0.2'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '0.2'
42
+ - !ruby/object:Gem::Dependency
43
+ name: bundler
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '1.10'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: '1.10'
56
+ - !ruby/object:Gem::Dependency
57
+ name: rake
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - "~>"
61
+ - !ruby/object:Gem::Version
62
+ version: '10.0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: '10.0'
70
+ description:
71
+ email:
72
+ - mbeckerwork@gmail.com
73
+ - adam.beynon@gmail.com
74
+ executables: []
75
+ extensions: []
76
+ extra_rdoc_files: []
77
+ files:
78
+ - ".gitignore"
79
+ - ".travis.yml"
80
+ - CODE_OF_CONDUCT.md
81
+ - Gemfile
82
+ - Gemfile.lock
83
+ - LICENSE.txt
84
+ - README.md
85
+ - Rakefile
86
+ - bin/console
87
+ - bin/setup
88
+ - lib/ruta.rb
89
+ - lib/ruta/context.rb
90
+ - lib/ruta/handler.rb
91
+ - lib/ruta/history.rb
92
+ - lib/ruta/route.rb
93
+ - lib/ruta/router.rb
94
+ - lib/ruta/version.rb
95
+ - ruta.gemspec
96
+ homepage: https://github.com/Thermatix/ruta
97
+ licenses:
98
+ - MIT
99
+ metadata: {}
100
+ post_install_message:
101
+ rdoc_options: []
102
+ require_paths:
103
+ - lib
104
+ required_ruby_version: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - ">="
107
+ - !ruby/object:Gem::Version
108
+ version: '0'
109
+ required_rubygems_version: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ version: '0'
114
+ requirements: []
115
+ rubyforge_project:
116
+ rubygems_version: 2.4.5.1
117
+ signing_key:
118
+ specification_version: 4
119
+ summary: Front end agnostic router built in opal
120
+ test_files: []
121
+ has_rdoc: