infinite_scroll_mixin.js 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. /**
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing, software
  13. * distributed under the License is distributed on an "AS IS" BASIS,
  14. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing permissions and
  16. * limitations under the License.
  17. */
  18. var App = require('app');
  19. /**
  20. * @typedef {Object} InfiniteScrollMixinOptions
  21. * @property {String} appendHtml html to append when scroll ends and callback executed. It's common
  22. * that root node has unique <code>id</code> or <code>class</code> attributes.
  23. * @property {Function} callback function to execute when scroll ends. This function should return
  24. * <code>$.Deferred().promise()</code> instance
  25. * @property {Function} onReject function to execute when <code>callback</code> rejected.
  26. */
  27. /**
  28. * @mixin App.InfiniteScrollMixin
  29. * This mixin provides methods to attach infinite scroll to specific scrollable element.
  30. *
  31. * Usage:
  32. * <code>
  33. * // mix it
  34. * var myView = Em.View.extend(App.InfiniteScrollMixin, {
  35. * didInsertElement: function() {
  36. * // call method infiniteScrollInit
  37. * this.infiniteScrollInit($('.some-scrollable'), {
  38. * callback: this.someCallbacOnEndReached
  39. * });
  40. * }
  41. * });
  42. * </code>
  43. *
  44. */
  45. App.InfiniteScrollMixin = Ember.Mixin.create({
  46. /**
  47. * Stores callback execution progress.
  48. *
  49. * @type {Boolean}
  50. */
  51. _infiniteScrollCallbackInProgress: false,
  52. /**
  53. * Stores HTMLElement infinite scroll initiated on.
  54. *
  55. * @type {HTMLElement}
  56. */
  57. _infiniteScrollEl: null,
  58. /**
  59. * Default options for infinite scroll.
  60. *
  61. * @type {InfiniteScrollMixinOptions}
  62. */
  63. _infiniteScrollDefaults: {
  64. appendHtml: '<div id="infinite-scroll-append"><i class="icon-spinner icon-spin"></i></div>',
  65. callback: function() { return $.Deferred().resolve().promise(); },
  66. onReject: function() {},
  67. onResolve: function() {}
  68. },
  69. /**
  70. * Determines that there is no data to load on next callback call.
  71. *
  72. */
  73. _infiniteScrollMoreData: true,
  74. /**
  75. * Initialize infinite scroll on specified HTMLElement.
  76. *
  77. * @param {HTMLElement} el DOM element to attach infinite scroll.
  78. * @param {InfiniteScrollMixinOptions} opts
  79. */
  80. infiniteScrollInit: function(el, opts) {
  81. var options = $.extend({}, this.get('_infiniteScrollDefaults'), opts || {});
  82. this.set('_infiniteScrollEl', el);
  83. this.get('_infiniteScrollEl').on('scroll', this._infiniteScrollHandler.bind(this));
  84. this.get('_infiniteScrollEl').on('infinite-scroll-end', this._infiniteScrollEndHandler(options).bind(this));
  85. },
  86. /**
  87. * Handler executed on scrolling.
  88. * @param {jQuery.Event} e
  89. */
  90. _infiniteScrollHandler: function(e) {
  91. var el = $(e.target);
  92. var height = el.get(0).clientHeight;
  93. var scrollHeight = el.prop('scrollHeight');
  94. var endPoint = scrollHeight - height;
  95. if (endPoint === el.scrollTop() && !this.get('_infiniteScrollCallbackInProgress')) {
  96. el.trigger('infinite-scroll-end');
  97. }
  98. },
  99. /**
  100. * Handler called when scroll ends.
  101. *
  102. * @param {InfiniteScrollMixinOptions} options
  103. * @return {Function}
  104. */
  105. _infiniteScrollEndHandler: function(options) {
  106. return function(e) {
  107. var self = this;
  108. if (this.get('_infiniteScrollCallbackInProgress') || !this.get('_infiniteScrollMoreData')) return;
  109. this._infiniteScrollAppendHtml(options.appendHtml);
  110. // always scroll to bottom
  111. this.get('_infiniteScrollEl').scrollTop(this.get('_infiniteScrollEl').get(0).scrollHeight);
  112. this.set('_infiniteScrollCallbackInProgress', true);
  113. options.callback().then(function() {
  114. options.onResolve();
  115. }, function() {
  116. options.onReject();
  117. }).always(function() {
  118. self.set('_infiniteScrollCallbackInProgress', false);
  119. self._infiniteScrollRemoveHtml(options.appendHtml);
  120. });
  121. }.bind(this);
  122. },
  123. /**
  124. * Helper function to append String as html node to.
  125. * @param {String} htmlString string to append
  126. */
  127. _infiniteScrollAppendHtml: function(htmlString) {
  128. this.get('_infiniteScrollEl').append(htmlString);
  129. },
  130. /**
  131. * Remove HTMLElement by specified string that can be converted to html. HTMLElement root node
  132. * should have unique <code>id</code> or <code>class</code> attribute to avoid removing additional
  133. * elements.
  134. *
  135. * @param {String} htmlString string to remove
  136. */
  137. _infiniteScrollRemoveHtml: function(htmlString) {
  138. this.get('_infiniteScrollEl').find(this._infiniteScrollGetSelector(htmlString)).remove();
  139. },
  140. /**
  141. * Get root node selector.
  142. * <code>id</code> attribute has higher priority and will return if found.
  143. * <code>class</code> if no <code>id</code> attribute found <code>class</code> attribute
  144. * will be used.
  145. *
  146. * @param {String} htmlString string processed as HTML
  147. * @return {[type]} [description]
  148. */
  149. _infiniteScrollGetSelector: function(htmlString) {
  150. var html = $(htmlString);
  151. var elId = html.attr('id');
  152. var elClass = (html.attr('class') || '').split(' ').join('.');
  153. html = null;
  154. return !!elId ? '#' + elId : '.' + elClass;
  155. },
  156. /**
  157. * Remove infinite scroll.
  158. * Unbind all listeners.
  159. */
  160. infiniteScrollDestroy: function() {
  161. this.get('_infiniteScrollEl').off('scroll', this._infiniteScrollHandler);
  162. this.get('_infiniteScrollEl').off('infinite-scroll-end', this._infiniteScrollHandler);
  163. this.set('_infiniteScrollEl', null);
  164. },
  165. /**
  166. * Set if there is more data to load on next scroll end event.
  167. * @param {boolean} isAvailable <code>true</code> when there are more data to fetch
  168. */
  169. infiniteScrollSetDataAvailable: function(isAvailable) {
  170. this.set('_infiniteScrollMoreData', isAvailable);
  171. }
  172. });