/* Copyright (c) 2010, Sage Software, Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @class argos.GroupedList
* @classdesc Grouped List provides a hook for grouping rows before rendering them to the page.
* The grouping adds a container for the set of rows and is collapsible.
* Note that it constructs the page sequentially meaning the rows should be in the correct
* order before attempting to group.
* @extends argos.List
*/
import declare from 'dojo/_base/declare';
import string from 'dojo/string';
import List from './List';
import Utility from './Utility';
const __class = declare('argos.GroupedList', [List], /** @lends argos.GroupedList# */{
accordion: null,
/**
* @property {Simplate}
* Simplate that defines the HTML Markup. This override adds the needed styling.
*/
widgetTemplate: new Simplate([
'<div id="{%= $.id %}" title="{%= $.titleText %}" class="list grouped-list listview-search {%= $.cls %}" {% if ($.resourceKind) { %}data-resource-kind="{%= $.resourceKind %}"{% } %}>',
'<div data-dojo-attach-point="searchNode"></div>',
'{%! $.emptySelectionTemplate %}',
'<div class="accordion panel inverse has-icons" data-dojo-attach-point="contentNode"></div>',
'{%! $.moreTemplate %}',
'{%! $.listActionTemplate %}',
'</div>',
]),
/**
* @property {Simplate}
* Simplate that defines the Group template that includes the header element with collapse button and the row container
*/
groupTemplate: new Simplate([`
<div class="accordion-header" role="presentation">
<a href="#" role="button"><span>{%: $.title %}</span></a>
</div>
<div class="accordion-pane" data-group="{%= $.tag %}">
</div>
`,
]),
/**
* @property {Simplate}
* The template used to render the pager at the bottom of the view. This template is not directly rendered, but is
* included in {@link #viewTemplate}.
*
* The default template uses the following properties:
*
* name description
* ----------------------------------------------------------------
* moreText The text to display on the more button.
*
* The default template exposes the following actions:
*
* * more
*/
moreTemplate: new Simplate([
'<div class="list-more" data-dojo-attach-point="moreNode">',
'<p class="list-remaining"><span data-dojo-attach-point="remainingContentNode"></span></p>',
'<button class="btn" data-action="more">',
'<span>{%= $.moreText %}</span>',
'</button>',
'</div>',
]),
/**
* @property {Object}
* The current group object that is compared to the next entries group object
* Must have a `tag` property that identifies the group.
* The `title` property will be placed into the `groupTemplate` for the header text.
*/
_groupBySections: null,
_currentGroupBySection: null,
/**
* Function that returns a "group object". The group object must have a tag property that is
* based off the passed entry as it will be used to compare to other entries.
* The title should also reflect the current entry as it will be used for the header text in the group splitter.
*
* An example for a Yellow Page type list:
*
* `entryA = {first: 'Henry', last: 'Smith', phone: '123'}`
* `entryB = {first: 'Mary', last: 'Sue', phone: '345'}`
*
* groupGroupForEntry: function(entry) {
* var lastInitial = entry.last.substr(0,1).toUppperCase();
* return {
* tag: lastInitial,
* title: lastInitial
* };
* }
*
* @template
* @param {Object} entry The current entry being processed.
* @return {Object} Object that contains a tag and title property where tag will be used in comparisons
*/
getGroupForEntry: function getGroupForEntry(entry) {
if (this._currentGroupBySection) {
let title;
const sectionDef = this._currentGroupBySection.section.getSection(entry);
if (this._currentGroupBySection.description) {
title = `${this._currentGroupBySection.description}: ${sectionDef.title}`;
} else {
title = sectionDef.title;
}
return {
tag: sectionDef.key,
title,
collapsed: !!sectionDef.collapsed,
};
}
return {
tag: 1,
title: 'Default',
};
},
/**
* Overwrites the parent {@link List#processFeed processFeed} to introduce grouping by group tags, see {@link #getGroupForEntry getGroupForEntry}.
* @param {Object} feed The SData feed result
* @deprecated Use processData instead
*/
processFeed: function processFeed(feed) {
const getGroupsNode = Utility.memoize(this.getGroupsNode.bind(this), (entryGroup) => {
return entryGroup.tag;
});
if (!this.feed) {
this.set('listContent', '');
}
this.feed = feed;
if (this.feed.$totalResults === 0) {
this.set('listContent', this.noDataTemplate.apply(this));
} else if (feed.$resources) {
for (let i = 0; i < feed.$resources.length; i++) {
const entry = feed.$resources[i];
const entryGroup = this.getGroupForEntry(entry);
entry.$groupTag = entryGroup.tag;
entry.$groupTitle = entryGroup.title;
this.entries[entry.$key] = entry;
const rowNode = $(this.rowTemplate.apply(entry, this));
this.onApplyRowTemplate(entry, rowNode.get(0));
$(getGroupsNode(entryGroup)).append(rowNode);
}
}
// todo: add more robust handling when $totalResults does not exist, i.e., hide element completely
if (typeof this.feed.$totalResults !== 'undefined') {
const remaining = this.feed.$totalResults - (this.feed.$startIndex + this.feed.$itemsPerPage - 1);
this.set('remainingContent', string.substitute(this.remainingText, [remaining]));
}
$(this.domNode).toggleClass('list-has-more', this.hasMoreData());
this.updateSoho();
},
processData: function processData(entries) {
const count = entries.length;
const store = this.get('store');
const getGroupsNode = Utility.memoize(this.getGroupsNode.bind(this), (entryGroup) => {
return entryGroup.tag;
});
if (count > 0) {
for (let i = 0; i < count; i++) {
const entry = this._processEntry(entries[i]);
this.entries[store.getIdentity(entry)] = entry;
const entryGroup = this.getGroupForEntry(entry);
entry.$groupTag = entryGroup.tag;
entry.$groupTitle = entryGroup.title;
const rowNode = $(this.rowTemplate.apply(entry, this));
this.onApplyRowTemplate(entry, rowNode.get(0));
$(getGroupsNode(entryGroup)).append(rowNode);
}
}
this.updateSoho();
},
getGroupsNode: function getGroupsNode(entryGroup) {
let results = $(`[data-group="${entryGroup.tag}"]`, this.contentNode);
if (results.length > 0) {
results = results.get(0);
} else {
// Does not exist, lets create it
results = $(this.groupTemplate.apply(entryGroup, this));
$(this.contentNode).append(results);
// re-query what we just place in (which was a doc frag)
results = $(`[data-group="${entryGroup.tag}"]`, this.contentNode).get(0);
}
return results;
},
/**
* Called on application startup to configure the search widget if present and create the list actions.
*/
startup: function startup() {
this.inherited(arguments);
this._initGroupBySections();
},
_initGroupBySections: function _initGroupBySections() {
this._groupBySections = this.getGroupBySections();
this.setDefaultGroupBySection();
this.applyGroupByOrderBy();
},
setDefaultGroupBySection: function setDefaultGroupBySection() {
let count = 0;
if (this._groupBySections) {
count = this._groupBySections.length;
for (let i = 0; i < count; i++) {
if (this._groupBySections[i].isDefault === true) {
this._currentGroupBySection = this._groupBySections[i];
}
}
if ((this._currentGroupBySection === null) && (count > 0)) {
this._currentGroupBySection = this._groupBySections[0];
}
}
},
getGroupBySection: function getGroupBySection(sectionId) {
let groupSection = null;
if (this._groupBySections) {
for (let i = 0; i < this._groupBySections.length; i++) {
if (this._groupBySections[i].Id === sectionId) {
groupSection = this._groupBySections[i];
}
}
}
return groupSection;
},
setCurrentGroupBySection: function setCurrentGroupBySection(sectionId) {
this._currentGroupBySection = this.getGroupBySection(sectionId);
this.applyGroupByOrderBy(); // need to refresh view
},
getGroupBySections: function getGroupBySections() {
return null;
},
applyGroupByOrderBy: function applyGroupByOrderBy() {
if (this._currentGroupBySection) {
this.queryOrderBy = this._currentGroupBySection.section.getOrderByQuery();
}
},
initSoho: function initSoho() {
const accordion = $('.accordion.panel', this.domNode);
accordion.accordion();
this.accordion = accordion.data('accordion');
},
updateSoho: function updateSoho() {
if (this.accordion) {
this.accordion.updated();
}
},
});
export default __class;