nitro 0.23.0 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. data/CHANGELOG +350 -0
  2. data/INSTALL +2 -2
  3. data/ProjectInfo +61 -0
  4. data/README +5 -4
  5. data/Rakefile +5 -4
  6. data/bin/nitrogen +3 -1
  7. data/doc/AUTHORS +27 -3
  8. data/doc/RELEASES +193 -0
  9. data/doc/lhttpd.txt +4 -0
  10. data/lib/nitro.rb +1 -1
  11. data/lib/nitro/adapter/cgi.rb +6 -321
  12. data/lib/nitro/adapter/fastcgi.rb +2 -14
  13. data/lib/nitro/adapter/scgi.rb +237 -71
  14. data/lib/nitro/adapter/webrick.rb +25 -7
  15. data/lib/nitro/caching.rb +1 -0
  16. data/lib/nitro/cgi.rb +296 -0
  17. data/lib/nitro/{cookie.rb → cgi/cookie.rb} +0 -0
  18. data/lib/nitro/cgi/http.rb +62 -0
  19. data/lib/nitro/{request.rb → cgi/request.rb} +4 -1
  20. data/lib/nitro/{response.rb → cgi/response.rb} +0 -0
  21. data/lib/nitro/cgi/stream.rb +43 -0
  22. data/lib/nitro/cgi/utils.rb +38 -0
  23. data/lib/nitro/compiler.rb +23 -11
  24. data/lib/nitro/compiler/css.rb +8 -0
  25. data/lib/nitro/compiler/morphing.rb +66 -0
  26. data/lib/nitro/context.rb +21 -30
  27. data/lib/nitro/controller.rb +23 -100
  28. data/lib/nitro/dispatcher.rb +18 -8
  29. data/lib/nitro/element.rb +6 -2
  30. data/lib/nitro/flash.rb +2 -2
  31. data/lib/nitro/mixin/buffer.rb +2 -2
  32. data/lib/nitro/mixin/form.rb +204 -93
  33. data/lib/nitro/mixin/javascript.rb +170 -11
  34. data/lib/nitro/mixin/markup.rb +1 -0
  35. data/lib/nitro/mixin/pager.rb +7 -4
  36. data/lib/nitro/mixin/rss.rb +2 -0
  37. data/lib/nitro/mixin/table.rb +23 -6
  38. data/lib/nitro/mixin/xhtml.rb +2 -2
  39. data/lib/nitro/render.rb +19 -5
  40. data/lib/nitro/scaffold.rb +12 -6
  41. data/lib/nitro/server.rb +4 -6
  42. data/lib/nitro/server/runner.rb +2 -2
  43. data/lib/nitro/session.rb +8 -1
  44. data/lib/nitro/session/file.rb +40 -0
  45. data/lib/part/admin.rb +2 -0
  46. data/lib/part/admin/controller.rb +7 -3
  47. data/lib/part/admin/skin.rb +8 -1
  48. data/lib/part/admin/template/index.xhtml +39 -1
  49. data/proto/public/error.xhtml +5 -3
  50. data/proto/public/js/behaviour.js +254 -254
  51. data/proto/public/js/controls.js +427 -165
  52. data/proto/public/js/dragdrop.js +255 -276
  53. data/proto/public/js/effects.js +476 -277
  54. data/proto/public/js/prototype.js +561 -127
  55. data/proto/public/js/scaffold.js +74 -0
  56. data/proto/public/js/scriptaculous.js +44 -0
  57. data/proto/public/js/util.js +548 -0
  58. data/proto/public/scaffold/list.xhtml +4 -1
  59. data/proto/scgi.rb +333 -0
  60. data/script/scgi_ctl +221 -0
  61. data/script/scgi_service +120 -0
  62. data/test/nitro/adapter/raw_post1.bin +0 -0
  63. data/test/nitro/{tc_cookie.rb → cgi/tc_cookie.rb} +1 -1
  64. data/test/nitro/{tc_request.rb → cgi/tc_request.rb} +1 -1
  65. data/test/nitro/mixin/tc_xhtml.rb +1 -1
  66. data/test/nitro/{adapter/tc_cgi.rb → tc_cgi.rb} +12 -12
  67. data/test/nitro/tc_controller.rb +9 -5
  68. metadata +159 -169
  69. data/benchmark/bench.rb +0 -5
  70. data/benchmark/simple-webrick-n-200.txt +0 -44
  71. data/benchmark/static-webrick-n-200.txt +0 -43
  72. data/benchmark/tiny-lhttpd-n-200-c-5.txt +0 -43
  73. data/benchmark/tiny-webrick-n-200-c-5.txt +0 -44
  74. data/benchmark/tiny-webrick-n-200.txt +0 -44
  75. data/benchmark/tiny2-webrick-n-200.txt +0 -44
  76. data/examples/README +0 -7
