fron 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|