bat-ria
Version:
RIA extension for Brand Ads Team
708 lines (644 loc) • 22.9 kB
JavaScript
/**
* JN 2.0
* Copyright 2014 Baidu Inc. All rights reserved.
*
* @ignore
* @file 搜索树控件
* @author loutongbing(loutongbing@baidu.com)
*/
define(
function (require) {
require('esui/Tree');
require('esui/Label');
require('esui/Panel');
require('esui/SearchBox');
var painter = require('esui/painters');
var ui = require('esui/main');
var lib = require('esui/lib');
var u = require('underscore');
var TreeStrategy = require('./SearchTreeStrategy');
var InputControl = require('esui/InputControl');
/**
* 控件类
*
* @constructor
* @param {Object} options 初始化参数
*/
function SearchTree(options) {
InputControl.apply(this, arguments);
}
lib.inherits(SearchTree, InputControl);
/**
* 控件类型,始终为`"SearchTree"`
*
* @type {string}
* @readonly
* @override
*/
SearchTree.prototype.type = 'SearchTree';
SearchTree.prototype.styleType = 'SearchTree';
SearchTree.prototype.initOptions = function (options) {
var properties = {
// 数据源
datasource: null,
// 树的相关属性
defaultExpand: true,
wideToggleArea: false,
onlyLeafSelect: false,
allowUnselectNode: false,
hideRoot: true,
treeSkin: 'flat',
height: '200',
width: '200',
// 是否有搜索功能
hasSearchBox: true,
// 这个字段是对腿部信息的填充
itemName: '结果',
// 搜索为空的提示
emptyText: '没有相应的搜索结果',
// 是否刷新数据时保持搜索状态
holdState: false
};
if (options.hasSearchBox === 'false') {
options.hasSearchBox = false;
}
if (options.holdState === 'false') {
options.holdState = false;
}
lib.extend(properties, options);
// properties.width = Math.max(200, properties.width);
this.setProperties(properties);
};
/**
* @override
*/
SearchTree.prototype.initStructure = function () {
/* eslint-disable fecs-indent */
var tpl = [
// 内容
'<div data-ui="type:Panel;childName:body;"',
' class="${bodyClass}">',
'${searchInput}',
// 搜索结果列表区
'<div data-ui="type:Panel;childName:content"',
' class="${contentClass}">',
// 结果为空提示
'<div data-ui="type:Label;childName:emptyText"',
' class="${emptyTextClass}">${emptyText}</div>',
// 结果列表
'<div data-ui="type:Panel;childName:queryList"',
' class="${queryListClass}">',
'</div>',
'</div>',
'</div>'
];
/* eslint-enable fecs-indent */
var helper = this.helper;
var searchInput = '';
if (this.hasSearchBox) {
var searchBoxWidth = this.width - 45;
// var searchBoxWidth = 150;
searchInput = [
// 搜索区
'<div data-ui="type:Panel;childName:searchBoxArea"',
' class="${searchWrapperClass}">',
'<div data-ui="type:SearchBox;childName:itemSearch;"',
' data-ui-skin="magnifier"',
' data-ui-width="' + searchBoxWidth + '">',
'</div>',
'</div>',
// 搜索结果概要
'<div data-ui="type:Panel;',
'childName:generalQueryResultArea"',
' class="${generalQueryResultClass}"',
' id="${queryResultId}">',
'<span class="${linkClass}" id="${linkId}">清空</span>',
'共找到<span id="${queryResultCountId}"></span>个',
'</div>'
].join('\n');
searchInput = lib.format(
searchInput,
{
searchWrapperClass:
helper.getPartClassName('search-wrapper'),
generalQueryResultClass:
helper.getPartClassName('query-result-general'),
queryResultCountId: helper.getId('result-count'),
linkClass: helper.getPartClassName('clear-query-link'),
linkId: helper.getId('clear-query')
}
);
}
this.main.style.width = this.width + 'px';
this.main.innerHTML = lib.format(
tpl.join('\n'),
{
bodyClass: helper.getPartClassName('body'),
searchInput: searchInput,
contentClass: helper.getPartClassName('content-wrapper'),
emptyTextClass: helper.getPartClassName('empty-text'),
emptyText: this.emptyText,
queryListClass: helper.getPartClassName('query-list')
}
);
this.initChildren();
var clearQueryLink = helper.getPart('clear-query');
if (clearQueryLink) {
helper.addDOMEvent(
clearQueryLink,
'click',
u.bind(this.clearQuery, this)
);
}
var searchBox = this.getSearchBox();
if (searchBox) {
searchBox.on('search', this.search, this);
}
lib.addClass(
this.main,
'ui-searchtree'
);
};
SearchTree.prototype.search = function (e) {
var keyword = lib.trim(e.target.getValue());
// 查询
this.queryItem(keyword);
// 更新概要搜索结果区
this.refreshResult();
// 更新状态
this.addState('queried');
// 调整高度
this.adjustHeight();
};
SearchTree.prototype.refreshResult = function () {
var count = this.getCurrentStateItemsCount();
this.helper.getPart('result-count').innerHTML = count;
};
/**
* 清除搜索结果
* @return {false} 阻止默认行为
* @ignore
*/
SearchTree.prototype.clearQuery = function () {
// 更新状态
this.removeState('queried');
// 清空搜索框
var searchBox = this.getSearchBox();
if (searchBox) {
searchBox.set('text', '');
}
// 清空数据
this.clearData();
// 概要搜索结果区归零
this.refreshResult();
// 更新备选区
this.refreshContent();
// 调整高度
this.adjustHeight();
if (this.curSeleId) {
this.selectItem(this.curSeleId, true);
}
return false;
};
/**
* 获取结果列表承载容器控件,列表在它里面
* @return {ui.Panel}
* @ignore
*/
SearchTree.prototype.getContent = function () {
var body = this.getChild('body');
if (body) {
return body.getChild('content');
}
return null;
};
SearchTree.prototype.getKeyword = function () {
var searchBox = this.getSearchBox();
var isQuery = this.isQuery();
if (searchBox && isQuery) {
return lib.trim(searchBox.getValue());
}
return null;
};
/**
* 获取结果列表控件
* @return {ui.TreeForSelector | ui.ListForSelector}
* @ignore
*/
SearchTree.prototype.getQueryList = function () {
var content = this.getContent();
if (content) {
return content.getChild('queryList');
}
return null;
};
/**
* 获取搜索控件
* @return {ui.Panel}
* @ignore
*/
SearchTree.prototype.getSearchBox = function () {
var searchBoxArea =
this.getChild('body').getChild('searchBoxArea');
if (searchBoxArea) {
return searchBoxArea.getChild('itemSearch');
}
};
/**
* 判断是否处于query状态
* @return {boolean}
*/
SearchTree.prototype.isQuery = function () {
return this.hasState('queried');
};
/**
* 调整高度。
* 出现搜索信息时,结果区域的高度要变小,才能使整个控件高度不变
*
*/
SearchTree.prototype.adjustHeight = function() {
// 用户设置总高度
var settingHeight = this.height;
// 头部高度 contentHeight + border
var headHeight = 28;
// 是否有搜索框
var searchBoxHeight = this.hasSearchBox ? 45 : 0;
// 是否有腿部信息
var footHeight = this.hasFoot ? 25 : 0;
var content = this.getContent().main;
if (settingHeight === 'auto') {
content.style.height = 'auto';
}
else {
// 结果区高度 = 总高度 - 头部高度 - 搜索框高度 - 腿部高度
var contentHeight =
settingHeight - headHeight - searchBoxHeight - footHeight;
// 处于query状态时,会有一个30px的概要信息区
if (this.isQuery()) {
contentHeight -= 30;
}
content.style.height = contentHeight + 'px';
}
};
/**
* 手动刷新
*
* @ignore
*/
SearchTree.prototype.refresh = function () {
// 刷新搜索区
if (this.hasSearchBox) {
// 有的时候需要保留搜索状态
if (this.holdState && this.isQuery()) {
var keyword = this.getKeyword();
// 根据关键字获取结果
this.queryItem(keyword);
}
else {
// 清空搜索区
this.clearQuery();
// 重建数据
this.adaptData();
// 构建选区
this.refreshContent();
}
}
else {
// 重建数据
this.adaptData();
// 构建选区
this.refreshContent();
}
// 更新高度
this.adjustHeight();
};
/**
* 重新渲染视图
* 仅当生命周期处于RENDER时,该方法才重新渲染
*
* @param {Array=} 变更过的属性的集合
* @override
*/
SearchTree.prototype.repaint = painter.createRepaint(
SearchTree.prototype.repaint,
{
name: 'datasource',
paint: function (control, datasource) {
control.refresh();
}
}
);
/**
* 适配数据,创建一个全集扁平索引
*
* @ignore
*/
SearchTree.prototype.adaptData = function () {
// 这是一个不具备任何状态的东西
this.allData = this.datasource;
// 一个扁平化的索引
// 其中包含父节点信息,以及节点选择状态
var indexData = {};
if (this.allData && this.allData.children) {
indexData[this.allData.id] = {
parentId: null,
node: this.allData,
isSelected: false
};
walkTree(
this.allData,
this.allData.children,
function (parent, child) {
indexData[child.id] = {
parentId: parent.id,
node: child,
isSelected: false
};
}
);
}
this.indexData = indexData;
};
/**
* 刷新备选区
* @override
*/
SearchTree.prototype.refreshContent = function () {
var treeData = this.isQuery() ? this.queriedData : this.allData;
if (!treeData
|| !treeData.children
|| !treeData.children.length) {
this.addState('empty');
}
else {
this.removeState('empty');
}
if (!treeData || !treeData.children) {
return;
}
var queryList = this.getQueryList();
var tree = queryList.getChild('tree');
if (!tree) {
var options = {
childName: 'tree',
datasource: treeData,
allowUnselectNode: this.allowUnselectNode,
strategy:
new TreeStrategy(
{
onlyLeafSelect: this.onlyLeafSelect,
defaultExpand: this.defaultExpand
}
),
wideToggleArea: this.wideToggleArea,
hideRoot: this.hideRoot,
selectMode: 'single',
skin: this.treeSkin
};
if (this.getItemHTML) {
options.getItemHTML = this.getItemHTML;
}
tree = ui.create('Tree', options);
queryList.addChild(tree);
tree.appendTo(queryList.main);
var me = this;
var indexData = this.indexData;
tree.on(
'selectnode',
function (e) {
var node = e.node;
me.selectItem(node.id, true);
me.fire('change');
}
);
tree.on(
'unselectnode',
function (e) {
var node = e.node;
if (indexData[node.id]) {
indexData[node.id].isSelected = false;
}
}
);
}
else {
tree.setProperties({
'datasource': u.deepClone(treeData),
'keyword': this.getKeyword()
});
}
};
/**
* 撤销选择当前项
* @param {ui.SearchTree} control 类实例
* @ignore
*/
function unselectCurrent(control) {
var curId = control.curSeleId;
// 撤销当前选中项
if (curId) {
var treeList = control.getQueryList().getChild('tree');
treeList.unselectNode(curId);
control.curSeleId = null;
}
}
/**
* 获取指定状态的叶子节点,递归
*
* @param {Array=} data 检测的数据源
* @param {boolean} isSelected 选择状态还是未选状态
* @return {Array} 子节点数组
* @ignore
*/
SearchTree.prototype.getLeafItems = function (data, isSelected) {
var leafItems = [];
var me = this;
var indexData = this.indexData;
u.each(data, function (item) {
if (isLeaf(item)) {
var indexItem = indexData[item.id];
var valid = (isSelected === indexItem.isSelected);
if (valid) {
leafItems.push(item);
}
}
else {
leafItems = u.union(
leafItems,
me.getLeafItems(item.children, isSelected)
);
}
});
return leafItems;
};
/**
* 选择或取消选择
* 如果控件是单选的,则将自己置灰且将其他节点恢复可选
* 如果控件是多选的,则仅将自己置灰
* @param {Object} id 结点对象id
* @param {boolean} toBeSelected 置为选择还是取消选择
* @ignore
*/
SearchTree.prototype.selectItem = function (id, toBeSelected) {
var tree = this.getQueryList().getChild('tree');
// 完整数据
var indexData = this.indexData;
var item = indexData[id];
if (!item) {
return;
}
// 如果是单选,需要将其他的已选项置为未选
if (toBeSelected) {
unselectCurrent(this);
// 赋予新值
this.curSeleId = id;
}
item.isSelected = toBeSelected;
if (toBeSelected) {
tree.selectNode(id, true);
}
else {
tree.unselectNode(id);
}
};
/**
* 或许当前已选择的数据
*
* @return {Object}
* @public
*/
SearchTree.prototype.getSelectedItem = function () {
var me = this;
return u.find(
me.indexData,
function(item) {
return item.node.id === me.curSeleId;
}
).node;
};
/**
* 或许当前已选择的id
*
* @return {Object}
* @public
*/
SearchTree.prototype.getSelectedId = function () {
return this.curSeleId;
};
/**
* 清空搜索的结果
*
*/
SearchTree.prototype.clearData = function() {
// 清空数据
this.queriedData = {};
};
/**
* 搜索含有关键字的结果
*
* @param {string} keyword 关键字
*/
SearchTree.prototype.queryItem = function (keyword) {
var filteredTreeData = [];
filteredTreeData = queryFromNode(keyword, this.allData);
// 更新状态
this.queriedData = {
id: '-1', text: '符合条件的结果', children: filteredTreeData
};
this.addState('queried');
this.refreshContent();
if (this.curSeleId) {
this.selectItem(this.curSeleId, true);
}
};
/**
* 供递归调用的搜索方法
*
* @param {string} keyword 关键字
* @param {Object} node 节点对象
* @return {Array} 结果集
*/
function queryFromNode(keyword, node) {
var filteredTreeData = [];
var treeData = node.children;
u.each(
treeData,
function (data, key) {
var filteredData;
// 命中,先保存副本
if (data.text.indexOf(keyword) !== -1) {
filteredData = u.clone(data);
}
// 如果子节点也有符合条件的,那么只把符合条件的子结点放进去
if (data.children && data.children.length) {
var filteredChildren = queryFromNode(keyword, data);
if (filteredChildren.length > 0) {
if (!filteredData) {
filteredData = u.clone(data);
}
filteredData.children = filteredChildren;
}
}
if (filteredData) {
filteredTreeData.push(filteredData);
}
}
);
return filteredTreeData;
}
/**
* 一个遍历树的方法
*
* @param {Object} parent 父节点
* @param {Array} children 需要遍历的树的孩子节点
* @param {Function} callback 遍历时执行的函数
* @ignore
*/
function walkTree(parent, children, callback) {
u.each(
children,
function (child, key) {
callback(parent, child);
walkTree(child, child.children, callback);
}
);
}
function isLeaf(node) {
return !node.children;
}
function getLeavesCount(node) {
// 是叶子节点,但不是root节点
if (isLeaf(node)) {
// FIXME: 这里感觉不应该hardcode,后期想想办法
if (!node.id || node.id === '-1' || node.id === '0') {
return 0;
}
return 1;
}
var count = u.reduce(
node.children,
function (sum, child) {
return sum + getLeavesCount(child);
},
0
);
return count;
}
/**
* 获取当前状态的显示个数
*
* @return {number}
* @override
*/
SearchTree.prototype.getCurrentStateItemsCount = function () {
var node = this.isQuery() ? this.queriedData : this.allData;
if (!node) {
return 0;
}
var count = getLeavesCount(node);
return count;
};
require('esui').register(SearchTree);
return SearchTree;
}
);