opal/browser/form_data.rb
# backtick_javascript: true
module Browser
class FormData
include NativeCachedWrapper
module Converter
# Encode as URI component.
#
# @return [String] the string encoded for usage as URI component
def encode(string)
`encodeURIComponent(#{string})`
end
# Decode as URI component.
#
# @return [String] the string decoded as URI component
def decode(string)
`decodeURIComponent(#{string})`
end
# Encode as URI.
#
# @return [String] the string encoded as URI
def encode_uri(string)
`encodeURI(#{string})`
end
# Decode as URI.
#
# @return [String] the string decoded as URI
def decode_uri(string)
`decodeURI(#{string})`
end
# Flattens a hash to build a flat array, later to be formatted to
# produce a nested query.
#
# This code should be compatible with what Rack::Utils#build_nested_query [1]
# does.
#
# [1] https://github.com/rack/rack/blob/master/lib/rack/utils.rb
def flatten(value, key="")
case value
when Hash
out = []
value.each do |k,v|
k = "#{key}[#{k}]" if key != ''
out += flatten(v,k)
end
out
when Array
out = []
value.each do |v|
k = "#{key}[]"
out += flatten(v,k)
end
out
else
[[key,value]]
end
end
# Converts a flat array to a Hash.
#
# This code should be compatible with what Rack::Utils#parse_nested_query [1]
# does.
#
# [1] https://github.com/rack/rack/blob/master/lib/rack/utils.rb
def unflatten(array)
out = {}
array.each do |k,v|
path = [k.split("[").first] + k.scan(/\[(.*?)\]/).flatten
c = out
set = proc { |v,weak| } # Do nothing for the first level
path.each do |i|
case i
when "" # Array
set.([], true)
set = proc do |v,weak|
c << v
c = c.last
end
else # Hash
set.({}, true)
set = proc do |v,weak|
c[i] ||= v
c[i] = v if !weak
c = c[i]
end
end
end
set.(v, false)
end
out
end
# Checks if a query Hash contains any files.
def contain_files?(hash)
flatten(hash).any? { |k,v| [File, Blob].include?(v.class) }
end
# Convert a query Hash to a query string
#
# @return [String] the string encoded as URI
def build_query(hash, sep=?&)
flatten(hash).map { |k,v| encode(k) + ?= + encode(v.to_s) }.join(sep)
end
# Convert a query Hash to a FormData instance
#
# @return [FormData] the instance of FormData
def build_form_data(hash)
fd = FormData.create
flatten(hash).each { |k,v| fd << [k,v] }
fd
end
# Convert a query string to a query Hash
#
# @return [Hash] the query hash
def parse_query(string, sep=?&)
unflatten(string.split(sep).map { |s| s.split(?=).map(&method(:decode)) })
end
# Converts a JS native value to a wrapped one if possible.
#
# @return [String, File, Blob]
def from_native(n)
%x{
var c = #{n}.constructor;
if (c === File) {
#{n = File.new(n)}
}
else if (c === Blob) {
#{n = Blob.new(n)}
}
}
n
end
end
extend Converter
include Enumerable
# Create a new FormData instance
def self.create(hash=nil)
if Hash === hash
FormData.build_form_data(hash)
elsif DOM::Element::Form === hash
new(`new FormData(#{hash.to_n})`)
else
new(`new FormData()`)
end
end
# Append a tuple to this FormData instance
#
# @param tuple [Array(String, String), Array(String, Blob), Array(String, File),
# Array(String, Blob, String), Array(String, File, String)]
# a tuple of a key, value and possibly a filename
def <<(tuple)
key, value, filename = tuple
unless filename
`#@native.append(#{key}, #{Native.convert(value)})`
else
`#@native.append(#{key}, #{Native.convert(value)}, #{filename})`
end
end
# Get a field from this FormData instance with a given name
def [](key)
FormData.from_native(`#@native.get(#{key})`)
end
# Set a field in this FormData instance with a given name
def set(key, value, filename = nil)
unless filename
`#@native.set(#{key}, #{Native.convert(value)})`
else
`#@native.set(#{key}, #{Native.convert(value)}, #{filename})`
end
end
alias []= set
# Convert to hash
def to_h
hash = {}
%x{
var pair, v, e = #@native.entries();
while (true) {
v = e.next();
if (v.done) break;
pair = v.value;
#{hash[`pair[0]`] = FormData.from_native(`pair[1]`)}
}
}
hash
end
# Convert to array
def to_a
to_h.to_a
end
# Iterate over all elements of this FormData
def each(&block)
to_h.each(&block)
end
# Checks if a field of this name exists in this FormData instance
def include?(key)
`#@native.has(#{key})`
end
# Delete a field from this FormData instance
def delete(key)
`#@native.delete(#{key})`
end
end
end