rest_in_place 2.0.0.beta

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.
@@ -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
+