shutterbug 0.1.2 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +7 -3
- data/bower.json +26 -0
- data/demo/iframe3.html +29 -0
- data/demo/iframe4.html +29 -0
- data/demo/iframe_no_shutterbug.html +20 -0
- data/demo/index.html +1 -0
- data/demo/nested_iframe_example.html +41 -0
- data/lib/shutterbug/handlers/shutterbug.js +143 -83
- data/lib/shutterbug.rb +1 -1
- metadata +9 -4
data/README.md
CHANGED
@@ -106,11 +106,15 @@ some useful things like finding and including all the css on the page, and 'seri
|
|
106
106
|
|
107
107
|
### Shutterbug JQuery custom events ###
|
108
108
|
|
109
|
-
Shutterbug emits a jQuery custom event called
|
109
|
+
Shutterbug emits a jQuery custom event called `shutterbug-saycheese` just prior to copying styles, elements, and canvas contents to the document fragment. This allows applications to do any preparation required before they are ready to be snapshotted.
|
110
110
|
|
111
|
-
|
111
|
+
In your application, you can register your event-handler like this:
|
112
112
|
|
113
|
-
|
113
|
+
$(window).on('shutterbug-saycheese', function() {
|
114
|
+
api.renderCanvas();
|
115
|
+
});
|
116
|
+
|
117
|
+
After all elements are copied, emits a `shutterbug-asyouwere` event.
|
114
118
|
|
115
119
|
### Deploying on Heroku ###
|
116
120
|
|
data/bower.json
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
{
|
2
|
+
"name": "shutterbug",
|
3
|
+
"version": "0.1.3",
|
4
|
+
"homepage": "https://github.com/concord-consortium/shutterbug",
|
5
|
+
"authors": [
|
6
|
+
"Noah Paessel <npaessel@concord.org>"
|
7
|
+
],
|
8
|
+
"description": "Javascript library for facilitating rendering DOM components in rasterized form.",
|
9
|
+
"main": "./lib/shutterbug/handlers/shutterbug.js",
|
10
|
+
"dependencies": {
|
11
|
+
"jquery": "1.11.0"
|
12
|
+
},
|
13
|
+
"keywords": [
|
14
|
+
"shutterbug"
|
15
|
+
],
|
16
|
+
"license": "MIT",
|
17
|
+
"private": true,
|
18
|
+
"ignore": [
|
19
|
+
"**/.*",
|
20
|
+
"node_modules",
|
21
|
+
"bower_components",
|
22
|
+
"spec",
|
23
|
+
"demo",
|
24
|
+
"images"
|
25
|
+
]
|
26
|
+
}
|
data/demo/iframe3.html
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
<!doctype html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>demo</title>
|
5
|
+
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"></script>
|
6
|
+
<script type="text/javascript" src="/shutterbug/shutterbug.js"></script>
|
7
|
+
<link rel="stylesheet" type="text/css" href="main.css"></link>
|
8
|
+
<style type="text/css">
|
9
|
+
#src {
|
10
|
+
color: red;
|
11
|
+
width: 350px;
|
12
|
+
height: 250px;
|
13
|
+
}
|
14
|
+
</style>
|
15
|
+
</head>
|
16
|
+
<body>
|
17
|
+
<div id="src">
|
18
|
+
<p>(iframe that contains other iframes)</p>
|
19
|
+
<iframe src="iframe.html" width="140" height="140"></iframe>
|
20
|
+
<iframe src="iframe2.html" width="140" height="140"></iframe>
|
21
|
+
</div>
|
22
|
+
</div>
|
23
|
+
</body>
|
24
|
+
<script type="text/javascript">
|
25
|
+
$(document).ready(function(e) {
|
26
|
+
new Shutterbug('#src');
|
27
|
+
});
|
28
|
+
</script>
|
29
|
+
</html>
|
data/demo/iframe4.html
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
<!doctype html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>demo</title>
|
5
|
+
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"></script>
|
6
|
+
<script type="text/javascript" src="/shutterbug/shutterbug.js"></script>
|
7
|
+
<link rel="stylesheet" type="text/css" href="main.css"></link>
|
8
|
+
<style type="text/css">
|
9
|
+
#src {
|
10
|
+
color: navy;
|
11
|
+
width: 350px;
|
12
|
+
height: 250px;
|
13
|
+
}
|
14
|
+
</style>
|
15
|
+
</head>
|
16
|
+
<body>
|
17
|
+
<div id="src">
|
18
|
+
<p>(iframe that contains other iframes, but not all of them support Shutterbug)</p>
|
19
|
+
<iframe src="iframe.html" width="140" height="140"></iframe>
|
20
|
+
<iframe src="iframe_no_shutterbug.html" width="140" height="140"></iframe>
|
21
|
+
</div>
|
22
|
+
</div>
|
23
|
+
</body>
|
24
|
+
<script type="text/javascript">
|
25
|
+
$(document).ready(function(e) {
|
26
|
+
new Shutterbug('#src');
|
27
|
+
});
|
28
|
+
</script>
|
29
|
+
</html>
|
@@ -0,0 +1,20 @@
|
|
1
|
+
<!doctype html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>demo</title>
|
5
|
+
<link rel="stylesheet" type="text/css" href="main.css"></link>
|
6
|
+
<style type="text/css">
|
7
|
+
#src {
|
8
|
+
color: red;
|
9
|
+
width: 100px;
|
10
|
+
height: 100px;
|
11
|
+
}
|
12
|
+
</style>
|
13
|
+
</head>
|
14
|
+
<body>
|
15
|
+
<div id="src">
|
16
|
+
(iframe without Shutterbug support)
|
17
|
+
</div>
|
18
|
+
</div>
|
19
|
+
</body>
|
20
|
+
</html>
|
data/demo/index.html
CHANGED
@@ -13,6 +13,7 @@
|
|
13
13
|
<li>Sourced vs. inline <a href="svg_example.html">SVG</a>.</li>
|
14
14
|
<li>Very <a href="simple_example.html">simple</a> example.</li>
|
15
15
|
<li><a href="iframe_example.html">IFrame</a> example.</li>
|
16
|
+
<li><a href="nested_iframe_example.html">Nested IFrames</a> example.</li>
|
16
17
|
</ul>
|
17
18
|
</body>
|
18
19
|
</html>
|
@@ -0,0 +1,41 @@
|
|
1
|
+
<!doctype html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>demo</title>
|
5
|
+
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"></script>
|
6
|
+
<script type="text/javascript" src="/shutterbug/shutterbug.js"></script>
|
7
|
+
<link rel="stylesheet" type="text/css" href="main.css">
|
8
|
+
<style type="text/css">
|
9
|
+
#dst, #dst2 {
|
10
|
+
padding: 0;
|
11
|
+
}
|
12
|
+
</style>
|
13
|
+
</head>
|
14
|
+
|
15
|
+
<body>
|
16
|
+
<div id="src1">
|
17
|
+
<iframe src="iframe3.html" width="400" height="300"></iframe>
|
18
|
+
</div>
|
19
|
+
<button class="shutterbug" data-dst="#dst">Snapshot</button>
|
20
|
+
<div id="dst"></div>
|
21
|
+
|
22
|
+
<div id="src2">
|
23
|
+
<iframe src="iframe4.html" width="400" height="300"></iframe>
|
24
|
+
</div>
|
25
|
+
<button class="shutterbug2" data-dst="#dst">Snapshot</button>
|
26
|
+
<div id="dst2"></div>
|
27
|
+
|
28
|
+
</body>
|
29
|
+
<script type="text/javascript">
|
30
|
+
$(document).ready(function(e) {
|
31
|
+
var bug = new Shutterbug("#src1", "#dst");
|
32
|
+
var bug2 = new Shutterbug("#src2", "#dst2", null, "plugh");
|
33
|
+
$("button.shutterbug").click(function(e) {
|
34
|
+
bug.getDomSnapshot();
|
35
|
+
});
|
36
|
+
$("button.shutterbug2").click(function(e) {
|
37
|
+
bug2.getDomSnapshot();
|
38
|
+
});
|
39
|
+
});
|
40
|
+
</script>
|
41
|
+
</html>
|
@@ -2,12 +2,14 @@
|
|
2
2
|
(function(){
|
3
3
|
var $ = window.$;
|
4
4
|
|
5
|
+
var MAX_TIMEOUT = 1500;
|
6
|
+
|
5
7
|
var getBaseUrl = function() {
|
6
8
|
var base = window.location.href;
|
7
9
|
return base;
|
8
10
|
};
|
9
11
|
|
10
|
-
var cloneDomItem =function(elem, elemTag) {
|
12
|
+
var cloneDomItem = function(elem, elemTag) {
|
11
13
|
var width = elem.width();
|
12
14
|
var height = elem.height();
|
13
15
|
var returnElm = $(elemTag);
|
@@ -20,89 +22,142 @@
|
|
20
22
|
return returnElm;
|
21
23
|
};
|
22
24
|
|
23
|
-
var
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
return img;
|
39
|
-
});
|
25
|
+
var generateIFrameSrcData = function (iframeDesc) {
|
26
|
+
return "data:text/html," +
|
27
|
+
"<!DOCTYPE html>" +
|
28
|
+
"<html>" +
|
29
|
+
"<head>" +
|
30
|
+
"<base href='" + iframeDesc.base_url + "'>" +
|
31
|
+
"<meta content='text/html;charset=utf-8' http-equiv='Content-Type'>" +
|
32
|
+
"<title>content from " + iframeDesc.base_url + "</title>" +
|
33
|
+
iframeDesc.css +
|
34
|
+
"</head>" +
|
35
|
+
"<body>" +
|
36
|
+
iframeDesc.content +
|
37
|
+
"</body>" +
|
38
|
+
"</html>";
|
39
|
+
};
|
40
40
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
41
|
+
var getHtmlFragment = function(callback) {
|
42
|
+
var self = this;
|
43
|
+
var $element = $(this.element);
|
44
|
+
// .find('iframe').addBack("iframe") handles two cases:
|
45
|
+
// - element itself is an iframe - .addBack('iframe')
|
46
|
+
// - element descentands are iframes - .find('iframe')
|
47
|
+
var $iframes = $element.find('iframe').addBack("iframe");
|
48
|
+
this._iframeContentRequests = [];
|
49
|
+
|
50
|
+
$iframes.each(function(i, iframeElem) {
|
51
|
+
var message = {
|
52
|
+
type: 'htmlFragRequest',
|
53
|
+
id: self.id,
|
54
|
+
iframeReqId: i,
|
55
|
+
// We have to provide smaller timeout while sending message to nested iframes.
|
56
|
+
// Otherwise, when one of the nested iframes timeouts, then all will do the
|
57
|
+
// same and we won't render anything - even iframes that support Shutterbug.
|
58
|
+
iframeReqTimeout: self.iframeReqTimeout * 0.6
|
59
|
+
};
|
60
|
+
iframeElem.contentWindow.postMessage(JSON.stringify(message), "*");
|
61
|
+
var requestDeffered = new $.Deferred();
|
62
|
+
self._iframeContentRequests[i] = requestDeffered;
|
63
|
+
setTimeout(function() {
|
64
|
+
// It handles a situation in which iframe doesn't support Shutterbug.
|
65
|
+
// When we doesn't receive answer for some time, assume that we can't
|
66
|
+
// render this particular iframe (provide null as iframe description).
|
67
|
+
if (requestDeffered.state() !== "resolved") {
|
68
|
+
requestDeffered.resolve(null);
|
69
|
+
}
|
70
|
+
}, self.iframeReqTimeout);
|
47
71
|
});
|
48
72
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
'
|
53
|
-
|
54
|
-
'
|
55
|
-
|
73
|
+
$.when.apply($, this._iframeContentRequests).done(function() {
|
74
|
+
// This function is called when we receive responses from all nested iframes.
|
75
|
+
// Nested iframes descriptions will be provided as arguments.
|
76
|
+
$element.trigger('shutterbug-saycheese');
|
77
|
+
|
78
|
+
var css = $('<div>').append($('link[rel="stylesheet"]').clone()).append($('style').clone()).html();
|
79
|
+
var width = $element.width();
|
80
|
+
var height = $element.height();
|
81
|
+
var element = $element.clone();
|
82
|
+
|
83
|
+
if (arguments.length > 0) {
|
84
|
+
var nestedIFrames = arguments;
|
85
|
+
// This supports two cases:
|
86
|
+
// - element itself is an iframe - .addBack('iframe')
|
87
|
+
// - element descentands are iframes - .find('iframe')
|
88
|
+
element.find("iframe").addBack("iframe").each(function(i, iframeElem) {
|
89
|
+
// When iframe doesn't support Shutterbug, request will timeout and null will be received.
|
90
|
+
// In such case just ignore this iframe, we won't be able to render it.
|
91
|
+
if (nestedIFrames[i] == null) return;
|
92
|
+
$(iframeElem).attr("src", generateIFrameSrcData(nestedIFrames[i]));
|
93
|
+
});
|
94
|
+
}
|
56
95
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
96
|
+
var replacementImgs = $element.find('canvas').map( function(count,elem) {
|
97
|
+
var dataUrl = elem.toDataURL('image/png');
|
98
|
+
var img = cloneDomItem($(elem),"<img>");
|
99
|
+
img.attr('src', dataUrl);
|
100
|
+
return img;
|
101
|
+
});
|
102
|
+
|
103
|
+
element.find('canvas').each(function(i,elm) {
|
104
|
+
var backgroundDiv = cloneDomItem($(elm),"<div>");
|
105
|
+
// Add a backing (background) dom element for BG canvas property
|
106
|
+
$(elm).replaceWith(replacementImgs[i]);
|
107
|
+
backgroundDiv.insertBefore($(elm));
|
108
|
+
});
|
109
|
+
|
110
|
+
element.css({
|
111
|
+
'top':0,
|
112
|
+
'left':0,
|
113
|
+
'margin':0,
|
114
|
+
'width':width,
|
115
|
+
'height':height
|
116
|
+
});
|
117
|
+
|
118
|
+
var html_content = {
|
119
|
+
content: $('<div>').append(element).html(),
|
120
|
+
css: css,
|
121
|
+
width: width,
|
122
|
+
height: height,
|
123
|
+
base_url: getBaseUrl()
|
124
|
+
};
|
64
125
|
|
65
|
-
|
126
|
+
$element.trigger('shutterbug-asyouwere');
|
66
127
|
|
67
|
-
|
128
|
+
callback(html_content);
|
129
|
+
});
|
68
130
|
};
|
69
131
|
|
70
|
-
var
|
71
|
-
|
72
|
-
if($(this.element)[0] && $(this.element)[0].contentWindow) {
|
73
|
-
this.requestHtmlFrag();
|
74
|
-
return;
|
75
|
-
}
|
76
|
-
else {
|
77
|
-
html = this.getHtmlFragment();
|
78
|
-
}
|
79
|
-
}
|
132
|
+
var getDomSnapshot = function() {
|
133
|
+
// Start timer.
|
80
134
|
var self = this;
|
81
135
|
var time = 0;
|
82
136
|
var counter = $("<span>");
|
83
137
|
counter.html(time);
|
84
|
-
|
85
138
|
$(self.imgDst).html("creating snapshot: ").append(counter);
|
86
139
|
var timer = setInterval(function(t) {
|
87
140
|
time = time + 1;
|
88
141
|
counter.html(time);
|
89
142
|
}, 1000);
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
self.callback
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
143
|
+
// Ask for HTML fragment and render it on server.
|
144
|
+
this.getHtmlFragment(function(html) {
|
145
|
+
$.ajax({
|
146
|
+
url: "CONVERT_PATH",
|
147
|
+
type: "POST",
|
148
|
+
data: html
|
149
|
+
}).success(function(msg) {
|
150
|
+
if(self.imgDst) {
|
151
|
+
$(self.imgDst).html(msg);
|
152
|
+
}
|
153
|
+
if (self.callback) {
|
154
|
+
self.callback(msg);
|
155
|
+
}
|
156
|
+
clearInterval(timer);
|
157
|
+
}).fail(function(e) {
|
158
|
+
$(self.imgDst).html("snapshot failed");
|
159
|
+
clearInterval(timer);
|
160
|
+
});
|
106
161
|
});
|
107
162
|
};
|
108
163
|
|
@@ -112,10 +167,10 @@
|
|
112
167
|
type: 'htmlFragRequest',
|
113
168
|
id: this.id
|
114
169
|
};
|
115
|
-
destination.postMessage(JSON.stringify(message),"*");
|
170
|
+
destination.postMessage(JSON.stringify(message), "*");
|
116
171
|
};
|
117
172
|
|
118
|
-
window.Shutterbug = function(selector,imgDst,callback,id,jQuery) {
|
173
|
+
window.Shutterbug = function(selector, imgDst, callback, id, jQuery) {
|
119
174
|
if (typeof(jQuery) != "undefined" && jQuery != null) {
|
120
175
|
$ = jQuery;
|
121
176
|
}
|
@@ -130,10 +185,10 @@
|
|
130
185
|
imgDst: imgDst,
|
131
186
|
callback: callback,
|
132
187
|
id: id,
|
133
|
-
getDomSnapshot:
|
134
|
-
getPng: getPng,
|
188
|
+
getDomSnapshot: getDomSnapshot,
|
135
189
|
getHtmlFragment: getHtmlFragment,
|
136
|
-
requestHtmlFrag: requestHtmlFrag
|
190
|
+
requestHtmlFrag: requestHtmlFrag,
|
191
|
+
iframeReqTimeout: MAX_TIMEOUT
|
137
192
|
};
|
138
193
|
|
139
194
|
var handleMessage = function(message, signature, func) {
|
@@ -152,22 +207,27 @@
|
|
152
207
|
|
153
208
|
var htmlFragRequestListen = function(message) {
|
154
209
|
var send_response = function(data) {
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
210
|
+
// Update timeout. When we receive a request from parent,
|
211
|
+
// we have to finish nested iframes rendering in that time.
|
212
|
+
// Otherwise parent rendering will timeout.
|
213
|
+
shutterbugInstance.iframeReqTimeout = data.iframeReqTimeout;
|
214
|
+
shutterbugInstance.getHtmlFragment(function(html) {
|
215
|
+
var response = {
|
216
|
+
type: 'htmlFragResponse',
|
217
|
+
value: html,
|
218
|
+
iframeReqId: data.iframeReqId,
|
219
|
+
id: data.id // return to sender only...
|
220
|
+
};
|
221
|
+
message.source.postMessage(JSON.stringify(response), "*");
|
222
|
+
});
|
161
223
|
};
|
162
224
|
handleMessage(message, 'htmlFragRequest', send_response);
|
163
225
|
};
|
164
226
|
|
165
227
|
var htmlFragResponseListen = function(message) {
|
166
228
|
var send_response = function(data) {
|
167
|
-
|
168
|
-
|
169
|
-
html = data.value;
|
170
|
-
shutterbugInstance.getPng(data.value);
|
229
|
+
if (data.id === shutterbugInstance.id) {
|
230
|
+
shutterbugInstance._iframeContentRequests[data.iframeReqId].resolve(data.value);
|
171
231
|
}
|
172
232
|
};
|
173
233
|
handleMessage(message, 'htmlFragResponse', send_response);
|
@@ -179,4 +239,4 @@
|
|
179
239
|
});
|
180
240
|
return shutterbugInstance;
|
181
241
|
};
|
182
|
-
})();
|
242
|
+
})();
|
data/lib/shutterbug.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: shutterbug
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2014-02-12 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -175,12 +175,17 @@ files:
|
|
175
175
|
- LICENSE.md
|
176
176
|
- README.md
|
177
177
|
- Rakefile
|
178
|
+
- bower.json
|
178
179
|
- config.ru
|
179
180
|
- demo/iframe.html
|
180
181
|
- demo/iframe2.html
|
182
|
+
- demo/iframe3.html
|
183
|
+
- demo/iframe4.html
|
181
184
|
- demo/iframe_example.html
|
185
|
+
- demo/iframe_no_shutterbug.html
|
182
186
|
- demo/index.html
|
183
187
|
- demo/main.css
|
188
|
+
- demo/nested_iframe_example.html
|
184
189
|
- demo/oil-and-water/heatbath.svg
|
185
190
|
- demo/oil-and-water/ke-gradient.svg
|
186
191
|
- demo/oil-and-water/oil-and-water.svg
|
@@ -234,7 +239,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
234
239
|
version: '0'
|
235
240
|
segments:
|
236
241
|
- 0
|
237
|
-
hash:
|
242
|
+
hash: -737520193773818108
|
238
243
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
239
244
|
none: false
|
240
245
|
requirements:
|
@@ -243,7 +248,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
243
248
|
version: '0'
|
244
249
|
segments:
|
245
250
|
- 0
|
246
|
-
hash:
|
251
|
+
hash: -737520193773818108
|
247
252
|
requirements: []
|
248
253
|
rubyforge_project:
|
249
254
|
rubygems_version: 1.8.25
|