eyeballs 0.3.7 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. data/CHANGELOG +6 -0
  2. data/Gemfile +13 -0
  3. data/Gemfile.lock +47 -0
  4. data/README.md +62 -17
  5. data/Rakefile +1 -1
  6. data/dist/{jquery-1.4.2.min.js → jquery/jquery-1.4.2.min.js} +0 -0
  7. data/dist/jquery/jquery.ba-hashchange.js +244 -0
  8. data/dist/{jquery.livequery.js → jquery/jquery.livequery.js} +0 -0
  9. data/dist/{mustache.js → mustache/mustache.0.2.3.js} +27 -50
  10. data/eyeballs.gemspec +23 -14
  11. data/lib/eyeballs/app_generator.rb +3 -3
  12. data/spec/app_generator_spec.rb +7 -1
  13. data/spec/spec_helper.rb +1 -2
  14. data/src/{o_O.localstorage.js → adapters/o_O.localstorage.js} +0 -0
  15. data/src/{jquery.o_O.couchdb.js → drivers/jquery/adapters/o_O.couchdb.js} +0 -0
  16. data/src/{jquery.o_O.dom.js → drivers/jquery/adapters/o_O.dom.js} +0 -0
  17. data/src/{jquery.o_O.rails.js → drivers/jquery/adapters/o_O.rest.js} +1 -1
  18. data/src/{jquery.o_O.js → drivers/jquery/modules/o_O.controller.js} +3 -65
  19. data/src/drivers/jquery/modules/o_O.routes.js +45 -0
  20. data/src/drivers/jquery/modules/o_O.support.js +69 -0
  21. data/src/modules/o_O.model.js +188 -0
  22. data/src/modules/o_O.validations.js +45 -0
  23. data/src/o_O.js +0 -235
  24. data/templates/app_root/config/initializer.js +3 -0
  25. data/templates/app_root/config/routes.js +7 -0
  26. data/test/unit/test_controller.html +7 -7
  27. data/test/unit/test_dom.html +7 -4
  28. data/test/unit/test_dom_with_callbacks.html +7 -3
  29. data/test/unit/test_form.html +7 -2
  30. data/test/unit/test_localstorage.html +7 -4
  31. data/test/unit/test_model.html +7 -3
  32. data/test/unit/test_model_with_callbacks.html +7 -3
  33. data/test/unit/{test_rails.html → test_rest.html} +9 -6
  34. data/test/unit/test_routing.html +76 -0
  35. metadata +39 -15
  36. data/eyeballs.js.gemspec +0 -83
data/CHANGELOG CHANGED
@@ -1,3 +1,9 @@
1
+ Tue Jul 6 2010
2
+ - - - - - - - -
3
+ - Housekeeping, add adapters, modules and drivers directories and relevant sub-directories
4
+ - Add routing
5
+ - use "meetings#index" style data-bind shortcuts instead of "meetings/index"
6
+
1
7
  Thu Jun 17 2010
2
8
  - - - - - - - -
3
9
  - Add "prefix" to Rails find method
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source :rubygems
2
+
3
+ gem 'sinatra'
4
+ gem 'thor'
5
+ gem 'activesupport'
6
+
7
+ group :development do
8
+ gem 'jeweler'
9
+ end
10
+
11
+ group :test do
12
+ gem 'rspec'
13
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,47 @@
1
+ ---
2
+ dependencies:
3
+ thor:
4
+ group:
5
+ - :default
6
+ version: ">= 0"
7
+ rspec:
8
+ group:
9
+ - :test
10
+ version: ">= 0"
11
+ sinatra:
12
+ group:
13
+ - :default
14
+ version: ">= 0"
15
+ jeweler:
16
+ group:
17
+ - :development
18
+ version: ">= 0"
19
+ activesupport:
20
+ group:
21
+ - :default
22
+ version: ">= 0"
23
+ specs:
24
+ - activesupport:
25
+ version: 2.3.8
26
+ - json_pure:
27
+ version: 1.4.3
28
+ - gemcutter:
29
+ version: 0.5.0
30
+ - git:
31
+ version: 1.2.5
32
+ - rubyforge:
33
+ version: 2.0.4
34
+ - jeweler:
35
+ version: 1.4.0
36
+ - rack:
37
+ version: 1.2.1
38
+ - rspec:
39
+ version: 1.3.0
40
+ - sinatra:
41
+ version: "1.0"
42
+ - thor:
43
+ version: 0.13.7
44
+ hash: 17f409790dfc3ab7e279201874a3aab85e4c7c95
45
+ sources:
46
+ - Rubygems:
47
+ uri: http://gemcutter.org
data/README.md CHANGED
@@ -6,15 +6,16 @@ eyeballs.js is a slim javascript library designed to sit on top of a javascript
6
6
  The goals are:
