cartodb-4.42/lib/assets/javascripts/dashboard/components/paged-search/paged-search-view.js
2024-04-06 05:25:13 +00:00

258 lines
7.1 KiB
JavaScript

const CoreView = require('backbone/core-view');
const Utils = require('builder/helpers/utils');
const PaginationModel = require('builder/components/pagination/pagination-model');
const template = require('./paged-search.tpl');
const pagedSearchDialogWrapperTemplate = require('./paged-search-dialog-wrapper.tpl');
const errorTemplate = require('dashboard/views/data-library/content/error-template.tpl');
const loadingView = require('builder/components/loading/render-loading');
const noResultsView = require('builder/components/no-results/render-no-results.js');
const TabPane = require('dashboard/components/tabpane/tabpane');
const ViewFactory = require('builder/components/view-factory');
const PaginationView = require('builder/components/pagination/pagination-view');
const checkAndBuildOpts = require('builder/helpers/required-opts');
const REQUIRED_OPTS = [
'collection',
'pagedSearchModel'
];
/**
* View to render a searchable/pageable collection.
* Also allows to filter/search list.
* Set {isUsedInDialog: true} in view opts if intended to be used in a dialog, to have proper classes to position views
* properly.
*
* - collection is a collection which has a PagedSearchModel.
*/
module.exports = CoreView.extend({
events: {
'click .js-search-link': '_onSearchClick',
'click .js-clean-search': '_onCleanSearchClick',
'keydown .js-search-input': '_onKeyDown',
'submit .js-search-form': 'killEvent'
},
initialize: function (options) {
checkAndBuildOpts(options, REQUIRED_OPTS, this);
this.options.noResults = this.options.noResults || {};
const params = this._pagedSearchModel;
this.paginationModel = new PaginationModel({
current_page: params.get('page'),
total_count: this._collection.totalCount() || 0,
per_page: params.get('per_page')
});
this._initBinds();
this._pagedSearchModel.fetch(this._collection);
},
_initBinds: function () {
this.listenTo(this._collection, 'fetching', function () {
this._toggleCleanSearchBtn();
this._activatePane('loading');
});
this.listenTo(this._collection, 'error', function (e) {
// Old requests can be stopped, so aborted requests are not
// considered as an error
if (!e || (e && e.statusText !== 'abort')) {
this._activatePane('error');
}
this._toggleCleanSearchBtn();
});
this.listenTo(this._collection, 'sync', function (collection) {
this.paginationModel.set({
total_count: this._collection.totalCount(),
current_page: this._pagedSearchModel.get('page')
});
this._activatePane(this._collection.totalCount() > 0 ? 'list' : 'no_results');
this._toggleCleanSearchBtn();
});
this.listenTo(this.paginationModel, 'change:current_page', function (model, newPage) {
this._pagedSearchModel.set('page', newPage);
this._pagedSearchModel.fetch(this._collection);
});
},
render: function () {
this.clearSubViews();
this._renderContent(
template({
thinFilters: this.options.thinFilters || false,
q: this._pagedSearchModel.get('q')
})
);
this._initViews();
this._$cleanSearchBtn().hide();
this._renderExtraFilters();
return this;
},
_renderExtraFilters: function () {
if (this.options.filtersExtrasView) {
this.$('.js-filters').append(this.options.filtersExtrasView.render().el);
}
},
_renderContent: function (html) {
if (this.options.isUsedInDialog) {
html = pagedSearchDialogWrapperTemplate({
htmlToWrap: html
});
}
this.$el.html(html);
// Needs to be called after $el html changed:
if (this.options.isUsedInDialog) {
this.$el.addClass('Dialog-expandedSubContent');
this._$tabPane().addClass('Dialog-bodyInnerExpandedWithSubFooter');
}
},
_toggleCleanSearchBtn: function () {
this._$cleanSearchBtn().toggle(!!this._pagedSearchModel.get('q'));
},
_initViews: function () {
this._panes = new TabPane({
el: this._$tabPane()
});
this.addView(this._panes);
this._panes.addTab('list',
ViewFactory.createListView([
() => this._createListView(),
() => new PaginationView({
className: 'CDB-Text CDB-Size-medium Pagination Pagination--shareList',
model: this.paginationModel
})
])
);
this._panes.addTab('error',
ViewFactory.createByHTML(errorTemplate({
msg: ''
})).render()
);
this._panes.addTab('no_results',
ViewFactory.createByHTML(noResultsView({
icon: this.options.noResults.icon || 'CDB-IconFont-defaultUser',
title: this.options.noResults.title || 'Oh! No results',
msg: this.options.noResults.msg || 'Unfortunately we could not find anything with these parameters'
})).render()
);
this._panes.addTab('loading',
ViewFactory.createByHTML(loadingView({
title: 'Searching'
})).render()
);
if (this._pagedSearchModel.get('q')) {
this._focusSearchInput();
}
this._activatePane(this._chooseActivePaneName(this._collection.totalCount()));
},
_createListView: function () {
var view = this.options.createListView();
if (view instanceof CoreView) {
return view;
} else {
console.error('createListView function must return a view');
// fallback for view to not fail miserably
return new CoreView();
}
},
_activatePane: function (name) {
// Only change active pane if the panes is actually initialized
if (this._panes && this._panes.size() > 0) {
// explicit render required, since tabpane doesn't do it
this._panes.active(name).render();
}
},
_chooseActivePaneName: function (totalCount) {
if (totalCount === 0) {
return 'no_results';
} else if (totalCount > 0) {
return 'list';
} else {
return 'loading';
}
},
_focusSearchInput: function () {
// also selects the current search str on the focus
this._$searchInput().focus().val(this._$searchInput().val());
},
_onSearchClick: function (ev) {
this.killEvent(ev);
this._$searchInput().focus();
},
_onCleanSearchClick: function (ev) {
this.killEvent(ev);
this._cleanSearch();
},
_onKeyDown: function (ev) {
var enterPressed = (ev.key === 'Enter');
var escapePressed = (ev.key === 'Escape');
if (enterPressed) {
this.killEvent(ev);
this._submitSearch();
} else if (escapePressed) {
this.killEvent(ev);
if (this._pagedSearchModel.get('q')) {
this._cleanSearch();
}
}
},
_submitSearch: function (e) {
this._makeNewSearch(Utils.stripHTML(this._$searchInput().val().trim()));
},
_cleanSearch: function () {
this._$searchInput().val('');
this._makeNewSearch();
},
_makeNewSearch: function (query) {
this._pagedSearchModel.set({
q: query,
page: 1
});
this._pagedSearchModel.fetch(this._collection);
},
_$searchInput: function () {
return this.$('.js-search-input');
},
_$cleanSearchBtn: function () {
return this.$('.js-clean-search');
},
_$tabPane: function () {
return this.$('.js-tab-pane');
}
});