shutterbug 0.1.2 → 0.2.0
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/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
|