mack-javascript 0.8.2 → 0.8.3

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.
@@ -1,50 +1,16 @@
1
+ require File.join_from_here('script_generator')
2
+
1
3
  module Mack
2
4
  module JavaScript
3
5
  module Framework
4
- class Prototype
6
+ class PrototypeAjax
5
7
  @@callbacks = [:uninitialized, :loading, :loaded, :interactive, :complete, :failure, :success ] +
6
8
  [100,101] + (200..206).to_a + (300..307).to_a + (400..417).to_a + (500..505).to_a
7
9
  class << self
8
- def insert_html(position, id, html)
9
- insertion = position.to_s.camelcase
10
- "new Insertion.#{insertion}('#{id}', '#{html}')"
11
- end
12
-
13
- def replace_html(id, html)
14
- "Element.update('#{id}', '#{html}')"
15
- end
16
-
17
- def replace(id, html)
18
- "Element.replace('#{id}', '#{html}')"
19
- end
20
10
 
21
- def remove(*ids)
22
- "#{ids.to_json}.each(Element.remove)"
23
- end
24
-
25
- def show(*ids)
26
- "#{ids.to_json}.each(Element.show)"
27
- end
28
-
29
- def toggle(*ids)
30
- "#{ids.to_json}.each(Element.toggle)"
31
- end
32
11
 
33
- # def draggable(id, options = {})
34
- # record @context.send(:draggable_element_js, id, options)
35
- # end
36
- #
37
- # def visual_effect(name, id = nil, options = {})
38
- # record @context.send(:visual_effect, name, id, options)
39
- # end
40
- #
41
- # def drop_receiving(id, options = {})
42
- # record @context.send(:drop_receiving_element_js, id, options)
43
- # end
44
- #
45
12
  def remote_function(options)
46
13
  javascript_options = options_for_ajax(options)
47
- "new Ajax.Request('#{options[:url]}', #{javascript_options.to_json})"
48
14
  update = ''
49
15
  if options[:update] && options[:update].is_a?(Hash)
50
16
  update = []
@@ -82,13 +48,36 @@ module Mack
82
48
 
83
49
  if options[:form]
84
50
  js_options['parameters'] = 'Form.serialize(this)'
85
- elsif options[:submit]
86
- js_options['parameters'] = "Form.serialize('#{options[:submit]}')"
87
51
  elsif options[:with]
88
52
  js_options['parameters'] = options[:with]
89
53
  end
54
+
55
+ if options[:method].nil? || options[:method].to_sym != :get
56
+ js_options['method'] = "'post'"
57
+ else
58
+ js_options['method'] = "'get'"
59
+ end
60
+
61
+ if options[:method] && options[:method].to_sym == :put || options[:method] == :delete
62
+ js_options['parameters'] = append_ajax_data(js_options['parameters'], "_method=#{options[:method]}")
63
+ end
64
+
65
+ if js_options['method'] == "'post'" && options[:authenticity_token]
66
+ js_options['parameters'] = append_ajax_data(js_options['parameters'], "__authenticity_token=#{options.delete(:authenticity_token)}")
67
+ end
68
+
69
+
90
70
  options_for_javascript(js_options.reject {|key, value| value.nil?})
91
71
  end
72
+
73
+ def append_ajax_data(data, new_data)
74
+ if data
75
+ data << " + '&"
76
+ else
77
+ data = "'"
78
+ end
79
+ data << "#{new_data}'"
80
+ end
92
81
 
93
82
  def options_for_javascript(options)
94
83
  '{' + options.map {|k, v| "#{k}:#{v}"}.sort.join(', ') + '}'
@@ -107,6 +96,385 @@ module Mack
107
96
 
108
97
  end
109
98
  end
99
+
100
+ class PrototypeSelector < Mack::JavaScript::Selector
101
+
102
+ def select
103
+ "$$(#{@selector})"
104
+ end
105
+
106
+ def invoke(arr, options = {})
107
+ arr.collect! do |arg|
108
+ if !arg.is_a?(String) || arg =~ /^function/ || arg =~ /^null$/ || arg =~ /^\{.*\}$/
109
+ arg
110
+ else
111
+ "'#{arg}'"
112
+ end
113
+ end
114
+ function = "invoke(#{arr.compact.join(',')})"
115
+ function << '.flatten().uniq()' if options.delete(:flatten_uniq)
116
+ add function, options
117
+ end
118
+
119
+ def each(func, options = {})
120
+ add "each(function(elem){#{func}})", options
121
+ end
122
+
123
+
124
+ #--- Tree Walking ---#
125
+
126
+ # Will give you the immediate children underneath the selected elements
127
+ #
128
+ #
129
+ # Example:
130
+ # say you have the following html:
131
+ #
132
+ # <div class='rakim'>
133
+ # <ul>
134
+ # ...
135
+ # </ul>
136
+ # </div>
137
+ #
138
+ # <div class='rakim'>
139
+ # <p>Eric B</p>
140
+ # <div id='technique'>
141
+ # ...
142
+ # </div>
143
+ # </div>
144
+ #
145
+ # page.select('.rakim').children would give you a collection consisting of
146
+ # the ul element, the p element, and the div with id 'technique'
147
+ def children
148
+ invoke ["childElements"], :flatten_uniq => true
149
+ end
150
+
151
+ # returns a collection of the immediate parent of each selected element
152
+ def parent
153
+ invoke ["up"], :flatten_uniq => true
154
+ end
155
+
156
+ # returns a collection of every parent up the chain to the root of the document
157
+ # for each selected element.
158
+ def ancestors
159
+ invoke ["ancestors"], :flatten_uniq => true
160
+ end
161
+
162
+ # gets all siblings for each element selected
163
+ def siblings(selector = nil)
164
+ invoke ["siblings", selector], :flatten_uniq => true
165
+ end
166
+
167
+ # gets the next immediate sibling for each element selected
168
+ # Takes an optional selector as an argument
169
+ def next(selector = nil)
170
+ invoke ["next", selector], :flatten_uniq => true
171
+ end
172
+
173
+ # gets the previous immediate sibling for each element selected
174
+ # Takes an optional selector as an argument
175
+ def previous(selector = nil)
176
+ invoke ["prev", selector], :flatten_uniq => true
177
+ end
178
+
179
+ # gets every next sibling for each element selected
180
+ def all_next
181
+ invoke ["nextSiblings"], :flatten_uniq => true
182
+ end
183
+
184
+ # gets every previous sibling for each element selected
185
+ def all_previous
186
+ invoke ["previousSiblings"], :flatten_uniq => true
187
+ end
188
+
189
+
190
+ #-- Attributes --#
191
+
192
+ def add_class(klass)
193
+ invoke ["addClassName", klass]
194
+ end
195
+
196
+ def remove_class(klass = '')
197
+ invoke ["removeClassName", klass]
198
+ end
199
+
200
+ def set_attribute(name, value)
201
+ value = "null" if value.nil?
202
+ invoke ["writeAttribute", name, value]
203
+ end
204
+
205
+ def remove_attribute(name)
206
+ set_attribute(name, nil)
207
+ end
208
+
209
+
210
+
211
+ #-- DOM Manipulation --#
212
+
213
+ # inserts html into the selected place for the specfied elemets
214
+ #
215
+ # +position+ may be one of:
216
+ #
217
+ # <tt>:top</tt>:: HTML is inserted inside the element, before the
218
+ # element's existing content.
219
+ # <tt>:bottom</tt>:: HTML is inserted inside the element, after the
220
+ # element's existing content.
221
+ # <tt>:before</tt>:: HTML is inserted immediately preceding the element.
222
+ # <tt>:after</tt>:: HTML is inserted immediately following the element.
223
+ #
224
+ #
225
+ # Example
226
+ #
227
+ # <div class='rakim'>
228
+ # <ul>
229
+ # ...
230
+ # </ul>
231
+ # </div>
232
+ #
233
+ # <div class='rakim'>
234
+ # <p>Eric B</p>
235
+ # <div id='technique'>
236
+ # ...
237
+ # </div>
238
+ # </div>
239
+ #
240
+ # page.select('.rakim').insert(:before, "<h1> The R </h1>") would result in:
241
+ #
242
+ # <h1> The R </h2>
243
+ # <div class='rakim'>
244
+ # <ul>
245
+ # ...
246
+ # </ul>
247
+ # </div>
248
+ #
249
+ # <h1> The R </h2>
250
+ # <div class='rakim'>
251
+ # <p>Eric B</p>
252
+ # <div id='technique'>
253
+ # ...
254
+ # </div>
255
+ # </div>
256
+ #
257
+ #
258
+ # Tip: use this with a partial containing your html:
259
+ # page.select('.rakim').insert(:before, render(:partial, 'the_r', :format => :html))
260
+ def insert(position, html)
261
+ invoke ["insert", "{#{position.to_s}: '#{escape_javascript(html)}'}"]
262
+ end
263
+
264
+
265
+ # replaces the selected html.
266
+ #
267
+ # +repace+ may be:
268
+ #
269
+ # <tt>:inner</tt>:: The inner html of the selected elements
270
+ # are replaced
271
+ # <tt>:outer</tt>:: the selected elements themselves are replaced
272
+ #
273
+ # Example
274
+ #
275
+ # <div class='rakim'>
276
+ # <p>Dont Sweat the Techinique</p>
277
+ # </div>
278
+ # <div class='rakim'>
279
+ # <p>Follow the Leader</p>
280
+ # </div>
281
+ #
282
+ # page.select('.rakim').replace(:inner, "<p>Paid in Full</p>") would result in
283
+ #
284
+ # <div class='rakim'>
285
+ # <p>Paid in Full</p>
286
+ # </div>
287
+ # <div class='rakim'>
288
+ # <p>Paid in Full</p>
289
+ # </div>
290
+ #
291
+ # if we then did:
292
+ # page.select('.rakim').replace(:outer, "<div class='schoolyD'><p>SaturdayNight</p></div>")
293
+ # the result would be
294
+ #
295
+ # <div class='schoolyD'>
296
+ # <p>SaturdayNight</p>
297
+ # </div>
298
+ # <div class='schoolyD'>
299
+ # <p>SaturdayNight</p>
300
+ # </div>
301
+ def replace(replace, html)
302
+ function = {:inner =>"update",:outer => 'replace'}[replace.to_sym]
303
+ invoke [function, escape_javascript(html)]
304
+ end
305
+
306
+ #removes the selected elements from the DOM
307
+ def remove
308
+ invoke ['remove']
309
+ end
310
+
311
+
312
+ #-- Effects --#
313
+ #
314
+ # The methods morph and effect take the same options hash. This can consist of:
315
+ #
316
+ # <tt>:duration</tt>:: The duration of the effect in ms.
317
+ # <tt>:easing</tt>:: see below
318
+ # <tt>:fps</tt>:: Specifies the frames-per-second value. The default is 25.
319
+ # <tt>:sync</tt>:: Synchronizes effects when applied in parallel.
320
+ # <tt>:queue</tt>:: Sets the queuing position for effect queues.
321
+ #
322
+ #
323
+ # --Easing--
324
+ # This determines the mathematical function your effect will use while transitioning.
325
+ # For instance, if you do page.select(.rakim).effect(:slideUp, :easing => 'spring'),
326
+ # every element with class 'rakim' will slide up and when it reaches the top, they will
327
+ # bounce back down a little then go back up.
328
+ #
329
+ # The full list of prototype easing options:
330
+ # linear, sinoidal, reverse, flicker, wobble, pulse, spring, none, full
331
+
332
+ def morph(hsh, options = nil)
333
+ #hsh is an object of style values
334
+ invoke ['morph', options_for_javascript(hsh), options_for_effects(options)]
335
+ end
336
+
337
+ # Custom visual effects. Supports the following effect names:
338
+ # highlight, scale, opacity, move, fade, appear, blindUp, blindDown, slideUp, slideDown,
339
+ # dropOut, grow, shrink, puff, switchOff, squish, fold, pulsate, shake, scrollTo
340
+ def effect(name, options = nil)
341
+ invoke ['visualEffect', name.to_s, options_for_effects(options)]
342
+ end
343
+
344
+ # show() shows an element, hide() hides it, and toggle() shows a hidden and hides a shown.
345
+ def show()
346
+ invoke ['show']
347
+ end
348
+
349
+ def hide()
350
+ invoke ['hide']
351
+ end
352
+
353
+ def toggle()
354
+ invoke ['toggle']
355
+ end
356
+
357
+
358
+
359
+ #-- Events --#
360
+
361
+ # adds an event listener to the selected elements. If options[:prevent_default] == true
362
+ # the default behavior of the event isn't taken
363
+ #
364
+ # Example
365
+ #
366
+ # page.select('.rakim a').peep 'click', :prevent_default => true do |p|
367
+ # p.select('#sucka_mcs').effect(:fade)
368
+ # end
369
+ #
370
+ # After running this code, if you click any a tag under any element with the
371
+ # class 'rakim', the element with id "sucka_mcs" will fade. Because of the option
372
+ # :prevent_default => true, the default action when clicking the a tag (the browser
373
+ # goes to its href url) isn't done.
374
+ # This can also be used in conjunction with trigger to make and call custom events.
375
+ def peep(event_name, options = {}, &block)
376
+ invoke ["observe", event_name, event_function(options[:prevent_default], &block)]
377
+ end
378
+
379
+ #takes away any event listeners on the 'event_name' event fot the selected elements
380
+ def stop_peeping(event_name)
381
+ invoke ["stopObserving", event_name]
382
+ end
383
+
384
+ # triggers the 'event_name' event on the selected elements.
385
+ def trigger(event_name)
386
+ invoke ["fire", event_name]
387
+ end
388
+
389
+
390
+
391
+
392
+ #-- Drag and Drop--#
393
+
394
+ # Makes the selected elements draggable.
395
+ #
396
+ # options
397
+ # http://github.com/madrobby/scriptaculous/wikis/draggable
398
+ # <tt>:revert</tt>:: if true, will revert after being dropped. ‘failure’
399
+ # will instruct the draggable not to revert if
400
+ # successfully dropped in a droppable.
401
+ # <tt>:ghosting</tt>:: if true, will clone the element and drag the clone
402
+ # <tt>:zindex</tt>:: The css z-index of the draggable item.
403
+ def draggable(options = nil)
404
+ each "new Draggable(elem, #{options_for_effects(options)})"
405
+ end
406
+
407
+ # Makes the selected elements droppable.
408
+ #
409
+ # options are
410
+ # http://github.com/madrobby/scriptaculous/wikis/droppables
411
+
412
+ # <tt>:accept</tt>:: if set to a css class, the selected elements will only
413
+ # accept elements dragged to it with that class.
414
+ # <tt>:hoverclass</tt>:: a droppable element will have this class added to it
415
+ # when an element is dragged over it.
416
+ # <tt>:remote</tt>:: takes a hash of ajax options, the same as given to page.ajax
417
+ # if this options is, an ajax call is made using the specified
418
+ # options along. Added to the url is an id parameter which
419
+ # has the id of the element that was dropped
420
+ #
421
+ # if a block is given, the blocks code will be executed when a succesful drop is done.
422
+ #
423
+ # Example
424
+ #
425
+ # options = {:remote => {:url => '/stuff'}}
426
+ # page.select('#bucket').droppable options do |p|
427
+ # p.alert('you dropped it!')
428
+ # end
429
+ #
430
+ # This code will make the element with id 'bucket' droppable. If an element is
431
+ # dropped onto it, an ajax call to the url '/stuff' will be sent with an id
432
+ # parameter of the id of the dropped element. Then an js alert
433
+ # box with the message 'you dropped it' will appear.
434
+ def droppable(options = nil, &block)
435
+ remote_options = options.delete(:remote)
436
+ if remote_options || block_given?
437
+ func = Mack::JavaScript::Function.new(session_id, 'elem')
438
+ if remote_options
439
+ remote_options[:with] ||= "'id=' + elem.id"
440
+ func << Mack::JavaScript::ScriptGenerator.new(session_id).ajax(remote_options)
441
+ end
442
+ func.body(&block) if block_given?
443
+ options.merge!(:onDrop => func)
444
+ end
445
+ each "Droppables.add(elem, #{options_for_effects(options)})"
446
+ end
447
+
448
+
449
+ private
450
+
451
+ def build_multiple_selector_string(selector)
452
+ selector.collect{|s| "'#{s}'"}.join(',')
453
+ end
454
+
455
+ def options_for_effects(options)
456
+ return nil unless options
457
+ options[:duration] = (options[:duration]/1000.0) if options[:duration]
458
+ easing = options.delete(:easing)
459
+ options[:transition] = "function(){return Effect.Transitions.#{easing}}()" if easing
460
+ options_for_javascript(options)
461
+ end
462
+
463
+ def options_for_javascript(options)
464
+ options = options.collect do |key, value|
465
+ value = "'#{value}'" unless !value.is_a?(String) || value =~ /^function/
466
+ "#{key}: #{value}"
467
+ end
468
+ "{#{options.join(',')}}"
469
+ end
470
+
471
+ def event_function(prevent_default = false, &block)
472
+ func = Mack::JavaScript::Function.new(session_id, 'event')
473
+ func << "event.stop()" if prevent_default
474
+ func.body(&block)
475
+ end
476
+
477
+ end
110
478
  end