@@ -6,7 +6,6 @@ require 'nitro/mixin/form'
6
6
  module Nitro
7
7
 
8
8
  # The scaffolder adds default actions to a Controller.
9
- #
10
9
  #--
11
10
  # FIXME: handle controller base in generated routes.
12
11
  # FIXME: better handle templates (check if action exists).
@@ -108,7 +107,13 @@ module Scaffolding
108
107
  template.gsub!(/%list_name%/, "#{list_name}")
109
108
  end
110
109
  =end
111
- unless compiler.template_for_action(list_name, self.template_root)
110
+ unless compiler.template_for_action(list_name, self.template_root) or
111
+ #--
112
+ # FIXME: this is a hack fix. to better fix, implemente deferred scaffolding.
113
+ #++
114
+ compiler.template_for_action(list_name, 'public') or
115
+ compiler.template_for_action(list_name, 'src/template') or
116
+ compiler.template_for_action(list_name, 'src/view')
112
117
  source = File.read(File.join(Nitro.proto_path, 'public', 'scaffold', 'list.xhtml'))
113
118
  template = Compiler.new.transform_template(source)
114
119
 
@@ -129,9 +134,10 @@ module Scaffolding
129
134
 
130
135
  def save#{suffix}
131
136
  if oid = request['#{oid}']
