require 'native' require 'opal/jquery/constants' # {Element} is a toll-free bridged class that maps to native jQuery instances. # # As {Element} maps to a jQuery object, it can be used to represent 0, 1, or # more actual DOM elements. {Element} exposes a more ruby-esqe interface to # jQuery. # # ## Usage # # {Element} instances can be created in a number of ways. # # ### Creating new Elements # # A new element can be created using the {Element.new} method. # # el = Element.new(:div) # el.id = "title" # p el # # => #]> # # This is a nicer version of creating a javascript element using # `document.createElement` and then wrapping it in a jquery object. # # ### Finding existing elements in dom # # Any valid jQuery selector expressions can be used with either {Element.find} # or {Element.[]}. # # foos = Element.find('.foo') # # => #]> # # links = Element['a'] # # => #, ]> # # Alternatively, {Element.id} can be used to find an element by its document # id (or nil is returned for no match). # # bar = Element.id 'bar' # # => Element or nil # # ### DOM Content from string # # Finally, an {Element} instance can be created by parsing a string of html # content. This will parse multiple elements, like jquery, if string content # contains them: # # Element.parse '
hello world
' # # => #]> # class Element < `#{JQUERY_CLASS.to_n}` `var $ = #{JQUERY_SELECTOR.to_n}` # cache $ for SPEED include Enumerable # Find elements by the given css selector. # # Returns an empty {Element} if no matching elements. # # @param selector [String] css selector # @return [Element] def self.find(selector) `$(#{selector})` end # Find elements by the given css selector. # # Returns an empty {Element} if no matching elements. # # @param selector [String] css selector # @return [Element] def self.[](selector) `$(#{selector})` end # Find an element by the given id. # # If no matching element, then `nil` will be returned. A matching element # becomes the sole element in the returned collection. # # @param id [String] dom element id # @return [Element, nil] def self.id(id) %x{ var el = document.getElementById(id); if (!el) { return nil; } return $(el); } end # Create a new dom element, wrapped as {Element} instance with the given # `tag` name. # # @param tag [String] valid html tag name # @return [Element] def self.new(tag = 'div') `$(document.createElement(tag))` end # Parse a string of html content into an {Element} instance. # # If no valid elements in string, then an empty collection will be returned. # # @param str [String] html content to parse # @return [Element] def self.parse(str) `$.parseHTML ? $($.parseHTML(str)) : $(str)` end # Expose jQuery plugins to become available in ruby code. By default, # jQuery methods or plugins must be manually exposed as ruby methods. # This method simply creates an aliasing ruby method to call the original # javascript function. # # @example # # Expose bootstraps jQuery `modal` function # Element.expose :modal # # Element.find('.my-modal').modal # # @param methods [String, Symbol] all methods to expose to ruby # @return nil def self.expose(*methods) methods.each do |method| alias_native method end end # @return The original css selector used to create {Element} attr_reader :selector # @!method after(content) # # Inserts the given `content` after each element in this set of elements. # This method can accept either another {Element}, or a string. # # @param content [String, Element] string or element to insert alias_native :after # @!method before(content) # # Insert the given `content` before each element in this set of elements. # The given `content` can be either an {Element}, or a string. # # @param content [String, Element] string or element to insert alias_native :before # @!method parent(selector = nil) # # Returns a new {Element} set with the parents of each element in this # collection. An optional `selector` argument can be used to filter the # results to match the given selector. Result may be empty. # # @param selector [String] optional filter # @return [Element] alias_native :parent # @!method parents(selector = nil) # # Returns a new {Element} set with all parents of each element in this # collection. An optional `selector` may be provided to filter the # selection. Resulting collection may be empty. # # @example Without filtering collection # Element.find('#foo').parents # # => #, , ]> # # @example Using a filter # Element.find('#foo').parents('div') # # => #] # # @param selector [String] optional filter # @return [Element] alias_native :parents # @!method prev(selector = nil) alias_native :prev # @!method remove(selector = nil) alias_native :remove # @!method hide(duration = 400) alias_native :hide # @!method show(duration = 400) alias_native :show # @!method toggle(duration = 400) alias_native :toggle # @!method children(selector = nil) alias_native :children # @!method blur alias_native :blur # @!method closest(selector) alias_native :closest # @!method detach(selector = nil) alias_native :detach # @!method focus alias_native :focus # @!method find(selector) alias_native :find # @!method next(selector = nil) alias_native :next # @!method siblings(selector = nil) alias_native :siblings # @!method text(text = nil) # # Get or set the text content of each element in this collection. Setting # the content is provided as a compatibility method for jquery. Instead # {#text=} should be used for setting text content. # # If no `text` content is provided, then the text content of this element # will be returned. # # @see #text= # @param text [String] text content to set # @return [String] alias_native :text # @!method trigger(event) # # Trigger an event on this element. The given `event` specifies the event # type. # # @param event [String, Symbol] event type alias_native :trigger # @!method append(content) # # @param content [String, Element] alias_native :append # @!method prepend(content) # # @param content [String, Element] alias_native :prepend # @!method serialize alias_native :serialize # @!method is(selector) # @return [true, false] alias_native :is # @!method filter(selector) # @param selector [String] # @return [Element] alias_native :filter # @!method not(selector) # @param selector [String] # @return [Element] alias_native :not # @!method last # # Returns a new {Element} instance containing the last element in this # current set. # # @return [Element] alias_native :last # @!method wrap(wrapper) # @param wrapper [String, Element] html content, selector or element # @return [Element] alias_native :wrap # @!method stop # # Stop any currently running animations on element. alias_native :stop # @!method clone # # Clone all elements inside this collection, and return as a new instance. # @return [Element] alias_native :clone # @!method empty # # Remove all child nodes from each element in this collection. alias_native :empty # @!method get alias_native :get # @!method prop(name, value = undefined) # # Get or set the property `name` on each element in collection. def prop(*args) Native.call(self, :prop, *args) end alias succ next alias << append # @!method add_class(class_name) alias_native :add_class, :addClass # @!method append_to(element) alias_native :append_to, :appendTo # @!method has_class?(class_name) alias_native :has_class?, :hasClass # @!method html=(content) # # Set the html content of each element in this collection to the passed # content. Content can either be a string or another {Element}. # # @param content [String, Element] alias_native :html=, :html # @!method index(selector_or_element = nil) alias_native :index # @!method is?(selector) alias_native :is?, :is # @!method remove_attr(attr) alias_native :remove_attr, :removeAttr # @!method remove_class(class_name) alias_native :remove_class, :removeClass # @!method replace_all(target) alias_native :replace_all, :replaceAll # @!method replace_with(new_content) alias_native :replace_with, :replaceWith # @!method submit() alias_native :submit # @!method click() alias_native :click # @!method text=(text) # # Set text content of each element in this collection. # # @see #text # @param text [String] alias_native :text=, :text # @!method toggle_class alias_native :toggle_class, :toggleClass # @!method value=(value) alias_native :value=, :val # @!method scroll_top=(value) alias_native :scroll_top=, :scrollTop # @!method scroll_top alias_native :scroll_top, :scrollTop # @!method scroll_left=(value) alias_native :scroll_left=, :scrollLeft # @!method scroll_left alias_native :scroll_left, :scrollLeft # @!method remove_attribute(attr) alias_native :remove_attribute, :removeAttr # @!method slide_down(duration = 400) alias_native :slide_down, :slideDown # @!method slide_up(duration = 400) alias_native :slide_up, :slideUp # @!method slide_toggle(duration = 400) alias_native :slide_toggle, :slideToggle # @!method fade_toggle(duration = 400) alias_native :fade_toggle, :fadeToggle # @!method height=(value) alias_native :height=, :height # @!method width=(value) alias_native :width=, :width # @!method outer_width(include_margin = false) alias_native :outer_width, :outerWidth # @!method outer_height(include_margin = false) alias_native :outer_height, :outerHeight def to_n self end def [](name) %x{ var value = self.attr(name); if(value === undefined) return nil; return value; } end # Set the given attribute `attr` on each element in this collection. # # @see http://api.jquery.com/attr/ def []=(name, value) `return self.removeAttr(name)` if value.nil? `self.attr(name, value)` end def attr(*args) %x{ var size = args.length; switch (size) { case 1: var result = self.attr(args[0]); return( (result == null) ? nil : result ); break; case 2: return self.attr(args[0], args[1]); break; default: #{raise ArgumentError, '#attr only accepts 1 or 2 arguments'} } } end def has_attribute?(name) `self.attr(name) !== undefined` end def append_to_body `self.appendTo(document.body)` end def append_to_head `self.appendTo(document.head)` end # 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. # # @param index [Integer] index # @return [Element, nil] def at(index) %x{ var length = self.length; if (index < 0) { index += length; } if (index < 0 || index >= length) { return nil; } return $(self[index]); } end # 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. # # @return [String] def class_name %x{ var first = self[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 # the entire current class name. # # @param name [String] class name to set def class_name=(name) %x{ for (var i = 0, length = self.length; i < length; i++) { self[i].className = name; } } self end # 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 # 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)` else name.is_a?(Hash) ? `self.css(#{name.to_n})` : `self.css(name, value)` end self end # Set css values over time to create animations. The first parameter is a # set of css properties and values to animate to. The first parameter # 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 if block_given? `self.animate(#{params.to_n}, #{speed}, block)` else `self.animate(#{params.to_n}, #{speed})` end end def data(*args) %x{ var result = self.data.apply(self, args); if ( (typeof(result) === 'object') && !(result instanceof #{JQUERY_CLASS}) ) { result = #{ JSON.from_object `result` }; } return result == null ? nil : result; } end # Start a visual effect (e.g. fadeIn, fadeOut, …) passing its name. # Underscored style is automatically converted (e.g. `effect(:fade_in)`). # Also accepts additional arguments and a block for the finished callback. 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})` end def visible? `self.is(':visible')` end def offset Native(`self.offset()`) end def each `for (var i = 0, length = self.length; i < length; i++) {` yield `$(self[i])` `}` self end def first `self.length ? self.first() : nil` end def html(content = undefined) %x{ if (content != null) { return self.html(content); } return self.html() || ''; } end def id %x{ var first = self[0]; return (first && first.id) || ""; } end def id=(id) %x{ var first = self[0]; if (first) { first.id = id; } return self; } end def tag_name `self.length > 0 ? self[0].tagName.toLowerCase() : #{nil}` end def inspect %x{ if (self[0] === document) return '#' else if (self[0] === window ) return '#' var val, el, str, result = []; for (var i = 0, length = self.length; i < length; i++) { el = self[i]; if (!el.tagName) { return '#'); } return '#'; } end def to_s %x{ var val, el, result = []; for (var i = 0, length = self.length; i < length; i++) { el = self[i]; result.push(el.outerHTML) } return result.join(', '); } end # Returns the number of elements in this collection. May be zero. # @return [Integer] def length `self.length` end # Returns `true` if this collection has 1 or more elements, `false` # otherwise. # # @return [true, false] def any? `self.length > 0` end # Returns `true` if this collection contains no elements, `false` otherwise. # # @return [true, false] def empty? `self.length === 0` end alias empty? none? def on(name, sel = nil, &block) %x{ var has_args = #{block.arity} !== 0; var wrapper = function() { for(var args = new Array(arguments.length), i = 0, ii = args.length; i < ii; i++) { args[i] = arguments[i]; } // Use preventDefault as a canary for native events if (has_args && args[0].preventDefault) { args[0] = #{Event.new `args[0]`}; } return block.apply(null, args); }; block.$$jqwrap = wrapper; if (sel == nil) { self.on(name, wrapper); } else { self.on(name, sel, wrapper); } } block end def one(name, sel = nil, &block) %x{ var has_args = #{block.arity} !== 0; var wrapper = function() { for(var args = new Array(arguments.length), i = 0, ii = args.length; i < ii; i++) { args[i] = arguments[i]; } // Use preventDefault as a canary for native events if (has_args && args[0].preventDefault) { args[0] = #{Event.new `args[0]`}; } return block.apply(null, args); }; block.$$jqwrap = wrapper; if (sel == nil) { self.one(name, wrapper); } else { self.one(name, sel, wrapper); } } block end def off(name, sel, block = nil) %x{ if (sel == null) { return self.off(name); } else if (block === nil) { return self.off(name, sel.$$jqwrap); } else { return self.off(name, sel, block.$$jqwrap); } } end # Serializes a form into an Array of Hash objects. # # @return [Array] def serialize_array `self.serializeArray()`.map { |e| Hash.new(e) } end alias size length def value `self.val()` || "" end def height `self.height()` || nil end def width `self.width()` || nil end def position Native(`self.position()`) end def ==(other) `self.is(other)` end def respond_to_missing?(name, _) %x{ var method = self[#{name}]; if (typeof(method) === 'function') { return true; } else { return #{super}; } } end def method_missing(name, *args, &block) args << block if block_given? %x{ var method = self[#{name}]; if (typeof(method) === 'function') { return method.apply(self, #{args.to_n}); } else { return #{super}; } } end end