111
479
  end
112
480
  end
@@ -1,20 +1,52 @@
1
+ require File.join_from_here('..', 'view_helpers', 'string_helpers')
2
+
1
3
  module Mack
2
4
  module JavaScript
3
- class ScriptGenerator
4
- def initialize
5
- @lines = ''
5
+ class ScriptGenerator
6
+
7
+ attr_reader :session_id
8
+
9
+ def initialize(session_id = nil)
10
+ @lines = []
11
+ @session_id = session_id
12
+ end
13
+
14
+ # selects elements on the page using css3 selectors
15
+ # For more info: http://www.w3.org/TR/css3-selectors/
16
+ # A few useful examples: 'div' would select all divs. '.full' would
17
+ # select all elements with class 'full'. 'div.blah' would select
18
+ # all divs with class 'blah'. '#foo' would select the element
19
+ # with id 'foo'
20
+ #
21
+ # select can take multiple selector strings. For instance
22
+ # page.select('ul.blah', '#foo', '.full') would give you access to
23
+ # a collection of elements containing all uls with class 'blah', the
24
+ # element with id 'foo' and every element with class 'full'. See
25
+ # JquerySelector or PrototypeSelector for available methods on
26
+ # the returned collection.
27
+ def select(*selector)
28
+ self.class.selector_framework.new(self, *selector)
6
29
  end
