Skip to content

Commit

Permalink
Initial v2 of native
Browse files Browse the repository at this point in the history
adambeynon committed Nov 1, 2013
1 parent 7bde6f1 commit 6237439
Showing 4 changed files with 322 additions and 114 deletions.
416 changes: 305 additions & 111 deletions opal/opal-jquery/element.rb
Original file line number Diff line number Diff line change
@@ -1,127 +1,229 @@
class Element
%x{
var root = $opal.global, dom_class;
include Enumerable

if (root.jQuery) {
dom_class = jQuery
}
else if (root.Zepto) {
dom_class = Zepto.zepto.Z;
}
else {
throw new Error("jQuery must be included before opal-jquery");
}
class << self
def find(selector)
self.new `$(#{selector})`
end

self._proto = dom_class.prototype, def = self._proto;
dom_class.prototype._klass = self;
}
alias [] find
alias parse find

include Kernel
include Enumerable
def id(id)
%x{
var el = document.getElementById(id);
if (!el) {
return nil;
}
return #{new `el`};
}
end

def define_manipulation(name)
define_method(name) do |content|
%x{
if (!content._isString) {
content = content['native'];
}
#@native[name](content);
}

self
end
end

def self.find(selector)
`$(#{selector})`
def define_traversing(name)
define_method(name) do
Element.new `#@native[name]()`
end
end

def expose(*methods)
methods.each do |meth|
define_method(meth) do
`#@native[meth].apply(#@native, arguments)`
end
end
end
end

def self.[](selector)
`$(#{selector})`
define_manipulation :after
define_manipulation :before
define_manipulation :append
define_manipulation :prepend

define_traversing :next
define_traversing :prev
define_traversing :parent
define_traversing :parents
define_traversing :children

def initialize(str)
if String === str
@native = `$(document.createElement(#{str}))`
else
@native = `$(#{str})`
end
end

def self.id(id)
%x{
var el = document.getElementById(id);
def method_missing(sym, *args)
`#@native[sym].apply(#@native, args)`
end

if (!el) {
return nil;
}
def remove
`#@native.remove()`
self
end

return $(el);
}
def hide
`#@native.hide()`
end

def show
`#@native.show()`
end

def self.new(tag = 'div')
`$(document.createElement(tag))`
def toggle
`#@native.toggle()`
end

def self.parse(str)
`$(str)`
def siblings(sel=nil)
if sel
Element.new `#@native.siblings(sel)`
else
Element.new `#@native.siblings()`
end
end

