aboutsummaryrefslogtreecommitdiffstats
path: root/sonar-server/src/main/js/navigator
diff options
context:
space:
mode:
Diffstat (limited to 'sonar-server/src/main/js/navigator')
-rw-r--r--sonar-server/src/main/js/navigator/filters/action-plan-filters.js104
-rw-r--r--sonar-server/src/main/js/navigator/filters/ajax-select-filters.js465
-rw-r--r--sonar-server/src/main/js/navigator/filters/base-filters.js225
-rw-r--r--sonar-server/src/main/js/navigator/filters/checkbox-filters.js50
-rw-r--r--sonar-server/src/main/js/navigator/filters/choice-filters.js363
-rw-r--r--sonar-server/src/main/js/navigator/filters/context-filters.js66
-rw-r--r--sonar-server/src/main/js/navigator/filters/date-filter-view.coffee11
-rw-r--r--sonar-server/src/main/js/navigator/filters/date-filter-view.js24
-rw-r--r--sonar-server/src/main/js/navigator/filters/favorite-filters.js79
-rw-r--r--sonar-server/src/main/js/navigator/filters/filter-bar.js107
-rw-r--r--sonar-server/src/main/js/navigator/filters/metric-filters.js169
-rw-r--r--sonar-server/src/main/js/navigator/filters/more-criteria-filters.js70
-rw-r--r--sonar-server/src/main/js/navigator/filters/range-filters.js195
-rw-r--r--sonar-server/src/main/js/navigator/filters/read-only-filters.js25
-rw-r--r--sonar-server/src/main/js/navigator/filters/rule-filters.js50
-rw-r--r--sonar-server/src/main/js/navigator/filters/string-filters.js77
16 files changed, 2080 insertions, 0 deletions
diff --git a/sonar-server/src/main/js/navigator/filters/action-plan-filters.js b/sonar-server/src/main/js/navigator/filters/action-plan-filters.js
new file mode 100644
index 00000000000..7503e344d2a
--- /dev/null
+++ b/sonar-server/src/main/js/navigator/filters/action-plan-filters.js
@@ -0,0 +1,104 @@
+define(['backbone', 'navigator/filters/base-filters', 'navigator/filters/choice-filters'], function (Backbone, BaseFilters, ChoiceFilters) {
+
+ return ChoiceFilters.ChoiceFilterView.extend({
+
+ initialize: function() {
+ ChoiceFilters.ChoiceFilterView.prototype.initialize.apply(this, arguments);
+ this.projectFilter = this.model.get('projectFilter');
+ this.listenTo(this.projectFilter, 'change:value', this.onChangeProjectFilter);
+ this.onChangeProjectFilter();
+ },
+
+
+ onChangeProjectFilter: function() {
+ var projects = this.projectFilter.get('value');
+ if (_.isArray(projects) && projects.length === 1) {
+ return this.fetchActionPlans(projects[0]);
+ } else {
+ return this.makeInactive();
+ }
+ },
+
+
+ showDetails: function() {
+ if (!this.$el.is('.navigator-filter-inactive')) {
+ ChoiceFilters.ChoiceFilterView.prototype.showDetails.apply(this, arguments);
+ }
+ },
+
+
+ makeActive: function() {
+ this.model.set({
+ inactive: false,
+ title: ''
+ });
+ this.model.trigger('change:enabled');
+ this.$el.removeClass('navigator-filter-inactive').prop('title', '');
+ },
+
+
+ makeInactive: function() {
+ this.model.set({
+ inactive: true,
+ title: window.SS.phrases.actionPlanNotAvailable
+ });
+ this.model.trigger('change:enabled');
+ this.choices.reset([]);
+ this.detailsView.updateLists();
+ this.detailsView.updateValue();
+ this.$el.addClass('navigator-filter-inactive')
+ .prop('title', window.SS.phrases.actionPlanNotAvailable);
+ },
+
+
+ fetchActionPlans: function(project) {
+ var that = this;
+ return jQuery.ajax({
+ url: baseUrl + '/api/action_plans/search',
+ type: 'GET',
+ data: { project: project }
+ }).done(function(r) {
+ var nonClosedActionPlans =
+ _.sortBy(_.reject(r.actionPlans, function(plan) {
+ return plan.status === 'CLOSED';
+ }), 'name');
+
+ that.choices.reset(nonClosedActionPlans.map(function(plan) {
+ return {
+ id: plan.key,
+ text: plan.name,
+ category: plan.fDeadLine
+ }
+ }));
+ _.each(that.model.get('choices'), function(v, k) {
+ that.choices.add(new Backbone.Model({ id: k, text: v }));
+ });
+ var value = that.model.get('value');
+ _.each(value, function(v) {
+ var cModel = that.choices.findWhere({ id: v });
+ cModel.set('checked', true);
+ });
+ that.detailsView.updateValue();
+ that.render();
+
+ that.makeActive();
+ });
+ },
+
+
+ restore: function(value) {
+ if (_.isString(value)) {
+ value = value.split(',');
+ }
+
+ if (this.choices && value.length > 0) {
+ this.model.set({ value: value, enabled: true });
+ this.onChangeProjectFilter();
+ } else {
+ this.clear();
+ }
+ }
+
+ });
+
+});
diff --git a/sonar-server/src/main/js/navigator/filters/ajax-select-filters.js b/sonar-server/src/main/js/navigator/filters/ajax-select-filters.js
new file mode 100644
index 00000000000..dd2e246d217
--- /dev/null
+++ b/sonar-server/src/main/js/navigator/filters/ajax-select-filters.js
@@ -0,0 +1,465 @@
+define(['backbone', 'navigator/filters/base-filters', 'navigator/filters/choice-filters', 'common/handlebars-extensions'], function (Backbone, BaseFilters, ChoiceFilters) {
+
+ var PAGE_SIZE = 100;
+
+
+ var Suggestions = Backbone.Collection.extend({
+ comparator: 'checked',
+
+ initialize: function() {
+ this.more = false;
+ this.page = 0;
+ },
+
+
+ parse: function(r) {
+ this.more = r.more;
+ return r.results;
+ },
+
+
+ fetch: function(options) {
+ this.data = _.extend({
+ p: 1,
+ ps: PAGE_SIZE
+ }, options.data || {});
+
+ var settings = _.extend({}, options, { data: this.data });
+ return Backbone.Collection.prototype.fetch.call(this, settings);
+ },
+
+
+ fetchNextPage: function(options) {
+ if (this.more) {
+ this.data.p += 1;
+ var settings = _.extend({ remove: false }, options, { data: this.data });
+ return this.fetch(settings);
+ }
+ return false;
+ }
+
+ });
+
+
+
+ var UserSuggestions = Suggestions.extend({
+
+ url: function() {
+ return baseUrl + '/api/users/search?f=s2';
+ }
+
+ });
+
+
+
+ var ProjectSuggestions = Suggestions.extend({
+
+ url: function() {
+ return baseUrl + '/api/resources/search?f=s2&q=TRK&display_key=true';
+ }
+
+ });
+
+
+
+ var ComponentSuggestions = Suggestions.extend({
+
+ url: function() {
+ return baseUrl + '/api/resources/search?f=s2&qp=supportsGlobalDashboards&display_key=true';
+ },
+
+ parse: function(r) {
+ this.more = r.more;
+
+ // If results are divided into categories
+ if (r.results.length > 0 && r.results[0].children) {
+ var results = [];
+ _.each(r.results, function(category) {
+ _.each(category.children, function(child) {
+ child.category = category.text;
+ results.push(child);
+ });
+ });
+ return results;
+ } else {
+ return r.results;
+ }
+ }
+
+ });
+
+
+
+ var AjaxSelectDetailsFilterView = ChoiceFilters.DetailsChoiceFilterView.extend({
+ template: getTemplate('#ajax-select-filter-template'),
+ listTemplate: getTemplate('#choice-filter-template'),
+
+
+ render: function() {
+ ChoiceFilters.DetailsChoiceFilterView.prototype.render.apply(this, arguments);
+
+ var that = this,
+ keyup = function(e) {
+ if (e.keyCode !== 38 && e.keyCode !== 40) {
+ that.search();
+ }
+ },
+ debouncedKeyup = _.debounce(keyup, 250),
+ scroll = function() { that.scroll(); },
+ throttledScroll = _.throttle(scroll, 1000);
+
+ this.$('.navigator-filter-search input')
+ .off('keyup keydown')
+ .on('keyup', debouncedKeyup)
+ .on('keydown', this.keydown);
+
+ this.$('.choices')
+ .off('scroll')
+ .on('scroll', throttledScroll);
+ },
+
+
+ search: function() {
+ var that = this;
+ this.query = this.$('.navigator-filter-search input').val();
+ if (this.query.length > 1) {
+ this.$el.addClass('fetching');
+ var selected = that.options.filterView.getSelected();
+ this.options.filterView.choices.fetch({
+ data: {
+ s: this.query,
+ ps: PAGE_SIZE
+ },
+ success: function() {
+ selected.forEach(function(item) {
+ that.options.filterView.choices.unshift(item);
+ });
+ _.each(that.model.get('choices'), function(v, k) {
+ that.options.filterView.choices.add(new Backbone.Model({ id: k, text: v }));
+ });
+ that.updateLists();
+ that.$el.removeClass('fetching');
+ }
+ });
+ } else {
+ this.resetChoices();
+ this.updateLists();
+ }
+ },
+
+
+ scroll: function() {
+ var that = this,
+ el = this.$('.choices'),
+ scrollBottom = el.scrollTop() >= el[0].scrollHeight - el.outerHeight();
+
+ if (scrollBottom) {
+ this.options.filterView.choices.fetchNextPage().done(function() {
+ that.updateLists();
+ });
+ }
+ },
+
+
+ keydown: function(e) {
+ if (_([37, 38, 39, 40, 13]).indexOf(e.keyCode) !== -1) {
+ e.preventDefault();
+ }
+ },
+
+
+ resetChoices: function() {
+ var that = this;
+ this.options.filterView.choices.reset(this.options.filterView.choices.filter(function(item) {
+ return item.get('checked');
+ }));
+ _.each(this.model.get('choices'), function(v, k) {
+ that.options.filterView.choices.add(new Backbone.Model({ id: k, text: v }));
+ });
+ },
+
+
+ onShow: function() {
+ ChoiceFilters.DetailsChoiceFilterView.prototype.onShow.apply(this, arguments);
+ this.resetChoices();
+ this.render();
+ this.$('.navigator-filter-search input').focus();
+ }
+
+ });
+
+
+
+ var AjaxSelectFilterView = ChoiceFilters.ChoiceFilterView.extend({
+
+ initialize: function() {
+ ChoiceFilters.ChoiceFilterView.prototype.initialize.call(this, {
+ detailsView: AjaxSelectDetailsFilterView
+ });
+ },
+
+
+ isDefaultValue: function() {
+ return this.getSelected().length === 0;
+ },
+
+
+ renderInput: function() {
+ var value = this.model.get('value') || [],
+ input = $j('<input>')
+ .prop('name', this.model.get('property'))
+ .prop('type', 'hidden')
+ .css('display', 'none')
+ .val(value.join());
+ input.appendTo(this.$el);
+ },
+
+
+ restoreFromQuery: function(q) {
+ var param = _.findWhere(q, { key: this.model.get('property') });
+
+ if (this.model.get('choices')) {
+ _.each(this.model.get('choices'), function(v, k) {
+ if (k[0] === '!') {
+ var x = _.findWhere(q, { key: k.substr(1) });
+ if (x) {
+ if (!param) {
+ param = { value: k };
+ } else {
+ param.value += ',' + k;
+ }
+ }
+ }
+ });
+ }
+
+ if (param && param.value) {
+ this.model.set('enabled', true);
+ this.restore(param.value, param);
+ } else {
+ this.clear();
+ }
+ },
+
+
+ restore: function(value, param) {
+ var that = this;
+ if (_.isString(value)) {
+ value = value.split(',');
+ }
+
+ if (this.choices && value.length > 0) {
+ this.model.set({ value: value, enabled: true });
+
+ var opposite = _.filter(value, function(item) {
+ return item[0] === '!';
+ });
+ opposite.forEach(function(item) {
+ that.choices.add(new Backbone.Model({
+ id: item,
+ text: that.model.get('choices')[item],
+ checked: true
+ }));
+ });
+
+ value = _.reject(value, function(item) {
+ return item[0] === '!';
+ });
+ if (_.isArray(param.text) && param.text.length === value.length) {
+ this.restoreFromText(value, param.text);
+ } else {
+ this.restoreByRequests(value);
+ }
+ } else {
+ this.clear();
+ }
+ },
+
+
+ restoreFromText: function(value, text) {
+ var that = this;
+ _.each(value, function(v, i) {
+ that.choices.add(new Backbone.Model({
+ id: v,
+ text: text[i],
+ checked: true
+ }));
+ });
+ this.onRestore(value);
+ },
+
+
+ restoreByRequests: function(value) {
+ var that = this,
+ requests = _.map(value, function(v) {
+ return that.createRequest(v);
+ });
+
+ $j.when.apply($j, requests).done(function () {
+ that.onRestore(value);
+ });
+ },
+
+
+ onRestore: function() {
+ this.detailsView.updateLists();
+ this.renderBase();
+ },
+
+
+ clear: function() {
+ this.model.unset('value');
+ if (this.choices) {
+ this.choices.reset([]);
+ }
+ this.render();
+ },
+
+
+ createRequest: function() {}
+
+ });
+
+
+
+ var ComponentFilterView = AjaxSelectFilterView.extend({
+
+ initialize: function() {
+ AjaxSelectFilterView.prototype.initialize.call(this, {
+ detailsView: AjaxSelectDetailsFilterView
+ });
+ this.choices = new ComponentSuggestions();
+ },
+
+
+ createRequest: function(v) {
+ var that = this;
+ return $j
+ .ajax({
+ url: baseUrl + '/api/resources',
+ type: 'GET',
+ data: { resource: v }
+ })
+ .done(function (r) {
+ that.selection.add(new Backbone.Model({
+ id: r[0].key,
+ text: r[0].name
+ }));
+ });
+ }
+
+ });
+
+
+
+ var ProjectFilterView = AjaxSelectFilterView.extend({
+
+ initialize: function() {
+ BaseFilters.BaseFilterView.prototype.initialize.call(this, {
+ detailsView: AjaxSelectDetailsFilterView
+ });
+
+ this.choices = new ProjectSuggestions();
+ },
+
+
+ createRequest: function(v) {
+ var that = this;
+ return $j
+ .ajax({
+ url: baseUrl + '/api/resources',
+ type: 'GET',
+ data: { resource: v }
+ })
+ .done(function (r) {
+ that.choices.add(new Backbone.Model({
+ id: r[0].key,
+ text: r[0].name,
+ checked: true
+ }));
+ });
+ }
+
+ });
+
+
+
+ var AssigneeFilterView = AjaxSelectFilterView.extend({
+
+ initialize: function() {
+ BaseFilters.BaseFilterView.prototype.initialize.call(this, {
+ detailsView: AjaxSelectDetailsFilterView
+ });
+
+ this.choices = new UserSuggestions();
+ },
+
+ createRequest: function(v) {
+ var that = this;
+ return $j
+ .ajax({
+ url: baseUrl + '/api/users/search',
+ type: 'GET',
+ data: { logins: v }
+ })
+ .done(function (r) {
+ that.choices.add(new Backbone.Model({
+ id: r.users[0].login,
+ text: r.users[0].name + ' (' + r.users[0].login + ')',
+ checked: true
+ }));
+ });
+ }
+
+ });
+
+
+
+ var ReporterFilterView = AjaxSelectFilterView.extend({
+
+ initialize: function() {
+ BaseFilters.BaseFilterView.prototype.initialize.call(this, {
+ detailsView: AjaxSelectDetailsFilterView
+ });
+
+ this.selection = new UserSuggestions();
+ this.choices = new UserSuggestions();
+ },
+
+
+ createRequest: function(v) {
+ var that = this;
+ return $j
+ .ajax({
+ url: baseUrl + '/api/users/search',
+ type: 'GET',
+ data: { logins: v }
+ })
+ .done(function (r) {
+ that.choices.add(new Backbone.Model({
+ id: r.users[0].login,
+ text: r.users[0].name + ' (' + r.users[0].login + ')',
+ checked: true
+ }));
+ });
+ }
+
+ });
+
+
+
+ /*
+ * Export public classes
+ */
+
+ return {
+ Suggestions: Suggestions,
+ AjaxSelectDetailsFilterView: AjaxSelectDetailsFilterView,
+ AjaxSelectFilterView: AjaxSelectFilterView,
+ ProjectFilterView: ProjectFilterView,
+ ComponentFilterView: ComponentFilterView,
+ AssigneeFilterView: AssigneeFilterView,
+ ReporterFilterView: ReporterFilterView
+ };
+
+});
diff --git a/sonar-server/src/main/js/navigator/filters/base-filters.js b/sonar-server/src/main/js/navigator/filters/base-filters.js
new file mode 100644
index 00000000000..e07a3517bfe
--- /dev/null
+++ b/sonar-server/src/main/js/navigator/filters/base-filters.js
@@ -0,0 +1,225 @@
+define(['backbone', 'backbone.marionette', 'common/handlebars-extensions'], function (Backbone, Marionette) {
+
+ var Filter = Backbone.Model.extend({
+
+ defaults: {
+ enabled: true,
+ optional: false,
+ multiple: true,
+ placeholder: ''
+ }
+
+ });
+
+
+
+ var Filters = Backbone.Collection.extend({
+ model: Filter
+ });
+
+
+
+ var DetailsFilterView = Marionette.ItemView.extend({
+ template: getTemplate('#base-details-filter-template'),
+ className: 'navigator-filter-details',
+
+
+ initialize: function() {
+ this.$el.on('click', function(e) {
+ e.stopPropagation();
+ });
+ },
+
+
+ onShow: function() {},
+ onHide: function() {}
+ });
+
+
+
+ var BaseFilterView = Marionette.ItemView.extend({
+ template: getTemplate('#base-filter-template'),
+ className: 'navigator-filter',
+
+
+ events: function() {
+ return {
+ 'click': 'toggleDetails',
+ 'click .navigator-filter-disable': 'disable'
+ };
+ },
+
+
+ modelEvents: {
+ 'change:enabled': 'focus',
+ 'change:value': 'renderBase',
+
+ // for more criteria filter
+ 'change:filters': 'render'
+ },
+
+
+ initialize: function(options) {
+ Marionette.ItemView.prototype.initialize.apply(this, arguments);
+
+ var detailsView = (options && options.detailsView) || DetailsFilterView;
+ this.detailsView = new detailsView({
+ model: this.model,
+ filterView: this
+ });
+
+ this.model.view = this;
+ },
+
+
+ attachDetailsView: function() {
+ this.detailsView.$el.detach().appendTo($j('body'));
+ },
+
+
+ render: function() {
+ this.renderBase();
+
+ this.attachDetailsView();
+ this.detailsView.render();
+
+ this.$el.toggleClass(
+ 'navigator-filter-disabled',
+ !this.model.get('enabled'));
+
+ this.$el.toggleClass(
+ 'navigator-filter-optional',
+ this.model.get('optional'));
+ },
+
+
+ renderBase: function() {
+ Marionette.ItemView.prototype.render.apply(this, arguments);
+ this.renderInput();
+ },
+
+
+ renderInput: function() {},
+
+
+ focus: function() {
+ this.render();
+// this.showDetails();
+ },
+
+
+ toggleDetails: function(e) {
+ e.stopPropagation();
+ if (this.$el.hasClass('active')) {
+ this.hideDetails();
+ } else {
+ this.showDetails();
+ }
+ },
+
+
+ showDetails: function() {
+ this.registerShowedDetails();
+
+ var top = this.$el.offset().top + this.$el.outerHeight() - 1,
+ left = this.$el.offset().left;
+
+ this.detailsView.$el.css({ top: top, left: left }).addClass('active');
+ this.$el.addClass('active');
+ this.detailsView.onShow();
+ },
+
+
+ registerShowedDetails: function() {
+ this.options.filterBarView.hideDetails();
+ this.options.filterBarView.showedView = this;
+ },
+
+
+ hideDetails: function() {
+ this.detailsView.$el.removeClass('active');
+ this.$el.removeClass('active');
+ this.detailsView.onHide();
+ },
+
+
+ isActive: function() {
+ return this.$el.is('.active');
+ },
+
+
+ renderValue: function() {
+ return this.model.get('value') || 'unset';
+ },
+
+
+ isDefaultValue: function() {
+ return true;
+ },
+
+
+ restoreFromQuery: function(q) {
+ var param = _.findWhere(q, { key: this.model.get('property') });
+ if (param && param.value) {
+ this.model.set('enabled', true);
+ this.restore(param.value, param);
+ } else {
+ this.clear();
+ }
+ },
+
+
+ restore: function(value) {
+ this.model.set({ value: value }, { silent: true });
+ this.renderBase();
+ },
+
+
+ clear: function() {
+ this.model.unset('value');
+ },
+
+
+ disable: function(e) {
+ e.stopPropagation();
+ this.hideDetails();
+ this.options.filterBarView.hideDetails();
+ this.model.set({
+ enabled: false,
+ value: null
+ });
+ },
+
+
+ formatValue: function() {
+ var q = {};
+ if (this.model.has('property') && this.model.has('value') && this.model.get('value')) {
+ q[this.model.get('property')] = this.model.get('value');
+ }
+ return q;
+ },
+
+
+ serializeData: function() {
+ return _.extend({}, this.model.toJSON(), {
+ value: this.renderValue(),
+ defaultValue: this.isDefaultValue()
+ });
+ }
+
+ });
+
+
+
+ /*
+ * Export public classes
+ */
+
+ return {
+ Filter: Filter,
+ Filters: Filters,
+ BaseFilterView: BaseFilterView,
+ DetailsFilterView: DetailsFilterView
+ };
+
+});
diff --git a/sonar-server/src/main/js/navigator/filters/checkbox-filters.js b/sonar-server/src/main/js/navigator/filters/checkbox-filters.js
new file mode 100644
index 00000000000..025ce9dc649
--- /dev/null
+++ b/sonar-server/src/main/js/navigator/filters/checkbox-filters.js
@@ -0,0 +1,50 @@
+define(['backbone', 'backbone.marionette', 'navigator/filters/base-filters', 'common/handlebars-extensions'], function (Backbone, Marionette, BaseFilters) {
+
+ return BaseFilters.BaseFilterView.extend({
+ template: getTemplate('#checkbox-filter-template'),
+ className: 'navigator-filter navigator-filter-inline',
+
+
+ events: function() {
+ return {
+ 'click .navigator-filter-disable': 'disable'
+ };
+ },
+
+
+ showDetails: function() {},
+
+
+ renderInput: function() {
+ if (this.model.get('enabled')) {
+ $j('<input>')
+ .prop('name', this.model.get('property'))
+ .prop('type', 'checkbox')
+ .prop('value', 'true')
+ .prop('checked', true)
+ .css('display', 'none')
+ .appendTo(this.$el);
+ }
+ },
+
+
+ renderValue: function() {
+ return this.model.get('value') || false;
+ },
+
+
+ isDefaultValue: function() {
+ return false;
+ },
+
+
+ restore: function(value) {
+ this.model.set({
+ value: value,
+ enabled: true
+ });
+ }
+
+ });
+
+});
diff --git a/sonar-server/src/main/js/navigator/filters/choice-filters.js b/sonar-server/src/main/js/navigator/filters/choice-filters.js
new file mode 100644
index 00000000000..cf74a21562e
--- /dev/null
+++ b/sonar-server/src/main/js/navigator/filters/choice-filters.js
@@ -0,0 +1,363 @@
+define(['handlebars', 'navigator/filters/base-filters', 'common/handlebars-extensions'], function (Handlebars, BaseFilters) {
+
+ var DetailsChoiceFilterView = BaseFilters.DetailsFilterView.extend({
+ template: getTemplate('#choice-filter-template'),
+ itemTemplate: getTemplate('#choice-filter-item-template'),
+
+
+ events: function() {
+ return {
+ 'change input[type=checkbox]': 'onCheck'
+ };
+ },
+
+
+ render: function() {
+ BaseFilters.DetailsFilterView.prototype.render.apply(this, arguments);
+ this.updateLists();
+ },
+
+
+ renderList: function(collection, selector) {
+ var that = this,
+ container = this.$(selector);
+
+ container.empty().toggleClass('hidden', collection.length === 0);
+ collection.each(function(item) {
+ container.append(that.itemTemplate(item.toJSON()));
+ });
+ },
+
+
+ updateLists: function() {
+ var choices = new Backbone.Collection(this.options.filterView.choices.reject(function(item) {
+ return item.get('id')[0] === '!';
+ })),
+ opposite = new Backbone.Collection(this.options.filterView.choices.filter(function(item) {
+ return item.get('id')[0] === '!';
+ }));
+
+ this.renderList(choices, '.choices');
+ this.renderList(opposite, '.opposite');
+
+ var current = this.currentChoice || 0;
+ this.updateCurrent(current);
+ },
+
+
+ onCheck: function(e) {
+ var checkbox = jQuery(e.target),
+ id = checkbox.val(),
+ checked = checkbox.prop('checked');
+
+ if (this.model.get('multiple')) {
+ if (checkbox.closest('.opposite').length > 0) {
+ this.options.filterView.choices.reject(function(item) {
+ return item.get('id')[0] === '!'
+ }).forEach(function(item) {
+ item.set('checked', false);
+ });
+ } else {
+ this.options.filterView.choices.filter(function(item) {
+ return item.get('id')[0] === '!'
+ }).forEach(function(item) {
+ item.set('checked', false);
+ });
+ }
+ } else {
+ this.options.filterView.choices.each(function(item) {
+ item.set('checked', false);
+ });
+ }
+
+ this.options.filterView.choices.get(id).set('checked', checked);
+ this.updateValue();
+ this.updateLists();
+ },
+
+
+ updateValue: function() {
+ this.model.set('value', this.options.filterView.getSelected().map(function(m) {
+ return m.get('id');
+ }));
+ },
+
+
+ updateCurrent: function(index) {
+ this.currentChoice = index;
+ this.$('label').removeClass('current')
+ .eq(this.currentChoice).addClass('current');
+ },
+
+
+ onShow: function() {
+ this.bindedOnKeyDown = _.bind(this.onKeyDown, this);
+ $j('body').on('keydown', this.bindedOnKeyDown);
+ },
+
+
+ onHide: function() {
+ $j('body').off('keydown', this.bindedOnKeyDown);
+ },
+
+
+ onKeyDown: function(e) {
+ switch (e.keyCode) {
+ case 38:
+ e.preventDefault();
+ this.selectPrevChoice();
+ break;
+ case 40:
+ e.preventDefault();
+ this.selectNextChoice();
+ break;
+ case 13:
+ e.preventDefault();
+ this.selectCurrent();
+ break;
+ }
+ },
+
+
+ selectNextChoice: function() {
+ if (this.$('label').length > this.currentChoice + 1) {
+ this.updateCurrent(this.currentChoice + 1);
+ this.scrollNext();
+ }
+ },
+
+
+ scrollNext: function() {
+ var currentLabel = this.$('label').eq(this.currentChoice);
+ if (currentLabel.length > 0) {
+ var list = currentLabel.closest('ul'),
+ labelPos = currentLabel.offset().top - list.offset().top + list.scrollTop(),
+ deltaScroll = labelPos - list.height() + currentLabel.outerHeight();
+
+ if (deltaScroll > 0) {
+ list.scrollTop(deltaScroll);
+ }
+ }
+ },
+
+
+ selectPrevChoice: function() {
+ if (this.currentChoice > 0) {
+ this.updateCurrent(this.currentChoice - 1);
+ this.scrollPrev();
+ }
+ },
+
+
+ scrollPrev: function() {
+ var currentLabel = this.$('label').eq(this.currentChoice);
+ if (currentLabel.length > 0) {
+ var list = currentLabel.closest('ul'),
+ labelPos = currentLabel.offset().top - list.offset().top;
+
+ if (labelPos < 0) {
+ list.scrollTop(list.scrollTop() + labelPos);
+ }
+ }
+ },
+
+
+ selectCurrent: function() {
+ this.$('label').eq(this.currentChoice).click();
+ },
+
+
+ serializeData: function() {
+ return _.extend({}, this.model.toJSON(), {
+ choices: new Backbone.Collection(this.options.filterView.choices.reject(function(item) {
+ return item.get('id')[0] === '!';
+ })).toJSON(),
+ opposite: new Backbone.Collection(this.options.filterView.choices.filter(function(item) {
+ return item.get('id')[0] === '!';
+ })).toJSON()
+ });
+ }
+
+ });
+
+
+
+ var ChoiceFilterView = BaseFilters.BaseFilterView.extend({
+
+ initialize: function(options) {
+ BaseFilters.BaseFilterView.prototype.initialize.call(this, {
+ detailsView: (options && options.detailsView) ? options.detailsView : DetailsChoiceFilterView
+ });
+
+ var index = 0,
+ icons = this.model.get('choiceIcons');
+
+ this.choices = new Backbone.Collection(
+ _.map(this.model.get('choices'), function(value, key) {
+ var model = new Backbone.Model({
+ id: key,
+ text: value,
+ checked: false,
+ index: index++
+ });
+
+ if (icons && icons[key]) {
+ model.set('icon', icons[key]);
+ }
+
+ return model;
+ }), { comparator: 'index' }
+ );
+ },
+
+
+ getSelected: function() {
+ return this.choices.filter(function(m) {
+ return m.get('checked');
+ });
+ },
+
+
+ renderInput: function() {
+ var input = $j('<select>')
+ .prop('name', this.model.get('property'))
+ .prop('multiple', true)
+ .css('display', 'none');
+ this.choices.each(function(item) {
+ var option = $j('<option>')
+ .prop('value', item.get('id'))
+ .prop('selected', item.get('checked'))
+ .text(item.get('text'));
+ option.appendTo(input);
+ });
+ input.appendTo(this.$el);
+ },
+
+
+ renderValue: function() {
+ var value = this.getSelected().map(function(item) {
+ return item.get('text');
+ }),
+ defaultValue = this.model.has('defaultValue') ?
+ this.model.get('defaultValue') :
+ this.model.get('multiple') ? t('all') : t('any');
+
+ return this.isDefaultValue() ? defaultValue : value.join(', ');
+ },
+
+
+ isDefaultValue: function() {
+ var selected = this.getSelected();
+ return selected.length === 0;
+ },
+
+
+ disable: function() {
+ this.choices.each(function(item) {
+ item.set('checked', false);
+ });
+ BaseFilters.BaseFilterView.prototype.disable.apply(this, arguments);
+ },
+
+
+ restoreFromQuery: function(q) {
+ var param = _.findWhere(q, { key: this.model.get('property') });
+
+ if (this.choices) {
+ this.choices.forEach(function(item) {
+ if (item.get('id')[0] === '!') {
+ var x = _.findWhere(q, { key: item.get('id').substr(1) });
+ if (x) {
+ if (!param) {
+ param = { value: item.get('id') };
+ } else {
+ param.value += ',' + item.get('id');
+ }
+ }
+ }
+ });
+ }
+
+ if (param && param.value) {
+ this.model.set('enabled', true);
+ this.restore(param.value, param);
+ } else {
+ this.clear();
+ }
+ },
+
+
+ restore: function(value) {
+ if (_.isString(value)) {
+ value = value.split(',');
+ }
+
+ if (this.choices && value.length > 0) {
+ var that = this;
+
+ that.choices.each(function(item) {
+ item.set('checked', false);
+ });
+
+ _.each(value, function(v) {
+ var cModel = that.choices.findWhere({ id: v });
+ cModel.set('checked', true);
+ });
+
+ this.model.set({
+ value: value,
+ enabled: true
+ });
+
+ this.render();
+ } else {
+ this.clear();
+ }
+ },
+
+
+ clear: function() {
+ if (this.choices) {
+ this.choices.each(function(item) {
+ item.set('checked', false);
+ });
+ }
+ this.model.unset('value');
+ this.detailsView.render();
+ if (this.detailsView.updateCurrent) {
+ this.detailsView.updateCurrent(0);
+ }
+ },
+
+
+ formatValue: function() {
+ var q = {};
+ if (this.model.has('property') && this.model.has('value') && this.model.get('value').length > 0) {
+ var opposite = _.filter(this.model.get('value'), function(item) {
+ return item[0] === '!';
+ });
+ if (opposite.length > 0) {
+ opposite.forEach(function(item) {
+ q[item.substr(1)] = false;
+ });
+ } else {
+ q[this.model.get('property')] = this.model.get('value').join(',');
+ }
+ }
+ return q;
+ }
+
+ });
+
+
+
+ /*
+ * Export public classes
+ */
+
+ return {
+ DetailsChoiceFilterView: DetailsChoiceFilterView,
+ ChoiceFilterView: ChoiceFilterView
+ };
+
+});
diff --git a/sonar-server/src/main/js/navigator/filters/context-filters.js b/sonar-server/src/main/js/navigator/filters/context-filters.js
new file mode 100644
index 00000000000..ec71ff3f774
--- /dev/null
+++ b/sonar-server/src/main/js/navigator/filters/context-filters.js
@@ -0,0 +1,66 @@
+define(['backbone'], function (Backbone) {
+
+ return Backbone.View.extend({
+
+ initialize: function() {
+ this.model.view = this;
+ },
+
+
+ render: function() {
+ this.$el.hide();
+ },
+
+
+ renderBase: function() {},
+ renderInput: function() {},
+ focus: function() {},
+ showDetails: function() {},
+ registerShowedDetails: function() {},
+ hideDetails: function() {},
+ isActive: function() {},
+ renderValue: function() {},
+ isDefaultValue: function() {},
+
+
+ restoreFromQuery: function(q) {
+ var param = _.findWhere(q, { key: this.model.get('property') });
+ if (param && param.value) {
+ this.restore(param.value);
+ } else {
+ this.clear();
+ }
+ },
+
+
+ restore: function(value) {
+ this.model.set({ value: value });
+ },
+
+
+ clear: function() {
+ this.model.unset('value');
+ },
+
+
+ disable: function(e) {
+ e.stopPropagation();
+ this.hideDetails();
+ this.options.filterBarView.hideDetails();
+ this.model.set({
+ enabled: false,
+ value: null
+ });
+ },
+
+
+ formatValue: function() {
+ var q = {};
+ if (this.model.has('property') && this.model.has('value') && this.model.get('value')) {
+ q[this.model.get('property')] = this.model.get('value');
+ }
+ return q;
+ }
+ });
+
+});
diff --git a/sonar-server/src/main/js/navigator/filters/date-filter-view.coffee b/sonar-server/src/main/js/navigator/filters/date-filter-view.coffee
new file mode 100644
index 00000000000..da9fc81679b
--- /dev/null
+++ b/sonar-server/src/main/js/navigator/filters/date-filter-view.coffee
@@ -0,0 +1,11 @@
+define [
+ 'navigator/filters/string-filters'
+], (
+ StringFilterView
+) ->
+
+ class DateFilterView extends StringFilterView
+
+ render: ->
+ super
+ @detailsView.$('input').prop 'placeholder', '1970-01-31'
diff --git a/sonar-server/src/main/js/navigator/filters/date-filter-view.js b/sonar-server/src/main/js/navigator/filters/date-filter-view.js
new file mode 100644
index 00000000000..30b6a7df873
--- /dev/null
+++ b/sonar-server/src/main/js/navigator/filters/date-filter-view.js
@@ -0,0 +1,24 @@
+(function() {
+ var __hasProp = {}.hasOwnProperty,
+ __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+ define(['navigator/filters/string-filters'], function(StringFilterView) {
+ var DateFilterView;
+ return DateFilterView = (function(_super) {
+ __extends(DateFilterView, _super);
+
+ function DateFilterView() {
+ return DateFilterView.__super__.constructor.apply(this, arguments);
+ }
+
+ DateFilterView.prototype.render = function() {
+ DateFilterView.__super__.render.apply(this, arguments);
+ return this.detailsView.$('input').prop('placeholder', '1970-01-31');
+ };
+
+ return DateFilterView;
+
+ })(StringFilterView);
+ });
+
+}).call(this);
diff --git a/sonar-server/src/main/js/navigator/filters/favorite-filters.js b/sonar-server/src/main/js/navigator/filters/favorite-filters.js
new file mode 100644
index 00000000000..a5c8134b07d
--- /dev/null
+++ b/sonar-server/src/main/js/navigator/filters/favorite-filters.js
@@ -0,0 +1,79 @@
+define(['backbone', 'backbone.marionette', 'navigator/filters/base-filters', 'navigator/filters/choice-filters', 'common/handlebars-extensions'], function (Backbone, Marionette, BaseFilters, ChoiceFilters) {
+
+ var DetailsFavoriteFilterView = BaseFilters.DetailsFilterView.extend({
+ template: getTemplate('#favorite-details-filter-template'),
+
+
+ events: {
+ 'click label[data-id]': 'applyFavorite',
+ 'click .manage label': 'manage'
+ },
+
+
+ applyFavorite: function(e) {
+ var id = $j(e.target).data('id');
+ window.location = baseUrl + this.model.get('favoriteUrl') + '/' + id;
+ },
+
+
+ manage: function() {
+ window.location = baseUrl + this.model.get('manageUrl');
+ },
+
+
+ serializeData: function() {
+ var choices = this.model.get('choices'),
+ choicesArray =
+ _.sortBy(
+ _.map(choices, function (v, k) {
+ return { v: v, k: k };
+ }),
+ 'v');
+
+ return _.extend({}, this.model.toJSON(), {
+ choicesArray: choicesArray
+ });
+ }
+
+ });
+
+
+
+ var FavoriteFilterView = ChoiceFilters.ChoiceFilterView.extend({
+ template: getTemplate('#favorite-filter-template'),
+ className: 'navigator-filter navigator-filter-favorite',
+
+
+ initialize: function() {
+ ChoiceFilters.ChoiceFilterView.prototype.initialize.call(this, {
+ detailsView: DetailsFavoriteFilterView
+ });
+ },
+
+
+ renderValue: function() {
+ return '';
+ },
+
+
+ renderInput: function() {},
+
+
+ isDefaultValue: function() {
+ return false;
+ }
+
+ });
+
+
+
+ /*
+ * Export public classes
+ */
+
+ return {
+ DetailsFavoriteFilterView: DetailsFavoriteFilterView,
+ FavoriteFilterView: FavoriteFilterView
+ };
+
+});
diff --git a/sonar-server/src/main/js/navigator/filters/filter-bar.js b/sonar-server/src/main/js/navigator/filters/filter-bar.js
new file mode 100644
index 00000000000..a859c721382
--- /dev/null
+++ b/sonar-server/src/main/js/navigator/filters/filter-bar.js
@@ -0,0 +1,107 @@
+define(
+ [
+ 'backbone.marionette',
+ 'navigator/filters/base-filters',
+ 'navigator/filters/more-criteria-filters',
+ 'navigator/filters/favorite-filters',
+ 'common/handlebars-extensions'
+ ],
+ function (Marionette, BaseFilters) {
+
+ return Marionette.CompositeView.extend({
+ template: getTemplate('#filter-bar-template'),
+ itemViewContainer: '.navigator-filters-list',
+
+
+ collectionEvents: {
+ 'change:enabled': 'changeEnabled'
+ },
+
+
+ getItemView: function (item) {
+ return item.get('type') || BaseFilters.BaseFilterView;
+ },
+
+
+ itemViewOptions: function () {
+ return {
+ filterBarView: this,
+ app: this.options.app
+ };
+ },
+
+
+ initialize: function () {
+ Marionette.CompositeView.prototype.initialize.apply(this, arguments);
+
+ var that = this;
+ $j('body').on('click', function () {
+ that.hideDetails();
+ });
+ this.addMoreCriteriaFilter();
+ },
+
+
+ addMoreCriteriaFilter: function() {
+ var disabledFilters = this.collection.where({ enabled: false });
+ if (disabledFilters.length > 0) {
+ this.moreCriteriaFilter = new BaseFilters.Filter({
+ type: require('navigator/filters/more-criteria-filters').MoreCriteriaFilterView,
+ enabled: true,
+ optional: false,
+ filters: disabledFilters
+ });
+ this.collection.add(this.moreCriteriaFilter);
+ }
+ },
+
+
+ onAfterItemAdded: function (itemView) {
+ if (itemView.model.get('type') === require('navigator/filters/favorite-filters').FavoriteFilterView) {
+ jQuery('.navigator-header').addClass('navigator-header-favorite');
+ }
+ },
+
+
+ restoreFromQuery: function (q) {
+ this.collection.each(function (item) {
+ item.set('enabled', !item.get('optional'));
+ item.view.clear();
+ item.view.restoreFromQuery(q);
+ });
+ },
+
+
+ hideDetails: function () {
+ if (_.isObject(this.showedView)) {
+ this.showedView.hideDetails();
+ }
+ },
+
+
+ enableFilter: function (id) {
+ var filter = this.collection.get(id),
+ filterView = filter.view;
+
+ filterView.$el.detach().insertBefore(this.$('.navigator-filter-more-criteria'));
+ filter.set('enabled', true);
+ filterView.showDetails();
+ },
+
+
+ changeEnabled: function () {
+ var disabledFilters = _.reject(this.collection.where({ enabled: false }), function (filter) {
+ return filter.get('type') === require('navigator/filters/more-criteria-filters').MoreCriteriaFilterView;
+ });
+
+ if (disabledFilters.length === 0) {
+ this.moreCriteriaFilter.set({ enabled: false }, { silent: true });
+ } else {
+ this.moreCriteriaFilter.set({ enabled: true }, { silent: true });
+ }
+ this.moreCriteriaFilter.set('filters', disabledFilters);
+ }
+
+ });
+
+ });
diff --git a/sonar-server/src/main/js/navigator/filters/metric-filters.js b/sonar-server/src/main/js/navigator/filters/metric-filters.js
new file mode 100644
index 00000000000..f680d0e06e1
--- /dev/null
+++ b/sonar-server/src/main/js/navigator/filters/metric-filters.js
@@ -0,0 +1,169 @@
+define(['navigator/filters/base-filters', 'common/handlebars-extensions'], function (BaseFilters) {
+
+ var DetailsMetricFilterView = BaseFilters.DetailsFilterView.extend({
+ template: getTemplate('#metric-filter-template'),
+
+
+ events: {
+ 'change :input': 'inputChanged'
+ },
+
+
+ inputChanged: function() {
+ var value = {
+ metric: this.$('[name=metric]').val(),
+ metricText: this.$('[name=metric] option:selected').text(),
+ period: this.$('[name=period]').val(),
+ periodText: this.$('[name=period] option:selected').text(),
+ op: this.$('[name=op]').val(),
+ opText: this.$('[name=op] option:selected').text(),
+ val: this.$('[name=val]').val(),
+ valText: this.$('[name=val]').originalVal()
+ };
+ this.updateDataType(value);
+ this.model.set('value', value);
+ },
+
+
+ updateDataType: function(value) {
+ var metric = _.find(window.SS.metrics, function(m) {
+ return m.metric.name === value.metric;
+ });
+ if (metric) {
+ this.$('[name=val]').data('type', metric.metric.val_type);
+ switch (metric.metric.val_type) {
+ case 'WORK_DUR':
+ this.$('[name=val]').prop('placeholder', '1d 7h 59min');
+ break;
+ case 'RATING':
+ this.$('[name=val]').prop('placeholder', 'A');
+ break;
+ }
+ }
+ },
+
+
+ onRender: function() {
+ var value = this.model.get('value') || {};
+ this.$('[name=metric]').val(value.metric).select2({
+ width: '100%',
+ placeholder: window.SS.phrases.metric
+ });
+ this.$('[name=period]').val(value.period || 0).select2({
+ width: '100%',
+ minimumResultsForSearch: 100
+ });
+ this.$('[name=op]').val(value.op || 'eq').select2({
+ width: '60px',
+ placeholder: '=',
+ minimumResultsForSearch: 100
+ });
+ this.updateDataType(value);
+ this.$('[name=val]').val(value.val);
+ this.inputChanged();
+ },
+
+
+ onShow: function() {
+ var select = this.$('[name=metric]');
+ if (!select.val()) {
+ select.select2('open');
+ }
+ }
+
+ });
+
+
+
+ return BaseFilters.BaseFilterView.extend({
+
+ initialize: function() {
+ BaseFilters.BaseFilterView.prototype.initialize.call(this, {
+ detailsView: DetailsMetricFilterView
+ });
+
+ this.groupMetrics();
+ },
+
+
+ groupMetrics: function() {
+ var metrics = _.map(this.model.get('metrics'), function (metric) {
+ return metric.metric;
+ }),
+ groupedMetrics =
+ _.sortBy(
+ _.map(
+ _.groupBy(metrics, 'domain'),
+ function (metrics, domain) {
+ return {
+ domain: domain,
+ metrics: _.sortBy(metrics, 'short_name')
+ };
+ }),
+ 'domain'
+ );
+ this.model.set('groupedMetrics', groupedMetrics);
+ },
+
+
+ renderValue: function() {
+ return this.isDefaultValue() ?
+ window.SS.phrases.notSet :
+ this.model.get('value').metricText + ' ' + this.model.get('value').opText + ' ' + this.model.get('value').valText;
+ },
+
+
+ renderInput: function() {
+ var that = this,
+ value = this.model.get('value');
+
+ if (_.isObject(value) && value.metric && value.op && value.val) {
+ _.each(['metric', 'period', 'op', 'val'], function(key) {
+ var v = value[key];
+ if (key === 'period' && v === '0') {
+ v = '';
+ }
+
+ $j('<input>')
+ .prop('name', that.model.get('property') + '_' + key)
+ .prop('type', 'hidden')
+ .css('display', 'none')
+ .val(v)
+ .appendTo(that.$el);
+ });
+ }
+ },
+
+
+ isDefaultValue: function() {
+ var value = this.model.get('value');
+ if (!_.isObject(value)) {
+ return true;
+ }
+ return !(value.metric && value.op && value.val);
+ },
+
+
+ restoreFromQuery: function(q) {
+ var that = this,
+ value = {};
+ _.each(['metric', 'period', 'op', 'val'], function(p) {
+ var property = that.model.get('property') + '_' + p,
+ pValue = _.findWhere(q, { key: property });
+
+ if (pValue && pValue.value) {
+ value[p] = pValue.value;
+ }
+ });
+
+ if (value && value.metric && value.op && value.val) {
+ this.model.set({
+ value: value,
+ enabled: true
+ });
+ }
+ }
+
+ });
+
+});
diff --git a/sonar-server/src/main/js/navigator/filters/more-criteria-filters.js b/sonar-server/src/main/js/navigator/filters/more-criteria-filters.js
new file mode 100644
index 00000000000..755c4bbfb42
--- /dev/null
+++ b/sonar-server/src/main/js/navigator/filters/more-criteria-filters.js
@@ -0,0 +1,70 @@
+define(['navigator/filters/base-filters', 'navigator/filters/choice-filters', 'common/handlebars-extensions'], function (BaseFilters, ChoiceFilters) {
+
+ var DetailsMoreCriteriaFilterView = BaseFilters.DetailsFilterView.extend({
+ template: getTemplate('#more-criteria-details-filter-template'),
+
+
+ events: {
+ 'click label[data-id]:not(.inactive)': 'enableFilter'
+ },
+
+
+ enableFilter: function(e) {
+ var id = $j(e.target).data('id');
+ this.model.view.options.filterBarView.enableFilter(id);
+ this.model.view.hideDetails();
+ },
+
+
+ serializeData: function() {
+ var filters = this.model.get('filters').map(function(filter) {
+ return _.extend(filter.toJSON(), { id: filter.cid });
+ }),
+ uniqueFilters = _.unique(filters, function(filter) {
+ return filter.name;
+ });
+ return _.extend(this.model.toJSON(), { filters: uniqueFilters });
+ }
+
+ });
+
+
+
+ var MoreCriteriaFilterView = ChoiceFilters.ChoiceFilterView.extend({
+ template: getTemplate('#more-criteria-filter-template'),
+ className: 'navigator-filter navigator-filter-more-criteria',
+
+
+ initialize: function() {
+ ChoiceFilters.ChoiceFilterView.prototype.initialize.call(this, {
+ detailsView: DetailsMoreCriteriaFilterView
+ });
+ },
+
+
+ renderValue: function() {
+ return '';
+ },
+
+
+ renderInput: function() {},
+
+
+ isDefaultValue: function() {
+ return false;
+ }
+
+ });
+
+
+
+ /*
+ * Export public classes
+ */
+
+ return {
+ DetailsMoreCriteriaFilterView: DetailsMoreCriteriaFilterView,
+ MoreCriteriaFilterView: MoreCriteriaFilterView
+ };
+
+});
diff --git a/sonar-server/src/main/js/navigator/filters/range-filters.js b/sonar-server/src/main/js/navigator/filters/range-filters.js
new file mode 100644
index 00000000000..910d37c7fa5
--- /dev/null
+++ b/sonar-server/src/main/js/navigator/filters/range-filters.js
@@ -0,0 +1,195 @@
+define(['navigator/filters/base-filters', 'common/handlebars-extensions'], function (BaseFilters) {
+
+ var DetailsRangeFilterView = BaseFilters.DetailsFilterView.extend({
+ template: getTemplate('#range-filter-template'),
+
+
+ events: {
+ 'change input': 'change'
+ },
+
+
+ change: function() {
+ var value = {},
+ valueFrom = this.$('input').eq(0).val(),
+ valueTo = this.$('input').eq(1).val();
+
+ if (valueFrom.length > 0) {
+ value[this.model.get('propertyFrom')] = valueFrom;
+ }
+
+ if (valueTo.length > 0) {
+ value[this.model.get('propertyTo')] = valueTo;
+ }
+
+ this.model.set('value', value);
+ },
+
+
+ populateInputs: function() {
+ var value = this.model.get('value'),
+ propertyFrom = this.model.get('propertyFrom'),
+ propertyTo = this.model.get('propertyTo'),
+ valueFrom = _.isObject(value) && value[propertyFrom],
+ valueTo = _.isObject(value) && value[propertyTo];
+
+ this.$('input').eq(0).val(valueFrom || '');
+ this.$('input').eq(1).val(valueTo || '');
+ },
+
+
+ onShow: function() {
+ this.$(':input:first').focus();
+ }
+
+ });
+
+
+
+ var RangeFilterView = BaseFilters.BaseFilterView.extend({
+
+ initialize: function() {
+ BaseFilters.BaseFilterView.prototype.initialize.call(this, {
+ detailsView: DetailsRangeFilterView
+ });
+ },
+
+
+ renderValue: function() {
+ if (!this.isDefaultValue()) {
+ var value = _.values(this.model.get('value'));
+ return value.join(' — ');
+ } else {
+ return window.SS.phrases.any;
+ }
+ },
+
+
+ renderInput: function() {
+ var value = this.model.get('value'),
+ propertyFrom = this.model.get('propertyFrom'),
+ propertyTo = this.model.get('propertyTo'),
+ valueFrom = _.isObject(value) && value[propertyFrom],
+ valueTo = _.isObject(value) && value[propertyTo];
+
+ $j('<input>')
+ .prop('name', propertyFrom)
+ .prop('type', 'hidden')
+ .css('display', 'none')
+ .val(valueFrom || '')
+ .appendTo(this.$el);
+
+ $j('<input>')
+ .prop('name', propertyTo)
+ .prop('type', 'hidden')
+ .css('display', 'none')
+ .val(valueTo || '')
+ .appendTo(this.$el);
+ },
+
+
+ isDefaultValue: function() {
+ var value = this.model.get('value'),
+ propertyFrom = this.model.get('propertyFrom'),
+ propertyTo = this.model.get('propertyTo'),
+ valueFrom = _.isObject(value) && value[propertyFrom],
+ valueTo = _.isObject(value) && value[propertyTo];
+
+ return !valueFrom && !valueTo;
+ },
+
+
+ restoreFromQuery: function(q) {
+ var paramFrom = _.findWhere(q, { key: this.model.get('propertyFrom') }),
+ paramTo = _.findWhere(q, { key: this.model.get('propertyTo') }),
+ value = {};
+
+ if ((paramFrom && paramFrom.value) || (paramTo && paramTo.value)) {
+ if (paramFrom && paramFrom.value) {
+ value[this.model.get('propertyFrom')] = paramFrom.value;
+ }
+
+ if (paramTo && paramTo.value) {
+ value[this.model.get('propertyTo')] = paramTo.value;
+ }
+
+ this.model.set({
+ value: value,
+ enabled: true
+ });
+
+ this.detailsView.populateInputs();
+ }
+ },
+
+
+ restore: function(value) {
+ if (this.choices && this.selection && value.length > 0) {
+ var that = this;
+ this.choices.add(this.selection.models);
+ this.selection.reset([]);
+
+ _.each(value, function(v) {
+ var cModel = that.choices.findWhere({ id: v });
+
+ if (cModel) {
+ that.selection.add(cModel);
+ that.choices.remove(cModel);
+ }
+ });
+
+ this.detailsView.updateLists();
+
+ this.model.set({
+ value: value,
+ enabled: true
+ });
+ }
+ },
+
+
+ formatValue: function() {
+ return this.model.get('value');
+ },
+
+
+ clear: function() {
+ this.model.unset('value');
+ this.detailsView.render();
+ }
+
+ });
+
+
+
+ var DateRangeFilterView = RangeFilterView.extend({
+
+ render: function() {
+ RangeFilterView.prototype.render.apply(this, arguments);
+ this.detailsView.$('input').prop('placeholder', '1970-01-31');
+ },
+
+
+ renderValue: function() {
+ if (!this.isDefaultValue()) {
+ var value = _.values(this.model.get('value'));
+ return value.join(' — ');
+ } else {
+ return window.SS.phrases.anytime;
+ }
+ }
+
+ });
+
+
+
+ /*
+ * Export public classes
+ */
+
+ return {
+ RangeFilterView: RangeFilterView,
+ DateRangeFilterView: DateRangeFilterView
+ };
+
+});
diff --git a/sonar-server/src/main/js/navigator/filters/read-only-filters.js b/sonar-server/src/main/js/navigator/filters/read-only-filters.js
new file mode 100644
index 00000000000..5a203e5214f
--- /dev/null
+++ b/sonar-server/src/main/js/navigator/filters/read-only-filters.js
@@ -0,0 +1,25 @@
+define(['backbone', 'navigator/filters/base-filters'], function (Backbone, BaseFilters) {
+
+ return BaseFilters.BaseFilterView.extend({
+ className: 'navigator-filter navigator-filter-read-only',
+
+
+ events: {
+ 'click .navigator-filter-disable': 'disable'
+ },
+
+
+ isDefaultValue: function() {
+ return false;
+ },
+
+
+ renderValue: function() {
+ var value = this.model.get('value'),
+ format = this.model.get('format');
+ return value ? (format ? format(value) : value) : '';
+ }
+
+ });
+
+});
diff --git a/sonar-server/src/main/js/navigator/filters/rule-filters.js b/sonar-server/src/main/js/navigator/filters/rule-filters.js
new file mode 100644
index 00000000000..aed4b8d894a
--- /dev/null
+++ b/sonar-server/src/main/js/navigator/filters/rule-filters.js
@@ -0,0 +1,50 @@
+define(['backbone', 'navigator/filters/base-filters', 'navigator/filters/ajax-select-filters'], function (Backbone, BaseFilters, AjaxSelectFilters) {
+
+ var RuleSuggestions = AjaxSelectFilters.Suggestions.extend({
+
+ url: function() {
+ return baseUrl + '/api/rules/list';
+ },
+
+
+ parse: function(r) {
+ this.more = r.more;
+ return r.results.map(function(r) {
+ return { id: r.key, text: r.name, category: r.language };
+ });
+ }
+
+ });
+
+ return AjaxSelectFilters.AjaxSelectFilterView.extend({
+
+ initialize: function() {
+ AjaxSelectFilters.AjaxSelectFilterView.prototype.initialize.call(this, {
+ detailsView: AjaxSelectFilters.AjaxSelectDetailsFilterView
+ });
+
+ this.choices = new RuleSuggestions();
+ },
+
+
+ createRequest: function(v) {
+ var that = this;
+ return jQuery
+ .ajax({
+ url: baseUrl + '/api/rules/show',
+ type: 'GET',
+ data: { key: v }
+ })
+ .done(function (r) {
+ that.choices.add(new Backbone.Model({
+ id: r.rule.key,
+ text: r.rule.name,
+ category: r.rule.language,
+ checked: true
+ }));
+ });
+ }
+
+ });
+
+});
diff --git a/sonar-server/src/main/js/navigator/filters/string-filters.js b/sonar-server/src/main/js/navigator/filters/string-filters.js
new file mode 100644
index 00000000000..2e1278f350f
--- /dev/null
+++ b/sonar-server/src/main/js/navigator/filters/string-filters.js
@@ -0,0 +1,77 @@
+define(['navigator/filters/base-filters', 'common/handlebars-extensions'], function (BaseFilters) {
+
+ var DetailsStringFilterView = BaseFilters.DetailsFilterView.extend({
+ template: getTemplate('#string-filter-template'),
+
+
+ events: {
+ 'change input': 'change'
+ },
+
+
+ change: function(e) {
+ this.model.set('value', $j(e.target).val());
+ },
+
+
+ onShow: function() {
+ BaseFilters.DetailsFilterView.prototype.onShow.apply(this, arguments);
+ this.$(':input').focus();
+ },
+
+
+ serializeData: function() {
+ return _.extend({}, this.model.toJSON(), {
+ value: this.model.get('value') || ''
+ });
+ }
+
+ });
+
+
+
+ return BaseFilters.BaseFilterView.extend({
+
+ initialize: function() {
+ BaseFilters.BaseFilterView.prototype.initialize.call(this, {
+ detailsView: DetailsStringFilterView
+ });
+ },
+
+
+ renderValue: function() {
+ return this.isDefaultValue() ? '—' : this.model.get('value');
+ },
+
+
+ renderInput: function() {
+ $j('<input>')
+ .prop('name', this.model.get('property'))
+ .prop('type', 'hidden')
+ .css('display', 'none')
+ .val(this.model.get('value') || '')
+ .appendTo(this.$el);
+ },
+
+
+ isDefaultValue: function() {
+ return !this.model.get('value');
+ },
+
+
+ restore: function(value) {
+ this.model.set({
+ value: value,
+ enabled: true
+ });
+ },
+
+
+ clear: function() {
+ this.model.unset('value');
+ this.detailsView.render();
+ }
+
+ });
+
+});