{"id":13503636,"url":"https://github.com/opal/vienna","last_synced_at":"2025-12-17T11:30:41.998Z","repository":{"id":3807056,"uuid":"4886506","full_name":"opal/vienna","owner":"opal","description":"Client-side MVC framework for Opal","archived":true,"fork":false,"pushed_at":"2016-12-02T23:19:49.000Z","size":380,"stargazers_count":181,"open_issues_count":11,"forks_count":22,"subscribers_count":21,"default_branch":"master","last_synced_at":"2024-11-01T00:32:07.601Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"http://opalrb.org","language":"Ruby","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/opal.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2012-07-04T16:07:22.000Z","updated_at":"2024-04-30T23:51:01.000Z","dependencies_parsed_at":"2022-09-12T19:40:17.983Z","dependency_job_id":null,"html_url":"https://github.com/opal/vienna","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/opal%2Fvienna","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/opal%2Fvienna/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/opal%2Fvienna/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/opal%2Fvienna/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/opal","download_url":"https://codeload.github.com/opal/vienna/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246227010,"owners_count":20743870,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-31T23:00:41.910Z","updated_at":"2025-12-17T11:30:36.942Z","avatar_url":"https://github.com/opal.png","language":"Ruby","readme":"### ⚠️ ALERT\n\n**The project is no longer actively developed and looking for new maintainers.**\n\n---\n\n\n# Vienna: Client side MVC framework for Opal\n\n[![Build Status](https://travis-ci.org/opal/vienna.svg?branch=master)](https://travis-ci.org/opal/vienna)\n\nUntil a better README is out (shame on us) you can take a look at\nthe [Opal implementation](https://github.com/opal/opal-todos)\nof [TodoMVC](http://todomvc.com).\n\n## Installation\n\nAdd vienna to your ```Gemfile``` with a reference to the Github source.\n\nNote: The vienna hosted on rubygems.org is a different project.\n\n```ruby\ngem 'opal-vienna'\n```\n\nIf you're compiling opal in a static application, make sure to require bundler first.\n\n```ruby\nrequire 'bundler'\nBundler.require\n```\n\n## Model\n\nClient side models.\n\n```ruby\nclass Book \u003c Vienna::Model\n  attributes :title, :author\nend\n\nbook = Book.new(title: 'My awesome book', author: 'Bob')\nbook.title = 'Bob: A story of awesome'\n```\n\n### Attributes\n\nAttributes can be defined on subclasses using `attributes`. This simply defines\na getter/setter method using `attr_accessor`. You can override either method as\nexpected:\n\n```ruby\nclass Book \u003c Vienna::Model\n  attributes :title, :release_date\n\n  # If date is a string, then we need to parse it\n  def release_date=(date)\n    date = Date.parse(date) if String === date\n    @release_date = date\n  end\nend\n\nbook = Book.new(:release_date =\u003e '2013-1-10')\nbook.release_date\n# =\u003e #\u003cDate: 2013-1-10\u003e\n```\n\n## Views\n\n`Vienna::View` is a simple wrapper class around a dom element representing a\nview of some model (or models). A view's `element` is dynamically created when\nfirst accessed. `View.element` can be used to specify a dom selector to find\nthe view in the dom.\n\nAssuming the given html:\n\n```html\n\u003cbody\u003e\n  \u003cdiv id=\"foo\"\u003e\n    \u003cspan\u003eHi\u003c/span\u003e\n  \u003c/div\u003e\n\u003c/body\u003e\n```\n\nWe can create our view like so:\n\n```ruby\nclass MyView \u003c Vienna::View\n  element '#foo'\nend\n\nMyView.new.element\n# =\u003e #\u003cElement: [\u003cdiv id=\"foo\"\u003e]\u003e\n```\n\nA real, existing, element can also be passed into the class method:\n\n```ruby\nclass MyView \u003c Vienna::View\n  # Instances of this view will have the document as an element\n  element Document\nend\n```\n\nViews can have parents. If a child view is created, then the dom selector is\nonly searched inside the parents element.\n\n### Customizing elements\n\nA `View` will render as a div tag, by default, with no classes (unless an\nelement selector is defined). Both these can be overriden inside your view\nsubclass.\n\n```ruby\nclass NavigationView \u003c Vienna::View\n  def tag_name\n    :ul\n  end\n\n  def class_name\n    \"navbar navbar-blue\"\n  end\nend\n```\n\n### Rendering views\n\nViews have a placeholder `render` method, that doesnt do anything by default.\nThis is the place to put rendering logic.\n\n```ruby\nclass MyView \u003c Vienna::View\n  def render\n    element.html = 'Welcome to my rubyicious page'\n  end\nend\n\nview = MyView.new\nview.render\n\nview.element\n# =\u003e '\u003cdiv\u003eWelcome to my rubyicious page\u003c/div\u003e'\n```\n\n### Listening for events\n\nWhen an element is created, defined events can be added to it. When a view is\ndestroyed, these event handlers are then removed.\n\n```ruby\nclass ButtonView \u003c Vienna::View\n  on :click do |evt|\n    puts \"clicked on button\"\n  end\n\n  def tag_name\n    :button\n  end\nend\n```\n\nFor complex views, you can provide an optional css selector to scope the events:\n\n```ruby\nclass NavigationView \u003c Vienna::View\n  on :click, 'ul.navbar li' do |evt|\n    puts \"clicked: #{evt.target}\"\n  end\n\n  on :mouseover, 'ul.navbar li.selected', :handle_mouseover\n\n  def handle_mouseover(evt)\n    # ...\n  end\nend\n```\n\nAs you can see, you can specify a method to handle events instead of a block.\n\n### Customizing element creation\n\nYou can also override `create_element` if you wish to have any custom element\ncreation behaviour.\n\nFor example, a subview that is created from a parent element\n\n```ruby\nclass NavigationView \u003c Vienna::View\n  def initialize(parent, selector)\n    @parent, @selector = parent, selector\n  end\n\n  def create_element\n    @parent.find(@selector)\n  end\nend\n```\n\nAssuming we have the html:\n\n```html\n\u003cdiv id=\"header\"\u003e\n  \u003cimg id=\"logo\" src=\"logo.png\" /\u003e\n  \u003cul class=\"navigation\"\u003e\n    \u003cli\u003eHomepage\u003c/li\u003e\n  \u003c/ul\u003e\n\u003c/div\u003e\n```\n\nWe can use the navigation view like this:\n\n```ruby\n@header = Element.find '#header'\nnav_view = NavigationView.new @header, '.navigation'\n\nnav_view.element\n# =\u003e [\u003cul class=\"navigation\"\u003e]\n```\n\n## Router\n\n`Vienna::Router` is a simple router that watches for hashchange events.\n\n```ruby\nrouter = Vienna::Router.new\n\nrouter.route(\"/users\") do\n  puts \"need to show all users\"\nend\n\nrouter.route(\"/users/:id\") do |params|\n  puts \"need to show user: #{ params[:id] }\"\nend\n\n\n# visit \"example.com/#/users\"\n# visit \"example.com/#/users/3\"\n# visit \"example.com/#/users/5\"\n\n# =\u003e \"need to show all users\"\n# =\u003e need to show user: 3\n# =\u003e need to show user: 5\n```\n\n## Observable\n\nAdds KVO style attribute observing.\n\n```ruby\nclass MyObject\n  include Vienna::Observable\n\n  attr_accessor :name\n  attr_reader :age\n\n  def age=(age)\n    @age = age + 10\n  end\nend\n\nobj = MyObject.new\nobj.add_observer(:name) { |new_val| puts \"name changed to #{new_val}\" }\nobj.add_observer(:age) { |new_age| puts \"age changed to #{new_age}\" }\n\nobj.name = \"bob\"\nobj.age = 42\n\n# =\u003e \"name changed to bob\"\n# =\u003e \"age changed to 52\"\n```\n\n## Observable Arrays\n\n```ruby\nclass MyArray\n  include Vienna::ObservableArray\nend\n\narray = MyArray.new\n\narray.add_observer(:content) { |content| puts \"content is now #{content}\" }\narray.add_observer(:size) { |size| puts \"size is now #{size}\" }\n\narray \u003c\u003c :foo\narray \u003c\u003c :bar\n\n# =\u003e content is now [:foo]\n# =\u003e size is now 1\n# =\u003e content is now [:bar]\n# =\u003e size is now 2\n```\n\n#### Todo\n\n* Support older browsers which do not support onhashchange.\n* Support not-hash style routes with HTML5 routing.\n\n## License\n\nMIT\n\n","funding_links":[],"categories":["Uncategorized","Ruby"],"sub_categories":["Uncategorized"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fopal%2Fvienna","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fopal%2Fvienna","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fopal%2Fvienna/lists"}