aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main/js/components/navigator
diff options
context:
space:
mode:
authorStas Vilchik <vilchiks@gmail.com>2014-12-18 15:17:57 +0100
committerStas Vilchik <vilchiks@gmail.com>2014-12-22 10:46:42 +0100
commit4b69d42d7f83aa22853dffbf2b49acda85c7ef99 (patch)
tree9a92219c8ebc0afeade341755e7ba92ded6502f3 /server/sonar-web/src/main/js/components/navigator
parent2b6c121e6764b6baf106c94767d8daedd85b7839 (diff)
downloadsonarqube-4b69d42d7f83aa22853dffbf2b49acda85c7ef99.tar.gz
sonarqube-4b69d42d7f83aa22853dffbf2b49acda85c7ef99.zip
SONAR-5820 Create a new coding rules page
Diffstat (limited to 'server/sonar-web/src/main/js/components/navigator')
-rw-r--r--server/sonar-web/src/main/js/components/navigator/controller.js142
-rw-r--r--server/sonar-web/src/main/js/components/navigator/facets-view.js37
-rw-r--r--server/sonar-web/src/main/js/components/navigator/facets/base-facet.js76
-rw-r--r--server/sonar-web/src/main/js/components/navigator/models/facet.js22
-rw-r--r--server/sonar-web/src/main/js/components/navigator/models/facets.js10
-rw-r--r--server/sonar-web/src/main/js/components/navigator/models/state.js52
-rw-r--r--server/sonar-web/src/main/js/components/navigator/router.js29
-rw-r--r--server/sonar-web/src/main/js/components/navigator/workspace-header-view.js49
-rw-r--r--server/sonar-web/src/main/js/components/navigator/workspace-list-item-view.js26
-rw-r--r--server/sonar-web/src/main/js/components/navigator/workspace-list-view.js112
10 files changed, 555 insertions, 0 deletions
diff --git a/server/sonar-web/src/main/js/components/navigator/controller.js b/server/sonar-web/src/main/js/components/navigator/controller.js
new file mode 100644
index 00000000000..d1ae597ce0a
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/navigator/controller.js
@@ -0,0 +1,142 @@
+define([
+ 'backbone.marionette'
+], function (Marionette) {
+
+ return Marionette.Controller.extend({
+ pageSize: 50,
+ allFacets: [],
+ facetsFromServer: [],
+ transform: {},
+
+ initialize: function (options) {
+ this.app = options.app;
+ this.listenTo(options.app.state, 'change:query', this.fetchList);
+ },
+
+ _allFacets: function () {
+ return this.allFacets.map(function (facet) {
+ return {property: facet};
+ });
+ },
+
+ _enabledFacets: function () {
+ var that = this,
+ facets = this.options.app.state.get('facets'),
+ criteria = Object.keys(this.options.app.state.get('query'));
+ facets = facets.concat(criteria);
+ facets = facets.map(function (facet) {
+ return that.transform[facet] != null ? that.transform[facet] : facet;
+ });
+ return facets.filter(function (facet) {
+ return that.allFacets.indexOf(facet) !== -1;
+ });
+ },
+
+ _facetsFromServer: function () {
+ var that = this,
+ facets = this._enabledFacets();
+ return facets.filter(function (facet) {
+ return that.facetsFromServer.indexOf(facet) !== -1;
+ });
+ },
+
+ fetchList: function () {
+
+ },
+
+ fetchNextPage: function () {
+ this.options.app.state.nextPage();
+ return this.fetchList(false);
+ },
+
+ enableFacet: function (id) {
+ var facet = this.options.app.facets.get(id);
+ if (facet.has('values') || this.facetsFromServer.indexOf(id) === -1) {
+ facet.set({enabled: true});
+ } else {
+ var p = window.process.addBackgroundProcess();
+ this.requestFacet(id)
+ .done(function () {
+ facet.set({enabled: true});
+ window.process.finishBackgroundProcess(p);
+ })
+ .fail(function () {
+ window.process.failBackgroundProcess(p);
+ });
+ }
+ },
+
+ disableFacet: function (id) {
+ var facet = this.options.app.facets.get(id);
+ facet.set({enabled: false});
+ this.options.app.facetsView.children.findByModel(facet).disable();
+ },
+
+ toggleFacet: function (id) {
+ var facet = this.options.app.facets.get(id);
+ if (facet.get('enabled')) {
+ this.disableFacet(id);
+ } else {
+ this.enableFacet(id);
+ }
+ },
+
+ enableFacets: function (facets) {
+ facets.forEach(this.enableFacet, this);
+ },
+
+ newSearch: function () {
+ this.options.app.state.setQuery({});
+ },
+
+ parseQuery: function (query, separator) {
+ separator = separator || '|';
+ var q = {};
+ (query || '').split(separator).forEach(function (t) {
+ var tokens = t.split('=');
+ if (tokens[0] && tokens[1] != null) {
+ q[tokens[0]] = decodeURIComponent(tokens[1]);
+ }
+ });
+ return q;
+ },
+
+ getQuery: function (separator) {
+ separator = separator || '|';
+ var filter = this.options.app.state.get('query'),
+ route = [];
+ _.map(filter, function (value, property) {
+ route.push('' + property + '=' + encodeURIComponent(value));
+ });
+ return route.join(separator);
+ },
+
+ getRoute: function (separator) {
+ separator = separator || '|';
+ return this.getQuery(separator);
+ },
+
+ selectNext: function () {
+ var index = this.options.app.state.get('selectedIndex') + 1;
+ if (index < this.options.app.list.length) {
+ this.options.app.state.set({ selectedIndex: index });
+ } else {
+ if (!this.options.app.state.get('maxResultsReached')) {
+ var that = this;
+ this.fetchNextPage().done(function () {
+ that.options.app.state.set({ selectedIndex: index });
+ });
+ }
+ }
+ },
+
+ selectPrev: function () {
+ var index = this.options.app.state.get('selectedIndex') - 1;
+ if (index >= 0) {
+ this.options.app.state.set({ selectedIndex: index });
+ }
+ }
+
+ });
+
+});
diff --git a/server/sonar-web/src/main/js/components/navigator/facets-view.js b/server/sonar-web/src/main/js/components/navigator/facets-view.js
new file mode 100644
index 00000000000..0d3f9102833
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/navigator/facets-view.js
@@ -0,0 +1,37 @@
+define([
+ 'backbone.marionette',
+ 'components/navigator/facets/base-facet'
+], function (Marionette, BaseFacet) {
+
+ return Marionette.CollectionView.extend({
+ className: 'search-navigator-facets-list',
+
+ itemViewOptions: function () {
+ return {
+ app: this.options.app
+ };
+ },
+
+ getItemView: function () {
+ return BaseFacet;
+ },
+
+ collectionEvents: function () {
+ return {
+ 'change:enabled': 'updateState'
+ };
+ },
+
+ updateState: function () {
+ var enabledFacets = this.collection.filter(function (model) {
+ return model.get('enabled');
+ }),
+ enabledFacetIds = enabledFacets.map(function (model) {
+ return model.id;
+ });
+ this.options.app.state.set({facets: enabledFacetIds});
+ }
+
+ });
+
+});
diff --git a/server/sonar-web/src/main/js/components/navigator/facets/base-facet.js b/server/sonar-web/src/main/js/components/navigator/facets/base-facet.js
new file mode 100644
index 00000000000..c2c894ab45a
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/navigator/facets/base-facet.js
@@ -0,0 +1,76 @@
+define([
+ 'backbone.marionette'
+], function (Marionette) {
+
+ var $ = jQuery;
+
+ return Marionette.ItemView.extend({
+ className: 'search-navigator-facet-box',
+
+ modelEvents: function () {
+ return {
+ 'change': 'render'
+ };
+ },
+
+ events: function () {
+ return {
+ 'click .js-facet-toggle': 'toggle',
+ 'click .js-facet': 'toggleFacet'
+ };
+ },
+
+ onRender: function () {
+ this.$el.toggleClass('search-navigator-facet-box-collapsed', !this.model.get('enabled'));
+ var that = this,
+ property = this.model.get('property'),
+ value = this.options.app.state.get('query')[property];
+ if (typeof value === 'string') {
+ value.split(',').forEach(function (s) {
+ var facet = that.$('.js-facet').filter('[data-value="' + s + '"]');
+ if (facet.length > 0) {
+ facet.addClass('active');
+ }
+ });
+ }
+ },
+
+ toggle: function () {
+ this.options.app.controller.toggleFacet(this.model.id);
+ },
+
+ getValue: function () {
+ return this.$('.js-facet.active').map(function () {
+ return $(this).data('value');
+ }).get().join();
+ },
+
+ toggleFacet: function (e) {
+ $(e.currentTarget).toggleClass('active');
+ var property = this.model.get('property'),
+ obj = {};
+ obj[property] = this.getValue();
+ this.options.app.state.updateFilter(obj);
+ },
+
+ disable: function () {
+ var property = this.model.get('property'),
+ obj = {};
+ obj[property] = null;
+ this.options.app.state.updateFilter(obj);
+ },
+
+ sortValues: function (values) {
+ return _.sortBy(values, function (v) {
+ return -v.count;
+ });
+ },
+
+ serializeData: function () {
+ return _.extend(Marionette.ItemView.prototype.serializeData.apply(this, arguments), {
+ values: this.sortValues(this.model.getValues())
+ });
+ }
+ });
+
+});
diff --git a/server/sonar-web/src/main/js/components/navigator/models/facet.js b/server/sonar-web/src/main/js/components/navigator/models/facet.js
new file mode 100644
index 00000000000..7d15da0507a
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/navigator/models/facet.js
@@ -0,0 +1,22 @@
+define([
+ 'backbone'
+], function (Backbone) {
+
+ return Backbone.Model.extend({
+ idAttribute: 'property',
+
+ defaults: {
+ enabled: false
+ },
+
+ getValues: function () {
+ return this.get('values') || [];
+ },
+
+ toggle: function () {
+ var enabled = this.get('enabled');
+ this.set({ enabled: !enabled });
+ }
+ });
+
+});
diff --git a/server/sonar-web/src/main/js/components/navigator/models/facets.js b/server/sonar-web/src/main/js/components/navigator/models/facets.js
new file mode 100644
index 00000000000..88929deb8da
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/navigator/models/facets.js
@@ -0,0 +1,10 @@
+define([
+ 'backbone',
+ 'components/navigator/models/facet'
+], function (Backbone, Facet) {
+
+ return Backbone.Collection.extend({
+ model: Facet
+ });
+
+});
diff --git a/server/sonar-web/src/main/js/components/navigator/models/state.js b/server/sonar-web/src/main/js/components/navigator/models/state.js
new file mode 100644
index 00000000000..8a4f2ff669c
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/navigator/models/state.js
@@ -0,0 +1,52 @@
+define([
+ 'backbone'
+], function (Backbone) {
+
+ return Backbone.Model.extend({
+ defaults: {
+ page: 1,
+ maxResultsReached: false,
+ query: {},
+ facets: []
+ },
+
+ nextPage: function () {
+ var page = this.get('page');
+ this.set({ page: page + 1 });
+ },
+
+ clearQuery: function (query) {
+ var q = {};
+ Object.keys(query).forEach(function (key) {
+ if (query[key]) {
+ q[key] = query[key];
+ }
+ });
+ return q;
+ },
+
+ _areQueriesEqual: function (a, b) {
+ var equal = Object.keys(a).length === Object.keys(b).length;
+ Object.keys(a).forEach(function (key) {
+ equal = equal && a[key] === b[key];
+ });
+ return equal;
+ },
+
+ updateFilter: function (obj) {
+ var oldQuery = this.get('query'),
+ query = _.extend({}, oldQuery, obj);
+ query = this.clearQuery(query);
+ if (!this._areQueriesEqual(oldQuery, query)) {
+ this.setQuery(query);
+ }
+ },
+
+ setQuery: function (query) {
+ this.set({ query: query }, { silent: true });
+ this.set({ changed: true });
+ this.trigger('change:query');
+ }
+ });
+
+});
diff --git a/server/sonar-web/src/main/js/components/navigator/router.js b/server/sonar-web/src/main/js/components/navigator/router.js
new file mode 100644
index 00000000000..8792dd519c7
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/navigator/router.js
@@ -0,0 +1,29 @@
+define([
+ 'backbone'
+], function (Backbone) {
+
+ return Backbone.Router.extend({
+ routeSeparator: '|',
+
+ routes: {
+ '': 'index',
+ ':query': 'index'
+ },
+
+ initialize: function (options) {
+ this.options = options;
+ this.listenTo(this.options.app.state, 'change:query', this.updateRoute);
+ },
+
+ index: function (query) {
+ query = this.options.app.controller.parseQuery(query);
+ this.options.app.state.setQuery(query);
+ },
+
+ updateRoute: function () {
+ var route = this.options.app.controller.getRoute();
+ this.navigate(route);
+ }
+ });
+
+});
diff --git a/server/sonar-web/src/main/js/components/navigator/workspace-header-view.js b/server/sonar-web/src/main/js/components/navigator/workspace-header-view.js
new file mode 100644
index 00000000000..aec6efa6284
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/navigator/workspace-header-view.js
@@ -0,0 +1,49 @@
+define([
+ 'backbone.marionette'
+], function (Marionette) {
+
+ return Marionette.ItemView.extend({
+
+ collectionEvents: function () {
+ return {
+ 'all': 'render'
+ };
+ },
+
+ events: function () {
+ return {
+ 'click .js-bulk-change': 'bulkChange',
+ 'click .js-reload': 'reload',
+ 'click .js-next': 'selectNext',
+ 'click .js-prev': 'selectPrev'
+ };
+ },
+
+ initialize: function (options) {
+ this.listenTo(options.app.state, 'change', this.render);
+ },
+
+ bulkChange: function () {
+
+ },
+
+ reload: function () {
+ this.options.app.controller.fetchList();
+ },
+
+ selectNext: function () {
+ this.options.app.controller.selectNext();
+ },
+
+ selectPrev: function () {
+ this.options.app.controller.selectPrev();
+ },
+
+ serializeData: function () {
+ return _.extend(Marionette.ItemView.prototype.serializeData.apply(this, arguments), {
+ state: this.options.app.state.toJSON()
+ });
+ }
+ });
+
+});
diff --git a/server/sonar-web/src/main/js/components/navigator/workspace-list-item-view.js b/server/sonar-web/src/main/js/components/navigator/workspace-list-item-view.js
new file mode 100644
index 00000000000..b4bb669303a
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/navigator/workspace-list-item-view.js
@@ -0,0 +1,26 @@
+define([
+ 'backbone.marionette'
+], function (Marionette) {
+
+ return Marionette.ItemView.extend({
+
+ initialize: function (options) {
+ this.listenTo(options.app.state, 'change:selectedIndex', this.select);
+ },
+
+ onRender: function () {
+ this.select();
+ },
+
+ select: function () {
+ var selected = this.model.get('index') === this.options.app.state.get('selectedIndex');
+ this.$el.toggleClass('selected', selected);
+ },
+
+ selectCurrent: function () {
+ this.options.app.state.set({ selectedIndex: this.model.get('index') });
+ }
+
+ });
+
+});
diff --git a/server/sonar-web/src/main/js/components/navigator/workspace-list-view.js b/server/sonar-web/src/main/js/components/navigator/workspace-list-view.js
new file mode 100644
index 00000000000..2c9ef0def7d
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/navigator/workspace-list-view.js
@@ -0,0 +1,112 @@
+define([
+ 'backbone.marionette'
+], function (Marionette) {
+
+ var $ = jQuery;
+
+ return Marionette.CompositeView.extend({
+
+ ui: {
+ loadMore: '.js-more'
+ },
+
+ itemViewOptions: function () {
+ return {
+ app: this.options.app
+ };
+ },
+
+ collectionEvents: {
+ 'reset': 'scrollToTop'
+ },
+
+ initialize: function (options) {
+ this.loadMoreThrottled = _.throttle(this.loadMore, 1000, {trailing: false});
+ this.listenTo(options.app.state, 'change:maxResultsReached', this.toggleLoadMore);
+ this.listenTo(options.app.state, 'change:selectedIndex', this.scrollTo);
+ this.bindShortcuts();
+ },
+
+ onClose: function () {
+ this.unbindScrollEvents();
+ this.unbindShortcuts();
+ },
+
+ toggleLoadMore: function () {
+ this.ui.loadMore.toggle(!this.options.app.state.get('maxResultsReached'));
+ },
+
+ bindScrollEvents: function () {
+ var that = this;
+ $(window).on('scroll.workspace-list-view', function () {
+ that.onScroll();
+ });
+ },
+
+ unbindScrollEvents: function () {
+ $(window).off('scroll.workspace-list-view');
+ },
+
+ bindShortcuts: function () {
+ var that = this;
+ key('up', 'list', function () {
+ that.options.app.controller.selectPrev();
+ return false;
+ });
+
+ key('down', 'list', function () {
+ that.options.app.controller.selectNext();
+ return false;
+ });
+ },
+
+ loadMore: function () {
+ if (!this.options.app.state.get('maxResultsReached')) {
+ var that = this;
+ this.unbindScrollEvents();
+ this.options.app.controller.fetchNextPage().done(function () {
+ that.bindScrollEvents();
+ });
+ }
+ },
+
+ disablePointerEvents: function () {
+ clearTimeout(this.scrollTimer);
+ $('body').addClass('disabled-pointer-events');
+ this.scrollTimer = setTimeout(function () {
+ $('body').removeClass('disabled-pointer-events');
+ }, 250);
+ },
+
+ onScroll: function () {
+ this.disablePointerEvents();
+ if ($(window).scrollTop() + $(window).height() >= this.ui.loadMore.offset().top) {
+ this.loadMoreThrottled();
+ }
+ },
+
+ scrollToTop: function () {
+ this.$el.scrollParent().scrollTop(0);
+ },
+
+ scrollTo: function () {
+ var selected = this.collection.at(this.options.app.state.get('selectedIndex'));
+ if (selected == null) {
+ return;
+ }
+ var selectedView = this.children.findByModel(selected),
+ viewTop = selectedView.$el.offset().top,
+ viewBottom = selectedView.$el.offset().top + selectedView.$el.outerHeight(),
+ windowTop = $(window).scrollTop(),
+ windowBottom = windowTop + $(window).height();
+ if (viewTop < windowTop) {
+ $(window).scrollTop(viewTop);
+ }
+ if (viewBottom > windowBottom) {
+ $(window).scrollTop($(window).scrollTop() - windowBottom + viewBottom);
+ }
+ }
+
+ });
+
+});