infinite.js 36 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162
  1. ;(function() {
  2. var util = {}, events = {}, base = {}, plugins_infinite = {};
  3. util = function (exports) {
  4. var SUBSTITUTE_REG = /\\?\{([^{}]+)\}/g, EMPTY = '';
  5. var RE_TRIM = /^[\s\xa0]+|[\s\xa0]+$/g, trim = String.prototype.trim;
  6. var _trim = trim ? function (str) {
  7. return str == null ? EMPTY : trim.call(str);
  8. } : function (str) {
  9. return str == null ? EMPTY : (str + '').replace(RE_TRIM, EMPTY);
  10. };
  11. function upperCase() {
  12. return arguments[1].toUpperCase();
  13. }
  14. function Empty() {
  15. }
  16. function createObject(proto, constructor) {
  17. var newProto;
  18. if (Object.create) {
  19. newProto = Object.create(proto);
  20. } else {
  21. Empty.prototype = proto;
  22. newProto = new Empty();
  23. }
  24. newProto.constructor = constructor;
  25. return newProto;
  26. }
  27. function getNodes(node, rootNode) {
  28. if (!node)
  29. return;
  30. if (node.nodeType)
  31. return [node];
  32. var rootNode = rootNode && rootNode.nodeType ? rootNode : document;
  33. if (node && typeof node === 'string') {
  34. return rootNode.querySelectorAll(node);
  35. }
  36. return;
  37. }
  38. // Useful for temporary DOM ids.
  39. var idCounter = 0;
  40. var getOffsetTop = function (el) {
  41. var offset = el.offsetTop;
  42. if (el.offsetParent != null)
  43. offset += getOffsetTop(el.offsetParent);
  44. return offset;
  45. };
  46. var getOffsetLeft = function (el) {
  47. var offset = el.offsetLeft;
  48. if (el.offsetParent != null)
  49. offset += getOffsetLeft(el.offsetParent);
  50. return offset;
  51. };
  52. var Util = {
  53. // Is a given variable an object?
  54. isObject: function (obj) {
  55. return obj === Object(obj);
  56. },
  57. isArray: Array.isArray || function (obj) {
  58. return toString.call(obj) == '[object Array]';
  59. },
  60. // Is a given array, string, or object empty?
  61. // An "empty" object has no enumerable own-properties.
  62. isEmpty: function (obj) {
  63. if (obj == null)
  64. return true;
  65. if (this.isArray(obj) || this.isString(obj))
  66. return obj.length === 0;
  67. for (var key in obj)
  68. if (this.has(obj, key))
  69. return false;
  70. return true;
  71. },
  72. mix: function (to, from, deep) {
  73. for (var i in from) {
  74. to[i] = from[i];
  75. }
  76. return to;
  77. },
  78. extend: function (r, s, px, sx) {
  79. if (!s || !r) {
  80. return r;
  81. }
  82. var sp = s.prototype, rp;
  83. // add prototype chain
  84. rp = createObject(sp, r);
  85. r.prototype = this.mix(rp, r.prototype);
  86. r.superclass = createObject(sp, s);
  87. // add prototype overrides
  88. if (px) {
  89. this.mix(rp, px);
  90. }
  91. // add object overrides
  92. if (sx) {
  93. this.mix(r, sx);
  94. }
  95. return r;
  96. },
  97. /**
  98. * test whether a string start with a specified substring
  99. * @param {String} str the whole string
  100. * @param {String} prefix a specified substring
  101. * @return {Boolean} whether str start with prefix
  102. * @member util
  103. */
  104. startsWith: function (str, prefix) {
  105. return str.lastIndexOf(prefix, 0) === 0;
  106. },
  107. /**
  108. * test whether a string end with a specified substring
  109. * @param {String} str the whole string
  110. * @param {String} suffix a specified substring
  111. * @return {Boolean} whether str end with suffix
  112. * @member util
  113. */
  114. endsWith: function (str, suffix) {
  115. var ind = str.length - suffix.length;
  116. return ind >= 0 && str.indexOf(suffix, ind) === ind;
  117. },
  118. /**
  119. * Removes the whitespace from the beginning and end of a string.
  120. * @method
  121. * @member util
  122. */
  123. trim: _trim,
  124. /**
  125. * Substitutes keywords in a string using an object/array.
  126. * Removes undef keywords and ignores escaped keywords.
  127. * @param {String} str template string
  128. * @param {Object} o json data
  129. * @member util
  130. * @param {RegExp} [regexp] to match a piece of template string
  131. */
  132. substitute: function (str, o, regexp) {
  133. if (typeof str !== 'string' || !o) {
  134. return str;
  135. }
  136. return str.replace(regexp || SUBSTITUTE_REG, function (match, name) {
  137. if (match.charAt(0) === '\\') {
  138. return match.slice(1);
  139. }
  140. return o[name] === undefined ? EMPTY : o[name];
  141. });
  142. },
  143. /**
  144. * vendors
  145. * @return { String } webkit|moz|ms|o
  146. * @memberOf Util
  147. */
  148. vendor: function () {
  149. var el = document.createElement('div').style;
  150. var vendors = [
  151. 't',
  152. 'webkitT',
  153. 'MozT',
  154. 'msT',
  155. 'OT'
  156. ], transform, i = 0, l = vendors.length;
  157. for (; i < l; i++) {
  158. transform = vendors[i] + 'ransform';
  159. if (transform in el)
  160. return vendors[i].substr(0, vendors[i].length - 1);
  161. }
  162. return false;
  163. }(),
  164. /**
  165. * add vendor to attribute
  166. * @memberOf Util
  167. * @param {String} attrName name of attribute
  168. * @return { String }
  169. **/
  170. prefixStyle: function (attrName) {
  171. if (this.vendor === false)
  172. return false;
  173. if (this.vendor === '')
  174. return attrName;
  175. return this.vendor + attrName.charAt(0).toUpperCase() + attrName.substr(1);
  176. },
  177. /**
  178. * judge if has class
  179. * @memberOf Util
  180. * @param {HTMLElement} el
  181. * @param {String} className
  182. * @return {Boolean}
  183. */
  184. hasClass: function (el, className) {
  185. return el && el.className && className && el.className.indexOf(className) != -1;
  186. },
  187. /**
  188. * add className for the element
  189. * @memberOf Util
  190. * @param {HTMLElement} el
  191. * @param {String} className
  192. */
  193. addClass: function (el, className) {
  194. if (el && className && !this.hasClass(el, className)) {
  195. el.className += ' ' + className;
  196. }
  197. },
  198. /**
  199. * remove className for the element
  200. * @memberOf Util
  201. * @param {HTMLElement} el
  202. * @param {String} className
  203. */
  204. removeClass: function (el, className) {
  205. if (el && el.className && className) {
  206. el.className = el.className.replace(className, '');
  207. }
  208. },
  209. /**
  210. * remove an element
  211. * @memberOf Util
  212. * @param {HTMLElement} el
  213. */
  214. remove: function (el) {
  215. if (!el || !el.parentNode)
  216. return;
  217. el.parentNode.removeChild(el);
  218. },
  219. /**
  220. * get offset top
  221. * @memberOf Util
  222. * @param {HTMLElement} el
  223. * @return {Number} offsetTop
  224. */
  225. getOffsetTop: getOffsetTop,
  226. /**
  227. * get offset left
  228. * @memberOf Util
  229. * @param {HTMLElement} el
  230. * @return {Number} offsetLeft
  231. */
  232. getOffsetLeft: getOffsetLeft,
  233. /**
  234. * get offset left
  235. * @memberOf Util
  236. * @param {HTMLElement} el
  237. * @param {String} selector
  238. * @param {HTMLElement} rootNode
  239. * @return {HTMLElement} parent element
  240. */
  241. findParentEl: function (el, selector, rootNode) {
  242. var rs = null, parent = null;
  243. var type = /^#/.test(selector) ? 'id' : /^\./.test(selector) ? 'class' : 'tag';
  244. var sel = selector.replace(/\.|#/g, '');
  245. if (rootNode && typeof rootNode === 'string') {
  246. rootNode = document.querySelector(rootNode);
  247. }
  248. rootNode = rootNode || document.body;
  249. if (!el || !selector)
  250. return;
  251. if (type == 'class' && el.className && el.className.match(sel)) {
  252. return el;
  253. } else if (type == 'id' && el.id && _trim(el.id) == sel) {
  254. return el;
  255. } else if (type == 'tag' && el.tagName.toLowerCase() == sel) {
  256. return el;
  257. }
  258. while (!rs) {
  259. if (parent == rootNode)
  260. break;
  261. parent = el.parentNode;
  262. if (!parent)
  263. break;
  264. if (type == 'class' && parent.className && parent.className.match(sel) || type == 'id' && parent.id && _trim(parent.id) == sel || type == 'tag' && parent.tagName && parent.tagName.toLowerCase() == sel) {
  265. rs = parent;
  266. return rs;
  267. break;
  268. } else {
  269. el = parent;
  270. }
  271. }
  272. return null;
  273. },
  274. /**
  275. * Generate a unique integer id (unique within the entire client session).
  276. * @param {String} prefix
  277. * @return {String} guid
  278. */
  279. guid: function (prefix) {
  280. var id = ++idCounter + '';
  281. return prefix ? prefix + id : id;
  282. },
  283. /**
  284. * judge if is an android os
  285. * @return {Boolean} [description]
  286. */
  287. isAndroid: function () {
  288. return /Android /.test(window.navigator.appVersion);
  289. },
  290. /**
  291. * judge if is an android device with low performance
  292. * @return {Boolean}
  293. */
  294. isBadAndroid: function () {
  295. return /Android /.test(window.navigator.appVersion) && !/Chrome\/\d/.test(window.navigator.appVersion);
  296. },
  297. px2Num: function (px) {
  298. return Number(px.replace(/px/, ''));
  299. },
  300. getNodes: getNodes,
  301. getNode: function (node, rootNode) {
  302. var nodes = getNodes(node, rootNode);
  303. return nodes && nodes[0];
  304. },
  305. stringifyStyle: function (style) {
  306. var styleStr = '';
  307. for (var i in style) {
  308. styleStr += [
  309. i,
  310. ':',
  311. style[i],
  312. ';'
  313. ].join('');
  314. }
  315. return styleStr;
  316. }
  317. };
  318. // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp.
  319. var names = [
  320. 'Arguments',
  321. 'Function',
  322. 'String',
  323. 'Number',
  324. 'Date',
  325. 'RegExp'
  326. ];
  327. for (var i = 0; i < names.length; i++) {
  328. Util['is' + names[i]] = function (obj) {
  329. return toString.call(obj) == '[object ' + names[i] + ']';
  330. };
  331. }
  332. if (typeof module == 'object' && module.exports) {
  333. exports = Util;
  334. } /** ignored by jsdoc **/ else {
  335. return Util;
  336. }
  337. return exports;
  338. }(util);
  339. events = function (exports) {
  340. var Util = util;
  341. // Returns a function that will be executed at most one time, no matter how
  342. // often you call it. Useful for lazy initialization.
  343. var _once = function (func) {
  344. var ran = false, memo;
  345. return function () {
  346. if (ran)
  347. return memo;
  348. ran = true;
  349. memo = func.apply(this, arguments);
  350. func = null;
  351. return memo;
  352. };
  353. };
  354. /**
  355. * @discription events
  356. * @mixin
  357. */
  358. var Events = {
  359. // Bind an event to a `callback` function. Passing `"all"` will bind
  360. // the callback to all events fired.
  361. on: function (name, callback, context) {
  362. if (!eventsApi(this, 'on', name, [
  363. callback,
  364. context
  365. ]) || !callback)
  366. return this;
  367. this._events || (this._events = {});
  368. var events = this._events[name] || (this._events[name] = []);
  369. events.push({
  370. callback: callback,
  371. context: context,
  372. ctx: context || this
  373. });
  374. return this;
  375. },
  376. // Bind an event to only be triggered a single time. After the first time
  377. // the callback is invoked, it will be removed.
  378. once: function (name, callback, context) {
  379. if (!eventsApi(this, 'once', name, [
  380. callback,
  381. context
  382. ]) || !callback)
  383. return this;
  384. var self = this;
  385. var once = _once(function () {
  386. self.off(name, once);
  387. callback.apply(this, arguments);
  388. });
  389. once._callback = callback;
  390. return this.on(name, once, context);
  391. },
  392. // Remove one or many callbacks. If `context` is null, removes all
  393. // callbacks with that function. If `callback` is null, removes all
  394. // callbacks for the event. If `name` is null, removes all bound
  395. // callbacks for all events.
  396. off: function (name, callback, context) {
  397. if (!this._events || !eventsApi(this, 'off', name, [
  398. callback,
  399. context
  400. ]))
  401. return this;
  402. // Remove all callbacks for all events.
  403. if (!name && !callback && !context) {
  404. this._events = void 0;
  405. return this;
  406. }
  407. var names = name ? [name] : Object.keys(this._events);
  408. for (var i = 0, length = names.length; i < length; i++) {
  409. name = names[i];
  410. // Bail out if there are no events stored.
  411. var events = this._events[name];
  412. if (!events)
  413. continue;
  414. // Remove all callbacks for this event.
  415. if (!callback && !context) {
  416. delete this._events[name];
  417. continue;
  418. }
  419. // Find any remaining events.
  420. var remaining = [];
  421. for (var j = 0, k = events.length; j < k; j++) {
  422. var event = events[j];
  423. if (callback && callback !== event.callback && callback !== event.callback._callback || context && context !== event.context) {
  424. remaining.push(event);
  425. }
  426. }
  427. // Replace events if there are any remaining. Otherwise, clean up.
  428. if (remaining.length) {
  429. this._events[name] = remaining;
  430. } else {
  431. delete this._events[name];
  432. }
  433. }
  434. return this;
  435. },
  436. // Trigger one or many events, firing all bound callbacks. Callbacks are
  437. // passed the same arguments as `trigger` is, apart from the event name
  438. // (unless you're listening on `"all"`, which will cause your callback to
  439. // receive the true name of the event as the first argument).
  440. trigger: function (name) {
  441. if (!this._events)
  442. return this;
  443. var args = Array.prototype.slice.call(arguments, 1);
  444. if (!eventsApi(this, 'trigger', name, args))
  445. return this;
  446. var events = this._events[name];
  447. var allEvents = this._events.all;
  448. if (events)
  449. triggerEvents(events, args);
  450. if (allEvents)
  451. triggerEvents(allEvents, arguments);
  452. return this;
  453. },
  454. // Inversion-of-control versions of `on` and `once`. Tell *this* object to
  455. // listen to an event in another object ... keeping track of what it's
  456. // listening to.
  457. listenTo: function (obj, name, callback) {
  458. var listeningTo = this._listeningTo || (this._listeningTo = {});
  459. var id = obj._listenId || (obj._listenId = Util.guid('l'));
  460. listeningTo[id] = obj;
  461. if (!callback && typeof name === 'object')
  462. callback = this;
  463. obj.on(name, callback, this);
  464. return this;
  465. },
  466. listenToOnce: function (obj, name, callback) {
  467. if (typeof name === 'object') {
  468. for (var event in name)
  469. this.listenToOnce(obj, event, name[event]);
  470. return this;
  471. }
  472. var cb = _once(function () {
  473. this.stopListening(obj, name, cb);
  474. callback.apply(this, arguments);
  475. });
  476. cb._callback = callback;
  477. return this.listenTo(obj, name, cb);
  478. },
  479. // Tell this object to stop listening to either specific events ... or
  480. // to every object it's currently listening to.
  481. stopListening: function (obj, name, callback) {
  482. var listeningTo = this._listeningTo;
  483. if (!listeningTo)
  484. return this;
  485. var remove = !name && !callback;
  486. if (!callback && typeof name === 'object')
  487. callback = this;
  488. if (obj)
  489. (listeningTo = {})[obj._listenId] = obj;
  490. for (var id in listeningTo) {
  491. obj = listeningTo[id];
  492. obj.off(name, callback, this);
  493. if (remove || Util.isEmpty(obj._events))
  494. delete this._listeningTo[id];
  495. }
  496. return this;
  497. }
  498. };
  499. // Regular expression used to split event strings.
  500. var eventSplitter = /\s+/;
  501. // Implement fancy features of the Events API such as multiple event
  502. // names `"change blur"` and jQuery-style event maps `{change: action}`
  503. // in terms of the existing API.
  504. var eventsApi = function (obj, action, name, rest) {
  505. if (!name)
  506. return true;
  507. // Handle event maps.
  508. if (typeof name === 'object') {
  509. for (var key in name) {
  510. obj[action].apply(obj, [
  511. key,
  512. name[key]
  513. ].concat(rest));
  514. }
  515. return false;
  516. }
  517. // Handle space separated event names.
  518. if (eventSplitter.test(name)) {
  519. var names = name.split(eventSplitter);
  520. for (var i = 0, length = names.length; i < length; i++) {
  521. obj[action].apply(obj, [names[i]].concat(rest));
  522. }
  523. return false;
  524. }
  525. return true;
  526. };
  527. // A difficult-to-believe, but optimized internal dispatch function for
  528. // triggering events. Tries to keep the usual cases speedy (most internal
  529. var triggerEvents = function (events, args) {
  530. var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2];
  531. switch (args.length) {
  532. case 0:
  533. while (++i < l)
  534. (ev = events[i]).callback.call(ev.ctx);
  535. return;
  536. case 1:
  537. while (++i < l)
  538. (ev = events[i]).callback.call(ev.ctx, a1);
  539. return;
  540. case 2:
  541. while (++i < l)
  542. (ev = events[i]).callback.call(ev.ctx, a1, a2);
  543. return;
  544. case 3:
  545. while (++i < l)
  546. (ev = events[i]).callback.call(ev.ctx, a1, a2, a3);
  547. return;
  548. default:
  549. while (++i < l)
  550. (ev = events[i]).callback.apply(ev.ctx, args);
  551. return;
  552. }
  553. };
  554. // Aliases for backwards compatibility.
  555. Events.bind = Events.on;
  556. Events.unbind = Events.off;
  557. if (typeof module == 'object' && module.exports) {
  558. exports = Events;
  559. } /** ignored by jsdoc **/ else {
  560. return Events;
  561. }
  562. return exports;
  563. }(events);
  564. base = function (exports) {
  565. var Util = util;
  566. var Events = events;
  567. /**
  568. @constructor
  569. @mixes Events
  570. */
  571. var Base = function () {
  572. };
  573. Util.mix(Base.prototype, Events);
  574. Util.mix(Base.prototype, {
  575. /**
  576. * @memberof Base
  577. * @param {object} plugin plug a plugin
  578. */
  579. plug: function (plugin) {
  580. var self = this;
  581. if (!plugin || !plugin.pluginId)
  582. return;
  583. if (!self.__plugins) {
  584. self.__plugins = [];
  585. }
  586. var __plugin = self.getPlugin(plugin.pluginId);
  587. __plugin && self.unplug(plugin.pluginId);
  588. plugin.pluginInitializer(self);
  589. self.__plugins.push(plugin);
  590. return self;
  591. },
  592. /**
  593. * @memberof Base
  594. * @param {object|string} plugin unplug a plugin by pluginId or plugin instance
  595. */
  596. unplug: function (plugin) {
  597. var self = this;
  598. if (!plugin || !self.__plugins)
  599. return;
  600. var _plugin = typeof plugin == 'string' ? self.getPlugin(plugin) : plugin;
  601. _plugin.pluginDestructor(self);
  602. for (var i = 0, l = self.__plugins.length; i < l; i++) {
  603. if (self.__plugins[i] == _plugin) {
  604. return self.__plugins.splice(i, 1);
  605. }
  606. }
  607. },
  608. /**
  609. * @memberof Base
  610. * @param {object|string} plugin get plugin by pluginId
  611. */
  612. getPlugin: function (pluginId) {
  613. var self = this;
  614. var plugins = [];
  615. if (!self.__plugins)
  616. return;
  617. for (var i = 0, l = self.__plugins.length; i < l; i++) {
  618. if (self.__plugins[i] && self.__plugins[i].pluginId == pluginId) {
  619. plugins.push(self.__plugins[i]);
  620. }
  621. }
  622. return plugins.length > 1 ? plugins : plugins[0] || null;
  623. }
  624. });
  625. if (typeof module == 'object' && module.exports) {
  626. exports = Base;
  627. } /** ignored by jsdoc **/ else {
  628. return Base;
  629. }
  630. return exports;
  631. }(base);
  632. plugins_infinite = function (exports) {
  633. var Util = util, Base = base;
  634. var transform = Util.prefixStyle('transform');
  635. var transition = Util.prefixStyle('transition');
  636. /**
  637. * An infinity dom-recycled list plugin for xscroll.
  638. * @constructor
  639. * @param {object} cfg
  640. * @param {string} cfg.transition recomposition cell with a transition
  641. * @param {string} cfg.infiniteElements dom-selector for reused elements
  642. * @param {function} cfg.renderHook render function for cell by per col or per row duration scrolling
  643. * @extends {Base}
  644. */
  645. var Infinite = function (cfg) {
  646. Infinite.superclass.constructor.call(this, cfg);
  647. this.userConfig = Util.mix({ transition: 'all 0.5s ease' }, cfg);
  648. };
  649. Util.extend(Infinite, Base, {
  650. /**
  651. * a pluginId
  652. * @memberOf Infinite
  653. * @type {string}
  654. */
  655. pluginId: 'infinite',
  656. /**
  657. * store the visible elements inside of view.
  658. * @memberOf Infinite
  659. * @type {object}
  660. */
  661. visibleElements: {},
  662. /**
  663. * store all elements data.
  664. * @memberOf Infinite
  665. * @type {object}
  666. */
  667. sections: {},
  668. /**
  669. * plugin initializer
  670. * @memberOf Infinite
  671. * @override Base
  672. * @return {Infinite}
  673. */
  674. pluginInitializer: function (xscroll) {
  675. var self = this;
  676. self.xscroll = xscroll;
  677. self.isY = !!(xscroll.userConfig.zoomType == 'y');
  678. self._ = {
  679. _top: self.isY ? '_top' : '_left',
  680. _height: self.isY ? '_height' : '_width',
  681. top: self.isY ? 'top' : 'left',
  682. height: self.isY ? 'height' : 'width',
  683. width: self.isY ? 'width' : 'height',
  684. y: self.isY ? 'y' : 'x',
  685. translate: self.isY ? 'translateY' : 'translateX',
  686. containerHeight: self.isY ? 'containerHeight' : 'containerWidth',
  687. scrollTop: self.isY ? 'scrollTop' : 'scrollLeft'
  688. };
  689. self._initInfinite();
  690. xscroll.on('afterrender', function () {
  691. self.render();
  692. self._bindEvt();
  693. });
  694. return self;
  695. },
  696. /**
  697. * detroy the plugin
  698. * @memberOf Infinite
  699. * @override Base
  700. * @return {Infinite}
  701. */
  702. pluginDestructor: function () {
  703. var self = this;
  704. var _ = self._;
  705. for (var i = 0; i < self.infiniteLength; i++) {
  706. self.infiniteElements[i].style[_.top] = 'auto';
  707. self.infiniteElements[i].style[transform] = 'none';
  708. self.infiniteElements[i].style.visibility = 'hidden';
  709. }
  710. self.xscroll && self.xscroll.off('scroll', self._updateByScroll, self);
  711. self.xscroll && self.xscroll.off('tap panstart pan panend', self._cellEventsHandler, self);
  712. return self;
  713. },
  714. _initInfinite: function () {
  715. var self = this;
  716. var xscroll = self.xscroll;
  717. var _ = self._;
  718. self.sections = {};
  719. self.infiniteElements = xscroll.renderTo.querySelectorAll(self.userConfig.infiniteElements);
  720. self.infiniteLength = self.infiniteElements.length;
  721. self.infiniteElementsCache = function () {
  722. var tmp = [];
  723. for (var i = 0; i < self.infiniteLength; i++) {
  724. tmp.push({});
  725. self.infiniteElements[i].style.position = 'absolute';
  726. self.infiniteElements[i].style[_.top] = 0;
  727. self.infiniteElements[i].style.visibility = 'hidden';
  728. self.infiniteElements[i].style.display = 'block';
  729. Util.addClass(self.infiniteElements[i], '_xs_infinite_elements_');
  730. }
  731. return tmp;
  732. }();
  733. self.elementsPos = {};
  734. return self;
  735. },
  736. _renderUnRecycledEl: function () {
  737. var self = this;
  738. var _ = self._;
  739. var translateZ = self.userConfig.gpuAcceleration ? ' translateZ(0) ' : '';
  740. for (var i in self.__serializedData) {
  741. var unrecycledEl = self.__serializedData[i];
  742. if (self.__serializedData[i]['recycled'] === false) {
  743. var el = unrecycledEl.id && document.getElementById(unrecycledEl.id.replace('#', '')) || document.createElement('div');
  744. var randomId = Util.guid('xs-row-');
  745. el.id = unrecycledEl.id || randomId;
  746. unrecycledEl.id = el.id;
  747. self.xscroll.content.appendChild(el);
  748. for (var attrName in unrecycledEl.style) {
  749. if (attrName != _.height && attrName != 'display' && attrName != 'position') {
  750. el.style[attrName] = unrecycledEl.style[attrName];
  751. }
  752. }
  753. el.style[_.top] = 0;
  754. el.style.position = 'absolute';
  755. el.style.display = 'block';
  756. el.style[_.height] = unrecycledEl[_._height] + 'px';
  757. el.style[transform] = _.translate + '(' + unrecycledEl[_._top] + 'px) ' + translateZ;
  758. Util.addClass(el, unrecycledEl.className);
  759. self.userConfig.renderHook.call(self, el, unrecycledEl);
  760. }
  761. }
  762. },
  763. /**
  764. * render or update the scroll contents
  765. * @memberOf Infinite
  766. * @return {Infinite}
  767. */
  768. render: function () {
  769. var self = this;
  770. var _ = self._;
  771. var xscroll = self.xscroll;
  772. var offset = self.isY ? xscroll.getScrollTop() : xscroll.getScrollLeft();
  773. self.visibleElements = self.getVisibleElements(offset);
  774. self.__serializedData = self._computeDomPositions();
  775. xscroll.sticky && xscroll.sticky.render(true);
  776. //force render
  777. xscroll.fixed && xscroll.fixed.render();
  778. var size = xscroll[_.height];
  779. var containerSize = self._containerSize;
  780. if (containerSize < size) {
  781. containerSize = size;
  782. }
  783. xscroll[_.containerHeight] = containerSize;
  784. xscroll.container.style[_.height] = containerSize + 'px';
  785. xscroll.content.style[_.height] = containerSize + 'px';
  786. self._renderUnRecycledEl();
  787. self._updateByScroll();
  788. self._updateByRender(offset);
  789. self.xscroll.boundryCheck();
  790. return self;
  791. },
  792. _getChangedRows: function (newElementsPos) {
  793. var self = this;
  794. var changedRows = {};
  795. for (var i in self.elementsPos) {
  796. if (!newElementsPos.hasOwnProperty(i)) {
  797. changedRows[i] = 'delete';
  798. }
  799. }
  800. for (var i in newElementsPos) {
  801. if (newElementsPos[i].recycled && !self.elementsPos.hasOwnProperty(i)) {
  802. changedRows[i] = 'add';
  803. }
  804. }
  805. self.elementsPos = newElementsPos;
  806. return changedRows;
  807. },
  808. _updateByScroll: function (e) {
  809. var self = this;
  810. var xscroll = self.xscroll;
  811. var _ = self._;
  812. var _pos = e && e[_.scrollTop];
  813. var pos = _pos === undefined ? self.isY ? xscroll.getScrollTop() : xscroll.getScrollLeft() : _pos;
  814. var elementsPos = self.getVisibleElements(pos);
  815. var changedRows = self.changedRows = self._getChangedRows(elementsPos);
  816. try {
  817. for (var i in changedRows) {
  818. if (changedRows[i] == 'delete') {
  819. self._pushEl(i);
  820. }
  821. if (changedRows[i] == 'add') {
  822. var elObj = self._popEl(elementsPos[i][self.guid]);
  823. var index = elObj.index;
  824. var el = elObj.el;
  825. if (el) {
  826. self.infiniteElementsCache[index].guid = elementsPos[i].guid;
  827. self.__serializedData[elementsPos[i].guid].__infiniteIndex = index;
  828. self._renderData(el, elementsPos[i]);
  829. self._renderStyle(el, elementsPos[i]);
  830. }
  831. }
  832. }
  833. } catch (e) {
  834. console.warn('Not enough infiniteElements setted!');
  835. }
  836. return self;
  837. },
  838. _updateByRender: function (pos) {
  839. var self = this;
  840. var _ = self._;
  841. var xscroll = self.xscroll;
  842. var pos = pos === undefined ? self.isY ? xscroll.getScrollTop() : xscroll.getScrollLeft() : pos;
  843. var prevElementsPos = self.visibleElements;
  844. var newElementsPos = self.getVisibleElements(pos);
  845. var prevEl, newEl;
  846. //repaint
  847. for (var i in newElementsPos) {
  848. newEl = newElementsPos[i];
  849. for (var j in prevElementsPos) {
  850. prevEl = prevElementsPos[j];
  851. if (prevEl.guid === newEl.guid) {
  852. if (newEl.style != prevEl.style || newEl[_._top] != prevEl[_._top] || newEl[_._height] != prevEl[_._height]) {
  853. self._renderStyle(self.infiniteElements[newEl.__infiniteIndex], newEl, true);
  854. }
  855. if (JSON.stringify(newEl.data) != JSON.stringify(prevEl.data)) {
  856. self._renderData(self.infiniteElements[newEl.__infiniteIndex], newEl);
  857. }
  858. } else {
  859. // paint
  860. if (self.__serializedData[newEl.guid].recycled && self.__serializedData[newEl.guid].__infiniteIndex === undefined) {
  861. var elObj = self._popEl();
  862. self.__serializedData[newEl.guid].__infiniteIndex = elObj.index;
  863. self._renderData(elObj.el, newEl);
  864. self._renderStyle(elObj.el, newEl);
  865. }
  866. }
  867. }
  868. }
  869. self.visibleElements = newElementsPos;
  870. },
  871. /**
  872. * get all element posInfo such as top,height,template,html
  873. * @return {Array}
  874. **/
  875. _computeDomPositions: function () {
  876. var self = this;
  877. var _ = self._;
  878. var pos = 0, size = 0, sections = self.sections, section;
  879. var data = [];
  880. var serializedData = {};
  881. for (var i in sections) {
  882. for (var j = 0, len = sections[i].length; j < len; j++) {
  883. section = sections[i][j];
  884. section.sectionId = i;
  885. section.index = j;
  886. data.push(section);
  887. }
  888. }
  889. //f = v/itemSize*1000 < 60 => v = 0.06 * itemSize
  890. self.userConfig.maxSpeed = 0.06 * 50;
  891. for (var i = 0, l = data.length; i < l; i++) {
  892. var item = data[i];
  893. size = item.style && item.style[_.height] >= 0 && item.style.position != 'fixed' ? item.style[_.height] : 0;
  894. item.guid = item.guid || Util.guid();
  895. item[_._top] = pos;
  896. item[_._height] = size;
  897. item.recycled = item.recycled === false ? false : true;
  898. pos += size;
  899. serializedData[item.guid] = item;
  900. }
  901. self._containerSize = pos;
  902. return serializedData;
  903. },
  904. /**
  905. * get all elements inside of the view.
  906. * @memberOf Infinite
  907. * @param {number} pos scrollLeft or scrollTop
  908. * @return {object} visibleElements
  909. */
  910. getVisibleElements: function (pos) {
  911. var self = this;
  912. var xscroll = self.xscroll;
  913. var _ = self._;
  914. var pos = pos === undefined ? self.isY ? xscroll.getScrollTop() : xscroll.getScrollLeft() : pos;
  915. var threshold = self.userConfig.threshold >= 0 ? self.userConfig.threshold : xscroll[_.height] / 3;
  916. var tmp = {}, item;
  917. var data = self.__serializedData;
  918. for (var i in data) {
  919. item = data[i];
  920. if (item[_._top] >= pos - threshold && item[_._top] <= pos + xscroll[_.height] + threshold) {
  921. tmp[item.guid] = item;
  922. }
  923. }
  924. return JSON.parse(JSON.stringify(tmp));
  925. },
  926. _popEl: function () {
  927. var self = this;
  928. for (var i = 0; i < self.infiniteLength; i++) {
  929. if (!self.infiniteElementsCache[i]._visible) {
  930. self.infiniteElementsCache[i]._visible = true;
  931. return {
  932. index: i,
  933. el: self.infiniteElements[i]
  934. };
  935. }
  936. }
  937. },
  938. _pushEl: function (guid) {
  939. var self = this;
  940. for (var i = 0; i < self.infiniteLength; i++) {
  941. if (self.infiniteElementsCache[i].guid == guid) {
  942. self.infiniteElementsCache[i]._visible = false;
  943. self.infiniteElements[i].style.visibility = 'hidden';
  944. self.infiniteElementsCache[i].guid = null;
  945. }
  946. }
  947. },
  948. _renderData: function (el, elementObj) {
  949. var self = this;
  950. if (!el || !elementObj || elementObj.style.position == 'fixed')
  951. return;
  952. self.userConfig.renderHook.call(self, el, elementObj);
  953. },
  954. _renderStyle: function (el, elementObj, useTransition) {
  955. var self = this;
  956. var _ = self._;
  957. if (!el)
  958. return;
  959. var translateZ = self.xscroll.userConfig.gpuAcceleration ? ' translateZ(0) ' : '';
  960. //update style
  961. for (var attrName in elementObj.style) {
  962. if (attrName != _.height && attrName != 'display' && attrName != 'position') {
  963. el.style[attrName] = elementObj.style[attrName];
  964. }
  965. }
  966. el.setAttribute('xs-index', elementObj.index);
  967. el.setAttribute('xs-sectionid', elementObj.sectionId);
  968. el.setAttribute('xs-guid', elementObj.guid);
  969. el.style.visibility = 'visible';
  970. el.style[_.height] = elementObj[_._height] + 'px';
  971. el.style[transform] = _.translate + '(' + elementObj[_._top] + 'px) ' + translateZ;
  972. el.style[transition] = useTransition ? self.userConfig.transition : 'none';
  973. },
  974. getCell: function (e) {
  975. var self = this, cell;
  976. var el = Util.findParentEl(e.target, '._xs_infinite_elements_', self.xscroll.renderTo);
  977. if (!el) {
  978. el = Util.findParentEl(e.target, '.xs-sticky-handler', self.xscroll.renderTo);
  979. }
  980. var guid = el && el.getAttribute('xs-guid');
  981. if (undefined === guid)
  982. return;
  983. return {
  984. data: self.__serializedData[guid],
  985. el: el
  986. };
  987. },
  988. _bindEvt: function () {
  989. var self = this;
  990. if (self._isEvtBinded)
  991. return;
  992. self._isEvtBinded = true;
  993. self.xscroll.renderTo.addEventListener('webkitTransitionEnd', function (e) {
  994. if (e.target.className.match(/xs-row/)) {
  995. e.target.style.webkitTransition = '';
  996. }
  997. });
  998. self.xscroll.on('scroll', self._updateByScroll, self);
  999. self.xscroll.on('tap panstart pan panend', self._cellEventsHandler, self);
  1000. return self;
  1001. },
  1002. _cellEventsHandler: function (e) {
  1003. var self = this;
  1004. var cell = self.getCell(e);
  1005. e.cell = cell.data;
  1006. e.cellEl = cell.el;
  1007. e.cell && self[e.type].call(self, e);
  1008. },
  1009. /**
  1010. * tap event
  1011. * @memberOf Infinite
  1012. * @param {object} e events data include cell object
  1013. * @event
  1014. */
  1015. tap: function (e) {
  1016. this.trigger('tap', e);
  1017. return this;
  1018. },
  1019. /**
  1020. * panstart event
  1021. * @memberOf Infinite
  1022. * @param {object} e events data include cell object
  1023. * @event
  1024. */
  1025. panstart: function (e) {
  1026. this.trigger('panstart', e);
  1027. return this;
  1028. },
  1029. /**
  1030. * pan event
  1031. * @memberOf Infinite
  1032. * @param {object} e events data include cell object
  1033. * @event
  1034. */
  1035. pan: function (e) {
  1036. this.trigger('pan', e);
  1037. return this;
  1038. },
  1039. /**
  1040. * panend event
  1041. * @memberOf Infinite
  1042. * @param {object} e events data include cell object
  1043. * @event
  1044. */
  1045. panend: function (e) {
  1046. this.trigger('panend', e);
  1047. return this;
  1048. },
  1049. /**
  1050. * insert data before a position
  1051. * @memberOf Infinite
  1052. * @param {string} sectionId sectionId of the target cell
  1053. * @param {number} index index of the target cell
  1054. * @param {object} data data to insert
  1055. * @return {Infinite}
  1056. */
  1057. insertBefore: function (sectionId, index, data) {
  1058. var self = this;
  1059. if (sectionId === undefined || index === undefined || data === undefined)
  1060. return self;
  1061. if (!self.sections[sectionId]) {
  1062. self.sections[sectionId] = [];
  1063. }
  1064. self.sections[sectionId].splice(index, 0, data);
  1065. return self;
  1066. },
  1067. /**
  1068. * insert data after a position
  1069. * @memberOf Infinite
  1070. * @param {string} sectionId sectionId of the target cell
  1071. * @param {number} index index of the target cell
  1072. * @param {object} data data to insert
  1073. * @return {Infinite}
  1074. */
  1075. insertAfter: function (sectionId, index, data) {
  1076. var self = this;
  1077. if (sectionId === undefined || index === undefined || data === undefined)
  1078. return self;
  1079. if (!self.sections[sectionId]) {
  1080. self.sections[sectionId] = [];
  1081. }
  1082. self.sections[sectionId].splice(Number(index) + 1, 0, data);
  1083. return self;
  1084. },
  1085. /**
  1086. * append data after a section
  1087. * @memberOf Infinite
  1088. * @param {string} sectionId sectionId for the append cell
  1089. * @param {object} data data to append
  1090. * @return {Infinite}
  1091. */
  1092. append: function (sectionId, data) {
  1093. var self = this;
  1094. if (!self.sections[sectionId]) {
  1095. self.sections[sectionId] = [];
  1096. }
  1097. self.sections[sectionId] = self.sections[sectionId].concat(data);
  1098. return self;
  1099. },
  1100. /**
  1101. * remove some data by sectionId,from,number
  1102. * @memberOf Infinite
  1103. * @param {string} sectionId sectionId for the append cell
  1104. * @param {number} from removed index from
  1105. * @param {number} number removed data number
  1106. * @return {Infinite}
  1107. */
  1108. remove: function (sectionId, from, number) {
  1109. var self = this;
  1110. var number = number || 1;
  1111. if (undefined === sectionId || !self.sections[sectionId])
  1112. return self;
  1113. //remove a section
  1114. if (undefined === from) {
  1115. self.sections[sectionId] = null;
  1116. return self;
  1117. }
  1118. //remove some data in section
  1119. if (self.sections[sectionId] && self.sections[sectionId][from]) {
  1120. self.sections[sectionId].splice(from, number);
  1121. return self;
  1122. }
  1123. return self;
  1124. },
  1125. /**
  1126. * replace some data by sectionId and index
  1127. * @memberOf Infinite
  1128. * @param {string} sectionId sectionId to replace
  1129. * @param {number} index removed index from
  1130. * @param {object} data new data to replace
  1131. * @return {Infinite}
  1132. */
  1133. replace: function (sectionId, index, data) {
  1134. var self = this;
  1135. if (undefined === sectionId || !self.sections[sectionId])
  1136. return self;
  1137. self.sections[sectionId][index] = data;
  1138. return self;
  1139. },
  1140. /**
  1141. * get data by sectionId and index
  1142. * @memberOf Infinite
  1143. * @param {string} sectionId sectionId
  1144. * @param {number} index index in the section
  1145. * @return {object} data data
  1146. */
  1147. get: function (sectionId, index) {
  1148. if (undefined === sectionId)
  1149. return;
  1150. if (undefined === index)
  1151. return this.sections[sectionId];
  1152. return this.sections[sectionId][index];
  1153. }
  1154. });
  1155. if (typeof module == 'object' && module.exports) {
  1156. exports = Infinite;
  1157. } /** ignored by jsdoc **/ else if (window.XScroll && window.XScroll.Plugins) {
  1158. return XScroll.Plugins.Infinite = Infinite;
  1159. }
  1160. return exports;
  1161. }(plugins_infinite);
  1162. }());