fron 0.2.0rc1 → 1.0.0rc1

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.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +20 -0
  3. data/.reek +2 -0
  4. data/.rubocop.yml +14 -11
  5. data/Gemfile +6 -3
  6. data/Gemfile.lock +73 -86
  7. data/Rakefile +11 -15
  8. data/Readme.md +1 -1
  9. data/fron.gemspec +2 -2
  10. data/lib/fron/version.rb +1 -1
  11. data/opal/fron.rb +2 -0
  12. data/opal/fron/core.rb +1 -0
  13. data/opal/fron/core/behaviors/components.rb +18 -10
  14. data/opal/fron/core/behaviors/events.rb +9 -10
  15. data/opal/fron/core/behaviors/routes.rb +6 -10
  16. data/opal/fron/core/behaviors/style.rb +30 -0
  17. data/opal/fron/core/component.rb +44 -23
  18. data/opal/fron/core/eventable.rb +3 -3
  19. data/opal/fron/core/logger.rb +1 -1
  20. data/opal/fron/core/sheet.rb +140 -0
  21. data/opal/fron/core_ext.rb +1 -0
  22. data/opal/fron/core_ext/array.rb +23 -0
  23. data/opal/fron/core_ext/hash.rb +6 -6
  24. data/opal/fron/core_ext/kernel.rb +10 -1
  25. data/opal/fron/core_ext/numeric.rb +7 -0
  26. data/opal/fron/core_ext/time.rb +6 -0
  27. data/opal/fron/dom/document.rb +6 -3
  28. data/opal/fron/dom/element.rb +79 -19
  29. data/opal/fron/dom/event.rb +5 -1
  30. data/opal/fron/dom/modules/dimensions.rb +0 -14
  31. data/opal/fron/dom/modules/element_accessor.rb +25 -0
  32. data/opal/fron/dom/modules/events.rb +1 -1
  33. data/opal/fron/dom/node.rb +7 -5
  34. data/opal/fron/dom/style.rb +0 -2
  35. data/opal/fron/dom/window.rb +14 -0
  36. data/opal/fron/event_mock.rb +24 -6
  37. data/opal/fron/js/scroll_into_view_if_needed.js +27 -0
  38. data/opal/fron/js/syntetic_event.js +20 -13
  39. data/opal/fron/request/request.rb +21 -19
  40. data/opal/fron/request/response.rb +1 -1
  41. data/opal/fron/storage.rb +2 -0
  42. data/opal/fron/storage/local_storage.rb +3 -45
  43. data/opal/fron/storage/session_storage.rb +12 -0
  44. data/opal/fron/storage/store.rb +54 -0
  45. data/opal/fron/utils/drag.rb +21 -18
  46. data/opal/fron/utils/keyboard.rb +14 -12
  47. data/opal/fron/utils/point.rb +12 -4
  48. data/opal/fron/utils/render_proc.rb +6 -2
  49. data/spec/core-ext/array_spec.rb +10 -2
  50. data/spec/core-ext/numeric_spec.rb +6 -0
  51. data/spec/core/behaviors/style_spec.rb +51 -0
  52. data/spec/core/component_inheritance_spec.rb +10 -15
  53. data/spec/core/component_spec.rb +10 -15
  54. data/spec/dom/element_spec.rb +12 -1
  55. data/spec/dom/modules/classlist_spec.rb +8 -9
  56. data/spec/dom/modules/dimensions_spec.rb +2 -1
  57. data/spec/dom/modules/events_spec.rb +42 -31
  58. data/spec/dom/style_spec.rb +1 -1
  59. data/spec/spec_helper.rb +0 -1
  60. data/spec/utils/drag_spec.rb +2 -2
  61. data/spec/utils/keyboard_spec.rb +4 -1
  62. data/website/application.rb +4 -0
  63. data/website/config.ru +30 -0
  64. data/website/examples/content_editable.rb +29 -0
  65. data/website/examples/converter.rb +49 -0
  66. data/website/examples/icon_button.rb +20 -0
  67. data/website/examples/image_paragraph.rb +33 -0
  68. data/website/examples/my_blue_box.rb +9 -0
  69. data/website/examples/my_box.rb +9 -0
  70. data/website/examples/my_button.rb +27 -0
  71. data/website/examples/my_green_box.rb +14 -0
  72. data/website/examples/source_reader.rb +32 -0
  73. data/website/examples/text_area.rb +42 -0
  74. data/website/pages/components.md.erb +16 -0
  75. data/website/pages/components/composition.md.erb +9 -0
  76. data/website/pages/components/events.md.erb +20 -0
  77. data/website/pages/components/inheritance.md.erb +19 -0
  78. data/website/pages/components/routes.md.erb +49 -0
  79. data/website/pages/components/styles.md.erb +38 -0
  80. data/website/pages/getting-started.md +8 -0
  81. data/website/pages/home.md +4 -0
  82. data/website/pages/intro.md +30 -0
  83. data/website/pages/utilities.md +10 -0
  84. data/website/pages/utilities/local-storage.md.erb +16 -0
  85. data/website/pages/utilities/request.md.erb +12 -0
  86. data/website/setup.rb +162 -0
  87. data/website/vendor/highlight.js +2 -0
  88. data/website/vendor/highlight.ruby.js +1 -0
  89. data/website/vendor/marked.min.js +6 -0
  90. metadata +43 -7
