I am using the jstree to build up tasks on a calender. I have set the div on which the tree is initialised to a min height of 50px, which means when I am dragging the first node onto an empty tree I can drop it anywhere within that 50px which is great.
However if I then want to drop another node (onto main tree branch) I now much drop it almost on top of the previous node, which means the user needs to be very exact about where they are dropping
Here is the div container

here is the code where the JSTree is attached:
dayTree = moment.format('DD-MM-YYYY');
$('#' + dayTree).jstree({
"core": {
"id": moment.format('DD-MM-YYYY'),
"animation": 150,
'check_callback': function (operation, node, node_parent, node_position, more) {
return true; //allow all other operations
},
"themes": {"stripes": false},
"data": {
async: false,
"url": APIURL+"/shiftassignments?asg_date=" + moment.format('YYYY-MM-DD'),
"dataType": "json",
"success": function (data) {
}
}
},
"rules": {
droppable: ["tree-drop"],
multiple: true,
deletable: "all",
draggable: "all"
},
"contextmenu": {
'items' : getContextMenu
},
"types": {
"#": {
"valid_children": ["SH_LS", "SH_AS", "TO_LS", "TO_AS"]
},
"SH_AS": {
"valid_children": ["ST_LS", "ST_AS"]
},
"TO_AS" : {
"valid_children" : ["ST_LS", "ST_AS"]
},
"TO_LS" : {
"valid_children" : [""]
},
"SH_LS": {
"valid_children": [""]
},
"ST_LS": {
"valid_children": [""]
},
"ST_AS": {
"valid_children": [""]
}
},
"dnd": {
open_timeout: 100,
always_copy: true,
large_drop_target: true
},
"plugins": ["contextmenu", "dnd", "types"]
})
attached is a screenshot to explain!
I guess effectively I would like to increase the drop-zone and have a 'snap-to' effect to the main '#' node. Maybe its not possible?
Screenshot
I would listen to events on the div container for the second tree and if a dragged item is then dropped on that container and second tree has only the root, then just add it there.
The code could look something like below. See demo - Fiddle.
var overSecondTree = false;
$(document).on('dnd_move.vakata', function(e, data) {
// change icon to `allow drop` if from first tree and over second tree with a single root node
if ( $('#tree2 li').length === 1 && overSecondTree ) {
// change icon to `allow drop`
$('#vakata-dnd').find('i').removeClass('jstree-er').addClass('jstree-ok');
} else {
// change icon to `restrict drop`
$('#vakata-dnd').find('i').removeClass('jstree-ok').addClass('jstree-er');
}
});
$(document).on('dnd_stop.vakata', function(e, data) {
// allow drop to tree2 only if single root node
if ( $('#tree2 li').length === 1 && overSecondTree) {
$("#tree2").jstree().create_node('#rootTree2', data.element.text);
}
})
$("#tree2").mouseenter(function(event){
overSecondTree = true;
if ( $('#tree2 li').length === 1 ) {
$('#rootTree2').addClass('highlighted');
}
})
$("#tree2").mouseleave(function(event){
overSecondTree = false;
$('#rootTree2').removeClass('highlighted');
})
Related
Application made with RactiveJS, Redux and Gridstack.
When new widgets are added, all is fine and widgets are movable/resizable as well. But when I delete all widgets and add, for example, two new, then:
widgets can't be moved and can't be resized. As in picture:
when try to delete widget it disappear, but other widgets change their position.
jsFiddle is provided as follows
You can see that right widget is moved to the right side, but handle stays where it is. So why is such behavior and how to deal with that?
RactiveJS application have three components
DashboardComponent
WidgetGridComponent
WidgetComponent
Code provided as follows:
var Widget = Ractive.extend({
isolated: false, // To pass events to WidgetGrid component (makeWidget, removeWidget, etc.)
template: '#widgetTemplate',
components: {},
oninit: function() {
// Load data to widget
},
oncomplete: function() {
this.drawChart();
},
drawChart: function() {
var self = this;
function exampleData() {
return [{
"label": "One",
"value": 29.765957771107
},
{
"label": "Two",
"value": 0
},
{
"label": "Three",
"value": 32.807804682612
},
{
"label": "Four",
"value": 196.45946739256
},
{
"label": "Five",
"value": 0.19434030906893
},
{
"label": "Six",
"value": 98.079782601442
},
{
"label": "Seven",
"value": 13.925743130903
},
{
"label": "Eight",
"value": 5.1387322875705
}
];
}
nv.addGraph(function() {
var chart = nv.models.pieChart()
.x(function(d) {
return d.label
})
.y(function(d) {
return d.value
})
.showLabels(true);
d3.select("#widget" + self.get("id") + " svg")
.datum(exampleData())
.transition().duration(350)
.call(chart);
return chart;
});
},
data: function() {
return {
id: null,
x: null,
y: null,
width: null,
height: null,
}
}
});
var WidgetGrid = Ractive.extend({
// isolated:false,
// twoway:false,
template: '#widgetGridTemplate',
components: {
Widget: Widget,
},
onrender: function() {
// Init gridstack instance
this.bindGridstack();
},
deleteWidget: function(id) {
Action.deleteWidget(id);
},
removeWidget: function(id) {
$(".grid-stack").data("gridstack").removeWidget("#widget" + id);
},
createWidget: function(id) {
$(".grid-stack").data("gridstack").makeWidget("#widget" + id);
},
updateWidgetSize: function(id, width, height) {
Action.updateWidgetSize(id, width, height);
},
updateWidgetPosition: function(id, x, y) {
Action.updateWidgetPosition(id, x, y);
},
bindGridstack: function() {
var self = this;
var options = {
animate: true,
auto: false, // if false gridstack will not initialize existing items (default: true)
float: true, // enable floating widgets (default: false)
disableOneColumnMode: true,
width: 10, // amount of columns (default: 12)
height: 10, // maximum rows amount. Default is 0 which means no maximum rows
// height: 10,
// cellHeight: 80,
disableResize: false,
disableDrag: false,
verticalMargin: 0,
resizable: {
handles: 'se'
}
};
var grid = $(".grid-stack").gridstack(options);
// On user ends resizing
grid.on('gsresizestop', function(event, elem) {
var $el = $(elem);
var node = $el.data('_gridstack_node');
var id = node.el.attr("id").replace("widget", "");
self.updateWidgetSize(id, node.width, node.height, node.el.css("width"), node.el.css("height"));
});
// On user ends dragging
grid.on('dragstop', function(event, ui) {
var $el = $(event.target);
var node = $el.data('_gridstack_node');
var id = $el.attr("id").replace("ar-widget", "");
self.updateWidgetPosition(id, node.x, node.y);
});
},
data: function() {
return {
widgets: [],
}
}
});
var Dashboard = Ractive.extend({
el: '#dashboard', //document.body,
template: '#dashboardTemplate',
isolated: true,
append: false,
oninit: function() {
this.on("add", function() {
Action.addWidget()
});
},
components: {
WidgetGrid: WidgetGrid,
},
data: function() {
return {
store: {}
}
}
});
There's several problems in there:
detach is not called when unrendering a component. It's only called when ractive.detach() is called. Your grid was not aware of the widget's removal. After Ractive unrendered the widget elements, this caused the grid to act weird.
On the flip side, grid.removeWidget() removes the widget from the DOM. After a state change, Ractive will still try to unrender. But since the element is no longer there and because Ractive wasn't aware of the widget's removal, it will cause a double removal.
After a state change, the data for the widget is no longer present. id is already undefined. You can no longer use id when you handle teardown, unrender nor destruct events. You'll have to disconnect a widget from the grid earlier, preferably before the state change.
Ideally, you should just let Ractive do the rendering/unrendering of elements and only inform gridstack about making/disconnecting widgets from the grid. You're already doing this on render by using makeWidget instead of addWidget. For removal, you simply need to do the same thing. removeWidget accepts a second argument which, if false, will disassociate a widget from the grid but not remove the element from the DOM. This leaves unrendering to Ractive after the state change.
Here's an example: https://jsfiddle.net/fm133mk6/
I have to work on a jstree so that it saves what nodes are checked, but also opens checked nodes and their parents on load. This is the code i have to work with:
initializeGeoLevelTree: function () {
var GeoLevelTree = $('#geoLevelTree');
GeoLevelTree.jstree({
"core": {
"multiple": false,
"themes": {
"responsive": false
},
"check_callback": true,
"data":
CreateAdministrator.TreeGeoLevelArray
},
"types": {
"default": {
"icon": "fa fa-folder icon-warning icon-lg"
},
"file": {
"icon": "fa fa-file icon-warning icon-lg"
},
"disabled": {
"check_node": false,
"uncheck_node": false
}
},
"state": { "key": "createAdministrator" },
"checkbox": {
"three_state": false,
"whole_node": false,
"tie_selection": false
},
"plugins": ["checkbox", "wholerow", "types", "themes", "state"]
});
GeoLevelTree.bind("loaded.jstree", function (e, data) {
GeoLevelTree.bind("select_node.jstree", function (e, data) {
$("#lblName").text(data.node.original.Name);
$("#lblType").text(data.node.original.GeoLevelType);
if (data.node.original.IdGeoLevelType == 4) {
$("#officeDetail").show();
$("#lblAddress").text("");
CreateAdministrator.getOfficelDetail(data.node.id);
} else {
$("#officeDetail").hide();
}
});
CreateAdministrator.SelectGeoLevelAdministratorSavedNode();
});
CreateAdministrator.hideLoadingPage();
},
SelectGeoLevelAdministratorSavedNode: function () {
for (var i = 0, l = CreateAdministrator.GeoLevelAdministratorSavedArray.length; i < l; i++) {
var option = CreateAdministrator.GeoLevelAdministratorSavedArray[i];
$.jstree.reference("#geoLevelTree").check_node(option.IdGeoLevel);
jQuery.jstree._reference("#geoLevelTree")._open_to(option.IdGeoLevel);
}
},
The checked nodes adre stored on an external array, which is processed to find which ones are checked. Is there a way to set those nodes to opened as well? using the open_node or the _open_to methods doesn't seem to work. Thanks in advance
_open_to seems to do the trick. Here is a demo:
http://jsfiddle.net/DGAF4/1267/
Maybe the problem is the format of the IDs you pass to both functions - notice the ID is a string and has no # prefix.
Best regards,
Ivan
My jstree function is here.
I have set 'select_limit' : 3, but is not working. when I run, I am able to select more than 3 nodes, but I need to select no more than 3 nodes.
var j1 = jQuery.noConflict();
j1("#utree_activity").jstree({
"plugins": ["themes", "html_data", "ui", "crrm", "checkbox"],
"html_data": {
"ajax": {
"url": urlGlobal + "jstrees/activitytree/",
"asynchronous": "false",
"data": function (n) {
return {
id: n.attr ? n.attr("id") : 0,
default_activities: default_activities
};
},
"success": function (gb) {
},
}
},
"ui": {
"select_limit": 3,
},
"cookies": {
cookie_options: {
path: "/"
}
},
"checkbox": {
two_state: true,
real_checkboxes: false
}
});
select_limit doens't handle checkbox you must roll your own before.jstree method.
j1.bind("before.jstree", function (e, data) {
if (data.func === "check_node") {
if (j1.jstree('get_checked').length >= 1) {
e.preventDefault();
return false;
}
}
});
Note that this code if for example only, and doesn't handle child nodes
Working fiddle: http://jsfiddle.net/cfb9J/1/
There is another option missing, probably need to add the ui module, try this:
j1("#utree_activity").jstree({
"plugins" : ["html_data","ui"],
//the rest of your code
});
I have am currently using Dojo EnhancedGrid with Pagination, filtering and cell edition.
The problem is that in one page, I need to update another value when a cell is edited. When I update this value, I loose the cell selected so I need to click on the next cell to select it and modify it.
It is so not possible to do Enter / edit / enter / down / enter / edit (Excel like edition).
Here is a part of my code :
var store = new dojo.data.ItemFileWriteStore({'data':data});
var grid = new dojox.grid.EnhancedGrid({
id: 'grid',
store: store,
structure: structure,
columnReordering: true,
autoHeight: true,
autoWidth: true,
initialWidth: '100%',
escapeHTMLInData: false,
plugins: {
pagination: {
pageSizes: ["10", "25", "50", "All"],
description: true,
sizeSwitch: true,
pageStepper: true,
gotoButton: true,
maxPageStep: 4,
position: "bottom"
},
filter : {}
},
onStartEdit: function(inCell, inRowIndex)
{
item = grid.selection.getSelected()[0];
currentVal = item[inCell['field']][0];
},
doApplyCellEdit: function(inValue, inRowIndex, inAttrName) {
if(inValue != currentVal){
[...]
$.ajax(url, data, {
success:function(data, textStatus) {
val = parseInt(data["info"]);
if(!isNaN(val)) {
grid.store.setValue(item, 'info', val);
grid.update();
} else {
grid.store.setValue(item, 'info', 0);
grid.update();
}
}
});
}
}
});
dojo.byId("gridDiv").appendChild(grid.domNode);
grid.startup();
Do you see any solution to handle this ?
I too use an enhanced dojo grid, where I had a similar problem. I used this to reload the data right:
require(["dijit/registry"],function(registry) {
var grid = registry.byId('grid');
grid._lastScrollTop=grid.scrollTop;
grid._refresh();
});
With this you always should get the last row you manipulated, and eventually also the last selected one... .
After many research, I have found the following solution :
$.ajax(url, data, {
success:function(data, textStatus) {
val = parseInt(data["info"]);
if(!isNaN(val)) {
grid.store.setValue(item, 'info', val);
grid.update();
window.setTimeout(function() {
grid.focus.next();
}, 10);
} else {
grid.store.setValue(item, 'info', 0);
grid.update();
window.setTimeout(function() {
grid.focus.next();
}, 10);
}
}
});
The timer is needed because there is a short delay before grid update the thus loosing the focus.
I have been searching the interent for an age and couldnt find an answer. I was trying to change node text colour dependant on the type of the particular node. So in this example i wanted to change the text colour of every node that is a "Role" type defined in data.
$("#roleTree").jstree({
json_data: {
data: roleTreeData
},
"themes": {
"theme": "default",
"dots": true,
"icons": false
},
"ui": {
"select_limit": 1,
"select_multiple_modifier": "none"
},
"types": {
"types": {
"AM": {
"hover_node": false,
"select_node": false
},
"AF": {
"hover_node": false,
"select_node": false
},
"Role": {
// i dont know if possible to be done here? add class?
// this.css("color", "red")
//{ font-weight:bold}
}
}
},
plugins: ["themes", "json_data", "ui", "Select", "types", "crrm"]
}).bind("loaded.jstree", function (event, data)
{
$("#roleTree").jstree("open_all");
data.inst.select_node('ul > li:first');
}).bind("select_node.jstree", function (event, data)
{
selectedRoleId = data.rslt.obj.attr("id");
window.selectedRole = GetRole(selectedRoleId);
});
}
Or any other method to highlight or tell users that only the role is selectable.
This can be done using the following CSS, assuming you are using the default classic theme.
.jstree-classic li[rel="Role"] > a { color:red; }
Get jstree-classic class, where child li has rel attribute of Role, get the first child a of that li and assign color to red.