7
7
 
8
8
  * Organisation of client-side web app code using the Model-View-Controller pattern.
9
- * Simple model implementation for client-side form validation.
10
- * Rapid development of javascript actions using strong conventions.
9
+ * Simple model implementation for handling non event-related concerns.
10
+ * Simple routing layer for hash-tag change based navigation, preserving the back-button
11
+ * Rapid development of javascript apps using strong conventions.
11
12
  * Easing the pain of building fast, responsive interfaces.
12
13
  * Exploring the possibilities of offline web apps.
13
14
 
14
15
  The implementation is owes a lot to Ruby on Rails, but also attempts to be idiomatic javascript.
15
16
 
16
17
  Overview
17
- ========
18
+ --------
18
19
 
19
20
  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.
20
21
 
@@ -22,14 +23,20 @@ eyeballs.js models are not necessarily one-to-one mapped to server side models u
22
23
 
23
24
  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.
24
25
 
26
+ eyeballs.js is supposed to be both agnostic and modular. The code is broken down into modules, drivers and adapters.
27
+
28
+ *Modules* add various parts of functionality, for example the code that powers the individual model, controller, routing and validation layers.
29
+ *Drivers* add support for underlying javascript frameworks. Features that rely on event handling etc. are part of driver logic.
30
+ *Adapters* provide an API to various persistence layers, eg. HTML5 Local Storage, a REST interface or a CouchDB instance.
31
+
25
32
  Getting Started
26
- ===============
33
+ ---------------
27
34
 
28
35
  eyeballs.js is packaged into modules, according to dependencies.
29
36
 
30
- The main lib, o\_O.js, has no dependencies and lets you use eyeballs.js models standalone.
37
+ The main library has no dependencies and lets you use eyeballs.js models standalone.
31
38
 
32
- Standalone, o\_O.js doesn't do much: it provides the o\_O() function for initializing models and some validations.
39
+ Standalone, eyeballs.js doesn't do much: it provides the o\_O() function for initializing models and some validations.
33
40
 
34
41
  At a very minimum, you should choose an adapter. There are a few to choose from:
35
42
 
@@ -38,7 +45,7 @@ At a very minimum, you should choose an adapter. There are a few to choose from:
38
45
  - **o\_O.couchdb** - persist records to a local CouchDB instance, for building MVC CouchApps, for example.
39
46
  - **o\_O.rails** - An adapter for persisting models to a backend powered by Rails, or using Rails-style RESTful routing.
40
47
 
41
- Finally, you need a controller. The first release of eyeballs.js includes a controller initializer for jQuery. This adapter happens to depend on jQuery.livequery, for reasons of magic.
48
+ Finally, you need a controller. The first release of eyeballs.js includes a controller as part of the jQuery driver. This adapter also depends on jQuery.livequery.
42
49
 
43
50
  You can also use a javascript templating language. Mustache.js fits this need quite nicely.
44
51
 
@@ -51,17 +58,23 @@ Wrapping that all up, to use eyeballs.js with the Rails adapter and jQuery:
51
58
  <!-- Mustache for templating -->
52
59
  <script src="mustache.js"></script>
53
60
 
54
- <!-- eyeballs.js -->
61
+ <!-- eyeballs.js basic -->
55
62
  <script src="o_O.js"></script>
