Open previously selected sub-nodes in JSON generated JsTree - javascript

I have page, where user can create a document and one of the inputs is JsTree input, where on click opens a modal and new JsTree generates by JSON data every time. After user select some of the options, they submit it and text from selected nodes displays in input, also selected nodes themselves are saved in another hidden input.
Problem is, whenever user wants to change selected nodes, they need to open all the subnodes again, manually. I succeed only at making previously selected 1st level nodes be opened, after user selected it and opened JsTree again.
The problem is also, it takes some time for JsTree to generate subnodes, after parent node opening. So when my code opened node "2", it wants to open "2.2", but it doesn't exist at this moment. I tried to manually set setTimeout between opening every node, but it doesn't help.
My question is, how to open and select previously selected subnodes (for example 3.1.1.1) in just generated JSON JsTree
$("#container-test").on("ready.jstree", function() {
if(selectedNodes && selectedNodes.length > 0) {
selectedNodes.forEach(async function(node) {
if(node.parents.length >= 2) {
var allParents = node.parents.reverse()
for(i = 1; i < allParents.length; i++) {
nodesOpener(allParents[i])
}
}
})
}
})
function nodesOpener(parent) {
$("#container-test").jstree("open_node", $("#" + parent))
setTimeout(function() {
}, 1000)
}

