fron 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/docs/application.md +7 -0
- data/docs/configuration.md +29 -0
- data/docs/controllers.md +35 -0
- data/docs/routing.md +63 -0
- data/fron.gemspec +20 -0
- data/lib/fron.rb +4 -0
- data/lib/fron/version.rb +3 -0
- data/opal/fron.rb +5 -0
- data/opal/fron/core-ext/hash.rb +17 -0
- data/opal/fron/core.rb +10 -0
- data/opal/fron/core/adapters/local-storage.rb +41 -0
- data/opal/fron/core/adapters/rails.rb +48 -0
- data/opal/fron/core/application.rb +39 -0
- data/opal/fron/core/component.rb +81 -0
- data/opal/fron/core/configuration.rb +26 -0
- data/opal/fron/core/controller.rb +41 -0
- data/opal/fron/core/eventable.rb +31 -0
- data/opal/fron/core/logger.rb +8 -0
- data/opal/fron/core/model.rb +68 -0
- data/opal/fron/core/router.rb +81 -0
- data/opal/fron/dom.rb +11 -0
- data/opal/fron/dom/document.rb +20 -0
- data/opal/fron/dom/element.rb +102 -0
- data/opal/fron/dom/event.rb +70 -0
- data/opal/fron/dom/fragment.rb +9 -0
- data/opal/fron/dom/modules/classlist.rb +27 -0
- data/opal/fron/dom/modules/dimensions.rb +37 -0
- data/opal/fron/dom/modules/events.rb +19 -0
- data/opal/fron/dom/node.rb +79 -0
- data/opal/fron/dom/style.rb +21 -0
- data/opal/fron/dom/text.rb +9 -0
- data/opal/fron/dom/window.rb +23 -0
- data/opal/fron/request.rb +3 -0
- data/opal/fron/request/request.rb +51 -0
- data/opal/fron/request/response.rb +36 -0
- data/opal/fron/storage.rb +1 -0
- data/opal/fron/storage/local-storage.rb +18 -0
- metadata +107 -0
@@ -0,0 +1,41 @@
|
|
1
|
+
module Fron
|
2
|
+
class Controller
|
3
|
+
class << self
|
4
|
+
attr_accessor :baseComponent, :routes, :beforeFilters, :events
|
5
|
+
|
6
|
+
def base(component)
|
7
|
+
@baseComponent = component
|
8
|
+
end
|
9
|
+
|
10
|
+
def route(*args)
|
11
|
+
@routes ||= []
|
12
|
+
@routes << Router.map(*args)
|
13
|
+
end
|
14
|
+
|
15
|
+
def on(name,action)
|
16
|
+
@events ||= []
|
17
|
+
@events << {name: name, action: action}
|
18
|
+
end
|
19
|
+
|
20
|
+
def beforeFilter(method,actions)
|
21
|
+
@beforeFilters ||= []
|
22
|
+
@beforeFilters << {method: method, actions: actions}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
attr_reader :base
|
27
|
+
|
28
|
+
def initialize
|
29
|
+
if self.class.baseComponent
|
30
|
+
@base = self.class.baseComponent.new
|
31
|
+
else
|
32
|
+
@base = DOM::Element.new 'div'
|
33
|
+
end
|
34
|
+
|
35
|
+
return unless self.class.events
|
36
|
+
self.class.events.each do |event|
|
37
|
+
Eventable.on event[:name] do self.send(event[:action]) end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Fron
|
2
|
+
module Eventable
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def on(event, &block)
|
6
|
+
@events ||= {}
|
7
|
+
@events[event] ||= []
|
8
|
+
@events[event] << block
|
9
|
+
end
|
10
|
+
|
11
|
+
def trigger(event, triggerGlobal = true)
|
12
|
+
Eventable.trigger event, false if triggerGlobal
|
13
|
+
return unless @events
|
14
|
+
return unless @events[event]
|
15
|
+
@events[event].each do |block|
|
16
|
+
block.call
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def off(event = nil, &block)
|
21
|
+
return unless @events
|
22
|
+
if block_given?
|
23
|
+
@events[event].delete block
|
24
|
+
elsif event
|
25
|
+
@events[event] = []
|
26
|
+
else
|
27
|
+
@events = {}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Fron
|
2
|
+
class Model
|
3
|
+
include Eventable
|
4
|
+
attr_reader :errors
|
5
|
+
|
6
|
+
class << self
|
7
|
+
attr_accessor :fields
|
8
|
+
attr_accessor :adapter
|
9
|
+
|
10
|
+
def adapter(adapter, options = {})
|
11
|
+
options.merge! fields: @fields
|
12
|
+
@adapter = adapter.new options
|
13
|
+
end
|
14
|
+
|
15
|
+
def field(name)
|
16
|
+
@fields ||= []
|
17
|
+
@fields << name
|
18
|
+
define_method(name) do
|
19
|
+
@data[name]
|
20
|
+
end
|
21
|
+
define_method(name+"=") do |value|
|
22
|
+
@data[name] = value
|
23
|
+
trigger 'change'
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def all(&block)
|
28
|
+
@adapter.all do |items|
|
29
|
+
block.call items.map{ |item| self.new item }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def find(id, &block)
|
34
|
+
user = self.new
|
35
|
+
@adapter.get id do |data|
|
36
|
+
user.merge data
|
37
|
+
block.call user
|
38
|
+
end
|
39
|
+
user
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def initialize(data = {})
|
44
|
+
@data = data
|
45
|
+
end
|
46
|
+
|
47
|
+
def update(attributes, &block)
|
48
|
+
data = @data.dup.merge! attributes
|
49
|
+
self.class.instance_variable_get("@adapter").set id, data do |errors|
|
50
|
+
@errors = errors
|
51
|
+
merge data
|
52
|
+
block.call if block_given?
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def dirty?
|
57
|
+
!self.id
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def merge(data)
|
63
|
+
data.each_pair do |key,value|
|
64
|
+
self.send(key+"=", value) if self.respond_to?(key+"=")
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module Fron
|
2
|
+
class Router
|
3
|
+
def initialize(routes,config)
|
4
|
+
@config = config
|
5
|
+
@routes = routes
|
6
|
+
|
7
|
+
DOM::Window.on 'load' do
|
8
|
+
route
|
9
|
+
end
|
10
|
+
DOM::Window.on 'hashchange' do
|
11
|
+
route
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.map(*args)
|
16
|
+
data = case args.length
|
17
|
+
when 1
|
18
|
+
action = args[0]
|
19
|
+
{path: "*"}
|
20
|
+
when 2
|
21
|
+
action = args[1]
|
22
|
+
{path: Router.pathToRegexp(args[0]) }
|
23
|
+
end
|
24
|
+
if action.is_a? Class
|
25
|
+
data[:controller] = action.new
|
26
|
+
else
|
27
|
+
data[:action] = action.to_s
|
28
|
+
end
|
29
|
+
data
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.pathToRegexp(path)
|
33
|
+
return path if path == "*"
|
34
|
+
{regexp: Regexp.new('^'+path.gsub(/:(.+)/, '(.+)')), map: path.match(/:(.+)/).to_a[1..-1] }
|
35
|
+
end
|
36
|
+
|
37
|
+
def route(hash = DOM::Window.hash, controller = nil)
|
38
|
+
routes = controller ? controller.class.routes : @routes
|
39
|
+
routes.each do |r|
|
40
|
+
if r[:path] == '*'
|
41
|
+
if r[:controller]
|
42
|
+
break route(hash,r[:controller])
|
43
|
+
else
|
44
|
+
break applyRoute(controller,r)
|
45
|
+
end
|
46
|
+
else
|
47
|
+
matches = hash.match(r[:path][:regexp]).to_a[1..-1]
|
48
|
+
if matches
|
49
|
+
params = {}
|
50
|
+
if r[:path][:map]
|
51
|
+
r[:path][:map].each_with_index do |key, index|
|
52
|
+
params[key.to_sym] = matches[index]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
if r[:action]
|
56
|
+
break applyRoute(controller,r,params)
|
57
|
+
else
|
58
|
+
break route hash.gsub(r[:path][:regexp],''), r[:controller]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def applyRoute(controller,route, params = {})
|
68
|
+
if controller.class.beforeFilters
|
69
|
+
controller.class.beforeFilters.each do |filter|
|
70
|
+
if filter[:actions].include?(route[:action])
|
71
|
+
controller.send(filter[:method], params)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
controller.send(route[:action], params)
|
76
|
+
@config.logger.info "Navigate >> #{controller.class}##{route[:action]} with params #{params}"
|
77
|
+
@config.main.empty
|
78
|
+
@config.main << controller.base
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
data/opal/fron/dom.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require './dom/modules/events'
|
2
|
+
require './dom/modules/classlist'
|
3
|
+
require './dom/modules/dimensions'
|
4
|
+
require './dom/style'
|
5
|
+
require './dom/node'
|
6
|
+
require './dom/text'
|
7
|
+
require './dom/element'
|
8
|
+
require './dom/fragment'
|
9
|
+
require './dom/document'
|
10
|
+
require './dom/window'
|
11
|
+
require './dom/event'
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module DOM
|
2
|
+
module Document
|
3
|
+
def self.head
|
4
|
+
find 'head'
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.body
|
8
|
+
find 'body'
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.title=(value)
|
12
|
+
`document.title = #{value}`
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.find(selector)
|
16
|
+
value = `document.querySelector(#{selector}) || false`
|
17
|
+
value ? DOM::Element.new(value) : nil
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
module DOM
|
2
|
+
class Element
|
3
|
+
include Node
|
4
|
+
include ClassList
|
5
|
+
include Dimensions
|
6
|
+
|
7
|
+
attr_reader :style
|
8
|
+
|
9
|
+
ATTRIBUTE_REGEXP = /\[(.*?)=(.*?)\]/
|
10
|
+
TAG_REGEXP = /(^[A-Za-z_\-0-9]+)(.*)/
|
11
|
+
MODIFIER_REGEXP = /(#|\.)(.+?)(?=#|\.| |$)/
|
12
|
+
|
13
|
+
def initialize(data)
|
14
|
+
if `typeof #{data} === 'string'`
|
15
|
+
match, tag, rest = data.match(TAG_REGEXP).to_a
|
16
|
+
@el = `document.createElement(#{tag})`
|
17
|
+
rest = rest.gsub ATTRIBUTE_REGEXP do |match|
|
18
|
+
m,key,value = match.match(ATTRIBUTE_REGEXP).to_a
|
19
|
+
self[key] = value
|
20
|
+
''
|
21
|
+
end
|
22
|
+
rest = rest.gsub MODIFIER_REGEXP do |match|
|
23
|
+
m,type,value = match.match(MODIFIER_REGEXP).to_a
|
24
|
+
case type
|
25
|
+
when "#"
|
26
|
+
self['id'] = value
|
27
|
+
when "."
|
28
|
+
addClass value
|
29
|
+
end
|
30
|
+
''
|
31
|
+
end
|
32
|
+
if (m = rest.match /\s(.+)$/)
|
33
|
+
self.text = m[0].strip
|
34
|
+
end
|
35
|
+
else
|
36
|
+
@el = data
|
37
|
+
end
|
38
|
+
@style = Style.new @el
|
39
|
+
end
|
40
|
+
|
41
|
+
# Visiblity
|
42
|
+
# --------------------------------
|
43
|
+
def hide
|
44
|
+
@style.display = 'none'
|
45
|
+
end
|
46
|
+
|
47
|
+
def show
|
48
|
+
@style.display = 'block'
|
49
|
+
end
|
50
|
+
|
51
|
+
# Attribute access
|
52
|
+
# --------------------------------
|
53
|
+
def [](name)
|
54
|
+
`#{@el}.getAttribute(#{name})`
|
55
|
+
end
|
56
|
+
|
57
|
+
def []=(name,value)
|
58
|
+
`#{@el}.setAttribute(#{name},#{value})`
|
59
|
+
end
|
60
|
+
|
61
|
+
# Traversing
|
62
|
+
# --------------------------------
|
63
|
+
def find(selector)
|
64
|
+
value = `#{@el}.querySelector(#{selector}) || false`
|
65
|
+
value ? DOM::Element.new(value) : nil
|
66
|
+
end
|
67
|
+
|
68
|
+
# HTML Modification
|
69
|
+
# --------------------------------
|
70
|
+
def html
|
71
|
+
`#{@el}.innerHTML`
|
72
|
+
end
|
73
|
+
|
74
|
+
def html=(value)
|
75
|
+
`#{@el}.innerHTML = #{value}`
|
76
|
+
end
|
77
|
+
|
78
|
+
def empty
|
79
|
+
self.html = ''
|
80
|
+
end
|
81
|
+
|
82
|
+
def value
|
83
|
+
`#{@el}.value`
|
84
|
+
end
|
85
|
+
|
86
|
+
def value=(value)
|
87
|
+
`#{@el}.value = #{value}`
|
88
|
+
end
|
89
|
+
|
90
|
+
def checked
|
91
|
+
`!!#{@el}.checked`
|
92
|
+
end
|
93
|
+
|
94
|
+
def checked=(value)
|
95
|
+
`#{@el}.checked = #{value}`
|
96
|
+
end
|
97
|
+
|
98
|
+
def tag
|
99
|
+
`#{@el}.tagName`.downcase
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
class Event
|
2
|
+
def initialize(e)
|
3
|
+
@e = e
|
4
|
+
end
|
5
|
+
|
6
|
+
def target
|
7
|
+
`#{@e}.target`
|
8
|
+
end
|
9
|
+
|
10
|
+
def charCode
|
11
|
+
`#{@e}.charCode`
|
12
|
+
end
|
13
|
+
|
14
|
+
def keyCode
|
15
|
+
`#{@e}.keyCode`
|
16
|
+
end
|
17
|
+
|
18
|
+
def stop
|
19
|
+
preventDefault
|
20
|
+
stopPropagation
|
21
|
+
end
|
22
|
+
|
23
|
+
def preventDefault
|
24
|
+
`#{@e}.preventDefault()`
|
25
|
+
end
|
26
|
+
|
27
|
+
def stopPropagation
|
28
|
+
`#{@e}.stopPropagation()`
|
29
|
+
end
|
30
|
+
|
31
|
+
def pageX
|
32
|
+
`#{@e}.pageX`
|
33
|
+
end
|
34
|
+
|
35
|
+
def pageY
|
36
|
+
`#{@e}.pageY`
|
37
|
+
end
|
38
|
+
|
39
|
+
def screenX
|
40
|
+
`#{@e}.screenX`
|
41
|
+
end
|
42
|
+
|
43
|
+
def screenY
|
44
|
+
`#{@e}.screenY`
|
45
|
+
end
|
46
|
+
|
47
|
+
def clientX
|
48
|
+
`#{@e}.clientX`
|
49
|
+
end
|
50
|
+
|
51
|
+
def clientY
|
52
|
+
`#{@e}.clientY`
|
53
|
+
end
|
54
|
+
|
55
|
+
def alt?
|
56
|
+
`#{@e}.altkey`
|
57
|
+
end
|
58
|
+
|
59
|
+
def shift?
|
60
|
+
`#{@e}.shiftkey`
|
61
|
+
end
|
62
|
+
|
63
|
+
def ctrl?
|
64
|
+
`#{@e}.ctrlkey`
|
65
|
+
end
|
66
|
+
|
67
|
+
def meta?
|
68
|
+
`#{@e}.metakey`
|
69
|
+
end
|
70
|
+
end
|