56
- <script src="jquery.o_O.js"></script>
63
+ <script src="modules/o_O.model.js"></script>
64
+ <script src="modules/o_O.validations.js"></script>
65
+
66
+ <!-- eyeballs.js jquery driver for controller logic -->
67
+ <script src="drivers/jquery/modules/o_O.controller.js"></script>
68
+ <script src="drivers/jquery/modules/o_O.support.js"></script>
69
+ <script src="drivers/jquery/modules/o_O.routes.js"></script>
57
70
 
58
- <!-- Rails adapter -->
59
- <script src="o_O.rails.js"></script>
71
+ <!-- REST adapter -->
72
+ <script src="drivers/jquery/adapters/o_O.rest.js"></script>
60
73
 
61
74
  Badabing, badaboom! You're now ready to start creating some models and controllers.
62
75
 
63
76
  Generators
64
- ==========
77
+ ----------
65
78
 
66
79
  If you install the eyeballs.js Ruby gem, you can use the eyeballs command to generate eyeballs.js apps, models and controllers:
67
80
 
@@ -89,7 +102,7 @@ This will generate a `posts.html`, a `post.js` and a `posts_controller.js`.
89
102
  If the generator detects a "public" directory when you run it, it will install into public/javascripts.
90
103
 
91
104
  Models
92
- ======
105
+ ------
93
106
 
94
107
  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.
95
108
 
@@ -181,7 +194,7 @@ Finding, saving, updating and deleting. With callbacks? Easy peasy:
181
194
  There's a strong emphasis on callbacks: since any persisting to backends should be done asynchronously.
182
195
 
183
196
  Controllers
184
- ===========
197
+ -----------
185
198
 
186
199
  An eyeballs.js controller is also initialized with the eyeballs function, by passing a string name and an object containing the controller actions.
187
200
 
@@ -196,6 +209,36 @@ An eyeballs.js controller is also initialized with the eyeballs function, by pas
196
209
 
197
210
  Again, this looks nice and familiar. Dead, dead simple.
198
211
 
212
+ ### Calling Controller Actions & Binding Events ###
213
+
214
+ There are several ways to bind events to controller actions.
215
+
216
+ #### Calling Directly ####
217
+
218
+ The simplest way to call controller actions is to bind them directly. From the above example, you can simply call:
219
+
220
+ PostsController.new()
221
+
222
+ ...once you have initialized your controller.
223
+
224
+ #### Routing ####
225
+
226
+ 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.
227
+
228
+ Your `config/routes.js` file would look something like this:
229
+
230
+ o_O.routes.draw(function(map){
231
+ map.match('/posts/new/', {to: 'posts#new'})
232
+ })
233
+
234
+ You can now bind this to particular links, by adding the `data-ajax-history` attribute to your `a` elements:
235
+
236
+ <a href="/posts/new" data-ajax-history="true">Click Me!</a>
237
+
238
+ This link will now call `PostsController.new()` when it is clicked.
239
+
240
+ #### Binding actions to events ####
241
+
199
242
  To bind events to these controller actions, use the data-controller and data-action attributes:
200
243
 
201
244
  <a href="/posts/new" data-controller="posts" data-action="new">Click me!</a>
@@ -231,7 +274,9 @@ Finally, in shorthand only, you can bind multiple events to a single element:
231
274
  Isn't that cool?
232
275
 
233
276
  Putting it all together
234
- =======================
277
+ -----------------------
278
+
279
+ TODO: The demo app isn't working right now. There should be a generator to generate a new demo app after each release.
235
280
 
236
281
  There's a small demo app included in this package, a simple app for adding personal reviews. It's a simple Sinatra app, so you can run it with:
237
282
 
@@ -290,12 +335,12 @@ That's all for now!
290
335
 
291
336
 
292
337
  Running the tests
293
- =================
338
+ -----------------
294
339
 
295
340
  eyeballs.js uses QUnit for in-browser testing. Just load the files in the test directory in any browser to run them.
296
341
 
297
342
  About me
298
- ========
343
+ --------
299
344
 
