Eyeballs.js
A lightweight MVC framework for building fast, tidy javascript web apps
Install / Use
/learn @paulca/Eyeballs.jsREADME
eyeballs.js
eyeballs.js is a slim javascript library designed to sit on top of a javascript framework, such as jQuery or Prototype.
The goals are:
- Organisation of client-side web app code using the Model-View-Controller pattern.
- Simple model implementation for handling non event-related concerns.
- Simple routing layer for hash-tag change based navigation, preserving the back-button
- Rapid development of javascript apps using strong conventions.
- Easing the pain of building fast, responsive interfaces.
- Exploring the possibilities of offline web apps.
The implementation is owes a lot to Ruby on Rails, but also attempts to be idiomatic javascript.
Overview
eyeballs.js can sit on top of an already implemented web app with a well thought out object model. It can also be used to build standalone javascript apps, backed by HTML5 local storage or something like CouchDB.
eyeballs.js models are not necessarily one-to-one mapped to server side models using your web framework of choice. They more closely map to interface elements, but there aren't really any rules as yet.
Finally, eyeballs.js is still a bit of an experiment. It's a quick implementation of a crazy idea to help make javascript code a little bit more organised.
eyeballs.js is supposed to be both agnostic and modular. The code is broken down into modules, drivers and adapters.
Modules add various parts of functionality, for example the code that powers the individual model, controller, routing and validation layers. Drivers add support for underlying javascript frameworks. Features that rely on event handling etc. are part of driver logic. Adapters provide an API to various persistence layers, eg. HTML5 Local Storage, a REST interface or a CouchDB instance.
Getting Started
eyeballs.js is packaged into modules, according to dependencies.
The main library has no dependencies and lets you use eyeballs.js models standalone.
Standalone, eyeballs.js doesn't do much: it provides the o_O() function for initializing models and some validations.
At a very minimum, you should choose an adapter. There are a few to choose from:
- o_O.dom - a basic adapter without any persistence.
- o_O.localstorage - persist records to HTML5 local storage.
- o_O.couchdb - persist records to a local CouchDB instance, for building MVC CouchApps, for example.
- o_O.rest - An adapter for persisting models to a backend powered by Rails, or using Rails-style RESTful routing.
Finally, you need a controller. The first release of eyeballs.js includes a controller as part of the jQuery driver.
You can also use a javascript templating language. Mustache.js fits this need quite nicely.
Wrapping that all up, to use eyeballs.js with the Rails adapter and jQuery:
<!-- jQuery and livequery -->
<script src="vendor/javascripts/jquery.1.4.min.js"></script>
<script src="vendor/jquery/jquery.ba-bbq.min.js"></script>
<!-- Mustache for templating -->
<script src="vendor/mustache/mustache.0.3.0.js"></script>
<!-- eyeballs.js basic -->
<script src="vendor/eyeballs/o_O.js"></script>
<script src="vendor/eyeballs/modules/o_O.model.js"></script>
<script src="vendor/eyeballs/modules/o_O.validations.js"></script>
<!-- eyeballs.js jquery driver for controller logic -->
<script src="vendor/eyeballs/drivers/jquery/modules/o_O.controller.js"></script>
<script src="vendor/eyeballs/drivers/jquery/modules/o_O.support.js"></script>
<script src="vendor/eyeballs/drivers/jquery/modules/o_O.routes.js"></script>
<!-- REST adapter -->
<script src="vendor/eyeballs/drivers/jquery/adapters/o_O.rest.js"></script>
<!-- Configuration -->
<script src="config/initializer.js"></script>
<script src="config/routes.js"></script>
Badabing, badaboom! You're now ready to start creating some models and controllers.
Generators
If you install the eyeballs.js Ruby gem, you can use the eyeballs command to generate eyeballs.js apps, models and controllers:
gem install eyeballs
To create a new eyeballs.js app:
eyeballs my_new_app
This will create a new app in the my_new_app folder.
When you're up and running, you can use the model and controller generators:
eyeballs generate model Post
eyeballs generate controller Posts
These generators will install files to app/models and app/controllers relative to where you run the eyeballs command.
You can also generate a scaffold:
eyeballs generate scaffold Post
This will generate a posts.html, a post.js and a posts_controller.js.
If the generator detects a "public" directory when you run it, it will install into public/javascripts.
Models
You define a model by passing a name and function to the eyeballs ( o_O ) function (pronounced 'eep eep'). As inspired by Rails, model definitions are capitalised. Note, however, that the new prefix is not used.
o_O('Post', function(){ })
You can now initialize an individual post:
var post = Post.initialize({title: 'My New Post'});
post.title //=> 'My New Post'
Not very exciting.
However, if you're familiar with Rails, you'll be familiar with the wonderful syntax for adding validations to your models. eyeballs.js lets you add validations to your models as follows:
o_O('Post', function(){
this.validates_presence_of('title')
})
Now, when you initialize a new Post, you can validate it, nice and easy:
var post = Post.initialize()
post.valid()
post.errors // => [{
// field: 'title',
// message: 'title should be present',
// type: 'presence'}]
and so on, so forth. This will be very familiar to those who use Rails.
You can also add your own validations, again, similar to how Rails does things:
o_O('Post', function(){
this.validates(function(post){
if(post.title != 'Awesome')
{
post.errors.push({message: 'Not Awesome Enough'})
}
})
})
var post = Post.initialize()
post.save(function(saved_post){
post.errors // => [{
// message: 'title should be present'}]
}) // yep, there's a save method too!
Even better, using the invalid callback:
post.save({
invalid: function(saved_post){
// you can assume:
saved_post.errors
}
})
When you want to find things:
Post.find({
loading: function(){
console.log("I'm loading...")
},
success: function(post){
console.log("here I am")
}
})
And if you want to add your own methods:
o_O('Post', function(){
this.methods.title_downcased: function(){
this.title.toLowerCase();
}
})
var post = Post.initialize({title: "HUGE"})
post.title_downcased() //=> 'huge'
Connecting to an adapter:
If you want to hook eyeballs.js up to the local storage adapter for example:
o_O.model.adapter = o_O.localstorage
Finding, saving, updating and deleting. With callbacks? Easy peasy:
post = Post.initialize({title: 'My new post'})
post.save({
invalid: function(post){
alert('Sorry, invalid!');
},
loading: function(post){
alert('I hapeen straight away');
},
success: function(post){
alert('Saved, whoop!');
}
})
There's a strong emphasis on callbacks: since any persisting to backends should be done asynchronously.
Controllers
An eyeballs.js controller is also initialized with the eyeballs function, by passing a string name and an object containing the controller actions.
o_O('PostsController', {
new: function(){
alert("I'm new");
},
create: function(){
alert("I'm create");
}
})
Again, this looks nice and familiar. Dead, dead simple.
Calling Controller Actions & Binding Events
There are several ways to bind events to controller actions.
Calling Directly
The simplest way to call controller actions is to bind them directly. From the above example, you can simply call:
o_O('PostsController').new()
...once you have initialized your controller.
Routing
You can use the eyeballs.js router to bind events to changes in the URL hash. This is particularly effective for graceful degradation, as well as preserving the back button history.
Your config/routes.js file would look something like this:
o_O.routes.draw(function(map){
map.match('/posts/new/', {to: 'posts#new'})
})
You can now bind this to particular links, by adding the data-ajax-history attribute to your a elements:
<a href="/posts/new" data-ajax-history="true">Click Me!</a>
This link will now call PostsController.new() when it is clicked.
You can also set params in the URL, eg:
o_O.routes.draw(function(map){
map.match('/posts/:id/', {to: 'posts#show'})
})
<a href="/posts/1">Click Me for Post 1!</a>
or in the route:
o_O.routes.draw(function(map){
map.match('/posts/:id/', {to: 'posts#show', 'custom':'posts'})
})
o_O.params('custom') #=> 'post'
and your controller:
o_O('PostsController', {
show: function(){
alert(o_O.params('id')) //=> '1'
}
})
If you want a default action to fire, that is when the document.hash is empty, just hook up a map.root:
o_O.routes.draw(function(map){
map.root({to: 'posts#index'})
})
If you have several routes that share the same prefix, you can use a namespace:
o_O.routes.draw(function(map){
map.namespace('my', function(){
map.match('posts/new', {to: "myposts#new"}) # hooks up to MypostsController.new()
})
})
Tasty!
Binding actions to events
To bind events to these controller actions, use the dat
Related Skills
node-connect
343.1kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
90.0kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
343.1kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
343.1kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
