rest_in_place 2.0.0.beta

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 [Jan Varwig]
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.
@@ -0,0 +1,227 @@
1
+ REST in Place
2
+ ===========
3
+ _______
4
+ / \
5
+ | R.I.P.|
6
+ | |
7
+ | |
8
+ -------------
9
+
10
+ REST in Place is an AJAX Inplace-Editor that talks to RESTful controllers.
11
+ It requires absolutely no additional server-side code if your controller
12
+ fulfills the following REST preconditions:
13
+
14
+ - It uses the HTTP PUT method to update a record
15
+ - It delivers an object in JSON form for requests with
16
+ "Accept: application/json" headers
17
+
18
+ The editor works by PUTting the updated value to the server and GETting the
19
+ updated record afterwards to display the updated value.
20
+ That way any authentication methods or otherwise funky workflows in your
21
+ controllers are used for the inplace-editors requests.
22
+ To save the additional GET request, you can take the shortcut of returning the
23
+ updated record in the response to the PUT request. See the testapp for an
24
+ example.
25
+
26
+ URL: <http://github.com/janv/rest_in_place/>
27
+ REPOSITORY: git://github.com/janv/rest_in_place.git
28
+ BLOG: <http://jan.varwig.org/projects/rest-in-place>
29
+
30
+ If you like REST in Place, you can flattr me: <a href="http://flattr.com/thing/1984/REST-in-Place" target="_blank">
31
+ <img src="http://api.flattr.com/button/flattr-badge-large.png" alt="Flattr this" title="Flattr this" border="0" /></a>
32
+
33
+ Requirements
34
+ ============
35
+
36
+ JavaScript
37
+ ----------
38
+
39
+ The JavaScript code (`app/assets/javascripts/rest_in_place/rest_in_place.js.erb`)
40
+ only relies on the presence of jQuery. You can extract just that file and use
41
+ it with whatever framework in whatever server-side language you want, given
42
+ that you follow the coventions described later in this document.
43
+
44
+ Even though this is processed by ERB, you can use it as a JavaScript file
45
+ without modification.
46
+
47
+ Rails
48
+ -----
49
+
50
+ Since I guess most people use REST in Place in Rails apps, I turned this
51
+ entire thing into a gem that you can require in your Gemfile. It requires
52
+ jQuery, but it will NOT install a `jquery-rails` dependency. This is done so
53
+ you aren't forced to use `jquery-rails` if you want to run a more up-to-date
54
+ version of jQuery. Just make sure that jQuery is there.
55
+
56
+ REST in Place requires Rails >= 3.1 as a dependency since it loads through the
57
+ asset pipeline.
58
+
59
+ Installation
60
+ ============
61
+
62
+ Just add
63
+
64
+ gem 'rest_in_place'
65
+
66
+ to your Gemfile.
67
+
68
+ Then load the JavaScript by adding <%= javascript_include_tag "rest_in_place" %>
69
+ into your layout. Alternatively you can require 'rest_in_place' in your
70
+ JavaScript files in `app/assets`, for example in your application.js:
71
+
72
+ //= require 'rest_in_place'
73
+
74
+ In both cases, make sure you load REST in Place __after__ jQuery.
75
+
76
+ Rails Request forgery Protection
77
+ ================================
78
+
79
+ For REST in Place to work with Rails request forgery protection, place the
80
+ following lines into your applications layout:
81
+
82
+ <script type="text/javascript">
83
+ rails_authenticity_token = '<%= form_authenticity_token %>'
84
+ </script>
85
+
86
+ Usage Instructions
87
+ ==================
88
+
89
+ To make a piece of Text inplace-editable, wrap it into an element (a span
90
+ usually) with class "rest_in_place". The editor needs 3 pieces of information
91
+ to work: a URL, an object name and the attribute name. These are provided as
92
+ follows:
93
+
94
+ - put attributes into the element, like this:
95
+
96
+ <span class="rest_in_place" data-url="/users/1" data-object="user" data-attribute="name">
97
+ <%= @user.name %>
98
+ </span>
99
+
100
+ - if any of these attributes is missing, DOM parents of the element are searched
101
+ for them. That means you can write something like:
102
+
103
+ <div data-object="user" data-url="/users/1">
104
+ Name: <span class="rest_in_place" data-attribute="name" ><%= @user.name %></span><br/>
105
+ eMail: <span class="rest_in_place" data-attribute="email"><%= @user.email %></span>
106
+ </div>
107
+
108
+ - You can completely omit the url to use the current document's url.
109
+ With proper RESTful controllers this should always work, the explicit
110
+ url-attribute is for cases when you want to edit a resource that is
111
+ displayed as part of a non-RESTful webpage.
112
+
113
+ - Rails provides the dom_id helper that constructs a dom id out of an
114
+ ActiveRecord for you. So, your HTML page may look like this:
115
+
116
+ <div id="<%= dom_id @user # == "user_1" %>">
117
+ Name: <span class="rest_in_place" data-attribute="name" ><%= @user.name %></span><br/>
118
+ eMail: <span class="rest_in_place" data-attribute="email"><%= @user.email %></span>
119
+ </div>
120
+
121
+ REST in Place recognizes dom_ids of this form and derives the object parameter
122
+ from them, so that (with the current documents url used) you really only need
123
+ to provide the attributes name in most cases.
124
+
125
+ **Note that a manually defined (in the element or in one of the parents)
126
+ object always overrides dom_id recognition.**
127
+
128
+ - REST in Place supports multiple form types. The default type is a input
129
+ field, a textarea is also supported. To select a form type use the
130
+ `data-formtype` attribute in the element and set it to the name of your
131
+ formtype ( `input`, or `textarea` ).
132
+
133
+ To write your own form types, just extend the `RestInPlace.forms` object
134
+ and select your new form type throught the `data-formtype` attribute.
135
+
136
+ Example
137
+ =======
138
+
139
+ Your routes.rb:
140
+
141
+ map.resources :users
142
+
143
+ Your app/controllers/users_controller.rb:
144
+
145
+ class UsersController < ApplicationController
146
+ def show
147
+ @user = User.find params[:id]
148
+ respond_to do |type|
149
+ type.html
150
+ type.json {render :json => @user}
151
+ end
152
+ end
153
+
154
+ def update
155
+ @user = User.find params[:id]
156
+ if @user.update_attributes!(params[:user])
157
+ respond_to do |format|
158
+ format.html { redirect_to( @person ) }
159
+ format.json { render :json => @user }
160
+ end
161
+ else
162
+ respond_to do |format|
163
+ format.html { render :action => :edit } # edit.html.erb
164
+ format.json { render :nothing => true }
165
+ end
166
+ end
167
+ end
168
+ end
169
+
170
+ Your app/views/users/show.html.erb:
171
+
172
+ <html>
173
+ <head>
174
+ <%= javascript_include_tag "jquery-1.4.min" , "jquery.rest_in_place" %>
175
+ <script type="text/javascript">
176
+ rails_authenticity_token = '<%= form_authenticity_token %>'
177
+ jQuery(function(){
178
+ jQuery(".rest_in_place").rest_in_place();
179
+ });
180
+ </script>
181
+ </head>
182
+ <body>
183
+ <div id="<%= dom_id @user %>">
184
+ ID: <%= @user.id %><br />
185
+ Name: <span class="rest_in_place" data-formtype="input" data-attribute="name"><%= @user.name %></span><br/><br/>
186
+ Hobbies: <span class="rest_in_place" data-formtype="textarea" data-attribute="hobbies"><%= @user.hobbies %></span>
187
+ </div>
188
+ </body>
189
+ </html>
190
+
191
+ You can run this example by running to the testapp included in this
192
+ plugin with script/server (sqlite3 required) and visiting
193
+ localhost:3000/users/
194
+
195
+ Hint:
196
+ you need to set up the database first.
197
+ Copy and edit `testapp/config/database.yml.sample` accordingly.
198
+ If you don't want to use the included sqlite3 database, run `rake db:create`
199
+ and `rake db:schema:load`.
200
+
201
+ Troubleshooting
202
+ ===============
203
+
204
+ REST in Place is very picky about correct headers and formatting.
205
+ If you experience errors, please make sure your controller sends responses as
206
+ properly formatted JSON with the correct MIME-Type "application/json".
207
+
208
+ Non-Rails
209
+ =========
210
+
211
+ REST in Place was written for Ruby on Rails but is usable with any kind of
212
+ RESTful web api. You should be able to adapt the instructions above to your
213
+ framework easily.
214
+
215
+ Participation
216
+ =============
217
+
218
+ I'd love to get comments, bug reports (or better, patches) about REST in Place.
219
+ For this, you can either fork the project and send a pull request, or submit a
220
+ bug in the tracker at github: <http://github.com/janv/rest_in_place/issues>
221
+
222
+ For general comments and questions, please use the comment function on my blog:
223
+ <http://jan.varwig.org/projects/rest-in-place>
224
+
225
+
226
+ ---
227
+ Copyright (c) 2010 [Jan Varwig], released under the MIT license
@@ -0,0 +1 @@
1
+ //= require ./rest_in_place.js
@@ -0,0 +1,193 @@
1
+ function RestInPlaceEditor(e) {
2
+ this.element = jQuery(e);
3
+ this.initOptions();
4
+ this.bindForm();
5
+
6
+ this.element.bind('click', {editor: this}, this.clickHandler);
7
+ }
8
+
9
+ RestInPlaceEditor.prototype = {
10
+ // Public Interface Functions //////////////////////////////////////////////
11
+
12
+ activate : function() {
13
+ this.oldValue = this.element.html();
14
+ this.element.addClass('rip-active');
15
+ this.element.unbind('click', this.clickHandler)
16
+ this.activateForm();
17
+ },
18
+
19
+ abort : function() {
20
+ this.element
21
+ .html(this.oldValue)
22
+ .removeClass('rip-active')
23
+ .bind('click', {editor: this}, this.clickHandler);
24
+ },
25
+
26
+ update : function() {
27
+ var editor = this;
28
+ editor.ajax({
29
+ "type" : "post",
30
+ "dataType" : "json",
31
+ "data" : editor.requestData(),
32
+ "error" : function(response) {
33
+ if (response.status == 100 && response.statusText == "parsererror") {
34
+ editor.ajax({
35
+ "dataType" : 'json',
36
+ "success" : function(data){ editor.loadSuccessCallback(data) }
37
+ });
38
+ } else {
39
+ editor.abort();
40
+ }
41
+ },
42
+ "success" : function(data){
43
+ if (data) {
44
+ editor.loadSuccessCallback(data)
45
+ } else {
46
+ editor.ajax({
47
+ "dataType" : 'json',
48
+ "success" : function(data){ editor.loadSuccessCallback(data) }
49
+ });
50
+ }
51
+ }
52
+ });
53
+ editor.element.html("saving...");
54
+ },
55
+
56
+ activateForm : function() {
57
+ alert("The form was not properly initialized. activateForm is unbound");
58
+ },
59
+
60
+ // Helper Functions ////////////////////////////////////////////////////////
61
+
62
+ initOptions : function() {
63
+ // Try parent supplied info
64
+ var self = this;
65
+ self.element.parents().each(function(){
66
+ self.url = self.url || jQuery(this).attr("data-url");
67
+ self.formType = self.formType || jQuery(this).attr("data-formtype");
68
+ self.objectName = self.objectName || jQuery(this).attr("data-object");
69
+ self.attributeName = self.attributeName || jQuery(this).attr("data-attribute");
70
+ });
71
+ // Try Rails-id based if parents did not explicitly supply something
72
+ self.element.parents().each(function(){
73
+ var res;
74
+ if (res = this.id.match(/^(\w+)_(\d+)$/i)) {
75
+ self.objectName = self.objectName || res[1];
76
+ }
77
+ });
78
+ // Load own attributes (overrides all others)
79
+ self.url = self.element.attr("data-url") || self.url || document.location.pathname;
80
+ self.formType = self.element.attr("data-formtype") || self.formtype || "input";
81
+ self.objectName = self.element.attr("data-object") || self.objectName;
82
+ self.attributeName = self.element.attr("data-attribute") || self.attributeName;
83
+ },
84
+
85
+ bindForm : function() {
86
+ this.activateForm = RestInPlaceEditor.forms[this.formType].activateForm;
87
+ this.getValue = RestInPlaceEditor.forms[this.formType].getValue;
88
+ },
89
+
90
+ getValue : function() {
91
+ alert("The form was not properly initialized. getValue is unbound");
92
+ },
93
+
94
+ /* Generate the data sent in the POST request */
95
+ requestData : function() {
96
+ var data = "_method=put";
97
+ data += "&"+this.objectName+'['+this.attributeName+']='+encodeURIComponent(this.getValue());
98
+ data += this.getEncodedTokenAuthentication()
99
+ return data;
100
+ },
101
+
102
+ getEncodedTokenAuthentication : function() {
103
+ var param = $('meta[name=csrf-param]').attr('content');
104
+ var token = $('meta[name=csrf-token]').attr('content');
105
+ if (param && token) {
106
+ param = encodeURIComponent(param);
107
+ token = encodeURIComponent(token);
108
+ return "&"+param+"="+token;
109
+ } else {
110
+ return "";
111
+ }
112
+ },
113
+
114
+ ajax : function(options) {
115
+ options.url = this.url;
116
+ return jQuery.ajax(options);
117
+ },
118
+
119
+ extractAttributeFromData : function(data) {
120
+ var include_root_in_json = "<%= ActiveRecord::Base.include_root_in_json %>";
121
+ if (include_root_in_json == "false") {
122
+ return data[this.attributeName];
123
+ } else {
124
+ return data[this.objectName][this.attributeName];
125
+ }
126
+ },
127
+
128
+ // Handlers ////////////////////////////////////////////////////////////////
129
+
130
+ loadSuccessCallback : function(data) {
131
+ this.element.html(this.extractAttributeFromData(data));
132
+ this.element.bind('click', {editor: this}, this.clickHandler);
133
+ this.element.removeClass('rip-active');
134
+ },
135
+
136
+ clickHandler : function(event) {
137
+ event.data.editor.activate();
138
+ }
139
+ };
140
+
141
+
142
+ RestInPlaceEditor.forms = {
143
+ "input" : {
144
+ /* is bound to the editor and called to replace the element's content with a form for editing data */
145
+ activateForm : function() {
146
+ this.element.html('<form action="javascript:void(0)" style="display:inline;"><input type="text" value="' + jQuery.trim(this.oldValue) + '"></form>');
147
+ this.element.find('input')[0].select();
148
+ this.element.find("form")
149
+ .bind('submit', {editor: this}, RestInPlaceEditor.forms.input.submitHandler);
150
+ this.element.find("input")
151
+ .bind('blur', {editor: this}, RestInPlaceEditor.forms.input.inputBlurHandler);
152
+ },
153
+
154
+ getValue : function() {
155
+ return this.element.find("input").val();
156
+ },
157
+
158
+ inputBlurHandler : function(event) {
159
+ event.data.editor.abort();
160
+ },
161
+
162
+ submitHandler : function(event) {
163
+ event.data.editor.update();
164
+ return false;
165
+ }
166
+ },
167
+
168
+ "textarea" : {
169
+ /* is bound to the editor and called to replace the element's content with a form for editing data */
170
+ activateForm : function() {
171
+ this.element.html('<form action="javascript:void(0)" style="display:inline;"><textarea>' + jQuery.trim(this.oldValue) + '</textarea></form>');
172
+ this.element.find('textarea')[0].select();
173
+ this.element.find("textarea")
174
+ .bind('blur', {editor: this}, RestInPlaceEditor.forms.textarea.blurHandler);
175
+ },
176
+
177
+ getValue : function() {
178
+ return this.element.find("textarea").val();
179
+ },
180
+
181
+ blurHandler : function(event) {
182
+ event.data.editor.update();
183
+ }
184
+
185
+ }
186
+ };
187
+
188
+ jQuery.fn.rest_in_place = function() {
189
+ this.each(function(){
190
+ jQuery(this).data('restInPlaceEditor', new RestInPlaceEditor(this));
191
+ })
192
+ return this;
193
+ };
@@ -0,0 +1,7 @@
1
+ require "rest_in_place/version"
2
+
3
+ module RestInPlace
4
+ class Engine < Rails::Engine
5
+ # no ruby code here!
6
+ end
7
+ end
@@ -0,0 +1,3 @@
1
+ module RestInPlace
2
+ VERSION = "2.0.0.beta"
3
+ end
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "rest_in_place/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "rest_in_place"
7
+ s.version = RestInPlace::VERSION
8
+ s.date = '2011-11-27'
9
+ s.authors = ["Jan Varwig"]
10
+ s.email = ["jan@varwig.org"]
11
+ s.homepage = "http://jan.varwig.org"
12
+ s.summary = %q{An AJAX Inplace-Editor for the Rails 3.1 asset pipeline.}
13
+ s.description = %q{REST in Place is an AJAX Inplace-Editor that talks to RESTful controllers.}
14
+ s.license = "MIT"
15
+
16
+ s.files = Dir["Gemfile", "MIT-LICENSE", "README.markdown", "rest_in_place.gemspec", "app/**/*", "lib/**/*"]
17
+
18
+ s.require_paths = ["lib"]
19
+ s.add_dependency('rails', '>= 3.1')
20
+ end
metadata ADDED
@@ -0,0 +1,89 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rest_in_place
3
+ version: !ruby/object:Gem::Version
4
+ hash: 31098209
5
+ prerelease: 6
6
+ segments:
7
+ - 2
8
+ - 0
9
+ - 0
10
+ - beta
11
+ version: 2.0.0.beta
12
+ platform: ruby
13
+ authors:
14
+ - Jan Varwig
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2011-11-27 00:00:00 Z
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: rails
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 5
30
+ segments:
31
+ - 3
32
+ - 1
33
+ version: "3.1"
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ description: REST in Place is an AJAX Inplace-Editor that talks to RESTful controllers.
37
+ email:
38
+ - jan@varwig.org
39
+ executables: []
40
+
41
+ extensions: []
42
+
43
+ extra_rdoc_files: []
44
+
45
+ files:
46
+ - MIT-LICENSE
47
+ - README.markdown
48
+ - rest_in_place.gemspec
49
+ - app/assets/javascripts/rest_in_place/index.js
50
+ - app/assets/javascripts/rest_in_place/rest_in_place.js.erb
51
+ - lib/rest_in_place/version.rb
52
+ - lib/rest_in_place.rb
53
+ homepage: http://jan.varwig.org
54
+ licenses:
55
+ - MIT
56
+ post_install_message:
57
+ rdoc_options: []
58
+
59
+ require_paths:
60
+ - lib
61
+ required_ruby_version: !ruby/object:Gem::Requirement
62
+ none: false
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ hash: 3
67
+ segments:
68
+ - 0
69
+ version: "0"
70
+ required_rubygems_version: !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - ">"
74
+ - !ruby/object:Gem::Version
75
+ hash: 25
76
+ segments:
77
+ - 1
78
+ - 3
79
+ - 1
80
+ version: 1.3.1
81
+ requirements: []
82
+
83
+ rubyforge_project:
84
+ rubygems_version: 1.8.11
85
+ signing_key:
86
+ specification_version: 3
87
+ summary: An AJAX Inplace-Editor for the Rails 3.1 asset pipeline.
88
+ test_files: []
89
+