jQueryUI sortable placeholder index incorrect - jquery-ui

I have a sortable list that has a corresponding data array, I need to change the order of the array when the list is reordered. I use this code
o.dom.$editor.find('.sortable').sortable({
start: function(event, ui) {
var start_pos = ui.item.index();
ui.item.data('startIndex', start_pos); // store original index
},
change: function(event, ui) {
var start_pos = ui.item.data('startIndex'); // get original index
var index = ui.placeholder.index() - 1; // get new index
console.log(start_pos, index); // log start and end positions for debugging
ui.item.data('startIndex', index); // store new index
}
});
As an example, let's say I have this list:
A
B
C
I hold list item A and drag it down to the bottom, the change event fires twice, first swapping item A with item B, then A with C, the console shows:
0 1
1 2
And it's correct. Now (WITHOUT releasing the mouse from the previous drag), I drag back item A to the top, again the change event fires twice, swapping A with C, then A with B, console shows:
2 1
1 0
All good.
Now if I release the mouse, and start dragging item B to the top, the change event fires once, swapping B with A, but console shows:
1 -1
Which is wrong. What's going on here? I've been trying to figure this out for hours.
jsfiddle: https://jsfiddle.net/4dtdk2qo/1/

Problem is in var index = ui.placeholder.index() - 1; line.
When you move item from up to down , index for ui.placeholder.index() starts from 1
when you move item from down to up , index for ui.placeholder.index() starts from 0
I have changed logic of getting index.
$(function(){
$('.sortable').sortable({
start: function(event, ui) {
var start_pos = ui.item.index();
ui.item.data('startIndex', start_pos);
},
change: function(event, ui) {
var start_pos = ui.item.data('startIndex');
var index = ui.placeholder.index();
index = (start_pos > index) ? index : index - 1;
$("#output").append(start_pos + ' ' + index + '<br>');
ui.item.data('startIndex', index);
}
});
})
li{
display: block;
background: lightgreen;
margin:20px 0;
}
#output{
width:100%;
min-height: 20px;
background: lightgray;
}
<link href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/smoothness/jquery-ui.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<ul class='sortable'>
<li>A</li>
<li>B</li>
<li>C</li>
</ul>
<div id="output">

Related

Create jQuery ui dialog box for each row in a table

