Skip to content

Instantly share code, notes, and snippets.

@johnspackman
Created August 14, 2019 10:42
Show Gist options
  • Save johnspackman/42e7d5e746ad69ad346c7f1c22c1c913 to your computer and use it in GitHub Desktop.
Save johnspackman/42e7d5e746ad69ad346c7f1c22c1c913 to your computer and use it in GitHub Desktop.
/**
* Header cell widget for filtering
*
* @childControl label {qx.ui.basic.Label} label of the header cell
* @childControl icon {qx.ui.basic.Image} icon of the header cell
*
* @asset(grasshopper/decoration/table/16/*)
* @asset(grasshopper/icon/16/tick.png)
* @asset(grasshopper/icon/16/cross.png)
*/
qx.Class.define("grasshopper.af.table.filter.FilterHeaderCell", {
extend: qx.ui.container.Composite,
construct: function() {
this.base(arguments);
var layout = new qx.ui.layout.Canvas();
this.setLayout(layout);
this._add(this.getChildControl("label"), { top: 0, left: 0, right: 0 });
this._add(this.getChildControl("filter-icon"), { bottom: 0, right: 0 });
},
properties: {
appearance: {
refine: true,
init: "table-filter-header-cell"
},
/** header cell label */
label: {
check: "String",
init: null,
nullable: true,
apply: "_applyLabel"
},
cellInfo: {
nullable: false,
apply: "_applyCellInfo"
},
filtered: {
check: "Boolean",
init: false,
nullable: false,
apply: "_applyFiltered"
},
sorted: {
check: [ "asc", "desc" ],
init: null,
nullable: true,
apply: "_applySorted"
},
dataType: {
check: [ "string", "number", "date" ],
init: null,
nullable: true
},
caseInsensitive: {
check: "Boolean",
init: true,
nullable: false
},
searchable: {
check: "Boolean",
init: true,
nullable: false
},
dateFormat: {
check: "qx.util.format.DateFormat",
init: null,
nullable: true
}
},
members: {
_applyCellInfo: function(cellInfo) {
var label = (cellInfo.name && cellInfo.name.translate ? cellInfo.name.translate() : cellInfo.name)||"";
this.setLabel(label.replace(/\n/g, "<br>"));
this.setSorted(cellInfo.sorted ? (cellInfo.sortedAscending ? "asc" : "desc") : null);
},
// property apply
_applyLabel: function(value, old) {
if (value) {
this._showChildControl("label").setValue(value);
} else {
this._excludeChildControl("label");
}
},
_applyFiltered: function(value, oldValue) {
if (value)
this.addState("filtered");
else
this.removeState("filtered");
this.__updateIcon();
},
_applySorted: function(value, oldValue) {
if (!value) {
this.removeState("sorted");
this.removeState("sortedAsc");
this.removeState("sortedDesc");
} else if (value == "asc") {
this.addState("sorted");
this.removeState("sortedDesc");
this.addState("sortedAsc");
} else {
this.addState("sorted");
this.removeState("sortedAsc");
this.addState("sortedDesc");
}
this.__updateIcon();
},
__updateIcon: function() {
var filtered = this.getFiltered();
var sorted = this.getSorted();
var str = filtered ? "filtered" : "unfiltered";
str += "-";
str += !sorted ? "unsorted" : sorted;
this.getChildControl("filter-icon").setIcon("grasshopper/decoration/table/16/" + str + ".png");
},
open: function() {
this.getChildControl("search").setValue("");
var popup = this.getChildControl("popup");
popup.placeToWidget(this, true);
popup.show();
this.addState("open");
},
close: function() {
var popup = this.getChildControl("popup");
popup.hide();
},
toggle: function() {
if (this.getChildControl("popup").isVisible())
this.close();
else
this.open();
},
_onPopupHide: function() {
this.removeState("open");
},
_onFilterClick: function() {
if (this.getChildControl("popup").isVisible()) {
this.close();
return;
}
var lst = this.getChildControl("list");
var cellInfo = this.getCellInfo();
var tm = cellInfo.table.getTableModel();
function createLookup(rows) {
var lookup = {};
rows.forEach(row => {
var cell = row[cellInfo.col];
var value = cell.filter();
if (lookup[value] === undefined)
lookup[value] = cell.text();
});
return lookup;
}
var lookup = createLookup(tm.getUnfiltered());
var lookupArray = Object.keys(lookup);
if (lookupArray.length > 2000)
lookupArray.splice(2000, lookupArray.length - 2000);
lookupArray.sort((l, r) => {
l = l.toLowerCase();
r = r.toLowerCase();
return l < r ? -1 : l > r ? 1 : 0;
});
var filteredLookup = createLookup(tm.getData());
var pool = tm.getCheckboxPool();
var children = lst.getChildren();
lookupArray.forEach((key, index) => {
var cbx = null;
if (index < children.length)
cbx = children[index];
else {
cbx = pool.getObject(qx.ui.form.CheckBox);
if (cbx == null)
cbx = new qx.ui.form.CheckBox(key);
lst.add(cbx);
}
cbx.set({
label: key,
value: !!filteredLookup[key]
});
});
var len = lookupArray.length;
while (children.length > len) {
var item = children[children.length - 1];
lst.remove(item);
pool.poolObject(item);
}
this.open();
},
_onSearch: function(str) {
str = (str||"").toLowerCase().trim();
var lst = this.getChildControl("list");
qx.lang.Array.clone(lst.getChildren()).forEach(item => {
if (item.getLabel().toLowerCase().indexOf(str) > -1)
item.setVisibility("visible");
else
item.setVisibility("excluded");
});
},
onFilter: function(row) {
if (this.__lookupFilter === null)
return true;
var cellInfo = this.getCellInfo();
var cell = row[cellInfo.col];
var value = cell.filter();
return !!this.__lookupFilter[value];
},
__lookupFilter: null,
_onApplyFilter: function() {
this.getChildControl("popup").hide();
var lst = this.getChildControl("list");
var cellInfo = this.getCellInfo();
var lookup = this.__lookupFilter = {};
var hasUnchecked = false;
lst.getChildren().forEach(item => {
if (item.getValue())
lookup[item.getLabel()] = true;
else
hasUnchecked = true;
});
if (!hasUnchecked)
this.__lookupFilter = null;
this.setFiltered(hasUnchecked);
cellInfo.table.getTableModel().updateFilters();
},
_onCancelFilter: function() {
this.getChildControl("popup").hide();
},
_onSelectAll: function() {
var lst = this.getChildControl("list");
lst.getChildren().forEach(item => {
item.setValue(true);
});
},
_onSelectNone: function() {
var lst = this.getChildControl("list");
lst.getChildren().forEach(item => {
item.setValue(false);
});
},
comparatorAscending: function(l, r, lrow, rrow, i) {
function get(cell) {
var value = cell.sortValue !== undefined ? cell.sortValue : cell.value;
if (typeof value == "function")
value = value.call(cell);
return value;
}
l = get(l);
r = get(r);
if (this.getDataType() === "string" && this.isCaseInsensitive()) {
l = l.toLowerCase();
r = r.toLowerCase();
}
return l < r ? -1 : l > r ? 1 : 0;
},
comparatorDescending: function(l, r, lrow, rrow, i) {
return this.comparatorAscending(l, r, i) * -1;
},
// overridden
_createChildControlImpl: function(id, hash) {
switch (id) {
case "label":
var control = new qx.ui.basic.Label(this.getLabel()).set({
anonymous: true,
allowShrinkX: true,
rich: true
});
return control;
case "popup":
var pop = new qx.ui.popup.Popup(new qx.ui.layout.VBox()).set({
autoHide: false,
keepActive: true
});
pop.add(this.getChildControl("toolbar"));
pop.add(this.getChildControl("search"));
pop.add(this.getChildControl("listScroll"), { flex: 1 });
pop.addListener("close", this._onPopupHide, this);
return pop;
case "toolbar":
var tb = new qx.ui.toolbar.ToolBar();
tb.add(this.getChildControl("btnApply"));
tb.add(this.getChildControl("btnCancel"));
tb.add(this.getChildControl("btnSelectAll"));
tb.add(this.getChildControl("btnSelectNone"));
return tb;
case "btnApply":
var btn = new qx.ui.toolbar.Button("Apply", "grasshopper/icon/16/tick.png").set({ appearance: "toolbar-slim-button" });
btn.addListener("execute", () => this._onApplyFilter());
return btn;
case "btnCancel":
var btn = new qx.ui.toolbar.Button("Cancel", "grasshopper/icon/16/cross.png").set({ appearance: "toolbar-slim-button" });
btn.addListener("execute", () => this._onCancelFilter());
return btn;
case "btnSelectAll":
var btn = new qx.ui.toolbar.Button("All", "grasshopper/icon/16/select-all.png").set({ appearance: "toolbar-slim-button" });
btn.addListener("execute", () => this._onSelectAll());
return btn;
case "btnSelectNone":
var btn = new qx.ui.toolbar.Button("None", "grasshopper/icon/16/select-none.png").set({ appearance: "toolbar-slim-button" });
btn.addListener("execute", () => this._onSelectNone());
return btn;
case "search":
var edt = new qx.ui.form.TextField().set({ placeholder: "Search", liveUpdate: true });
edt.addListener("changeValue", evt => this._onSearch(evt.getData()));
return edt;
case "listScroll":
return new qx.ui.container.Scroll(this.getChildControl("list")).set({ maxHeight: 300 });
case "list":
var comp = new qx.ui.container.Composite(new qx.ui.layout.VBox());
return comp;
case "filter-icon":
var control = new qx.ui.form.Button("Filter", "grasshopper/icon/16/table-column-unfiltered.png").set({
show: "icon"
});
control.addListener("execute", () => this._onFilterClick());
return control;
}
return this.base(arguments, id);
}
}
});
qx.Class.define("grasshopper.af.table.filter.FilterHeaderRenderer", {
extend: qx.core.Object,
implement: qx.ui.table.IHeaderRenderer,
/*
* ****************************************************************************
* MEMBERS
* ****************************************************************************
*/
properties: {
dataType: {
check: [ "string", "number", "date" ],
init: null,
nullable: true
},
caseInsensitive: {
check: "Boolean",
init: true,
nullable: false
},
searchable: {
check: "Boolean",
init: true,
nullable: false
},
dateFormat: {
check: "qx.util.format.DateFormat",
init: null,
nullable: true
}
},
members: {
// overridden
createHeaderCell: function(cellInfo) {
var widget = new grasshopper.af.table.filter.FilterHeaderCell();
this.updateHeaderCell(cellInfo, widget);
return widget;
},
// overridden
updateHeaderCell: function(cellInfo, cellWidget) {
cellWidget.setCellInfo(qx.lang.Object.clone(cellInfo));
cellWidget.set({
dataType: this.getDataType(),
caseInsensitive: this.getCaseInsensitive(),
searchable: this.getSearchable(),
dateFormat: this.getDateFormat()
});
},
getCellContentHtml: function(cellInfo) {
var cell = cellInfo.value;
if (!cell)
return "";
if (this.getDataType() == "date") {
var dt = cell.value();
var DF = this.getDateFormat() || qx.util.format.DateFormat.getDateInstance();
return dt ? DF.format(dt) : "";
}
return cell.text(cellInfo);
}
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment