Initial commit

This commit is contained in:
zhongjin
2020-06-15 10:58:47 +08:00
commit 4f1dfe7564
8590 changed files with 1516878 additions and 0 deletions
@@ -0,0 +1,151 @@
var _ = require('underscore');
var WidgetModel = require('../widget-model');
var LockedCategoriesCollection = require('./locked-categories-collection');
/**
* Model for a category widget
*/
module.exports = WidgetModel.extend({
defaults: _.extend({
type: 'category',
search: false,
locked: false
},
WidgetModel.prototype.defaults
),
defaultState: _.extend({
acceptedCategories: [],
locked: false,
autoStyle: false
},
WidgetModel.prototype.defaultState
),
initialize: function () {
WidgetModel.prototype.initialize.apply(this, arguments);
this.lockedCategories = new LockedCategoriesCollection();
this.listenTo(this.dataviewModel, 'change:allCategoryNames', this._onDataviewAllCategoryNamesChange);
this.on('change:locked', this._onLockedChange, this);
this.on('change:collapsed', this._onCollapsedChange, this);
if (this.isAutoStyleEnabled()) {
this.on('change:style', this._updateAutoStyle, this);
}
this.dataviewModel.filter.on('change', function () {
this.set('acceptedCategories', this._acceptedCategories().pluck('name'));
}, this);
this.dataviewModel.once('change:allCategoryNames', function () {
if (this.get('autoStyle')) {
this.autoStyle();
}
}, this);
},
setupSearch: function () {
this.dataviewModel.setupSearch();
this.lockedCategories.addItems(this._acceptedCategories().toJSON());
this.toggleSearch();
},
toggleSearch: function () {
this.set('search', !this.get('search'));
},
enableSearch: function () {
this.set('search', true);
},
disableSearch: function () {
this.set('search', false);
},
isSearchEnabled: function () {
return this.get('search');
},
cleanSearch: function () {
this.dataviewModel.cleanSearch();
this.lockedCategories.reset([]);
},
autoStyle: function () {
// NOTE: maybe not pre-assing colors to categories?
this.autoStyler.colors.updateData(this.dataviewModel.get('allCategoryNames'));
WidgetModel.prototype.autoStyle.call(this);
},
isLocked: function () {
return this.get('locked');
},
canBeLocked: function () {
return this.isLocked() || this._acceptedCategories().size() > 0;
},
canApplyLocked: function () {
if (this._acceptedCategories().size() !== this.lockedCategories.size()) {
return true;
}
return this._acceptedCategories().any(function (m) {
return !this.lockedCategories.isItemLocked(m.get('name'));
}, this);
},
applyLocked: function () {
var currentLocked = this.lockedCategories.getItemsName();
if (!currentLocked.length) {
this.unlockCategories();
return false;
}
this.set('locked', true);
var f = this.dataviewModel.filter;
f.cleanFilter(false);
f.accept(currentLocked);
f.applyFilter();
this.cleanSearch();
},
lockCategories: function () {
this.set('locked', true);
this.dataviewModel.fetch();
},
unlockCategories: function () {
this.set('locked', false);
this.dataviewModel.filter.acceptAll();
},
_onDataviewAllCategoryNamesChange: function (m, names) {
if (this.isAutoStyleEnabled()) {
if (!this.isAutoStyle()) {
this.autoStyler.colors.updateData(names);
}
}
},
_onLockedChange: function (m, isLocked) {
if (isLocked) {
this.dataviewModel.enableFilter();
} else {
this.dataviewModel.disableFilter();
}
},
_acceptedCategories: function () {
return this.dataviewModel.filter.acceptedCategories;
},
_onCollapsedChange: function (m, isCollapsed) {
this.dataviewModel.set('enabled', !isCollapsed);
}
});
@@ -0,0 +1,5 @@
<div class="CDB-Widget-header js-header"></div>
<div class="CDB-Widget-content CDB-Widget-content--noSidesMargin">
<div class="CDB-Widget-listWrapper js-content"></div>
</div>
<div class="CDB-Widget-footer js-footer"></div>
@@ -0,0 +1,125 @@
var Backbone = require('backbone');
var CoreView = require('backbone/core-view');
var SearchTitleView = require('./title/search-title-view');
var CategoryOptionsView = require('./options/options-view');
var CategoryItemsView = require('./list/items-view');
var CategoryStatsView = require('./stats/stats-view');
var CategoryPaginatorView = require('./paginator/paginator-view');
var SearchCategoryItemsView = require('./list/search-items-view');
var SearchCategoryPaginatorView = require('./paginator/search-paginator-view');
var template = require('./content-template.tpl');
var STARTING_PAGE = 1;
/**
* Content view for category widget
*/
module.exports = CoreView.extend({
className: 'CDB-Widget-body',
_ITEMS_PER_PAGE: 6,
initialize: function () {
this._dataviewModel = this.model.dataviewModel;
this._layerModel = this.model.layerModel;
this._paginatorModel = new Backbone.Model({
page: STARTING_PAGE
});
this._initBinds();
},
render: function () {
this.clearSubViews();
this.$el.toggleClass('is-collapsed', !!this.model.get('collapsed'));
this.$el.html(template());
this._initViews();
return this;
},
_initBinds: function () {
if (this.model.get('hasInitialState') === true) {
this._onInitialState();
} else {
this.model.bind('change:hasInitialState', this._onInitialState, this);
}
this.model.bind('change:collapsed', function (mdl, isCollapsed) {
this.$el.toggleClass('is-collapsed', !!isCollapsed);
}, this);
},
_onInitialState: function () {
this.render();
if (this.model.get('autoStyle') === true) {
this.model.autoStyle();
}
},
_initViews: function () {
var searchTitle = new SearchTitleView({
widgetModel: this.model,
dataviewModel: this._dataviewModel,
layerModel: this._layerModel
});
this.$('.js-header').append(searchTitle.render().el);
this.addView(searchTitle);
var stats = new CategoryStatsView({
widgetModel: this.model,
dataviewModel: this._dataviewModel
});
this.$('.js-header').append(stats.render().el);
this.addView(stats);
var options = new CategoryOptionsView({
widgetModel: this.model,
dataviewModel: this._dataviewModel
});
this.$('.js-content').html(options.render().el);
this.addView(options);
var dataList = new CategoryItemsView({
widgetModel: this.model,
dataviewModel: this._dataviewModel,
itemsPerPage: this._ITEMS_PER_PAGE,
paginatorModel: this._paginatorModel
});
this.$('.js-content').append(dataList.render().el);
this.addView(dataList);
var pagination = new CategoryPaginatorView({
widgetModel: this.model,
dataviewModel: this._dataviewModel,
itemsPerPage: this._ITEMS_PER_PAGE,
paginatorModel: this._paginatorModel
});
this.$('.js-footer').append(pagination.render().el);
this.addView(pagination);
var searchList = new SearchCategoryItemsView({
widgetModel: this.model,
dataviewModel: this._dataviewModel,
itemsPerPage: this._ITEMS_PER_PAGE,
paginator: true,
paginatorModel: this._paginatorModel
});
this.$('.js-content').append(searchList.render().el);
this.addView(searchList);
var searchPagination = new SearchCategoryPaginatorView({
widgetModel: this.model,
dataviewModel: this._dataviewModel,
itemsPerPage: this._ITEMS_PER_PAGE,
paginator: true,
paginatorModel: this._paginatorModel
});
this.$('.js-footer').append(searchPagination.render().el);
this.addView(searchPagination);
}
});
@@ -0,0 +1,9 @@
<button type="button" class="CDB-Widget-listItemInner CDB-Widget-listButton js-button <%- isDisabled ? 'is-disabled' : '' %>">
<div class="CDB-Widget-contentSpaced">
<p class="CDB-Text is-semibold u-upperCase CDB-Size-medium u-ellipsis u-rSpace--xl" title="<%- name %>"><%- name %></p>
<p class="CDB-Text CDB-Size-small u-secondaryTextColor" title="<%- value %>"><%- prefix %><%- formattedValue %><%- suffix %></p>
</div>
<div class="CDB-Widget-progressBar">
<div class="CDB-Widget-progressState <%- isAggregated ? 'CDB-Widget-progressState--pattern' : '' %> <%- isAccepted ? 'is-accepted' : '' %>" style="width: <%- percentage %>%; background-color: <%- color %>"></div>
</div>
</button>
@@ -0,0 +1,10 @@
<div class="CDB-Widget-listItemInner <%- isDisabled ? 'is-disabled' : '' %>">
<div class="CDB-Widget-contentSpaced">
<p class="CDB-Text is-semibold u-upperCase CDB-Size-medium u-ellipsis u-rSpace--xl" title="<%- name %>"><%- name %></p>
<p class="CDB-Text CDB-Size-small u-secondaryTextColor" title="<%- value %>"><%- prefix %><%- formattedValue %><%- suffix %></p>
</div>
<div class="CDB-Widget-progressBar">
<div class="CDB-Widget-progressState CDB-Widget-progressState--pattern <%- isAggregated ? 'CDB-Widget-progressState--inactive' : '' %>"
style="width: <%- percentage %>%; background-color: <%- color %>"></div>
</div>
</div>
@@ -0,0 +1,61 @@
var CoreView = require('backbone/core-view');
var formatter = require('../../../../formatter');
var _ = require('underscore');
var clickableTemplate = require('./item-clickable-template.tpl');
var unclickableTemplate = require('./item-unclickable-template.tpl');
/**
* Category list item view
*/
module.exports = CoreView.extend({
tagName: 'li',
className: 'CDB-Widget-listItem',
events: {
'click .js-button': '_onItemClick'
},
initialize: function (options) {
this.widgetModel = this.options.widgetModel;
this.dataviewModel = this.options.dataviewModel;
this._initBinds();
},
render: function () {
var name = this.model.get('name');
var value = this.model.get('value');
var template = this.model.get('agg') || this.widgetModel.isLocked()
? unclickableTemplate
: clickableTemplate;
var acceptedCategories = this.dataviewModel.filter.getAcceptedCategoryNames();
var isAccepted = _.contains(acceptedCategories, name);
this.$el.html(
template({
isAggregated: this.model.get('agg'),
name: name,
value: value,
formattedValue: formatter.formatNumber(value),
percentage: ((value / this.dataviewModel.get('max')) * 100),
color: this.widgetModel.getColor(name),
isDisabled: !this.model.get('selected') ? 'is-disabled' : '',
isAccepted: isAccepted,
prefix: this.widgetModel.get('prefix'),
suffix: this.widgetModel.get('suffix')
})
);
return this;
},
_initBinds: function () {
this.model.bind('change', this.render, this);
this.widgetModel.bind('change:search change:prefix change:suffix change:autoStyle', this.render, this);
this.add_related_model(this.widgetModel);
},
_onItemClick: function () {
this.trigger('itemClicked', this.model, this);
}
});
@@ -0,0 +1,12 @@
<button type="button" class="CDB-Widget-listItemInner CDB-Widget-listItemInner--fullSpace CDB-Widget-listButton js-button <%- isDisabled ? 'is-disabled' : '' %>">
<span class="CDB-Widget-checkbox <%- isDisabled ? '' : 'is-checked' %>"></span>
<div class="u-lSpace u-ellipsis">
<div class="CDB-Widget-contentSpaced">
<p class="CDB-Text is-semibold u-upperCase CDB-Size-medium u-ellipsis u-rSpace--xl" title="<%- name %>"><%- name %></p>
<p class="CDB-Text CDB-Size-small u-secondaryTextColor" title="<%- value %>"><%- prefix %><%- formattedValue %><%- suffix %></p>
</div>
<div class="CDB-Widget-progressBar">
<div class="CDB-Widget-progressState is-accepted" style="width: <%- percentage %>%"></div>
</div>
</div>
</button>
@@ -0,0 +1,54 @@
var CoreView = require('backbone/core-view');
var formatter = require('../../../../formatter');
var template = require('./search-item-clickable-template.tpl');
/**
* Category search list view
*/
module.exports = CoreView.extend({
tagName: 'li',
className: 'CDB-Widget-listItem',
events: {
'click .js-button': '_onItemClick'
},
initialize: function (options) {
// This data model comes from the original data in order to get
// the max value and set properly the progress bar and add the
// necessary suffix and prefix for the item.
this.dataviewModel = this.options.dataviewModel;
this.widgetModel = this.options.widgetModel;
this._initBinds();
},
render: function () {
var value = this.model.get('value');
this.$el.html(
template({
name: this.model.get('name'),
value: value,
formattedValue: formatter.formatNumber(value),
percentage: ((value / this.dataviewModel.get('max')) * 100),
isDisabled: !this.model.get('selected'),
prefix: this.widgetModel.get('prefix'),
suffix: this.widgetModel.get('suffix')
})
);
return this;
},
_initBinds: function () {
this.model.bind('change:selected', this.render, this);
this.widgetModel.bind('change:prefix change:suffix', this.render, this);
this.add_related_model(this.widgetModel);
},
_onItemClick: function () {
this.model.set('selected', !this.model.get('selected'));
}
});
@@ -0,0 +1,4 @@
<li class="CDB-Widget-listItem CDB-Widget-listItem--fake"></li>
<li class="CDB-Widget-listItem CDB-Widget-listItem--fake"></li>
<li class="CDB-Widget-listItem CDB-Widget-listItem--fake"></li>
<li class="CDB-Widget-listItem CDB-Widget-listItem--fake"></li>
@@ -0,0 +1,175 @@
var _ = require('underscore');
var CoreView = require('backbone/core-view');
var checkAndBuildOpts = require('builder/helpers/required-opts');
var CategoryItemView = require('./item/item-view');
var placeholder = require('./items-placeholder-template.tpl');
var REQUIRED_OPTS = [
'dataviewModel',
'widgetModel',
'paginatorModel'
];
/**
* Category list view
*/
module.exports = CoreView.extend({
options: {
paginator: false,
itemsPerPage: 6
},
className: 'CDB-Widget-list js-list',
tagName: 'ul',
initialize: function (options) {
checkAndBuildOpts(options, REQUIRED_OPTS, this);
if (!this._dataviewModel.get('sync_on_bbox_change')) {
this.$el.addClass('CDB-Widget-list--nodynamic');
}
this._initBinds();
},
render: function () {
this.clearSubViews();
this.$el.empty();
var data = this._dataviewModel.getData();
var isDataEmpty = _.isEmpty(data) || _.size(data) === 0;
if (isDataEmpty) {
this._renderPlaceholder();
} else {
this._renderList();
}
return this;
},
_initBinds: function () {
this.listenTo(this._paginatorModel, 'change:page', this.render);
this.listenTo(this._widgetModel, 'change:search', this.toggle);
this.listenTo(this._widgetModel, 'change:style change:autoStyle', this.render);
this.listenTo(this._dataviewModel, 'change:data', this.render);
this.listenTo(this._dataviewModel, 'change:sync_on_bbox_change', this.blockFiltering);
},
_renderPlaceholder: function () {
// Change view classes
this.$el.addClass('CDB-Widget-list--withBorders CDB-Widget-list--fake');
this.$el.append(placeholder());
},
_renderList: function () {
this._createList();
this.$el.removeClass('CDB-Widget-list--withBorders CDB-Widget-list--fake CDB-Widget-list--noresults');
this._renderItems();
},
_renderItems: function () {
var currentPage = this._paginatorModel.get('page');
var pageIndex = currentPage - 1; // Transform from 1-based index to zero-based index
var items = this.pagesData[pageIndex];
_.each(items, function (model) {
this._addItem(model);
}.bind(this));
},
_createList: function () {
this.pagesData = {};
var data = this._getData();
var currentIndex = 0;
data.each(function (model, index) {
if (index % this.options.itemsPerPage === 0) {
if (index !== 0) {
currentIndex += 1;
}
this.pagesData[currentIndex] = [];
}
this.pagesData[currentIndex].push(model);
}, this);
},
_getData: function () {
return this._dataviewModel.getData();
},
_addItem: function (model) {
var view = new CategoryItemView({
model: model,
widgetModel: this._widgetModel,
dataviewModel: this._dataviewModel
});
view.bind('itemClicked', this._setFilters, this);
this.addView(view);
this.$el.append(view.render().el);
},
_setFilters: function (mdl) {
var isSelected = mdl.get('selected');
var filter = this._dataviewModel.filter;
var clickedName = mdl.get('name');
if (isSelected) {
// If there isn't any filter applied,
// clicking over one will turn rest into as "unselected"
if (filter.rejectedCategories.size() === 0 &&
filter.acceptedCategories.size() === 0
) {
var data = this._dataviewModel.getData();
// Make elements "unselected"
data.each(function (m) {
var name = m.get('name');
if (name !== clickedName) {
m.set('selected', false);
}
});
filter.accept(mdl.get('name'));
} else {
mdl.set('selected', false);
filter.reject(clickedName);
}
} else {
mdl.set('selected', true);
filter.accept(clickedName);
}
this._widgetModel.set('acceptedCategories', this._widgetModel._acceptedCategories().pluck('name'));
},
blockFiltering: function (e) {
if (e.changed['sync_on_bbox_change']) { // Is dynamic
this.$el.removeClass('CDB-Widget-list--nodynamic');
} else { // It is not
this.$el.addClass('CDB-Widget-list--nodynamic');
this._dataviewModel.filter.acceptAll();
}
},
removeSelections: function () {
this._dataviewModel.filter.acceptAll();
},
toggle: function () {
this[!this._widgetModel.isSearchEnabled() ? 'show' : 'hide']();
},
show: function () {
this.$el.removeClass('is-hidden');
},
hide: function () {
this.$el.addClass('is-hidden');
}
});
@@ -0,0 +1,5 @@
<li class="CDB-Widget-listItem">
<h4 class="CDB-Text CDB-Size-large">No results</h4>
<p class="CDB-Text CDB-Size-medium u-tSpace-xl u-altTextColor">Your search "<%- q %>" didn't match<br/>with any value.</p>
<p class="CDB-Text CDB-Size-medium u-tSpace-xl u-altTextColor">Try searching again.</p>
</li>
@@ -0,0 +1,63 @@
var CategoryItemsView = require('./items-view');
var WidgetSearchCategoryItemView = require('./item/search-item-view');
var placeholder = require('./search-items-no-results-template.tpl');
/**
* Category list view
*/
module.exports = CategoryItemsView.extend({
className: 'CDB-Widget-list is-hidden js-list',
render: function () {
this.clearSubViews();
this.$el.empty();
var data = this._searchResultsCollection;
var isDataEmpty = data.isEmpty() || data.size() === 0;
if (isDataEmpty) {
this._renderPlaceholder();
} else {
this._renderList();
}
return this;
},
_initBinds: function () {
CategoryItemsView.prototype._initBinds.apply(this, arguments);
this._searchResultsCollection = this._dataviewModel.getSearchResult();
this.listenTo(this._searchResultsCollection, 'change:selected', this._onSelectedItemChange);
this.listenTo(this._searchResultsCollection, 'reset', this.render);
},
_getData: function () {
return this._searchResultsCollection;
},
_renderPlaceholder: function () {
this.$el.addClass('CDB-Widget-list--noresults');
this.$el.html(
placeholder({
q: this._dataviewModel.getSearchQuery()
})
);
},
_addItem: function (model) {
var view = new WidgetSearchCategoryItemView({
model: model,
widgetModel: this._widgetModel,
dataviewModel: this._dataviewModel
});
this.addView(view);
this.$el.append(view.render().el);
},
toggle: function () {
this[this._widgetModel.isSearchEnabled() ? 'show' : 'hide']();
},
_onSelectedItemChange: function (model, isSelected) {
this._widgetModel.lockedCategories[isSelected ? 'addItem' : 'removeItem'](model);
}
});
@@ -0,0 +1,49 @@
var _ = require('underscore');
var Backbone = require('backbone');
/**
* Locked categories collection
*
*/
module.exports = Backbone.Collection.extend({
addItem: function (mdl) {
if (!this.isItemLocked(mdl.get('name'))) {
this.add(mdl);
}
},
addItems: function (mdls) {
_.each(mdls, function (m) {
if (!this.isItemLocked(m.name)) {
this.add(m);
}
}, this);
},
resetItems: function (mdls) {
this.reset(mdls);
},
removeItem: function (mdl) {
var lockedItem = this.isItemLocked(mdl.get('name'));
if (lockedItem) {
this.remove(lockedItem);
}
},
removeItems: function () {
this.reset([]);
},
isItemLocked: function (name) {
return this.find(function (d) {
return d.get('name') === name;
});
},
getItemsName: function () {
return this.pluck('name');
}
});
@@ -0,0 +1,23 @@
<% if (isSearchEnabled) { %>
<p class="CDB-Text is-semibold CDB-Size-small u-upperCase js-lockCategories"><%- totalLocked %> selected</p>
<% } else { %>
<p class="CDB-Text is-semibold CDB-Size-small u-upperCase js-textInfo">
<% if (isLocked) { %>
<%- totalCats %> blocked <button class="CDB-Text CDB-Size-small u-upperCase u-actionTextColor CDB-Widget-link u-lSpace js-unlock">unlock</button>
<% } else { %>
<% if (noneSelected) { %>
None selected
<% } else { %>
<%- allSelected ? "All selected" : acceptedCats + " selected" %>
<% if (canBeLocked) { %>
<button class="CDB-Text CDB-Size-small u-upperCase u-actionTextColor CDB-Widget-link u-lSpace js-lock">lock</button>
<% }%>
<% }%>
<% } %>
</p>
<% if (canSelectAll) { %>
<div class="CDB-Widget-filterButtons">
<button class="CDB-Text CDB-Size-small u-upperCase u-actionTextColor CDB-Widget-link CDB-Widget-filterButton js-all">all</button>
</div>
<% } %>
<% } %>
@@ -0,0 +1,75 @@
var CoreView = require('backbone/core-view');
var template = require('./options-template.tpl');
/**
* Category filter view
*
*/
module.exports = CoreView.extend({
className: 'CDB-Widget-filter CDB-Widget-contentSpaced CDB-Widget-contentSpaced--sideMargins',
events: {
'click .js-all': '_onSelectAll',
'click .js-lock': '_lockCategories',
'click .js-unlock': '_unlockCategories'
},
initialize: function () {
this.dataviewModel = this.options.dataviewModel;
this.widgetModel = this.options.widgetModel;
this._initBinds();
},
render: function () {
var acceptedCats = this.dataviewModel.filter.acceptedCategories.size();
var rejectedCats = this.dataviewModel.filter.rejectedCategories.size();
var areAllRejected = this.dataviewModel.filter.areAllRejected();
var totalCats = this.dataviewModel.getData().size();
var isLocked = this.widgetModel.isLocked();
this.$el.html(
template({
isSearchEnabled: this.widgetModel.isSearchEnabled(),
isSearchApplied: this.dataviewModel.isSearchApplied(),
isLocked: isLocked,
canBeLocked: this.widgetModel.canBeLocked(),
allSelected: (rejectedCats === 0 && acceptedCats === 0 && !areAllRejected),
canSelectAll: !isLocked && (rejectedCats > 0 || acceptedCats > 0 || areAllRejected),
noneSelected: areAllRejected || (!totalCats && !acceptedCats),
acceptedCats: acceptedCats,
totalLocked: this.widgetModel.lockedCategories.size(),
totalCats: totalCats
})
);
return this;
},
_initBinds: function () {
this.widgetModel.bind('change:search change:locked', this.render, this);
this.widgetModel.lockedCategories.bind('change add remove', this.render, this);
this.add_related_model(this.widgetModel);
this.add_related_model(this.widgetModel.lockedCategories);
this.dataviewModel.bind('change:data', this.render, this);
this.add_related_model(this.dataviewModel);
var f = this.dataviewModel.filter;
f.acceptedCategories.bind('add remove reset', this.render, this);
f.rejectedCategories.bind('add remove reset', this.render, this);
this.add_related_model(f.rejectedCategories);
this.add_related_model(f.acceptedCategories);
},
_lockCategories: function () {
this.widgetModel.lockCategories();
},
_unlockCategories: function () {
this.widgetModel.unlockCategories();
},
_onSelectAll: function () {
this.dataviewModel.filter.acceptAll();
}
});
@@ -0,0 +1,21 @@
<section class="CDB-Widget-pagination">
<button class="CDB-Widget-paginationButton u-rSpace--m js-prev">
<svg width="6px" height="10px" viewBox="0 0 6 10" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g fill="#1785FB">
<path d="M5.09530663,4.3528203 C5.12374481,4.37148693 5.15072296,4.39333837 5.17574487,4.41836028 C5.20217765,4.44479306 5.22503325,4.47380933 5.24426546,4.50470442 C5.4081023,4.70181793 5.39325947,4.99960853 5.21412378,5.17874423 C5.15044967,5.24241833 5.07410755,5.28556158 4.99327465,5.30793729 L0.85159735,9.44961458 C0.653891927,9.64732 0.339500325,9.64512912 0.144238179,9.44986697 C-0.0453133946,9.2603154 -0.0509109683,8.93790934 0.144490568,8.7425078 L4.10763557,4.7793628 L0.182869475,0.85459671 C-0.012532062,0.659195174 -0.00693448836,0.336789113 0.182617085,0.14723754 C0.377879231,-0.0480246061 0.692270834,-0.050215493 0.889976256,0.147489929 L5.09530663,4.3528203 Z" transform="translate(2.679181, 4.798552) scale(-1, 1) translate(-2.679181, -4.798552) "></path>
</g>
</svg>
</button>
<p class="CDB-Text CDB-Size-small is-semibold u-upperCase">
<span class="u-mainTextColor"><%- currentPage %></span>
<span class="u-altTextColor u-lSpace u-rSpace">of</span>
<span class="u-mainTextColor"><%- pages %></span>
</p>
<button class="CDB-Widget-paginationButton u-lSpace--m js-next">
<svg width="6px" height="10px" viewBox="0 0 6 10" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g fill="#1785FB">
<path d="M5.28600662,4.78918286 C5.3144448,4.80784949 5.34142295,4.82970094 5.36644486,4.85472284 C5.39287764,4.88115562 5.41573324,4.91017189 5.43496545,4.94106698 C5.59880229,5.13818049 5.58395946,5.4359711 5.40482376,5.61510679 C5.34114966,5.6787809 5.26480754,5.72192414 5.18397463,5.74429985 L1.04229734,9.88597714 C0.844591917,10.0836826 0.530200314,10.0814917 0.334938168,9.88622953 C0.145386595,9.69667796 0.139789021,9.3742719 0.335190558,9.17887036 L4.29833556,5.21572536 L0.373569464,1.29095927 C0.178167927,1.09555773 0.183765501,0.773151675 0.373317075,0.583600101 C0.56857922,0.388337955 0.882970823,0.386147068 1.08067625,0.58385249 L5.28600662,4.78918286 Z"></path>
</g>
</svg>
</button>
</section>
@@ -0,0 +1,11 @@
<div class="CDB-Widget-contentFlex">
<button class="u-rSpace--m CDB-Text is-semibold u-upperCase CDB-Size-small js-searchToggle">
<div class="CDB-Shape u-iBlock">
<span class="CDB-Shape-magnify is-small is-blue"></span>
</div>
<span class="u-iBlock u-actionTextColor">
search in <%- categoriesCount %> categor<%- categoriesCount === 1 ? 'y' : 'ies' %>
</span>
</button>
</div>
@@ -0,0 +1,144 @@
var $ = require('jquery');
var CoreView = require('backbone/core-view');
var checkAndBuildOpts = require('builder/helpers/required-opts');
var defaultTemplate = require('./paginator-template.tpl');
var paginationTemplate = require('./pagination-template.tpl');
var MIN_CATEGORIES = 5;
var STARTING_PAGE = 1;
var PAGINATION_STEP = 1;
var REQUIRED_OPTS = [
'dataviewModel',
'widgetModel',
'paginatorModel'
];
/**
* Display paginator for category widget
*
*/
module.exports = CoreView.extend({
options: {
itemsPerPage: 6,
template: defaultTemplate,
paginator: false
},
className: 'CDB-Widget-nav CDB-Widget-contentSpaced',
events: {
'click .js-searchToggle': '_onSearchClicked',
'click .js-next': '_onNextPage',
'click .js-prev': '_onPrevPage'
},
initialize: function (options) {
checkAndBuildOpts(options, REQUIRED_OPTS, this);
this._initBinds();
},
render: function () {
this.clearSubViews();
this.$el.empty();
this._initViews();
return this;
},
_initBinds: function () {
this.listenTo(this._paginatorModel, 'change:page', this.render);
this.listenTo(this._dataviewModel, 'change:categoriesCount', this.render);
this.listenTo(this._dataviewModel, 'change:data change:searchData', this._onDataChanged);
this.listenTo(this._widgetModel, 'change:search', this.toggle);
},
_initViews: function () {
var categoriesCount = this._dataviewModel.getCount();
if (categoriesCount > MIN_CATEGORIES) {
var template = this.options.template;
this.$el.html(template({
categoriesCount: categoriesCount
}));
if (this.options.paginator) {
this.$el.append(paginationTemplate({
currentPage: this._paginatorModel.get('page'),
pages: this._totalPages()
}));
}
} else {
this._paginatorModel.set('page', STARTING_PAGE);
}
},
// If current page doesn't exist due to a data change, we should reset it
_setPage: function () {
var pages = this._totalPages();
if (this._paginatorModel.get('page') > pages || this._paginatorModel.get('page') < STARTING_PAGE) {
this._paginatorModel.set({ page: STARTING_PAGE }, { silent: true });
}
},
_onSearchClicked: function () {
this._widgetModel.setupSearch();
},
_onDataChanged: function () {
this._setPage();
this.render();
},
_onPrevPage: function () {
this._changePage(-PAGINATION_STEP);
},
_onNextPage: function () {
this._changePage(PAGINATION_STEP);
},
_changePage: function (step) {
var totalPages = this._totalPages();
var currentPage = this._paginatorModel.get('page');
var nextPage = currentPage + step;
if (nextPage > totalPages) {
nextPage = STARTING_PAGE;
}
if (nextPage < STARTING_PAGE) {
nextPage = totalPages;
}
this._paginatorModel.set('page', nextPage);
},
_totalPages: function () {
return Math.ceil(this._dataviewModel.getSize() / this.options.itemsPerPage);
},
toggle: function () {
this[ this._widgetModel.isSearchEnabled() ? 'hide' : 'show' ]();
},
hide: function () {
this.$el.addClass('is-hidden');
},
show: function () {
this.$el.removeClass('is-hidden');
},
clean: function () {
$(window).unbind('resize.' + this.cid);
CoreView.prototype.clean.call(this);
}
});
@@ -0,0 +1,3 @@
<div class="CDB-Widget-contentFlex u-rSpace--xl">
<button class="CDB-Text is-semibold u-upperCase CDB-Size-small u-rSpace--m js-searchToggle u-actionTextColor">cancel</button>
</div>
@@ -0,0 +1,37 @@
var PaginatorView = require('./paginator-view');
var searchTemplate = require('./search-paginator-template.tpl');
var paginationTemplate = require('./pagination-template.tpl');
module.exports = PaginatorView.extend({
className: 'CDB-Widget-nav is-hidden CDB-Widget-contentSpaced',
render: function () {
this.clearSubViews();
this.$el.empty();
var pages = this._totalPages();
this.$el.html(searchTemplate());
if (pages > 1) {
this.$el.append(paginationTemplate({
currentPage: this._paginatorModel.get('page'),
pages: pages
}));
}
return this;
},
_totalPages: function () {
return Math.ceil(this._dataviewModel.getSearchCount() / this.options.itemsPerPage);
},
toggle: function () {
this[ !this._widgetModel.isSearchEnabled() ? 'hide' : 'show' ]();
},
_onSearchClicked: function () {
this._widgetModel.cleanSearch();
this._widgetModel.toggleSearch();
}
});
@@ -0,0 +1 @@
<%- value %>
@@ -0,0 +1,11 @@
<% if (isSearchEnabled) { %>
<% if (isSearchApplied) { %>
<dt class="CDB-Widget-infoCount"><%- resultsCount %></dt><dd class="CDB-Widget-infoDescription">found</dd>
<% } else { %>
&nbsp;
<% } %>
</dt>
<% } else { %>
<dt class="CDB-Widget-infoCount"><%- nullsPer %>%</dt><dd class="CDB-Widget-infoDescription"> null rows</dd>
<dt class="CDB-Widget-infoCount"><span class="js-cats"><%- catsPer %></span>%</dt><dd class="CDB-Widget-infoDescription">of total</dd>
<% } %>
@@ -0,0 +1,103 @@
var _ = require('underscore');
var CoreView = require('backbone/core-view');
var formatter = require('../../../formatter');
var template = require('./stats-template.tpl');
var animationTemplate = require('./cats-template.tpl');
var AnimateValues = require('../../animate-values');
/**
* Category stats info view
*
*/
module.exports = CoreView.extend({
className: 'CDB-Widget-info CDB-Text CDB-Size-small u-secondaryTextColor u-upperCase u-tSpace',
tagName: 'dl',
initialize: function () {
this.widgetModel = this.options.widgetModel;
this.dataviewModel = this.options.dataviewModel;
this._initBinds();
},
render: function () {
this.$el.html(
template({
isSearchEnabled: this.widgetModel.isSearchEnabled(),
isSearchApplied: this.dataviewModel.isSearchApplied(),
isLocked: this.widgetModel.isLocked(),
isOtherAvailable: this.dataviewModel.isOtherAvailable(),
resultsCount: this.dataviewModel.getSearchCount(),
totalCats: this._getCategoriesSize(),
nullsPer: this._getNullPercentage(),
catsPer: this._getCurrentCategoriesPercentage()
})
);
var animator = new AnimateValues({
el: this.$el
});
animator.animateFromValues(this._getPreviousCategoriesPercentage(), this._getCurrentCategoriesPercentage(), '.js-cats',
animationTemplate, { defaultValue: '-', animationSpeed: 700, formatter: formatter.formatValue }
);
this._checkVisibility();
return this;
},
_initBinds: function () {
this.dataviewModel.bind('change:data change:totalCount', this.render, this);
this.widgetModel.bind('change:search change:locked', this.render, this);
this.widgetModel.bind('change:show_stats change:collapsed', this._checkVisibility, this);
this.add_related_model(this.dataviewModel);
this.add_related_model(this.widgetModel);
},
_getNullPercentage: function () {
var nulls = this.dataviewModel.get('nulls');
var total = this.dataviewModel.get('totalCount') || 0;
return !nulls ? 0 : ((nulls / total) * 100).toFixed(2);
},
_getPreviousCategoriesPercentage: function () {
var total = this.dataviewModel.previous('totalCount') || 0;
var data = this.dataviewModel.getPreviousData();
return this._getCategoriesPercentage(data, total);
},
_getCurrentCategoriesPercentage: function () {
var total = this.dataviewModel.get('totalCount') || 0;
var data = this.dataviewModel.getData().toJSON();
return this._getCategoriesPercentage(data, total);
},
_getCategoriesPercentage: function (data, total) {
if (!total) {
return 0;
}
var currentTotal = data.reduce(function (memo, mdl) {
return !mdl.agg ? (memo + parseFloat(mdl.value)) : memo;
}, 0);
if (!currentTotal) {
return 0;
}
return ((currentTotal / total) * 100).toFixed(2);
},
_getCategoriesSize: function () {
return _.pluck(
this.dataviewModel.getData().reject(function (mdl) {
return mdl.get('agg');
}), 'name').length;
},
_checkVisibility: function () {
var isVisible = !!this.widgetModel.get('show_stats') && !this.widgetModel.get('collapsed');
this.$el.toggle(isVisible);
}
});
@@ -0,0 +1,57 @@
<% if (isSearchEnabled) { %>
<form class="CDB-Widget-search js-form">
<div class="CDB-Shape CDB-Widget-searchLens u-iBlock u-rSpace js-searchIcon">
<span class="CDB-Shape-magnify is-small is-blue"></span>
</div>
<input type="text" class="CDB-Text CDB-Size-large CDB-Widget-textInput CDB-Widget-searchTextInput js-textInput" value="<%- q %>" placeholder="Search by <%- columnName %>"/>
<% if (canShowApply) { %>
<button type="button" class="CDB-Text is-semibold u-upperCase CDB-Size-small CDB-Widget-searchApply js-applyLocked u-actionTextColor">apply</button>
<% } %>
</form>
<% } else { %>
<div class="CDB-Widget-title CDB-Widget-contentSpaced js-title">
<h3 class="CDB-Text CDB-Size-large u-ellipsis js-titleText" title="<%- title %>"><%- title %></h3>
<div class="CDB-Widget-options CDB-Widget-contentSpaced">
<% if (isAutoStyleEnabled) { %>
<button class="CDB-Widget-buttonIcon CDB-Widget-buttonIcon--circle js-colors
<%- isAutoStyle ? 'is-selected' : '' %>
<%- isAutoStyle ? 'js-cancelAutoStyle' : 'js-autoStyle' %>
" data-tooltip="
<%- isAutoStyle ? 'Remove Auto style' : 'Apply Auto Style' %>
">
<i class="CDB-IconFont CDB-IconFont-drop CDB-IconFont--small CDB-IconFont--top"></i>
</button>
<% } %>
<button class="CDB-Shape CDB-Widget-actions js-actions u-lSpace" data-tooltip="More options">
<div class="CDB-Shape-threePoints is-blue is-small">
<div class="CDB-Shape-threePointsItem"></div>
<div class="CDB-Shape-threePointsItem"></div>
<div class="CDB-Shape-threePointsItem"></div>
</div>
</button>
</div>
</div>
<% if (showSource) { %>
<dl class="CDB-Widget-info u-tSpace">
<div class="u-flex u-alignCenter u-ellipsis">
<span class="CDB-Text CDB-Size-small is-semibold u-upperCase" style="color: <%- sourceColor %>;">
<%- sourceId %>
</span>
<% if (!isSourceType) { %>
<span class="CDB-Text CDB-Size-small u-lSpace--s u-flex" style="color: <%- sourceColor %>;">
<i class="CDB-IconFont CDB-IconFont-ray CDB-Size-small"></i>
</span>
<% } %>
<span class="CDB-Text CDB-Size-small u-mainTextColor u-lSpace">
<%= sourceType %>
</span>
<span class="CDB-Text CDB-Size-small u-altTextColor u-ellipsis u-lSpace" title="<%= layerName %>">
<%= layerName %>
</span>
</div>
</dl>
<% } %>
<% } %>
@@ -0,0 +1,186 @@
var _ = require('underscore');
var $ = require('jquery');
var CoreView = require('backbone/core-view');
var DropdownView = require('../../dropdown/widget-dropdown-view');
var TipsyTooltipView = require('../../../../builder/components/tipsy-tooltip-view');
var template = require('./search-title-template.tpl');
var layerColors = require('../../../util/layer-colors');
var analyses = require('../../../data/analyses');
var escapeHTML = require('../../../util/escape-html');
/**
* Show category title or search any category
* + another options for this widget, as in,
* colorize categories, lock defined categories...
*
*/
module.exports = CoreView.extend({
events: {
'keyup .js-textInput': '_onKeyupInput',
'submit .js-form': '_onSubmitForm',
'click .js-applyLocked': '_applyLocked',
'click .js-autoStyle': '_autoStyle',
'click .js-cancelAutoStyle': '_cancelAutoStyle'
},
initialize: function () {
if (!this.options.widgetModel) throw new Error('widgetModel is required');
if (!this.options.dataviewModel) throw new Error('dataviewModel is required');
if (!this.options.layerModel) throw new Error('layerModel is required');
this.model = this.options.widgetModel;
this.dataviewModel = this.options.dataviewModel;
this.layerModel = this.options.layerModel;
this._initBinds();
},
render: function () {
this.clearSubViews();
var sourceId = this.dataviewModel.get('source').id;
var letter = layerColors.letter(sourceId);
var sourceColor = layerColors.getColorForLetter(letter);
var sourceType = this.dataviewModel.getSourceType() || '';
var isSourceType = this.dataviewModel.isSourceType();
var layerName = isSourceType
? this.model.get('table_name')
: this.layerModel.get('layer_name');
this.$el.html(
template({
isCollapsed: this.model.get('collapsed'),
isAutoStyleEnabled: this._isAutoStyleButtonVisible(),
isAutoStyle: this.model.isAutoStyle(),
title: this.model.get('title'),
sourceId: sourceId,
sourceType: analyses.title(sourceType),
isSourceType: isSourceType,
showSource: this.model.get('show_source') && letter !== '',
sourceColor: sourceColor,
layerName: escapeHTML(layerName),
columnName: this.dataviewModel.get('column'),
q: this.dataviewModel.getSearchQuery(),
isLocked: this.model.isLocked(),
canBeLocked: this.model.canBeLocked(),
isSearchEnabled: this.model.isSearchEnabled(),
canShowApply: this.model.canApplyLocked()
})
);
this._initViews();
return this;
},
_initBinds: function () {
this.listenTo(this.model, 'change:search', this._onSearchToggled);
this.listenTo(this.model, 'change:title change:collapsed change:autoStyle change:style', this.render);
this.listenTo(this.model.lockedCategories, 'change add remove', this.render);
this.listenTo(this.dataviewModel, 'change:column', this.render);
this.listenTo(this.dataviewModel.filter, 'change', this.render);
this.listenTo(this.layerModel, 'change:visible change:cartocss change:layer_name', this.render);
},
_initViews: function () {
var dropdown = new DropdownView({
model: this.model,
target: '.js-actions',
container: this.$el
});
this.addView(dropdown);
var colorsTooltip = new TipsyTooltipView({
el: this.$el.find('.js-colors'),
gravity: 'auto'
});
this.addView(colorsTooltip);
var actionsTooltip = new TipsyTooltipView({
el: this.$el.find('.js-actions'),
gravity: 'auto'
});
this.addView(actionsTooltip);
},
_isAutoStyleButtonVisible: function () {
return this.model.isAutoStyleEnabled() &&
this.layerModel.get('visible') &&
this.model.hasColorsAutoStyle();
},
_onSearchToggled: function () {
var isSearchEnabled = this.model.isSearchEnabled();
this[isSearchEnabled ? '_bindESC' : '_unbindESC']();
this.render();
if (isSearchEnabled) {
this._focusOnInput();
}
},
_onSubmitForm: function (ev) {
if (ev) {
ev.preventDefault();
}
var q = this.$('.js-textInput').val();
if (this.dataviewModel.getSearchQuery() !== q) {
this.dataviewModel.setSearchQuery(q);
if (this.dataviewModel.isSearchValid()) {
this.dataviewModel.applySearch();
}
}
},
_focusOnInput: function () {
var self = this;
setTimeout(function () {
self.$('.js-textInput').focus();
}, 0);
},
_onKeyupInput: _.debounce(
function (ev) {
var q = this.$('.js-textInput').val();
if (ev.keyCode !== 13 && ev.keyCode !== 27 && q !== '') {
this._onSubmitForm();
}
}, 250
),
_bindESC: function () {
$(document).bind('keyup.' + this.cid, _.bind(this._onKeyUp, this));
},
_unbindESC: function () {
$(document).unbind('keyup.' + this.cid);
},
_onKeyUp: function (ev) {
if (ev.keyCode === 27) {
this._cancelSearch();
return false;
}
},
_applyLocked: function () {
this.model.toggleSearch();
this.model.applyLocked();
},
_autoStyle: function () {
this.model.autoStyle();
},
_cancelAutoStyle: function () {
this.model.cancelAutoStyle();
},
_cancelSearch: function () {
this.model.cleanSearch();
this.model.disableSearch();
},
clean: function () {
this._unbindESC();
CoreView.prototype.clean.call(this);
}
});