I am trying to append rows to a table using an array called searchResults. Everything works as expected until I introduce the jQuery UI dialog box. The problem is I need a new dialog box for each row in the first column. I'm pretty new to all of this so I'm pretty sure I'm using the index incorrectly at times. This is just to give you an idea of what I'm trying to accomplish. Any ideas how to do this correctly?
for (var i = 0; i < searchResults.length; i++)
{
$('#patientFileDialog[i]').dialog();
$'#openPFDialog[i]').click(function() {
$('#patientFileDialog[i]').dialog('open');
});
var dialog[i] = $(`<div id="patientFileDialog[i]" title="Patient File">${searchResults[i].patientWebLink}</div>`);
body.append('<tr>'+
`<td><button id="openPFDialog[i]">Click Here</button></td>` +
`<td>${searchResults[i].patientFirstName}</td>` +
`<td>${searchResults[i].patientLastName}</td>` +
`<td>${searchResults[i].patientDateOfBirth}</td>` +
`<td>${searchResults[i].patientDataPulseID}</td>` +
`<td>${searchResults[i].patientLaserFicheID}</td>` +
'</tr>')
}
After looking at your code a bit more I think I can see what you are trying to do. Working JSFiddle, with some faked searchResults so we can see it in action.
There are a few problems with the code in your question:
Using selectors like $('#patientFileDialog[i]') and $'#openPFDialog[i]') will try to match elements on the page with those IDs. AFAICT those don't actually exist yet, you are trying to create them.
var dialog[i] = ... sets up some divs as strings, but those are never added to the page;
As I mentioned in my comment, there are some syntax errors, maybe just typos and mixed up formatting here on SO;
Here's an updated version of the code. Notable changes:
Instead of adding an event handler for every individual openPFDialog button, it is better practice to add just one which matches them all. That single handler can then work out which button was clicked, and take the right action for just that one, not all of them. In this case if you have all your buttons use IDs that match openPFDialog-X, where X is a number, you can target anything matching that pattern (using a starts with selector, and find the X by removing the openPFDialog- part with replace.
There's an added complication with the above though. Selectors parsed at page load will only match elements that exist at that time. In this case, you're adding new elements to the page, and a selector defined at page load won't match them. The solution is to select instead some parent element which does exist at page load, and filter. This is called event delegation (search for the paragraph starting with "Delegated event handlers").
Working from what you have, I am guessing the patientFileDialogs you create should be placed inside some parent element which is not displayed on the page? That's what I've done.
Here's the code (and working JSFiddle):
var dialog, i;
// Single click handler for anything that starts with "openPFDialog-".
// Since those elements don't exist on the page yet, we need to instead
// select a parent object, say the body, and filter for clicks on our
// elements starting with our pattern
$('body').on('click', '[id^=openPFDialog]', function() {
// We need to find the "i"
i = $(this).attr('id').replace(/openPFDialog-/,'');
console.log('clicked on id', i);
$('#patientFileDialog-' + i).dialog();
});
for (var i = 0; i < searchResults.length; i++) {
// Create a new div with ID like "patientFileDialog-1", using the current
// search result
dialog = $('<div id="patientFileDialog-' + i + '" title="Patient File">' + searchResults[i].patientWebLink + '</div>');
// Add it to the page. I've use a div with ID dialogs which is hidden
$('#dialogs').append(dialog);
$('table').append('<tr>'+
'<td><button id="openPFDialog-' + i + '">Click Here</button></td>' +
'<td>' + searchResults[i].patientFirstName + '</td>' +
'<td>' + searchResults[i].patientLastName + '</td>' +
'<td>' + searchResults[i].patientDateOfBirth + '</td>' +
'<td>' + searchResults[i].patientDataPulseID + '</td>' +
'<td>' + searchResults[i].patientLaserFicheID + '</td>' +
'</tr>');
}
Update
One last suggestion - manipulating the DOM by adding/removing elements is slow. If you need to do that for each element in an array, it is best to avoid actually adding your content on each iteration, and rather just build up a string. Then once you're done iterating, just add the big single string, so you're chaning the DOM just once. Here's the basic changes needed to do that:
// Add some new variables to hold our big strings
var dialog, dialogs, row, rows, i;
// ... your code ...
for (var i = 0; i < searchResults.length; i++) {
// Create the dialog ...
dialog = ...
// Append it to our big string of all dialogs
dialogs += dialog;
// Same approach for rows
row = '<tr>'+ ... all that stuff
rows += row;
}
// Finished iterating, nothing added to DOM yet. Do it all at once::
$('#dialogs').append(dialogs);
$('table').append(rows);
Here is what I finally ended up having to do:
$(document).ready(function(){
if ($('[attr="searchResultsJson"]').length)
{
$('.approval-outer-wrap').prepend(drawTable());
$('.approval-outer-wrap').append('<div id="result-details" title="Search Result Detail"><p></p></div>')
}
$('body').on('click', '[id^=openPFDialog]', function() {
var result = $(this).parents('tr').data('result');
$('#result-details p').html(result.patientFirstName);
$('#result-details').dialog();
});
});
function drawTable(){
var table = $('<table id="search-results" />');
var header = $('<thead />');
table.append(header);
header.append('<tr><th>Patient File</th><th>First Name</th><th>Last Name</th><th>Date of Birth</th><th>Data Pulse ID</th><th>Laserfiche ID</th></tr>');
var body = $('<tbody />');
table.append(body);
var json = $('[attr="searchResultsJson"] [type="text"]').text();
var searchResults = JSON.parse(json);
for (var i = 0; i < searchResults.length; i++) {
body.append(`<tr data-result='${JSON.stringify(searchResults[i])}'>`+
`<td><button id="openPFDialog-` + i + `">🔍</button></td>` +
`<td>${searchResults[i].patientFirstName}</td>` +
`<td>${searchResults[i].patientLastName}</td>` +
`<td>${searchResults[i].patientDateOfBirth}</td>` +
`<td>${searchResults[i].patientDataPulseID}</td>` +
`<td>${searchResults[i].patientLaserFicheID}</td>` +
'</tr>');
}
return table;
}
Consider the following code.
function showPatientDialog(cnt){
$("#patient-file-dialog").html(cnt).dialog("open");
}
var d = $("<div>", {
id: "patient-file-dialog",
title: "Patient File"
})
.appendTo("body")
.dialog({
autoOpen: false
});
$.each(searchResults, function(i, result) {
var row = $("<tr>").appendTo(body);
$("<td>").appendTo(row).html($("<button>", {
id: "open-pdf-dialog-" + i
}).click(function() {
showPatientDialog(result.patientWebLink);
}));
$("<td>").appendTo(row).html(result.patientFirstName);
$("<td>").appendTo(row).html(result.patientLastName);
$("<td>").appendTo(row).html(result.patientDateOfBirth);
$("<td>").appendTo(row).html(result.patientDataPulseID);
$("<td>").appendTo(row).html(result.patientLaserFicheID);
});

Revert or execute function in jQuery UI

I'm looking to revert the element to it's original position if it hasn't been dragged by more than a certain distance.
If the element has been dragged by more than the set distance on axis X I want to execute a function that will slide the dragged element out of the viewport and slide it back in. I got it to track the distance but now it somehow won't return function as true or false if the condition is not met... I get an error with D undefined. Does anyone know what could be wrong?
I have tried different solutions, but couldn't get neither one of them to work and wasn't able to find an answer on the web.
This what I have done so far: http://jsfiddle.net/DzEu2/4/
$(".video").draggable({
revert: $("#video").mousedown(function (e) {
$(this).data('p0', {
x: e.pageX,
y: e.pageY
});
}).mouseup(function (e) {
p0 = $(this).data('p0'),
p1 = {
x: e.pageX,
y: e.pageY
},
d = Math.sqrt(Math.pow(p1.x - p0.x, 2) + Math.pow(p1.y - p0.y, 2));
});
if (d > 100) {
return false;
} else {
return true;
}
start: function (event) {
x = event.originalEvent.pageX;
y = event.originalEvent.pageY;
console.log(x, y);
},
drag: function (event) {
if (x && y) {
axis = Math.abs(event.originalEvent.pageX - x) > Math.abs(event.originalEvent.pageY - y) ? 'x' : 'y';
$(".video").draggable('option', 'axis', axis);
x = y = null;
}
},
stop: function () {
x = y = null;
$(".video").draggable('option', 'axis', false);
},
distance: 20,
});
You are massively overdoing it. Measuring distance travelled on a single axis is rather trivial:
revert: function() {
var orig = $(this).data("uiDraggable").originalPosition.left;
var curr = $(this).data("uiDraggable").position.left;
return curr - orig > 100;
}
See a working update on your fiddle
Note that the name of the data object is different for different versions of jQueryUI. For the one you selected in the fiddle, it's draggable, for newer ones it's uiDraggable.
There are a number of other errors in your code, which I commented out in the fiddle:
You haven't actually surrounded the code you meant to put in revert in a function(){}
Event binders and asynchronous callbacks don't work like what you assume in mousedown and mouseup. The d variable in your d>100 condition will always be undefined
It's generally bad form to use a selector (".video") inside an event handler, when you can get the same object with this or event.currentTarget.
Don't end the last property in an object with a comma. Internet explorer randomly decides to hate that.
Summary? Code reviewing is your friend, don't forget about him! :)

Wicked pdf footer at the bottom of the last page

I render a pdf that doesn't have a fixed height.
I would like to append a footer at the bottom of the last page only, I have tried to play with css but it's still showing on the footer of the first page.
Any ideas?
At this moment I have a css like this
body {
# tried with height: 100% too
height: 269mm; # a standard A4 height
}
div.footer {
position: absolute; # tried with fixed too
bottom: 0px;
height: 200px;
}
I think that the best solution is working with javascript and checking when the last page is coming, but how can I do?
Another note, seems that having:
body { height: (nr_pages * 269)mm }
is a working solution (maybe not so nice). How can I retrieve the total number of the pages (inside the view)?
You can find last page by checking whether page attribute and topage attribute is same , in js you can use following snippet in page footer layout
function subst() {
var vars = {};
var x = document.location.search.substring(1).split('&');
for (var i in x) {
var z = x[i].split('=', 2);
vars[z[0]] = unescape(z[1]);
}
var x = ['frompage', 'topage', 'page', 'webpage', 'section', 'subsection', 'subsubsection'];
for (var i in x) {
var y = document.getElementsByClassName(x[i]);
for (var j = 0; j < y.length; ++j) y[j].textContent = vars[x[i]];
if (vars["topage"] == vars["page"]) {} else {
document.getElementById("pdf-footer").innerHTML = ""
}
}
}

jQuery UI draggable revert

I have 4 tables generated dynamically. There is two divs in each cell (td). All the divs are droppable. I dont know why but only the 2nd div accepts the draggable, the first one reverts it. There is no different between the two divs (exepts one has a "dotted" class).
Here is the jsbin:
http://jsbin.com/OMIbOGU/22/edit
JS
$("document").ready(function(){
function init(){
var table = $("<table></table>");
$("body").append(table);
for(var i=0 ;i < 9; i++){
var row = $("<tr></tr>");
var cell = $("<td></td>");
for(var j = 0;j<2;j++){
var slot = $("<div></div>").addClass('slot');
slot.droppable({
accept: ".unassigned"
});
if (j!==1)
slot.addClass("dotted");
cell.append(slot);
}
row.append(cell);
table.append(row);
}
$(".unassigned").draggable({
revert: "invalid",
snap: "td div"
});
}
for(var i = 0 ; i<4;i++)
init();
});
if you add
tolerance: 'touch'
to the droppable it works. Which points to it being more of a css issue than one with the script.
Updated JS BIN

JQuery Mobile - Dynamic Count Bubbles

I am creating a JQuery Mobile application that has a listview. I am populating that listview with the results of a web service. Because of this, the items in the list view are being populated as shown here:
$.each(results, function (i, result) {
var s = "<li><h2 style='padding-left:40px;'>" + result.title + "</h2><p style='padding-left:40px;'>";
s += result.subTitle;
s += "</p><span class='ul-li-count'>" + result.count + "</span></li>";
$("#resultListView").append(s);
});
$("#resultListView").listview("refresh");
My listview is being populated correctly. The value for the count bubble is showing. However, the UI does not render the bubble. Is there a way to dynamically build a result set with count bubbles in a list view? If so, how?
Thank you!
Your way should work. The only thing I can think of is that the HTML is not valid.
Anyway, I created a simple version to show that it's possible. http://jsfiddle.net/kiliman/HDUqp/
Basically, just build up the HTML for <li/> and append to the list, then call .listview('refresh')
$('#page1').bind('pageinit', function(e, data) {
var n = 0;
$('#addResult').click(function(e) {
var $list = $('#resultListView');
n++;
$('<li/>')
.append($('<h2>', { text: 'Title ' + n }))
.append($('<p>', { text: 'SubTitle ' + n }))
.append($('<span />', { text: n, class: 'ui-li-count'}))
.appendTo($list);
$list.listview('refresh');
});
});

Resources