Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

checkbox plugin #2534

Open
wants to merge 5 commits into
base: v.4.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions dist/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,15 @@
}
return data;
}
var instance = new jsTree({}, document.getElementById('tree'));
var instance = new jsTree({
plugins:{
checkbox:jsTree.plugin.checkbox
}
// , renderer : 'scroller'
}, document.getElementById('tree'));
instance
.empty()
.create(dummy(2, 10))
.create(dummy(3, 4))
.openAll();
console.log(c);

Expand Down
23 changes: 22 additions & 1 deletion dist/jstree.css
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,25 @@
.jstree-node-icon:before { content: " "; width:60%; height:45%; left:20%; top:32%; background:navy; position:absolute; border-radius:0 1px 1px 1px; }
.jstree-node-icon:after { content: " "; width:30%; height:10%; left:20%; top:22%; background:navy; position:absolute; border-radius:1px 1px 0 0; }
.jstree-selected .jstree-node-icon:after,
.jstree-selected .jstree-node-icon:before { background:white; }
.jstree-selected .jstree-node-icon:before { background:white; }
.jstree-icon-checkbox {
text-align : center;
display : inline-block;
width : var(--jstree-size);
height : var(--jstree-size);
line-height : var(--jstree-size);
position : relative;
vertical-align : middle;
}

.jstree-icon-checkbox-checked {
background-image: url("data:image/svg+xml,%3Csvg width='32' height='32' xmlns='http://www.w3.org/2000/svg'%3E%3Cdefs%3E%3Cfilter id='svg_3_blur'%3E%3CfeGaussianBlur stdDeviation='0.5' in='SourceGraphic'/%3E%3C/filter%3E%3C/defs%3E%3Cg%3E%3Crect rx='4' id='svg_1' height='22' width='22' y='5' x='5' stroke='%23000' fill='none'/%3E%3Cpath stroke-width='3' id='svg_3' d='m8.9609,13.93296c4.35754,8.49162 5.13966,9.83241 5.11731,9.75419c0.02235,0.07822 8.9609,-15.56424 8.93855,-15.64246' opacity='NaN' stroke='%23000' fill='none'/%3E%3Cpath filter='url(%23svg_3_blur)' stroke-width='3' id='svg_3' d='m8.9609,13.93296c4.35754,8.49162 5.13966,9.83241 5.11731,9.75419c0.02235,0.07822 8.9609,-15.56424 8.93855,-15.64246' opacity='NaN' stroke='%23628C52' fill='none'/%3E%3C/g%3E%3C/svg%3E");
}

.jstree-icon-checkbox-unchecked {
background-image: url("data:image/svg+xml,%3Csvg width='32' height='32' xmlns='http://www.w3.org/2000/svg'%3E%3Cg%3E%3Ctitle%3ELayer 1%3C/title%3E%3Crect rx='4' id='svg_1' height='22' width='22' y='5' x='5' stroke='%23000' fill='none'/%3E%3C/g%3E%3C/svg%3E");
}

.jstree-icon-checkbox-indeterminate {
background-image: url("data:image/svg+xml,%3Csvg width='32' height='32' xmlns='http://www.w3.org/2000/svg'%3E%3Cdefs%3E%3Cfilter height='200%25' width='200%25' y='-50%25' x='-50%25' id='svg_2_blur'%3E%3CfeGaussianBlur stdDeviation='1' in='SourceGraphic'/%3E%3C/filter%3E%3C/defs%3E%3Cg%3E%3Ctitle%3ELayer 1%3C/title%3E%3Crect rx='4' id='svg_1' height='22' width='22' y='5' x='5' stroke='%23000' fill='none'/%3E%3Crect rx='4' filter='url(%23svg_2_blur)' id='svg_2' height='16' width='16' y='8' x='8' stroke='%23000' fill='%23729C62'/%3E%3C/g%3E%3C/svg%3E");
}
201 changes: 201 additions & 0 deletions dist/jstree.js
Original file line number Diff line number Diff line change
Expand Up @@ -1245,3 +1245,204 @@ jsTree.defaults = {
};





if (!jsTree.plugin)
jsTree.plugin = {}

jsTree.plugin.checkbox = function (options) {
const defaults = {
visible : false,
cascade : { up : true, down : true, indeterminate : true, includeHidden : true, includeDisabled : true },
selection : true,
wholeNode : false
};
options = jsTree._extend({}, defaults, options);
this.on(
`check uncheck checkAll uncheckAll`,
() => this.redraw()
);
this.addRenderHandler(function (dom, node) {
// text (node content including icon)
const a = document.evaluate("a[contains(@class,'jstree-node-text')]",dom,null,XPathResult.FIRST_ORDERED_NODE_TYPE).singleNodeValue
// checkbox should/will be in front...
let checkBoxDomElement = a.previousSibling;
if (!checkBoxDomElement.classList.contains("jstree-icon-checkbox")) {
// checkbox element needs to be created...
checkBoxDomElement = document.createElement("i")

if (this.getState(node, "checked", false))
checkBoxDomElement.className = "jstree-icon-checkbox jstree-icon-checkbox-checked"
else if (this.getState(node, "indeterminate", false))
checkBoxDomElement.className = "jstree-icon-checkbox jstree-icon-checkbox-indeterminate"
else
checkBoxDomElement.className = "jstree-icon-checkbox jstree-icon-checkbox-unchecked"

// ... and added first.
a.parentElement.insertBefore(checkBoxDomElement,a);

// register event listner to trigger check change ...
checkBoxDomElement.addEventListener('click', (e) => {
if (this.getState(node,"checked",false))
this.uncheck(node);
else
this.check(node);
});
} else {
// checkbox state will be set/updated
if (this.getState(node, "checked", false))
checkBoxDomElement.classList.replace(/jstree-icon-checkbox-.+/,"jstree-icon-checkbox-checked")
else if (this.getState(node, "indeterminate", false))
checkBoxDomElement.classList.replace(/jstree-icon-checkbox-.+/,"jstree-icon-checkbox-indeterminate")
else
checkBoxDomElement.classList.replace(/jstree-icon-checkbox-.+/,"jstree-icon-checkbox-unchecked")
}
});
// TODO: all other plugin stuff (cascade, etc)

// checkboxes (just visual indication - redraw involved nodes or loop and apply minor changes)
this.check = function(node) {
if (Array.isArray(node)) {
node.forEach(x => this.check(x));
return this;
}
node = this.node(node);
if (node) {
this.setState(node, "checked", true);
this.setState(node, "indeterminate", false);
this.trigger("check", { node : node });
if (options.cascade.down) {
if (!this.getState(node, 'loaded', true)) {
this.load(node, () => this.check(node));
} else {
this.check(node.children);
}
}
if (options.cascade.up) {
const traverseUpIndeterminate = (node)=>{
const parent = node.getParent();
if (parent) {
const nodeAndSiblings = parent.getChildren();
if (nodeAndSiblings.every(s=>this.getState(s,"checked"))) // if 'checked'===true we know that indetermiante must be false
{
if (!this.getState(parent,"checked"))
this.check(parent) // this will tirgger update at parent as well (if required)
} else if (
options.cascade.indeterminate &&
nodeAndSiblings.some(s=>this.getState(s,"checked") || this.getState(s,"indeterminate",false))
) {
if (!this.getState(parent,"indeterminate"))
{
this.setState(parent,"indeterminate",true);
// since we don't have a 'undeterminate' setter, we need to traverse this up 'manually'
traverseUpIndeterminate(parent);
}
}
}
}
traverseUpIndeterminate(node);
}
}
return this;
}
this.uncheck = function(node) {
if (Array.isArray(node)) {
node.forEach(x => this.uncheck(x));
return this;
}
node = this.node(node);
if (node) {
this.setState(node, "checked", false);
this.setState(node, "indeterminate", false);
this.trigger("uncheck", { node : node });
if (options.cascade.down) {
if (!this.getState(node, 'loaded', true)) {
this.load(node, () => this.uncheck(node));
} else {
this.uncheck(node.children);
}
}
if (options.cascade.up) {
const traverseUpIndeterminate = (node)=>{
const parent = node.getParent();
if (parent) {
const nodeAndSiblings = parent.getChildren();
if (nodeAndSiblings.every(s=>!this.getState(s,"checked") && !this.getState(s,"indeterminate")))
{
if (this.getState(parent,"checked") || this.getState(parent,"indeterminate"))
{
this.uncheck(parent) // this will tirgger update at parent as well (if required)
}
} else if (
options.cascade.indeterminate &&
nodeAndSiblings.some(s=>this.getState(s,"checked",false) || this.getState(s,"indeterminate",false))
) {
if (this.getState(parent,"checked",false))
{
this.setState(parent,"checked",false);
this.setState(parent,"indeterminate",true);
// since we don't have a 'undeterminate' setter, we need to traverse this up 'manually'
// also to avoid infitive update loop...
traverseUpIndeterminate(parent);
}
}
}
}
traverseUpIndeterminate(node);
}
}
return this;
}
this.checkAll = function() {
for (let node of this.tree) {
this.setState(node, "checked", true);
this.setState(node, "indeterminate", false);
}
this.trigger("checkAll");
return this;
}
this.uncheckAll = function() {
for (let node of this.tree) {
this.setState(node, "checked", false);
this.setState(node, "indeterminate", false);
}
this.trigger("uncheckAll");
return this;
}
this.getChecked = function() {
// how about 'not yet loaded' nodes in case of 'cascade down'?
return this.tree.find(function (node) {
return node.data && node.data.state && node.data.state.checked;
});
}
this.getTopChecked = function() {
let recurse = function * (node) {
for (let child of node.children) {
if (node.data.state && node.data.state.checked) {
yield child;
}
yield * recurse(child);
}
};
return Array.from(recurse(this.root));
}
this.getBottomChecked = function() {
// how about 'not yet loaded' nodes in case of 'cascade down'?
let recurse = function * (node) {
for (let child of node.children) {
if (node.data.state && node.data.state.checked && !child.children.length) {
yield child;
}
yield * recurse(child);
}
};
return Array.from(recurse(this.root));
}
this.getIndeterminate = function() {
// how about 'not yet loaded' nodes in case of 'cascade down'?
return this.tree.find(function (node) {
return node.data && node.data.state && node.data.state.indeterminate;
});
}
};
2 changes: 1 addition & 1 deletion dist/jstree.min.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1,443 changes: 1,442 additions & 1 deletion dist/jstree.min.js

Large diffs are not rendered by default.

Loading