def self.expose(*methods)
def append_to(target)
%x{
for (var i = 0, length = methods.length, method; i < length; i++) {
method = methods[i];
self._proto['$' + method] = self._proto[method];
if (!target._isString) {
target = target['native'];
}
return nil;
#@native.appendTo(target);
}
end

attr_reader :selector
self
end

# Bridged functions - we just expose all core jquery functions as ruby
# methods on this class.
expose :after, :before, :parent, :parents, :prepend, :prev, :remove
expose :hide, :show, :toggle, :children, :blur, :closest, :data
expose :focus, :find, :next, :siblings, :text, :trigger, :append
expose :height, :width, :serialize, :is, :filter, :last, :first
expose :wrap, :stop, :clone, :empty
def selector
`#@native.selector`
end

# We alias some jquery methods to common ruby method names.
alias succ next
alias << append

# Here we map the remaining jquery methods, but change their names to
# snake_case to be more consistent with ruby.
alias_native :[]=, :attr
alias_native :add_class, :addClass
alias_native :append_to, :appendTo
alias_native :has_class?, :hasClass
alias_native :html=, :html
alias_native :remove_attr, :removeAttr
alias_native :remove_class, :removeClass
alias_native :text=, :text
alias_native :toggle_class, :toggleClass
alias_native :value=, :val
alias_native :scroll_left=, :scrollLeft
alias_native :scroll_left, :scrollLeft
alias_native :remove_attribute, :removeAttr
alias_native :slide_down, :slideDown
alias_native :slide_up, :slideUp
alias_native :slide_toggle, :slideToggle
alias_native :fade_toggle, :fadeToggle
def focus
`#@native.focus()`
self
end

def height
`#@native.height()`
end

def to_n
def width
`#@native.width()`
end

def serialize
`#@native.serialize()`
end

def is(selector)
`#@native.is(selector)`
end

def data(key, val=undefined)
%x{
if (val == undefined) {
return #@native.data(key);
}
else {
return #@native.data(key, val);
}
}
end

def closest(selector)
Element.new `#@native.closest(selector)`
end

def find(content)
Element.new `#@native.find(content)`
end

def add_class(name)
`#@native.addClass(name)`
self
end

def remove_class(name)
`#@native.removeClass(name)`
self
end

def has_class?(name)
`#@native.hasClass(name)`
end

def toggle_class(name)
`#@native.toggleClass(name)`
self
end

def to_n
@native
end

def [](name)
`self.attr(name) || ""`
`#@native.attr(name) || ""`
end

def []=(name, value)
`#@native.attr(name, value)`
end

def attr(name, val=nil)
if val
self[name] = val
else
self[name]
end
end

def add_attribute(name)
`#@native.attr(name, '')`
end

def add_attribute name
self[name] = ''
def remove_attr(name)
`#@native.removeAttr(name)`
self
end

def has_attribute? name
`!!self.attr(name)`
def has_attribute?(name)
`!!#@native.attr(name)`
end

def append_to_body
`self.appendTo(document.body)`
`#@native.appendTo(document.body)`
end

def append_to_head
`self.appendTo(document.head)`
`#@native.appendTo(document.head)`
end

# Returns the element at the given index as a new `DOM` instance.
# Returns the element at the given index as a new `Element` instance.
# Negative indexes can be used and are counted from the end. If the
# given index is outside the range then `nil` is returned.
def at(index)
%x{
var length = self.length;
var length = #@native.length;
if (index < 0) {
index += length;
@@ -131,43 +233,43 @@ def at(index)
return nil;
}
return $(self[index]);
return #{Element.new `$(#@native[index])`};
}
end

# Returns the CSS class name of the firt element in self collection.
# Returns the CSS class name of the firt element in #{self} collection.
# If the collection is empty then an empty string is returned. Only
# the class name of the first element will ever be returned.
def class_name
%x{
var first = self[0];
var first = #@native[0];
return (first && first.className) || "";
}
end

# Sets the CSS class name of every element in self collection to the
# given string. self does not append the class names, it replaces
# Sets the CSS class name of every element in #{self} collection to the
# given string. #{self} does not append the class names, it replaces
# the entire current class name.
def class_name=(name)
%x{
for (var i = 0, length = self.length; i < length; i++) {
self[i].className = name;
for (var i = 0, length = #@native.length; i < length; i++) {
#@native[i].className = name;
}
}
self
end

# Get or set css properties on each element in self collection. If
# Get or set css properties on each element in #{self} collection. If
# only the `name` is given, then that css property name is read from
# the first element in the collection and returned. If the `value`
# property is also given then the given css property is set to the
# given value for each of the elements in self collection. The
# given value for each of the elements in #{self} collection. The
# property can also be a hash of properties and values.
def css(name, value=nil)
if value.nil? && name.is_a?(String)
return `self.css(name)`
return `#@native.css(name)`
else
name.is_a?(Hash) ? `self.css(#{name.to_n})` : `self.css(name, value)`
name.is_a?(Hash) ? `#@native.css(#{name.to_n})` : `#@native.css(name, value)`
end
self
end
@@ -177,9 +279,9 @@ def css(name, value=nil)
# also accepts a special :speed value to set animation speed. If a block
# is given, the block is run as a callback when the animation finishes.
def animate(params, &block)
speed = params.has_key?(:speed) ? params.delete(:speed) : 400
speed = params.delete(:speed) || 400
%x{
self.animate(#{params.to_n}, #{speed}, function() {
#@native.animate(#{params.to_n}, #{speed}, function() {
#{block.call if block_given?}
})
}
@@ -192,61 +294,105 @@ def effect(name, *args, &block)
name = name.gsub(/_\w/) { |match| match[1].upcase }
args = args.map { |a| a.to_n if a.respond_to? :to_n }.compact
args << `function() { #{block.call if block_given?} }`
`self[#{name}].apply(self, #{args})`
`#@native[#{name}].apply(#@native, #{args})`
end

def visible?
`self.is(':visible')`
`#@native.is(':visible')`
end

def offset
Hash.from_native(`self.offset()`)
Hash.from_native(`#@native.offset()`)
end

def each
`for (var i = 0, length = self.length; i < length; i++) {`
yield `$(self[i])`
`for (var i = 0, length = #@native.length; i < length; i++) {`
yield Element.new `$(#@native[i])`
`}`
self
end

def filter(selector)
Element.new `#@native.filter(selector)`
end

def first
`self.length ? self.first() : nil`
`#@native.length ? #{Element.new `#@native.first()`} : nil`
end

def last
Element.new `#@native.last()` unless size == 0
end

def stop(*args)
`#@native.stop.apply(#@native, args)`
self
end

def wrap(content)
%x{
if (!content._isString) {
content = content['native'];
}
return #{Element.new `#@native.wrap(content)`};
}
end

def text
`#@native.text() || ""`
end

def text=(text)
`#@native.text(text)`
self
end

def html
`self.html() || ""`
`#@native.html() || ""`
end

def html=(content)
%x{
if (!content._isString) {
content = content['native'];
}
#@native.html(content);
}

self
end

def id
%x{
var first = self[0];
var first = #@native[0];
return (first && first.id) || "";
}
end

def id=(id)
%x{
var first = self[0];
var first = #@native[0];
if (first) {
first.id = id;
}
return self;
return #{self};
}
end

def tag_name
`self.length > 0 ? self[0].tagName.toLowerCase() : #{nil}`
`#@native.length > 0 ? #@native[0].tagName.toLowerCase() : #{nil}`
end

def inspect
%x{
var val, el, str, result = [];
for (var i = 0, length = self.length; i < length; i++) {
el = self[i];
for (var i = 0, length = #@native.length; i < length; i++) {
el = #@native[i];
str = "<" + el.tagName.toLowerCase();
if (val = el.id) str += (' id="' + val + '"');
@@ -260,19 +406,23 @@ def inspect
end

def length
`self.length`
`#@native.length`
end

def any?
`self.length > 0`
`#@native.length > 0`
end

def empty?
`self.length === 0`
`#@native.length === 0`
end

alias empty? none?

def clone
Element.new `#@native.clone()`
end

def on(name, sel = nil, &block)
%x{
var wrapper = function(evt) {
@@ -286,10 +436,10 @@ def on(name, sel = nil, &block)
block._jq_wrap = wrapper;
if (sel == nil) {
self.on(name, wrapper);
#@native.on(name, wrapper);
}
else {
self.on(name, sel, wrapper);
#@native.on(name, sel, wrapper);
}
}

@@ -299,20 +449,64 @@ def on(name, sel = nil, &block)
def off(name, sel, block = nil)
%x{
if (sel == null) {
return self.off(name);
return #@native.off(name);
}
else if (block === nil) {
return self.off(name, sel._jq_wrap);
return #@native.off(name, sel._jq_wrap);
}
else {
return self.off(name, sel, block._jq_wrap);
return #@native.off(name, sel, block._jq_wrap);
}
}
end

def trigger(*args)
`#@native.trigger.apply(#@native, args)`
end

alias size length

def value
`self.val() || ""`
def val
`#@native.val() || ""`
end
alias value val

def val=(name, val)
`#@native.val(name, val)`
val
end
alias value= val=

def scroll_left=(left)
`#@native.scrollLeft(left)`
left
end

def scroll_top=(top)
`#@native.scrollTop(top)`
end

def scroll_left
`#@native.scrollLeft()`
end

def scroll_top
`#@native.scrollTop()`
end

def slide_down(*args)
`#@native.slideDown.apply(#@native, args)`
end

def slide_up(*args)
`#@native.slideUp.apply(#@native, args)`
end

def slide_toggle(*args)
`#@native.slideToggle.apply(#@native, args)`
end

def fade_toggle(*args)
`#@native.fadeToggle.apply(#@native, args)`
end
end
4 changes: 2 additions & 2 deletions opal/opal-jquery/event.rb
Original file line number Diff line number Diff line change
@@ -16,11 +16,11 @@ def type
# Element

def current_target
`$(#@native.currentTarget)`
Element.new `#@native.currentTarget`
end

def target
`$(#@native.target)`
Element.new `#@native.target`
end

##
6 changes: 5 additions & 1 deletion spec/element/length_spec.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
require "spec_helper"

describe "Element#length" do
html <<-HTML
<div id="foo-bar-baz"></div>
HTML

it "should report the number of elements in the instance" do
Element.new.length.should == 1
Element.find("#foo-bar-baz").length.should == 1
end
end
10 changes: 10 additions & 0 deletions spec/element/method_missing_spec.rb
Original file line number Diff line number Diff line change
@@ -9,6 +9,10 @@
$.fn.opal_specs_args = function() {
return Array.prototype.slice.call(arguments);
};
$.fn.opal_meth_missing = function() {
return Array.prototype.slice.call(arguments);
};
}

class Element
@@ -30,3 +34,9 @@ class Element
}.should raise_error(NoMethodError)
end
end

describe "Element#method_missing" do
it "forwards calls to jquery plugins" do
Element.new.opal_meth_missing(1, 2, 3).should eq([1, 2, 3])
end
end

0 comments on commit 6237439

Please sign in to comment.