foundation.interchange.js 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. ;(function ($, window, document, undefined) {
  2. 'use strict';
  3. Foundation.libs.interchange = {
  4. name : 'interchange',
  5. version : '5.5.3',
  6. cache : {},
  7. images_loaded : false,
  8. nodes_loaded : false,
  9. settings : {
  10. load_attr : 'interchange',
  11. named_queries : {
  12. 'default' : 'only screen',
  13. 'small' : Foundation.media_queries['small'],
  14. 'small-only' : Foundation.media_queries['small-only'],
  15. 'medium' : Foundation.media_queries['medium'],
  16. 'medium-only' : Foundation.media_queries['medium-only'],
  17. 'large' : Foundation.media_queries['large'],
  18. 'large-only' : Foundation.media_queries['large-only'],
  19. 'xlarge' : Foundation.media_queries['xlarge'],
  20. 'xlarge-only' : Foundation.media_queries['xlarge-only'],
  21. 'xxlarge' : Foundation.media_queries['xxlarge'],
  22. 'landscape' : 'only screen and (orientation: landscape)',
  23. 'portrait' : 'only screen and (orientation: portrait)',
  24. 'retina' : 'only screen and (-webkit-min-device-pixel-ratio: 2),' +
  25. 'only screen and (min--moz-device-pixel-ratio: 2),' +
  26. 'only screen and (-o-min-device-pixel-ratio: 2/1),' +
  27. 'only screen and (min-device-pixel-ratio: 2),' +
  28. 'only screen and (min-resolution: 192dpi),' +
  29. 'only screen and (min-resolution: 2dppx)'
  30. },
  31. directives : {
  32. replace : function (el, path, trigger) {
  33. // The trigger argument, if called within the directive, fires
  34. // an event named after the directive on the element, passing
  35. // any parameters along to the event that you pass to trigger.
  36. //
  37. // ex. trigger(), trigger([a, b, c]), or trigger(a, b, c)
  38. //
  39. // This allows you to bind a callback like so:
  40. // $('#interchangeContainer').on('replace', function (e, a, b, c) {
  41. // console.log($(this).html(), a, b, c);
  42. // });
  43. if (el !== null && /IMG/.test(el[0].nodeName)) {
  44. var orig_path = $.each(el, function(){this.src = path;});
  45. // var orig_path = el[0].src;
  46. if (new RegExp(path, 'i').test(orig_path)) {
  47. return;
  48. }
  49. el.attr("src", path);
  50. return trigger(el[0].src);
  51. }
  52. var last_path = el.data(this.data_attr + '-last-path'),
  53. self = this;
  54. if (last_path == path) {
  55. return;
  56. }
  57. if (/\.(gif|jpg|jpeg|tiff|png)([?#].*)?/i.test(path)) {
  58. $(el).css('background-image', 'url(' + path + ')');
  59. el.data('interchange-last-path', path);
  60. return trigger(path);
  61. }
  62. return $.get(path, function (response) {
  63. el.html(response);
  64. el.data(self.data_attr + '-last-path', path);
  65. trigger();
  66. });
  67. }
  68. }
  69. },
  70. init : function (scope, method, options) {
  71. Foundation.inherit(this, 'throttle random_str');
  72. this.data_attr = this.set_data_attr();
  73. $.extend(true, this.settings, method, options);
  74. this.bindings(method, options);
  75. this.reflow();
  76. },
  77. get_media_hash : function () {
  78. var mediaHash = '';
  79. for (var queryName in this.settings.named_queries ) {
  80. mediaHash += matchMedia(this.settings.named_queries[queryName]).matches.toString();
  81. }
  82. return mediaHash;
  83. },
  84. events : function () {
  85. var self = this, prevMediaHash;
  86. $(window)
  87. .off('.interchange')
  88. .on('resize.fndtn.interchange', self.throttle(function () {
  89. var currMediaHash = self.get_media_hash();
  90. if (currMediaHash !== prevMediaHash) {
  91. self.resize();
  92. }
  93. prevMediaHash = currMediaHash;
  94. }, 50));
  95. return this;
  96. },
  97. resize : function () {
  98. var cache = this.cache;
  99. if (!this.images_loaded || !this.nodes_loaded) {
  100. setTimeout($.proxy(this.resize, this), 50);
  101. return;
  102. }
  103. for (var uuid in cache) {
  104. if (cache.hasOwnProperty(uuid)) {
  105. var passed = this.results(uuid, cache[uuid]);
  106. if (passed) {
  107. this.settings.directives[passed
  108. .scenario[1]].call(this, passed.el, passed.scenario[0], (function (passed) {
  109. if (arguments[0] instanceof Array) {
  110. var args = arguments[0];
  111. } else {
  112. var args = Array.prototype.slice.call(arguments, 0);
  113. }
  114. return function() {
  115. passed.el.trigger(passed.scenario[1], args);
  116. }
  117. }(passed)));
  118. }
  119. }
  120. }
  121. },
  122. results : function (uuid, scenarios) {
  123. var count = scenarios.length;
  124. if (count > 0) {
  125. var el = this.S('[' + this.add_namespace('data-uuid') + '="' + uuid + '"]');
  126. while (count--) {
  127. var mq, rule = scenarios[count][2];
  128. if (this.settings.named_queries.hasOwnProperty(rule)) {
  129. mq = matchMedia(this.settings.named_queries[rule]);
  130. } else {
  131. mq = matchMedia(rule);
  132. }
  133. if (mq.matches) {
  134. return {el : el, scenario : scenarios[count]};
  135. }
  136. }
  137. }
  138. return false;
  139. },
  140. load : function (type, force_update) {
  141. if (typeof this['cached_' + type] === 'undefined' || force_update) {
  142. this['update_' + type]();
  143. }
  144. return this['cached_' + type];
  145. },
  146. update_images : function () {
  147. var images = this.S('img[' + this.data_attr + ']'),
  148. count = images.length,
  149. i = count,
  150. loaded_count = 0,
  151. data_attr = this.data_attr;
  152. this.cache = {};
  153. this.cached_images = [];
  154. this.images_loaded = (count === 0);
  155. while (i--) {
  156. loaded_count++;
  157. if (images[i]) {
  158. var str = images[i].getAttribute(data_attr) || '';
  159. if (str.length > 0) {
  160. this.cached_images.push(images[i]);
  161. }
  162. }
  163. if (loaded_count === count) {
  164. this.images_loaded = true;
  165. this.enhance('images');
  166. }
  167. }
  168. return this;
  169. },
  170. update_nodes : function () {
  171. var nodes = this.S('[' + this.data_attr + ']').not('img'),
  172. count = nodes.length,
  173. i = count,
  174. loaded_count = 0,
  175. data_attr = this.data_attr;
  176. this.cached_nodes = [];
  177. this.nodes_loaded = (count === 0);
  178. while (i--) {
  179. loaded_count++;
  180. var str = nodes[i].getAttribute(data_attr) || '';
  181. if (str.length > 0) {
  182. this.cached_nodes.push(nodes[i]);
  183. }
  184. if (loaded_count === count) {
  185. this.nodes_loaded = true;
  186. this.enhance('nodes');
  187. }
  188. }
  189. return this;
  190. },
  191. enhance : function (type) {
  192. var i = this['cached_' + type].length;
  193. while (i--) {
  194. this.object($(this['cached_' + type][i]));
  195. }
  196. return $(window).trigger('resize.fndtn.interchange');
  197. },
  198. convert_directive : function (directive) {
  199. var trimmed = this.trim(directive);
  200. if (trimmed.length > 0) {
  201. return trimmed;
  202. }
  203. return 'replace';
  204. },
  205. parse_scenario : function (scenario) {
  206. // This logic had to be made more complex since some users were using commas in the url path
  207. // So we cannot simply just split on a comma
  208. var directive_match = scenario[0].match(/(.+),\s*(\w+)\s*$/),
  209. // getting the mq has gotten a bit complicated since we started accounting for several use cases
  210. // of URLs. For now we'll continue to match these scenarios, but we may consider having these scenarios
  211. // as nested objects or arrays in F6.
  212. // regex: match everything before close parenthesis for mq
  213. media_query = scenario[1].match(/(.*)\)/);
  214. if (directive_match) {
  215. var path = directive_match[1],
  216. directive = directive_match[2];
  217. } else {
  218. var cached_split = scenario[0].split(/,\s*$/),
  219. path = cached_split[0],
  220. directive = '';
  221. }
  222. return [this.trim(path), this.convert_directive(directive), this.trim(media_query[1])];
  223. },
  224. object : function (el) {
  225. var raw_arr = this.parse_data_attr(el),
  226. scenarios = [],
  227. i = raw_arr.length;
  228. if (i > 0) {
  229. while (i--) {
  230. // split array between comma delimited content and mq
  231. // regex: comma, optional space, open parenthesis
  232. var scenario = raw_arr[i].split(/,\s?\(/);
  233. if (scenario.length > 1) {
  234. var params = this.parse_scenario(scenario);
  235. scenarios.push(params);
  236. }
  237. }
  238. }
  239. return this.store(el, scenarios);
  240. },
  241. store : function (el, scenarios) {
  242. var uuid = this.random_str(),
  243. current_uuid = el.data(this.add_namespace('uuid', true));
  244. if (this.cache[current_uuid]) {
  245. return this.cache[current_uuid];
  246. }
  247. el.attr(this.add_namespace('data-uuid'), uuid);
  248. return this.cache[uuid] = scenarios;
  249. },
  250. trim : function (str) {
  251. if (typeof str === 'string') {
  252. return $.trim(str);
  253. }
  254. return str;
  255. },
  256. set_data_attr : function (init) {
  257. if (init) {
  258. if (this.namespace.length > 0) {
  259. return this.namespace + '-' + this.settings.load_attr;
  260. }
  261. return this.settings.load_attr;
  262. }
  263. if (this.namespace.length > 0) {
  264. return 'data-' + this.namespace + '-' + this.settings.load_attr;
  265. }
  266. return 'data-' + this.settings.load_attr;
  267. },
  268. parse_data_attr : function (el) {
  269. var raw = el.attr(this.attr_name()).split(/\[(.*?)\]/),
  270. i = raw.length,
  271. output = [];
  272. while (i--) {
  273. if (raw[i].replace(/[\W\d]+/, '').length > 4) {
  274. output.push(raw[i]);
  275. }
  276. }
  277. return output;
  278. },
  279. reflow : function () {
  280. this.load('images', true);
  281. this.load('nodes', true);
  282. }
  283. };
  284. }(jQuery, window, window.document));