7
30
 
8
- def method_missing(sym, *args)
9
- self << self.class.framework.send(sym, *args)
31
+ def ajax(options)
32
+ unless configatron.mack.disable_forgery_detector || !session_id
33
+ options.merge!(:authenticity_token => Mack::Utils::AuthenticityTokenDispenser.instance.dispense_token(session_id))
34
+ end
35
+ self << self.class.ajax_framework.remote_function(options)
10
36
  end
11
37
 
12
- def <<(s)
13
- @lines << s + ";"
38
+ def <<(s, options = {})
39
+ if options[:add_to_last]
40
+ @lines.last << s
41
+ else
42
+ @lines << s
43
+ end
14
44
  end
15
45
 
16
46
  def to_s
17
- @lines
47
+ string = @lines.join(';')
48
+ string << ';' unless string =~ /;$/
49
+ string
18
50
  end
19
51
 
20
52
  def alert(message)
@@ -27,25 +59,82 @@ module Mack
27
59
  args.each {|arg| a << arg.to_json}
28
60
  self << s + a.join(',') + ')'
29
61
  end
62
+
63
+ def function(*args)
64
+ Mack::JavaScript::Function.new(session_id, *args)
65
+ end
30
66
 
31
67
  def assign(variable, value)
32
68
  self << "#{variable} = #{value.to_json}"
