I have a jsTree using the dnd plugin to allow for drag and dropping of tree items to change their position. However I have noticed that the index is wrong depending on whether you drop the item before or after an object and after googling for many hours about how to fix the index issue, I have come to the conclusion that it would be easier to disable the after unless it is the last tree node of the subset
I am using the following code to call the configure the dnd plugin:
'dnd': {
'drop_finish': function (data) {
alert("hi");
},
'drag_check': function (data) {
alert('hi1');
if (data.r.attr('id') == 'RootNode') {
return false;
} else if (data.r.hasClass('jstree-last')) {
return {
after: true,
before: true,
inside: true
};
} else {
return {
after: false,
before: true,
inside: true
};
}
}
}
however, the hi1 never gets alerted (but the hi does after I have dropped the item) so I can still drop after elements. I have tried finding out how to get the drag check to be called and tried many things like adding the jstree-drop class and other things that have been suggested on this site but I just can't get the hi1 to be alerted.
Any help would be appreciated in solving this problem
Thanks
it looks like "dnd" plugin is using for external drag and drop.
You can use check_move to prevent moving to the last position in node:
"crrm": {
"move": {
"check_move": function(m) {
var length = $(m.np).find("ul:first > li").length,
afterLast = length == m.cp;
if (length > 0 && afterLast) {
return false;
}
return true;
}
}
Related
Trying to get DOM-Autoscroller to work with Dragula.
We have Dragula up and running. I've managed to get the syntax to Autoscroller correct, so that it doesn't break Dragula. But Autoscroller doesn't appear to be doing anything.
Our Autoscroller call is reasonably close to the example.
$(document).ready(function () {
var drake = dragula({
containers: Array.prototype.slice.call(document.querySelectorAll('[id^="checklistItems-"')),
revertOnSpill: true,
direction: 'vertical',
accepts: function (el, target, source, sibling) {
return target == source;
}
})
.on('drop', function (el, target, sibling) {
var destinationIndex = [].slice.call(el.parentElement.children).indexOf(el);
// call AJAX with element id and destination index
$.ajax(
{
url: "#Url.Action("ReorderTicketChecklist", "Tickets")",
data: { checklist_ID: el.parentElement.dataset.checklistid, checklistItem_ID: el.dataset.id, destinationNum: destinationIndex+1 },
error: function () {
alert("Something went wrong reordering the list. Please refresh and try again.");
}
})
});
var scroll = autoScroll([
//Array.prototype.slice.call(document.querySelectorAll('[id^="checklistRow-"'))
document.querySelector('#checklistRow-100')
], {
margin: 20,
pixels: 10,
scrollWhenOutside: true,
autoScroll: function () {
return this.down && drake && drake.dragging && drake.drake && drake.drake.dragging;
}
});
})
I currently have the first list hard-coded, and I can work on the syntax for the rest, later.
I guess I mostly need some advice on how to debug this. What can I do to shake the box and get some behavior out of Autoscroller, so I can track down what's wrong?
I am working in JavaScript. I get more and more frustrated because of the way the code looks. The code is so nested that I very soon need to invest in a third 37" monitor to prevent damaging my fingers for all time.
Here is some working code I am working on right now:
$(function () {
var mainContainer = $('#mainContainer'),
countyContainer = $('#countyContainer'),
workAreaContainer = $('#workAreaContainer');
$('body').fadeIn(400, function() {
mainContainer.slideDown(400, function() {
ShowLoader();
ListCounties(function(counties,success) {
if (success) {
HideLoader();
for (var i = 0; i < counties.length; i++) {
if (counties[i] != "") {
countyContainer.append(
'<div class="col-md-3 county-result-item">'+
'<h3>'+counties[i]+'</h3>'+
'<i class=" '+FA('fa-folder-open','3x')+' text-center" style="color:'+RandomColor()+'"/>'+
'</div>'
);
}
}
var countyResultItem = $('.county-result-item');
countyResultItem.on('click', function(event) {
event.preventDefault();
var county = $(this).text().split("(")[0];
if (county != "") {
ShowLoader();
countyContainer.slideUp(400);
FetchWorkAreas(county,function(workAreaData,success) {
if (success) {
for (var i = 0; i < workAreaData.workAreas.length; i++) {
workAreaContainer.append(
'<div class="col-md-3 workArea-result-item">'+
'<h3>'+workAreaData.workAreas[i]+'</h3>'+
'<i class=" '+FA('fa-folder-open','3x')+' text-center" style="color:'+RandomColor()+'"/>'+
'</div>'
);
}
HideLoader();
workAreaContainer.slideDown(400, function() {
var workAreaResultItem = $('.workArea-result-item');
workAreaResultItem.on('click', function(event) {
event.preventDefault();
var selectedWorkArea = $(this).text().split("(")[0];
FetchJobListings(workAreaData.countyID,selectedWorkArea,function(jobListings,success) {
if (success) {
console.log(jobListings);
}
});
});
})
}
});
}
});
}
});
});
});
function FetchJobListings(countyID,selectedWorkArea,callback) {
$.post('Fetch.php', {fetch: 'jobListings',countyID : countyID, selectedWorkArea: selectedWorkArea}, function(data) {
if (data) {
callback(data,true);
}
});
}
function FetchWorkAreas(county,callback)
{
$.post('Fetch.php', {fetch: 'workAreasID',county:county}, function(data) {
if (data && data.workAreas.length > 0) {
callback(data,true);
}
});
}
function ListCounties(callback)
{
$.post('Fetch.php', {fetch: 'counties'}, function(data) {
if (data) {
if (data.length > 0) {
callback(data,true);
}
}
});
}
function RandomColor() {
var colors = ["#cea547","#7e8b58","#002c44","6da6a7"];
var rand = Math.floor(Math.random()*colors.length);
return colors[rand];
}
function FA(icon,size) {
var iconSize = '';
if (typeof size != undefined) {
iconSize = size;
}
return 'fa '+ icon+' fa-'+size;
}
function ShowLoader() {
if ($('.imgLoader').length === 0) {
var loaders = ["loading1.gif","loading2.gif","loading3.gif","loading4.gif"];
var rand = Math.floor(Math.random()*loaders.length);
$('#mainContainer').append('<div class="imgLoader"><img class="imgLoader img-responsive img-center" src="imgs/'+loaders[rand]+'" /><h3 class="text-center">Laster</3></div>');
}
}
function HideLoader() {
$('.imgLoader').fadeOut(400,function() {
$(this).remove();
});
}
});
This just hurt my eyes and I get a little sad really since it is important for me that code is readable and pretty, and not ending up looking like a fish ladder:
}
});
});
})
}
});
}
});
}
});
});
});
I am still what I call a novice when it comes to JavaScript and it may be something fundamentally I have not noticed. But basically, is there a way to keep the code so it doesn't indent 1000 times by the time I am done with it?
This is an example of borderline Spaghetti Code.
There are a few things you can do, the first being to split out all your functions so that they exist independently as smaller processes. For example:
ListCounties(function(counties,success) {
if (success) {
Rather than create an inline function (that isn't reusable), ask yourself what is it that I want to do with a list of counties, and build that independently.
// write a nice comment here to explain what the function does and what the arguments mean
function handleCountyList(countries, success){
}
Now you can call your ListCounties as follows:
ListCounties(handleCountyList);
By splitting out all your functions into more manageable pieces, you will avoid the ladder.
This should also make your code much more maintainable, as it becomes easier to read, think about, debug and update. Empathise with other developers, and ask, "If I showed my friend this code, could she easily understand what it does?"
If you want to get a bit more advanced, and callbacks are what is annoying you, try promises. They are semantically, and functionally better in my opinion, and allow you to do things like:
ListCounties().then(function(counties){
// do something with the counties
});
Promises come in different flavours, but this library is a good start: https://github.com/petkaantonov/bluebird
Name your callbacks and separate the logic, e.g.
ListCounties(function(counties,success) {
if (success) {....
becomes:
ListCounties(processCounties);
function processCounties(counties,success)
if (success) {...
}
Looking at your code there are a couple of things you could do, but before that it's much more important to be consistent across your codebase - with most IDEs allowing you to collapse blocks it'd be a little bit more difficult to deal with a shift in coding style than deep nesting
Guard Clauses
You could replace your
ListCounties(function(counties,success) {
if (success) {
...
with
ListCounties(function(counties,success) {
if (!success)
return;
...
to reduces 1 level of nesting. You can apply this for most of the checks that'd you do before deciding to proceed with the bulk of the code.
Be warned that this would this would make the code less readable if the guard condition is not very obvious. For instance while this is a good place for a guard clause (the guard clauses serves more like a filter)
KillWasps(function(bug) {
if (bug !== "wasp")
return
// code to deploy nuclear warheads
...
this would NOT be a good place for a guard clause (the guard block actually has program logic and 2nd block which executes for wasps is more of a fall-through block, just like a fall-through block for switch cases)
HandleInsect(function(bug) {
if (bug !== "wasp") {
// code to sign up bug for insects anonymous
return
}
// code to deploy nuclear warheads
...
An else block (and a thick magazine) is what I would use in this case.
Related reading : http://blog.codinghorror.com/flattening-arrow-code/
Delegated Event Handlers
I see you have one places where you do (for when you just added some .county-result-item elements)
var countyResultItem = $('.county-result-item');
countyResultItem.on('click', function (event) {
...
You might want to consider moving this to a delegated event handler
countyContainer.on('click', '.county-result-item', function(event) {
...
And you can move this from inside your current handler that does the append to an outer scope.
Of course, if you don't have any .county-result-item in your DOM this is a good way to perplex someone if they can't easily figure out where .county-result-item are / are added - this would be BAD, so keep your append logic close your delegated click handlers if that is the case.
Related reading : http://api.jquery.com/on/ (see the section on delegated events)
Other methods are already listed in other answers.
I've been cobbling together a function to put together a custom context menu for different nodes. Well, so far so good on getting different label to show up for clicks on folders or files, but not so much on actually deleting them.
Have a look. I had to ... do a little bit of a hacky workaround because I couldn't get the node.hasClass('jstree-open') yada yada to work right, but this is generally working up to the bit that's supposed to do the deleting
function customMenu(node) {
//Show a different label for renaming files and folders
var ID = $(node).attr('id');
if (ID == "j1_1") {
return items = {}; //no context menu for the root
}
var $mynode = $('#' + ID);
var renameLabel;
var deleteLabel;
var folder = false;
if ($mynode.hasClass("jstree-closed") || $mynode.hasClass("jstree-open")) { //If node is a folder
renameLabel = "Rename Folder";
deleteLabel = "Delete Folder";
folder = true;
}
else {
renameLabel = "Rename File";
deleteLabel = "Delete File";
}
var items = {
"rename" : {
"label" : renameLabel,
"action": function (obj) {
//nothing here yet.
}
},
"delete" : {
"label" : deleteLabel,
"action": function (obj) {
//tree.delete_node($(node));
//this.remove(obj);
//$('#treeView').jstree('remove', $(node));
//nothing is working.
}
}
};
return items;
}
I've put together a fiddle for your convenience: http://jsfiddle.net/dpzy8xjb/
I don't think it really needs to be said that I'm not super experienced with jQuery or dealing with third party APIs, so... Be gentle.
DO use tree.delete_node([node]); for delete.
Updated Fiddle
Edit:
The code you did is same as the node.
var ID = $(node).attr('id');
var $mynode = $('#' + ID);
Its the same object node.
I swear to god there is nothing that drives me to figure out a problem faster than posting it on StackOverflow.
Fixed:
"delete": {
"label": deleteLabel,
"action": function (obj) {
//tree.delete_node($(node));
tree.delete_node($mynode); //<<--works.
}
I had this problem, and none of the solutions worked. And as the documentation says:
all modifications to the tree are prevented (create, rename, move, delete). To enable them set core.check_callback to true.
In my case, I had a check_callback function (I'm using drag and drop) that was returning false when deleting a node.
I've adjusted it to 'delete_node' like this:
check_callback: function(operation, node, parent, position){
if(operation == 'delete_node'){
return true;
}
// ... rest of the code
}
The nodes cannot be deleted unless core.check_callback is set to true.
Here is my javascript. It was working well prior to conducting a Git Pull from my partner. On a click the hint loads with fancy box. The load does not work now.
Game = {
loadLevel: function(levelNum) {
$("#level").load("/level/" + levelNum + "/", function() {
// disable all hints except the first
$("#level .hint:not(:first)").prop('disabled', true);
// clicking a hint displays the hint
$("#level .hint").each(function(index, el) {
$(el).fancybox({
href : $(el).attr("data-url"),
type : 'ajax',
});
});
// enable next hint when clicking on a hint
$("#level .hint").on("click", function() {
$(this).next().prop('disabled', false);
});
// if answer is correct load next level
$("#answer").on("click", function() {
$.get("/answer/" + levelNum + "/", {
guess : $('.guess').val()
}, function(answer) {
console.log(answer);
if (answer) {
Game.loadLevel(levelNum+1);
}
});
});
});
},
}
From the error message, it sounds like your partner's code either has an infinatly looping recursive call somewhere or is calling too many functions deep.
Try this:
loadLevel: function(levelNum) {
if (levelNum > 5) return;
$("#level").load("/level/" + levelNum + "/", function() {
I think the problem might be here -- but it could be in code you don't show:
Game.loadLevel(levelNum+1);
This will recurse but there is no way to stop it.
I am writing a piece of code that changes some lights on a screen from red to green randomly and waits for the user to hit the key that corresponds to the light lit.
When I run this code you are able to hit the a,d,j or l key and an alert will pop up. However, as soon as I click the start button no keys are recognised. And when the loop has finished the bind still seems to become disabled. I have tried moving the bind to other places but I have had no joy. Your help is much appreciated.
$( function() {
$('#start').bind('click', function() { main(); });
$(document).bind('keypress', function(e) { keyPress(e); } );
} );
function getRand(val) {
return Math.floor(Math.random()*val)+1;
}
function main() {
preD = new Date;
preDs = preD.getTime();
randTime=Math.floor(Math.random()*1001)+1500;
playSound();
flash();
}
function flash() {
zone = getZone();
setTimeout(function() {
$('#r'+zone).css("background-image", "url(images/rea_grn.jpg)");
setTimeout(function() {
$('#r'+zone).css("background-image", "url(images/rea_red.jpg)");
if(cond[1] < 8) {
main();
}
} , 200);
} , randTime);
}
function getZone() {
if(condition==1) {
zone = getRand(2);
if( test[1][zone] < 8 ) {
test[1][zone] += 1;
cond[1] += 1;
return zone;
} else {
getZone();
}
}
}
function keyPress(e) {
var evtobj=window.event? event : e //distinguish between IE's explicit event object (window.event) and Firefox's implicit.
var unicode=evtobj.charCode? evtobj.charCode : evtobj.keyCode
var actualkey=String.fromCharCode(unicode)
if (actualkey=="a" || actualkey=="d" || actualkey=="j" || actualkey=="l" ) {
dd = new Date;
reat = dd.getTime();
alert(1);
//keypressed[condition][zone]['k']=actualkey;
//keypressed[condition][zone]['t']=(reat-preDs);
}
}
The reason that this could be happening is, when you generate code dynamically or alter any existing code the bind needs to be done again, because the function to bind just runs once and only for the members already created. So when you create dynamically code, you are forced to run the binding function to recognize the new elements.
this ways is not very recommended, instead of this, you could bind a container like 'div' or something and inside of this validate which element is calling you. This will work because your container is created once and the binding is properly assigned and doesn't matter if the content of your container changes, the binding always work.
Regards
Using a jquery sound plugin was the answer.
Fixed it with this : plugins.jquery.com/project/sound_plugin