From 49ea48a0419b35997070f5408f82580c9715b53b Mon Sep 17 00:00:00 2001 From: guy Date: Sun, 21 May 2017 21:10:58 +0800 Subject: [PATCH] add --- bi/base.css | 32 ++ bi/base.js | 379 ++++++++++++++++++++- demo/js/config/core.js | 4 + demo/js/core/abstract/demo.virtual_list.js | 11 + docs/base.css | 32 ++ docs/base.js | 379 ++++++++++++++++++++- docs/demo.js | 14 + src/base/list/clusterize.js | 329 ++++++++++++++++++ src/base/list/virtuallist.js | 43 +++ src/base/tree/treeview.js | 7 +- src/less/base/list/clusterize.less | 36 ++ 11 files changed, 1246 insertions(+), 20 deletions(-) create mode 100644 demo/js/core/abstract/demo.virtual_list.js create mode 100644 src/base/list/clusterize.js create mode 100644 src/base/list/virtuallist.js create mode 100644 src/less/base/list/clusterize.less diff --git a/bi/base.css b/bi/base.css index 9a756c5503..a470b9f270 100644 --- a/bi/base.css +++ b/bi/base.css @@ -633,6 +633,38 @@ li.CodeMirror-hint-active { cursor: text; font-size: 14px; } +/* max-height - the only parameter in this file that needs to be edited. + * Change it to suit your needs. The rest is recommended to leave as is. + */ +.clusterize-scroll { + overflow: auto; +} +/** + * Avoid vertical margins for extra tags + * Necessary for correct calculations when rows have nonzero vertical margins + */ +.clusterize-extra-row { + margin-top: 0 !important; + margin-bottom: 0 !important; +} +/* By default extra tag .clusterize-keep-parity added to keep parity of rows. + * Useful when used :nth-child(even/odd) + */ +.clusterize-extra-row.clusterize-keep-parity { + display: none; +} +/* During initialization clusterize adds tabindex to force the browser to keep focus + * on the scrolling list, see issue #11 + * Outline removes default browser's borders for focused elements. + */ +.clusterize-content { + outline: 0; +} +/* Centering message that appears when no data provided + */ +.clusterize-no-data td { + text-align: center; +} /****添加计算宽度的--运算符直接需要space****/ /****** common color(常用颜色,可用于普遍场景) *****/ /**** custom color(自定义颜色,用于特定场景) ****/ diff --git a/bi/base.js b/bi/base.js index 47d72dbfbe..b17b359288 100644 --- a/bi/base.js +++ b/bi/base.js @@ -1937,6 +1937,7 @@ BI.TreeView = BI.inherit(BI.Pane, { this.nodes && this.nodes.expandAll(flag); }, + //设置树节点的状态 setValue: function (value, param) { this.setSelectedValue(value); this.checkAll(false); @@ -1964,12 +1965,6 @@ BI.TreeView = BI.inherit(BI.Pane, { }); }, - getExpandedValue: function () { - if (!this.nodes) { - return null; - } - }, - refresh: function () { this.nodes && this.nodes.refresh(); }, @@ -15453,7 +15448,377 @@ BI.SearcherView = BI.inherit(BI.Pane, { }); BI.SearcherView.EVENT_CHANGE = "EVENT_CHANGE"; -BI.shortcut("bi.searcher_view", BI.SearcherView);/** +BI.shortcut("bi.searcher_view", BI.SearcherView);/*! Clusterize.js - v0.17.6 - 2017-03-05 + * http://NeXTs.github.com/Clusterize.js/ + * Copyright (c) 2015 Denis Lukov; Licensed GPLv3 */ + +;(function(name, definition) { + if (typeof module != 'undefined') module.exports = definition(); + else if (typeof define == 'function' && typeof define.amd == 'object') define(definition); + else this[name] = definition(); +}('Clusterize', function() { + "use strict" + + // detect ie9 and lower + // https://gist.github.com/padolsey/527683#comment-786682 + var ie = (function(){ + for( var v = 3, + el = document.createElement('b'), + all = el.all || []; + el.innerHTML = '', + all[0]; + ){} + return v > 4 ? v : document.documentMode; + }()), + is_mac = navigator.platform.toLowerCase().indexOf('mac') + 1; + var Clusterize = function(data) { + if( ! (this instanceof Clusterize)) + return new Clusterize(data); + var self = this; + + var defaults = { + rows_in_block: 50, + blocks_in_cluster: 4, + tag: null, + show_no_data_row: true, + no_data_class: 'clusterize-no-data', + no_data_text: 'No data', + keep_parity: true, + callbacks: {} + } + + // public parameters + self.options = {}; + var options = ['rows_in_block', 'blocks_in_cluster', 'show_no_data_row', 'no_data_class', 'no_data_text', 'keep_parity', 'tag', 'callbacks']; + for(var i = 0, option; option = options[i]; i++) { + self.options[option] = typeof data[option] != 'undefined' && data[option] != null + ? data[option] + : defaults[option]; + } + + var elems = ['scroll', 'content']; + for(var i = 0, elem; elem = elems[i]; i++) { + self[elem + '_elem'] = data[elem + 'Id'] + ? document.getElementById(data[elem + 'Id']) + : data[elem + 'Elem']; + if( ! self[elem + '_elem']) + throw new Error("Error! Could not find " + elem + " element"); + } + + // tabindex forces the browser to keep focus on the scrolling list, fixes #11 + if( ! self.content_elem.hasAttribute('tabindex')) + self.content_elem.setAttribute('tabindex', 0); + + // private parameters + var rows = isArray(data.rows) + ? data.rows + : self.fetchMarkup(), + cache = {}, + scroll_top = self.scroll_elem.scrollTop; + + // append initial data + self.insertToDOM(rows, cache); + + // restore the scroll position + self.scroll_elem.scrollTop = scroll_top; + + // adding scroll handler + var last_cluster = false, + scroll_debounce = 0, + pointer_events_set = false, + scrollEv = function() { + // fixes scrolling issue on Mac #3 + if (is_mac) { + if( ! pointer_events_set) self.content_elem.style.pointerEvents = 'none'; + pointer_events_set = true; + clearTimeout(scroll_debounce); + scroll_debounce = setTimeout(function () { + self.content_elem.style.pointerEvents = 'auto'; + pointer_events_set = false; + }, 50); + } + if (last_cluster != (last_cluster = self.getClusterNum())) + self.insertToDOM(rows, cache); + if (self.options.callbacks.scrollingProgress) + self.options.callbacks.scrollingProgress(self.getScrollProgress()); + }, + resize_debounce = 0, + resizeEv = function() { + clearTimeout(resize_debounce); + resize_debounce = setTimeout(self.refresh, 100); + } + on('scroll', self.scroll_elem, scrollEv); + on('resize', window, resizeEv); + + // public methods + self.destroy = function(clean) { + off('scroll', self.scroll_elem, scrollEv); + off('resize', window, resizeEv); + self.html((clean ? self.generateEmptyRow() : rows).join('')); + } + self.refresh = function(force) { + if(self.getRowsHeight(rows) || force) self.update(rows); + } + self.update = function(new_rows) { + rows = isArray(new_rows) + ? new_rows + : []; + var scroll_top = self.scroll_elem.scrollTop; + // fixes #39 + if(rows.length * self.options.item_height < scroll_top) { + self.scroll_elem.scrollTop = 0; + last_cluster = 0; + } + self.insertToDOM(rows, cache); + self.scroll_elem.scrollTop = scroll_top; + } + self.clear = function() { + self.update([]); + } + self.getRowsAmount = function() { + return rows.length; + } + self.getScrollProgress = function() { + return this.options.scroll_top / (rows.length * this.options.item_height) * 100 || 0; + } + + var add = function(where, _new_rows) { + var new_rows = isArray(_new_rows) + ? _new_rows + : []; + if( ! new_rows.length) return; + rows = where == 'append' + ? rows.concat(new_rows) + : new_rows.concat(rows); + self.insertToDOM(rows, cache); + } + self.append = function(rows) { + add('append', rows); + } + self.prepend = function(rows) { + add('prepend', rows); + } + } + + Clusterize.prototype = { + constructor: Clusterize, + // fetch existing markup + fetchMarkup: function() { + var rows = [], rows_nodes = this.getChildNodes(this.content_elem); + while (rows_nodes.length) { + rows.push(rows_nodes.shift().outerHTML); + } + return rows; + }, + // get tag name, content tag name, tag height, calc cluster height + exploreEnvironment: function(rows, cache) { + var opts = this.options; + opts.content_tag = this.content_elem.tagName.toLowerCase(); + if( ! rows.length) return; + if(ie && ie <= 9 && ! opts.tag) opts.tag = rows[0].match(/<([^>\s/]*)/)[1].toLowerCase(); + if(this.content_elem.children.length <= 1) cache.data = this.html(rows[0] + rows[0] + rows[0]); + if( ! opts.tag) opts.tag = this.content_elem.children[0].tagName.toLowerCase(); + this.getRowsHeight(rows); + }, + getRowsHeight: function(rows) { + var opts = this.options, + prev_item_height = opts.item_height; + opts.cluster_height = 0; + if( ! rows.length) return; + var nodes = this.content_elem.children; + var node = nodes[Math.floor(nodes.length / 2)]; + opts.item_height = node.offsetHeight; + // consider table's border-spacing + if(opts.tag == 'tr' && getStyle('borderCollapse', this.content_elem) != 'collapse') + opts.item_height += parseInt(getStyle('borderSpacing', this.content_elem), 10) || 0; + // consider margins (and margins collapsing) + if(opts.tag != 'tr') { + var marginTop = parseInt(getStyle('marginTop', node), 10) || 0; + var marginBottom = parseInt(getStyle('marginBottom', node), 10) || 0; + opts.item_height += Math.max(marginTop, marginBottom); + } + opts.block_height = opts.item_height * opts.rows_in_block; + opts.rows_in_cluster = opts.blocks_in_cluster * opts.rows_in_block; + opts.cluster_height = opts.blocks_in_cluster * opts.block_height; + return prev_item_height != opts.item_height; + }, + // get current cluster number + getClusterNum: function () { + this.options.scroll_top = this.scroll_elem.scrollTop; + return Math.floor(this.options.scroll_top / (this.options.cluster_height - this.options.block_height)) || 0; + }, + // generate empty row if no data provided + generateEmptyRow: function() { + var opts = this.options; + if( ! opts.tag || ! opts.show_no_data_row) return []; + var empty_row = document.createElement(opts.tag), + no_data_content = document.createTextNode(opts.no_data_text), td; + empty_row.className = opts.no_data_class; + if(opts.tag == 'tr') { + td = document.createElement('td'); + // fixes #53 + td.colSpan = 100; + td.appendChild(no_data_content); + } + empty_row.appendChild(td || no_data_content); + return [empty_row.outerHTML]; + }, + // generate cluster for current scroll position + generate: function (rows, cluster_num) { + var opts = this.options, + rows_len = rows.length; + if (rows_len < opts.rows_in_block) { + return { + top_offset: 0, + bottom_offset: 0, + rows_above: 0, + rows: rows_len ? rows : this.generateEmptyRow() + } + } + var items_start = Math.max((opts.rows_in_cluster - opts.rows_in_block) * cluster_num, 0), + items_end = items_start + opts.rows_in_cluster, + top_offset = Math.max(items_start * opts.item_height, 0), + bottom_offset = Math.max((rows_len - items_end) * opts.item_height, 0), + this_cluster_rows = [], + rows_above = items_start; + if(top_offset < 1) { + rows_above++; + } + for (var i = items_start; i < items_end; i++) { + rows[i] && this_cluster_rows.push(rows[i]); + } + return { + top_offset: top_offset, + bottom_offset: bottom_offset, + rows_above: rows_above, + rows: this_cluster_rows + } + }, + renderExtraTag: function(class_name, height) { + var tag = document.createElement(this.options.tag), + clusterize_prefix = 'clusterize-'; + tag.className = [clusterize_prefix + 'extra-row', clusterize_prefix + class_name].join(' '); + height && (tag.style.height = height + 'px'); + return tag.outerHTML; + }, + // if necessary verify data changed and insert to DOM + insertToDOM: function(rows, cache) { + // explore row's height + if( ! this.options.cluster_height) { + this.exploreEnvironment(rows, cache); + } + var data = this.generate(rows, this.getClusterNum()), + this_cluster_rows = data.rows.join(''), + this_cluster_content_changed = this.checkChanges('data', this_cluster_rows, cache), + top_offset_changed = this.checkChanges('top', data.top_offset, cache), + only_bottom_offset_changed = this.checkChanges('bottom', data.bottom_offset, cache), + callbacks = this.options.callbacks, + layout = []; + + if(this_cluster_content_changed || top_offset_changed) { + if(data.top_offset) { + this.options.keep_parity && layout.push(this.renderExtraTag('keep-parity')); + layout.push(this.renderExtraTag('top-space', data.top_offset)); + } + layout.push(this_cluster_rows); + data.bottom_offset && layout.push(this.renderExtraTag('bottom-space', data.bottom_offset)); + callbacks.clusterWillChange && callbacks.clusterWillChange(); + this.html(layout.join('')); + this.options.content_tag == 'ol' && this.content_elem.setAttribute('start', data.rows_above); + callbacks.clusterChanged && callbacks.clusterChanged(); + } else if(only_bottom_offset_changed) { + this.content_elem.lastChild.style.height = data.bottom_offset + 'px'; + } + }, + // unfortunately ie <= 9 does not allow to use innerHTML for table elements, so make a workaround + html: function(data) { + var content_elem = this.content_elem; + if(ie && ie <= 9 && this.options.tag == 'tr') { + var div = document.createElement('div'), last; + div.innerHTML = '' + data + '
'; + while((last = content_elem.lastChild)) { + content_elem.removeChild(last); + } + var rows_nodes = this.getChildNodes(div.firstChild.firstChild); + while (rows_nodes.length) { + content_elem.appendChild(rows_nodes.shift()); + } + } else { + content_elem.innerHTML = data; + } + }, + getChildNodes: function(tag) { + var child_nodes = tag.children, nodes = []; + for (var i = 0, ii = child_nodes.length; i < ii; i++) { + nodes.push(child_nodes[i]); + } + return nodes; + }, + checkChanges: function(type, value, cache) { + var changed = value != cache[type]; + cache[type] = value; + return changed; + } + } + + // support functions + function on(evt, element, fnc) { + return element.addEventListener ? element.addEventListener(evt, fnc, false) : element.attachEvent("on" + evt, fnc); + } + function off(evt, element, fnc) { + return element.removeEventListener ? element.removeEventListener(evt, fnc, false) : element.detachEvent("on" + evt, fnc); + } + function isArray(arr) { + return Object.prototype.toString.call(arr) === '[object Array]'; + } + function getStyle(prop, elem) { + return window.getComputedStyle ? window.getComputedStyle(elem)[prop] : elem.currentStyle[prop]; + } + + return Clusterize; +}));/** + * 表示当前对象 + * + * Created by GUY on 2015/9/7. + * @class BI.VirtualList + * @extends BI.Widget + */ +BI.VirtualList = BI.inherit(BI.Widget, { + props: function () { + return { + baseCls: "bi-virtual-list clusterize-scroll", + }; + }, + render: function () { + var self = this, o = this.options; + return { + type: "bi.default", + items: [{ + type: "bi.layout", + ref: function () { + self.contentEl = this; + }, + cls: "clusterize-content" + }] + } + }, + + mounted: function () { + var data = []; + for (var i = 0; i < 10000; i++) { + data.push("
" + i + "
"); + } + new Clusterize({ + rows: data, + scrollElem: this.element[0], + contentElem: this.contentEl.element[0] + }) + }, + + populate: function () { + } +}); +BI.shortcut('bi.virtual_list', BI.VirtualList);/** * 分页控件 * * Created by GUY on 2015/8/31. diff --git a/demo/js/config/core.js b/demo/js/config/core.js index 05f334a539..02af07dee0 100644 --- a/demo/js/config/core.js +++ b/demo/js/config/core.js @@ -105,6 +105,10 @@ Demo.CORE_CONFIG = [{ pId: 102, text: "bi.collection_view", value: "demo.collection_view" +},{ + pId: 102, + text: "bi.virtual_list", + value: "demo.virtual_list" }, { pId: 102, id: 10201, diff --git a/demo/js/core/abstract/demo.virtual_list.js b/demo/js/core/abstract/demo.virtual_list.js new file mode 100644 index 0000000000..792a5d8288 --- /dev/null +++ b/demo/js/core/abstract/demo.virtual_list.js @@ -0,0 +1,11 @@ +Demo.Func = BI.inherit(BI.Widget, { + props: { + baseCls: "demo-func" + }, + render: function () { + return { + type: "bi.virtual_list" + } + } +}); +BI.shortcut("demo.virtual_list", Demo.Func); \ No newline at end of file diff --git a/docs/base.css b/docs/base.css index 9a756c5503..a470b9f270 100644 --- a/docs/base.css +++ b/docs/base.css @@ -633,6 +633,38 @@ li.CodeMirror-hint-active { cursor: text; font-size: 14px; } +/* max-height - the only parameter in this file that needs to be edited. + * Change it to suit your needs. The rest is recommended to leave as is. + */ +.clusterize-scroll { + overflow: auto; +} +/** + * Avoid vertical margins for extra tags + * Necessary for correct calculations when rows have nonzero vertical margins + */ +.clusterize-extra-row { + margin-top: 0 !important; + margin-bottom: 0 !important; +} +/* By default extra tag .clusterize-keep-parity added to keep parity of rows. + * Useful when used :nth-child(even/odd) + */ +.clusterize-extra-row.clusterize-keep-parity { + display: none; +} +/* During initialization clusterize adds tabindex to force the browser to keep focus + * on the scrolling list, see issue #11 + * Outline removes default browser's borders for focused elements. + */ +.clusterize-content { + outline: 0; +} +/* Centering message that appears when no data provided + */ +.clusterize-no-data td { + text-align: center; +} /****添加计算宽度的--运算符直接需要space****/ /****** common color(常用颜色,可用于普遍场景) *****/ /**** custom color(自定义颜色,用于特定场景) ****/ diff --git a/docs/base.js b/docs/base.js index 47d72dbfbe..b17b359288 100644 --- a/docs/base.js +++ b/docs/base.js @@ -1937,6 +1937,7 @@ BI.TreeView = BI.inherit(BI.Pane, { this.nodes && this.nodes.expandAll(flag); }, + //设置树节点的状态 setValue: function (value, param) { this.setSelectedValue(value); this.checkAll(false); @@ -1964,12 +1965,6 @@ BI.TreeView = BI.inherit(BI.Pane, { }); }, - getExpandedValue: function () { - if (!this.nodes) { - return null; - } - }, - refresh: function () { this.nodes && this.nodes.refresh(); }, @@ -15453,7 +15448,377 @@ BI.SearcherView = BI.inherit(BI.Pane, { }); BI.SearcherView.EVENT_CHANGE = "EVENT_CHANGE"; -BI.shortcut("bi.searcher_view", BI.SearcherView);/** +BI.shortcut("bi.searcher_view", BI.SearcherView);/*! Clusterize.js - v0.17.6 - 2017-03-05 + * http://NeXTs.github.com/Clusterize.js/ + * Copyright (c) 2015 Denis Lukov; Licensed GPLv3 */ + +;(function(name, definition) { + if (typeof module != 'undefined') module.exports = definition(); + else if (typeof define == 'function' && typeof define.amd == 'object') define(definition); + else this[name] = definition(); +}('Clusterize', function() { + "use strict" + + // detect ie9 and lower + // https://gist.github.com/padolsey/527683#comment-786682 + var ie = (function(){ + for( var v = 3, + el = document.createElement('b'), + all = el.all || []; + el.innerHTML = '', + all[0]; + ){} + return v > 4 ? v : document.documentMode; + }()), + is_mac = navigator.platform.toLowerCase().indexOf('mac') + 1; + var Clusterize = function(data) { + if( ! (this instanceof Clusterize)) + return new Clusterize(data); + var self = this; + + var defaults = { + rows_in_block: 50, + blocks_in_cluster: 4, + tag: null, + show_no_data_row: true, + no_data_class: 'clusterize-no-data', + no_data_text: 'No data', + keep_parity: true, + callbacks: {} + } + + // public parameters + self.options = {}; + var options = ['rows_in_block', 'blocks_in_cluster', 'show_no_data_row', 'no_data_class', 'no_data_text', 'keep_parity', 'tag', 'callbacks']; + for(var i = 0, option; option = options[i]; i++) { + self.options[option] = typeof data[option] != 'undefined' && data[option] != null + ? data[option] + : defaults[option]; + } + + var elems = ['scroll', 'content']; + for(var i = 0, elem; elem = elems[i]; i++) { + self[elem + '_elem'] = data[elem + 'Id'] + ? document.getElementById(data[elem + 'Id']) + : data[elem + 'Elem']; + if( ! self[elem + '_elem']) + throw new Error("Error! Could not find " + elem + " element"); + } + + // tabindex forces the browser to keep focus on the scrolling list, fixes #11 + if( ! self.content_elem.hasAttribute('tabindex')) + self.content_elem.setAttribute('tabindex', 0); + + // private parameters + var rows = isArray(data.rows) + ? data.rows + : self.fetchMarkup(), + cache = {}, + scroll_top = self.scroll_elem.scrollTop; + + // append initial data + self.insertToDOM(rows, cache); + + // restore the scroll position + self.scroll_elem.scrollTop = scroll_top; + + // adding scroll handler + var last_cluster = false, + scroll_debounce = 0, + pointer_events_set = false, + scrollEv = function() { + // fixes scrolling issue on Mac #3 + if (is_mac) { + if( ! pointer_events_set) self.content_elem.style.pointerEvents = 'none'; + pointer_events_set = true; + clearTimeout(scroll_debounce); + scroll_debounce = setTimeout(function () { + self.content_elem.style.pointerEvents = 'auto'; + pointer_events_set = false; + }, 50); + } + if (last_cluster != (last_cluster = self.getClusterNum())) + self.insertToDOM(rows, cache); + if (self.options.callbacks.scrollingProgress) + self.options.callbacks.scrollingProgress(self.getScrollProgress()); + }, + resize_debounce = 0, + resizeEv = function() { + clearTimeout(resize_debounce); + resize_debounce = setTimeout(self.refresh, 100); + } + on('scroll', self.scroll_elem, scrollEv); + on('resize', window, resizeEv); + + // public methods + self.destroy = function(clean) { + off('scroll', self.scroll_elem, scrollEv); + off('resize', window, resizeEv); + self.html((clean ? self.generateEmptyRow() : rows).join('')); + } + self.refresh = function(force) { + if(self.getRowsHeight(rows) || force) self.update(rows); + } + self.update = function(new_rows) { + rows = isArray(new_rows) + ? new_rows + : []; + var scroll_top = self.scroll_elem.scrollTop; + // fixes #39 + if(rows.length * self.options.item_height < scroll_top) { + self.scroll_elem.scrollTop = 0; + last_cluster = 0; + } + self.insertToDOM(rows, cache); + self.scroll_elem.scrollTop = scroll_top; + } + self.clear = function() { + self.update([]); + } + self.getRowsAmount = function() { + return rows.length; + } + self.getScrollProgress = function() { + return this.options.scroll_top / (rows.length * this.options.item_height) * 100 || 0; + } + + var add = function(where, _new_rows) { + var new_rows = isArray(_new_rows) + ? _new_rows + : []; + if( ! new_rows.length) return; + rows = where == 'append' + ? rows.concat(new_rows) + : new_rows.concat(rows); + self.insertToDOM(rows, cache); + } + self.append = function(rows) { + add('append', rows); + } + self.prepend = function(rows) { + add('prepend', rows); + } + } + + Clusterize.prototype = { + constructor: Clusterize, + // fetch existing markup + fetchMarkup: function() { + var rows = [], rows_nodes = this.getChildNodes(this.content_elem); + while (rows_nodes.length) { + rows.push(rows_nodes.shift().outerHTML); + } + return rows; + }, + // get tag name, content tag name, tag height, calc cluster height + exploreEnvironment: function(rows, cache) { + var opts = this.options; + opts.content_tag = this.content_elem.tagName.toLowerCase(); + if( ! rows.length) return; + if(ie && ie <= 9 && ! opts.tag) opts.tag = rows[0].match(/<([^>\s/]*)/)[1].toLowerCase(); + if(this.content_elem.children.length <= 1) cache.data = this.html(rows[0] + rows[0] + rows[0]); + if( ! opts.tag) opts.tag = this.content_elem.children[0].tagName.toLowerCase(); + this.getRowsHeight(rows); + }, + getRowsHeight: function(rows) { + var opts = this.options, + prev_item_height = opts.item_height; + opts.cluster_height = 0; + if( ! rows.length) return; + var nodes = this.content_elem.children; + var node = nodes[Math.floor(nodes.length / 2)]; + opts.item_height = node.offsetHeight; + // consider table's border-spacing + if(opts.tag == 'tr' && getStyle('borderCollapse', this.content_elem) != 'collapse') + opts.item_height += parseInt(getStyle('borderSpacing', this.content_elem), 10) || 0; + // consider margins (and margins collapsing) + if(opts.tag != 'tr') { + var marginTop = parseInt(getStyle('marginTop', node), 10) || 0; + var marginBottom = parseInt(getStyle('marginBottom', node), 10) || 0; + opts.item_height += Math.max(marginTop, marginBottom); + } + opts.block_height = opts.item_height * opts.rows_in_block; + opts.rows_in_cluster = opts.blocks_in_cluster * opts.rows_in_block; + opts.cluster_height = opts.blocks_in_cluster * opts.block_height; + return prev_item_height != opts.item_height; + }, + // get current cluster number + getClusterNum: function () { + this.options.scroll_top = this.scroll_elem.scrollTop; + return Math.floor(this.options.scroll_top / (this.options.cluster_height - this.options.block_height)) || 0; + }, + // generate empty row if no data provided + generateEmptyRow: function() { + var opts = this.options; + if( ! opts.tag || ! opts.show_no_data_row) return []; + var empty_row = document.createElement(opts.tag), + no_data_content = document.createTextNode(opts.no_data_text), td; + empty_row.className = opts.no_data_class; + if(opts.tag == 'tr') { + td = document.createElement('td'); + // fixes #53 + td.colSpan = 100; + td.appendChild(no_data_content); + } + empty_row.appendChild(td || no_data_content); + return [empty_row.outerHTML]; + }, + // generate cluster for current scroll position + generate: function (rows, cluster_num) { + var opts = this.options, + rows_len = rows.length; + if (rows_len < opts.rows_in_block) { + return { + top_offset: 0, + bottom_offset: 0, + rows_above: 0, + rows: rows_len ? rows : this.generateEmptyRow() + } + } + var items_start = Math.max((opts.rows_in_cluster - opts.rows_in_block) * cluster_num, 0), + items_end = items_start + opts.rows_in_cluster, + top_offset = Math.max(items_start * opts.item_height, 0), + bottom_offset = Math.max((rows_len - items_end) * opts.item_height, 0), + this_cluster_rows = [], + rows_above = items_start; + if(top_offset < 1) { + rows_above++; + } + for (var i = items_start; i < items_end; i++) { + rows[i] && this_cluster_rows.push(rows[i]); + } + return { + top_offset: top_offset, + bottom_offset: bottom_offset, + rows_above: rows_above, + rows: this_cluster_rows + } + }, + renderExtraTag: function(class_name, height) { + var tag = document.createElement(this.options.tag), + clusterize_prefix = 'clusterize-'; + tag.className = [clusterize_prefix + 'extra-row', clusterize_prefix + class_name].join(' '); + height && (tag.style.height = height + 'px'); + return tag.outerHTML; + }, + // if necessary verify data changed and insert to DOM + insertToDOM: function(rows, cache) { + // explore row's height + if( ! this.options.cluster_height) { + this.exploreEnvironment(rows, cache); + } + var data = this.generate(rows, this.getClusterNum()), + this_cluster_rows = data.rows.join(''), + this_cluster_content_changed = this.checkChanges('data', this_cluster_rows, cache), + top_offset_changed = this.checkChanges('top', data.top_offset, cache), + only_bottom_offset_changed = this.checkChanges('bottom', data.bottom_offset, cache), + callbacks = this.options.callbacks, + layout = []; + + if(this_cluster_content_changed || top_offset_changed) { + if(data.top_offset) { + this.options.keep_parity && layout.push(this.renderExtraTag('keep-parity')); + layout.push(this.renderExtraTag('top-space', data.top_offset)); + } + layout.push(this_cluster_rows); + data.bottom_offset && layout.push(this.renderExtraTag('bottom-space', data.bottom_offset)); + callbacks.clusterWillChange && callbacks.clusterWillChange(); + this.html(layout.join('')); + this.options.content_tag == 'ol' && this.content_elem.setAttribute('start', data.rows_above); + callbacks.clusterChanged && callbacks.clusterChanged(); + } else if(only_bottom_offset_changed) { + this.content_elem.lastChild.style.height = data.bottom_offset + 'px'; + } + }, + // unfortunately ie <= 9 does not allow to use innerHTML for table elements, so make a workaround + html: function(data) { + var content_elem = this.content_elem; + if(ie && ie <= 9 && this.options.tag == 'tr') { + var div = document.createElement('div'), last; + div.innerHTML = '' + data + '
'; + while((last = content_elem.lastChild)) { + content_elem.removeChild(last); + } + var rows_nodes = this.getChildNodes(div.firstChild.firstChild); + while (rows_nodes.length) { + content_elem.appendChild(rows_nodes.shift()); + } + } else { + content_elem.innerHTML = data; + } + }, + getChildNodes: function(tag) { + var child_nodes = tag.children, nodes = []; + for (var i = 0, ii = child_nodes.length; i < ii; i++) { + nodes.push(child_nodes[i]); + } + return nodes; + }, + checkChanges: function(type, value, cache) { + var changed = value != cache[type]; + cache[type] = value; + return changed; + } + } + + // support functions + function on(evt, element, fnc) { + return element.addEventListener ? element.addEventListener(evt, fnc, false) : element.attachEvent("on" + evt, fnc); + } + function off(evt, element, fnc) { + return element.removeEventListener ? element.removeEventListener(evt, fnc, false) : element.detachEvent("on" + evt, fnc); + } + function isArray(arr) { + return Object.prototype.toString.call(arr) === '[object Array]'; + } + function getStyle(prop, elem) { + return window.getComputedStyle ? window.getComputedStyle(elem)[prop] : elem.currentStyle[prop]; + } + + return Clusterize; +}));/** + * 表示当前对象 + * + * Created by GUY on 2015/9/7. + * @class BI.VirtualList + * @extends BI.Widget + */ +BI.VirtualList = BI.inherit(BI.Widget, { + props: function () { + return { + baseCls: "bi-virtual-list clusterize-scroll", + }; + }, + render: function () { + var self = this, o = this.options; + return { + type: "bi.default", + items: [{ + type: "bi.layout", + ref: function () { + self.contentEl = this; + }, + cls: "clusterize-content" + }] + } + }, + + mounted: function () { + var data = []; + for (var i = 0; i < 10000; i++) { + data.push("
" + i + "
"); + } + new Clusterize({ + rows: data, + scrollElem: this.element[0], + contentElem: this.contentEl.element[0] + }) + }, + + populate: function () { + } +}); +BI.shortcut('bi.virtual_list', BI.VirtualList);/** * 分页控件 * * Created by GUY on 2015/8/31. diff --git a/docs/demo.js b/docs/demo.js index e7fa71450f..7542faa5b0 100644 --- a/docs/demo.js +++ b/docs/demo.js @@ -3038,6 +3038,10 @@ Demo.COMPONENT_CONFIG = [{ pId: 102, text: "bi.collection_view", value: "demo.collection_view" +},{ + pId: 102, + text: "bi.virtual_list", + value: "demo.virtual_list" }, { pId: 102, id: 10201, @@ -3476,6 +3480,16 @@ BI.shortcut("demo.virtual_group_item", Demo.Item);Demo.Func = BI.inherit(BI.Widg props: { baseCls: "demo-func" }, + render: function () { + return { + type: "bi.virtual_list" + } + } +}); +BI.shortcut("demo.virtual_list", Demo.Func);Demo.Func = BI.inherit(BI.Widget, { + props: { + baseCls: "demo-func" + }, _createDefaultTree: function () { var TREEITEMS = [{id: -1, pId: -2, value: "根目录", open: true, type: "bi.plus_group_node", height: 25}, diff --git a/src/base/list/clusterize.js b/src/base/list/clusterize.js new file mode 100644 index 0000000000..63c59e2baf --- /dev/null +++ b/src/base/list/clusterize.js @@ -0,0 +1,329 @@ +/*! Clusterize.js - v0.17.6 - 2017-03-05 + * http://NeXTs.github.com/Clusterize.js/ + * Copyright (c) 2015 Denis Lukov; Licensed GPLv3 */ + +;(function(name, definition) { + if (typeof module != 'undefined') module.exports = definition(); + else if (typeof define == 'function' && typeof define.amd == 'object') define(definition); + else this[name] = definition(); +}('Clusterize', function() { + "use strict" + + // detect ie9 and lower + // https://gist.github.com/padolsey/527683#comment-786682 + var ie = (function(){ + for( var v = 3, + el = document.createElement('b'), + all = el.all || []; + el.innerHTML = '', + all[0]; + ){} + return v > 4 ? v : document.documentMode; + }()), + is_mac = navigator.platform.toLowerCase().indexOf('mac') + 1; + var Clusterize = function(data) { + if( ! (this instanceof Clusterize)) + return new Clusterize(data); + var self = this; + + var defaults = { + rows_in_block: 50, + blocks_in_cluster: 4, + tag: null, + show_no_data_row: true, + no_data_class: 'clusterize-no-data', + no_data_text: 'No data', + keep_parity: true, + callbacks: {} + } + + // public parameters + self.options = {}; + var options = ['rows_in_block', 'blocks_in_cluster', 'show_no_data_row', 'no_data_class', 'no_data_text', 'keep_parity', 'tag', 'callbacks']; + for(var i = 0, option; option = options[i]; i++) { + self.options[option] = typeof data[option] != 'undefined' && data[option] != null + ? data[option] + : defaults[option]; + } + + var elems = ['scroll', 'content']; + for(var i = 0, elem; elem = elems[i]; i++) { + self[elem + '_elem'] = data[elem + 'Id'] + ? document.getElementById(data[elem + 'Id']) + : data[elem + 'Elem']; + if( ! self[elem + '_elem']) + throw new Error("Error! Could not find " + elem + " element"); + } + + // tabindex forces the browser to keep focus on the scrolling list, fixes #11 + if( ! self.content_elem.hasAttribute('tabindex')) + self.content_elem.setAttribute('tabindex', 0); + + // private parameters + var rows = isArray(data.rows) + ? data.rows + : self.fetchMarkup(), + cache = {}, + scroll_top = self.scroll_elem.scrollTop; + + // append initial data + self.insertToDOM(rows, cache); + + // restore the scroll position + self.scroll_elem.scrollTop = scroll_top; + + // adding scroll handler + var last_cluster = false, + scroll_debounce = 0, + pointer_events_set = false, + scrollEv = function() { + // fixes scrolling issue on Mac #3 + if (is_mac) { + if( ! pointer_events_set) self.content_elem.style.pointerEvents = 'none'; + pointer_events_set = true; + clearTimeout(scroll_debounce); + scroll_debounce = setTimeout(function () { + self.content_elem.style.pointerEvents = 'auto'; + pointer_events_set = false; + }, 50); + } + if (last_cluster != (last_cluster = self.getClusterNum())) + self.insertToDOM(rows, cache); + if (self.options.callbacks.scrollingProgress) + self.options.callbacks.scrollingProgress(self.getScrollProgress()); + }, + resize_debounce = 0, + resizeEv = function() { + clearTimeout(resize_debounce); + resize_debounce = setTimeout(self.refresh, 100); + } + on('scroll', self.scroll_elem, scrollEv); + on('resize', window, resizeEv); + + // public methods + self.destroy = function(clean) { + off('scroll', self.scroll_elem, scrollEv); + off('resize', window, resizeEv); + self.html((clean ? self.generateEmptyRow() : rows).join('')); + } + self.refresh = function(force) { + if(self.getRowsHeight(rows) || force) self.update(rows); + } + self.update = function(new_rows) { + rows = isArray(new_rows) + ? new_rows + : []; + var scroll_top = self.scroll_elem.scrollTop; + // fixes #39 + if(rows.length * self.options.item_height < scroll_top) { + self.scroll_elem.scrollTop = 0; + last_cluster = 0; + } + self.insertToDOM(rows, cache); + self.scroll_elem.scrollTop = scroll_top; + } + self.clear = function() { + self.update([]); + } + self.getRowsAmount = function() { + return rows.length; + } + self.getScrollProgress = function() { + return this.options.scroll_top / (rows.length * this.options.item_height) * 100 || 0; + } + + var add = function(where, _new_rows) { + var new_rows = isArray(_new_rows) + ? _new_rows + : []; + if( ! new_rows.length) return; + rows = where == 'append' + ? rows.concat(new_rows) + : new_rows.concat(rows); + self.insertToDOM(rows, cache); + } + self.append = function(rows) { + add('append', rows); + } + self.prepend = function(rows) { + add('prepend', rows); + } + } + + Clusterize.prototype = { + constructor: Clusterize, + // fetch existing markup + fetchMarkup: function() { + var rows = [], rows_nodes = this.getChildNodes(this.content_elem); + while (rows_nodes.length) { + rows.push(rows_nodes.shift().outerHTML); + } + return rows; + }, + // get tag name, content tag name, tag height, calc cluster height + exploreEnvironment: function(rows, cache) { + var opts = this.options; + opts.content_tag = this.content_elem.tagName.toLowerCase(); + if( ! rows.length) return; + if(ie && ie <= 9 && ! opts.tag) opts.tag = rows[0].match(/<([^>\s/]*)/)[1].toLowerCase(); + if(this.content_elem.children.length <= 1) cache.data = this.html(rows[0] + rows[0] + rows[0]); + if( ! opts.tag) opts.tag = this.content_elem.children[0].tagName.toLowerCase(); + this.getRowsHeight(rows); + }, + getRowsHeight: function(rows) { + var opts = this.options, + prev_item_height = opts.item_height; + opts.cluster_height = 0; + if( ! rows.length) return; + var nodes = this.content_elem.children; + var node = nodes[Math.floor(nodes.length / 2)]; + opts.item_height = node.offsetHeight; + // consider table's border-spacing + if(opts.tag == 'tr' && getStyle('borderCollapse', this.content_elem) != 'collapse') + opts.item_height += parseInt(getStyle('borderSpacing', this.content_elem), 10) || 0; + // consider margins (and margins collapsing) + if(opts.tag != 'tr') { + var marginTop = parseInt(getStyle('marginTop', node), 10) || 0; + var marginBottom = parseInt(getStyle('marginBottom', node), 10) || 0; + opts.item_height += Math.max(marginTop, marginBottom); + } + opts.block_height = opts.item_height * opts.rows_in_block; + opts.rows_in_cluster = opts.blocks_in_cluster * opts.rows_in_block; + opts.cluster_height = opts.blocks_in_cluster * opts.block_height; + return prev_item_height != opts.item_height; + }, + // get current cluster number + getClusterNum: function () { + this.options.scroll_top = this.scroll_elem.scrollTop; + return Math.floor(this.options.scroll_top / (this.options.cluster_height - this.options.block_height)) || 0; + }, + // generate empty row if no data provided + generateEmptyRow: function() { + var opts = this.options; + if( ! opts.tag || ! opts.show_no_data_row) return []; + var empty_row = document.createElement(opts.tag), + no_data_content = document.createTextNode(opts.no_data_text), td; + empty_row.className = opts.no_data_class; + if(opts.tag == 'tr') { + td = document.createElement('td'); + // fixes #53 + td.colSpan = 100; + td.appendChild(no_data_content); + } + empty_row.appendChild(td || no_data_content); + return [empty_row.outerHTML]; + }, + // generate cluster for current scroll position + generate: function (rows, cluster_num) { + var opts = this.options, + rows_len = rows.length; + if (rows_len < opts.rows_in_block) { + return { + top_offset: 0, + bottom_offset: 0, + rows_above: 0, + rows: rows_len ? rows : this.generateEmptyRow() + } + } + var items_start = Math.max((opts.rows_in_cluster - opts.rows_in_block) * cluster_num, 0), + items_end = items_start + opts.rows_in_cluster, + top_offset = Math.max(items_start * opts.item_height, 0), + bottom_offset = Math.max((rows_len - items_end) * opts.item_height, 0), + this_cluster_rows = [], + rows_above = items_start; + if(top_offset < 1) { + rows_above++; + } + for (var i = items_start; i < items_end; i++) { + rows[i] && this_cluster_rows.push(rows[i]); + } + return { + top_offset: top_offset, + bottom_offset: bottom_offset, + rows_above: rows_above, + rows: this_cluster_rows + } + }, + renderExtraTag: function(class_name, height) { + var tag = document.createElement(this.options.tag), + clusterize_prefix = 'clusterize-'; + tag.className = [clusterize_prefix + 'extra-row', clusterize_prefix + class_name].join(' '); + height && (tag.style.height = height + 'px'); + return tag.outerHTML; + }, + // if necessary verify data changed and insert to DOM + insertToDOM: function(rows, cache) { + // explore row's height + if( ! this.options.cluster_height) { + this.exploreEnvironment(rows, cache); + } + var data = this.generate(rows, this.getClusterNum()), + this_cluster_rows = data.rows.join(''), + this_cluster_content_changed = this.checkChanges('data', this_cluster_rows, cache), + top_offset_changed = this.checkChanges('top', data.top_offset, cache), + only_bottom_offset_changed = this.checkChanges('bottom', data.bottom_offset, cache), + callbacks = this.options.callbacks, + layout = []; + + if(this_cluster_content_changed || top_offset_changed) { + if(data.top_offset) { + this.options.keep_parity && layout.push(this.renderExtraTag('keep-parity')); + layout.push(this.renderExtraTag('top-space', data.top_offset)); + } + layout.push(this_cluster_rows); + data.bottom_offset && layout.push(this.renderExtraTag('bottom-space', data.bottom_offset)); + callbacks.clusterWillChange && callbacks.clusterWillChange(); + this.html(layout.join('')); + this.options.content_tag == 'ol' && this.content_elem.setAttribute('start', data.rows_above); + callbacks.clusterChanged && callbacks.clusterChanged(); + } else if(only_bottom_offset_changed) { + this.content_elem.lastChild.style.height = data.bottom_offset + 'px'; + } + }, + // unfortunately ie <= 9 does not allow to use innerHTML for table elements, so make a workaround + html: function(data) { + var content_elem = this.content_elem; + if(ie && ie <= 9 && this.options.tag == 'tr') { + var div = document.createElement('div'), last; + div.innerHTML = '' + data + '
'; + while((last = content_elem.lastChild)) { + content_elem.removeChild(last); + } + var rows_nodes = this.getChildNodes(div.firstChild.firstChild); + while (rows_nodes.length) { + content_elem.appendChild(rows_nodes.shift()); + } + } else { + content_elem.innerHTML = data; + } + }, + getChildNodes: function(tag) { + var child_nodes = tag.children, nodes = []; + for (var i = 0, ii = child_nodes.length; i < ii; i++) { + nodes.push(child_nodes[i]); + } + return nodes; + }, + checkChanges: function(type, value, cache) { + var changed = value != cache[type]; + cache[type] = value; + return changed; + } + } + + // support functions + function on(evt, element, fnc) { + return element.addEventListener ? element.addEventListener(evt, fnc, false) : element.attachEvent("on" + evt, fnc); + } + function off(evt, element, fnc) { + return element.removeEventListener ? element.removeEventListener(evt, fnc, false) : element.detachEvent("on" + evt, fnc); + } + function isArray(arr) { + return Object.prototype.toString.call(arr) === '[object Array]'; + } + function getStyle(prop, elem) { + return window.getComputedStyle ? window.getComputedStyle(elem)[prop] : elem.currentStyle[prop]; + } + + return Clusterize; +})); \ No newline at end of file diff --git a/src/base/list/virtuallist.js b/src/base/list/virtuallist.js new file mode 100644 index 0000000000..aca28e1979 --- /dev/null +++ b/src/base/list/virtuallist.js @@ -0,0 +1,43 @@ +/** + * 表示当前对象 + * + * Created by GUY on 2015/9/7. + * @class BI.VirtualList + * @extends BI.Widget + */ +BI.VirtualList = BI.inherit(BI.Widget, { + props: function () { + return { + baseCls: "bi-virtual-list clusterize-scroll", + }; + }, + render: function () { + var self = this, o = this.options; + return { + type: "bi.default", + items: [{ + type: "bi.layout", + ref: function () { + self.contentEl = this; + }, + cls: "clusterize-content" + }] + } + }, + + mounted: function () { + var data = []; + for (var i = 0; i < 10000; i++) { + data.push("
" + i + "
"); + } + new Clusterize({ + rows: data, + scrollElem: this.element[0], + contentElem: this.contentEl.element[0] + }) + }, + + populate: function () { + } +}); +BI.shortcut('bi.virtual_list', BI.VirtualList); \ No newline at end of file diff --git a/src/base/tree/treeview.js b/src/base/tree/treeview.js index 37c66d55a0..0326b38ae7 100644 --- a/src/base/tree/treeview.js +++ b/src/base/tree/treeview.js @@ -443,6 +443,7 @@ BI.TreeView = BI.inherit(BI.Pane, { this.nodes && this.nodes.expandAll(flag); }, + //设置树节点的状态 setValue: function (value, param) { this.setSelectedValue(value); this.checkAll(false); @@ -470,12 +471,6 @@ BI.TreeView = BI.inherit(BI.Pane, { }); }, - getExpandedValue: function () { - if (!this.nodes) { - return null; - } - }, - refresh: function () { this.nodes && this.nodes.refresh(); }, diff --git a/src/less/base/list/clusterize.less b/src/less/base/list/clusterize.less new file mode 100644 index 0000000000..6aafca8652 --- /dev/null +++ b/src/less/base/list/clusterize.less @@ -0,0 +1,36 @@ +/* max-height - the only parameter in this file that needs to be edited. + * Change it to suit your needs. The rest is recommended to leave as is. + */ +.clusterize-scroll{ + overflow: auto; +} + +/** + * Avoid vertical margins for extra tags + * Necessary for correct calculations when rows have nonzero vertical margins + */ +.clusterize-extra-row{ + margin-top: 0 !important; + margin-bottom: 0 !important; +} + +/* By default extra tag .clusterize-keep-parity added to keep parity of rows. + * Useful when used :nth-child(even/odd) + */ +.clusterize-extra-row.clusterize-keep-parity{ + display: none; +} + +/* During initialization clusterize adds tabindex to force the browser to keep focus + * on the scrolling list, see issue #11 + * Outline removes default browser's borders for focused elements. + */ +.clusterize-content{ + outline: 0; +} + +/* Centering message that appears when no data provided + */ +.clusterize-no-data td{ + text-align: center; +} \ No newline at end of file