<commit_before>goog.provide('app.searchFiltersDirective');
goog.provide('app.SearchFiltersController');

goog.require('app');
goog.require('app.utils');
goog.require('ngeo.Debounce');
goog.require('ngeo.Location');


/**
 * This directive is used to integrate a criterias form in the advanced search
 * page. See also {app.advancedSearchDirective}.
 *
 * @return {angular.Directive} The directive specs.
 * @ngInject
 */
app.searchFiltersDirective = function() {
  return {
    restrict: 'A',
    controller: 'appSearchFiltersController',
    bindToController: true,
    scope: true,
    controllerAs: 'filtersCtrl',
    link: function(scope, element, attrs, ctrl) {
      // FIXME On some screen sizes, the dropdown menu is too large and is
      // hidden within documents-list-section because it's to small. Given that
      // this menu is inside, it will not be shown entirely...
      // If someone can fix it using CSS only, you're da real MVP !
      element.on('click', '.dropdown-toggle', function() {
        $(this).next().css({
          position: 'fixed',
          top: $(this).offset().top + 30,
          left: $(this).offset().left - 10
        });
      });

      // This prevents to 'jump' or 'stutter' on phone - before, if you first
      // opened more-filters and scrolled, it would unfold filters on whole
      // page and make a stutter. Now it overflows.
      if (window.innerWidth < app.constants.SCREEN.SMARTPHONE) {
        $('.more-filters-btn, .search-filters-btn, .less-filters-btn').click(function() {
          $('.filters').toggleClass('filters-phone');
        });
      }
    }
  }
};


app.module.directive('appSearchFilters', app.searchFiltersDirective);


/**
 * @param {angular.Scope} $scope Scope.
 * @param {ngeo.Location} ngeoLocation ngeo Location service.
 * @param {ngeo.Debounce} ngeoDebounce ngeo Debounce service.
 * @constructor
 * @ngInject
 * @export
 */
app.SearchFiltersController = function($scope, ngeoLocation, ngeoDebounce) {

  /**
   * @type {angular.Scope}
   * @private
   */
  this.scope_ = $scope;

  /**
   * @type {ngeo.Location}
   * @private
   */
  this.location_ = ngeoLocation;

  /**
   * @type {Object}
   * @export
   */
  this.filters = {};

  /**
   * @type {boolean}
   * @private
   */
  this.loading_ = true;

  // Fill the filters according to the loaded URL parameters
  var keys = this.location_.getParamKeys().filter(function(x) {
    return app.SearchFiltersController.IGNORED_FILTERS.indexOf(x) === -1;
  });
  for (var i = 0, n = keys.length; i < n; i++) {
    var key = keys[i];
    this.filters[key] = this.location_.getParam(key);
  }

  this.scope_.$watchCollection(function() {
    return this.filters;
  }.bind(this), ngeoDebounce(
      this.handleFiltersChange_.bind(this),
      500, /* invokeApply */ true)
  );
};


/**
 * @const
 * @type {Array.<string>}
 */
app.SearchFiltersController.IGNORED_FILTERS = ['bbox', 'offset', 'limit'];


/**
 * @private
 */
app.SearchFiltersController.prototype.handleFiltersChange_ = function() {
  // FIXME remove console.log
  console.log(this.filters);
  // ignore the initial $watchCollection triggering (at loading time)
  if (!this.loading_) {
    this.location_.updateParams(this.filters);
    this.location_.deleteParam('offset');
    this.scope_.$root.$emit('searchFilterChange');
  } else {
    this.loading_ = false;
  }
};


/**
 * @param {string} prop Where to save the value.
 * @param {string} value Value to save.
 * @param {jQuery.Event | goog.events.Event} event click
 * @export
 */
app.SearchFiltersController.prototype.selectOption = function(prop, val, event) {
  // Don't close the menu after selecting an option
  event.stopPropagation();
  app.utils.pushToArray(this.filters, prop, val, event);
  // FIXME advertise that this.filters has changed
};


app.module.controller('appSearchFiltersController', app.SearchFiltersController);
<commit_msg>Advanced search watch and permalink<commit_after>goog.provide('app.searchFiltersDirective');
goog.provide('app.SearchFiltersController');

goog.require('app');
goog.require('app.utils');
goog.require('ngeo.Debounce');
goog.require('ngeo.Location');


/**
 * This directive is used to integrate a criterias form in the advanced search
 * page. See also {app.advancedSearchDirective}.
 *
 * @return {angular.Directive} The directive specs.
 * @ngInject
 */