33
69
  end
34
70
 
35
71
  def delay(seconds = 1, &block)
36
- self << "setTimeout(function() {\n\n" + yield(Mack::JavaScript::ScriptGenerator.new) + "}, #{(seconds * 1000).to_i})"
72
+ self << "setTimeout(#{function.body(&block)}, #{(seconds * 1000).to_i})"
73
+ end
74
+
75
+
76
+ #-- Deprecated Methods --#
77
+
78
+ def insert_html(position, id, html)
79
+ deprecate_method(:insert_html, 'page.select("#id").insert(position, html)', '0.8.3')
80
+ self.select("##{id}").insert(position, html)
81
+ end
82
+
83
+ def replace_html(id, html)
84
+ deprecate_method(:replace_html, 'page.select("#id").replace(:inner, html)', '0.8.3')
85
+ self.select("##{id}").replace(:inner, html).to_s
86
+ end
87
+
88
+ def replace(id, html)
89
+ deprecate_method(:replace, 'page.select("#id").replace(:outer, html)', '0.8.3')
90
+ self.select("##{id}").replace(:outer, html).to_s
91
+ end
92
+
93
+ def remove(*ids)
94
+ deprecate_method(:remove, 'page.select("#id1", "#id2").remove', '0.8.3')
95
+ ids = [ids] if ids.is_a? String
96
+ ids.collect! {|id| "##{id}"}
97
+ self.select(*ids).remove
98
+ end
99
+
100
+ def show(*ids)
101
+ deprecate_method(:show, 'page.select("#id1", "#id2").show', '0.8.3')
102
+ ids = [ids] if ids.is_a? String
103
+ ids.collect! {|id| "##{id}"}
104
+ self.select(*ids).show
105
+ end
106
+
107
+ def hide(*ids)
108
+ deprecate_method(:hide, 'page.select("#id1", "#id2").hide', '0.8.3')
109
+ ids = [ids] if ids.is_a? String
110
+ ids.collect! {|id| "##{id}"}
111
+ self.select(*ids).hide
112
+ end
113
+
114
+ def toggle(*ids)
115
+ deprecate_method(:toggle, 'page.select("#id1", "#id2").toggle', '0.8.3')
116
+ ids = [ids] if ids.is_a? String
117
+ ids.collect! {|id| "##{id}"}
118
+ self.select(*ids).toggle
37
119
  end
