Viewing File: /usr/local/cpanel/3rdparty/share/angular-ui-scroll/1.6.1/src/ui-scroll.js

import JQLiteExtras from './modules/jqLiteExtras';
import ElementRoutines from './modules/elementRoutines.js';
import ScrollBuffer from './modules/buffer.js';
import Viewport from './modules/viewport.js';
import Adapter from './modules/adapter.js';

angular.module('ui.scroll', [])

  .service('jqLiteExtras', () => new JQLiteExtras())
  .run(['jqLiteExtras', (jqLiteExtras) =>
    !window.jQuery ? jqLiteExtras.registerFor(angular.element) : null
  ])

  .directive('uiScrollViewport', function () {
    return {
      restrict: 'A',
      controller: [
        '$scope',
        '$element',
        function (scope, element) {
          this.container = element;
          this.viewport = element;
          this.scope = scope;

          angular.forEach(element.children(), (child => {
            if (child.tagName.toLowerCase() === 'tbody') {
              this.viewport = angular.element(child);
            }
          }));

          return this;
        }
      ]
    };
  })

  .directive('uiScroll', [
    '$log',
    '$injector',
    '$rootScope',
    '$timeout',
    '$q',
    '$parse',
    function (console, $injector, $rootScope, $timeout, $q, $parse) {

      return {
        require: ['?^uiScrollViewport'],
        restrict: 'A',
        transclude: 'element',
        priority: 1000,
        terminal: true,
        link: link
      };

      function link($scope, element, $attr, controllers, linker) {
        const match = $attr.uiScroll.match(/^\s*(\w+)\s+in\s+([(\w|\$)\.]+)\s*$/);
        if (!match) {
          throw new Error('Expected uiScroll in form of \'_item_ in _datasource_\' but got \'' + $attr.uiScroll + '\'');
        }

        function parseNumericAttr(value, defaultValue) {
          let result = $parse(value)($scope);
          return isNaN(result) ? defaultValue : result;
        }

        const BUFFER_MIN = 3;
        const BUFFER_DEFAULT = 10;
        const PADDING_MIN = 0.3;
        const PADDING_DEFAULT = 0.5;

        let datasource = null;
        const itemName = match[1];
        const datasourceName = match[2];
        const viewportController = controllers[0];
        const bufferSize = Math.max(BUFFER_MIN, parseNumericAttr($attr.bufferSize, BUFFER_DEFAULT));
        const padding = Math.max(PADDING_MIN, parseNumericAttr($attr.padding, PADDING_DEFAULT));
        let startIndex = parseNumericAttr($attr.startIndex, 1);
        let ridActual = 0;// current data revision id
        let pending = [];

        let elementRoutines = new ElementRoutines($injector, $q);
        let buffer = new ScrollBuffer(elementRoutines, bufferSize);
        let viewport = new Viewport(elementRoutines, buffer, element, viewportController, $rootScope, padding);
        let adapter = new Adapter(viewport, buffer, adjustBuffer, reload, $attr, $parse, element, $scope);

        if (viewportController) {
          viewportController.adapter = adapter;
        }

        let isDatasourceValid = () => angular.isObject(datasource) && angular.isFunction(datasource.get);
        datasource = $parse(datasourceName)($scope); // try to get datasource on scope
        if (!isDatasourceValid()) {
          datasource = $injector.get(datasourceName); // try to inject datasource as service
          if (!isDatasourceValid()) {
            throw new Error(datasourceName + ' is not a valid datasource');
          }
        }

        let indexStore = {};

        function defineProperty(datasource, propName, propUserName) {
          let descriptor = Object.getOwnPropertyDescriptor(datasource, propName);
          if (!descriptor || (!descriptor.set && !descriptor.get)) {
            Object.defineProperty(datasource, propName, {
              set: (value) => {
                indexStore[propName] = value;
                $timeout(() => {
                  buffer[propUserName] = value;
                  if (!pending.length) {
                    let topPaddingHeightOld = viewport.topDataPos();
                    viewport.adjustPadding();
                    if (propName === 'minIndex') {
                      viewport.adjustScrollTopAfterMinIndexSet(topPaddingHeightOld);
                    }
                  }
                });
              },
              get: () => indexStore[propName]
            });
          }
        }

        defineProperty(datasource, 'minIndex', 'minIndexUser');
        defineProperty(datasource, 'maxIndex', 'maxIndexUser');

        const fetchNext = (datasource.get.length !== 2) ? (success) => datasource.get(buffer.next, bufferSize, success)
          : (success) => {
          datasource.get({
            index: buffer.next,
            append: buffer.length ? buffer[buffer.length - 1].item : void 0,
            count: bufferSize
          }, success);
        };

        const fetchPrevious = (datasource.get.length !== 2) ? (success) => datasource.get(buffer.first - bufferSize, bufferSize, success)
          : (success) => {
          datasource.get({
            index: buffer.first - bufferSize,
            prepend: buffer.length ? buffer[0].item : void 0,
            count: bufferSize
          }, success);
        };

        /**
         * Build padding elements
         *
         * Calling linker is the only way I found to get access to the tag name of the template
         * to prevent the directive scope from pollution a new scope is created and destroyed
         * right after the builder creation is completed
         */
        linker((clone, scope) => {
          viewport.createPaddingElements(clone[0]);
          // we do not include the clone in the DOM. It means that the nested directives will not
          // be able to reach the parent directives, but in this case it is intentional because we
          // created the clone to access the template tag name
          scope.$destroy();
          clone.remove();
        });

        $scope.$on('$destroy', () => {
          unbindEvents();
          viewport.unbind('mousewheel', wheelHandler);
        });

        viewport.bind('mousewheel', wheelHandler);

        $timeout(() => {
          viewport.applyContainerStyle();
          reload();
        });

        /* Private function definitions */

        function isInvalid(rid) {
          return (rid && rid !== ridActual) || $scope.$$destroyed;
        }

        function bindEvents() {
          viewport.bind('resize', resizeAndScrollHandler);
          viewport.bind('scroll', resizeAndScrollHandler);
        }

        function unbindEvents() {
          viewport.unbind('resize', resizeAndScrollHandler);
          viewport.unbind('scroll', resizeAndScrollHandler);
        }

        function reload() {
          viewport.resetTopPadding();
          viewport.resetBottomPadding();
          if (arguments.length) {
            startIndex = arguments[0];
          }
          buffer.reset(startIndex);
          adjustBuffer();
        }

        function isElementVisible(wrapper) {
          return wrapper.element.height() && wrapper.element[0].offsetParent;
        }

        function visibilityWatcher(wrapper) {
          if (isElementVisible(wrapper)) {
            buffer.forEach((item) => {
              if (angular.isFunction(item.unregisterVisibilityWatcher)) {
                item.unregisterVisibilityWatcher();
                delete item.unregisterVisibilityWatcher;
              }
            });
            if (!pending.length) {
              adjustBuffer();
            }
          }
        }

        function insertWrapperContent(wrapper, insertAfter) {
          createElement(wrapper, insertAfter, viewport.insertElement);
          if (!isElementVisible(wrapper)) {
            wrapper.unregisterVisibilityWatcher = wrapper.scope.$watch(() => visibilityWatcher(wrapper));
          }
          wrapper.element.addClass('ng-hide'); // hide inserted elements before data binding
        }

        function createElement(wrapper, insertAfter, insertElement) {
          let promises = null;
          let sibling = (insertAfter > 0) ? buffer[insertAfter - 1].element : undefined;
          linker((clone, scope) => {
            promises = insertElement(clone, sibling);
            wrapper.element = clone;
            wrapper.scope = scope;
            scope[itemName] = wrapper.item;
          });
          if (adapter.transform)
            adapter.transform(wrapper.scope, wrapper.element);
          return promises;
        }

        function updateDOM() {
          let promises = [];
          const toBePrepended = [];
          const toBeRemoved = [];
          const inserted = [];

          buffer.forEach((wrapper, i) => {
            switch (wrapper.op) {
              case 'prepend':
                toBePrepended.unshift(wrapper);
                break;
              case 'append':
                insertWrapperContent(wrapper, i);
                wrapper.op = 'none';
                inserted.push(wrapper);
                break;
              case 'insert':
                promises = promises.concat(createElement(wrapper, i, viewport.insertElementAnimated));
                wrapper.op = 'none';
                inserted.push(wrapper);
                break;
              case 'remove':
                toBeRemoved.push(wrapper);
            }
          });

          toBeRemoved.forEach((wrapper) => promises = promises.concat(buffer.remove(wrapper)));

          if (toBePrepended.length)
            toBePrepended.forEach((wrapper) => {
              insertWrapperContent(wrapper);
              wrapper.op = 'none';
            });

          buffer.forEach((item, i) => item.scope.$index = buffer.first + i);

          return {
            prepended: toBePrepended,
            removed: toBeRemoved,
            inserted: inserted,
            animated: promises
          };

        }

        function updatePaddings(rid, updates) {
          // schedule another adjustBuffer after animation completion
          if (updates.animated.length) {
            $q.all(updates.animated).then(() => {
              viewport.adjustPadding();
              adjustBuffer(rid);
            });
          } else {
            viewport.adjustPadding();
          }
        }

        function enqueueFetch(rid, updates) {
          if (viewport.shouldLoadBottom()) {
            if (!updates || buffer.effectiveHeight(updates.inserted) > 0) {
              // this means that at least one item appended in the last batch has height > 0
              if (pending.push(true) === 1) {
                fetch(rid);
                adapter.loading(true);
              }
            }
          } else if (viewport.shouldLoadTop()) {
            if ((!updates || buffer.effectiveHeight(updates.prepended) > 0) || pending[0]) {
              // this means that at least one item appended in the last batch has height > 0
              // pending[0] = true means that previous fetch was appending. We need to force at least one prepend
              // BTW there will always be at least 1 element in the pending array because bottom is fetched first
              if (pending.push(false) === 1) {
                fetch(rid);
                adapter.loading(true);
              }
            }
          }
        }

        function adjustBuffer(rid) {
          if (!rid) { // dismiss pending requests
            pending = [];
            rid = ++ridActual;
          }

          let updates = updateDOM();

          // We need the item bindings to be processed before we can do adjustment
          $timeout(() => {

            // show elements after data binging has been done
            updates.inserted.forEach(w => w.element.removeClass('ng-hide'));
            updates.prepended.forEach(w => w.element.removeClass('ng-hide'));

            if (isInvalid(rid)) {
              return;
            }

            updatePaddings(rid, updates);
            enqueueFetch(rid);

            if (!pending.length) {
              adapter.calculateProperties();
            }
          });
        }

        function adjustBufferAfterFetch(rid) {
          let updates = updateDOM();

          // We need the item bindings to be processed before we can do adjustment
          $timeout(() => {

            // show elements after data binging has been done
            updates.inserted.forEach(w => w.element.removeClass('ng-hide'));
            updates.prepended.forEach(w => w.element.removeClass('ng-hide'));

            viewport.adjustScrollTopAfterPrepend(updates);

            if (isInvalid(rid)) {
              return;
            }

            updatePaddings(rid, updates);
            enqueueFetch(rid, updates);
            pending.shift();

            if (pending.length)
              fetch(rid);
            else {
              adapter.loading(false);
              bindEvents();
              adapter.calculateProperties();
            }
          });
        }

        function fetch(rid) {
          if (pending[0]) {// scrolling down
            if (buffer.length && !viewport.shouldLoadBottom()) {
              adjustBufferAfterFetch(rid);
            } else {
              fetchNext((result) => {
                if (isInvalid(rid)) {
                  return;
                }

                if (result.length < bufferSize) {
                  buffer.eof = true;
                }

                if (result.length > 0) {
                  viewport.clipTop();
                  buffer.append(result);
                }

                adjustBufferAfterFetch(rid);
              });
            }
          } else {  // scrolling up
            if (buffer.length && !viewport.shouldLoadTop()) {
              adjustBufferAfterFetch(rid);
            } else {
              fetchPrevious((result) => {
                if (isInvalid(rid)) {
                  return;
                }

                if (result.length < bufferSize) {
                  buffer.bof = true;
                  // log 'bof is reached'
                }

                if (result.length > 0) {
                  if (buffer.length) {
                    viewport.clipBottom();
                  }
                  buffer.prepend(result);
                }

                adjustBufferAfterFetch(rid);
              });
            }
          }
        }

        function resizeAndScrollHandler() {
          if (!$rootScope.$$phase && !adapter.isLoading && !adapter.disabled) {

            enqueueFetch(ridActual);

            if (pending.length) {
              unbindEvents();
            } else {
              adapter.calculateProperties();
              $scope.$apply();
            }
          }
        }

        function wheelHandler(event) {
          if (!adapter.disabled) {
            let scrollTop = viewport[0].scrollTop;
            let yMax = viewport[0].scrollHeight - viewport[0].clientHeight;

            if ((scrollTop === 0 && !buffer.bof) || (scrollTop === yMax && !buffer.eof)) {
              event.preventDefault();
            }
          }
        }
      }

    }
  ]);
Back to Directory File Manager