master
1/*! Embedly jQuery - v3.1.1 - 2013-06-05
2 * https://github.com/embedly/embedly-jquery
3 * Copyright (c) 2013 Sean Creeley
4 * Licensed BSD
5 */
6(function($) {
7
8 /*
9 * Util Functions
10 */
11
12 // Defaults for Embedly.
13 var defaults = {
14 key: null,
15 endpoint: 'oembed', // default endpoint is oembed (preview and objectify available too)
16 secure: null, // use https endpoint vs http
17 query: {},
18 method: 'replace', // embed handling option for standard callback
19 addImageStyles: true, // add style="" attribute to images for query.maxwidth and query.maxhidth
20 wrapElement: 'div', // standard wrapper around all returned embeds
21 className: 'embed', // class on the wrapper element
22 batch: 20, // Default Batch Size.
23 urlRe: null
24 };
25
26 var urlRe = /(http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/;
27
28 var none = function(obj){
29 return obj === null || obj === undefined;
30 };
31 // Split a list into a bunch of batchs.
32 var batch = function(list, split){
33 var batches = [], current = [];
34 $.each(list, function(i, obj){
35 current.push(obj);
36 if (current.length === split){
37 batches.push(current);
38 current = [];
39 }
40 });
41 if (current.length !== 0){
42 batches.push(current);
43 }
44 return batches;
45 };
46 // Make an argument a list
47 var listify = function(obj){
48 if (none(obj)){
49 return [];
50 } else if (!$.isArray(obj)){
51 return [obj];
52 }
53 return obj;
54 };
55
56 // From: http://bit.ly/T9SjVv
57 var zip = function(arrays) {
58 return $.map(arrays[0], function(_,i){
59 return [$.map(arrays, function(array){return array[i];})];
60 });
61 };
62
63 /* Keeper
64 *
65 * alittle wrapper around Deferred that lets us keep track of
66 * all the callbacks that we have.
67 */
68 var Keeper = function (len, each, after) {
69 this.init(len, each, after);
70 };
71 Keeper.prototype = {
72
73 init: function(urls){
74 this.urls = urls;
75 this.count = 0;
76 this.results = {};
77 this._deferred = $.Deferred();
78 },
79 // Only 2 methods we really care about.
80 notify : function(result) {
81 // Store the result.
82 this.results[result.original_url] = result;
83 // Increase the count.
84 this.count++;
85 // Notify the success functions
86 this._deferred.notify.apply(this._deferred, [result]);
87 // If all the callbacks have completed, do your thing.
88 if (this.count === this.urls.length){
89 // This sorts the results in the manner in which they were added.
90 var self = this;
91 var results = $.map(this.urls, function(url){ return self.results[url];});
92 this._deferred.resolve(results);
93 }
94 return this;
95 },
96 state: function() {
97 return this._deferred.state.apply(this._deferred, arguments);
98 }
99 };
100 window.Keeper = Keeper;
101
102 // direct API for dealing with the
103 var API = function () {};
104 API.prototype = {
105 /*
106 For dealing directly with Embedly's API.
107
108 options: {
109 key: 'Your API key'
110 secure: false,
111 query: {
112 maxwidth: 500,
113 colors: true,
114 }
115 }
116 */
117 defaults: {},
118
119 log: function(level, message){
120 if (!none(window.console) && !none(window.console[level])){
121 window.console[level].apply(window.console, [message]);
122 }
123 },
124 // Based on the method and options, build the url,
125 build: function(method, urls, options){
126 // Technically, not great.
127 options = none(options) ? {}: options;
128 // Base method.
129
130 var secure = options.secure;
131 if (none(secure)){
132 // If the secure param was not see, use the protocol instead.
133 secure = window.location.protocol === 'https:'? true:false;
134 }
135
136 var base = (secure ? 'https': 'http') +
137 '://api.embed.ly/' + (method === 'objectify' ? '2/' : '1/') + method;
138
139 // Base Query;
140 var query = none(options.query) ? {} : options.query;
141 query.key = options.key;
142 base += '?'+$.param(query);
143
144 // Add the urls the way we like.
145 base += '&urls='+ $.map(urls, encodeURIComponent).join(',');
146
147 return base;
148 },
149 // Batch a bunch of URLS up for processing. Will split longer lists out
150 // into many batches and return the callback on each and after on done.
151 ajax: function(method, urls, options){
152
153 // Use the defaults.
154 options = $.extend({}, defaults, $.embedly.defaults, typeof options === 'object' && options);
155
156 if (none(options.key)){
157 this.log('error', 'Embedly jQuery requires an API Key. Please sign up for one at http://embed.ly');
158 return null;
159 }
160
161 // Everything is dealt with in lists.
162 urls = listify(urls);
163
164 // add a keeper that holds everything till we are good to go.
165 var keeper = new Keeper(urls);
166
167 var valid_urls = [], rejects = [], valid;
168 // Debunk the invalid urls right now.
169 $.each(urls, function(i, url){
170 valid = false;
171 // Make sure it's a URL
172 if (urlRe.test(url)){
173 valid = true;
174 // If the urlRe has been defined make sure it works.
175 if (options.urlRe !== null && options.urlRe.test && !options.urlRe.test(url)){
176 valid = false;
177 }
178 }
179 // deal with the valid urls
180 if(valid === true){
181 valid_urls.push(url);
182 } else {
183 // Notify the keeper that we have a bad url.
184 rejects.push({
185 url: url,
186 original_url: url,
187 error: true,
188 invalid: true,
189 type: 'error',
190 error_message: 'Invalid URL "'+ url+'"'
191 });
192 }
193 });
194
195 // Put everything into batches, even if these is only one.
196 var batches = batch(valid_urls, options.batch), self = this;
197
198 // Actually make those calls.
199 $.each(batches, function(i, batch){
200 $.ajax({
201 url: self.build(method, batch, options),
202 dataType: 'jsonp',
203 success: function(data){
204 // We zip together the urls and the data so we have the original_url
205 $.each(zip([batch, data]), function(i, obj){
206 var result = obj[1];
207 result.original_url = obj[0];
208 result.invalid = false;
209 keeper.notify(result);
210 });
211 }
212 });
213 });
214
215 if (rejects.length){
216 // set a short timeout so we can set up progress and done, otherwise
217 // the progress notifier will not get all the events.
218 setTimeout(function(){
219 $.each(rejects, function(i, reject){
220 keeper.notify(reject);
221 });
222 }, 1);
223 }
224
225 return keeper._deferred;
226 },
227
228 // Wrappers around ajax.
229 oembed: function(urls, options){
230 return this.ajax('oembed', urls, options);
231 },
232 preview: function(urls, options){
233 return this.ajax('preview', urls, options);
234 },
235 objectify: function(urls, options){
236 return this.ajax('objectify', urls, options);
237 },
238 extract: function(urls, options){
239 return this.ajax('extract', urls, options);
240 }
241 };
242
243 // direct API dealing directly with Embedly's Display API.
244 var ImageAPI = function () {};
245 ImageAPI.prototype = {
246
247 // Based on the method and options, build the image url,
248 build: function(method, url, options){
249 options = $.extend({}, $.embedly.defaults, typeof options === 'object' && options);
250
251 var secure = options.secure;
252 if (none(secure)){
253 // If the secure param was not seen, use the protocol instead.
254 secure = window.location.protocol === 'https:'? true:false;
255 }
256
257 var base = (secure ? 'https': 'http') +
258 '://i.embed.ly/' + (method === 'display' ? '1/' : '1/display/') + method;
259
260 // Base Query
261 var query = none(options.query) ? {} : options.query;
262 query.key = options.key;
263 base += '?'+$.param(query);
264
265 // Add the image url
266 base += '&url='+ encodeURIComponent(url);
267
268 return base;
269 },
270 // Wrappers around build image url function.
271 display: function(url, options){
272 return this.build('display', url, options);
273 },
274 resize: function(url, options){
275 return this.build('resize', url, options);
276 },
277 fill: function(url, options){
278 return this.build('fill', url, options);
279 },
280 crop: function(url, options){
281 return this.build('crop', url, options);
282 }
283 };
284
285 var Embedly = function (element, url, options) {
286 this.init(element, url, options);
287 };
288
289 Embedly.prototype = {
290 init: function(elem, original_url, options){
291 this.elem = elem;
292 this.$elem = $(elem);
293 this.original_url = original_url;
294 this.options = options;
295 this.loaded = $.Deferred();
296
297 // Sets up some triggers.
298 var self = this;
299 this.loaded.done(function(){
300 self.$elem.trigger('loaded', [self]);
301 });
302
303 // So you can listen when the tag has been initialized;
304 this.$elem.trigger('initialized', [this]);
305 },
306 progress: function(obj){
307 $.extend(this, obj);
308
309 // if there is a custom display method, use it.
310 if (this.options.display){
311 this.options.display.apply(this.elem, [this, this.elem]);
312 }
313 // We only have a simple case for oEmbed. Everything else should be a custom
314 // success method.
315 else if(this.options.endpoint === 'oembed'){
316 this.display();
317 }
318
319 // Notifies all listeners that the data has been loaded.
320 this.loaded.resolve(this);
321 },
322 imageStyle: function(){
323 var style = [], units;
324 if (this.options.addImageStyles) {
325 if (this.options.query.maxwidth) {
326 units = isNaN(parseInt(this.options.query.maxwidth, 10)) ? '' : 'px';
327 style.push("max-width: " + (this.options.query.maxwidth)+units);
328 }
329 if (this.options.query.maxheight) {
330 units = isNaN(parseInt(this.options.query.maxheight,10)) ? '' : 'px';
331 style.push("max-height: " + (this.options.query.maxheight)+units);
332 }
333 }
334 return style.join(';');
335 },
336
337 display: function(){
338 // Ignore errors
339 if (this.type === 'error'){
340 return false;
341 }
342
343 // Image Style.
344 this.style = this.imageStyle();
345
346 var html;
347 if (this.type === 'photo'){
348 html = "<a href='" + this.original_url + "' target='_blank'>";
349 html += "<img style='" + this.style + "' src='" + this.url + "' alt='" + this.title + "' /></a>";
350 } else if (this.type === 'video' || this.type === 'rich'){
351 html = this.html;
352 } else {
353 this.title = this.title || this.url;
354 html = this.thumbnail_url ? "<img src='" + this.thumbnail_url + "' class='thumb' style='" + this.style + "'/>" : "";
355 html += "<a href='" + this.original_url + "'>" + this.title + "</a>";
356 html += this.provider_name ? "<a href='" + this.provider_url + "' class='provider'>" + this.provider_name + "</a>" : "";
357 html += this.description ? '<div class="description">' + this.description + '</div>' : '';
358 }
359
360 if (this.options.wrapElement) {
361 html = '<' + this.options.wrapElement+ ' class="' + this.options.className + '">' + html + '</' + this.options.wrapElement + '>';
362 }
363
364 this.code = html;
365 // Yay.
366 if (this.options.method === 'replace'){
367 this.$elem.replaceWith(this.code);
368 } else if (this.options.method === 'after'){
369 this.$elem.after(this.code);
370 } else if (this.options.method === 'afterParent'){
371 this.$elem.parent().after(this.code);
372 } else if (this.options.method === 'replaceParent'){
373 this.$elem.parent().replaceWith(this.code);
374 }
375 // for DOM elements we add the oembed object as a data field to that element and trigger a custom event called oembed
376 // with the custom event, developers can do any number of custom interactions with the data that is returned.
377 this.$elem.trigger('displayed', [this]);
378 }
379 };
380
381 // Sets up a generic API for use.
382 $.embedly = new API();
383
384 // Add display to it.
385 $.embedly.display = new ImageAPI();
386
387 $.fn.embedly = function ( options ) {
388 if (options === undefined || typeof options === 'object') {
389
390 // Use the defaults
391 options = $.extend({}, defaults, $.embedly.defaults, typeof options === 'object' && options);
392
393 // Kill these early.
394 if (none(options.key)){
395 $.embedly.log('error', 'Embedly jQuery requires an API Key. Please sign up for one at http://embed.ly');
396 return this.each($.noop);
397 }
398 // Keep track of the nodes we are working on so we can add them to the
399 // progress events.
400 var nodes = {};
401
402 // Create the node.
403 var create = function (elem){
404 if (!$.data($(elem), 'embedly')) {
405 var url = $(elem).attr('href');
406
407 var node = new Embedly(elem, url, options);
408 $.data(elem, 'embedly', node);
409
410 if (nodes.hasOwnProperty(url)){
411 nodes[url].push(node);
412 } else {
413 nodes[url] = [node];
414 }
415 }
416 };
417
418 // Find everything with a URL on it.
419 var elems = this.each(function () {
420 if ( !none($(this).attr('href')) ){
421 create(this);
422 } else {
423 $(this).find('a').each(function(){
424 if ( ! none($(this).attr('href')) ){
425 create(this);
426 }
427 });
428 }
429 });
430
431 // set up the api call.
432 var deferred = $.embedly.ajax(options.endpoint,
433 $.map(nodes, function(value, key) {return key;}),
434 options)
435 .progress(function(obj){
436 $.each(nodes[obj.original_url], function(i, node){
437 node.progress(obj);
438 });
439 });
440
441 if (options.progress){
442 deferred.progress(options.progress);
443 }
444 if (options.done){
445 deferred.done(options.done);
446 }
447 return elems;
448 }
449 };
450
451 // Custom selector.
452 $.expr[':'].embedly = function(elem) {
453 return ! none($(elem).data('embedly'));
454 };
455
456 // Use with selector to find img tags with data-src attribute
457 // e.g. <img data-src="http://embed.ly/static/images/logo.png"></img>
458 $.fn.display = function (endpoint, options) {
459
460 // default to display
461 if (none(endpoint)) {
462 endpoint = 'display';
463 }
464
465 if (options === undefined || typeof options === 'object') {
466
467 // Use the defaults
468 options = $.extend({}, defaults, $.embedly.defaults, typeof options === 'object' && options);
469
470 // Key Check.
471 if (none(options.key)){
472 $.embedly.log('error', 'Embedly jQuery requires an API Key. Please sign up for one at http://embed.ly/display');
473 return this.each($.noop);
474 }
475
476 // Create the node for all elements
477 var create = function (elem){
478 var $elem = $(elem);
479 if (!$elem.data('display')) {
480 var url = $elem.data('src') || $elem.attr('href');
481
482 var data = {
483 original_url : url,
484 url : $.embedly.display.build(endpoint, url, options)
485 };
486
487 $elem.data('display', data);
488 $elem.trigger('initialized', [elem]);
489
490 var html = "<img src='" + data.url + "' />";
491 if ($elem.is('a')){
492 $elem.append(html);
493 }else {
494 $elem.replaceWith(html);
495 }
496 }
497 };
498 var doCreate = function(elem){
499 if (none($(elem).data('src')) && none($(elem).attr('href'))){
500 return false;
501 }
502 return true;
503 };
504
505 // Find every image or a tag with a data-src attribute
506 var elems = this.each(function () {
507 if ( doCreate(this) ){
508 create(this);
509 } else {
510 $(this).find('img,a').each(function(){
511 if ( doCreate(this) ){
512 create(this);
513 }
514 });
515 }
516 });
517
518 return elems;
519 }
520 };
521
522 // Custom selector.
523 $.expr[':'].display = function(elem) {
524 return ! none($(elem).data('display'));
525 };
526
527}(jQuery, window));