wedgeio 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/Gemfile +9 -0
- data/LICENSE.txt +22 -0
- data/README.md +49 -0
- data/Rakefile +9 -0
- data/lib/roda/plugins/wedge.rb +93 -0
- data/lib/wedge.rb +238 -0
- data/lib/wedge/component.rb +321 -0
- data/lib/wedge/config.rb +128 -0
- data/lib/wedge/dom.rb +139 -0
- data/lib/wedge/events.rb +136 -0
- data/lib/wedge/html.rb +29 -0
- data/lib/wedge/opal.rb +18 -0
- data/lib/wedge/plugins/form.rb +431 -0
- data/lib/wedge/plugins/history.rb +92 -0
- data/lib/wedge/plugins/location.rb +78 -0
- data/lib/wedge/plugins/pjax.rb +65 -0
- data/lib/wedge/plugins/validations.rb +251 -0
- data/lib/wedge/utilis/blank.rb +133 -0
- data/lib/wedge/utilis/element.rb +23 -0
- data/lib/wedge/utilis/hash.rb +77 -0
- data/lib/wedge/utilis/indifferent_hash.rb +209 -0
- data/lib/wedge/utilis/methods.rb +25 -0
- data/lib/wedge/utilis/nokogiri.rb +44 -0
- data/lib/wedge/utilis/titleize.rb +97 -0
- data/lib/wedge/utilis/try.rb +106 -0
- data/lib/wedge/version.rb +3 -0
- data/test.rb +44 -0
- data/test/dummy/app.rb +34 -0
- data/test/dummy/components/bar.rb +14 -0
- data/test/dummy/components/base.rb +5 -0
- data/test/dummy/components/root.rb +42 -0
- data/test/dummy/config.ru +6 -0
- data/test/dummy/forms/bar.rb +5 -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
- data/wedge.gemspec +32 -0
- metadata +236 -0
@@ -0,0 +1,44 @@
|
|
1
|
+
module Nokogiri
|
2
|
+
module XML
|
3
|
+
class NodeSet
|
4
|
+
# fix: this is really shity
|
5
|
+
# alias_method :original_to_html, :to_html
|
6
|
+
# def to_html *args
|
7
|
+
# original_to_html(*args).gsub('%7B', "{").gsub('%7D', "}")
|
8
|
+
# end
|
9
|
+
end
|
10
|
+
class Node
|
11
|
+
# fix: this is really shity
|
12
|
+
# alias_method :original_to_html, :to_html
|
13
|
+
# def to_html *args
|
14
|
+
# original_to_html(*args).gsub('%7B', "{").gsub('%7D', "}")
|
15
|
+
# end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def coerce data # :nodoc:
|
20
|
+
if data.class.to_s == 'Wedge::DOM'
|
21
|
+
data = data.dom
|
22
|
+
end
|
23
|
+
|
24
|
+
case data
|
25
|
+
when XML::NodeSet
|
26
|
+
return data
|
27
|
+
when XML::DocumentFragment
|
28
|
+
return data.children
|
29
|
+
when String
|
30
|
+
return fragment(data).children
|
31
|
+
when Document, XML::Attr
|
32
|
+
# unacceptable
|
33
|
+
when XML::Node
|
34
|
+
return data
|
35
|
+
end
|
36
|
+
|
37
|
+
raise ArgumentError, <<-EOERR
|
38
|
+
Requires a Node, NodeSet or String argument, and cannot accept a #{data.class}.
|
39
|
+
(You probably want to select a node from the Document with at() or search(), or create a new Node via Node.new().)
|
40
|
+
EOERR
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
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
|
@@ -0,0 +1,106 @@
|
|
1
|
+
class Object
|
2
|
+
# Invokes the public method whose name goes as first argument just like
|
3
|
+
# +public_send+ does, except that if the receiver does not respond to it the
|
4
|
+
# call returns +nil+ rather than raising an exception.
|
5
|
+
#
|
6
|
+
# This method is defined to be able to write
|
7
|
+
#
|
8
|
+
# @person.try(:name)
|
9
|
+
#
|
10
|
+
# instead of
|
11
|
+
#
|
12
|
+
# @person.name if @person
|
13
|
+
#
|
14
|
+
# +try+ calls can be chained:
|
15
|
+
#
|
16
|
+
# @person.try(:spouse).try(:name)
|
17
|
+
#
|
18
|
+
# instead of
|
19
|
+
#
|
20
|
+
# @person.spouse.name if @person && @person.spouse
|
21
|
+
#
|
22
|
+
# +try+ will also return +nil+ if the receiver does not respond to the method:
|
23
|
+
#
|
24
|
+
# @person.try(:non_existing_method) # => nil
|
25
|
+
#
|
26
|
+
# instead of
|
27
|
+
#
|
28
|
+
# @person.non_existing_method if @person.respond_to?(:non_existing_method) # => nil
|
29
|
+
#
|
30
|
+
# +try+ returns +nil+ when called on +nil+ regardless of whether it responds
|
31
|
+
# to the method:
|
32
|
+
#
|
33
|
+
# nil.try(:to_i) # => nil, rather than 0
|
34
|
+
#
|
35
|
+
# Arguments and blocks are forwarded to the method if invoked:
|
36
|
+
#
|
37
|
+
# @posts.try(:each_slice, 2) do |a, b|
|
38
|
+
# ...
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# The number of arguments in the signature must match. If the object responds
|
42
|
+
# to the method the call is attempted and +ArgumentError+ is still raised
|
43
|
+
# in case of argument mismatch.
|
44
|
+
#
|
45
|
+
# If +try+ is called without arguments it yields the receiver to a given
|
46
|
+
# block unless it is +nil+:
|
47
|
+
#
|
48
|
+
# @person.try do |p|
|
49
|
+
# ...
|
50
|
+
# end
|
51
|
+
#
|
52
|
+
# You can also call try with a block without accepting an argument, and the block
|
53
|
+
# will be instance_eval'ed instead:
|
54
|
+
#
|
55
|
+
# @person.try { upcase.truncate(50) }
|
56
|
+
#
|
57
|
+
# Please also note that +try+ is defined on +Object+. Therefore, it won't work
|
58
|
+
# with instances of classes that do not have +Object+ among their ancestors,
|
59
|
+
# like direct subclasses of +BasicObject+. For example, using +try+ with
|
60
|
+
# +SimpleDelegator+ will delegate +try+ to the target instead of calling it on
|
61
|
+
# the delegator itself.
|
62
|
+
def try(*a, &b)
|
63
|
+
try!(*a, &b) if a.empty? || respond_to?(a.first)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Same as #try, but raises a NoMethodError exception if the receiver is
|
67
|
+
# not +nil+ and does not implement the tried method.
|
68
|
+
#
|
69
|
+
# "a".try!(:upcase) # => "A"
|
70
|
+
# nil.try!(:upcase) # => nil
|
71
|
+
# 123.try!(:upcase) # => NoMethodError: undefined method `upcase' for 123:Fixnum
|
72
|
+
def try!(*a, &b)
|
73
|
+
if a.empty? && block_given?
|
74
|
+
if b.arity.zero?
|
75
|
+
instance_eval(&b)
|
76
|
+
else
|
77
|
+
yield self
|
78
|
+
end
|
79
|
+
else
|
80
|
+
public_send(*a, &b)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
class NilClass
|
86
|
+
# Calling +try+ on +nil+ always returns +nil+.
|
87
|
+
# It becomes especially helpful when navigating through associations that may return +nil+.
|
88
|
+
#
|
89
|
+
# nil.try(:name) # => nil
|
90
|
+
#
|
91
|
+
# Without +try+
|
92
|
+
# @person && @person.children.any? && @person.children.first.name
|
93
|
+
#
|
94
|
+
# With +try+
|
95
|
+
# @person.try(:children).try(:first).try(:name)
|
96
|
+
def try(*args)
|
97
|
+
nil
|
98
|
+
end
|
99
|
+
|
100
|
+
# Calling +try!+ on +nil+ always returns +nil+.
|
101
|
+
#
|
102
|
+
# nil.try!(:name) # => nil
|
103
|
+
def try!(*args)
|
104
|
+
nil
|
105
|
+
end
|
106
|
+
end
|
data/test.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'awesome_print'
|
2
|
+
|
3
|
+
requires = {
|
4
|
+
form_plugin: [],
|
5
|
+
list: [
|
6
|
+
{
|
7
|
+
name: 'claim',
|
8
|
+
requires: [
|
9
|
+
{name: 'filter_form', requires: [{ name: 'form_plugin', requires: [] }]},
|
10
|
+
{name: 'claim_form', requires: []}
|
11
|
+
],
|
12
|
+
},
|
13
|
+
{
|
14
|
+
name: 'claim_form',
|
15
|
+
requires: [
|
16
|
+
{name: 'form_plugin', requires: []},
|
17
|
+
{name: 'address_form', requires: [{ name: 'form_plugin', requires: [] }]}
|
18
|
+
],
|
19
|
+
}
|
20
|
+
]
|
21
|
+
}
|
22
|
+
|
23
|
+
$loaded_requires = []
|
24
|
+
|
25
|
+
def get_requires reqs, requires_array = []
|
26
|
+
new_reqs = []
|
27
|
+
|
28
|
+
reqs.each do |r|
|
29
|
+
if r[:requires].any?
|
30
|
+
get_requires(r[:requires], requires_array)
|
31
|
+
end
|
32
|
+
|
33
|
+
unless $loaded_requires.include? r[:name]
|
34
|
+
$loaded_requires << r[:name]
|
35
|
+
new_reqs << r[:name]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
requires_array << new_reqs if new_reqs.any?
|
40
|
+
|
41
|
+
requires_array
|
42
|
+
end
|
43
|
+
|
44
|
+
ap get_requires requires[:list]
|
data/test/dummy/app.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'wedge'
|
2
|
+
require 'roda'
|
3
|
+
|
4
|
+
require 'pry'
|
5
|
+
require 'awesome_print'
|
6
|
+
|
7
|
+
ROOT_PATH = File.dirname(__FILE__)
|
8
|
+
|
9
|
+
class DummyApp < Roda
|
10
|
+
plugin :wedge, {
|
11
|
+
scope: self,
|
12
|
+
plugins: [:form]
|
13
|
+
}
|
14
|
+
|
15
|
+
plugin :assets, {
|
16
|
+
group_subdirs: false,
|
17
|
+
path: ROOT_PATH,
|
18
|
+
css_dir: '',
|
19
|
+
js_dir: ''
|
20
|
+
}
|
21
|
+
|
22
|
+
route do |r|
|
23
|
+
r.wedge_assets
|
24
|
+
|
25
|
+
r.root do
|
26
|
+
wedge(:root, :js).display
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
Dir["#{ROOT_PATH}/forms/*.rb"].sort.each { |file| require file }
|
32
|
+
Dir["#{ROOT_PATH}/components/*.rb"].sort.each { |file| require file }
|
33
|
+
|
34
|
+
Wedge.cache # cache files on app load
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require_relative 'base' unless RUBY_ENGINE == 'opal'
|
2
|
+
|
3
|
+
class DummyApp
|
4
|
+
class RootComponent < BaseComponent
|
5
|
+
config.name :root
|
6
|
+
config.html <<-HTML
|
7
|
+
<!DOCTYPE html>
|
8
|
+
<html>
|
9
|
+
<head>
|
10
|
+
<script src="//code.jquery.com/jquery-1.11.2.js"></script>
|
11
|
+
#{Wedge.script_tag}
|
12
|
+
</head>
|
13
|
+
<body>
|
14
|
+
<div id='foo'>bar</div>
|
15
|
+
</body>
|
16
|
+
</html>
|
17
|
+
HTML
|
18
|
+
config.dom do
|
19
|
+
dom.find('body') << assets(:js)
|
20
|
+
end
|
21
|
+
config.requires :base, :bar, :foo_form, :pjax_plugin
|
22
|
+
|
23
|
+
def display
|
24
|
+
if server?
|
25
|
+
dom
|
26
|
+
else
|
27
|
+
el = Element['<div>']
|
28
|
+
el.html 'foo'
|
29
|
+
dom.find('#foo').before el
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
on :ready do
|
34
|
+
puts 'dom ready'
|
35
|
+
end
|
36
|
+
|
37
|
+
on :click, '#foo' do |el|
|
38
|
+
el.after '<div>bar</div>'
|
39
|
+
trigger :clicked_foo
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/test/test.js
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
var page = require('webpage').create();
|
2
|
+
page.settings.localToRemoteUrlAccessEnabled = true;
|
3
|
+
page.settings.resourceTimeout = 1000;
|
4
|
+
// page.content = "<!doctype html>\n<html>\n<head>\new<script type=\"text/javascript\" src=\"https://code.jquery.com/jquery-1.11.2.min.js\"></script>\n</head>\n<body>\n<div id=\"foo\">bar<div>\n</body>\n</html>";
|
5
|
+
var content = '<!doctype html>';
|
6
|
+
content += '<html><head>';
|
7
|
+
content += '<script type="text/javascript" src="https://code.jquery.com/jquery-1.11.2.min.js"></script>';
|
8
|
+
content += '</head><body>';
|
9
|
+
content += '<h1 id="foo">bar</h1>';
|
10
|
+
content += '</body></html>';
|
11
|
+
|
12
|
+
// page.content = "<div id='foo'>bar</div>";
|
13
|
+
|
14
|
+
page.onConsoleMessage = function(msg) {
|
15
|
+
console.log(msg);
|
16
|
+
};
|
17
|
+
|
18
|
+
page.onResourceTimeout = function(a) {
|
19
|
+
phantom.exit(1);
|
20
|
+
};
|
21
|
+
|
22
|
+
page.onError = function(msg, trace) {
|
23
|
+
|
24
|
+
var msgStack = ['ERROR: ' + msg];
|
25
|
+
|
26
|
+
if (trace && trace.length) {
|
27
|
+
msgStack.push('TRACE:');
|
28
|
+
trace.forEach(function(t) {
|
29
|
+
msgStack.push(' -> ' + t.file + ': ' + t.line + (t.function ? ' (in function "' + t.function +'")' : ''));
|
30
|
+
});
|
31
|
+
}
|
32
|
+
|
33
|
+
console.log(msgStack.join('\n'));
|
34
|
+
phantom.exit();
|
35
|
+
};
|
36
|
+
|
37
|
+
phantom.onError = function(msg, trace) {
|
38
|
+
var msgStack = ['PHANTOM ERROR: ' + msg];
|
39
|
+
if (trace && trace.length) {
|
40
|
+
msgStack.push('TRACE:');
|
41
|
+
trace.forEach(function(t) {
|
42
|
+
msgStack.push(' -> ' + (t.file || t.sourceURL) + ': ' + t.line + (t.function ? ' (in function ' + t.function +')' : ''));
|
43
|
+
});
|
44
|
+
}
|
45
|
+
console.log(msgStack.join('\n'));
|
46
|
+
phantom.exit();
|
47
|
+
};
|
48
|
+
|
49
|
+
page.content = content
|
50
|
+
|
51
|
+
page.onLoadFinished = function() {
|
52
|
+
page.evaluate(function() {
|
53
|
+
console.log($('#foo').html());
|
54
|
+
});
|
55
|
+
phantom.exit();
|
56
|
+
};
|
57
|
+
|
58
|
+
// page.includeJs("http://code.jquery.com/jquery-1.11.2.min.js", function(){
|
59
|
+
// });
|