1*bd500cd2Sbeccabroek/**
2*bd500cd2Sbeccabroek * dirPagination - AngularJS module for paginating (almost) anything.
3*bd500cd2Sbeccabroek * https://github.com/michaelbromley/angularUtils
4*bd500cd2Sbeccabroek *
5*bd500cd2Sbeccabroek *
6*bd500cd2Sbeccabroek * Credits
7*bd500cd2Sbeccabroek * =======
8*bd500cd2Sbeccabroek *
9*bd500cd2Sbeccabroek * Daniel Tabuenca:
10*bd500cd2Sbeccabroek * https://groups.google.com/d/msg/angular/an9QpzqIYiM/r8v-3W1X5vcJ for the idea
11*bd500cd2Sbeccabroek * on how to dynamically invoke the ng-repeat directive.
12*bd500cd2Sbeccabroek *
13*bd500cd2Sbeccabroek * I borrowed a couple of lines and a few attribute names from the AngularUI
14*bd500cd2Sbeccabroek * Bootstrap project:
15*bd500cd2Sbeccabroek * https://github.com/angular-ui/bootstrap/blob/master/src/pagination/pagination.js
16*bd500cd2Sbeccabroek *
17*bd500cd2Sbeccabroek * Copyright 2014 Michael Bromley <michael@michaelbromley.co.uk>
18*bd500cd2Sbeccabroek */
19*bd500cd2Sbeccabroek
20*bd500cd2Sbeccabroek(function() {
21*bd500cd2Sbeccabroek
22*bd500cd2Sbeccabroek/**
23*bd500cd2Sbeccabroek * Config
24*bd500cd2Sbeccabroek */
25*bd500cd2Sbeccabroekvar moduleName = 'app.common.directives.dirPagination';
26*bd500cd2Sbeccabroekvar DEFAULT_ID = '__default';
27*bd500cd2Sbeccabroek
28*bd500cd2Sbeccabroek/**
29*bd500cd2Sbeccabroek * Module
30*bd500cd2Sbeccabroek */
31*bd500cd2Sbeccabroekangular.module(moduleName, [])
32*bd500cd2Sbeccabroek    .directive(
33*bd500cd2Sbeccabroek        'dirPaginate',
34*bd500cd2Sbeccabroek        ['$compile', '$parse', 'paginationService', dirPaginateDirective])
35*bd500cd2Sbeccabroek    .directive('dirPaginateNoCompile', noCompileDirective)
36*bd500cd2Sbeccabroek    .directive(
37*bd500cd2Sbeccabroek        'dirPaginationControls',
38*bd500cd2Sbeccabroek        [
39*bd500cd2Sbeccabroek          'paginationService', 'paginationTemplate',
40*bd500cd2Sbeccabroek          dirPaginationControlsDirective
41*bd500cd2Sbeccabroek        ])
42*bd500cd2Sbeccabroek    .filter('itemsPerPage', ['paginationService', itemsPerPageFilter])
43*bd500cd2Sbeccabroek    .service('paginationService', paginationService)
44*bd500cd2Sbeccabroek    .provider('paginationTemplate', paginationTemplateProvider)
45*bd500cd2Sbeccabroek    .run(['$templateCache', dirPaginationControlsTemplateInstaller]);
46*bd500cd2Sbeccabroek
47*bd500cd2Sbeccabroekfunction dirPaginateDirective($compile, $parse, paginationService) {
48*bd500cd2Sbeccabroek  return {
49*bd500cd2Sbeccabroek    terminal: true,
50*bd500cd2Sbeccabroek    multiElement: true,
51*bd500cd2Sbeccabroek    priority: 100,
52*bd500cd2Sbeccabroek    compile: dirPaginationCompileFn
53*bd500cd2Sbeccabroek  };
54*bd500cd2Sbeccabroek
55*bd500cd2Sbeccabroek  function dirPaginationCompileFn(tElement, tAttrs) {
56*bd500cd2Sbeccabroek    var expression = tAttrs.dirPaginate;
57*bd500cd2Sbeccabroek    // regex taken directly from
58*bd500cd2Sbeccabroek    // https://github.com/angular/angular.js/blob/v1.4.x/src/ng/directive/ngRepeat.js#L339
59*bd500cd2Sbeccabroek    var match = expression.match(
60*bd500cd2Sbeccabroek        /^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+track\s+by\s+([\s\S]+?))?\s*$/);
61*bd500cd2Sbeccabroek
62*bd500cd2Sbeccabroek    var filterPattern =
63*bd500cd2Sbeccabroek        /\|\s*itemsPerPage\s*:\s*(.*\(\s*\w*\)|([^\)]*?(?=\s+as\s+))|[^\)]*)/;
64*bd500cd2Sbeccabroek    if (match[2].match(filterPattern) === null) {
65*bd500cd2Sbeccabroek      throw 'pagination directive: the \'itemsPerPage\' filter must be set.';
66*bd500cd2Sbeccabroek    }
67*bd500cd2Sbeccabroek    var itemsPerPageFilterRemoved = match[2].replace(filterPattern, '');
68*bd500cd2Sbeccabroek    var collectionGetter = $parse(itemsPerPageFilterRemoved);
69*bd500cd2Sbeccabroek
70*bd500cd2Sbeccabroek    addNoCompileAttributes(tElement);
71*bd500cd2Sbeccabroek
72*bd500cd2Sbeccabroek    // If any value is specified for paginationId, we register the un-evaluated
73*bd500cd2Sbeccabroek    // expression at this stage for the benefit of any dir-pagination-controls
74*bd500cd2Sbeccabroek    // directives that may be looking for this ID.
75*bd500cd2Sbeccabroek    var rawId = tAttrs.paginationId || DEFAULT_ID;
76*bd500cd2Sbeccabroek    paginationService.registerInstance(rawId);
77*bd500cd2Sbeccabroek
78*bd500cd2Sbeccabroek    return function dirPaginationLinkFn(scope, element, attrs) {
79*bd500cd2Sbeccabroek      // Now that we have access to the `scope` we can interpolate any
80*bd500cd2Sbeccabroek      // expression given in the paginationId attribute and potentially register
81*bd500cd2Sbeccabroek      // a new ID if it evaluates to a different value than the rawId.
82*bd500cd2Sbeccabroek      var paginationId =
83*bd500cd2Sbeccabroek          $parse(attrs.paginationId)(scope) || attrs.paginationId || DEFAULT_ID;
84*bd500cd2Sbeccabroek
85*bd500cd2Sbeccabroek      // (TODO: this seems sound, but I'm reverting as many bug reports followed
86*bd500cd2Sbeccabroek      // it's introduction in 0.11.0. Needs more investigation.) In case rawId
87*bd500cd2Sbeccabroek      // != paginationId we deregister using rawId for the sake of general
88*bd500cd2Sbeccabroek      // cleanliness before registering using paginationId
89*bd500cd2Sbeccabroek      // paginationService.deregisterInstance(rawId);
90*bd500cd2Sbeccabroek      paginationService.registerInstance(paginationId);
91*bd500cd2Sbeccabroek
92*bd500cd2Sbeccabroek      var repeatExpression = getRepeatExpression(expression, paginationId);
93*bd500cd2Sbeccabroek      addNgRepeatToElement(element, attrs, repeatExpression);
94*bd500cd2Sbeccabroek
95*bd500cd2Sbeccabroek      removeTemporaryAttributes(element);
96*bd500cd2Sbeccabroek      var compiled = $compile(element);
97*bd500cd2Sbeccabroek
98*bd500cd2Sbeccabroek      var currentPageGetter =
99*bd500cd2Sbeccabroek          makeCurrentPageGetterFn(scope, attrs, paginationId);
100*bd500cd2Sbeccabroek      paginationService.setCurrentPageParser(
101*bd500cd2Sbeccabroek          paginationId, currentPageGetter, scope);
102*bd500cd2Sbeccabroek
103*bd500cd2Sbeccabroek      if (typeof attrs.totalItems !== 'undefined') {
104*bd500cd2Sbeccabroek        paginationService.setAsyncModeTrue(paginationId);
105*bd500cd2Sbeccabroek        scope.$watch(
106*bd500cd2Sbeccabroek            function() {
107*bd500cd2Sbeccabroek              return $parse(attrs.totalItems)(scope);
108*bd500cd2Sbeccabroek            },
109*bd500cd2Sbeccabroek            function(result) {
110*bd500cd2Sbeccabroek              if (0 <= result) {
111*bd500cd2Sbeccabroek                paginationService.setCollectionLength(paginationId, result);
112*bd500cd2Sbeccabroek              }
113*bd500cd2Sbeccabroek            });
114*bd500cd2Sbeccabroek      } else {
115*bd500cd2Sbeccabroek        paginationService.setAsyncModeFalse(paginationId);
116*bd500cd2Sbeccabroek        scope.$watchCollection(
117*bd500cd2Sbeccabroek            function() {
118*bd500cd2Sbeccabroek              return collectionGetter(scope);
119*bd500cd2Sbeccabroek            },
120*bd500cd2Sbeccabroek            function(collection) {
121*bd500cd2Sbeccabroek              if (collection) {
122*bd500cd2Sbeccabroek                var collectionLength = (collection instanceof Array) ?
123*bd500cd2Sbeccabroek                    collection.length :
124*bd500cd2Sbeccabroek                    Object.keys(collection).length;
125*bd500cd2Sbeccabroek                paginationService.setCollectionLength(
126*bd500cd2Sbeccabroek                    paginationId, collectionLength);
127*bd500cd2Sbeccabroek              }
128*bd500cd2Sbeccabroek            });
129*bd500cd2Sbeccabroek      }
130*bd500cd2Sbeccabroek
131*bd500cd2Sbeccabroek      // Delegate to the link function returned by the new compilation of the
132*bd500cd2Sbeccabroek      // ng-repeat
133*bd500cd2Sbeccabroek      compiled(scope);
134*bd500cd2Sbeccabroek
135*bd500cd2Sbeccabroek      // (TODO: Reverting this due to many bug reports in v 0.11.0. Needs
136*bd500cd2Sbeccabroek      // investigation as the principle is sound) When the scope is destroyed,
137*bd500cd2Sbeccabroek      // we make sure to remove the reference to it in paginationService so that
138*bd500cd2Sbeccabroek      // it can be properly garbage collected scope.$on('$destroy', function
139*bd500cd2Sbeccabroek      // destroyDirPagination() {
140*bd500cd2Sbeccabroek      //     paginationService.deregisterInstance(paginationId);
141*bd500cd2Sbeccabroek      // });
142*bd500cd2Sbeccabroek    };
143*bd500cd2Sbeccabroek  }
144*bd500cd2Sbeccabroek
145*bd500cd2Sbeccabroek  /**
146*bd500cd2Sbeccabroek   * If a pagination id has been specified, we need to check that it is present
147*bd500cd2Sbeccabroek   * as the second argument passed to the itemsPerPage filter. If it is not
148*bd500cd2Sbeccabroek   * there, we add it and return the modified expression.
149*bd500cd2Sbeccabroek   *
150*bd500cd2Sbeccabroek   * @param expression
151*bd500cd2Sbeccabroek   * @param paginationId
152*bd500cd2Sbeccabroek   * @returns {*}
153*bd500cd2Sbeccabroek   */
154*bd500cd2Sbeccabroek  function getRepeatExpression(expression, paginationId) {
155*bd500cd2Sbeccabroek    var repeatExpression,
156*bd500cd2Sbeccabroek        idDefinedInFilter =
157*bd500cd2Sbeccabroek            !!expression.match(/(\|\s*itemsPerPage\s*:[^|]*:[^|]*)/);
158*bd500cd2Sbeccabroek
159*bd500cd2Sbeccabroek    if (paginationId !== DEFAULT_ID && !idDefinedInFilter) {
160*bd500cd2Sbeccabroek      repeatExpression = expression.replace(
161*bd500cd2Sbeccabroek          /(\|\s*itemsPerPage\s*:\s*[^|\s]*)/, '$1 : \'' + paginationId + '\'');
162*bd500cd2Sbeccabroek    } else {
163*bd500cd2Sbeccabroek      repeatExpression = expression;
164*bd500cd2Sbeccabroek    }
165*bd500cd2Sbeccabroek
166*bd500cd2Sbeccabroek    return repeatExpression;
167*bd500cd2Sbeccabroek  }
168*bd500cd2Sbeccabroek
169*bd500cd2Sbeccabroek  /**
170*bd500cd2Sbeccabroek   * Adds the ng-repeat directive to the element. In the case of multi-element
171*bd500cd2Sbeccabroek   * (-start, -end) it adds the appropriate multi-element ng-repeat to the first
172*bd500cd2Sbeccabroek   * and last element in the range.
173*bd500cd2Sbeccabroek   * @param element
174*bd500cd2Sbeccabroek   * @param attrs
175*bd500cd2Sbeccabroek   * @param repeatExpression
176*bd500cd2Sbeccabroek   */
177*bd500cd2Sbeccabroek  function addNgRepeatToElement(element, attrs, repeatExpression) {
178*bd500cd2Sbeccabroek    if (element[0].hasAttribute('dir-paginate-start') ||
179*bd500cd2Sbeccabroek        element[0].hasAttribute('data-dir-paginate-start')) {
180*bd500cd2Sbeccabroek      // using multiElement mode (dir-paginate-start, dir-paginate-end)
181*bd500cd2Sbeccabroek      attrs.$set('ngRepeatStart', repeatExpression);
182*bd500cd2Sbeccabroek      element.eq(element.length - 1).attr('ng-repeat-end', true);
183*bd500cd2Sbeccabroek    } else {
184*bd500cd2Sbeccabroek      attrs.$set('ngRepeat', repeatExpression);
185*bd500cd2Sbeccabroek    }
186*bd500cd2Sbeccabroek  }
187*bd500cd2Sbeccabroek
188*bd500cd2Sbeccabroek  /**
189*bd500cd2Sbeccabroek   * Adds the dir-paginate-no-compile directive to each element in the tElement
190*bd500cd2Sbeccabroek   * range.
191*bd500cd2Sbeccabroek   * @param tElement
192*bd500cd2Sbeccabroek   */
193*bd500cd2Sbeccabroek  function addNoCompileAttributes(tElement) {
194*bd500cd2Sbeccabroek    angular.forEach(tElement, function(el) {
195*bd500cd2Sbeccabroek      if (el.nodeType === 1) {
196*bd500cd2Sbeccabroek        angular.element(el).attr('dir-paginate-no-compile', true);
197*bd500cd2Sbeccabroek      }
198*bd500cd2Sbeccabroek    });
199*bd500cd2Sbeccabroek  }
200*bd500cd2Sbeccabroek
201*bd500cd2Sbeccabroek  /**
202*bd500cd2Sbeccabroek   * Removes the variations on dir-paginate (data-, -start, -end) and the
203*bd500cd2Sbeccabroek   * dir-paginate-no-compile directives.
204*bd500cd2Sbeccabroek   * @param element
205*bd500cd2Sbeccabroek   */
206*bd500cd2Sbeccabroek  function removeTemporaryAttributes(element) {
207*bd500cd2Sbeccabroek    angular.forEach(element, function(el) {
208*bd500cd2Sbeccabroek      if (el.nodeType === 1) {
209*bd500cd2Sbeccabroek        angular.element(el).removeAttr('dir-paginate-no-compile');
210*bd500cd2Sbeccabroek      }
211*bd500cd2Sbeccabroek    });
212*bd500cd2Sbeccabroek    element.eq(0)
213*bd500cd2Sbeccabroek        .removeAttr('dir-paginate-start')
214*bd500cd2Sbeccabroek        .removeAttr('dir-paginate')
215*bd500cd2Sbeccabroek        .removeAttr('data-dir-paginate-start')
216*bd500cd2Sbeccabroek        .removeAttr('data-dir-paginate');
217*bd500cd2Sbeccabroek    element.eq(element.length - 1)
218*bd500cd2Sbeccabroek        .removeAttr('dir-paginate-end')
219*bd500cd2Sbeccabroek        .removeAttr('data-dir-paginate-end');
220*bd500cd2Sbeccabroek  }
221*bd500cd2Sbeccabroek
222*bd500cd2Sbeccabroek  /**
223*bd500cd2Sbeccabroek   * Creates a getter function for the current-page attribute, using the
224*bd500cd2Sbeccabroek   * expression provided or a default value if no current-page expression was
225*bd500cd2Sbeccabroek   * specified.
226*bd500cd2Sbeccabroek   *
227*bd500cd2Sbeccabroek   * @param scope
228*bd500cd2Sbeccabroek   * @param attrs
229*bd500cd2Sbeccabroek   * @param paginationId
230*bd500cd2Sbeccabroek   * @returns {*}
231*bd500cd2Sbeccabroek   */
232*bd500cd2Sbeccabroek  function makeCurrentPageGetterFn(scope, attrs, paginationId) {
233*bd500cd2Sbeccabroek    var currentPageGetter;
234*bd500cd2Sbeccabroek    if (attrs.currentPage) {
235*bd500cd2Sbeccabroek      currentPageGetter = $parse(attrs.currentPage);
236*bd500cd2Sbeccabroek    } else {
237*bd500cd2Sbeccabroek      // If the current-page attribute was not set, we'll make our own.
238*bd500cd2Sbeccabroek      // Replace any non-alphanumeric characters which might confuse
239*bd500cd2Sbeccabroek      // the $parse service and give unexpected results.
240*bd500cd2Sbeccabroek      // See https://github.com/michaelbromley/angularUtils/issues/233
241*bd500cd2Sbeccabroek      var defaultCurrentPage =
242*bd500cd2Sbeccabroek          (paginationId + '__currentPage').replace(/\W/g, '_');
243*bd500cd2Sbeccabroek      scope[defaultCurrentPage] = 1;
244*bd500cd2Sbeccabroek      currentPageGetter = $parse(defaultCurrentPage);
245*bd500cd2Sbeccabroek    }
246*bd500cd2Sbeccabroek    return currentPageGetter;
247*bd500cd2Sbeccabroek  }
248*bd500cd2Sbeccabroek}
249*bd500cd2Sbeccabroek
250*bd500cd2Sbeccabroek/**
251*bd500cd2Sbeccabroek * This is a helper directive that allows correct compilation when in
252*bd500cd2Sbeccabroek * multi-element mode (ie dir-paginate-start, dir-paginate-end). It is
253*bd500cd2Sbeccabroek * dynamically added to all elements in the dir-paginate compile function, and
254*bd500cd2Sbeccabroek * it prevents further compilation of any inner directives. It is then removed
255*bd500cd2Sbeccabroek * in the link function, and all inner directives are then manually compiled.
256*bd500cd2Sbeccabroek */
257*bd500cd2Sbeccabroekfunction noCompileDirective() {
258*bd500cd2Sbeccabroek  return {priority: 5000, terminal: true};
259*bd500cd2Sbeccabroek}
260*bd500cd2Sbeccabroek
261*bd500cd2Sbeccabroekfunction dirPaginationControlsTemplateInstaller($templateCache) {
262*bd500cd2Sbeccabroek  $templateCache.put(
263*bd500cd2Sbeccabroek      'app.common.directives.dirPagination.template',
264*bd500cd2Sbeccabroek      '<ul class="pagination" ng-if="1 < pages.length || !autoHide"><li ng-if="boundaryLinks" ng-class="{ disabled : pagination.current == 1 }"><a href="" ng-click="setCurrent(1)">&laquo;</a></li><li ng-if="directionLinks" ng-class="{ disabled : pagination.current == 1 }"><a href="" ng-click="setCurrent(pagination.current - 1)">&lsaquo;</a></li><li ng-repeat="pageNumber in pages track by tracker(pageNumber, $index)" ng-class="{ active : pagination.current == pageNumber, disabled : pageNumber == \'...\' || ( ! autoHide && pages.length === 1 ) }"><a href="" ng-click="setCurrent(pageNumber)">{{ pageNumber }}</a></li><li ng-if="directionLinks" ng-class="{ disabled : pagination.current == pagination.last }"><a href="" ng-click="setCurrent(pagination.current + 1)">&rsaquo;</a></li><li ng-if="boundaryLinks"  ng-class="{ disabled : pagination.current == pagination.last }"><a href="" ng-click="setCurrent(pagination.last)">&raquo;</a></li></ul>');
265*bd500cd2Sbeccabroek}
266*bd500cd2Sbeccabroek
267*bd500cd2Sbeccabroekfunction dirPaginationControlsDirective(paginationService, paginationTemplate) {
268*bd500cd2Sbeccabroek  var numberRegex = /^\d+$/;
269*bd500cd2Sbeccabroek
270*bd500cd2Sbeccabroek  var DDO = {
271*bd500cd2Sbeccabroek    restrict: 'AE',
272*bd500cd2Sbeccabroek    scope:
273*bd500cd2Sbeccabroek        {maxSize: '=?', onPageChange: '&?', paginationId: '=?', autoHide: '=?'},
274*bd500cd2Sbeccabroek    link: dirPaginationControlsLinkFn
275*bd500cd2Sbeccabroek  };
276*bd500cd2Sbeccabroek
277*bd500cd2Sbeccabroek  // We need to check the paginationTemplate service to see whether a template
278*bd500cd2Sbeccabroek  // path or string has been specified, and add the `template` or `templateUrl`
279*bd500cd2Sbeccabroek  // property to the DDO as appropriate. The order of priority to decide which
280*bd500cd2Sbeccabroek  // template to use is (highest priority first):
281*bd500cd2Sbeccabroek  // 1. paginationTemplate.getString()
282*bd500cd2Sbeccabroek  // 2. attrs.templateUrl
283*bd500cd2Sbeccabroek  // 3. paginationTemplate.getPath()
284*bd500cd2Sbeccabroek  var templateString = paginationTemplate.getString();
285*bd500cd2Sbeccabroek  if (templateString !== undefined) {
286*bd500cd2Sbeccabroek    DDO.template = templateString;
287*bd500cd2Sbeccabroek  } else {
288*bd500cd2Sbeccabroek    DDO.templateUrl = function(elem, attrs) {
289*bd500cd2Sbeccabroek      return attrs.templateUrl || paginationTemplate.getPath();
290*bd500cd2Sbeccabroek    };
291*bd500cd2Sbeccabroek  }
292*bd500cd2Sbeccabroek  return DDO;
293*bd500cd2Sbeccabroek
294*bd500cd2Sbeccabroek  function dirPaginationControlsLinkFn(scope, element, attrs) {
295*bd500cd2Sbeccabroek    // rawId is the un-interpolated value of the pagination-id attribute. This
296*bd500cd2Sbeccabroek    // is only important when the corresponding dir-paginate directive has not
297*bd500cd2Sbeccabroek    // yet been linked (e.g. if it is inside an ng-if block), and in that case
298*bd500cd2Sbeccabroek    // it prevents this controls directive from assuming that there is no
299*bd500cd2Sbeccabroek    // corresponding dir-paginate directive and wrongly throwing an exception.
300*bd500cd2Sbeccabroek    var rawId = attrs.paginationId || DEFAULT_ID;
301*bd500cd2Sbeccabroek    var paginationId = scope.paginationId || attrs.paginationId || DEFAULT_ID;
302*bd500cd2Sbeccabroek
303*bd500cd2Sbeccabroek    if (!paginationService.isRegistered(paginationId) &&
304*bd500cd2Sbeccabroek        !paginationService.isRegistered(rawId)) {
305*bd500cd2Sbeccabroek      var idMessage =
306*bd500cd2Sbeccabroek          (paginationId !== DEFAULT_ID) ? ' (id: ' + paginationId + ') ' : ' ';
307*bd500cd2Sbeccabroek      if (window.console) {
308*bd500cd2Sbeccabroek        console.warn(
309*bd500cd2Sbeccabroek            'Pagination directive: the pagination controls' + idMessage +
310*bd500cd2Sbeccabroek            'cannot be used without the corresponding pagination directive, which was not found at link time.');
311*bd500cd2Sbeccabroek      }
312*bd500cd2Sbeccabroek    }
313*bd500cd2Sbeccabroek
314*bd500cd2Sbeccabroek    if (!scope.maxSize) {
315*bd500cd2Sbeccabroek      scope.maxSize = 9;
316*bd500cd2Sbeccabroek    }
317*bd500cd2Sbeccabroek    scope.autoHide = scope.autoHide === undefined ? true : scope.autoHide;
318*bd500cd2Sbeccabroek    scope.directionLinks = angular.isDefined(attrs.directionLinks) ?
319*bd500cd2Sbeccabroek        scope.$parent.$eval(attrs.directionLinks) :
320*bd500cd2Sbeccabroek        true;
321*bd500cd2Sbeccabroek    scope.boundaryLinks = angular.isDefined(attrs.boundaryLinks) ?
322*bd500cd2Sbeccabroek        scope.$parent.$eval(attrs.boundaryLinks) :
323*bd500cd2Sbeccabroek        false;
324*bd500cd2Sbeccabroek
325*bd500cd2Sbeccabroek    var paginationRange = Math.max(scope.maxSize, 5);
326*bd500cd2Sbeccabroek    scope.pages = [];
327*bd500cd2Sbeccabroek    scope.pagination = {last: 1, current: 1};
328*bd500cd2Sbeccabroek    scope.range = {lower: 1, upper: 1, total: 1};
329*bd500cd2Sbeccabroek
330*bd500cd2Sbeccabroek    scope.$watch('maxSize', function(val) {
331*bd500cd2Sbeccabroek      if (val) {
332*bd500cd2Sbeccabroek        paginationRange = Math.max(scope.maxSize, 5);
333*bd500cd2Sbeccabroek        generatePagination();
334*bd500cd2Sbeccabroek      }
335*bd500cd2Sbeccabroek    });
336*bd500cd2Sbeccabroek
337*bd500cd2Sbeccabroek    scope.$watch(
338*bd500cd2Sbeccabroek        function() {
339*bd500cd2Sbeccabroek          if (paginationService.isRegistered(paginationId)) {
340*bd500cd2Sbeccabroek            return (paginationService.getCollectionLength(paginationId) + 1) *
341*bd500cd2Sbeccabroek                paginationService.getItemsPerPage(paginationId);
342*bd500cd2Sbeccabroek          }
343*bd500cd2Sbeccabroek        },
344*bd500cd2Sbeccabroek        function(length) {
345*bd500cd2Sbeccabroek          if (0 < length) {
346*bd500cd2Sbeccabroek            generatePagination();
347*bd500cd2Sbeccabroek          }
348*bd500cd2Sbeccabroek        });
349*bd500cd2Sbeccabroek
350*bd500cd2Sbeccabroek    scope.$watch(
351*bd500cd2Sbeccabroek        function() {
352*bd500cd2Sbeccabroek          if (paginationService.isRegistered(paginationId)) {
353*bd500cd2Sbeccabroek            return (paginationService.getItemsPerPage(paginationId));
354*bd500cd2Sbeccabroek          }
355*bd500cd2Sbeccabroek        },
356*bd500cd2Sbeccabroek        function(current, previous) {
357*bd500cd2Sbeccabroek          if (current != previous && typeof previous !== 'undefined') {
358*bd500cd2Sbeccabroek            goToPage(scope.pagination.current);
359*bd500cd2Sbeccabroek          }
360*bd500cd2Sbeccabroek        });
361*bd500cd2Sbeccabroek
362*bd500cd2Sbeccabroek    scope.$watch(
363*bd500cd2Sbeccabroek        function() {
364*bd500cd2Sbeccabroek          if (paginationService.isRegistered(paginationId)) {
365*bd500cd2Sbeccabroek            return paginationService.getCurrentPage(paginationId);
366*bd500cd2Sbeccabroek          }
367*bd500cd2Sbeccabroek        },
368*bd500cd2Sbeccabroek        function(currentPage, previousPage) {
369*bd500cd2Sbeccabroek          if (currentPage != previousPage) {
370*bd500cd2Sbeccabroek            goToPage(currentPage);
371*bd500cd2Sbeccabroek          }
372*bd500cd2Sbeccabroek        });
373*bd500cd2Sbeccabroek
374*bd500cd2Sbeccabroek    scope.setCurrent = function(num) {
375*bd500cd2Sbeccabroek      if (paginationService.isRegistered(paginationId) &&
376*bd500cd2Sbeccabroek          isValidPageNumber(num)) {
377*bd500cd2Sbeccabroek        num = parseInt(num, 10);
378*bd500cd2Sbeccabroek        paginationService.setCurrentPage(paginationId, num);
379*bd500cd2Sbeccabroek      }
380*bd500cd2Sbeccabroek    };
381*bd500cd2Sbeccabroek
382*bd500cd2Sbeccabroek    /**
383*bd500cd2Sbeccabroek     * Custom "track by" function which allows for duplicate "..." entries on
384*bd500cd2Sbeccabroek     * long lists, yet fixes the problem of wrongly-highlighted links which
385*bd500cd2Sbeccabroek     * happens when using "track by $index" - see
386*bd500cd2Sbeccabroek     * https://github.com/michaelbromley/angularUtils/issues/153
387*bd500cd2Sbeccabroek     * @param id
388*bd500cd2Sbeccabroek     * @param index
389*bd500cd2Sbeccabroek     * @returns {string}
390*bd500cd2Sbeccabroek     */
391*bd500cd2Sbeccabroek    scope.tracker = function(id, index) {
392*bd500cd2Sbeccabroek      return id + '_' + index;
393*bd500cd2Sbeccabroek    };
394*bd500cd2Sbeccabroek
395*bd500cd2Sbeccabroek    function goToPage(num) {
396*bd500cd2Sbeccabroek      if (paginationService.isRegistered(paginationId) &&
397*bd500cd2Sbeccabroek          isValidPageNumber(num)) {
398*bd500cd2Sbeccabroek        var oldPageNumber = scope.pagination.current;
399*bd500cd2Sbeccabroek
400*bd500cd2Sbeccabroek        scope.pages = generatePagesArray(
401*bd500cd2Sbeccabroek            num, paginationService.getCollectionLength(paginationId),
402*bd500cd2Sbeccabroek            paginationService.getItemsPerPage(paginationId), paginationRange);
403*bd500cd2Sbeccabroek        scope.pagination.current = num;
404*bd500cd2Sbeccabroek        updateRangeValues();
405*bd500cd2Sbeccabroek
406*bd500cd2Sbeccabroek        // if a callback has been set, then call it with the page number as the
407*bd500cd2Sbeccabroek        // first argument and the previous page number as a second argument
408*bd500cd2Sbeccabroek        if (scope.onPageChange) {
409*bd500cd2Sbeccabroek          scope.onPageChange(
410*bd500cd2Sbeccabroek              {newPageNumber: num, oldPageNumber: oldPageNumber});
411*bd500cd2Sbeccabroek        }
412*bd500cd2Sbeccabroek      }
413*bd500cd2Sbeccabroek    }
414*bd500cd2Sbeccabroek
415*bd500cd2Sbeccabroek    function generatePagination() {
416*bd500cd2Sbeccabroek      if (paginationService.isRegistered(paginationId)) {
417*bd500cd2Sbeccabroek        var page =
418*bd500cd2Sbeccabroek            parseInt(paginationService.getCurrentPage(paginationId)) || 1;
419*bd500cd2Sbeccabroek        scope.pages = generatePagesArray(
420*bd500cd2Sbeccabroek            page, paginationService.getCollectionLength(paginationId),
421*bd500cd2Sbeccabroek            paginationService.getItemsPerPage(paginationId), paginationRange);
422*bd500cd2Sbeccabroek        scope.pagination.current = page;
423*bd500cd2Sbeccabroek        scope.pagination.last = scope.pages[scope.pages.length - 1];
424*bd500cd2Sbeccabroek        if (scope.pagination.last < scope.pagination.current) {
425*bd500cd2Sbeccabroek          scope.setCurrent(scope.pagination.last);
426*bd500cd2Sbeccabroek        } else {
427*bd500cd2Sbeccabroek          updateRangeValues();
428*bd500cd2Sbeccabroek        }
429*bd500cd2Sbeccabroek      }
430*bd500cd2Sbeccabroek    }
431*bd500cd2Sbeccabroek
432*bd500cd2Sbeccabroek    /**
433*bd500cd2Sbeccabroek     * This function updates the values (lower, upper, total) of the
434*bd500cd2Sbeccabroek     * `scope.range` object, which can be used in the pagination template to
435*bd500cd2Sbeccabroek     * display the current page range, e.g. "showing 21 - 40 of 144 results";
436*bd500cd2Sbeccabroek     */
437*bd500cd2Sbeccabroek    function updateRangeValues() {
438*bd500cd2Sbeccabroek      if (paginationService.isRegistered(paginationId)) {
439*bd500cd2Sbeccabroek        var currentPage = paginationService.getCurrentPage(paginationId),
440*bd500cd2Sbeccabroek            itemsPerPage = paginationService.getItemsPerPage(paginationId),
441*bd500cd2Sbeccabroek            totalItems = paginationService.getCollectionLength(paginationId);
442*bd500cd2Sbeccabroek
443*bd500cd2Sbeccabroek        scope.range.lower = (currentPage - 1) * itemsPerPage + 1;
444*bd500cd2Sbeccabroek        scope.range.upper = Math.min(currentPage * itemsPerPage, totalItems);
445*bd500cd2Sbeccabroek        scope.range.total = totalItems;
446*bd500cd2Sbeccabroek      }
447*bd500cd2Sbeccabroek    }
448*bd500cd2Sbeccabroek    function isValidPageNumber(num) {
449*bd500cd2Sbeccabroek      return (
450*bd500cd2Sbeccabroek          numberRegex.test(num) && (0 < num && num <= scope.pagination.last));
451*bd500cd2Sbeccabroek    }
452*bd500cd2Sbeccabroek  }
453*bd500cd2Sbeccabroek
454*bd500cd2Sbeccabroek  /**
455*bd500cd2Sbeccabroek   * Generate an array of page numbers (or the '...' string) which is used in an
456*bd500cd2Sbeccabroek   * ng-repeat to generate the links used in pagination
457*bd500cd2Sbeccabroek   *
458*bd500cd2Sbeccabroek   * @param currentPage
459*bd500cd2Sbeccabroek   * @param rowsPerPage
460*bd500cd2Sbeccabroek   * @param paginationRange
461*bd500cd2Sbeccabroek   * @param collectionLength
462*bd500cd2Sbeccabroek   * @returns {Array}
463*bd500cd2Sbeccabroek   */
464*bd500cd2Sbeccabroek  function generatePagesArray(
465*bd500cd2Sbeccabroek      currentPage, collectionLength, rowsPerPage, paginationRange) {
466*bd500cd2Sbeccabroek    var pages = [];
467*bd500cd2Sbeccabroek    var totalPages = Math.ceil(collectionLength / rowsPerPage);
468*bd500cd2Sbeccabroek    var halfWay = Math.ceil(paginationRange / 2);
469*bd500cd2Sbeccabroek    var position;
470*bd500cd2Sbeccabroek
471*bd500cd2Sbeccabroek    if (currentPage <= halfWay) {
472*bd500cd2Sbeccabroek      position = 'start';
473*bd500cd2Sbeccabroek    } else if (totalPages - halfWay < currentPage) {
474*bd500cd2Sbeccabroek      position = 'end';
475*bd500cd2Sbeccabroek    } else {
476*bd500cd2Sbeccabroek      position = 'middle';
477*bd500cd2Sbeccabroek    }
478*bd500cd2Sbeccabroek
479*bd500cd2Sbeccabroek    var ellipsesNeeded = paginationRange < totalPages;
480*bd500cd2Sbeccabroek    var i = 1;
481*bd500cd2Sbeccabroek    while (i <= totalPages && i <= paginationRange) {
482*bd500cd2Sbeccabroek      var pageNumber =
483*bd500cd2Sbeccabroek          calculatePageNumber(i, currentPage, paginationRange, totalPages);
484*bd500cd2Sbeccabroek
485*bd500cd2Sbeccabroek      var openingEllipsesNeeded =
486*bd500cd2Sbeccabroek          (i === 2 && (position === 'middle' || position === 'end'));
487*bd500cd2Sbeccabroek      var closingEllipsesNeeded =
488*bd500cd2Sbeccabroek          (i === paginationRange - 1 &&
489*bd500cd2Sbeccabroek           (position === 'middle' || position === 'start'));
490*bd500cd2Sbeccabroek      if (ellipsesNeeded && (openingEllipsesNeeded || closingEllipsesNeeded)) {
491*bd500cd2Sbeccabroek        pages.push('...');
492*bd500cd2Sbeccabroek      } else {
493*bd500cd2Sbeccabroek        pages.push(pageNumber);
494*bd500cd2Sbeccabroek      }
495*bd500cd2Sbeccabroek      i++;
496*bd500cd2Sbeccabroek    }
497*bd500cd2Sbeccabroek    return pages;
498*bd500cd2Sbeccabroek  }
499*bd500cd2Sbeccabroek
500*bd500cd2Sbeccabroek  /**
501*bd500cd2Sbeccabroek   * Given the position in the sequence of pagination links [i], figure out what
502*bd500cd2Sbeccabroek   * page number corresponds to that position.
503*bd500cd2Sbeccabroek   *
504*bd500cd2Sbeccabroek   * @param i
505*bd500cd2Sbeccabroek   * @param currentPage
506*bd500cd2Sbeccabroek   * @param paginationRange
507*bd500cd2Sbeccabroek   * @param totalPages
508*bd500cd2Sbeccabroek   * @returns {*}
509*bd500cd2Sbeccabroek   */
510*bd500cd2Sbeccabroek  function calculatePageNumber(i, currentPage, paginationRange, totalPages) {
511*bd500cd2Sbeccabroek    var halfWay = Math.ceil(paginationRange / 2);
512*bd500cd2Sbeccabroek    if (i === paginationRange) {
513*bd500cd2Sbeccabroek      return totalPages;
514*bd500cd2Sbeccabroek    } else if (i === 1) {
515*bd500cd2Sbeccabroek      return i;
516*bd500cd2Sbeccabroek    } else if (paginationRange < totalPages) {
517*bd500cd2Sbeccabroek      if (totalPages - halfWay < currentPage) {
518*bd500cd2Sbeccabroek        return totalPages - paginationRange + i;
519*bd500cd2Sbeccabroek      } else if (halfWay < currentPage) {
520*bd500cd2Sbeccabroek        return currentPage - halfWay + i;
521*bd500cd2Sbeccabroek      } else {
522*bd500cd2Sbeccabroek        return i;
523*bd500cd2Sbeccabroek      }
524*bd500cd2Sbeccabroek    } else {
525*bd500cd2Sbeccabroek      return i;
526*bd500cd2Sbeccabroek    }
527*bd500cd2Sbeccabroek  }
528*bd500cd2Sbeccabroek}
529*bd500cd2Sbeccabroek
530*bd500cd2Sbeccabroek/**
531*bd500cd2Sbeccabroek * This filter slices the collection into pages based on the current page number
532*bd500cd2Sbeccabroek * and number of items per page.
533*bd500cd2Sbeccabroek * @param paginationService
534*bd500cd2Sbeccabroek * @returns {Function}
535*bd500cd2Sbeccabroek */
536*bd500cd2Sbeccabroekfunction itemsPerPageFilter(paginationService) {
537*bd500cd2Sbeccabroek  return function(collection, itemsPerPage, paginationId) {
538*bd500cd2Sbeccabroek    if (typeof (paginationId) === 'undefined') {
539*bd500cd2Sbeccabroek      paginationId = DEFAULT_ID;
540*bd500cd2Sbeccabroek    }
541*bd500cd2Sbeccabroek    if (!paginationService.isRegistered(paginationId)) {
542*bd500cd2Sbeccabroek      throw 'pagination directive: the itemsPerPage id argument (id: ' +
543*bd500cd2Sbeccabroek          paginationId + ') does not match a registered pagination-id.';
544*bd500cd2Sbeccabroek    }
545*bd500cd2Sbeccabroek    var end;
546*bd500cd2Sbeccabroek    var start;
547*bd500cd2Sbeccabroek    if (angular.isObject(collection)) {
548*bd500cd2Sbeccabroek      itemsPerPage = parseInt(itemsPerPage) || 9999999999;
549*bd500cd2Sbeccabroek      if (paginationService.isAsyncMode(paginationId)) {
550*bd500cd2Sbeccabroek        start = 0;
551*bd500cd2Sbeccabroek      } else {
552*bd500cd2Sbeccabroek        start =
553*bd500cd2Sbeccabroek            (paginationService.getCurrentPage(paginationId) - 1) * itemsPerPage;
554*bd500cd2Sbeccabroek      }
555*bd500cd2Sbeccabroek      end = start + itemsPerPage;
556*bd500cd2Sbeccabroek      paginationService.setItemsPerPage(paginationId, itemsPerPage);
557*bd500cd2Sbeccabroek
558*bd500cd2Sbeccabroek      if (collection instanceof Array) {
559*bd500cd2Sbeccabroek        // the array just needs to be sliced
560*bd500cd2Sbeccabroek        return collection.slice(start, end);
561*bd500cd2Sbeccabroek      } else {
562*bd500cd2Sbeccabroek        // in the case of an object, we need to get an array of keys, slice
563*bd500cd2Sbeccabroek        // that, then map back to the original object.
564*bd500cd2Sbeccabroek        var slicedObject = {};
565*bd500cd2Sbeccabroek        angular.forEach(keys(collection).slice(start, end), function(key) {
566*bd500cd2Sbeccabroek          slicedObject[key] = collection[key];
567*bd500cd2Sbeccabroek        });
568*bd500cd2Sbeccabroek        return slicedObject;
569*bd500cd2Sbeccabroek      }
570*bd500cd2Sbeccabroek    } else {
571*bd500cd2Sbeccabroek      return collection;
572*bd500cd2Sbeccabroek    }
573*bd500cd2Sbeccabroek  };
574*bd500cd2Sbeccabroek}
575*bd500cd2Sbeccabroek
576*bd500cd2Sbeccabroek/**
577*bd500cd2Sbeccabroek * Shim for the Object.keys() method which does not exist in IE < 9
578*bd500cd2Sbeccabroek * @param obj
579*bd500cd2Sbeccabroek * @returns {Array}
580*bd500cd2Sbeccabroek */
581*bd500cd2Sbeccabroekfunction keys(obj) {
582*bd500cd2Sbeccabroek  if (!Object.keys) {
583*bd500cd2Sbeccabroek    var objKeys = [];
584*bd500cd2Sbeccabroek    for (var i in obj) {
585*bd500cd2Sbeccabroek      if (obj.hasOwnProperty(i)) {
586*bd500cd2Sbeccabroek        objKeys.push(i);
587*bd500cd2Sbeccabroek      }
588*bd500cd2Sbeccabroek    }
589*bd500cd2Sbeccabroek    return objKeys;
590*bd500cd2Sbeccabroek  } else {
591*bd500cd2Sbeccabroek    return Object.keys(obj);
592*bd500cd2Sbeccabroek  }
593*bd500cd2Sbeccabroek}
594*bd500cd2Sbeccabroek
595*bd500cd2Sbeccabroek/**
596*bd500cd2Sbeccabroek * This service allows the various parts of the module to communicate and stay
597*bd500cd2Sbeccabroek * in sync.
598*bd500cd2Sbeccabroek */
599*bd500cd2Sbeccabroekfunction paginationService() {
600*bd500cd2Sbeccabroek  var instances = {};
601*bd500cd2Sbeccabroek  var lastRegisteredInstance;
602*bd500cd2Sbeccabroek
603*bd500cd2Sbeccabroek  this.registerInstance = function(instanceId) {
604*bd500cd2Sbeccabroek    if (typeof instances[instanceId] === 'undefined') {
605*bd500cd2Sbeccabroek      instances[instanceId] = {asyncMode: false};
606*bd500cd2Sbeccabroek      lastRegisteredInstance = instanceId;
607*bd500cd2Sbeccabroek    }
608*bd500cd2Sbeccabroek  };
609*bd500cd2Sbeccabroek
610*bd500cd2Sbeccabroek  this.deregisterInstance = function(instanceId) {
611*bd500cd2Sbeccabroek    delete instances[instanceId];
612*bd500cd2Sbeccabroek  };
613*bd500cd2Sbeccabroek
614*bd500cd2Sbeccabroek  this.isRegistered = function(instanceId) {
615*bd500cd2Sbeccabroek    return (typeof instances[instanceId] !== 'undefined');
616*bd500cd2Sbeccabroek  };
617*bd500cd2Sbeccabroek
618*bd500cd2Sbeccabroek  this.getLastInstanceId = function() {
619*bd500cd2Sbeccabroek    return lastRegisteredInstance;
620*bd500cd2Sbeccabroek  };
621*bd500cd2Sbeccabroek
622*bd500cd2Sbeccabroek  this.setCurrentPageParser = function(instanceId, val, scope) {
623*bd500cd2Sbeccabroek    instances[instanceId].currentPageParser = val;
624*bd500cd2Sbeccabroek    instances[instanceId].context = scope;
625*bd500cd2Sbeccabroek  };
626*bd500cd2Sbeccabroek  this.setCurrentPage = function(instanceId, val) {
627*bd500cd2Sbeccabroek    instances[instanceId].currentPageParser.assign(
628*bd500cd2Sbeccabroek        instances[instanceId].context, val);
629*bd500cd2Sbeccabroek  };
630*bd500cd2Sbeccabroek  this.getCurrentPage = function(instanceId) {
631*bd500cd2Sbeccabroek    var parser = instances[instanceId].currentPageParser;
632*bd500cd2Sbeccabroek    return parser ? parser(instances[instanceId].context) : 1;
633*bd500cd2Sbeccabroek  };
634*bd500cd2Sbeccabroek
635*bd500cd2Sbeccabroek  this.setItemsPerPage = function(instanceId, val) {
636*bd500cd2Sbeccabroek    instances[instanceId].itemsPerPage = val;
637*bd500cd2Sbeccabroek  };
638*bd500cd2Sbeccabroek  this.getItemsPerPage = function(instanceId) {
639*bd500cd2Sbeccabroek    return instances[instanceId].itemsPerPage;
640*bd500cd2Sbeccabroek  };
641*bd500cd2Sbeccabroek
642*bd500cd2Sbeccabroek  this.setCollectionLength = function(instanceId, val) {
643*bd500cd2Sbeccabroek    instances[instanceId].collectionLength = val;
644*bd500cd2Sbeccabroek  };
645*bd500cd2Sbeccabroek  this.getCollectionLength = function(instanceId) {
646*bd500cd2Sbeccabroek    return instances[instanceId].collectionLength;
647*bd500cd2Sbeccabroek  };
648*bd500cd2Sbeccabroek
649*bd500cd2Sbeccabroek  this.setAsyncModeTrue = function(instanceId) {
650*bd500cd2Sbeccabroek    instances[instanceId].asyncMode = true;
651*bd500cd2Sbeccabroek  };
652*bd500cd2Sbeccabroek
653*bd500cd2Sbeccabroek  this.setAsyncModeFalse = function(instanceId) {
654*bd500cd2Sbeccabroek    instances[instanceId].asyncMode = false;
655*bd500cd2Sbeccabroek  };
656*bd500cd2Sbeccabroek
657*bd500cd2Sbeccabroek  this.isAsyncMode = function(instanceId) {
658*bd500cd2Sbeccabroek    return instances[instanceId].asyncMode;
659*bd500cd2Sbeccabroek  };
660*bd500cd2Sbeccabroek}
661*bd500cd2Sbeccabroek
662*bd500cd2Sbeccabroek/**
663*bd500cd2Sbeccabroek * This provider allows global configuration of the template path used by the
664*bd500cd2Sbeccabroek * dir-pagination-controls directive.
665*bd500cd2Sbeccabroek */
666*bd500cd2Sbeccabroekfunction paginationTemplateProvider() {
667*bd500cd2Sbeccabroek  var templatePath = 'app.common.directives.dirPagination.template';
668*bd500cd2Sbeccabroek  var templateString;
669*bd500cd2Sbeccabroek
670*bd500cd2Sbeccabroek  /**
671*bd500cd2Sbeccabroek   * Set a templateUrl to be used by all instances of <dir-pagination-controls>
672*bd500cd2Sbeccabroek   * @param {String} path
673*bd500cd2Sbeccabroek   */
674*bd500cd2Sbeccabroek  this.setPath = function(path) {
675*bd500cd2Sbeccabroek    templatePath = path;
676*bd500cd2Sbeccabroek  };
677*bd500cd2Sbeccabroek
678*bd500cd2Sbeccabroek  /**
679*bd500cd2Sbeccabroek   * Set a string of HTML to be used as a template by all instances
680*bd500cd2Sbeccabroek   * of <dir-pagination-controls>. If both a path *and* a string have been set,
681*bd500cd2Sbeccabroek   * the string takes precedence.
682*bd500cd2Sbeccabroek   * @param {String} str
683*bd500cd2Sbeccabroek   */
684*bd500cd2Sbeccabroek  this.setString = function(str) {
685*bd500cd2Sbeccabroek    templateString = str;
686*bd500cd2Sbeccabroek  };
687*bd500cd2Sbeccabroek
688*bd500cd2Sbeccabroek  this.$get = function() {
689*bd500cd2Sbeccabroek    return {
690*bd500cd2Sbeccabroek      getPath: function() {
691*bd500cd2Sbeccabroek        return templatePath;
692*bd500cd2Sbeccabroek      },
693*bd500cd2Sbeccabroek      getString: function() {
694*bd500cd2Sbeccabroek        return templateString;
695*bd500cd2Sbeccabroek      }
696*bd500cd2Sbeccabroek    };
697*bd500cd2Sbeccabroek  };
698*bd500cd2Sbeccabroek}
699*bd500cd2Sbeccabroek})();
700