132
- obj = request.fill(#{klass}[oid])
137
+ oid = oid.to_s # in case oid is a StringIO (multipart).
138
+ obj = request.fill(#{klass}[oid], :assign_relations => true, :force_boolean => true)
133
139
  else
134
- obj = request.fill(#{klass}.new)
140
+ obj = request.fill(#{klass}.new, :assign_relations => true)
135
141
  end
136
142
  unless obj.valid?
137
143
  session[:ERRORS] = obj.errors
@@ -153,8 +159,6 @@ module Scaffolding
153
159
  }
154
160
  end
155
161
 
156
- # puts '--', klass, '..', code
157
-
158
162
  class_eval(code)
159
163
  end
160
164
 
@@ -163,3 +167,5 @@ module Scaffolding
163
167
  end
164
168
 
165
169
  end
170
+
171
+ # * George Moschovitis <gm@navel.gr>
data/lib/nitro/server.rb CHANGED
@@ -6,11 +6,8 @@ module Nitro
6
6
  class Server
7
7
 
8
8
  # The server listening address.
9
- #--
10
- # 0.0.0.0 may be a better default?
11
- #++
12
9
 
13
- setting :address, :default => '127.0.0.1', :doc => 'The server listening address'
10
+ setting :address, :default => '0.0.0.0', :doc => 'The server listening address'
14
11
 
15
12
  # The server listening port.
16
13
 
@@ -18,7 +15,7 @@ class Server
18
15
 
19
16
  # The map.
20
17
 
21
- setting :map, :default => { '/' => SimpleController }, :doc => 'The server map'
18
+ setting :map, :default => { '/' => Controller }, :doc => 'The server map'
22
19
 
23
20
  # The public files root directory.
24
21
 
@@ -89,7 +86,8 @@ class Server
89
86
 
90
87
  def start(options = {})
91
88
  @map['/'] = options[:controller] if options[:controller]
92
- @dispatcher = options[:dispatche] || Dispatcher.new(@map)
89
+ @dispatcher = options[:dispatcher] || Dispatcher.new(@map)
90
+ return self
93
91
  end
94
92
 
95
93
  def root=(controller)
@@ -239,7 +239,7 @@ class Runner
239
239
 
240
240
  elsif 'cgi_proc' == ENV['NITRO_INVOKE']
241
241
  require 'nitro/adapter/cgi'
242
- Cgi.start(server)
242
+ CgiAdapter.start(server)
243
243
 
244
244
  elsif 'irb' == ENV['NITRO_INVOKE']
245
245
  $server = server
@@ -283,7 +283,7 @@ class Runner
283
283
 
284
284
  when :lhttpd
285
285
  require 'nitro/adapter/fastcgi'
286
- `lighttpd -f conf/lhttpd.conf`
286
+ `lighttpd -f conf/lhttpd_fcgi.conf`
287
287
 
288
288
  when :lhttpd_scgi
289
289
  require 'nitro/adapter/scgi'
data/lib/nitro/session.rb CHANGED
@@ -7,7 +7,7 @@ require 'mega/time_in_english'
7
7
  require 'glue/attribute'
8
8
  require 'glue/configuration'
9
9
 
10
- require 'nitro/cookie'
10
+ require 'nitro/cgi/cookie'
11
11
 
12
12
  module Nitro
13
13
 
@@ -92,6 +92,13 @@ class Session < Hash
92
92
  Session.store.delete_if { |key, s| s.invalid? }
93
93
  end
94
94
  alias_method :gc!, :garbage_collection!
95
+
96
+ # Helper method that returns all sessions.
97
+
98
+ def all
99
+ Session.store.values
100
+ end
101
+
95
102
  end
96
103
 
97
104
  # The unique id of this session.
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'nitro/session'
4
+
5
+ module Nitro
6
+ class FileSessionStore
7
+ # The session keepalive time. The session is eligable for
8
+ # garbage collection after this time passes.
9
+
10
+ setting :path, :default => "/tmp/nitro_session", :doc => 'The directory to store file session'
11
+
12
+ def initialize
13
+ @path = FileSessionStore.path
14
+ Dir.mkdir(@path) unless File.exists?(@path)
15
+ end
16
+
17
+ def []=(k,v)
18
+ File.open(File.join(@path, k), "w") { |f| f.write(Marshal.dump(v)) }
19
+ end
20
+
21
+ def [](k)
22
+ fn = File.join(@path, k)
23
+ return nil unless File.exists?(fn)
24
+ Marshal.load( File.read(fn) )
25
+ end
26
+
27
+ # def values
28
+ # []
29
+ # end
30
+ #
31
+ # def each
32
+ # #yield
33
+ # end
34
+
35
+ end
36
+
37
+ Session.store = FileSessionStore.new
38
+ end
39
+
40
+ # * Guillaume Pierronnet <guillaume.pierronnet@gmail.com>
data/lib/part/admin.rb CHANGED
@@ -1,3 +1,5 @@
1
+ require 'nano/time/stamp'
2
+
1
3
  require 'nitro/server'
2
4
 
3
5
  require 'part/admin/controller'
@@ -1,9 +1,13 @@
1
1
  require 'nitro/controller'
2
+ require 'nitro/mixin/form'
2
3
 
3
4
  class AdminController < Nitro::Controller
4
-
5
- @template_root = File.join(File.dirname(__FILE__), 'template')
5
+ include Nitro::FormMixin
6
6
 
7
+ def self.template_root
8
+ File.join(File.dirname(__FILE__), 'template')
9
+ end
10
+
7
11
  def index
8
12
  @classes = self.class.managed_classes
9
13
  end
@@ -15,7 +19,7 @@ class AdminController < Nitro::Controller
15
19
 
16
20
  # Called when this controller is mounted.
17
21
 
18
- def mounted
22
+ def mounted(path)
19
23
  @managed_classes = Og.manager.manageable_classes
20
24
  @managed_classes.each { |c| scaffold(c) }
21
25
  end
@@ -8,13 +8,20 @@ class SystemPage < Nitro::Element
8
8
  <title>#@name</title>
9
9
  <base href="\#{context.host_url}/" />
10
10
  <link rel="stylesheet" href="/system.css" type="text/css" media="screen" />
11
+ <script src="/js/behaviour.js" type="text/javascript"> </script>
12
+ <script src="/js/prototype.js" type="text/javascript"> </script>
13
+ <script src="/js/effects.js" type="text/javascript"> </script>
14
+ <script src="/js/dragdrop.js" type="text/javascript"> </script>
15
+ <script src="/js/controls.js" type="text/javascript"> </script>
16
+ <script src="/js/scaffold.js" type="text/javascript"> </script>
11
17
  </head>
12
18
  <body>
13
- <h1>#@name</h1>
19
+ <h1><a href="/">Home</a> / #@name</h1>
14
20
  #{content}
15
21
  <br />
16
22
  Powered by <a href="http://www.nitrohq.com">Nitro</a> version #{Nitro::Version}
17
23
  </body>
24
+ <script type="text/javascript" src="js/scaffold.js" />
18
25
  </html>
19
26
  ~
20
27
  end
@@ -1,9 +1,47 @@
1
1
  <SystemPage name="System">
2
+
3
+ <style>
4
+ th {
5
+ border: 1px solid #ccc;
6
+ background: #eee;
7
+ text-align: left;
8
+ }
9
+ </style>
10
+
11
+ <h2>Og managed classes</h2>
12
+
2
13
  <table>
14
+ <tr>
15
+ <th>Class</th>
16
+ <th>Count</th>
17
+ <th>Properties</th>
18
+ </tr>
3
19
  <?r for c in @classes ?>
4
20
  <tr>
5
- <td><a href="#@base/#{c.name.plural.underscore}">#{c.name}</a> (#{c.count})</td>
21
+ <td><a href="#@base/#{c.name.plural.underscore}">#{c.name}</a></td>
22
+ <td>#{c.count}</td>
23
+ <td width="100%">#{c.properties.values.join(', ')}</td>
24
+ </tr>
25
+ <?r end ?>
26
+ </table>
27
+
28
+ <h2>System configuration</h2>
29
+
30
+ <table width="100%">
31
+ <tr>
32
+ <th>Name</th>
33
+ <th>Value</th>
34
+ <th>Type</th>
35
+ <th>Description</th>
36
+ </tr>
37
+ <?r for s in Configuration.settings ?>
38
+ <tr>
39
+ <td>#{s.owner}.<strong>#{s.name}</strong></td>
40
+ <td>#{s.value.inspect}</td>
41
+ <td>#{s.type}</td>
42
+ <td>#{s.options[:doc]}</td>
6
43
  </tr>
7
44
  <?r end ?>
8
45
  </table>
46
+
9
47
  </SystemPage>
@@ -31,11 +31,13 @@
31
31
  <body>
32
32
  <h1>Error</h1>
33
33
 
34
- <?r if Run.mode == :debug ?>
35
-
34
+ <?r
35
+ if Run.mode == :debug
36
+ require 'cgi'
37
+ ?>
36
38
  <?r for error, path in @context.rendering_errors ?>
37
39
  <div class="path"><strong>Path:</strong> #{path}</div>
38
- <div class="error"><strong>#{error.to_s}</strong></div>
40
+ <div class="error"><strong>#{CGI.escapeHTML(error.to_s)}</strong></div>
39
41
  <div class="load">Click here to <strong><a href="#{request.uri}">reload</a></strong>.</div>
40
42
  <div class="load">Click here to go to the <strong><a href="#{request.referer}">referer</a></strong> or the <strong><a href="/">home page</a></strong>.</div>
41
43
  <?r if error.respond_to?(:source_extract) ?>
@@ -1,254 +1,254 @@
1
- /*
2
- Behaviour v1.0 by Ben Nolan, June 2005. Based largely on the work
3
- of Simon Willison (see comments by Simon below).
4
-
5
- Description:
6
-
7
- Uses css selectors to apply javascript behaviours to enable
8
- unobtrusive javascript in html documents.
9
-
10
- Usage:
11
-
12
- var myrules = {
13
- 'b.someclass' : function(element){
14
- element.onclick = function(){
15
- alert(this.innerHTML);
16
- }
17
- },
18
- '#someid u' : function(element){
19
- element.onmouseover = function(){
20
- this.innerHTML = "BLAH!";
21
- }
22
- }
23
- );
24
-
25
- Behaviour.register(myrules);
26
-
27
- // Call Behaviour.apply() to re-apply the rules (if you
28
- // update the dom, etc).
29
-
30
- License:
31
-
32
- My stuff is BSD licensed. Not sure about Simon's.
33
-
34
- More information:
35
-
36
- http://ripcord.co.nz/behaviour/
37
-
38
- */
39
-
40
- var Behaviour = {
41
- list : new Array,
42
-
43
- register : function(sheet){
44
- Behaviour.list.push(sheet);
45
- },
46
-
47
- start : function(){
48
- Behaviour.addLoadEvent(function(){
49
- Behaviour.apply();
50
- });
51
- },
52
-
53
- apply : function(){
54
- for (h=0;sheet=Behaviour.list[h];h++){
55
- for (selector in sheet){
56
- list = document.getElementsBySelector(selector);
57
-
58
- if (!list){
59
- continue;
60
- }
61
-
62
- for (i=0;element=list[i];i++){
63
- sheet[selector](element);
64
- }
65
- }
66
- }
67
- },
68
-
69
- addLoadEvent : function(func){
70
- var oldonload = window.onload;
71
-
72
- if (typeof window.onload != 'function') {
73
- window.onload = func;
74
- } else {
75
- window.onload = function() {
76
- oldonload();
77
- func();
78
- }
79
- }
80
- }
81
- }
82
-
83
- Behaviour.start();
84
-
85
- /*
86
- The following code is Copyright (C) Simon Willison 2004.
87
-
88
- document.getElementsBySelector(selector)
89
- - returns an array of element objects from the current document
90
- matching the CSS selector. Selectors can contain element names,
91
- class names and ids and can be nested. For example:
92
-
93
- elements = document.getElementsBySelect('div#main p a.external')
94
-
95
- Will return an array of all 'a' elements with 'external' in their
96
- class attribute that are contained inside 'p' elements that are
97
- contained inside the 'div' element which has id="main"
98
-
99
- New in version 0.4: Support for CSS2 and CSS3 attribute selectors:
100
- See http://www.w3.org/TR/css3-selectors/#attribute-selectors
101
-
102
- Version 0.4 - Simon Willison, March 25th 2003
103
- -- Works in Phoenix 0.5, Mozilla 1.3, Opera 7, Internet Explorer 6, Internet Explorer 5 on Windows
104
- -- Opera 7 fails
105
- */
106
-
107
- function getAllChildren(e) {
108
- // Returns all children of element. Workaround required for IE5/Windows. Ugh.
109
- return e.all ? e.all : e.getElementsByTagName('*');
110
- }
111
-
112
- document.getElementsBySelector = function(selector) {
113
- // Attempt to fail gracefully in lesser browsers
114
- if (!document.getElementsByTagName) {
115
- return new Array();
116
- }
117
- // Split selector in to tokens
118
- var tokens = selector.split(' ');
119
- var currentContext = new Array(document);
120
- for (var i = 0; i < tokens.length; i++) {
121
- token = tokens[i].replace(/^\s+/,'').replace(/\s+$/,'');;
122
- if (token.indexOf('#') > -1) {
123
- // Token is an ID selector
124
- var bits = token.split('#');
125
- var tagName = bits[0];
126
- var id = bits[1];
127
- var element = document.getElementById(id);
128
- if (tagName && element.nodeName.toLowerCase() != tagName) {
129
- // tag with that ID not found, return false
130
- return new Array();
131
- }
132
- // Set currentContext to contain just this element
133
- currentContext = new Array(element);
134
- continue; // Skip to next token
135
- }
136
- if (token.indexOf('.') > -1) {
137
- // Token contains a class selector
138
- var bits = token.split('.');
139
- var tagName = bits[0];
140
- var className = bits[1];
141
- if (!tagName) {
142
- tagName = '*';
143
- }
144
- // Get elements matching tag, filter them for class selector
145
- var found = new Array;
146
- var foundCount = 0;
147
- for (var h = 0; h < currentContext.length; h++) {
148
- var elements;
149
- if (tagName == '*') {
150
- elements = getAllChildren(currentContext[h]);
151
- } else {
152
- elements = currentContext[h].getElementsByTagName(tagName);
153
- }
154
- for (var j = 0; j < elements.length; j++) {
155
- found[foundCount++] = elements[j];
156
- }
157
- }
158
- currentContext = new Array;
159
- var currentContextIndex = 0;
160
- for (var k = 0; k < found.length; k++) {
161
- if (found[k].className && found[k].className.match(new RegExp('\\b'+className+'\\b'))) {
162
- currentContext[currentContextIndex++] = found[k];
163
- }
164
- }
165
- continue; // Skip to next token
166
- }
167
- // Code to deal with attribute selectors
168
- if (token.match(/^(\w*)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/)) {
169
- var tagName = RegExp.$1;
170
- var attrName = RegExp.$2;
171
- var attrOperator = RegExp.$3;
172
- var attrValue = RegExp.$4;
173
- if (!tagName) {
174
- tagName = '*';
175
- }
176
- // Grab all of the tagName elements within current context
177
- var found = new Array;
178
- var foundCount = 0;
179
- for (var h = 0; h < currentContext.length; h++) {
180
- var elements;
181
- if (tagName == '*') {
182
- elements = getAllChildren(currentContext[h]);
183
- } else {
184
- elements = currentContext[h].getElementsByTagName(tagName);
185
- }
186
- for (var j = 0; j < elements.length; j++) {
187
- found[foundCount++] = elements[j];
188
- }
189
- }
190
- currentContext = new Array;
191
- var currentContextIndex = 0;
192
- var checkFunction; // This function will be used to filter the elements
193
- switch (attrOperator) {
194
- case '=': // Equality
195
- checkFunction = function(e) { return (e.getAttribute(attrName) == attrValue); };
196
- break;
197
- case '~': // Match one of space seperated words
198
- checkFunction = function(e) { return (e.getAttribute(attrName).match(new RegExp('\\b'+attrValue+'\\b'))); };
199
- break;
200
- case '|': // Match start with value followed by optional hyphen
201
- checkFunction = function(e) { return (e.getAttribute(attrName).match(new RegExp('^'+attrValue+'-?'))); };
202
- break;
203
- case '^': // Match starts with value
204
- checkFunction = function(e) { return (e.getAttribute(attrName).indexOf(attrValue) == 0); };
205
- break;
206
- case '$': // Match ends with value - fails with "Warning" in Opera 7
207
- checkFunction = function(e) { return (e.getAttribute(attrName).lastIndexOf(attrValue) == e.getAttribute(attrName).length - attrValue.length); };
208
- break;
209
- case '*': // Match ends with value
210
- checkFunction = function(e) { return (e.getAttribute(attrName).indexOf(attrValue) > -1); };
211
- break;
212
- default :
213
- // Just test for existence of attribute
214
- checkFunction = function(e) { return e.getAttribute(attrName); };
215
- }
216
- currentContext = new Array;
217
- var currentContextIndex = 0;
218
- for (var k = 0; k < found.length; k++) {
219
- if (checkFunction(found[k])) {
220
- currentContext[currentContextIndex++] = found[k];
221
- }
222
- }
223
- // alert('Attribute Selector: '+tagName+' '+attrName+' '+attrOperator+' '+attrValue);
224
- continue; // Skip to next token
225
- }
226
-
227
- if (!currentContext[0]){
228
- return;
229
- }
230
-
231
- // If we get here, token is JUST an element (not a class or ID selector)
232
- tagName = token;
233
- var found = new Array;
234
- var foundCount = 0;
235
- for (var h = 0; h < currentContext.length; h++) {
236
- var elements = currentContext[h].getElementsByTagName(tagName);
237
- for (var j = 0; j < elements.length; j++) {
238
- found[foundCount++] = elements[j];
239
- }
240
- }
241
- currentContext = found;
242
- }
243
- return currentContext;
244
- }
245
-
246
- /* That revolting regular expression explained
247
- /^(\w+)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/
248
- \---/ \---/\-------------/ \-------/
249
- | | | |
250
- | | | The value
251
- | | ~,|,^,$,* or =
252
- | Attribute
253
- Tag
254
- */
1
+ /*
2
+ Behaviour v1.0 by Ben Nolan, June 2005. Based largely on the work
3
+ of Simon Willison (see comments by Simon below).
4
+
5
+ Description:
6
+
7
+ Uses css selectors to apply javascript behaviours to enable
8
+ unobtrusive javascript in html documents.
9
+
10
+ Usage:
11
+
12
+ var myrules = {
13
+ 'b.someclass' : function(element){
14
+ element.onclick = function(){
15
+ alert(this.innerHTML);
16
+ }
17
+ },
18
+ '#someid u' : function(element){
19
+ element.onmouseover = function(){
20
+ this.innerHTML = "BLAH!";
21
+ }
22
+ }
23
+ );
24
+
25
+ Behaviour.register(myrules);
26
+
27
+ // Call Behaviour.apply() to re-apply the rules (if you
28
+ // update the dom, etc).
29
+
30
+ License:
31
+
32
+ My stuff is BSD licensed. Not sure about Simon's.
33
+
34
+ More information:
35
+
36
+ http://ripcord.co.nz/behaviour/
37
+
38
+ */
39
+
40
+ var Behaviour = {
41
+ list : new Array,
42
+
43
+ register : function(sheet){
44
+ Behaviour.list.push(sheet);
45
+ },
46
+
47
+ start : function(){
48
+ Behaviour.addLoadEvent(function(){
49
+ Behaviour.apply();
50
+ });
51
+ },
52
+
53
+ apply : function(){
54
+ for (h=0;sheet=Behaviour.list[h];h++){
55
+ for (selector in sheet){
56
+ list = document.getElementsBySelector(selector);
57
+
58
+ if (!list){
59
+ continue;
60
+ }
61
+
62
+ for (i=0;element=list[i];i++){
63
+ sheet[selector](element);
64
+ }
65
+ }
66
+ }
67
+ },
68
+
69
+ addLoadEvent : function(func){
70
+ var oldonload = window.onload;
71
+
72
+ if (typeof window.onload != 'function') {
73
+ window.onload = func;
74
+ } else {
75
+ window.onload = function() {
76
+ oldonload();
77
+ func();
78
+ }
79
+ }
80
+ }
81
+ }
82
+
83
+ Behaviour.start();
84
+
85
+ /*
86
+ The following code is Copyright (C) Simon Willison 2004.
87
+
88
+ document.getElementsBySelector(selector)
89
+ - returns an array of element objects from the current document
90
+ matching the CSS selector. Selectors can contain element names,
91
+ class names and ids and can be nested. For example:
92
+
93
+ elements = document.getElementsBySelect('div#main p a.external')
94
+
95
+ Will return an array of all 'a' elements with 'external' in their
96
+ class attribute that are contained inside 'p' elements that are
97
+ contained inside the 'div' element which has id="main"
98
+
99
+ New in version 0.4: Support for CSS2 and CSS3 attribute selectors:
100
+ See http://www.w3.org/TR/css3-selectors/#attribute-selectors
101
+
102
+ Version 0.4 - Simon Willison, March 25th 2003
103
+ -- Works in Phoenix 0.5, Mozilla 1.3, Opera 7, Internet Explorer 6, Internet Explorer 5 on Windows
104
+ -- Opera 7 fails
105
+ */
106
+
107
+ function getAllChildren(e) {
108
+ // Returns all children of element. Workaround required for IE5/Windows. Ugh.
109
+ return e.all ? e.all : e.getElementsByTagName('*');
110
+ }
111
+
112
+ document.getElementsBySelector = function(selector) {
113
+ // Attempt to fail gracefully in lesser browsers
114
+ if (!document.getElementsByTagName) {
115
+ return new Array();
116
+ }
117
+ // Split selector in to tokens
118
+ var tokens = selector.split(' ');
119
+ var currentContext = new Array(document);
120
+ for (var i = 0; i < tokens.length; i++) {
121
+ token = tokens[i].replace(/^\s+/,'').replace(/\s+$/,'');;
122
+ if (token.indexOf('#') > -1) {
123
+ // Token is an ID selector
124
+ var bits = token.split('#');
125
+ var tagName = bits[0];
126
+ var id = bits[1];
127
+ var element = document.getElementById(id);
128
+ if (tagName && element.nodeName.toLowerCase() != tagName) {
129
+ // tag with that ID not found, return false
130
+ return new Array();
131
+ }
132
+ // Set currentContext to contain just this element
133
+ currentContext = new Array(element);
134
+ continue; // Skip to next token
135
+ }
136
+ if (token.indexOf('.') > -1) {
137
+ // Token contains a class selector
138
+ var bits = token.split('.');
139
+ var tagName = bits[0];
140
+ var className = bits[1];
141
+ if (!tagName) {
142
+ tagName = '*';
143
+ }
144
+ // Get elements matching tag, filter them for class selector
145
+ var found = new Array;
146
+ var foundCount = 0;
147
+ for (var h = 0; h < currentContext.length; h++) {
148
+ var elements;
149
+ if (tagName == '*') {
150
+ elements = getAllChildren(currentContext[h]);
151
+ } else {
152
+ elements = currentContext[h].getElementsByTagName(tagName);
153
+ }
154
+ for (var j = 0; j < elements.length; j++) {
155
+ found[foundCount++] = elements[j];
156
+ }
157
+ }
158
+ currentContext = new Array;
159
+ var currentContextIndex = 0;
160
+ for (var k = 0; k < found.length; k++) {
161
+ if (found[k].className && found[k].className.match(new RegExp('\\b'+className+'\\b'))) {
162
+ currentContext[currentContextIndex++] = found[k];
163
+ }
164
+ }
165
+ continue; // Skip to next token
166
+ }
167
+ // Code to deal with attribute selectors
168
+ if (token.match(/^(\w*)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/)) {
169
+ var tagName = RegExp.$1;
170
+ var attrName = RegExp.$2;
171
+ var attrOperator = RegExp.$3;
172
+ var attrValue = RegExp.$4;
173
+ if (!tagName) {
174
+ tagName = '*';
175
+ }
176
+ // Grab all of the tagName elements within current context
177
+ var found = new Array;
178
+ var foundCount = 0;
179
+ for (var h = 0; h < currentContext.length; h++) {
180
+ var elements;
181
+ if (tagName == '*') {
182
+ elements = getAllChildren(currentContext[h]);
183
+ } else {
184
+ elements = currentContext[h].getElementsByTagName(tagName);
185
+ }
186
+ for (var j = 0; j < elements.length; j++) {
187
+ found[foundCount++] = elements[j];
188
+ }
189
+ }
190
+ currentContext = new Array;
191
+ var currentContextIndex = 0;
192
+ var checkFunction; // This function will be used to filter the elements
193
+ switch (attrOperator) {
194
+ case '=': // Equality
195
+ checkFunction = function(e) { return (e.getAttribute(attrName) == attrValue); };
196
+ break;
197
+ case '~': // Match one of space seperated words
198
+ checkFunction = function(e) { return (e.getAttribute(attrName).match(new RegExp('\\b'+attrValue+'\\b'))); };
199
+ break;
200
+ case '|': // Match start with value followed by optional hyphen
201
+ checkFunction = function(e) { return (e.getAttribute(attrName).match(new RegExp('^'+attrValue+'-?'))); };
202
+ break;
203
+ case '^': // Match starts with value
204
+ checkFunction = function(e) { return (e.getAttribute(attrName).indexOf(attrValue) == 0); };
205
+ break;
206
+ case '$': // Match ends with value - fails with "Warning" in Opera 7
207
+ checkFunction = function(e) { return (e.getAttribute(attrName).lastIndexOf(attrValue) == e.getAttribute(attrName).length - attrValue.length); };
208
+ break;
209
+ case '*': // Match ends with value
210
+ checkFunction = function(e) { return (e.getAttribute(attrName).indexOf(attrValue) > -1); };
211
+ break;
212
+ default :
213
+ // Just test for existence of attribute
214
+ checkFunction = function(e) { return e.getAttribute(attrName); };
215
+ }
216
+ currentContext = new Array;
217
+ var currentContextIndex = 0;
218
+ for (var k = 0; k < found.length; k++) {
219
+ if (checkFunction(found[k])) {
220
+ currentContext[currentContextIndex++] = found[k];
221
+ }
222
+ }
223
+ // alert('Attribute Selector: '+tagName+' '+attrName+' '+attrOperator+' '+attrValue);
224
+ continue; // Skip to next token
225
+ }
226
+
227
+ if (!currentContext[0]){
228
+ return;
229
+ }
230
+
231
+ // If we get here, token is JUST an element (not a class or ID selector)
232
+ tagName = token;
233
+ var found = new Array;
234
+ var foundCount = 0;
235
+ for (var h = 0; h < currentContext.length; h++) {
236
+ var elements = currentContext[h].getElementsByTagName(tagName);
237
+ for (var j = 0; j < elements.length; j++) {
238
+ found[foundCount++] = elements[j];
239
+ }
240
+ }
241
+ currentContext = found;
242
+ }
243
+ return currentContext;
244
+ }
245
+
246
+ /* That revolting regular expression explained
247
+ /^(\w+)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/
248
+ \---/ \---/\-------------/ \-------/
249
+ | | | |
250
+ | | | The value
251
+ | | ~,|,^,$,* or =
252
+ | Attribute
253
+ Tag
254
+ */