UI-Sortable - Ajax and cancel does not work - jquery-ui

Im using AngularJs together with Ui-sortable (https://github.com/angular-ui/ui-sortable). My use case is basically for dragging & dropping in the same list.
The usual drag and drop an item of the list (I mean, update the position of an item) works. My issue comes when Im trying to add an Ajax call to check if the movement is allowed. If the movement is not allowed, it should be canceled. The canceling is the part that I cannot make run well.
Maybe with some code this is easier to understand:
$scope.sortableOptions = {
'ui-floating': false,
update : function(e, ui) {
// Check distance of movement
var diffIndex = _computeDiff(ui.item.sortable);
var moveObject = {"operation": "move", "value": diffIndex};
// Http call to do move
HttpApi.post('check', moveObject).then(function(){
// TESTING: Im trying to Cancel all the movements
// When this code is executed, sortable has already trigger stop.
// Canceling does not work anymore.
ui.item.sortable.cancel();
});
}
};
Any suggestions about how to handle it?

Related

Accessing HTMLCollection

So I am trying to access all of a specific class name and then eventually ad an event listener to them. I'm doing it this way because I am building a hightcharts graph and cannot add click events specifically to the legend items. So after the graph is build I am trying to access the buttons and then add the event listener.
getButtons() {
let buttons = document.getElementsByClassName('legend-btn');
console.log(buttons);
console.log(buttons[0]);
},
The first console.log comes back with an HTMLCollection with a length of 48 (I know very long but for now it's more testing purposes than anything).
The second console.log comes back as undefined. Any ideas why? I was hoping to do something like this:
for (let i = 0; i < buttons.length; i++) {
buttons[i].addEventListener('click', function () {
changebackground(event);
});
}
Any suggestions for how I can add the click event for the newly built highcharts graph?
May I suggest that you try:
for (let button of buttons) {
button.addEventListener('click', changebackground);
}
I am not sure why you intended to call changebackground(event): in order to do that, you'd have to put event in the event listener's parentheses. Better pass changebackground directly (assuming it does take an event as a parameter).
I'm not quite sure about what I'm telling you: hard to tell without seeing more of the context.

JQM - Inject dynamic content at load time only

I'm trying to dynamically populate a select tag at load time (latest jQM version) using a custom template filling function.
If the fn is called in the "pagebeforechange" event, the select tag is properly initialized. Since this event is called on every page transition, I thought of moving the fn to the 'pageinit' event. This does not work, presumably because the DOM is not yet fully available. How can I coerce jQM to inject content in a page only once? Currently, I am using a kludge. There surely must be a smarter way. Thanks for any suggestions.
$(document).bind('pageinit', function () {
InitSelTagTest("#selActTag", "tplTag"); // Does not work.
});
$(document).bind("pagebeforechange", function (e, data) {
if ($("#selActTag").children().size() === 0) {
InitSelTagTest("#selActTag", "tplTag"); // Kludge, but it works
}
});
function InitSelTagTest(el,tpl) { // Append all tags to element el
var lstAllTags = JSON.parse($("#hidTag").val()); // Create tag array
// Retrieve html content from template.
var cbeg = "//<![" + "CDATA[", cend = "//]" + "]>";
var rslt = tmpl(tpl, { ddd: lstAllTags }).replace(cbeg, ").replace(cend,");
$(el).html(rslt).trigger("create"); // Add to DOM.
}
EDIT
In response to Shenaniganz' comment, it seems that the "pagebeforecreate" event could do the trick ie.
$("#pgAct").live("pagebeforecreate", function () {
// Populate tag select. Works. Traversed only once.
InitSelTag("#selActTag", "tplTag");
});
I'm not sure I fully understand your question but I'll throw a few things out there and you let me know if I can extend further.
To make something trigger only once on page load you can try to implement a regular JQuery $(document).ready(function(){}) aka $(function(){}) for the exact reason why JQuery Mobile users are told not to use it. It triggers only once on DOM load. Further pages don't trigger it because they're being switched via Ajax.
Other than that, on regular dynamic content loading you take a look at the following example I put together for someone else earlier:
http://jsbin.com/ozejif/1/edit

jQuery Draggable + Sortable - How to reject a drop into the sort?

I have a sortable list of videos and a draggable set of videos. Basically I want to make sure that the videos dragged in are not in the first 5 minutes of video. As the video lengths vary I want to test this on the drop - add up the time up to then and if not 5mins revert and show an error.
I have tried hooking into all of the callbacks for draggable and sortable (including the undocumented revert callback) to do my test but whatever I try, the dom always gets changed (and sortable calls its update callback)...
Does anyone have any suggestions?
You can revert the drag operation by calling the cancel method of the draggable widget (that method is undocumented, but its name does not start with an underscore, which arguably makes it "safer" to use reliably). It only works during the start event, though, as other events occur too late to trigger the revert animation.
However, the sortable widget will still register a drop even if the drag operation is canceled, so you also have to remove the newly-added item (during the stop event, as the start event occurs too early):
$("#yourSortable").sortable({
start: function(event, ui) {
if (!canDropThatVideo(ui.item)) {
ui.sender.draggable("cancel");
}
},
stop: function(event, ui) {
if (!canDropThatVideo(ui.item)) {
ui.item.remove();
// Show an error...
}
}
});
You can see the results in this fiddle (the fourth item will always revert).
Update: As John Kurlak rightfully points out in the comments, the item does not revert because of the call to draggable("cancel"), but because ui.sender is null in our case. Throwing anything results in the same behaviour.
Alas, all the other combinations I tried result in the item being reverted without the animation taking place, so maybe our best bet is to avoid accessing ui.sender and instead write something like:
start: function(event, ui) {
if (!canDropThatVideo(ui.item)) {
throw "cancel";
}
}
The uncaught exception will still appear in the console, though.
I found a different way. If you dont mind not having the animation of it floating back to it's original place you can always use this
.droppable({
drop: function (event, ui) {
var canDrop = false;
//if you need more calculations for
//validation, like me, put them here
if (/*your validation here*/)
canDrop = true;
if (!canDrop) {
ui.draggable.remove();
}
else{
//you can put whatever else you need here
//in case you needed the drop anyway
}
}
}).sortable({
//your choice of sortable options
});
i used this because i needed the drop event either way.

jQuery UI: moving items from one list to another

While this should be relatively straightforward, I can't figure out how to move (rather than copy) LI elements between ULs.
All I want is to drag any item from list foo to list bar (or vice versa) without duplicating the element.
While connectToSortable does almost exactly what I want (though I'd prefer to avoid sortable), it clones the element - and manually removing the original element by reacting to the stop event turns out to be not so easy (e.g. ensuring that a valid drop actually happened).
A simple "hello world" example would help me greatly.
A Hello World example can be found here: http://jqueryui.com/demos/sortable/#connect-lists. You don't need a draggable at all, only a sortable.
$(function() {
$("#sortable1, #sortable2").sortable({
connectWith: '.connectedSortable'
}).disableSelection();
});
If you want to disallow the sorting of items within one list, this may be a way to go. It's not the most beautiful UI though (the user is given false hope), and the user is also free to determine the drop position in a foreign list.
$(function() {
var sender;
var recvok = false;
$("#sortable1, #sortable2").sortable({
connectWith: '.connectedSortable',
start: function() {
sender = $(this);
recvok = false;
},
over: function() {
recvok = ($(this).not(sender).length != 0);
},
stop: function() {
if (!recvok)
$(this).sortable('cancel');
}
}).disableSelection();
});
This is a really horrible function working around what I feel is an incompleteness in jQuery UI. It saves the sender on sortstart and takes down a flag allowing drop. When another receiver is entered, it checks if it's not the sender and puts the flag up. On sortstop the flag is checked. Warning: this function is ugly to the eye of both the user and the programmer, but it works.

jQuery UI Sortable: Revert changes if update callback makes an AJAX call that fails?

I am using the sortable widget to re-order a list of items. After an item is dragged to a new location, I kick off an AJAX form post to the server to save the new order. How can I undo the sort (e.g. return the drag item to its original position in the list) if I receive an error message from the server?
Basically, I only want the re-order to "stick" if the server confirms that the changes were saved.
Try the following:
$(this).sortable('cancel');
I just encountered this same issue, and for the sake of a complete answer, I wanted to share my solution to this problem:
$('.list').sortable({
items:'.list:not(.loading)',
start: function(event,ui) {
var element = $(ui.item[0]);
element.data('lastParent', element.parent());
},
update: function(event,ui) {
var element = $(ui.item[0]);
if (element.hasClass('loading')) return;
element.addClass('loading');
$.ajax({
url:'/ajax',
context:element,
complete:function(xhr,status) {
$(this).removeClass('loading');
if (xhr.status != 200) {
$($(this).data('lastParent')).append(this);
}
},
});
}
});
You'll need to modify it to suit your codebase, but this is a completely multithread safe solution that works very well for me.
I'm pretty sure that sortable doesn't have any undo-last-drop function -- but it's a great idea!
In the mean time, though, I think your best bet is to write some sort of start that stores the ordering, and then on failure call a revert function. I.e. something like this:
$("list-container").sortable({
start: function () {
/* stash current order of sorted elements in an array */
},
update: function () {
/* ajax call; on failure, re-order based on the stashed order */
}
});
Would love to know if others have a better answer, though.

Resources