38
120
 
39
121
  class << self
40
122
 
41
- def framework
42
- ivar_cache('framework_constant') do
43
- "Mack::JavaScript::Framework::#{framework_name}".constantize
123
+ def ajax_framework
124
+ ivar_cache('ajax_framework') do
125
+ "Mack::JavaScript::Framework::#{framework_name}Ajax".constantize
126
+ end
127
+ end
128
+
129
+ def selector_framework
130
+ ivar_cache('selector_framework') do
131
+ "Mack::JavaScript::Framework::#{framework_name}Selector".constantize
44
132
  end
45
133
  end
46
134
 
47
135
  def framework=(args)
48
- @framework_constant = nil
136
+ @ajax_framework = nil
137
+ @selector_framework = nil
49
138
  @@framework_name = args.camelcase
50
139
  end
51
140
 
@@ -54,7 +143,55 @@ module Mack
54
143
  @@framework_name ||= configatron.mack.js_framework.camelcase
55
144
  end
56
145
  end
146
+ end
147
+
148
+ class Selector
149
+ include Mack::ViewHelpers::StringHelpers
150
+
151
+ attr_reader :session_id
152
+
153
+ def initialize(generator, *sel)
154
+ @generator = generator
155
+ @session_id = generator.session_id
156
+ @selector = sel.first == 'this' ? 'this' : build_multiple_selector_string(sel)
157
+ @generator << select
158
+ end
159
+
160
+ def add(statement, options = {})
161
+ @generator.<<(".#{statement}", :add_to_last => true)
162
+ self
163
+ end
57
164
 
58
165
  end
166
+
167
+
168
+ class Function
169
+
170
+ def initialize(session_id = nil, *args)
171
+ if args.first.is_a? Fixnum
172
+ args = Array.new(args.first){|i| i + 1}.collect{|x| "obj#{x}"}
173
+ end
174
+ @session_id = session_id
175
+ @arguments = args
176
+ @generator = Mack::JavaScript::ScriptGenerator.new(session_id)
177
+ end
178
+
179
+ def <<(*args)
180
+ @generator << args
181
+ to_s
182
+ end
183
+
184
+ def body(&block)
185
+ yield @generator
186
+ to_s
187
+ end
188
+
189
+
190
+ def to_s
191
+ "function(#{@arguments.join(', ')}){#{@generator.to_s}}"
192
+ end
193
+
194
+
195
+ end
59
196
  end
60
- end
197
+ end
@@ -8,10 +8,14 @@ module Mack
8
8
  if io.is_a?(File)
9
9
  io = io.read
10
10
  end
11
- @_jsp_page = Mack::JavaScript::ScriptGenerator.new
11
+ @_jsp_page = Mack::JavaScript::ScriptGenerator.new(view_template.controller.session.id)
12
12
  view_template.instance_variable_set("@_jsp_page", @_jsp_page)
13
13
  eval(io, binding)
14
- @_jsp_page.to_s
14
+ resp = @_jsp_page.to_s
15
+ if Mack.env == 'development'
16
+ resp = "try {#{resp}}catch(e){alert('RJS error:\\n\\n' + e.toString());throw e};"
17
+ end
18
+ resp
15
19
  end
16
20
 
17
21
  def extension