remoteable 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in remoteable.gemspec
4
+ gemspec
@@ -0,0 +1,67 @@
1
+ # Remoteable
2
+
3
+ Remoteable is meant to be a simple set of conventions about how data is loaded into
4
+ a browser. The main idea goes back to the idea of how a browser loads built-in elements that aren't
5
+ inline text. Specifically, the whole idea of remoteable came from the realization that we've been using the
6
+ "Asynchronous" from "AJAX" since the first day we wrote an `<img>` tag. For example:
7
+
8
+ <img src="http://upload.wikimedia.org/wikipedia/commons/6/63/Wikipedia-logo.png">
9
+
10
+ What does the browser do when it sees this in the HTML? It goes off, starts downloading the picture and
11
+ then renders the picture and inserts it where the `<img>` tag lives in the rendered document. What
12
+ we've been doing by manually managing AJAX requests that go out, get some content, and inject it into the
13
+ resulting document is akin to manually downloading the images and injecting the result into the DOM. This
14
+ took me a bit to really grasp. Here's the core remoteable idea:
15
+
16
+ <div id="mydiv" data-src="http://somewhere.com/users/3">
17
+
18
+ In this instance treat the rendered user div on the page as a single resource that you go out and download and
19
+ then inject into the DOM as a replacement for the `<div>` placeholder.
20
+
21
+ Triggering it to load itself can be done a number of ways. You can target the div yourself:
22
+
23
+ $("#mydiv").trigger("refresh")
24
+
25
+ You can do a search for divs that have a common URL
26
+
27
+ $("*:remoteable(http://somewhere.com/users/3)").trigger("refresh")
28
+
29
+ You can refresh all the remoteables:
30
+
31
+ $.remoteables.trigger("refresh")
32
+
33
+ And so on.
34
+
35
+ ## Delegates
36
+
37
+ There are many times where one part of the page needs to be updated with some content. Given that a remoteable
38
+ div has a data-src it's very easy to tell the div to refresh itself, but a common pattern is changing the
39
+ div's `data-src` attribute and asking it to refresh. Handling this pattern is as simple as:
40
+
41
+ <div data-remote-delegate="#some_selector" data-src="http://somewhere.com/users/3">
42
+
43
+ When a click event hits this element (either directly or through event bubbling), remoteable will wrap the `data-remote-delegate`
44
+ value in a jQuery selector and call `remoteable("update")` with its own `data-src` url and asks it to refresh. This is roughly
45
+ equivalent to:
46
+
47
+ $("#some_selector").remoteable("update", {url: "http://somewhere.com/users/3"}).trigger("refresh.remoteable");
48
+
49
+ This way, the target element(s) become remoteable (if they weren't already), and the rest of the remoteable flow happens
50
+ as normal. Note, the original event continues to bubble but preventDefault is called on it.
51
+
52
+ If the target element is already loading a request that request will be cancelled first.
53
+
54
+ ## Content on the way back
55
+
56
+ When the remoteable request completes the target element will check the response type. If it's HTML it will replace its content
57
+ with what was returned by the AJAX request. If you'd rather that the returned content *replace* the remoteable, just add
58
+ `data-replace-self=true` to the element:
59
+
60
+ <div data-src="http://somewhere.com/users/3" data-replace-self="true">
61
+
62
+ This is useful for divs that show loading status and should be removed once loading is complete. This way you don't have to worry
63
+ about cleaning up the loading div as it is handled automatically.
64
+
65
+ ## Redirects
66
+
67
+ TODO: writeup
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,61 @@
1
+ module RemoteableHelper
2
+ def remote_content_tag(tag, options = {}, &block)
3
+ normalize_remoteable_params!(options)
4
+
5
+ content_tag(tag, options) do
6
+ yield if options['data-loaded']
7
+ end
8
+ end
9
+
10
+ def link_to_refresh(link, target, options = {})
11
+ normalize_remoteable_params!(options)
12
+
13
+ link_to link, target, options
14
+ end
15
+
16
+ def link_to_remoteable(link, options = {})
17
+ normalize_remoteable_params!(options)
18
+
19
+ link_to_function link, "", options
20
+ end
21
+
22
+ def refresh_remoteable(options)
23
+ normalize_remoteable_params!(options)
24
+
25
+ %Q{$.remoteables("#{j options["data-src"]}").remoteable("refresh")}
26
+ end
27
+
28
+ private
29
+
30
+ def polymorphic_link_from_options(url, options = {})
31
+ return url if url.is_a?(String)
32
+ args = [url]
33
+ args << {:format => options[:format]} if options[:format]
34
+ polymorphic_url(*args)
35
+ end
36
+
37
+ def normalize_remoteable_params!(options)
38
+ data_method = options[:method] || "get"
39
+
40
+ if(options[:url])
41
+ url = polymorphic_link_from_options(options[:url], options)
42
+ options['data-src'] = url
43
+ options['data-method'] = data_method
44
+ end
45
+
46
+ options['data-loaded'] = !!options[:loaded]
47
+ Rails.logger.info("Data loaded: #{options['data-loaded']}")
48
+ options[:class] = options[:loaded_class] if options[:loaded] && options[:loaded_class]
49
+
50
+ options[:style] = "display: none;" if options[:hide_until_loaded] && !options['data-loaded']
51
+
52
+ options.delete(:method)
53
+ options.delete(:url)
54
+ options.delete(:format)
55
+ options.delete(:loaded)
56
+ options.delete(:hide_until_loaded)
57
+ options.delete(:loaded_class)
58
+ end
59
+
60
+
61
+ end
@@ -0,0 +1,185 @@
1
+ (function($) {
2
+
3
+ $.extend(
4
+ $.expr[":"], {
5
+ remoteable: function(elem, i, match, array) {
6
+ var url = match[3].split(",")[0];
7
+ var method = match[3].split(",")[1] || "get";
8
+ return $(elem).data("src") == url && $(elem).data("method") == method;
9
+ }
10
+ }
11
+ );
12
+
13
+ $.remoteables = function(url, method) {
14
+ var remoteables = "*[data-src]";
15
+ if(url) {
16
+ remoteables += "[data-src^='" + url + "']";
17
+ }
18
+ if(method) {
19
+ remoteables += "[data-method='" + method + "']";
20
+ }
21
+
22
+ return $(remoteables);
23
+ }
24
+
25
+ var httpMethods = {
26
+ put: function(options) {
27
+ if(options.data == undefined) {
28
+ options.data = {};
29
+ }
30
+ options.data._method = 'PUT';
31
+ options.type = "POST";
32
+ return $.ajax(options);
33
+ },
34
+ delete: function(options) {
35
+ if(options.data == undefined) {
36
+ options.data = {};
37
+ }
38
+ options.data._method = 'DELETE';
39
+ options.type = "POST";
40
+ return $.ajax(options);
41
+ },
42
+ post: function(options) {
43
+ options.type = "POST";
44
+ return $.ajax(options);
45
+ },
46
+ get: function(options) {
47
+ options.type = "GET";
48
+ return $.ajax(options);
49
+ }
50
+ };
51
+
52
+ var methods = {
53
+ init: function(options) {
54
+ options = options || {};
55
+ return this.each(function() {
56
+ var $this = $(this),
57
+ data = $this.data('remoteable');
58
+ // If the plugin hasn't been initialized yet
59
+ if(!data) {
60
+ /*
61
+ Do more setup stuff here
62
+ */
63
+ $(this).data('remoteable', {
64
+ target: $this
65
+ });
66
+
67
+ var self = $(this);
68
+ var method = options["method"] || self.data("method") || "get";
69
+ self.attr("data-method", method);
70
+ var url = options["url"] || self.data("src");
71
+ self.attr("data-src", url);
72
+
73
+ self.on("click.remoteable", function(event) {
74
+ if(self.data("remote-delegate")) {
75
+ $(self.data('remote-delegate')).remoteable("update", {url: url});
76
+ } else {
77
+ if(self.is("a")) {
78
+ event.preventDefault();
79
+ if(self.data("remoteable").currentRequest) {
80
+ self.data("remoteable").currentRequest.abort();
81
+ }
82
+ self.data("remoteable").currentRequest = httpMethods[method].call(self, {
83
+ url: url,
84
+ complete: function(jqXHR, textStatus) {
85
+ self.data("remoteable").currentRequest = null;
86
+ }
87
+ });
88
+ event.stopPropagation();
89
+ }
90
+ }
91
+ });
92
+
93
+ self.on("refresh.remoteable", function(event) {
94
+ var url = self.data("src");
95
+ event.preventDefault();
96
+ event.stopPropagation();
97
+
98
+ if(self.is(":not(a)")) {
99
+ self.addClass("remoteable-processing");
100
+ if(self.data("remoteable").currentRequest) {
101
+ self.data("remoteable").currentRequest.abort();
102
+ }
103
+ self.data("remoteable").currentRequest = httpMethods[method].call(self, {
104
+ url: url,
105
+ complete: function(jqXHR, textStatus) {
106
+ self.removeClass("remoteable-processing");
107
+ self.trigger("ajax:complete.remoteable", [jqXHR, textStatus]);
108
+ }
109
+ });
110
+ }
111
+ });
112
+
113
+ self.on("ajax:complete.remoteable", function(event, jqXHR, textStatus) {
114
+ event.stopPropagation();
115
+ event.preventDefault();
116
+ self.data("remoteable").currentRequest = null;
117
+ var ct = jqXHR.getResponseHeader("content-type") || "";
118
+ if(ct.indexOf('html') > -1) {
119
+ var returned = $(jqXHR.responseText);
120
+ if(self.data("replace-self")) {
121
+ self.replaceWith(returned);
122
+ } else {
123
+ self.html(returned);
124
+ self.trigger("refreshed.remoteable", [self.data("src")]);
125
+ }
126
+ }
127
+ if(ct.indexOf('json') > -1) {
128
+ var url = $.parseJSON(jqXHR.responseText).redirect_to;
129
+ self.remoteable("update", {url: url});
130
+ }
131
+ });
132
+
133
+ }
134
+ });
135
+ },
136
+
137
+ destroy: function() {
138
+ return this.each(function() {
139
+ var $this = $(this),
140
+ data = $this.data('remoteable');
141
+
142
+ $this.removeData('remoteable');
143
+ });
144
+ },
145
+
146
+ refreshAll: function(options) {
147
+ $.remoteables().remoteable("refresh");
148
+ },
149
+
150
+ initAll: function(options) {
151
+ $.remoteables().remoteable();
152
+ },
153
+
154
+ update: function(options) {
155
+ return this.each(function() {
156
+ var self = $(this);
157
+ self.remoteable();
158
+ if(options.url) {
159
+ self.data("src", options.url);
160
+ }
161
+ self.trigger("refresh.remoteable");
162
+ });
163
+ },
164
+
165
+ refresh: function(options) {
166
+ return this.each(function() {
167
+ var self = $(this);
168
+ self.trigger("refresh.remoteable");
169
+ });
170
+ }
171
+ };
172
+
173
+ $.fn.remoteable = function(method) {
174
+
175
+ if(methods[method]) {
176
+ return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
177
+ } else if(typeof method === 'object' || !method) {
178
+ return methods.init.apply(this, arguments);
179
+ } else {
180
+ $.error('Method ' + method + ' does not exist on jQuery.remoteable');
181
+ }
182
+
183
+ };
184
+
185
+ })(jQuery);
@@ -0,0 +1,7 @@
1
+ require "remoteable/version"
2
+
3
+ module Remoteable
4
+ # Your code goes here...
5
+ end
6
+
7
+ require 'remoteable/engine' if defined?(Rails)
@@ -0,0 +1,7 @@
1
+ module Remoteable
2
+ class Engine < Rails::Engine
3
+ config.to_prepare do
4
+ ActionController::Base.send(:helper, RemoteableHelper)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,3 @@
1
+ module Remoteable
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "remoteable/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "remoteable"
7
+ s.version = Remoteable::VERSION
8
+ s.authors = ["Andrew Eberbach"]
9
+ s.email = ["andrew@ebertech.ca"]
10
+ s.homepage = ""
11
+ s.summary = %q{Remoteable AJAX content helper}
12
+
13
+ s.rubyforge_project = "remoteable"
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
+ s.require_paths = ["lib"]
19
+
20
+ # specify any dependencies here; for example:
21
+ # s.add_development_dependency "rspec"
22
+ # s.add_runtime_dependency "rest-client"
23
+ end
metadata ADDED
@@ -0,0 +1,76 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: remoteable
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
+ prerelease:
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 0
10
+ version: 1.0.0
11
+ platform: ruby
12
+ authors:
13
+ - Andrew Eberbach
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-03-31 00:00:00 Z
19
+ dependencies: []
20
+
21
+ description:
22
+ email:
23
+ - andrew@ebertech.ca
24
+ executables: []
25
+
26
+ extensions: []
27
+
28
+ extra_rdoc_files: []
29
+
30
+ files:
31
+ - .gitignore
32
+ - Gemfile
33
+ - README.md
34
+ - Rakefile
35
+ - app/helpers/remoteable_helper.rb
36
+ - lib/assets/javascripts/remoteable.js
37
+ - lib/remoteable.rb
38
+ - lib/remoteable/engine.rb
39
+ - lib/remoteable/version.rb
40
+ - remoteable.gemspec
41
+ homepage: ""
42
+ licenses: []
43
+
44
+ post_install_message:
45
+ rdoc_options: []
46
+
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ hash: 3
55
+ segments:
56
+ - 0
57
+ version: "0"
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ none: false
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ hash: 3
64
+ segments:
65
+ - 0
66
+ version: "0"
67
+ requirements: []
68
+
69
+ rubyforge_project: remoteable
70
+ rubygems_version: 1.8.10
71
+ signing_key:
72
+ specification_version: 3
73
+ summary: Remoteable AJAX content helper
74
+ test_files: []
75
+
76
+ has_rdoc: