]> source.dussan.org Git - sonarqube.git/commitdiff
extract SelectList component
authorStas Vilchik <vilchiks@gmail.com>
Mon, 18 Jan 2016 13:02:39 +0000 (14:02 +0100)
committerStas Vilchik <vilchiks@gmail.com>
Mon, 18 Jan 2016 16:07:50 +0000 (17:07 +0100)
14 files changed:
server/sonar-web/src/main/js/apps/global-permissions/groups-view.js
server/sonar-web/src/main/js/apps/global-permissions/users-view.js
server/sonar-web/src/main/js/apps/groups/users-view.js
server/sonar-web/src/main/js/apps/permission-templates/groups-view.js
server/sonar-web/src/main/js/apps/permission-templates/users-view.js
server/sonar-web/src/main/js/apps/project-permissions/groups-view.js
server/sonar-web/src/main/js/apps/project-permissions/users-view.js
server/sonar-web/src/main/js/apps/quality-gates/gate-projects-view.js
server/sonar-web/src/main/js/apps/quality-profiles/profile-details-view.js
server/sonar-web/src/main/js/apps/users/groups-view.js
server/sonar-web/src/main/js/components/SelectList/index.js [new file with mode: 0644]
server/sonar-web/src/main/js/components/SelectList/templates/item.hbs [new file with mode: 0644]
server/sonar-web/src/main/js/components/SelectList/templates/list.hbs [new file with mode: 0644]
server/sonar-web/src/main/js/components/common/select-list.js [deleted file]

index c70a6a780055ca50862131730717e4047a99b650..45a774c3a9d7205b0541fb8b27a730a35f1e8e4d 100644 (file)
@@ -19,7 +19,7 @@
  */
 import Modal from '../../components/common/modals';
 import Template from './templates/global-permissions-groups.hbs';
-import '../../components/common/select-list';
+import '../../components/SelectList';
 
 function getSearchUrl (permission, project) {
   var url = baseUrl + '/api/permissions/groups?ps=100&permission=' + permission;
index 7130c357255838c8c7ba0cad71c4688daa1aeaff..61c454f39644f9e0d8a58b00c87c09c39f51ad3a 100644 (file)
@@ -19,7 +19,7 @@
  */
 import Modal from '../../components/common/modals';
 import Template from './templates/global-permissions-users.hbs';
-import '../../components/common/select-list';
+import '../../components/SelectList';
 
 function getSearchUrl (permission, project) {
   var url = baseUrl + '/api/permissions/users?ps=100&permission=' + permission;
index 5e8ad439d3779933ede0fe1bd07cc45f826ee2ed..ca21c132758637e8af032e3e4bd41e79d9fde93c 100644 (file)
@@ -18,7 +18,7 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import Modal from '../../components/common/modals';
-import '../../components/common/select-list';
+import '../../components/SelectList';
 import Template from './templates/groups-users.hbs';
 
 export default Modal.extend({
index 978cb03e4ee966dcb1896e9479188014a6aea5a7..5fb20a259b8935aff1bd2debaa4f27b3869a19a5 100644 (file)
@@ -19,7 +19,7 @@
  */
 import _ from 'underscore';
 import Modal from '../../components/common/modals';
-import '../../components/common/select-list';
+import '../../components/SelectList';
 import Template from './templates/permission-templates-groups.hbs';
 
 function getSearchUrl (permission, permissionTemplate) {
index 746097267d1becc4922dea154788da5c1159d369..3b73dd2060fe0f53264a142b0c130f470c86b8fd 100644 (file)
@@ -19,7 +19,7 @@
  */
 import _ from 'underscore';
 import Modal from '../../components/common/modals';
-import '../../components/common/select-list';
+import '../../components/SelectList';
 import Template from './templates/permission-templates-users.hbs';
 
 export default Modal.extend({
index bf9d695ce496884e1382b27ba190774bb11c7a33..4603c21efc761550aea4096f384861c668f64475 100644 (file)
@@ -19,7 +19,7 @@
  */
 import _ from 'underscore';
 import Modal from '../../components/common/modals';
-import '../../components/common/select-list';
+import '../../components/SelectList';
 import Template from './templates/project-permissions-groups.hbs';
 
 function getSearchUrl (permission, project) {
index c04bf3f74f969e6b84620272798c368c1b46a71a..42947b9ede77fa44a18f6501e375cdfab13ce913 100644 (file)
@@ -19,7 +19,7 @@
  */
 import _ from 'underscore';
 import Modal from '../../components/common/modals';
-import '../../components/common/select-list';
+import '../../components/SelectList';
 import Template from './templates/project-permissions-users.hbs';
 
 export default Modal.extend({
index 75d846b77a562caa68af5a9e443920861c99167a..ac2449ad86d8f799c27f61a4236bbd9a66f957ce 100644 (file)
@@ -20,7 +20,7 @@
 import _ from 'underscore';
 import Marionette from 'backbone.marionette';
 import Template from './templates/quality-gate-detail-projects.hbs';
-import '../../components/common/select-list';
+import '../../components/SelectList';
 import { translate } from '../../helpers/l10n';
 
 export default Marionette.ItemView.extend({
index 4101a1b4d8a0ba3313249c35ae317a9b8f4fcb80..5fa02436f01ecbd086c70b8086198c1063168f79 100644 (file)
@@ -23,7 +23,7 @@ import Marionette from 'backbone.marionette';
 import ChangeProfileParentView from './change-profile-parent-view';
 import ProfileChangelogView from './profile-changelog-view';
 import ProfileComparisonView from './profile-comparison-view';
-import '../../components/common/select-list';
+import '../../components/SelectList';
 import Template from './templates/quality-profiles-profile-details.hbs';
 import { translate } from '../../helpers/l10n';
 
index 2e5915b62d9b622eb0af03eb3cf8c5a60090043c..d619afacb50f6edbe666e941fadcd7239a9fc5c1 100644 (file)
@@ -18,7 +18,7 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import Modal from '../../components/common/modals';
-import '../../components/common/select-list';
+import '../../components/SelectList';
 import Template from './templates/users-groups.hbs';
 
 export default Modal.extend({
diff --git a/server/sonar-web/src/main/js/components/SelectList/index.js b/server/sonar-web/src/main/js/components/SelectList/index.js
new file mode 100644 (file)
index 0000000..b02014c
--- /dev/null
@@ -0,0 +1,450 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import $ from 'jquery';
+import _ from 'underscore';
+import Backbone from 'backbone';
+import { translate } from '../../helpers/l10n';
+import ItemTemplate from './templates/item.hbs';
+import ListTemplate from './templates/list.hbs';
+
+var showError = null;
+
+/*
+ * SelectList Collection
+ */
+
+var SelectListCollection = Backbone.Collection.extend({
+
+  initialize: function (options) {
+    this.options = options;
+  },
+
+  parse: function (r) {
+    return this.options.parse.call(this, r);
+  },
+
+  fetch: function (options) {
+    var data = $.extend({
+          page: 1,
+          pageSize: 100
+        }, options.data || {}),
+        settings = $.extend({}, options, { data: data });
+
+    this.settings = {
+      url: settings.url,
+      data: data
+    };
+
+    Backbone.Collection.prototype.fetch.call(this, settings);
+  },
+
+  fetchNextPage: function (options) {
+    if (this.more) {
+      var nextPage = this.settings.data.page + 1,
+          settings = $.extend(this.settings, options);
+
+      settings.data.page = nextPage;
+      settings.remove = false;
+      this.fetch(settings);
+    } else {
+      options.error();
+    }
+  }
+
+});
+
+
+/*
+ * SelectList Item View
+ */
+
+var SelectListItemView = Backbone.View.extend({
+  tagName: 'li',
+  template: ItemTemplate,
+
+  events: {
+    'change .select-list-list-checkbox': 'toggle'
+  },
+
+  initialize: function (options) {
+    this.listenTo(this.model, 'change', this.render);
+    this.settings = options.settings;
+  },
+
+  render: function () {
+    this.$el.html(this.template(this.settings.format(this.model.toJSON())));
+    this.$('input').prop('name', this.model.get('name'));
+    this.$el.toggleClass('selected', this.model.get('selected'));
+    this.$('.select-list-list-checkbox')
+        .prop('title',
+            this.model.get('selected') ?
+                this.settings.tooltips.deselect :
+                this.settings.tooltips.select)
+        .prop('checked', this.model.get('selected'));
+
+    if (this.settings.readOnly) {
+      this.$('.select-list-list-checkbox').prop('disabled', true);
+    }
+  },
+
+  remove: function (postpone) {
+    if (postpone) {
+      var that = this;
+      that.$el.addClass(this.model.get('selected') ? 'added' : 'removed');
+      setTimeout(function () {
+        Backbone.View.prototype.remove.call(that, arguments);
+      }, 500);
+    } else {
+      Backbone.View.prototype.remove.call(this, arguments);
+    }
+  },
+
+  toggle: function () {
+    var selected = this.model.get('selected'),
+        that = this,
+        url = selected ? this.settings.deselectUrl : this.settings.selectUrl,
+        data = $.extend({}, this.settings.extra || {});
+
+    data[this.settings.selectParameter] = this.model.get(this.settings.selectParameterValue);
+
+    that.$el.addClass('progress');
+    $.ajax({
+          url: url,
+          type: 'POST',
+          data: data,
+          statusCode: {
+            // do not show global error
+            400: null,
+            401: null,
+            403: null,
+            500: null
+          }
+        })
+        .done(function () {
+          that.model.set('selected', !selected);
+        })
+        .fail(function (jqXHR) {
+          that.render();
+          showError(jqXHR);
+        })
+        .always(function () {
+          that.$el.removeClass('progress');
+        });
+  }
+});
+
+
+/*
+ * SelectList View
+ */
+
+var SelectListView = Backbone.View.extend({
+  template: ListTemplate,
+
+  events: {
+    'click .select-list-control-button[name=selected]': 'showSelected',
+    'click .select-list-control-button[name=deselected]': 'showDeselected',
+    'click .select-list-control-button[name=all]': 'showAll'
+  },
+
+  initialize: function (options) {
+    this.listenTo(this.collection, 'add', this.renderListItem);
+    this.listenTo(this.collection, 'reset', this.renderList);
+    this.listenTo(this.collection, 'remove', this.removeModel);
+    this.listenTo(this.collection, 'change:selected', this.confirmFilter);
+    this.settings = options.settings;
+
+    var that = this;
+    this.showFetchSpinner = function () {
+      that.$listContainer.addClass('loading');
+    };
+    this.hideFetchSpinner = function () {
+      that.$listContainer.removeClass('loading');
+    };
+
+    var onScroll = function () {
+      that.showFetchSpinner();
+
+      that.collection.fetchNextPage({
+        success: function () {
+          that.hideFetchSpinner();
+        },
+        error: function () {
+          that.hideFetchSpinner();
+        }
+      });
+    };
+    this.onScroll = _.throttle(onScroll, 1000);
+  },
+
+  render: function () {
+    var that = this,
+        keyup = function () {
+          that.search();
+        };
+
+    this.$el.html(this.template(this.settings.labels))
+        .width(this.settings.width);
+
+    this.$listContainer = this.$('.select-list-list-container');
+    if (!this.settings.readOnly) {
+      this.$listContainer
+          .height(this.settings.height)
+          .css('overflow', 'auto')
+          .on('scroll', function () {
+            that.scroll();
+          });
+    } else {
+      this.$listContainer.addClass('select-list-list-container-readonly');
+    }
+
+    this.$list = this.$('.select-list-list');
+
+    var searchInput = this.$('.select-list-search-control input')
+        .on('keyup', _.debounce(keyup, 250))
+        .on('search', _.debounce(keyup, 250));
+
+    if (this.settings.focusSearch) {
+      setTimeout(function () {
+        searchInput.focus();
+      }, 250);
+    }
+
+    this.listItemViews = [];
+
+    showError = function (jqXHR) {
+      var message = translate('default_error_message');
+      if (jqXHR != null && jqXHR.responseJSON != null && jqXHR.responseJSON.errors != null) {
+        message = _.pluck(jqXHR.responseJSON.errors, 'msg').join('. ');
+      }
+
+      that.$el.prevAll('.alert').remove();
+      $('<div>')
+          .addClass('alert alert-danger').text(message)
+          .insertBefore(that.$el);
+    };
+
+    if (this.settings.readOnly) {
+      this.$('.select-list-control').remove();
+    }
+  },
+
+  renderList: function () {
+    this.listItemViews.forEach(function (view) {
+      view.remove();
+    });
+    this.listItemViews = [];
+    if (this.collection.length > 0) {
+      this.collection.each(this.renderListItem, this);
+    } else {
+      if (this.settings.readOnly) {
+        this.renderEmpty();
+      }
+    }
+    this.$listContainer.scrollTop(0);
+  },
+
+  renderListItem: function (item) {
+    var itemView = new SelectListItemView({
+      model: item,
+      settings: this.settings
+    });
+    this.listItemViews.push(itemView);
+    this.$list.append(itemView.el);
+    itemView.render();
+  },
+
+  renderEmpty: function () {
+    this.$list.append('<li class="empty-message">' + this.settings.labels.noResults + '</li>');
+  },
+
+  confirmFilter: function (model) {
+    if (this.currentFilter !== 'all') {
+      this.collection.remove(model);
+    }
+  },
+
+  removeModel: function (model, collection, options) {
+    this.listItemViews[options.index].remove(true);
+    this.listItemViews.splice(options.index, 1);
+  },
+
+  filterBySelection: function (filter) {
+    var that = this;
+    filter = this.currentFilter = filter || this.currentFilter;
+
+    if (filter != null) {
+      this.$('.select-list-check-control').toggleClass('disabled', false);
+      this.$('.select-list-search-control').toggleClass('disabled', true);
+      this.$('.select-list-search-control input').val('');
+
+      this.$('.select-list-control-button').removeClass('active')
+          .filter('[name=' + filter + ']').addClass('active');
+
+      this.showFetchSpinner();
+
+      this.collection.fetch({
+        url: this.settings.searchUrl,
+        reset: true,
+        data: { selected: filter },
+        success: function () {
+          that.hideFetchSpinner();
+        },
+        error: showError
+      });
+    }
+  },
+
+  showSelected: function () {
+    this.filterBySelection('selected');
+  },
+
+  showDeselected: function () {
+    this.filterBySelection('deselected');
+  },
+
+  showAll: function () {
+    this.filterBySelection('all');
+  },
+
+  search: function () {
+    var query = this.$('.select-list-search-control input').val(),
+        hasQuery = query.length > 0,
+        that = this,
+        data = {};
+
+    this.$('.select-list-check-control').toggleClass('disabled', hasQuery);
+    this.$('.select-list-search-control').toggleClass('disabled', !hasQuery);
+
+    if (hasQuery) {
+      this.showFetchSpinner();
+      this.currentFilter = 'all';
+
+      data[this.settings.queryParam] = query;
+      data.selected = 'all';
+      this.collection.fetch({
+        url: this.settings.searchUrl,
+        reset: true,
+        data: data,
+        success: function () {
+          that.hideFetchSpinner();
+        },
+        error: showError
+      });
+    } else {
+      this.filterBySelection();
+    }
+  },
+
+  searchByQuery: function (query) {
+    this.$('.select-list-search-control input').val(query);
+    this.search();
+  },
+
+  clearSearch: function () {
+    this.filterBySelection();
+  },
+
+  scroll: function () {
+    var scrollBottom = this.$listContainer.scrollTop() >=
+        this.$list[0].scrollHeight - this.$listContainer.outerHeight();
+
+    if (scrollBottom && this.collection.more) {
+      this.onScroll();
+    }
+  }
+
+});
+
+
+/*
+ * SelectList Entry Point
+ */
+
+window.SelectList = function (options) {
+  this.settings = $.extend(window.SelectList.defaults, options);
+
+  this.collection = new SelectListCollection({
+    parse: this.settings.parse
+  });
+
+  this.view = new SelectListView({
+    el: this.settings.el,
+    collection: this.collection,
+    settings: this.settings
+  });
+
+  this.view.render();
+  this.filter('selected');
+  return this;
+};
+
+
+/*
+ * SelectList API Methods
+ */
+
+window.SelectList.prototype.filter = function (filter) {
+  this.view.filterBySelection(filter);
+  return this;
+};
+
+window.SelectList.prototype.search = function (query) {
+  this.view.searchByQuery(query);
+  return this;
+};
+
+
+/*
+ * SelectList Defaults
+ */
+
+window.SelectList.defaults = {
+  width: '50%',
+  height: 400,
+
+  readOnly: false,
+  focusSearch: true,
+
+  format: function (item) {
+    return item.value;
+  },
+
+  parse: function (r) {
+    this.more = r.more;
+    return r.results;
+  },
+
+  queryParam: 'query',
+
+  labels: {
+    selected: 'Selected',
+    deselected: 'Deselected',
+    all: 'All',
+    noResults: ''
+  },
+
+  tooltips: {
+    select: 'Click this to select item',
+    deselect: 'Click this to deselect item'
+  },
+
+  errorMessage: 'Something gone wrong, try to reload the page and try again.'
+};
diff --git a/server/sonar-web/src/main/js/components/SelectList/templates/item.hbs b/server/sonar-web/src/main/js/components/SelectList/templates/item.hbs
new file mode 100644 (file)
index 0000000..347f383
--- /dev/null
@@ -0,0 +1,2 @@
+<input class="select-list-list-checkbox" type="checkbox">
+<div class="select-list-list-item">{{this}}</div>
diff --git a/server/sonar-web/src/main/js/components/SelectList/templates/list.hbs b/server/sonar-web/src/main/js/components/SelectList/templates/list.hbs
new file mode 100644 (file)
index 0000000..6500857
--- /dev/null
@@ -0,0 +1,16 @@
+<div class="select-list-container">
+  <div class="select-list-control">
+    <div class="select-list-check-control">
+      <a class="select-list-control-button" name="selected">{{this.selected}}</a><a class="select-list-control-button" name="deselected">{{this.deselected}}</a><a class="select-list-control-button" name="all">{{this.all}}</a>
+    </div>
+    <div class="select-list-search-control">
+      <form class="search-box">
+        <span class="search-box-submit button-clean"><i class="icon-search"></i></span>
+        <input class="search-box-input" type="search" name="q" placeholder="Search" maxlength="100" autocomplete="off">
+      </form>
+    </div>
+  </div>
+  <div class="select-list-list-container">
+    <ul class="select-list-list"></ul>
+  </div>
+</div>
diff --git a/server/sonar-web/src/main/js/components/common/select-list.js b/server/sonar-web/src/main/js/components/common/select-list.js
deleted file mode 100644 (file)
index 414afbc..0000000
+++ /dev/null
@@ -1,472 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-import $ from 'jquery';
-import _ from 'underscore';
-import Backbone from 'backbone';
-import { translate } from '../../helpers/l10n';
-
-var showError = null;
-
-/*
- * SelectList Collection
- */
-
-var SelectListCollection = Backbone.Collection.extend({
-
-  initialize: function (options) {
-    this.options = options;
-  },
-
-  parse: function (r) {
-    return this.options.parse.call(this, r);
-  },
-
-  fetch: function (options) {
-    var data = $.extend({
-          page: 1,
-          pageSize: 100
-        }, options.data || {}),
-        settings = $.extend({}, options, { data: data });
-
-    this.settings = {
-      url: settings.url,
-      data: data
-    };
-
-    Backbone.Collection.prototype.fetch.call(this, settings);
-  },
-
-  fetchNextPage: function (options) {
-    if (this.more) {
-      var nextPage = this.settings.data.page + 1,
-          settings = $.extend(this.settings, options);
-
-      settings.data.page = nextPage;
-      settings.remove = false;
-      this.fetch(settings);
-    } else {
-      options.error();
-    }
-  }
-
-});
-
-
-/*
- * SelectList Item View
- */
-
-var SelectListItemView = Backbone.View.extend({
-  tagName: 'li',
-
-  template: function (d) {
-    return '<input class="select-list-list-checkbox" type="checkbox">' +
-        '<div class="select-list-list-item">' + d + '</div>';
-  },
-
-  events: {
-    'change .select-list-list-checkbox': 'toggle'
-  },
-
-  initialize: function (options) {
-    this.listenTo(this.model, 'change', this.render);
-    this.settings = options.settings;
-  },
-
-  render: function () {
-    this.$el.html(this.template(this.settings.format(this.model.toJSON())));
-    this.$('input').prop('name', this.model.get('name'));
-    this.$el.toggleClass('selected', this.model.get('selected'));
-    this.$('.select-list-list-checkbox')
-        .prop('title',
-        this.model.get('selected') ?
-            this.settings.tooltips.deselect :
-            this.settings.tooltips.select)
-        .prop('checked', this.model.get('selected'));
-
-    if (this.settings.readOnly) {
-      this.$('.select-list-list-checkbox').prop('disabled', true);
-    }
-  },
-
-  remove: function (postpone) {
-    if (postpone) {
-      var that = this;
-      that.$el.addClass(this.model.get('selected') ? 'added' : 'removed');
-      setTimeout(function () {
-        Backbone.View.prototype.remove.call(that, arguments);
-      }, 500);
-    } else {
-      Backbone.View.prototype.remove.call(this, arguments);
-    }
-  },
-
-  toggle: function () {
-    var selected = this.model.get('selected'),
-        that = this,
-        url = selected ? this.settings.deselectUrl : this.settings.selectUrl,
-        data = $.extend({}, this.settings.extra || {});
-
-    data[this.settings.selectParameter] = this.model.get(this.settings.selectParameterValue);
-
-    that.$el.addClass('progress');
-    $.ajax({
-      url: url,
-      type: 'POST',
-      data: data,
-      statusCode: {
-        // do not show global error
-        400: null,
-        401: null,
-        403: null,
-        500: null
-      }
-    })
-        .done(function () {
-          that.model.set('selected', !selected);
-        })
-        .fail(function (jqXHR) {
-          that.render();
-          showError(jqXHR);
-        })
-        .always(function () {
-          that.$el.removeClass('progress');
-        });
-  }
-});
-
-
-/*
- * SelectList View
- */
-
-var SelectListView = Backbone.View.extend({
-  template: function (l) {
-    /* eslint max-len: 0 */
-    return '<div class="select-list-container">' +
-        '<div class="select-list-control">' +
-        '<div class="select-list-check-control">' +
-        '<a class="select-list-control-button" name="selected">' + l.selected + '</a>' +
-        '<a class="select-list-control-button" name="deselected">' + l.deselected + '</a>' +
-        '<a class="select-list-control-button" name="all">' + l.all + '</a>' +
-        '</div>' +
-        '<div class="select-list-search-control">' +
-        '<form class="search-box">' +
-        '<span class="search-box-submit button-clean"><i class="icon-search"></i></span>' +
-        '<input class="search-box-input" type="search" name="q" placeholder="Search" maxlength="100" autocomplete="off">' +
-        '</form>' +
-        '</div>' +
-        '</div>' +
-        '<div class="select-list-list-container">' +
-        '<ul class="select-list-list"></ul>' +
-        '</div>' +
-        '</div>';
-  },
-
-  events: {
-    'click .select-list-control-button[name=selected]': 'showSelected',
-    'click .select-list-control-button[name=deselected]': 'showDeselected',
-    'click .select-list-control-button[name=all]': 'showAll'
-  },
-
-  initialize: function (options) {
-    this.listenTo(this.collection, 'add', this.renderListItem);
-    this.listenTo(this.collection, 'reset', this.renderList);
-    this.listenTo(this.collection, 'remove', this.removeModel);
-    this.listenTo(this.collection, 'change:selected', this.confirmFilter);
-    this.settings = options.settings;
-
-    var that = this;
-    this.showFetchSpinner = function () {
-      that.$listContainer.addClass('loading');
-    };
-    this.hideFetchSpinner = function () {
-      that.$listContainer.removeClass('loading');
-    };
-
-    var onScroll = function () {
-      that.showFetchSpinner();
-
-      that.collection.fetchNextPage({
-        success: function () {
-          that.hideFetchSpinner();
-        },
-        error: function () {
-          that.hideFetchSpinner();
-        }
-      });
-    };
-    this.onScroll = _.throttle(onScroll, 1000);
-  },
-
-  render: function () {
-    var that = this,
-        keyup = function () {
-          that.search();
-        };
-
-    this.$el.html(this.template(this.settings.labels))
-        .width(this.settings.width);
-
-    this.$listContainer = this.$('.select-list-list-container');
-    if (!this.settings.readOnly) {
-      this.$listContainer
-          .height(this.settings.height)
-          .css('overflow', 'auto')
-          .on('scroll', function () {
-            that.scroll();
-          });
-    } else {
-      this.$listContainer.addClass('select-list-list-container-readonly');
-    }
-
-    this.$list = this.$('.select-list-list');
-
-    var searchInput = this.$('.select-list-search-control input')
-        .on('keyup', _.debounce(keyup, 250))
-        .on('search', _.debounce(keyup, 250));
-
-    if (this.settings.focusSearch) {
-      setTimeout(function () {
-        searchInput.focus();
-      }, 250);
-    }
-
-    this.listItemViews = [];
-
-    showError = function (jqXHR) {
-      var message = translate('default_error_message');
-      if (jqXHR != null && jqXHR.responseJSON != null && jqXHR.responseJSON.errors != null) {
-        message = _.pluck(jqXHR.responseJSON.errors, 'msg').join('. ');
-      }
-
-      that.$el.prevAll('.alert').remove();
-      $('<div>')
-          .addClass('alert alert-danger').text(message)
-          .insertBefore(that.$el);
-    };
-
-    if (this.settings.readOnly) {
-      this.$('.select-list-control').remove();
-    }
-  },
-
-  renderList: function () {
-    this.listItemViews.forEach(function (view) {
-      view.remove();
-    });
-    this.listItemViews = [];
-    if (this.collection.length > 0) {
-      this.collection.each(this.renderListItem, this);
-    } else {
-      if (this.settings.readOnly) {
-        this.renderEmpty();
-      }
-    }
-    this.$listContainer.scrollTop(0);
-  },
-
-  renderListItem: function (item) {
-    var itemView = new SelectListItemView({
-      model: item,
-      settings: this.settings
-    });
-    this.listItemViews.push(itemView);
-    this.$list.append(itemView.el);
-    itemView.render();
-  },
-
-  renderEmpty: function () {
-    this.$list.append('<li class="empty-message">' + this.settings.labels.noResults + '</li>');
-  },
-
-  confirmFilter: function (model) {
-    if (this.currentFilter !== 'all') {
-      this.collection.remove(model);
-    }
-  },
-
-  removeModel: function (model, collection, options) {
-    this.listItemViews[options.index].remove(true);
-    this.listItemViews.splice(options.index, 1);
-  },
-
-  filterBySelection: function (filter) {
-    var that = this;
-    filter = this.currentFilter = filter || this.currentFilter;
-
-    if (filter != null) {
-      this.$('.select-list-check-control').toggleClass('disabled', false);
-      this.$('.select-list-search-control').toggleClass('disabled', true);
-      this.$('.select-list-search-control input').val('');
-
-      this.$('.select-list-control-button').removeClass('active')
-          .filter('[name=' + filter + ']').addClass('active');
-
-      this.showFetchSpinner();
-
-      this.collection.fetch({
-        url: this.settings.searchUrl,
-        reset: true,
-        data: { selected: filter },
-        success: function () {
-          that.hideFetchSpinner();
-        },
-        error: showError
-      });
-    }
-  },
-
-  showSelected: function () {
-    this.filterBySelection('selected');
-  },
-
-  showDeselected: function () {
-    this.filterBySelection('deselected');
-  },
-
-  showAll: function () {
-    this.filterBySelection('all');
-  },
-
-  search: function () {
-    var query = this.$('.select-list-search-control input').val(),
-        hasQuery = query.length > 0,
-        that = this,
-        data = {};
-
-    this.$('.select-list-check-control').toggleClass('disabled', hasQuery);
-    this.$('.select-list-search-control').toggleClass('disabled', !hasQuery);
-
-    if (hasQuery) {
-      this.showFetchSpinner();
-      this.currentFilter = 'all';
-
-      data[this.settings.queryParam] = query;
-      data.selected = 'all';
-      this.collection.fetch({
-        url: this.settings.searchUrl,
-        reset: true,
-        data: data,
-        success: function () {
-          that.hideFetchSpinner();
-        },
-        error: showError
-      });
-    } else {
-      this.filterBySelection();
-    }
-  },
-
-  searchByQuery: function (query) {
-    this.$('.select-list-search-control input').val(query);
-    this.search();
-  },
-
-  clearSearch: function () {
-    this.filterBySelection();
-  },
-
-  scroll: function () {
-    var scrollBottom = this.$listContainer.scrollTop() >=
-        this.$list[0].scrollHeight - this.$listContainer.outerHeight();
-
-    if (scrollBottom && this.collection.more) {
-      this.onScroll();
-    }
-  }
-
-});
-
-
-/*
- * SelectList Entry Point
- */
-
-window.SelectList = function (options) {
-  this.settings = $.extend(window.SelectList.defaults, options);
-
-  this.collection = new SelectListCollection({
-    parse: this.settings.parse
-  });
-
-  this.view = new SelectListView({
-    el: this.settings.el,
-    collection: this.collection,
-    settings: this.settings
-  });
-
-  this.view.render();
-  this.filter('selected');
-  return this;
-};
-
-
-/*
- * SelectList API Methods
- */
-
-window.SelectList.prototype.filter = function (filter) {
-  this.view.filterBySelection(filter);
-  return this;
-};
-
-window.SelectList.prototype.search = function (query) {
-  this.view.searchByQuery(query);
-  return this;
-};
-
-
-/*
- * SelectList Defaults
- */
-
-window.SelectList.defaults = {
-  width: '50%',
-  height: 400,
-
-  readOnly: false,
-  focusSearch: true,
-
-  format: function (item) {
-    return item.value;
-  },
-
-  parse: function (r) {
-    this.more = r.more;
-    return r.results;
-  },
-
-  queryParam: 'query',
-
-  labels: {
-    selected: 'Selected',
-    deselected: 'Deselected',
-    all: 'All',
-    noResults: ''
-  },
-
-  tooltips: {
-    select: 'Click this to select item',
-    deselect: 'Click this to deselect item'
-  },
-
-  errorMessage: 'Something gone wrong, try to reload the page and try again.'
-};