300
345
  I'm Paul Campbell. I'm an avid web developer. Follow my ramblings at [http://www.pabcas.com](http://www.pabcas.com)
301
346
 
data/Rakefile CHANGED
@@ -27,7 +27,7 @@ begin
27
27
  require 'jeweler'
28
28
  Jeweler::Tasks.new do |s|
29
29
  s.name = "eyeballs"
30
- s.version = "0.3.7"
30
+ s.version = "0.4.1"
31
31
  s.author = "Paul Campbell"
32
32
  s.email = "paul@rslw.com"
33
33
  s.homepage = "http://www.github.com/paulca/eyeballs.js"
@@ -0,0 +1,244 @@
1
+ /*!
2
+ * jQuery hashchange event - v1.2 - 2/11/2010
3
+ * http://benalman.com/projects/jquery-hashchange-plugin/
4
+ *
5
+ * Copyright (c) 2010 "Cowboy" Ben Alman
6
+ * Dual licensed under the MIT and GPL licenses.
7
+ * http://benalman.com/about/license/
8
+ */
9
+
10
+ // Script: jQuery hashchange event
11
+ //
12
+ // *Version: 1.2, Last updated: 2/11/2010*
13
+ //
14
+ // Project Home - http://benalman.com/projects/jquery-hashchange-plugin/
15
+ // GitHub - http://github.com/cowboy/jquery-hashchange/
16
+ // Source - http://github.com/cowboy/jquery-hashchange/raw/master/jquery.ba-hashchange.js
17
+ // (Minified) - http://github.com/cowboy/jquery-hashchange/raw/master/jquery.ba-hashchange.min.js (1.1kb)
18
+ //
19
+ // About: License
20
+ //
21
+ // Copyright (c) 2010 "Cowboy" Ben Alman,
22
+ // Dual licensed under the MIT and GPL licenses.
23
+ // http://benalman.com/about/license/
24
+ //
25
+ // About: Examples
26
+ //
27
+ // This working example, complete with fully commented code, illustrate one way
28
+ // in which this plugin can be used.
29
+ //
30
+ // hashchange event - http://benalman.com/code/projects/jquery-hashchange/examples/hashchange/
31
+ //
32
+ // About: Support and Testing
33
+ //
34
+ // Information about what version or versions of jQuery this plugin has been
35
+ // tested with, what browsers it has been tested in, and where the unit tests
36
+ // reside (so you can test it yourself).
37
+ //
38
+ // jQuery Versions - 1.3.2, 1.4.1, 1.4.2pre
39
+ // Browsers Tested - Internet Explorer 6-8, Firefox 2-3.7, Safari 3-4, Chrome, Opera 9.6-10.1.
40
+ // Unit Tests - http://benalman.com/code/projects/jquery-hashchange/unit/
41
+ //
42
+ // About: Known issues
43
+ //
44
+ // While this jQuery hashchange event implementation is quite stable and robust,
45
+ // there are a few unfortunate browser bugs surrounding expected hashchange
46
+ // event-based behaviors, independent of any JavaScript window.onhashchange
47
+ // abstraction. See the following examples for more information:
48
+ //
49
+ // Chrome: Back Button - http://benalman.com/code/projects/jquery-hashchange/examples/bug-chrome-back-button/
50
+ // Firefox: Remote XMLHttpRequest - http://benalman.com/code/projects/jquery-hashchange/examples/bug-firefox-remote-xhr/
51
+ // WebKit: Back Button in an Iframe - http://benalman.com/code/projects/jquery-hashchange/examples/bug-webkit-hash-iframe/
52
+ // Safari: Back Button from a different domain - http://benalman.com/code/projects/jquery-hashchange/examples/bug-safari-back-from-diff-domain/
53
+ //
54
+ // About: Release History
55
+ //
56
+ // 1.2 - (2/11/2010) Fixed a bug where coming back to a page using this plugin
57
+ // from a page on another domain would cause an error in Safari 4. Also,
58
+ // IE6/7 Iframe is now inserted after the body (this actually works),
59
+ // which prevents the page from scrolling when the event is first bound.
60
+ // Event can also now be bound before DOM ready, but it won't be usable
61
+ // before then in IE6/7.
62
+ // 1.1 - (1/21/2010) Incorporated document.documentMode test to fix IE8 bug
63
+ // where browser version is incorrectly reported as 8.0, despite
64
+ // inclusion of the X-UA-Compatible IE=EmulateIE7 meta tag.
65
+ // 1.0 - (1/9/2010) Initial Release. Broke out the jQuery BBQ event.special
66
+ // window.onhashchange functionality into a separate plugin for users
67
+ // who want just the basic event & back button support, without all the
68
+ // extra awesomeness that BBQ provides. This plugin will be included as
69
+ // part of jQuery BBQ, but also be available separately.
70
+
71
+ (function($,window,undefined){
72
+ '$:nomunge'; // Used by YUI compressor.
73
+
74
+ // Method / object references.
75
+ var fake_onhashchange,
76
+ jq_event_special = $.event.special,
77
+
78
+ // Reused strings.
79
+ str_location = 'location',
80
+ str_hashchange = 'hashchange',
81
+ str_href = 'href',
82
+
83
+ // IE6/7 specifically need some special love when it comes to back-button
84
+ // support, so let's do a little browser sniffing..
85
+ browser = $.browser,
86
+ mode = document.documentMode,
87
+ is_old_ie = browser.msie && ( mode === undefined || mode < 8 ),
88
+
89
+ // Does the browser support window.onhashchange? Test for IE version, since
90
+ // IE8 incorrectly reports this when in "IE7" or "IE8 Compatibility View"!
91
+ supports_onhashchange = 'on' + str_hashchange in window && !is_old_ie;
92
+
93
+ // Get location.hash (or what you'd expect location.hash to be) sans any
94
+ // leading #. Thanks for making this necessary, Firefox!
95
+ function get_fragment( url ) {
96
+ url = url || window[ str_location ][ str_href ];
97
+ return url.replace( /^[^#]*#?(.*)$/, '$1' );
98
+ };
99
+
100
+ // Property: jQuery.hashchangeDelay
101
+ //
102
+ // The numeric interval (in milliseconds) at which the <hashchange event>
103
+ // polling loop executes. Defaults to 100.
104
+
105
+ $[ str_hashchange + 'Delay' ] = 100;
106
+
107
+ // Event: hashchange event
108
+ //
109
+ // Fired when location.hash changes. In browsers that support it, the native
110
+ // window.onhashchange event is used (IE8, FF3.6), otherwise a polling loop is
111
+ // initialized, running every <jQuery.hashchangeDelay> milliseconds to see if
112
+ // the hash has changed. In IE 6 and 7, a hidden Iframe is created to allow
113
+ // the back button and hash-based history to work.
114
+ //
115
+ // Usage:
116
+ //
117
+ // > $(window).bind( 'hashchange', function(e) {
118
+ // > var hash = location.hash;
119
+ // > ...
120
+ // > });
121
+ //
122
+ // Additional Notes:
123
+ //
124
+ // * The polling loop and Iframe are not created until at least one callback
125
+ // is actually bound to 'hashchange'.
126
+ // * If you need the bound callback(s) to execute immediately, in cases where
127
+ // the page 'state' exists on page load (via bookmark or page refresh, for
128
+ // example) use $(window).trigger( 'hashchange' );
129
+ // * The event can be bound before DOM ready, but since it won't be usable
130
+ // before then in IE6/7 (due to the necessary Iframe), recommended usage is
131
+ // to bind it inside a $(document).ready() callback.
132
+
133
+ jq_event_special[ str_hashchange ] = $.extend( jq_event_special[ str_hashchange ], {
134
+
135
+ // Called only when the first 'hashchange' event is bound to window.
136
+ setup: function() {
137
+ // If window.onhashchange is supported natively, there's nothing to do..
138
+ if ( supports_onhashchange ) { return false; }
139
+
140
+ // Otherwise, we need to create our own. And we don't want to call this
141
+ // until the user binds to the event, just in case they never do, since it
142
+ // will create a polling loop and possibly even a hidden Iframe.
143
+ $( fake_onhashchange.start );
144
+ },
145
+
146
+ // Called only when the last 'hashchange' event is unbound from window.
147
+ teardown: function() {
148
+ // If window.onhashchange is supported natively, there's nothing to do..
149
+ if ( supports_onhashchange ) { return false; }
150
+
151
+ // Otherwise, we need to stop ours (if possible).
152
+ $( fake_onhashchange.stop );
153
+ }
154
+
155
+ });
156
+
157
+ // fake_onhashchange does all the work of triggering the window.onhashchange
158
+ // event for browsers that don't natively support it, including creating a
159
+ // polling loop to watch for hash changes and in IE 6/7 creating a hidden
160
+ // Iframe to enable back and forward.
161
+ fake_onhashchange = (function(){
162
+ var self = {},
163
+ timeout_id,
164
+ iframe,
165
+ set_history,
166
+ get_history;
167
+
168
+ // Initialize. In IE 6/7, creates a hidden Iframe for history handling.
169
+ function init(){
170
+ // Most browsers don't need special methods here..
171
+ set_history = get_history = function(val){ return val; };
172
+
173
+ // But IE6/7 do!
174
+ if ( is_old_ie ) {
175
+
176
+ // Create hidden Iframe after the end of the body to prevent initial
177
+ // page load from scrolling unnecessarily.
178
+ iframe = $('<iframe src="javascript:0"/>').hide().insertAfter( 'body' )[0].contentWindow;
179
+
180
+ // Get history by looking at the hidden Iframe's location.hash.
181
+ get_history = function() {
182
+ return get_fragment( iframe.document[ str_location ][ str_href ] );
183
+ };
184
+
185
+ // Set a new history item by opening and then closing the Iframe
186
+ // document, *then* setting its location.hash.
187
+ set_history = function( hash, history_hash ) {
188
+ if ( hash !== history_hash ) {
189
+ var doc = iframe.document;
190
+ doc.open().close();
191
+ doc[ str_location ].hash = '#' + hash;
192
+ }
193
+ };
194
+
195
+ // Set initial history.
196
+ set_history( get_fragment() );
197
+ }
198
+ };
199
+
200
+ // Start the polling loop.
201
+ self.start = function() {
202
+ // Polling loop is already running!
203
+ if ( timeout_id ) { return; }
204
+
205
+ // Remember the initial hash so it doesn't get triggered immediately.
206
+ var last_hash = get_fragment();
207
+
208
+ // Initialize if not yet initialized.
209
+ set_history || init();
210
+
211
+ // This polling loop checks every $.hashchangeDelay milliseconds to see if
212
+ // location.hash has changed, and triggers the 'hashchange' event on
213
+ // window when necessary.
214
+ (function loopy(){
215
+ var hash = get_fragment(),
216
+ history_hash = get_history( last_hash );
217
+
218
+ if ( hash !== last_hash ) {
219
+ set_history( last_hash = hash, history_hash );
220
+
221
+ $(window).trigger( str_hashchange );
222
+
223
+ } else if ( history_hash !== last_hash ) {
224
+ window[ str_location ][ str_href ] = window[ str_location ][ str_href ].replace( /#.*/, '' ) + '#' + history_hash;
225
+ }
226
+
227
+ timeout_id = setTimeout( loopy, $[ str_hashchange + 'Delay' ] );
228
+ })();
229
+ };
230
+
231
+ // Stop the polling loop, but only if an IE6/7 Iframe wasn't created. In
232
+ // that case, even if there are no longer any bound event handlers, the
233
+ // polling loop is still necessary for back/next to work at all!
234
+ self.stop = function() {
235
+ if ( !iframe ) {
236
+ timeout_id && clearTimeout( timeout_id );
237
+ timeout_id = 0;
238
+ }
239
+ };
240
+
241
+ return self;
242
+ })();
243
+
244
+ })(jQuery,this);