Skip to content

Instantly share code, notes, and snippets.

@adambeynon
Created May 7, 2014 15:58
Show Gist options
  • Save adambeynon/ca4c2a0ba8b3d26d3faf to your computer and use it in GitHub Desktop.
Save adambeynon/ca4c2a0ba8b3d26d3faf to your computer and use it in GitHub Desktop.
Opal Bound attributes + DOM Compiler
module Vienna
class BaseBinding
def initialize(view, context, node, attr)
@view = view
@context = context
@node = node
@attr = attr
setup_binding
end
def destroy
puts "destroying #@context -> #@attr for #@observer"
@context.remove_observer @attr, @observer
end
end
class ValueBinding < BaseBinding
def setup_binding
if @attr.include? '|'
parts = @attr.partition(/\s+\|\s+/)
@attr = parts[0]
@filter = parts[2]
end
observer = proc { |val| value_did_change @node, val }
@view.add_view_observer(@context, @attr, observer)
if @attr.include? '.'
chain = @attr.split '.'
current = chain.inject(@context) do |o, p|
o.__send__ p
end
else
current = @context.__send__(@attr)
end
value_did_change @node, current
end
def value_did_change(node, value)
if @filter
value = @context.__send__ @filter, value
end
`$(node).html(#{value.to_s});`
end
end
class InputBinding < ValueBinding
def value_did_change(node, value)
if @filter
value = @context.__send__ @filter, value
end
`$(node).val(#{value.to_s})`
end
end
class ActionBinding < BaseBinding
def setup_binding
@element = Element.find @node
@handler = @element.on(:click) do |evt|
if @context.respond_to? @attr
@context.__send__ @attr, evt
else
raise "Unknown action: #@attr on #@context"
end
end
end
def destroy
@element.off :click, @handler
end
end
class HideIfBinding < BaseBinding
def setup_binding
@element = Element.find @node
if @attr.include? '.'
chain = @attr.split '.'
current = chain.inject(@context) do |o, p|
o.__send__ p
end
else
current = @context.__send__(@attr)
end
observer = proc { |val| value_did_change @node, val }
@view.add_view_observer @context, @attr, observer
value_did_change @node, current
end
def value_did_change(node, value)
@element.toggle(!value)
end
end
class ShowIfBinding < HideIfBinding
def value_did_change(node, value)
@element.toggle value
end
end
class ViewBinding < BaseBinding
def setup_binding
view = @context.__send__ @attr
view.render
Element.find(@node) << view.element
end
def destroy
# do nothing?
end
end
end
module Vienna
class DOMCompiler
def initialize(view, context)
%x{
self.view = view
self.node = view.element[0];
self.context = context;
self.$compile_tree(self.node);
}
end
def compile_tree(node)
%x{
while (node) {
self.$compile_node(node);
node = self.$next_node(node);
}
}
end
def next_node(current)
%x{
var children = current.childNodes;
if (children && children.length && !((' ' + children[0].className + ' ').indexOf('vienna-view') > -1)) {
return children[0];
}
var sibling = current.nextSibling;
if (self.node === current) {
return;
}
else if (sibling) {
return sibling;
}
var next_parent = current;
while (next_parent = next_parent.parentNode) {
var parent_sibling = next_parent.nextSibling;
if (self.node === next_parent) {
return;
}
else if (parent_sibling) {
return parent_sibling;
}
}
return;
}
end
def compile_node(node)
%x{
if (node.attributes && node.getAttribute) {
var attributes = node.attributes, bindings = [];
for (var idx = 0, len = attributes.length; idx < len; idx++) {
var attribute = attributes[idx], name = attribute.nodeName;
if (!name || name.substr(0, 5) !== 'data-') {
continue;
}
name = name.substr(5);
bindings.push([name, attribute.value]);
var binding_class;
if (name === 'bind') {
if (node.tagName.toLowerCase() === 'input') {
binding_class = Opal.Vienna.InputBinding;
}
else {
binding_class = Opal.Vienna.ValueBinding;
}
}
else if (name === 'action') {
binding_class = Opal.Vienna.ActionBinding;
}
else if (name === 'hideif') {
binding_class = Opal.Vienna.HideIfBinding;
}
else if (name === 'showif') {
binding_class = Opal.Vienna.ShowIfBinding;
}
else if (name === 'view') {
binding_class = Opal.Vienna.ViewBinding;
}
else {
// stop a binding class appearing on next iteration
binding_class = null;
}
if (binding_class) {
binding_class.$new(self.view, self.context, node, attribute.value);
}
else {
// console.log("unknown binding class for '" + name + "'");
}
}
}
}
end
end
end
.row
.col-sm-12
Name:
%span(data-bind="name")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment