-
-
Notifications
You must be signed in to change notification settings - Fork 261
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
adds support for checked list and select box, with controller
- Loading branch information
1 parent
4f1dc70
commit 8d1566d
Showing
16 changed files
with
1,230 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,348 @@ | ||
/* ************************************************************************ | ||
qooxdoo - the new era of web development | ||
http://qooxdoo.org | ||
Copyright: | ||
2021-2021 Zenesis Limited https://www.zenesis.com | ||
License: | ||
MIT: https://opensource.org/licenses/MIT | ||
See the LICENSE file in the project's top-level directory for details. | ||
Authors: | ||
* John Spackman (github.com/johnspackman) | ||
************************************************************************ */ | ||
|
||
/** | ||
* Extension of `qx.data.controller.List` which adds support for `qx.ui.form.CheckedList` | ||
* and `qx.ui.form.CheckedSelectBox`. | ||
* | ||
* The principal is that the underlying `List` controller implementation has a model which | ||
* is the complete array of items that can be selected, and that array is used to populate | ||
* the UI widget (ie as normal). | ||
* | ||
* The `checked` psuedo property in this `CheckedList` controller relates to the checked | ||
* property of the UI widget. | ||
*/ | ||
qx.Class.define("qx.data.controller.CheckedList", { | ||
extend: qx.data.controller.List, | ||
|
||
/** | ||
* Constructor | ||
* | ||
* @param model {qx.data.Array?null} the model array | ||
* @param widget {qx.ui.core.Widget?null} the widget target | ||
* @param path {String} the path in the model for the caption | ||
*/ | ||
construct(model, widget, path) { | ||
this.base(arguments, null, widget, path); | ||
this.setChecked(new qx.data.Array()); | ||
if (model) | ||
this.setModel(model); | ||
}, | ||
|
||
properties: { | ||
checked: { | ||
init: null, | ||
nullable: true, | ||
check: "qx.data.Array", | ||
event: "changeChecked", | ||
apply: "_applyChecked" | ||
}, | ||
|
||
/** | ||
* The path to the property which holds the information that should be | ||
* shown as a label for a tag for a checked item. This is only needed if | ||
* used with a CheckedSelectBox, and only if live updates of the label | ||
* are required. | ||
*/ | ||
checkedLabelPath: { | ||
check: "String", | ||
apply: "__updateTags", | ||
nullable: true | ||
}, | ||
|
||
|
||
/** | ||
* The path to the property which holds the information that should be | ||
* shown as an icon for a tag for a checked item. This is only needed if | ||
* used with a CheckedSelectBox, and only if live updates of the label | ||
* are required. | ||
*/ | ||
checkedIconPath: { | ||
check: "String", | ||
apply: "__updateTags", | ||
nullable: true | ||
}, | ||
|
||
|
||
/** | ||
* A map containing the options for the checkedLabel binding. The possible keys | ||
* can be found in the {@link qx.data.SingleValueBinding} documentation. | ||
*/ | ||
checkedLabelOptions: { | ||
apply: "__updateTags", | ||
nullable: true | ||
}, | ||
|
||
|
||
/** | ||
* A map containing the options for the checked icon binding. The possible keys | ||
* can be found in the {@link qx.data.SingleValueBinding} documentation. | ||
*/ | ||
checkedIconOptions: { | ||
apply: "__updateTags", | ||
nullable: true | ||
} | ||
}, | ||
|
||
members: { | ||
_applyChecked(value, oldValue) { | ||
if (oldValue) | ||
oldValue.removeListener("change", this.__onCheckedChange, this); | ||
if (value) | ||
value.addListener("change", this.__onCheckedChange, this); | ||
this._updateChecked(); | ||
}, | ||
|
||
/** | ||
* @Override | ||
*/ | ||
_createItem() { | ||
var delegate = this.getDelegate(); | ||
var item; | ||
|
||
// check if a delegate and a create method is set | ||
if (delegate != null && delegate.createItem != null) { | ||
item = delegate.createItem(); | ||
} else { | ||
item = new qx.ui.form.CheckBox(); | ||
} | ||
|
||
// if there is a configure method, invoke it | ||
if (delegate != null && delegate.configureItem != null) { | ||
delegate.configureItem(item); | ||
} | ||
|
||
return item; | ||
}, | ||
|
||
/** | ||
* Event handler for changes to the checked array | ||
* | ||
* @param evt {qx.event.type.Data} the event | ||
*/ | ||
__onCheckedChange(evt) { | ||
let data = evt.getData(); | ||
if (data.type == "order") | ||
return; | ||
this._updateChecked(); | ||
}, | ||
|
||
/** | ||
* @Override | ||
*/ | ||
update() { | ||
this.base(arguments); | ||
this._updateChecked(); | ||
}, | ||
|
||
/** | ||
* @Override | ||
*/ | ||
_setFilter(value, old) { | ||
this.base(arguments, value, old); | ||
this.__syncModelChecked = true; | ||
qx.ui.core.queue.Widget.add(this); | ||
}, | ||
|
||
/** | ||
* @Override | ||
*/ | ||
syncWidget() { | ||
this.base(arguments); | ||
if (this.__syncModelChecked) { | ||
this._updateChecked(); | ||
} | ||
this.__syncModelChecked = null; | ||
}, | ||
|
||
/** | ||
* @Override | ||
*/ | ||
_applyModel(value, oldValue) { | ||
if (!value || !value.getLength()) { | ||
let checked = this.getChecked(); | ||
if (checked) | ||
checked.removeAll(); | ||
} | ||
this.base(arguments, value, oldValue); | ||
this._updateChecked(); | ||
}, | ||
|
||
/** | ||
* @Override | ||
*/ | ||
_applyTarget(value, oldValue) { | ||
this.base(arguments, value, oldValue); | ||
if (oldValue) { | ||
oldValue.removeListener("changeChecked", this.__onTargetCheckedChange, this); | ||
if (qx.Class.supportsEvent(oldValue.constructor, "attachResultsTag")) { | ||
oldValue.removeListener("attachResultsTag", this.__onTargetAttachResultsTag, this); | ||
oldValue.removeListener("detachResultsTag", this.__onTargetDetachResultsTag, this); | ||
} | ||
} | ||
if (value) { | ||
value.addListener("changeChecked", this.__onTargetCheckedChange, this); | ||
if (qx.Class.supportsEvent(value.constructor, "attachResultsTag")) { | ||
value.addListener("attachResultsTag", this.__onTargetAttachResultsTag, this); | ||
value.addListener("detachResultsTag", this.__onTargetDetachResultsTag, this); | ||
} | ||
} | ||
}, | ||
|
||
/** | ||
* Event handler for changes in the target widget's `checked` property | ||
*/ | ||
__onTargetCheckedChange(evt) { | ||
if (this.__inUpdateChecked) | ||
return; | ||
let target = this.getTarget(); | ||
let replacement = []; | ||
target.getChecked().forEach(item => { | ||
let itemModel = item.getModel(); | ||
if (itemModel) | ||
replacement.push(itemModel); | ||
}); | ||
let checked = this.getChecked(); | ||
if (checked) | ||
checked.replace(replacement); | ||
}, | ||
|
||
/** | ||
* Event handler for changes in the target widget's `attachResults` property | ||
*/ | ||
__onTargetAttachResultsTag(evt) { | ||
let { tagWidget, item } = evt.getData(); | ||
item.setUserData(this.classname + ".tagWidget", tagWidget); | ||
this.__attachTag(tagWidget, item); | ||
}, | ||
|
||
/** | ||
* Event handler for changes in the target widget's `detachResults` property | ||
*/ | ||
__onTargetDetachResultsTag(evt) { | ||
let { tagWidget, item } = evt.getData(); | ||
this.__detachTag(tagWidget, item); | ||
item.setUserData(this.classname + ".tagWidget", null); | ||
}, | ||
|
||
/** | ||
* Updates all tags in the target widget | ||
*/ | ||
__updateTags() { | ||
let target = this.getTarget(); | ||
if (!target) { | ||
return; | ||
} | ||
target.getChecked().forEach(item => { | ||
let tagWidget = item.getUserData(this.classname + ".tagWidget"); | ||
this.__detachTag(tagWidget, item); | ||
this.__attachTag(tagWidget, item); | ||
}); | ||
}, | ||
|
||
/** | ||
* Attaches a single tag; used to bind to the tag so that live updates to the underlying model are reflected in tag names | ||
* | ||
* @param tagWidget {qx.ui.core.Widget} the widget which is the tag | ||
* @param item {qx.ui.core.Widget} the list item that lists the model item that this tag is for | ||
*/ | ||
__attachTag(tagWidget, item) { | ||
let itemModel = item.getModel(); | ||
let bindData = { }; | ||
if (this.getCheckedLabelPath()) { | ||
bindData.checkedLabelId = itemModel.bind(this.getCheckedLabelPath(), tagWidget, "label", this.getCheckedLabelOptions()); | ||
} | ||
if (this.getCheckedIconPath()) { | ||
bindData.checkedIconId = itemModel.bind(this.getCheckedIconPath(), tagWidget, "label", this.getCheckedIconOptions()); | ||
} | ||
itemModel.setUserData(this.classname + ".bindData", bindData); | ||
}, | ||
|
||
/** | ||
* Detaches a single tag, inverse of `__attachTag` | ||
* | ||
* @param tagWidget {qx.ui.core.Widget} the widget which is the tag | ||
* @param item {qx.ui.core.Widget} the list item that lists the model item that this tag is for | ||
*/ | ||
__detachTag(tagWidget, item) { | ||
let itemModel = item.getModel(); | ||
let bindData = itemModel.getUserData(this.classname + ".bindData"); | ||
if (bindData) { | ||
if (bindData.checkedLabelId) { | ||
itemModel.removeBinding(bindData.checkedLabelId); | ||
} | ||
if (bindData.checkedIconId) { | ||
itemModel.removeBinding(bindData.checkedIconId); | ||
} | ||
itemModel.setUserData(this.classname + ".bindData", null); | ||
} | ||
}, | ||
|
||
/** | ||
* Updates the checked widget items to match the array of checked model items | ||
*/ | ||
_updateChecked() { | ||
let target = this.getTarget(); | ||
if (!target) { | ||
return; | ||
} | ||
|
||
if (this.__inUpdateChecked) { | ||
return; | ||
} | ||
this.__inUpdateChecked = true; | ||
try { | ||
// Maps of the widget item, indexed by the hashcode of the model item | ||
let children = {}; | ||
let toUncheck = {}; | ||
|
||
target.getChildren().forEach(item => { | ||
let itemModel = item.getModel(); | ||
if (itemModel) { | ||
let hash = itemModel.toHashCode(); | ||
children[hash] = item; | ||
if (item.getValue()) | ||
toUncheck[hash] = item; | ||
} | ||
}); | ||
|
||
let toRemove = []; | ||
let checked = this.getChecked(); | ||
if (checked) { | ||
checked.forEach(itemModel => { | ||
let hash = itemModel.toHashCode(); | ||
if (itemModel) { | ||
delete toUncheck[hash]; | ||
if (children[hash]) { | ||
children[hash].setValue(true); | ||
} else { | ||
toRemove.push(itemModel); | ||
} | ||
} | ||
}); | ||
Object.values(toUncheck).forEach(item => item.setValue(false)); | ||
toRemove.forEach(item => checked.remove(item)); | ||
} | ||
} finally { | ||
this.__inUpdateChecked = false; | ||
} | ||
} | ||
|
||
|
||
} | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.