mjs 0.0.6
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.
- 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
|