app.searchFiltersDirective = function() {
  return {
    restrict: 'A',
    controller: 'appSearchFiltersController',
    bindToController: true,
    scope: true,
    controllerAs: 'filtersCtrl',
    link: function(scope, element, attrs, ctrl) {
      // FIXME On some screen sizes, the dropdown menu is too large and is
      // hidden within documents-list-section because it's to small. Given that
      // this menu is inside, it will not be shown entirely...
      // If someone can fix it using CSS only, you're da real MVP !
      element.on('click', '.dropdown-toggle', function() {
        $(this).next().css({
          position: 'fixed',
          top: $(this).offset().top + 30,
          left: $(this).offset().left - 10
        });
      });

      // This prevents to 'jump' or 'stutter' on phone - before, if you first
      // opened more-filters and scrolled, it would unfold filters on whole
      // page and make a stutter. Now it overflows.
      if (window.innerWidth < app.constants.SCREEN.SMARTPHONE) {
        $('.more-filters-btn, .search-filters-btn, .less-filters-btn').click(function() {
          $('.filters').toggleClass('filters-phone');
        });
      }
    }
  }
};


app.module.directive('appSearchFilters', app.searchFiltersDirective);


/**
 * @param {angular.Scope} $scope Scope.
 * @param {ngeo.Location} ngeoLocation ngeo Location service.
 * @param {ngeo.Debounce} ngeoDebounce ngeo Debounce service.
 * @constructor
 * @ngInject
 * @export
 */
app.SearchFiltersController = function($scope, ngeoLocation, ngeoDebounce) {

  /**
   * @type {angular.Scope}
   * @private
   */
  this.scope_ = $scope;

  /**
   * @type {ngeo.Location}
   * @private
   */
  this.location_ = ngeoLocation;

  /**
   * @type {Object}
   * @export
   */
  this.filters = {};

  /**
   * @type {boolean}
   * @private
   */
  this.loading_ = true;

  // Fill the filters according to the loaded URL parameters
  var keys = this.location_.getParamKeys().filter(function(x) {
    return app.SearchFiltersController.IGNORED_FILTERS.indexOf(x) === -1;
  });
  for (var i = 0, n = keys.length; i < n; i++) {
    this.getFilterFromPermalink_(keys[i]);
  }

  // Deep watch is used here because we need to watch the list filters as well
  // which a simple $watch or $watchCollection does not. Might cause
  // perf/memory issues though...
  this.scope_.$watch(function() {
    return this.filters;
  }.bind(this), ngeoDebounce(
      this.handleFiltersChange_.bind(this),
      500, /* invokeApply */ true), /* deep watch */ true
  );
};


/**
 * @const
 * @type {Array.<string>}
 */
app.SearchFiltersController.IGNORED_FILTERS = ['bbox', 'offset', 'limit'];


/**
 * @param {string} key Filter key.
 * @private
 */
app.SearchFiltersController.prototype.getFilterFromPermalink_ = function(key) {
  // TODO find a more generic way or at least factorize code
  var val;
  switch (key) {
    case 'wtyp':
      val = this.location_.getParam(key).split(',');
      break;
    case 'walt':
      val = this.location_.getParam(key).split(',').map(function(x) {
        return parseInt(x, 10);
      });
      break;
    default:
      val = this.location_.getParam(key);
  }
  this.filters[key] = val;
};


/**
 * @private
 */
app.SearchFiltersController.prototype.handleFiltersChange_ = function() {
  // FIXME remove console.log
  console.log(this.filters);
  // ignore the initial $watchCollection triggering (at loading time)
  if (!this.loading_) {
    this.location_.updateParams(this.filters);
    this.location_.deleteParam('offset');
    this.scope_.$root.$emit('searchFilterChange');
  } else {
    this.loading_ = false;
  }
};


/**
 * @param {string} prop Where to save the value.
 * @param {string} val Value to save.
 * @param {jQuery.Event | goog.events.Event} event click
 * @export
 */
app.SearchFiltersController.prototype.selectOption = function(prop, val, event) {
  // Don't close the menu after selecting an option
  event.stopPropagation();
  app.utils.pushToArray(this.filters, prop, val, event);
};


app.module.controller('appSearchFiltersController', app.SearchFiltersController);

{'fraction_non_alphanumeric': 0.1463098325866541, 'fraction_numerical': 0.002357934449422306, 'mean_word_length': 3.191205533596838, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2474646', 'n_tokens_mistral': 2655, 'n_tokens_neox': 2603, 'n_words': 660}