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.
Related
I am very new to Javascript.
I am trying to write this baby jQuery plugin that I will use to make dropdown lists. What I am failing to achieve (beyond things that I do not notice) is to neatly exit or deactivate my active instance as I click on another instance. I tried to illustrate my problem in the following fiddle (keeping the structure I am using):
https://jsfiddle.net/andinse/m0kwfj9d/23/
What the Javascript looks like:
$(document).ready(function() {
$.fn.activator = function() {
var Activator = function(el) {
this.html = $('html');
this.el = el;
this.is_active = false;
this.initialize();
};
Activator.prototype.initialize = function() {
var self = this;
self.el.on('click', function(e) {
if (self.is_active === false) {
self.toggle('activate');
} else {
self.toggle('deactivate');
}
});
};
Activator.prototype.toggle = function(action) {
var self = this;
if (action === 'activate') {
console.log('activating ' + self.el[0].className);
self.is_active = true;
self.el.addClass('red');
self.html.on('click', function(e) {
if (e.target != self.el[0]) {
self.toggle('deactivate');
}
});
}
if (action === 'deactivate') {
console.log('deactivating ' + self.el[0].className);
self.is_active = false;
self.el.removeClass('red');
self.html.off('click');
}
};
if (typeof this !== 'undefined') {
var activator = new Activator(this);
}
return this;
};
$('.a').activator();
$('.b').activator();
$('.c').activator();
});
My idea was:
To watch for clicks on html as soon as the instance is active (thus ready to be deactivated). On click, to check if the event.target is the same as the active instance. If not, to deactivate this instance.
To stop watching for clicks as soon as the instance is inactive. So that we're not doing unnecessary work.
When it is set like this, it seems to work for only one cycle (click on A activates A then click on B activates B and deactivates A then click on C activates C but doesn't deactivate B).
If I get rid of the "self.html.off('click')" it seems to work kind of ok but if I look at the log I can see the "toggle" function is sometimes triggered multiple times per click. There must be a cleaner way.
Any piece of help greatly appreciated.
With your logic, when clicking any element you should deactivate any current activated element. Either do it globally:
$('.your_activation_class').removeClass('.your_activation_class');
or in some parent scope
$('some_parent_selector .your_activation_class').removeClass('.your_activation_class');
I'm trying to use this Javascriptsnippet below to display a DIVin a view Dynamically. But it gives me this error new:624 Uncaught SyntaxError: missing ) after argument list for this line in the snippet $("#order_country"]).change(function() {
window.onload = function() {
$("#order_country"]).change(function() {
var val = $(this).val();
$("#country_div").toggle(val == "us");
});
});
I really can't find the missing ), could anyone take a look at this and see if they can find it.
In order to make the line appear and disappear dynamically, you'll need to use javascript:
$(function() {
$("#id-you-give-to-country_select"]).change(function() {
var val = $(this).val();
$("#id-you-give-to-div").toggle(val == "us");
});
});
One gotcha is that in your if you're comparing :country (symbol) with "US" (string), which will never succeed.
Edit
Just for testing, you can also add alert(val); right after the call to toggle, so you can see that the code is running and what the values are.
EDIT - THIS VERSION WORKS
First, the country-select has an ID generated in the html, it is #order_country
Then I had to make few adjustments to the code, below is the working version
window.onload = function() {
$("#order_country").change(function() {
var val = $(this).val();
$("#country_div").toggle(val == "US");
});
};
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 written a few events to handle opening and closing of a snap js drawer. This code below works, but I feel it could be written more efficiently. Any suggestions?
function openMobileMenu() {
event.preventDefault();
snapper.open('left');
$('#btn-menu').off('click', openMobileMenu);
$('#btn-menu').on('click', closeMobileMenu);
}
function closeMobileMenu() {
event.preventDefault();
snapper.close('left');
$('#btn-menu').on('click', openMobileMenu);
$('#btn-menu').off('click', closeMobileMenu);
}
$('#btn-menu').on('click', openMobileMenu);
Make your code modular and your concepts explicit.
You can start by creating a MobileMenu object which encapsulates the logic.
Note: The following code was not tested.
var MobileMenu = {
_snapper: null,
_$button: null,
_direction: 'left',
init: function (button, snapper, direction) {
this._$button = $(button);
this._snapper = snapper;
if (direction) this._direction = direction;
this._toggleSnapperVisibilityWhenButtonClicked();
},
_toggleSnapperVisibilityWhenbuttonClicked: function () {
this._$button.click($.proxy(this.toggle, this));
},
toggle: function () {
var snapperClosed = this._snapper.state().state == 'closed',
operation = snapperClosed? 'open' : 'closed';
this._snapper[operation](this._direction);
}
};
Then in your page you can just do the following to initialize your feature:
var mobileMenu = Object.create(MobileMenu).init('#btn-menu', snapper);
Modularizing your code will make it more maintainable and understandable in the long run, but also allow you to unit test it. You also gain a lot more flexibily because of the exposed API of your component which allows other code to interact with it.
E.g. you can now toggle the menu visibility with mobileMenu.toggle().
Use a variable to keep track of the state:
var menu_open = false;
$("#btn-menu").on('click', function(event) {
event.preventDefault();
if (menu_open) {
snapper.close('left');
} else {
snapper.open('left');
}
menu_open = !menu_open; // toggle variable
});
snap has a .state() method, which returns an object stuffed with properties, one of which is .state.
I think you want :
$('#btn-menu').on('click', function() {
if(snapper.state().state == "closed") {
snapper.open('left');
} else {
snapper.close('left');
}
});
Or, in one line :
$('#btn-menu').on('click', function() {
snapper[['close','open'][+(snapper.state().state == 'closed')]]('left');
});
Also, check How do I make a toggle button? in the documentation.
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;
}
}