@@ -0,0 +1,6 @@
1
+ # Time
2
+ class Time
3
+ def to_date
4
+ Date.new year, month, day
5
+ end
6
+ end
@@ -2,15 +2,18 @@ module DOM
2
2
  # This module is a wrapper for the native *document* object.
3
3
  module Document
4
4
  extend SingleForwardable
5
- @doc = DOM::Element.new `document`
6
5
 
7
- def_delegators :@doc, :find
6
+ def_delegators :doc, :find
8
7
 
9
8
  # Returns the active element
10
9
  #
11
10
  # @return [DOM::Element] The element
12
11
  def self.active_element
13
- find ':focus'
12
+ doc.find ':focus'
13
+ end
14
+
15
+ def self.doc
16
+ DOM::Element.new `document`
14
17
  end
15
18
 
16
19
  # Returns the head element
@@ -4,6 +4,7 @@ module DOM
4
4
  # TODO: Describe the element creation ways here
5
5
  class Element < NODE
6
6
  extend ElementAccessor
7
+
7
8
  include Attributes
8
9
  include ClassList
9
10
  include Dimensions
@@ -20,16 +21,26 @@ module DOM
20
21
  # Modifier regexp
21
22
  MODIFIER_REGEXP = /(#|\.)(.+?)(?=#|\.| |$)/
22
23
 
24
+ element_accessor :src
23
25
  element_accessor :value
24
26
  element_accessor :innerHTML, as: :html
25
27
 
26
- element_accessor :readonly, default: false
27
- element_accessor :checked, default: false
28
- element_accessor :disabled, default: false
28
+ element_accessor :readonly, default: false
29
+ element_accessor :checked, default: false
30
+ element_accessor :disabled, default: false
31
+ element_accessor :spellcheck, default: false
32
+ element_accessor :contentEditable, default: false, as: :contenteditable
33
+
34
+ element_accessor :scrollTop, as: :scroll_top, default: 0
35
+ element_accessor :scrollLeft, as: :scroll_left, default: 0
36
+ element_accessor :scrollWidth, as: :scroll_width, default: 0
37
+ element_accessor :scrollHeight, as: :scroll_height, default: 0
29
38
 
30
39
  element_method :focus
31
40
  element_method :blur
32
41
 
42
+ attribute_accessor :tabindex, default: nil
43
+
33
44
  # Initializes a new elment based on the data
34
45
  #
35
46
  # @param data [*] The data
@@ -38,21 +49,10 @@ module DOM
38
49
  tag, rest = data.match(TAG_REGEXP).to_a[1..2]
39
50
  @el = `document.createElement(#{tag})`
40
51
  `#{@el}._instance = #{self}`
41
- rest = rest.gsub ATTRIBUTE_REGEXP do |match|
42
- key, value = match.match(ATTRIBUTE_REGEXP).to_a[1..2]
43
- self[key] = value
44
- ''
45
- end
46
- rest = rest.gsub MODIFIER_REGEXP do |match|
47
- type, value = match.match(MODIFIER_REGEXP).to_a[1..2]
48
- case type
49
- when '#'
50
- self['id'] = value
51
- when '.'
52
- add_class value
53
- end
54
- ''
55
- end
52
+
53
+ rest = apply_attributes rest
54
+ rest = apply_modifiers rest
55
+
56
56
  if (match = rest.match(/\s(.+)$/))
57
57
  self.text = match[0].strip
58
58
  end
@@ -128,6 +128,10 @@ module DOM
128
128
  DOM::NodeList.new `Array.prototype.slice.call(#{@el}.querySelectorAll(#{selector}))`
129
129
  end
130
130
 
131
+ def active?
132
+ DOM::Document.active_element == self
133
+ end
134
+
131
135
  # Returns the next element sibling
132
136
  #
133
137
  # @return [DOM::Element] The element
@@ -149,7 +153,7 @@ module DOM
149
153
  #
150
154
  # @return [Boolean] True if contains false if not
151
155
  def include?(other)
152
- `#{@el}.contains(#{DOM::NODE.get_element(other)})`
156
+ `#{@el}.contains(#{DOM::NODE.get_element(other)})` || other == self
153
157
  end
154
158
 
155
159
  # Returns the path of the elemnt
@@ -164,5 +168,61 @@ module DOM
164
168
  end
165
169
  items.join ' '
166
170
  end
171
+
172
+ # Gets the z-index of the element
173
+ #
174
+ # @return [Number] The z-index
175
+ def z_index
176
+ `getComputedStyle(#{@el}).zIndex`.to_i
177
+ end
178
+
179
+ # Calls the click function on the element, this is needed for
180
+ # file inputs to programatically open the file browser.
181
+ #
182
+ # @return [Nil] Returns nothing
183
+ def click
184
+ `#{@el}.click() || Opal.nil`
185
+ end
186
+
187
+ def scroll_into_view
188
+ `#{@el}.scrollIntoView()`
189
+ end
190
+
191
+ def scroll_into_view_if_needed
192
+ `#{@el}.scrollIntoViewIfNeeded()`
193
+ end
194
+
195
+ private
196
+
197
+ # Applies attributes from the given string.
198
+ #
199
+ # @param string [String] The string
200
+ #
201
+ # @return [String] The string without the attributes
202
+ def apply_attributes(string)
203
+ string.gsub ATTRIBUTE_REGEXP do |match|
204
+ key, value = match.match(ATTRIBUTE_REGEXP).to_a[1..2]
205
+ self[key] = value
206
+ ''
207
+ end
208
+ end
209
+
210
+ # Applies modifiers from the given string.
211
+ #
212
+ # @param string [String] The string
213
+ #
214
+ # @return [String] The string without the modifiers
215
+ def apply_modifiers(string)
216
+ string.gsub MODIFIER_REGEXP do |match|
217
+ type, value = match.match(MODIFIER_REGEXP).to_a[1..2]
218
+ case type
219
+ when '#'
220
+ self['id'] = value
221
+ when '.'
222
+ add_class value
223
+ end
224
+ ''
225
+ end
226
+ end
167
227
  end
168
228
  end
@@ -110,7 +110,11 @@ module DOM
110
110
  #
111
111
  # @param name [String] The name of the method
112
112
  def method_missing(name)
113
- `#{@event}[#{name}]`
113
+ `#{@event}[#{name}] || Opal.NIL`
114
+ end
115
+
116
+ def data
117
+ JSON.from_object(`#{@event}.data`)
114
118
  end
115
119
 
116
120
  # Returns the string represenation of the pressed key
@@ -48,20 +48,6 @@ module DOM
48
48
  `#{client_rect}.height`
49
49
  end
50
50
 
51
- # Returns the top scroll position of the element
52
- #
53
- # @return [Number] The height
54
- def scroll_top
55
- `#{@el}.scrollTop`
56
- end
57
-
58
- # Returns the left scroll position of the element
59
- #
60
- # @return [Number] The height
61
- def scroll_left
62
- `#{@el}.scrollLeft`
63
- end
64
-
65
51
  # Returns whether or not the element covers the given position
66
52
  #
67
53
  # @param pos [Point] The point
@@ -11,6 +11,31 @@ module DOM
11
11
  end
12
12
  end
13
13
 
14
+ # Defines a attributes accessor that is delegated to the
15
+ # underlying elements gieven attribute.
16
+ #
17
+ # Options:
18
+ # * **as** - Use this instead of the attribute as identifier
19
+ # * **default** - Return this value if the attribute is null or undefined
20
+ # * **coerce** - Coerse the string value of the attribute with this method
21
+ #
22
+ # @param attribute [Symbol] The attribute
23
+ # @param options [Hash] The options
24
+ def attribute_accessor(attribute, options = {})
25
+ options = { as: attribute, default: nil, coerce: nil }.merge!(options)
26
+
27
+ define_method options[:as] do
28
+ value = self[attribute] || options[:default]
29
+ next value unless options[:coerce]
30
+ value.send(options[:coerce])
31
+ end
32
+
33
+ define_method "#{options[:as]}=" do |value|
34
+ self[attribute] = value
35
+ send('_attribute_changed', attribute, value) if respond_to?(:_attribute_changed)
36
+ end
37
+ end
38
+
14
39
  # Defines a property accessor that is delegated to the
15
40
  # underlying element.
16
41
  #
@@ -101,7 +101,7 @@ module DOM
101
101
  def add_listener(type, capture = false)
102
102
  method = `function(e){#{ yield Event.new(`e`)}}`
103
103
 
104
- @listeners ||= {}
104
+ @listeners ||= {}
105
105
  @listeners[type] ||= []
106
106
  @listeners[type] << method
107
107
 
@@ -1,7 +1,9 @@
1
1
  require 'native'
2
2
 
3
3
  module DOM
4
- # Node
4
+ # Low level wrapper for the Node class
5
+ #
6
+ # :reek:TooManyMethods
5
7
  class NODE
6
8
  include Events
7
9
  include Comparable
@@ -23,8 +25,8 @@ module DOM
23
25
  #
24
26
  # @param node [Native] The native node
25
27
  def initialize(node = nil)
26
- fail 'A node must be provided!' unless node
27
- fail 'Not a HTML Node given!' unless `#{node} instanceof Node`
28
+ raise 'A node must be provided!' unless node
29
+ raise 'Not a HTML Node given!' unless `#{node} instanceof Node`
28
30
  @el = node
29
31
  `#{@el}._instance = #{self}`
30
32
  end
@@ -108,7 +110,7 @@ module DOM
108
110
  # @param what [DOM::NODE] The node to insert
109
111
  # @param where [DOM::NODE] The other node
110
112
  def insert_before(what, where)
111
- return what >> self unless where # Fir for firefox...
113
+ return what >> self unless where # Fix for firefox...
112
114
  `#{@el}.insertBefore(#{NODE.get_element what},#{NODE.get_element where})`
113
115
  end
114
116
 
@@ -147,7 +149,7 @@ module DOM
147
149
  # @return [Boolean] The compareable with the other node
148
150
  def <=>(other)
149
151
  return 0 if other == self
150
- fail 'Nodes not Comparable!' if other.parent != parent
152
+ raise 'Nodes not Comparable!' if other.parent != parent
151
153
  other.index <=> index
152
154
  end
153
155
 
@@ -1,5 +1,3 @@
1
- # rubocop:disable MethodName
2
-
3
1
  module DOM
4
2
  # Style
5
3
  class Style
@@ -13,6 +13,20 @@ module DOM
13
13
  timeout { trigger 'popstate' }
14
14
  end
15
15
 
16
+ def self.replace_state(url)
17
+ return if url == state
18
+ `window.history.replaceState({},'',#{url})`
19
+ timeout { trigger 'popstate' }
20
+ end
21
+
22
+ def self.width
23
+ `window.innerWidth`
24
+ end
25
+
26
+ def self.height
27
+ `window.innerHeight`
28
+ end
29
+
16
30
  # Returns the locations pathname as state
17
31
  #
18
32
  # @return [String] The pathname
@@ -10,6 +10,13 @@ module EventMock
10
10
  # @return [Boolean] The verbosity
11
11
  attr_accessor :verbose
12
12
 
13
+ # Sets / gets the whether or not
14
+ # to mock events.
15
+ #
16
+ # @param value [Boolean] To mock or not
17
+ # @return [Boolean] The value
18
+ attr_accessor :mock
19
+
13
20
  # Triggers a syntetic event.
14
21
  #
15
22
  # @param element [DOM::Element] The element
@@ -35,20 +42,31 @@ module EventMock
35
42
  end
36
43
  end
37
44
  return unless `#{event}.propagate`
45
+ return unless element.respond_to?(:parent)
38
46
  return unless element.parent
39
47
  dispath_event element.parent, event, type
40
48
  end
41
49
 
42
50
  # Mocks triggers on DOM::Events
43
51
  def mock_events
44
- DOM::Events.alias_method :old_trigger, :trigger
45
- DOM::Events.define_method :trigger do |type, data = {}|
46
- puts "Triggered syntetic event \"#{type}\" for \"#{path}\"" if EventMock.verbose
47
- EventMock.trigger_event self, type, data
48
- end
52
+ @mock = true
49
53
  yield
50
54
  ensure
51
- DOM::Events.alias_method :trigger, :old_trigger
55
+ @mock = false
56
+ end
57
+ end
58
+ end
59
+
60
+ module DOM
61
+ # Events
62
+ module Events
63
+ # The old trigger method
64
+ alias old_trigger trigger
65
+
66
+ def trigger(type, data = {})
67
+ return old_trigger(type, data) if EventMock.mock
68
+ puts "Triggered syntetic event \"#{type}\" for \"#{path}\"" if EventMock.verbose
69
+ EventMock.trigger_event self, type, data
52
70
  end
53
71
  end
54
72
  end
@@ -0,0 +1,27 @@
1
+ if (!Element.prototype.scrollIntoViewIfNeeded) {
2
+ Element.prototype.scrollIntoViewIfNeeded = function (centerIfNeeded) {
3
+ centerIfNeeded = arguments.length === 0 ? true : !!centerIfNeeded;
4
+
5
+ var parent = this.parentNode,
6
+ parentComputedStyle = window.getComputedStyle(parent, null),
7
+ parentBorderTopWidth = parseInt(parentComputedStyle.getPropertyValue('border-top-width')),
8
+ parentBorderLeftWidth = parseInt(parentComputedStyle.getPropertyValue('border-left-width')),
9
+ overTop = this.offsetTop - parent.offsetTop < parent.scrollTop,
10
+ overBottom = (this.offsetTop - parent.offsetTop + this.clientHeight - parentBorderTopWidth) > (parent.scrollTop + parent.clientHeight),
11
+ overLeft = this.offsetLeft - parent.offsetLeft < parent.scrollLeft,
12
+ overRight = (this.offsetLeft - parent.offsetLeft + this.clientWidth - parentBorderLeftWidth) > (parent.scrollLeft + parent.clientWidth),
13
+ alignWithTop = overTop && !overBottom;
14
+
15
+ if ((overTop || overBottom) && centerIfNeeded) {
16
+ parent.scrollTop = this.offsetTop - parent.offsetTop - parent.clientHeight / 2 - parentBorderTopWidth + this.clientHeight / 2;
17
+ }
18
+
19
+ if ((overLeft || overRight) && centerIfNeeded) {
20
+ parent.scrollLeft = this.offsetLeft - parent.offsetLeft - parent.clientWidth / 2 - parentBorderLeftWidth + this.clientWidth / 2;
21
+ }
22
+
23
+ if ((overTop || overBottom || overLeft || overRight) && !centerIfNeeded) {
24
+ this.scrollIntoView(alignWithTop);
25
+ }
26
+ };
27
+ }
@@ -1,16 +1,23 @@
1
- SynteticEvent = function(target, data){
2
- this.target = target
3
- this.defaultPrevented = false
4
- this.immediatePropagate = true
5
- this.propagate = true
6
- this.data = data
7
- }
1
+ var SynteticEvent = function(target, data){
2
+ this.immediatePropagate = true;
3
+ this.defaultPrevented = false;
4
+ this.propagate = true;
5
+ this.target = target;
6
+ for (var key in data) {
7
+ this[key] = data[key];
8
+ }
9
+ };
10
+
8
11
  SynteticEvent.prototype.stopImmediatePropagation = function(){
9
- this.immediatePropagate = false
10
- }
12
+ this.immediatePropagate = false;
13
+ };
14
+
11
15
  SynteticEvent.prototype.stopPropagation = function(){
12
- this.propagate = false
13
- }
16
+ this.propagate = false;
17
+ };
18
+
14
19
  SynteticEvent.prototype.preventDefault = function(){
15
- this.defaultPrevented = true
16
- }
20
+ this.defaultPrevented = true;
21
+ };
22
+
23
+ window.SynteticEvent = SynteticEvent;
@@ -1,7 +1,7 @@
1
1
  require 'json'
