@qooxdoo/framework
Version:
The JS Framework for Coders
575 lines (484 loc) • 16.9 kB
JavaScript
/* ************************************************************************
qooxdoo - the new era of web development
http://qooxdoo.org
Copyright:
2008 Derrell Lipman
License:
MIT: https://opensource.org/licenses/MIT
See the LICENSE file in the project's top-level directory for details.
Authors:
* Derrell Lipman (derrell)
************************************************************************ */
/**
* Table Row renderer for Progressive.
*/
qx.Class.define("qx.ui.progressive.renderer.table.Row", {
extend: qx.ui.progressive.renderer.Abstract,
/**
* @param columnWidths {qx.ui.progressive.renderer.table.Widths}
* Information that indicates how to resize each of the column widths
*/
construct(columnWidths) {
super();
// Save the column widths
this.__columnWidths = columnWidths;
// Create space to store renderers for each column
this.__renderers = {};
// We need a default cell renderer to use if none is specified
this.__defaultCellRenderer =
new qx.ui.progressive.renderer.table.cell.Default();
// We don't yet know who our Progressive will be
this.__progressive = null;
this.__colors = {};
this.__linkColors();
// This layout is not connected to a widget but to this class. This class
// must implement the method "getLayoutChildren", which must return all
// columns (LayoutItems) which should be recalculated. The call
// "layout.renderLayout" will call the method "renderLayout" on each
// column data object The advantage of the use of the normal layout
// manager is that the semantics of flex and percent are exactly the same
// as in the widget code.
this.__layout = new qx.ui.layout.HBox();
this.__layout.connectToWidget(this);
// dynamic theme switch
if (qx.core.Environment.get("qx.dyntheme")) {
qx.theme.manager.Meta.getInstance().addListener(
"changeTheme",
this.__linkColors,
this
);
}
},
statics: {
/**
* Storage for each progressive.
*
* @internal
*/
__clazz: null,
/**
* Default row padding.
*
* @internal
*/
__padding: 6, // modify padding parameter below too if this changes
/**
* Default style sheet for table cells.
*
* @internal
*/
__tableCellStyleSheet:
" position: absolute;" +
" top: 0px;" +
" height: 100%;" +
" overflow:hidden;" +
(qx.core.Environment.get("css.textoverflow")
? qx.bom.Style.getCssName(qx.core.Environment.get("css.textoverflow")) +
":ellipsis;"
: "") +
" white-space:nowrap;" +
" border-right:1px solid #f2f2f2;" +
" border-bottom:1px solid #eeeeee;" +
" padding : 0px 6px 0px 6px;" +
" cursor:default;" +
" font-size: 11px;" +
" font-family: 'Segoe UI', Corbel, Calibri, Tahoma, 'Lucida Sans Unicode', sans-serif;" +
(qx.core.Environment.get("css.userselect")
? qx.bom.Style.getCssName(qx.core.Environment.get("css.userselect")) +
":" +
qx.core.Environment.get("css.userselect.none") +
";"
: "")
},
properties: {
/** The default height of a row, if not altered by a cell renderer. */
defaultRowHeight: {
init: 16
},
/**
* Use a table-specific alternating row color instead of
* row-renderer-instance-specific
*/
alternateBgColorsTableGlobal: {
init: false
}
},
members: {
__progressive: null,
__name: null,
__hash: null,
__columnWidths: null,
__renderers: null,
__defaultCellRenderer: null,
__colors: null,
__layout: null,
/**
* Helper to link the theme colors to the current class
*/
__linkColors() {
// link to color theme
var colorMgr = qx.theme.manager.Color.getInstance();
this.__colors.bgcol = [];
this.__colors.bgcol[0] = colorMgr.resolve(
"progressive-table-row-background-even"
);
this.__colors.bgcol[1] = colorMgr.resolve(
"progressive-table-row-background-odd"
);
},
// overridden
join(progressive, name) {
// Are we already joined?
if (this.__progressive) {
// Yup. Let 'em know they can't do that.
throw new Error("Renderer is already joined to a Progressive.");
}
// Save the Progressive to which we're joined
this.__progressive = progressive;
// Save the name that Progressive knows us by
this.__name = name;
// If we haven't created style sheets for this table yet...
var tr = qx.ui.progressive.renderer.table.Row;
if (!tr.__clazz) {
tr.__clazz = {};
}
var hash = progressive.toHashCode();
this.__hash = hash;
if (!tr.__clazz[hash]) {
// ... then do it now.
tr.__clazz[hash] = {
rowstylesheet: null,
cellstylesheet: []
};
var stylesheet =
".qx-progressive-" + hash + "-row {" + " width : 100%;" + "}";
tr.__clazz[hash].rowstylesheet =
qx.bom.Stylesheet.createElement(stylesheet);
var columnData = this.__columnWidths.getData();
for (var i = 0; i < columnData.length; i++) {
var stylesheet =
".qx-progressive-" +
hash +
"-col-" +
i +
" {" +
tr.__tableCellStyleSheet +
"}";
tr.__clazz[hash].cellstylesheet[i] =
qx.bom.Stylesheet.createElement(stylesheet);
}
// Arrange to be called when the window appears or is resized, so we
// can set each style sheet's left and width field appropriately.
var pane = progressive.getStructure().getPane();
pane.addListener("resize", this._resizeColumns, this);
}
},
/**
* Add a cell renderer for use within a row rendered by this row
* renderer.
*
* @param column {Integer}
* The column number for which the cell renderer applies
*
* @param renderer {qx.ui.progressive.renderer.table.cell.Abstract}
* The cell renderer for the specified column.
*
*/
addRenderer(column, renderer) {
var columnData = this.__columnWidths.getData();
if (column < 0 || column >= columnData.length) {
throw new Error(
"Column " +
column +
" out of range (max: " +
(columnData.length - 1) +
")"
);
}
this.__renderers[column] = renderer;
},
/**
* Remove a cell renderer previously added with {@link #addRenderer}.
*
* @param column {Integer}
* The column for which the cell renderer is to be removed.
*
*/
removeRenderer(column) {
var columnData = this.__columnWidths.getData();
if (column < 0 || column >= columnData.length) {
throw new Error(
"Column " +
column +
" out of range (max: " +
(columnData.length - 1) +
")"
);
}
if (!this.__renderers[column]) {
throw new Error("No existing renderer for column " + column);
}
delete this.__renderers[column];
},
// overridden
render(state, element) {
var data = element.data;
var html = [];
var cellInfo;
var renderer;
var height = 0;
// Initialize row counter, if necessary. We'll use this for shading
// alternate rows.
if (state.getRendererData()[this.__name].end === undefined) {
state.getRendererData()[this.__name] = {
end: 0,
start: 1,
rows: 0,
totalHeight: 0
};
}
// Create the div for this row
var div = document.createElement("div");
// For each cell...
for (var i = 0; i < data.length; i++) {
var stylesheet = "qx-progressive-" + this.__hash + "-col-" + i;
// Determine what renderer to use for this column
renderer = this.__renderers[i] || this.__defaultCellRenderer;
// Specify information that cell renderer will need
cellInfo = {
state: state,
rowDiv: div,
stylesheet: stylesheet,
element: element,
dataIndex: i,
cellData: data[i],
height: height,
rowRenderer: this // useful, e.g., for getting default row height
};
// Render this cell
html.push(renderer.render(cellInfo));
// If this cell's height was greater than our current maximum...
if (cellInfo.height > height) {
// ... then it becomes our row height
height = cellInfo.height;
}
}
height = height > 0 ? height : this.getDefaultRowHeight();
// Get a reference to our renderer data
var rendererData = state.getRendererData()[this.__name];
// Track total height so we can determine if there's a vertical scrollbar
rendererData.totalHeight += height;
// Set properties for the row div
div.style.position = "relative";
div.style.height = height + "px";
div.className = "qx-progressive-" + this.__hash + "-row";
div.innerHTML = html.join("");
// Add this row to the table
switch (element.location) {
case "end":
// Determine color of row based on state of last added row
var index;
if (this.getAlternateBgColorsTableGlobal()) {
index = state.getUserData().tableRowBgEnd || 0;
} else {
index = rendererData.end;
}
// Set the background color of this row
div.style.backgroundColor = this.__colors.bgcol[index];
// Update state for next time
rendererData.end = index == 0 ? 1 : 0;
state.getUserData().tableRowBgEnd = rendererData.end;
// Append our new row to the pane.
state.getPane().getContentElement().getDomElement().appendChild(div);
break;
case "start":
// Get the pane element
var elem = state.getPane().getContentElement().getDomElement();
// Get its children array
var children = elem.childNodes;
// Are there any children?
if (children.length > 0) {
// Yup. Determine color of row based on state of last added row
var index;
if (this.getAlternateBgColorsTableGlobal()) {
index = state.getUserData().tableRowBgStart == 0 ? 1 : 0; // == 0 includes undefined
} else {
index = rendererData.start == 0 ? 1 : 0;
}
// Set the background color of this row
div.style.backgroundColor = this.__colors.bgcol[index];
// Update state for next time
rendererData.start = index;
state.getUserData().tableRowBgStart = rendererData.start;
// Insert our new row before the first child.
elem.insertBefore(div, children[0]);
break;
} else {
// No children yet.
// Set the background color of this row
div.style.backgroundColor = this.__colors.bgcol[0];
// Update state for next time
rendererData.start = 0;
state.getUserData().tableRowBgStart = rendererData.start;
elem.appendChild(div);
}
break;
default:
throw new Error("Invalid location: " + element.location);
}
// Increment row count
++rendererData.rows;
},
/**
* This method is required by the box layout. If returns an array of items
* to relayout.
* @return {Array} Array of column data.
*/
getLayoutChildren() {
return this.__columnWidths.getData();
},
/**
* Event handler for the "resize" event. We recalculate the
* widths of each of the columns, and modify the stylesheet rule
* applicable to each column, to apply the new widths.
*
* @param e {qx.event.type.Event}
* Ignored
*
*/
_resizeColumns(e) {
var pane = this.__progressive.getStructure().getPane();
var width =
pane.getBounds().width - qx.bom.element.Scroll.getScrollbarWidth();
// Get the style sheet rule name for this row
var stylesheet = ".qx-progressive-" + this.__hash + "-row";
// Remove the style rule for this row
var tr = qx.ui.progressive.renderer.table.Row;
qx.bom.Stylesheet.removeRule(
tr.__clazz[this.__hash].rowstylesheet,
stylesheet
);
// Create the new rule for this row
var rule = "width: " + width + "px;";
// Apply the new rule
qx.bom.Stylesheet.addRule(
tr.__clazz[this.__hash].rowstylesheet,
stylesheet,
rule
);
// Compute the column widths
this.__layout.renderLayout(width, 100, {
top: 0,
right: 0,
bottom: 0,
left: 0
});
// Get the column data
var columnData = this.__columnWidths.getData();
// Reset each of the column style sheets to deal with width changes
for (var i = 0, left = 0; i < columnData.length; i++, left += width) {
// Get the style sheet rule name for this cell
var stylesheet = ".qx-progressive-" + this.__hash + "-col-" + i;
// Remove the style rule for this column
var tr = qx.ui.progressive.renderer.table.Row;
qx.bom.Stylesheet.removeRule(
tr.__clazz[this.__hash].cellstylesheet[i],
stylesheet
);
// Get this column width.
width = columnData[i].getComputedWidth();
if (qx.core.Environment.get("qx.debug")) {
if (qx.core.Environment.get("qx.tableResizeDebug")) {
this.debug("col " + i + ": width=" + width);
}
}
// Make our width calculations box-model independent
var inset;
if (qx.core.Environment.get("css.boxmodel") == "content") {
inset = qx.ui.progressive.renderer.table.Row.__padding * 2;
} else {
inset = -1;
}
// Create the new rule, based on calculated widths
var widthRule = width - inset + "px;";
var paddingRule =
"0px " +
qx.ui.progressive.renderer.table.Row.__padding +
"px " +
"0px " +
qx.ui.progressive.renderer.table.Row.__padding +
"px;";
var leftRule = left + "px;";
var rule =
tr.__tableCellStyleSheet +
"width: " +
widthRule +
"left: " +
leftRule +
"padding: " +
paddingRule;
// Apply the new rule
qx.bom.Stylesheet.addRule(
tr.__clazz[this.__hash].cellstylesheet[i],
stylesheet,
rule
);
}
}
},
destruct() {
// remove dynamic theme listener
qx.theme.manager.Meta.getInstance().removeListener(
"changeTheme",
this.__linkColors,
this
);
var name;
for (name in this.__renderers) {
this.__renderers[name] = null;
}
// Remove any style sheets that we had added
var tr = qx.ui.progressive.renderer.table.Row;
var hash = this.__progressive.toHashCode();
if (tr.__clazz && tr.__clazz[hash]) {
// Remove the row stylesheet
if (tr.__clazz[hash].rowstylesheet) {
// Get the style sheet rule name for this row
var stylesheet = ".qx-progressive-" + this.__hash + "-row";
// Remove the style rule for this row
var tr = qx.ui.progressive.renderer.table.Row;
qx.bom.Stylesheet.removeRule(
tr.__clazz[this.__hash].rowstylesheet,
stylesheet
);
}
// Remove each of the column style sheets
if (tr.__clazz[hash].cellstylesheet) {
for (var i = tr.__clazz[hash].cellstylesheet.length - 1; i >= 0; i--) {
// Get the style sheet rule name for this cell
var stylesheet = ".qx-progressive-" + this.__hash + "-col-" + i;
var rule = tr.__clazz[this.__hash].cellstylesheet[i];
// Remove the style rule for this column
var tr = qx.ui.progressive.renderer.table.Row;
qx.bom.Stylesheet.removeRule(rule, stylesheet);
}
}
}
if (this.__progressive && this.__progressive.getRendererData) {
var rendererData = this.__progressive.getRendererData();
if (
rendererData &&
rendererData[this.__name] &&
rendererData[this.__name].end !== undefined
) {
rendererData[this.__name] = null;
}
}
this.__colors =
this.__renderers =
this.__progressive =
this.__columnWidths =
null;
this._disposeObjects("__layout", "__defaultCellRenderer", "__columnData");
}
});