⚠️ ALERT
The project is no longer actively developed and looking for new maintainers.
Until a better README is out (shame on us) you can take a look at the Opal implementation of TodoMVC.
Installation
Add vienna to your Gemfile with a reference to the Github source.
Note: The vienna hosted on rubygems.org is a different project.
gem 'opal-vienna'
If you're compiling opal in a static application, make sure to require bundler first.
require 'bundler' Bundler.require
Model
Client side models.
class Book < Vienna::Model attributes :title, :author end book = Book.new(title: 'My awesome book', author: 'Bob') book.title = 'Bob: A story of awesome'
Attributes
Attributes can be defined on subclasses using attributes. This simply defines
a getter/setter method using attr_accessor. You can override either method as
expected:
class Book < Vienna::Model attributes :title, :release_date # If date is a string, then we need to parse it def release_date=(date) date = Date.parse(date) if String === date @release_date = date end end book = Book.new(:release_date => '2013-1-10') book.release_date # => #<Date: 2013-1-10>
Views
Vienna::View is a simple wrapper class around a dom element representing a
view of some model (or models). A view's element is dynamically created when
first accessed. View.element can be used to specify a dom selector to find
the view in the dom.
Assuming the given html:
<body> <div id="foo"> <span>Hi</span> </div> </body>
We can create our view like so:
class MyView < Vienna::View element '#foo' end MyView.new.element # => #<Element: [<div id="foo">]>
A real, existing, element can also be passed into the class method:
class MyView < Vienna::View # Instances of this view will have the document as an element element Document end
Views can have parents. If a child view is created, then the dom selector is only searched inside the parents element.
Customizing elements
A View will render as a div tag, by default, with no classes (unless an
element selector is defined). Both these can be overriden inside your view
subclass.
class NavigationView < Vienna::View def tag_name :ul end def class_name "navbar navbar-blue" end end
Rendering views
Views have a placeholder render method, that doesnt do anything by default.
This is the place to put rendering logic.
class MyView < Vienna::View def render element.html = 'Welcome to my rubyicious page' end end view = MyView.new view.render view.element # => '<div>Welcome to my rubyicious page</div>'
Listening for events
When an element is created, defined events can be added to it. When a view is destroyed, these event handlers are then removed.
class ButtonView < Vienna::View on :click do |evt| puts "clicked on button" end def tag_name :button end end
For complex views, you can provide an optional css selector to scope the events:
class NavigationView < Vienna::View on :click, 'ul.navbar li' do |evt| puts "clicked: #{evt.target}" end on :mouseover, 'ul.navbar li.selected', :handle_mouseover def handle_mouseover(evt) # ... end end
As you can see, you can specify a method to handle events instead of a block.
Customizing element creation
You can also override create_element if you wish to have any custom element
creation behaviour.
For example, a subview that is created from a parent element
class NavigationView < Vienna::View def initialize(parent, selector) @parent, @selector = parent, selector end def create_element @parent.find(@selector) end end
Assuming we have the html:
<div id="header"> <img id="logo" src="logo.png" /> <ul class="navigation"> <li>Homepage</li> </ul> </div>
We can use the navigation view like this:
@header = Element.find '#header' nav_view = NavigationView.new @header, '.navigation' nav_view.element # => [<ul class="navigation">]
Router
Vienna::Router is a simple router that watches for hashchange events.
router = Vienna::Router.new router.route("/users") do puts "need to show all users" end router.route("/users/:id") do |params| puts "need to show user: #{ params[:id] }" end # visit "example.com/#/users" # visit "example.com/#/users/3" # visit "example.com/#/users/5" # => "need to show all users" # => need to show user: 3 # => need to show user: 5
Observable
Adds KVO style attribute observing.
class MyObject include Vienna::Observable attr_accessor :name attr_reader :age def age=(age) @age = age + 10 end end obj = MyObject.new obj.add_observer(:name) { |new_val| puts "name changed to #{new_val}" } obj.add_observer(:age) { |new_age| puts "age changed to #{new_age}" } obj.name = "bob" obj.age = 42 # => "name changed to bob" # => "age changed to 52"
Observable Arrays
class MyArray include Vienna::ObservableArray end array = MyArray.new array.add_observer(:content) { |content| puts "content is now #{content}" } array.add_observer(:size) { |size| puts "size is now #{size}" } array << :foo array << :bar # => content is now [:foo] # => size is now 1 # => content is now [:bar] # => size is now 2
Todo
- Support older browsers which do not support onhashchange.
- Support not-hash style routes with HTML5 routing.
License
MIT