browserio 0.0.1 → 0.0.2
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 +4 -4
- data/Gemfile +2 -0
- data/LICENSE.txt +1 -1
- data/README.md +23 -5
- data/Rakefile +8 -1
- data/browserio.gemspec +14 -6
- data/lib/browserio.rb +183 -3
- data/lib/browserio/component.rb +269 -0
- data/lib/browserio/config.rb +113 -0
- data/lib/browserio/dom.rb +139 -0
- data/lib/browserio/events.rb +136 -0
- data/lib/browserio/html.rb +29 -0
- data/lib/browserio/opal.rb +18 -0
- data/lib/browserio/plugins/form.rb +343 -0
- data/lib/browserio/plugins/history.rb +92 -0
- data/lib/browserio/plugins/location.rb +78 -0
- data/lib/browserio/plugins/pjax.rb +65 -0
- data/lib/browserio/plugins/validations.rb +251 -0
- data/lib/browserio/utilis/blank.rb +133 -0
- data/lib/browserio/utilis/hash.rb +77 -0
- data/lib/browserio/utilis/indifferent_hash.rb +209 -0
- data/lib/browserio/utilis/methods.rb +25 -0
- data/lib/browserio/utilis/titleize.rb +97 -0
- data/lib/browserio/utilis/try.rb +106 -0
- data/lib/browserio/version.rb +1 -1
- data/lib/roda/plugins/browserio.rb +63 -0
- data/test/dummy/app.rb +34 -0
- data/test/dummy/components/bar.rb +16 -0
- data/test/dummy/components/root.rb +39 -0
- data/test/dummy/config.ru +6 -0
- data/test/dummy/forms/foo.rb +6 -0
- data/test/test.js +59 -0
- data/test/test_basic_component.rb +34 -0
- data/test/test_browserio.rb +13 -0
- data/test/test_helper.rb +38 -0
- metadata +152 -17
@@ -0,0 +1,133 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/object/blank.rb
|
4
|
+
|
5
|
+
class Object
|
6
|
+
# An object is blank if it's false, empty, or a whitespace string.
|
7
|
+
# For example, '', ' ', +nil+, [], and {} are all blank.
|
8
|
+
#
|
9
|
+
# This simplifies
|
10
|
+
#
|
11
|
+
# address.nil? || address.empty?
|
12
|
+
#
|
13
|
+
# to
|
14
|
+
#
|
15
|
+
# address.blank?
|
16
|
+
#
|
17
|
+
# @return [true, false]
|
18
|
+
def blank?
|
19
|
+
respond_to?(:empty?) ? !!empty? : !self
|
20
|
+
end
|
21
|
+
|
22
|
+
# An object is present if it's not blank.
|
23
|
+
#
|
24
|
+
# @return [true, false]
|
25
|
+
def present?
|
26
|
+
!blank?
|
27
|
+
end
|
28
|
+
|
29
|
+
# Returns the receiver if it's present otherwise returns +nil+.
|
30
|
+
# <tt>object.presence</tt> is equivalent to
|
31
|
+
#
|
32
|
+
# object.present? ? object : nil
|
33
|
+
#
|
34
|
+
# For example, something like
|
35
|
+
#
|
36
|
+
# state = params[:state] if params[:state].present?
|
37
|
+
# country = params[:country] if params[:country].present?
|
38
|
+
# region = state || country || 'US'
|
39
|
+
#
|
40
|
+
# becomes
|
41
|
+
#
|
42
|
+
# region = params[:state].presence || params[:country].presence || 'US'
|
43
|
+
#
|
44
|
+
# @return [Object]
|
45
|
+
def presence
|
46
|
+
self if present?
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class NilClass
|
51
|
+
# +nil+ is blank:
|
52
|
+
#
|
53
|
+
# nil.blank? # => true
|
54
|
+
#
|
55
|
+
# @return [true]
|
56
|
+
def blank?
|
57
|
+
true
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class FalseClass
|
62
|
+
# +false+ is blank:
|
63
|
+
#
|
64
|
+
# false.blank? # => true
|
65
|
+
#
|
66
|
+
# @return [true]
|
67
|
+
def blank?
|
68
|
+
true
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
class TrueClass
|
73
|
+
# +true+ is not blank:
|
74
|
+
#
|
75
|
+
# true.blank? # => false
|
76
|
+
#
|
77
|
+
# @return [false]
|
78
|
+
def blank?
|
79
|
+
false
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
class Array
|
84
|
+
# An array is blank if it's empty:
|
85
|
+
#
|
86
|
+
# [].blank? # => true
|
87
|
+
# [1,2,3].blank? # => false
|
88
|
+
#
|
89
|
+
# @return [true, false]
|
90
|
+
alias_method :blank?, :empty?
|
91
|
+
end
|
92
|
+
|
93
|
+
class Hash
|
94
|
+
# A hash is blank if it's empty:
|
95
|
+
#
|
96
|
+
# {}.blank? # => true
|
97
|
+
# { key: 'value' }.blank? # => false
|
98
|
+
#
|
99
|
+
# @return [true, false]
|
100
|
+
alias_method :blank?, :empty?
|
101
|
+
end
|
102
|
+
|
103
|
+
class String
|
104
|
+
BLANK_RE = /\A[[:space:]]*\z/
|
105
|
+
|
106
|
+
# A string is blank if it's empty or contains whitespaces only:
|
107
|
+
#
|
108
|
+
# ''.blank? # => true
|
109
|
+
# ' '.blank? # => true
|
110
|
+
# "\t\n\r".blank? # => true
|
111
|
+
# ' blah '.blank? # => false
|
112
|
+
#
|
113
|
+
# Unicode whitespace is supported:
|
114
|
+
#
|
115
|
+
# "\u00a0".blank? # => true
|
116
|
+
#
|
117
|
+
# @return [true, false]
|
118
|
+
def blank?
|
119
|
+
BLANK_RE === self
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
class Numeric #:nodoc:
|
124
|
+
# No number is blank:
|
125
|
+
#
|
126
|
+
# 1.blank? # => false
|
127
|
+
# 0.blank? # => false
|
128
|
+
#
|
129
|
+
# @return [false]
|
130
|
+
def blank?
|
131
|
+
false
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
class Hash
|
2
|
+
# add keys to hash
|
3
|
+
def to_obj
|
4
|
+
self.each do |k,v|
|
5
|
+
if v.kind_of? Hash
|
6
|
+
v.to_obj
|
7
|
+
end
|
8
|
+
k=k.to_s.gsub(/\.|\s|-|\/|\'/, '_').downcase.to_sym
|
9
|
+
|
10
|
+
## create and initialize an instance variable for this key/value pair
|
11
|
+
self.instance_variable_set("@#{k}", v)
|
12
|
+
|
13
|
+
## create the getter that returns the instance variable
|
14
|
+
self.class.send(:define_method, k, proc{self.instance_variable_get("@#{k}")})
|
15
|
+
|
16
|
+
## create the setter that sets the instance variable
|
17
|
+
self.class.send(:define_method, "#{k}=", proc{|v| self.instance_variable_set("@#{k}", v)})
|
18
|
+
end
|
19
|
+
return self
|
20
|
+
end
|
21
|
+
|
22
|
+
def deep_merge(second)
|
23
|
+
merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 }
|
24
|
+
self.merge(second, &merger)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Slice a hash to include only the given keys. Returns a hash containing
|
28
|
+
# the given keys.
|
29
|
+
#
|
30
|
+
# { a: 1, b: 2, c: 3, d: 4 }.slice(:a, :b)
|
31
|
+
# # => {:a=>1, :b=>2}
|
32
|
+
#
|
33
|
+
# This is useful for limiting an options hash to valid keys before
|
34
|
+
# passing to a method:
|
35
|
+
#
|
36
|
+
# def search(criteria = {})
|
37
|
+
# criteria.assert_valid_keys(:mass, :velocity, :time)
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# search(options.slice(:mass, :velocity, :time))
|
41
|
+
#
|
42
|
+
# If you have an array of keys you want to limit to, you should splat them:
|
43
|
+
#
|
44
|
+
# valid_keys = [:mass, :velocity, :time]
|
45
|
+
# search(options.slice(*valid_keys))
|
46
|
+
def slice(*keys)
|
47
|
+
keys.map! { |key| convert_key(key) } if respond_to?(:convert_key, true)
|
48
|
+
keys.each_with_object(self.class.new) { |k, hash| hash[k] = self[k] if has_key?(k) }
|
49
|
+
end
|
50
|
+
|
51
|
+
# Replaces the hash with only the given keys.
|
52
|
+
# Returns a hash containing the removed key/value pairs.
|
53
|
+
#
|
54
|
+
# { a: 1, b: 2, c: 3, d: 4 }.slice!(:a, :b)
|
55
|
+
# # => {:c=>3, :d=>4}
|
56
|
+
def slice!(*keys)
|
57
|
+
keys.map! { |key| convert_key(key) } if respond_to?(:convert_key, true)
|
58
|
+
omit = slice(*self.keys - keys)
|
59
|
+
hash = slice(*keys)
|
60
|
+
hash.default = default
|
61
|
+
hash.default_proc = default_proc if default_proc
|
62
|
+
replace(hash)
|
63
|
+
omit
|
64
|
+
end
|
65
|
+
|
66
|
+
# Removes and returns the key/value pairs matching the given keys.
|
67
|
+
#
|
68
|
+
# { a: 1, b: 2, c: 3, d: 4 }.extract!(:a, :b) # => {:a=>1, :b=>2}
|
69
|
+
# { a: 1, b: 2 }.extract!(:a, :x) # => {:a=>1}
|
70
|
+
def extract!(*keys)
|
71
|
+
keys.each_with_object(self.class.new) { |key, result| result[key] = delete(key) if has_key?(key) }
|
72
|
+
end
|
73
|
+
|
74
|
+
def indifferent
|
75
|
+
BrowserIO::IndifferentHash.new self
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,209 @@
|
|
1
|
+
module BrowserIO
|
2
|
+
class IndifferentHash < Hash
|
3
|
+
|
4
|
+
def initialize(constructor = {}, &block)
|
5
|
+
if block_given?
|
6
|
+
yield self
|
7
|
+
elsif constructor.is_a?(Hash)
|
8
|
+
super()
|
9
|
+
update(constructor)
|
10
|
+
else
|
11
|
+
super(constructor)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
alias_method :get_value, :[] unless method_defined?(:get_value)
|
16
|
+
alias_method :set_value, :[]= unless method_defined?(:set_value)
|
17
|
+
|
18
|
+
def ==(other_hash)
|
19
|
+
to_hash == self.class.new(other_hash).to_hash
|
20
|
+
end
|
21
|
+
|
22
|
+
# Assigns a new value to the hash:
|
23
|
+
#
|
24
|
+
# hash = IndifferentHash.new
|
25
|
+
# hash[:key] = "value"
|
26
|
+
#
|
27
|
+
def []=(key, value)
|
28
|
+
set_value convert_key(key), navigate(value)
|
29
|
+
end
|
30
|
+
|
31
|
+
alias :store :[]=
|
32
|
+
|
33
|
+
def [](key)
|
34
|
+
get_value convert_key(key)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Removes a specified key from the hash.
|
38
|
+
def delete(key)
|
39
|
+
super(convert_key(key))
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns an exact copy of the hash.
|
43
|
+
def dup
|
44
|
+
self.class.new to_hash
|
45
|
+
end
|
46
|
+
|
47
|
+
# Same as <tt>Hash#fetch</tt> where the key passed as argument can be
|
48
|
+
# either a string or a symbol:
|
49
|
+
#
|
50
|
+
# counters = IndifferentHash.new
|
51
|
+
# counters[:foo] = 1
|
52
|
+
#
|
53
|
+
# counters.fetch("foo") # => 1
|
54
|
+
# counters.fetch(:bar, 0) # => 0
|
55
|
+
# counters.fetch(:bar) {|key| 0} # => 0
|
56
|
+
# counters.fetch(:zoo) # => KeyError: key not found: "zoo"
|
57
|
+
#
|
58
|
+
def fetch(key, *extras)
|
59
|
+
super(convert_key(key), *extras)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Checks the hash for a key matching the argument passed in:
|
63
|
+
#
|
64
|
+
# hash = IndifferentHash.new
|
65
|
+
# hash["key"] = "value"
|
66
|
+
# hash.key? :key # => true
|
67
|
+
# hash.key? "key" # => true
|
68
|
+
#
|
69
|
+
def key?(key)
|
70
|
+
super(convert_key(key))
|
71
|
+
end
|
72
|
+
|
73
|
+
alias_method :include?, :key?
|
74
|
+
alias_method :has_key?, :key?
|
75
|
+
alias_method :member?, :key?
|
76
|
+
|
77
|
+
# Merges the instantiated and the specified hashes together, giving precedence to the values from the second hash.
|
78
|
+
# Does not overwrite the existing hash.
|
79
|
+
def merge(hash)
|
80
|
+
self.dup.update(hash)
|
81
|
+
end
|
82
|
+
|
83
|
+
def respond_to?(m, include_private = false)
|
84
|
+
has_key?(m) || super
|
85
|
+
end
|
86
|
+
|
87
|
+
def to_hash
|
88
|
+
reduce({}) do |hash, (key, value)|
|
89
|
+
hash.merge key.to_sym => convert_for_to_hash(value)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
alias_method :to_h, :to_hash
|
93
|
+
|
94
|
+
# Updates the instantized hash with values from the second:
|
95
|
+
#
|
96
|
+
# hash_1 = IndifferentHash.new
|
97
|
+
# hash_1[:key] = "value"
|
98
|
+
#
|
99
|
+
# hash_2 = IndifferentHash.new
|
100
|
+
# hash_2[:key] = "New Value!"
|
101
|
+
#
|
102
|
+
# hash_1.update(hash_2) # => {"key"=>"New Value!"}
|
103
|
+
#
|
104
|
+
def update(other_hash)
|
105
|
+
other_hash.reduce(self) { |hash, (k, v)| hash[k] = navigate(v) ; hash }
|
106
|
+
end
|
107
|
+
|
108
|
+
alias_method :merge!, :update
|
109
|
+
|
110
|
+
# Returns an array of the values at the specified indices:
|
111
|
+
#
|
112
|
+
# hash = IndifferentHash.new
|
113
|
+
# hash[:a] = "x"
|
114
|
+
# hash[:b] = "y"
|
115
|
+
# hash.values_at("a", "b") # => ["x", "y"]
|
116
|
+
#
|
117
|
+
def values_at(*indices)
|
118
|
+
indices.collect {|key| self[convert_key(key)]}
|
119
|
+
end
|
120
|
+
|
121
|
+
protected :get_value, :set_value
|
122
|
+
|
123
|
+
def convert_for_to_hash(value)
|
124
|
+
case value
|
125
|
+
when IndifferentHash
|
126
|
+
convert_indifferent_hash_for_to_hash value
|
127
|
+
when Array
|
128
|
+
convert_array_for_to_hash value
|
129
|
+
else
|
130
|
+
convert_value_for_to_hash value
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def convert_key(key)
|
135
|
+
key.kind_of?(Symbol) ? key.to_s : key
|
136
|
+
end
|
137
|
+
|
138
|
+
def convert_indifferent_hash_for_to_hash(value)
|
139
|
+
value.to_hash
|
140
|
+
end
|
141
|
+
|
142
|
+
def convert_array_for_to_hash(value)
|
143
|
+
value.map { |item| convert_for_to_hash item }
|
144
|
+
end
|
145
|
+
|
146
|
+
def convert_value_for_to_hash(value)
|
147
|
+
value
|
148
|
+
end
|
149
|
+
|
150
|
+
def navigate value
|
151
|
+
case value
|
152
|
+
when self.class
|
153
|
+
value
|
154
|
+
when Hash
|
155
|
+
navigate_hash value
|
156
|
+
when Array
|
157
|
+
navigate_array value
|
158
|
+
else
|
159
|
+
navigate_value value
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def navigate_hash(value)
|
164
|
+
self.class.new value
|
165
|
+
end
|
166
|
+
|
167
|
+
def navigate_array(value)
|
168
|
+
value.map { |item| navigate item }
|
169
|
+
end
|
170
|
+
|
171
|
+
def navigate_value(value)
|
172
|
+
value
|
173
|
+
end
|
174
|
+
|
175
|
+
def navigate_hash_from_block(key, &block)
|
176
|
+
self[key] = self.class.new &block
|
177
|
+
end
|
178
|
+
|
179
|
+
private
|
180
|
+
|
181
|
+
def set_and_cache_value(key, value)
|
182
|
+
cache_getter! key
|
183
|
+
self[key] = value
|
184
|
+
end
|
185
|
+
|
186
|
+
def get_and_cache_value(key)
|
187
|
+
cache_getter! key
|
188
|
+
self[key]
|
189
|
+
end
|
190
|
+
|
191
|
+
def cache_getter!(key)
|
192
|
+
define_singleton_method(key) { self[key] } unless respond_to? key
|
193
|
+
end
|
194
|
+
|
195
|
+
def method_missing(m, *args, &block)
|
196
|
+
m = m.to_s
|
197
|
+
if m.chomp!('=') && args.count == 1
|
198
|
+
set_and_cache_value(m, *args)
|
199
|
+
elsif args.empty? && block_given?
|
200
|
+
self.navigate_hash_from_block m, &block
|
201
|
+
elsif args.empty?
|
202
|
+
get_and_cache_value(m)
|
203
|
+
else
|
204
|
+
fail ArgumentError, "wrong number of arguments (#{args.count} for 0)"
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
end
|
209
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module BrowserIO
|
2
|
+
module Methods
|
3
|
+
def self.included(base)
|
4
|
+
base.extend(ClassMethods)
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def client?
|
9
|
+
RUBY_ENGINE == 'opal'
|
10
|
+
end
|
11
|
+
|
12
|
+
def server?
|
13
|
+
RUBY_ENGINE == 'ruby'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def server?
|
18
|
+
self.class.server?
|
19
|
+
end
|
20
|
+
|
21
|
+
def client?
|
22
|
+
self.class.client?
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
# Adds String#titleize for creating properly capitalized titles.
|
3
|
+
# It can be called as Titleize.titleize or "a string".titleize.
|
4
|
+
#
|
5
|
+
# titlecase is included as an alias for titleize.
|
6
|
+
#
|
7
|
+
# If loaded in a Rails environment, it modifies Inflector.titleize.
|
8
|
+
module Titleize
|
9
|
+
SMALL_WORDS = %w{a an and as at but by en for if in of on or the to v v. via vs vs.}
|
10
|
+
|
11
|
+
extend self
|
12
|
+
|
13
|
+
# Capitalizes most words to create a nicer looking title string.
|
14
|
+
#
|
15
|
+
# The list of "small words" which are not capped comes from
|
16
|
+
# the New York Times Manual of Style, plus 'vs' and 'v'.
|
17
|
+
#
|
18
|
+
# "notes on a scandal" # => "Notes on a Scandal"
|
19
|
+
# "the good german" # => "The Good German"
|
20
|
+
def titleize(title)
|
21
|
+
title = title.dup
|
22
|
+
title.downcase! unless title[/[[:lower:]]/] # assume all-caps need fixing
|
23
|
+
|
24
|
+
phrases(title).map do |phrase|
|
25
|
+
words = phrase.split
|
26
|
+
words.map do |word|
|
27
|
+
def word.capitalize
|
28
|
+
# like String#capitalize, but it starts with the first letter
|
29
|
+
self.sub(/[[:alpha:]].*/) {|subword| subword.capitalize}
|
30
|
+
end
|
31
|
+
|
32
|
+
case word
|
33
|
+
when /[[:alpha:]]\.[[:alpha:]]/ # words with dots in, like "example.com"
|
34
|
+
word
|
35
|
+
when /[-‑]/ # hyphenated word (regular and non-breaking)
|
36
|
+
word.split(/([-‑])/).map do |part|
|
37
|
+
SMALL_WORDS.include?(part) ? part : part.capitalize
|
38
|
+
end.join
|
39
|
+
when /^[[:alpha:]].*[[:upper:]]/ # non-first letter capitalized already
|
40
|
+
word
|
41
|
+
when /^[[:digit:]]/ # first character is a number
|
42
|
+
word
|
43
|
+
when words.first, words.last
|
44
|
+
word.capitalize
|
45
|
+
when *(SMALL_WORDS + SMALL_WORDS.map {|small| small.capitalize })
|
46
|
+
word.downcase
|
47
|
+
else
|
48
|
+
word.capitalize
|
49
|
+
end
|
50
|
+
end.join(" ")
|
51
|
+
end.join(" ")
|
52
|
+
end
|
53
|
+
|
54
|
+
# Splits a title into an array based on punctuation.
|
55
|
+
#
|
56
|
+
# "simple title" # => ["simple title"]
|
57
|
+
# "more complicated: titling" # => ["more complicated:", "titling"]
|
58
|
+
def phrases(title)
|
59
|
+
phrases = title.scan(/.+?(?:[:.;?!] |$)/).map {|phrase| phrase.strip }
|
60
|
+
|
61
|
+
# rejoin phrases that were split on the '.' from a small word
|
62
|
+
if phrases.size > 1
|
63
|
+
phrases[0..-2].each_with_index do |phrase, index|
|
64
|
+
if SMALL_WORDS.include?(phrase.split.last.downcase)
|
65
|
+
phrases[index] << " " + phrases.slice!(index + 1)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
phrases
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
class String
|
75
|
+
# Capitalizes most words to create a nicer looking title string.
|
76
|
+
#
|
77
|
+
# The list of "small words" which are not capped comes from
|
78
|
+
# the New York Times Manual of Style, plus 'vs' and 'v'.
|
79
|
+
#
|
80
|
+
# titleize is also aliased as titlecase.
|
81
|
+
#
|
82
|
+
# "notes on a scandal" # => "Notes on a Scandal"
|
83
|
+
# "the good german" # => "The Good German"
|
84
|
+
def titleize(opts={})
|
85
|
+
# if defined? ActiveSupport
|
86
|
+
# ActiveSupport::Inflector.titleize(self, opts)
|
87
|
+
# else
|
88
|
+
Titleize.titleize(self)
|
89
|
+
# end
|
90
|
+
end
|
91
|
+
alias_method :titlecase, :titleize
|
92
|
+
|
93
|
+
def titleize!
|
94
|
+
replace(titleize)
|
95
|
+
end
|
96
|
+
alias_method :titlecase!, :titleize!
|
97
|
+
end
|