Code coverage report for app/mixins/common/infinite_scroll_mixin.js

Statements: 8.11% (3 / 37)      Branches: 0% (0 / 14)      Functions: 6.25% (1 / 16)      Lines: 8.33% (3 / 36)      Ignored: none     

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