The open_node method accepts a callback to call after node opened. If that doesn't work with your data fetching mechanism, maybe you should use another event.
Here's an example:
// after_open
var elem = $('#jstree_demo')
elem.jstree({
'core': {
'data': [{
"id": "ajson1",
"parent": "#",
"text": "Simple root node"
},
{
"id": "ajson2",
"parent": "#",
"text": "Root node 2"
},
{
"id": "ajson3",
"parent": "ajson2",
"text": "Child 1"
},
{
"id": "ajson4",
"parent": "ajson2",
"text": "Child 2"
},
{
"id": "ajson5",
"parent": "ajson4",
"text": "Child 2.2"
},
]
}
});
function open(id_list) {
while (id_list.length) {
var id = id_list.shift();
elem.jstree('open_node', id, function() {
open(id_list);
});
}
}
elem.on("ready.jstree", function() {
open(['ajson2', 'ajson4']);
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jstree/3.3.12/jstree.min.js" integrity="sha512-TGClBy3S4qrWJtzel4qMtXsmM0Y9cap6QwRm3zo1MpVjvIURa90YYz5weeh6nvDGKZf/x3hrl1zzHW/uygftKg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jstree/3.3.12/themes/default/style.min.css" integrity="sha512-pg7xGkuHzhrV2jAMJvQsTV30au1VGlnxVN4sgmG8Yv0dxGR71B21QeHGLMvYod4AaygAzz87swLEZURw7VND2A==" crossorigin="anonymous" referrerpolicy="no-referrer"/>
<div id="jstree_demo"></div>

Related

Regarding tree view, is it possible to have an on click function for the tree view

I'm wondering if it's possible to have a custom on click function on the leaves of the tree view in ECharts?
I've look at the docs, but couldn't find any thing on it.
As seen in the image, node where the arrow is pointing, is where i will like to have an on click function
sample tree img
Please specify what exactly do you mean by leaves? Technically any node except first is a leaf.
If you need to handle click on specific node then before process event you need strictly check for equivalence target node by name.
// Params object that will passed to our event listener looks like
{
// ...
componentIndex: 0,
componentSubType: "tree",
componentType: "series",
data: {
name: "MergeEdge", // <-------- our target clicked node
value: 743
},
// ...
}
// Handle click event
myChart.on('click', function(params){
var data = params.data
if(data.name === 'MergeEdge'){ // <-------- node name test
// test passed
// doing something useful
}
});
If you need to handle click on specific node group then you should neatly named nodes. Suppose a chart has three (without root) levels and you only need to handle clicks from the second level.
Let's prepare the data so that nodes of the same level named are similar way:
data: [{
"name": "root",
"children": [{
"name": "node_first_level_01",
"children": [{
"name": "node_second_level_01", // <-------- this
"children": [{
"name": "node_third_level_01",
"value": 3938
}]
},
{
"name": "node_second_level_02", // <-------- this
"children": [{
"name": "node_third_level_02",
"value": 3938,
}]
},
{
"name": "node_second_level_03", // <-------- this
"children": [{
"name": "node_third_level_03",
"value": 3938,
}]
}
]
}]
}],
Now take the function from the first example and instead of a strict check, we will check for compliance with the regular expression:
// Handle click event
myChart.on('click', function(params){
var data = params.data
var rule = /\w+second\w+/gm; // <-------- regex rule
if(rule.test(data.name)){ // <-------- test name
// test passed
// doing something useful
}
});

React, javascript object, if key value exists render an element

How do I check to see if a javascript object item has a key and render an element if it does exist.
This is my javascript object which is then parsed through and each item is made into a bootstrap list item. Under items.title = Groups, there is an additional key "dropdown": "true" which is the element I would like to create a dropdown menu for.
var linksNav = {
items: [
{
"type": "heading",
"title": "News",
"href": "#",
"target": "_self"
},
{
"type": "link",
"title": "People",
"href": "#",
"target": "_self"
},
{
"type": "link",
"title": "Events",
"href": "#",
"target": "_self"
},
{
"type": "link",
"title": "Groups",
"href": "#",
"target": "_self",
"dropdown": "true"
},
{
"type": "heading",
"title": "Capabilities",
"href": "#",
"target": "_self"
},
{
"type": "link",
"title": "Initiatives",
"href": "#",
"target": "_self"
},
{
"type": "link",
"title": "Who we are",
"href": "#",
"target": "_blank"
},
]
}
And this is my code (that doesn't work) to try to conditionally render a dropdown (the <NavSub /> tag) if that key exists for that menu item.
The result I get is my dropdown menu for each of my list items is <div>Nothing</div>. Everything else shows up as normal, so I figure there's something wrong with my conditional statement.
render: function() {
let dropdownMenu;
if (this.props.dropdown=="true") {
dropdownMenu = (<Navsub />)
} else {
dropdownMenu = (<div>Nothing</div>)
}
return (
<li className={this.props.title + ' nav-items'}>
{this.props.title}
//I want to conditionall render this dropdown menu
<ul className="dropdown-menu fade">
{dropdownMenu}
</ul>
</li>
);
}
If "dropdown" may not be present in your props, you should use JavaScript's in operator. Also, avoid using == as it can lead to weird results due to silent type conversions.
if ('dropdown' in this.props && this.props.dropdown === 'true') {
//show dropdown
} else {
//don't show dropdown
}
The previous snippet works because JavaScript's if short-circuits.
Having said that, given that you're getting a div containing Nothing in each dropdown, it's likely your components are not receiving the props they're supposed to. You should check what props each component is getting.
This approach may be overkill for your situation where the following will work for your situation:
if ((this.props.dropdown) && this.props.dropdown === "true"){
dropdownMenu = <Navsub />;
} else {
dropdownMenu = <div>Nothing</div>
}
An alternative approach to checking whether the property dropdown exists on the object this.props, you can use Object#hasOwnProperty with the shortcut && binary operator in your condition:
// the condition will only check if dropdown is true if it exists on this.props
if (this.props.hasOwnProperty('dropdown') && this.props.dropdown === "true") {
dropdownMenu = <Navsub />;
} else {
dropdownMenu = <div>Nothing</div>
}
Object#hasOwnProperty is useful in scenarios where you want to
ensure that the property exists on the object even in the case where
its value is null or undefined.

how to add data in a row or kendo Grid

I have a scenario where I want to fill some of the kendo grid columns by program. So I assume that I have to catch the row and fill data in the columns.
I am able to fetch the row ID based on some event(click for example). But I have no Idea how to update the value of a column bases on row id pragmatically.
http://jsfiddle.net/xojke83s/4/
above is the JS fiddle where I am able to get the row ID of a particular row. I want to know the way fill some data in any of the column by program. In the above example that column should be operationContext.
following is the code for same -
<div id="grid"></div>
$("#grid").kendoGrid({
"dataSource": {
"schema": {
"model": {
"id": "id",
"fields": {
"OperationContext": {
"type": "string",
"editable": "false"
}
}
}
}
},
"editable": "popup",
"toolbar": [
{
"name": "create",
"text": "Add a New Record"
}
],
"columns": [
{
"field": "Name",
"title": "Name"
},
{
"field": "Age",
"title": "Age"
},
{
"field": "OperationContext",
"title": "Operation Context"
},
{ command: ["edit", "destroy"], title: " ", width: "250px" }
]
});
$(".k-grid-add").on("click", function () {
var grid = $("#grid").data("kendoGrid").dataSource.data([{OperationContext: "IsAdded"}]);
});
//bind click event to the checkbox
var grid = $("#grid").data("kendoGrid"); 
grid.bind("edit", grid_edit);
function grid_edit(e){
console.log(e.model.uid);
}
Thanks in advance.
Answer made from comment as requested
I have updated your fiddle with this: updated js fiddle
I have modified the edit code to do this:
function grid_edit(e){
console.log(e.model);
if(!e.model.isNew() || e.model.id === 0){
e.model.set("OperationContext","I am being updated");
}
}
It will only add the inserting (defaultValue) and updating text in for new items or where an id is greater than 0.
I can see the logic for newly created items and maybe for edited items if the value is blank or if it is being used as a status tracker.
But if you delete an item then surely that is deleted from your datasource and you will no longer have access to that item so why store an update/indicate an update to a value when it is to be deleted.

How to create Tree with checkboxes using JSON data with Parent Child Relation?

I have JSON data and that JSON data has parent child relation . I Want to create tree structure from it. i found many plugins and libraries but i can't found my requirement . I am getting this JSON data using PHP script.
Here is image that has tree structure that i want to create . i'm stuck at it.I know JSON is not as displayed in image but i only want to show you what a tree should look like .How to create tree like in image.All i want is javascript code to handle and create this type of structure of tree . Working example is must & much appreciated.
You can use JSON format as you like and tree should be collapsible.Also provide required JSON format for it.
and my JSON data as follows :
{
"2":
{
"5": "Wrist Watch"
},
"5":
{
"9": "Men's"
},
"18":
{
"3": "Clothing"
},
"28":
{
"1": "Perfumes"
},
"29":
{
"7": "Laptop",
"10": "Tablets"
},
"30":
{
"8": "Mobile"
},
"31":
{
"2": "Books"
},
"33":
{
"6": "Electronics"
},
"34":
{
"4": "Home & Kitchen\n"
}
}
If you want to roll your own, the keyword in "trees" is recursion. It needs to support any depth of data and the code and data should both support recursion.
This means your JSON data should be a recursive structure, where each node looks the same (and looks something like this):
{
id: 1, // data id
title: "title", // display title
children: [ // list of children, each with this same structure
// list of child nodes
]
}
Note: I have changed the sample data to contain more depth as 2 levels never shows up recursion problems.
e.g.:
{
id: 0,
title: "root - not displayed",
children: [{
id: 1,
title: "Option 1",
children: [{
id: 11,
title: "Option 11",
children: [{
id: 111,
title: "Option 111"
}, {
id: 112,
title: "Option 112"
}]
}, {
id: 12,
title: "Option 12"
}]
}, {
id: 2,
title: "Option 2",
children: [{
id: 21,
title: "Option 21"
}, {
id: 22,
title: "Option 22"
}]
}, {
id: 3,
title: "Option 3",
children: [{
id: 31,
title: "Option 31"
}, {
id: 32,
title: "Option 32"
}]
}]
}
The recursive function looks like this:
function addItem(parentUL, branch) {
for (var key in branch.children) {
var item = branch.children[key];
$item = $('<li>', {
id: "item" + item.id
});
$item.append($('<input>', {
type: "checkbox",
name: "item" + item.id
}));
$item.append($('<label>', {
for: "item" + item.id,
text: item.title
}));
parentUL.append($item);
if (item.children) {
var $ul = $('<ul>').appendTo($item);
addItem($ul, item);
}
}
}
JSFiddle: http://jsfiddle.net/0s0p3716/188/
The code recurses the structure, adding new ULs and LIs (with checkbox etc ) as it goes. The top level call just provides the initial root starting points of both the display and the data.
addItem($('#root'), data);
The end result looks like this:
If you want to toggle visibility, based on the checked state, use this:
$(':checkbox').change(function () {
$(this).closest('li').children('ul').slideToggle();
});
If you also want the labels to toggle the checkboxes, use this:
$('label').click(function(){
$(this).closest('li').find(':checkbox').trigger('click');
});
Note: I have only provided the most basic of styling as that will typically be "to taste". Examples in links were shown in another answer.
-- updated:
amended: possible wrong ids for items 31 & 32?
function for better selection and deselection(for parents cascading into child nodes):
$(function () {
addItem($('#root'), data);
$(':checkbox').click(function () {
$(this).find(':checkbox').trigger('click');
var matchingId = $(this).attr('id');
if ($(this).attr('checked'))
{
$('input[id*=' + matchingId +']').each(function() {
$(this).removeAttr('checked');
$(this).prop('checked', $(this).attr('checked'));
});
}
else {
$('input[id*=' + matchingId +']').each(function() {
$(this).attr('checked', 'checked');
$(this).prop('checked', $(this).attr('checked'));
});
}
});
$('label').click(function(){
$(this).closest('li').children('ul').slideToggle();
});
-- Update the fiddle with this as shown here(JsFiddle) and it will work better and also will allow you to click the text to expand without selecting at the same time - I know I find this far more useful. It will help (and this is personal preference) you to see what values and options are available without having to select the first.
The thing with programming is: existing libraries and tools rarely do exactly what you need. It's always up to you to convert the input data into exactly the format they expect and then the output data into the format you need. Occasionally this conversion requires more effort than writing your own code instead of a library function - this seems to be one of those occasions.
As #philosophocat already noted, the best way to present such a tree in HTML markup would be nested lists. All you need is iterate through the JSON data recursively and create the corresponding elements:
function createList(data)
{
var result = document.createElement("ul");
for (var key in data)
{
if (!data.hasOwnProperty(key) || key == "_title")
continue;
var value = data[key];
var item = createItem(key, typeof value == "string" ? value : value._title);
if (typeof value == "object")
item.appendChild(createList(value));
result.appendChild(item);
}
return result;
}
function createItem(value, title)
{
var result = document.createElement("li");
var checkbox = document.createElement("input");
checkbox.setAttribute("type", "checkbox");
checkbox.setAttribute("name", "selection");
checkbox.setAttribute("value", value);
result.appendChild(checkbox);
result.appendChild(document.createTextNode(title));
return result;
}
document.body.appendChild(createList(jsonData));
Note that the order in which the items appear is "random" here, as object keys are generally unordered. You can change the code above to sort the keys somehow, or you can change the data to use arrays and define an order. I also added a "_title" property to the data to make sure the categories are labeled - your data doesn't have any labels at all for the categories.
Now you need to style the lists in such a way that they look like a tree. The obvious solution is using the list-style-image CSS property to replace the usual bullet points by a grid lines image. However, that doesn't work for nested lists - there you need to show multiple images, vertical lines from the higher-level lists as well as the image actually belonging to the current list item.
This can be solved by using background images for the list items instead, these background images will be shown next to sublists as well then. Here are the example styles I've got:
ul
{
padding: 0;
list-style-type: none;
font-size: 14px;
}
li
{
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA0AAABkCAYAAABdELruAAAAP0lEQVR42u3PQQoAIAgEQP3/o6t7JAhdolkQD4sMZuwZazKKlGXniHRDOu6HfyKRSCQSiUQikUgkEolEIv0rTc/fNmQ78+lPAAAAAElFTkSuQmCC);
background-repeat: no-repeat;
padding-left: 13px;
}
li:last-child
{
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA0AAAAJCAYAAADpeqZqAAAAHUlEQVR42mNkwAT/gZiRAQ/AK0mKplGbqGETThoACFgJCVdBEqAAAAAASUVORK5CYII=);
}
li > ul
{
margin-left: 5px;
}
Note that this will still get ugly if the sublist is too high - the height of the background image I used is merely 100px. This can be solved by using a larger background image of course. A cleaner alternative would be using border-image-slice CSS property but that one is currently only supported in Firefox.
Fiddle for this code
Edit: This article goes into more detail on styling nested lists like a tree. While the approach is similar, it manages to avoid the image size issues I mentioned above by using a separate image for the vertical line which can be repeated vertically. On the downside, that approach looks like it might only work with solid lines and produce artifacts if applied to dotted lines.
Use http://www.jstree.com/. This library provides each function I ever need when working with trees and javascript.
You simple have to change your json-response according to the given format (http://www.jstree.com/docs/json/):
{
id : "string" // will be autogenerated if omitted
text : "string" // node text
icon : "string" // string for custom
state : {
opened : boolean // is the node open
disabled : boolean // is the node disabled
selected : boolean // is the node selected
},
children : [] // array of strings or objects
li_attr : {} // attributes for the generated LI node
a_attr : {} // attributes for the generated A node
}
Set up the javascript and include all required files and there you go.
I just skip repeating the documentation by referring to it: http://www.jstree.com/
I'm using DynaTree for an internal site at work and it works fantastic.
Download DynaTree
Format your JSON as such (taking your screenshot as an example):
{
"title": "Sports & Outdoors",
"isFolder": true,
"key": "0",
"children": [
{
"title": "Fitness Accessories",
"key": "1",
"isFolder": true,
"children": [
{
"title": "Fitness Accessories",
"key": "2",
"isFolder": true,
"children": [
{
"title": "Pedometer & Watches",
"key": "3"
}
]
}
]
}
]
}
Run this JS on page load:
$("#buildTree").dynatree({
onActivate: function (node) {
// A DynaTreeNode object is passed to the activation handler
// Note: we also get this event, if persistence is on, and the page is reloaded.
leftActiveNodeKey = node.data.key;
},
persist: false,
checkbox: true,
selectMode: 3,
children: $.parseJSON(response.d)
});
To get the selected nodes you can use:
var selectedNodes = $("#buildTree").dynatree("getTree").getSelectedNodes();
Dynatree is pretty customization, both in look and function. Read through the documentation for the settings you need.
Check these sites.Hope this helps.
http://www.jstree.com/docs/json/
http://www.jeasyui.com/documentation/tree.php
http://jqwidgets.com/jquery-widgets-demo/demos/jqxtree/index.htm#demos/jqxtree/checkboxes.htm
#Gone Coding's example is excellent, but the child check boxes will not show as 'uncheked' even though the checked attribute is removed, as rendered in Chrome.
If you add,
$(this).prop('checked', false);
to the code, so it reads as
$(function () {
addItem($('#root'), data);
$(':checkbox').click(function () {
var matchingId = $(this).attr('id');
if ($(this).attr('checked'))
{
$('input[id*=' + matchingId +']').each(function() {
$(this).removeAttr('checked');
$(this).prop('checked', false);
$(this).prop('checked', $(this).attr('checked'));
return;
});
}
else {
$('input[id*=' + matchingId +']').each(function() {
$(this).attr('checked', 'checked');
$(this).prop('checked', $(this).attr('checked'));
});
}
});
$('label').click(function(){
$(this).closest('li').children('ul').slideToggle();
});
});
the child check boxes will fill or clear when the user makes a change.

jsTree custom contextmenu not filtering based on type folder/file

I am trying to configure a custom context menu for jsTree. I want files to have two options [Rename, Delete] and I want folders to have one option [Create]
The below code seems correct as described here: Configuring jstree right-click contextmenu for different node types
However this does not seem to work, there are two problems.
Both context menus display the options [Rename, Delete]
Choosing either option
causes the error: Uncaught TypeError: undefined is not a function
What am I doing wrong?
Edit: here is a fiddle
$( document ).ready(function() {
function customMenu(node) {
// The default set of all items
var items = {
createItem: { // The "create" menu item
label: "Create",
action: function () {
this.create(node);
}
},
renameItem: { // The "rename" menu item
label: "Rename",
action: function () {
this.rename(node);
}
},
deleteItem: { // The "delete" menu item
label: "Delete",
action: function () {
this.remove(node);
}
}
};
if ($(node).hasClass("folder") || $(node).hasClass("jstree-closed") || $(node).hasClass("jstree-open")) {
delete items.deleteItem;
delete items.renameItem;
}
else{
delete items.createItem;
}
return items;
}
$('#tree').jstree({
'core': {
'data': [
{ "id": "ajson1", "parent": "#", "text": "Folder 1" },
{ "id": "ajson2", "parent": "ajson1", "text": "File 1" },
{ "id": "ajson3", "parent": "ajson1", "text": "File 2" }
]
},
"plugins": [ "contextmenu" ],
"contextmenu": {items: customMenu}
});
});
Okay so the answer is that jstree does not implicitly distinguish between files and folders. If you want to make the distinction you need to add an identifier and custom logic.
To accomplish this I added the following to each of my data objects.
"data" : { "file" : true }
The custom logic then became
if (node.data.file) {
delete items.createItem;
}
else{
delete items.deleteItem;
delete items.renameItem;
}
Furthermore, the manner in which I was implementing custom actions was wrong. I figured this out by looking at the source of jstree (jstree/src/jstree.contextmenu.js). To enable create and delete you must set 'check_callback': true. Then you can for example implement the create action as follows.
createItem: { // The "create" menu item
label: "Create",
action: function (data) {
var inst = $.jstree.reference(data.reference),
obj = inst.get_node(data.reference);
inst.create_node(obj, {}, "last", function (new_node) {
new_node.data = {file: true};
setTimeout(function () { inst.edit(new_node); },0);
});
}
},
Full working jsfiddle here.

Categories

Resources