mjs 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README +89 -0
- data/Rakefile +55 -0
- data/TODO +15 -0
- data/app/controllers/application.rb +5 -0
- data/app/controllers/main.rb +7 -0
- data/app/helpers/application_helper.rb +63 -0
- data/app/views/layout/mjs.html.erb +16 -0
- data/app/views/main/index.html.erb +1 -0
- data/lib/mjs/helper.rb +165 -0
- data/lib/mjs/java_script_context.rb +486 -0
- data/lib/mjs/merbtasks.rb +103 -0
- data/lib/mjs/page_object.rb +16 -0
- data/lib/mjs/slicetasks.rb +20 -0
- data/lib/mjs/spectasks.rb +53 -0
- data/lib/mjs/utils.rb +13 -0
- data/lib/mjs.rb +86 -0
- data/public/javascripts/master.js +0 -0
- data/public/stylesheets/master.css +2 -0
- data/spec/mjs_spec.rb +20 -0
- data/spec/requests/main_spec.rb +30 -0
- data/spec/spec_helper.rb +58 -0
- data/stubs/app/controllers/application.rb +2 -0
- data/stubs/app/controllers/main.rb +2 -0
- metadata +89 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Your Name
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
Mjs
|
2
|
+
===
|
3
|
+
|
4
|
+
A slice for the Merb framework that offers Ajax actions like RJS with jQuery
|
5
|
+
This privides new 'link_to' method for clients and 'page' object for servers.
|
6
|
+
|
7
|
+
|
8
|
+
Methods
|
9
|
+
=======
|
10
|
+
|
11
|
+
* Merb::Controller#link_to(name, url='', opts={})
|
12
|
+
link_to now recognize :remote, :submit option
|
13
|
+
|
14
|
+
* Merb::Controller#page
|
15
|
+
page method is reserved for RJS object.
|
16
|
+
You can access dom elements by it as many times and in anywhere.
|
17
|
+
No more ugly render(:update) block, just type 'page' as you want!
|
18
|
+
|
19
|
+
|
20
|
+
Setup
|
21
|
+
=====
|
22
|
+
|
23
|
+
Include jQuery as usual
|
24
|
+
|
25
|
+
app/views/layouts/application.html.erb:
|
26
|
+
<%= js_include_tag "jquery.js" -%>
|
27
|
+
|
28
|
+
|
29
|
+
Example1
|
30
|
+
========
|
31
|
+
[ajax get request]
|
32
|
+
|
33
|
+
1. Add :remote option to create ajax link
|
34
|
+
|
35
|
+
app/views/top/index.html.erb:
|
36
|
+
<div id="message"></div>
|
37
|
+
<%= link_to "hello", url(:action=>"hello"), :remote=>true %>
|
38
|
+
^^^^^^^^^^^^^
|
39
|
+
generates:
|
40
|
+
<a onclick="; $.getScript('/top/hello'); return false;" href="#">hello</a>
|
41
|
+
|
42
|
+
2. Use 'page' object to respond as RJS.
|
43
|
+
|
44
|
+
app/controllers/top.rb:
|
45
|
+
def hello
|
46
|
+
page[:message].text "Good morning!"
|
47
|
+
return page
|
48
|
+
end
|
49
|
+
|
50
|
+
generates:
|
51
|
+
$("#message").text("Good morning!");
|
52
|
+
|
53
|
+
|
54
|
+
Example2
|
55
|
+
========
|
56
|
+
[ajax post request]
|
57
|
+
|
58
|
+
1. Add :submit option to specify a DOM element that should be serialized
|
59
|
+
|
60
|
+
app/views/top/index.html.erb:
|
61
|
+
<div id="edit">
|
62
|
+
<%= text_field ... %>
|
63
|
+
<%= text_area ... %>
|
64
|
+
<%= link_to "update", url(:action=>"update"), :submit=>:edit %>
|
65
|
+
^^^^^^^^^^^^^
|
66
|
+
generates:
|
67
|
+
<a onclick="; $.post('/top/update', $('#edit input').serialize(), null, 'script');; return false;" href="#">update</a>
|
68
|
+
|
69
|
+
2. enjoy 'page' as you like
|
70
|
+
|
71
|
+
app/controllers/top.rb:
|
72
|
+
def update(id)
|
73
|
+
@item = Item.find(id)
|
74
|
+
... # update logic is here
|
75
|
+
page[:message].text "successfully saved"
|
76
|
+
update_canvas_for(@item)
|
77
|
+
return page
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
def update_canvas_for(item)
|
82
|
+
page[:workspace].html partial("record", :item=>item)
|
83
|
+
rescue => error
|
84
|
+
page[:message].text error.to_s
|
85
|
+
page << "alert('something wrong!')"
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
Copyright (c) 2008 maiha@wota.jp, released under the MIT license
|
data/Rakefile
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake/gempackagetask'
|
3
|
+
|
4
|
+
require 'merb-core'
|
5
|
+
require 'merb-core/tasks/merb'
|
6
|
+
|
7
|
+
GEM_NAME = "mjs"
|
8
|
+
AUTHOR = "maiha"
|
9
|
+
EMAIL = "maiha@wota.jp"
|
10
|
+
HOMEPAGE = "http://github.com/maiha/mjs"
|
11
|
+
SUMMARY = "A slice for the Merb framework that offers Ajax actions like RJS with jQuery"
|
12
|
+
GEM_VERSION = "0.0.6"
|
13
|
+
|
14
|
+
spec = Gem::Specification.new do |s|
|
15
|
+
s.rubyforge_project = 'merb'
|
16
|
+
s.name = GEM_NAME
|
17
|
+
s.version = GEM_VERSION
|
18
|
+
s.platform = Gem::Platform::RUBY
|
19
|
+
s.has_rdoc = true
|
20
|
+
s.extra_rdoc_files = ["README", "LICENSE", 'TODO']
|
21
|
+
s.summary = SUMMARY
|
22
|
+
s.description = s.summary
|
23
|
+
s.author = AUTHOR
|
24
|
+
s.email = EMAIL
|
25
|
+
s.homepage = HOMEPAGE
|
26
|
+
s.add_dependency('merb-slices', '>= 1.0.7.1')
|
27
|
+
s.require_path = 'lib'
|
28
|
+
s.files = %w(LICENSE README Rakefile TODO) + Dir.glob("{lib,spec,app,public,stubs}/**/*")
|
29
|
+
end
|
30
|
+
|
31
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
32
|
+
pkg.gem_spec = spec
|
33
|
+
end
|
34
|
+
|
35
|
+
desc "Install the gem"
|
36
|
+
task :install do
|
37
|
+
Merb::RakeHelper.install(GEM_NAME, :version => GEM_VERSION)
|
38
|
+
end
|
39
|
+
|
40
|
+
desc "Uninstall the gem"
|
41
|
+
task :uninstall do
|
42
|
+
Merb::RakeHelper.uninstall(GEM_NAME, :version => GEM_VERSION)
|
43
|
+
end
|
44
|
+
|
45
|
+
desc "Create a gemspec file"
|
46
|
+
task :gemspec do
|
47
|
+
File.open("#{GEM_NAME}.gemspec", "w") do |file|
|
48
|
+
file.puts spec.to_ruby
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
require 'spec/rake/spectask'
|
53
|
+
require 'merb-core/test/tasks/spectasks'
|
54
|
+
desc 'Default: run spec examples'
|
55
|
+
task :default => 'spec'
|
data/TODO
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
TODO:
|
2
|
+
|
3
|
+
- Fix Mjs.description and Mjs.version
|
4
|
+
- Fix LICENSE with your name
|
5
|
+
- Fix Rakefile with your name and contact info
|
6
|
+
- Add your code to lib/mjs.rb
|
7
|
+
- Add your Merb rake tasks to lib/mjs/merbtasks.rb
|
8
|
+
|
9
|
+
Remove anything that you don't need:
|
10
|
+
|
11
|
+
- app/controllers/main.rb Mjs::Main controller
|
12
|
+
- app/views/layout/mjs.html.erb
|
13
|
+
- spec/controllers/main_spec.rb controller specs
|
14
|
+
- public/* any public files
|
15
|
+
- stubs/* any stub files
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Merb
|
2
|
+
module Mjs
|
3
|
+
module ApplicationHelper
|
4
|
+
# @param *segments<Array[#to_s]> Path segments to append.
|
5
|
+
#
|
6
|
+
# @return <String>
|
7
|
+
# A path relative to the public directory, with added segments.
|
8
|
+
def image_path(*segments)
|
9
|
+
public_path_for(:image, *segments)
|
10
|
+
end
|
11
|
+
|
12
|
+
# @param *segments<Array[#to_s]> Path segments to append.
|
13
|
+
#
|
14
|
+
# @return <String>
|
15
|
+
# A path relative to the public directory, with added segments.
|
16
|
+
def javascript_path(*segments)
|
17
|
+
public_path_for(:javascript, *segments)
|
18
|
+
end
|
19
|
+
|
20
|
+
# @param *segments<Array[#to_s]> Path segments to append.
|
21
|
+
#
|
22
|
+
# @return <String>
|
23
|
+
# A path relative to the public directory, with added segments.
|
24
|
+
def stylesheet_path(*segments)
|
25
|
+
public_path_for(:stylesheet, *segments)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Construct a path relative to the public directory
|
29
|
+
#
|
30
|
+
# @param <Symbol> The type of component.
|
31
|
+
# @param *segments<Array[#to_s]> Path segments to append.
|
32
|
+
#
|
33
|
+
# @return <String>
|
34
|
+
# A path relative to the public directory, with added segments.
|
35
|
+
def public_path_for(type, *segments)
|
36
|
+
::Mjs.public_path_for(type, *segments)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Construct an app-level path.
|
40
|
+
#
|
41
|
+
# @param <Symbol> The type of component.
|
42
|
+
# @param *segments<Array[#to_s]> Path segments to append.
|
43
|
+
#
|
44
|
+
# @return <String>
|
45
|
+
# A path within the host application, with added segments.
|
46
|
+
def app_path_for(type, *segments)
|
47
|
+
::Mjs.app_path_for(type, *segments)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Construct a slice-level path.
|
51
|
+
#
|
52
|
+
# @param <Symbol> The type of component.
|
53
|
+
# @param *segments<Array[#to_s]> Path segments to append.
|
54
|
+
#
|
55
|
+
# @return <String>
|
56
|
+
# A path within the slice source (Gem), with added segments.
|
57
|
+
def slice_path_for(type, *segments)
|
58
|
+
::Mjs.slice_path_for(type, *segments)
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
2
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-us" lang="en-us">
|
3
|
+
<head>
|
4
|
+
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
5
|
+
<title>Fresh Mjs Slice</title>
|
6
|
+
<link href="<%= public_path_for :stylesheet, 'master.css' %>" type="text/css" charset="utf-8" rel="stylesheet" media="all" />
|
7
|
+
<script src="<%= public_path_for :javascript, 'master.js' %>" type="text/javascript" charset="utf-8"></script>
|
8
|
+
</head>
|
9
|
+
<!-- you can override this layout at slices/mjs/app/views/layout/mjs.html.erb -->
|
10
|
+
<body class="mjs-slice">
|
11
|
+
<div id="container">
|
12
|
+
<h1>Mjs Slice</h1>
|
13
|
+
<div id="main"><%= catch_content :for_layout %></div>
|
14
|
+
</div>
|
15
|
+
</body>
|
16
|
+
</html>
|
@@ -0,0 +1 @@
|
|
1
|
+
<strong><%= slice.description %></strong> (v. <%= slice.version %>)
|
data/lib/mjs/helper.rb
ADDED
@@ -0,0 +1,165 @@
|
|
1
|
+
require 'mjs/java_script_context'
|
2
|
+
|
3
|
+
module Mjs
|
4
|
+
module Helper
|
5
|
+
def page
|
6
|
+
@page ||= Mjs::JavaScriptContext.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def remote_function(opts)
|
10
|
+
build_href(opts)
|
11
|
+
unless opts[:submit]
|
12
|
+
opts[:url] ||= opts[:href]
|
13
|
+
opts[:dataType] = "script"
|
14
|
+
end
|
15
|
+
function = "jQuery.ajax(%s);" % options_for_ajax(opts)
|
16
|
+
confirm = opts.delete(:confirm)
|
17
|
+
function = "if (confirm('#{escape_javascript(confirm)}')) { #{function}; }" if confirm
|
18
|
+
return function
|
19
|
+
end
|
20
|
+
|
21
|
+
# experimental: not tested yet
|
22
|
+
def button_to(name, url='', opts={})
|
23
|
+
ajax = remote_function(opts)
|
24
|
+
opts[:type] = 'button'
|
25
|
+
opts[:value] = name
|
26
|
+
opts[:remote] ||= true if opts[:submit]
|
27
|
+
if opts.delete(:remote)
|
28
|
+
ajax = remote_function(opts)
|
29
|
+
opts[:onclick] = "#{opts.delete(:onclick)}; #{ajax}; return false;"
|
30
|
+
end
|
31
|
+
%{<input #{ opts.to_xml_attributes }>}
|
32
|
+
end
|
33
|
+
|
34
|
+
# override! :link_to # for Ajax
|
35
|
+
def link_to(name, url='', opts={})
|
36
|
+
opts[:href] ||= url
|
37
|
+
opts[:remote] ||= true if opts[:submit]
|
38
|
+
return super unless opts.delete(:remote)
|
39
|
+
|
40
|
+
ajax = remote_function(opts)
|
41
|
+
opts[:onclick] = "#{opts.delete(:onclick)}; #{ajax}; return false;"
|
42
|
+
opts[:href] = '#'
|
43
|
+
%{<a #{ opts.to_xml_attributes }>#{name}</a>}
|
44
|
+
end
|
45
|
+
|
46
|
+
######################################################################
|
47
|
+
### derived from actionpack
|
48
|
+
|
49
|
+
JS_ESCAPE_MAP = {
|
50
|
+
'\\' => '\\\\', '</' => '<\/',
|
51
|
+
"\r\n" => '\n',
|
52
|
+
"\n" => '\n', "\r" => '\n',
|
53
|
+
'"' => '\\"',
|
54
|
+
"'" => "\\'" }
|
55
|
+
|
56
|
+
# Escape carrier returns and single and double quotes for JavaScript segments.
|
57
|
+
def escape_javascript(javascript)
|
58
|
+
if javascript
|
59
|
+
javascript.gsub(/(\\|<\/|\r\n|[\n\r"'])/) { JS_ESCAPE_MAP[$1] }
|
60
|
+
else
|
61
|
+
''
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
def build_href(opts)
|
67
|
+
if opts[:href].is_a?(DataMapper::Resource)
|
68
|
+
record = opts[:href]
|
69
|
+
if record.new_record?
|
70
|
+
opts[:href] = resource(record.class.name.downcase.pluralize.intern, :new)
|
71
|
+
else
|
72
|
+
opts[:href] = resource(record)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
opts
|
76
|
+
end
|
77
|
+
|
78
|
+
AJAX_FUNCTIONS = {
|
79
|
+
:before => :beforeSend,
|
80
|
+
:before_send => :beforeSend,
|
81
|
+
:beforeSend => :beforeSend,
|
82
|
+
:complete => :complete,
|
83
|
+
:success => :success,
|
84
|
+
:error => :error,
|
85
|
+
:dataFilter => :dataFilter,
|
86
|
+
:data_filter => :dataFilter,
|
87
|
+
:xhr => :xhr,
|
88
|
+
}
|
89
|
+
|
90
|
+
|
91
|
+
def options_for_ajax(options)
|
92
|
+
js_options = build_callbacks!(options)
|
93
|
+
|
94
|
+
submit = options.delete(:submit)
|
95
|
+
target =
|
96
|
+
case submit
|
97
|
+
when Symbol then "jQuery('##{submit} input, ##{submit} select, ##{submit} textarea')"
|
98
|
+
when String then "jQuery('#{submit}')"
|
99
|
+
when NilClass # GET requst
|
100
|
+
else
|
101
|
+
raise ArgumentError, "link_to :submit expects Symbol or String, but got #{submit.class.name}"
|
102
|
+
end
|
103
|
+
build_href(options)
|
104
|
+
|
105
|
+
if target
|
106
|
+
js_options[:type] = "'POST'"
|
107
|
+
js_options[:data] = "#{target}.serialize()"
|
108
|
+
end
|
109
|
+
js_options[:url] = "'#{options[:url] || options[:href]}'"
|
110
|
+
js_options[:dataType] = "'script'"
|
111
|
+
|
112
|
+
if js_options[:url].blank?
|
113
|
+
raise "Cannot build ajax options because url is blank. (#{options.inspect})"
|
114
|
+
end
|
115
|
+
|
116
|
+
options_for_javascript(js_options)
|
117
|
+
end
|
118
|
+
|
119
|
+
def options_for_javascript(options)
|
120
|
+
if options.empty?
|
121
|
+
'{}'
|
122
|
+
else
|
123
|
+
"{#{options.keys.map { |k| "#{k}:#{options[k]}" }.sort.join(', ')}}"
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def jquery_selector(key)
|
128
|
+
case key
|
129
|
+
when Symbol then "jQuery('##{key}')"
|
130
|
+
when String then "jQuery('#{key}')"
|
131
|
+
else
|
132
|
+
raise "invalid jquery selector: [#{key.class}] #{key}"
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# this method affects "options"
|
137
|
+
def build_callbacks!(options)
|
138
|
+
callbacks = {}
|
139
|
+
|
140
|
+
[:before, :complete].each do |event|
|
141
|
+
options[event] = Array(options[event])
|
142
|
+
end
|
143
|
+
|
144
|
+
# special callback (:spinner)
|
145
|
+
if options[:spinner]
|
146
|
+
target = jquery_selector(options.delete(:spinner))
|
147
|
+
options[:before] << "#{target}.show()"
|
148
|
+
options[:complete] << "#{target}.hide()"
|
149
|
+
end
|
150
|
+
|
151
|
+
[:before, :complete].each do |event|
|
152
|
+
options[event] = options[event].compact * ';'
|
153
|
+
end
|
154
|
+
|
155
|
+
options.each do |callback, code|
|
156
|
+
if (name = AJAX_FUNCTIONS[callback])
|
157
|
+
callbacks[name.to_s] = "function(request){#{code}}"
|
158
|
+
options.delete(callback)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
callbacks
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|