2
2
 
3
3
  module Fron
4
- # Request
4
+ # Low level wrapper for the XMLHTTPRequest class
5
5
  class Request
6
6
  extend Eventable
7
7
  extend Forwardable
@@ -10,13 +10,13 @@ module Fron
10
10
  #
11
11
  # @param value [String] The URL
12
12
  # @return [String] The URL
13
- attr_accessor :url
13
+ attr_reader :url
14
14
 
15
15
  # Sets / gets the headers for the request
16
16
  #
17
17
  # @param value [Hash] The headers
18
18
  # @return [Hash] The headers
19
- attr_accessor :headers
19
+ attr_reader :headers
20
20
 
21
21
  def_delegators :class, :trigger
22
22
 
@@ -38,21 +38,23 @@ module Fron
38
38
  # @param data [Hash] The data
39
39
  #
40
40
  # @yieldparam response [Response] The response
41
- def request(method = 'GET', data = nil, &callback)
42
- if ready_state == 0 || ready_state == 4
43
- @callback = callback
44
- if method.upcase == 'UPLOAD'
45
- send 'POST', @url, data.to_form_data
46
- elsif method.upcase == 'GET' && data
47
- send method, @url + '?' + data.to_query_string.to_s, nil
48
- else
49
- data = data.to_json if data
50
- send method, @url, data
51
- end
52
- trigger :loading
53
- else
54
- fail 'The request is already running!'
55
- end
41
+ def request(method = 'GET', data = {}, &callback)
42
+ raise 'The request is already running!' if ready_state != 0 && ready_state != 4
43
+ method = method.upcase
44
+ @callback = callback
45
+
46
+ args = case method
47
+ when 'UPLOAD'
48
+ ['POST', @url, data.to_form_data]
49
+ when 'GET'
50
+ [method, "#{@url}?#{data.to_query_string}"]
51
+ else
52
+ [method, @url, data.to_json]
53
+ end
54
+
55
+ send(*args)
56
+
57
+ trigger :loading
56
58
  end
57
59
 
58
60
  # Runs a GET request
@@ -90,7 +92,7 @@ module Fron
90
92
  # @param method [String] The method
91
93
  # @param url [String] The URL
92
94
  # @param data [*] The data
93
- def send(method, url, data)
95
+ def send(method, url, data = nil)
94
96
  `#{@request}.open(#{method}, #{url})`
95
97
  `#{@request}.withCredentials = true`
96
98
  set_headers