jquery ui connected sortables: cancel, disable, destroy... nada - jquery-ui-sortable

I have two connected sortable lists.
For the sake of my question, when I start dragging an item from #sortable1 to #sortable2, in the start event I want to cancel/ disable/ the drop in #sortable2
Nothing works?
$("#sortable1, #sortable2").sortable({
connectWith: "#sortable1, #sortable2",
start: startDrag
});
function startDrag(event, ui) {
$("#sortable2").css("opacity","0.5");
// $("#sortable2").sortable("cancel"); // goes whooooo
/* docs say:
If the sortable item is being moved from one connected sortable to another:
$(ui.sender).sortable('cancel');
will cancel the change. Useful in the 'receive' callback.
*/
// $("#sortable1").sortable("cancel"); // I only want to cancel dropping in sortable2...
// $("#sortable2").sortable("disable"); // disables only after the drop event
// $("#sortable2").sortable("destroy"); // same as "disable"
}
function stopDrag(event, ui) {
$("#sortable2").css("opacity","1.0");
// $("#sortable2").sortable("enable");
}
My JSFiddle here: http://jsfiddle.net/tunafish/m32XW/
I have found 2 more questions like mine:
jQuery sortable('disable') from start event not entirely working like expected
http://forum.jquery.com/topic/disable-a-sortable-while-dragging-with-connectedlists
No responses..
Anything appreciated!
EDIT: I also tried to overlay a div like a modal on the sortable, but can still be dragged to that way. The only thing that worked is a show/hide, but that's not an option for me.

OK here is my app; two lists of images, sortable and you can copy over from the connected list.
If an item already exists in the target it's disabled.
Hopefully useful to someone...
JSFiffle here: http://jsfiddle.net/tunafish/VBG5V/
CSS:
.page { width: 410px; padding: 20px; margin: 0 auto; background: darkgray; }
.album { list-style: none; overflow: hidden;
width: 410px; margin: 0; padding: 0; padding-top: 5px;
background: gray;
}
.listing { margin-bottom: 10px; }
.album li { float: left; outline: none;
width: 120px; height: 80px; margin: 0 0 5px 5px; padding: 5px;
background: #222222;
}
li.placeholder { background: lightgray; }
JS:
$("ul, li").disableSelection();
$(".album, .favorites").sortable({
connectWith: ".album, .favorites",
placeholder: "placeholder",
forcePlaceholderSize: true,
revert: 300,
helper: "clone",
stop: uiStop,
receive: uiReceive,
over: uiOver
});
$(".album li").mousedown(mStart);
var iSender, iTarget, iIndex, iId, iSrc, iCopy;
var overCount = 0;
/* everything starts here */
function mStart() {
// remove any remaining .copy classes
$(iSender + " li").removeClass("copy");
// set vars
if ($(this).parent().hasClass("listing")) { iSender = ".listing"; iTarget = ".favorites"; }
else { iSender = ".favorites"; iTarget = ".listing"; }
iIndex = $(this).index();
iId = $(this).attr("id");
iSrc = $(this).find("img").attr("src");
iCopy = $(iTarget + " li img[src*='" + iSrc + "']").length > 0; // boolean, true if there is allready a copy in the target list
// disable target if item is allready in there
if (iCopy) { $(iTarget).css("opacity","0.5").sortable("disable"); }
}
/* when sorting has stopped */
function uiStop(event, ui) {
// enable target
$(iTarget).css("opacity","1.0").sortable("enable");
// reset item vars
iSender = iTarget = iIndex = iId = iSrc = iCopy = undefined;
overCount = 0;
// reinit mousedown, live() did not work to disable
$(".album li").mousedown(mStart);
}
/* rolling over the receiver - over, out, over etc. */
function uiOver(event, ui) {
// only if item in not allready in the target
if (!iCopy) {
// counter for over/out (numbers even/uneven)
overCount++;
// if even [over], clone to original index in sender, show and fadein (sortables hides it)
if (overCount%2) {
if (iIndex == 0) { ui.item.clone().addClass("copy").attr("id", iId).prependTo(iSender).fadeIn("slow"); }
else { ui.item.clone().addClass("copy").attr("id", iId).insertAfter(iSender + " li:eq(" + iIndex + ")").fadeIn("slow"); }
}
// else uneven [out], remove copies
else { $(iSender + " li.copy").remove(); }
}
// else whoooooo
}
/* list transfers, fix ID's here */
function uiReceive(event, ui) {
(iTarget == ".favorites") ? liPrefix = "fli-" : liPrefix = "lli-";
// set ID with index for each matched element
$(iTarget + " li").each(function(index) {
$(this).attr("id", liPrefix + (index + 1)); // id's start from 1
});
}
HTML:
<div class="page">
<div class="container">
<h2>Photo Album</h2>
<ul class="listing album">
<li id="li-1"><img src="tn/001.jpg" /></li>
<li id="li-2"><img src="tn/002.jpg" /></li>
<li id="li-3"><img src="tn/003.jpg" /></li>
<li id="li-4"><img src="tn/004.jpg" /></li>
<li id="li-5"><img src="tn/005.jpg" /></li>
</ul>
</div>
<div style="clear:both;"></div>
<div class="container">
<h2>Favorites</h2>
<ul class="favorites album">
<li id="fli-1"><img src="tn/001.jpg" /></li>
<li id="fli-2"><img src="tn/002.jpg" /></li>
<li id="fli-3"><img src="tn/010.jpg" /></li>
</ul>
</div>
</div>

/* docs say:
If the sortable item is being moved from one connected sortable to another:
$(ui.sender).sortable('cancel');
will cancel the change. Useful in the 'receive' callback.
*/
This code was what I spent 30 minutes looking for!

Ok I found some sort of hack for this.
When an item is moved from #sortable1 to #sortable2, when dragged over #sortable2 there is a list item added with class .placeholder
So in UI's over event I did
$("#sortable2 li.placeholder").hide();
Than set it back with UI's stop event
$("#sortable2 li.placeholder").show();
This is only for visuals though.. The item is still moved into #sortable2 so you need to remove() it there. To mimic copying you need to add a clone() back in #sortable2. You can get it's original index() in UI's start event and than use insertAfter(id - 1)
At the moment I can only clone in UI's receive event, I would prefer in UI's over but can't get it to work..

Related

Convert Excel form window to Google Sheets window

As you've probably found, there appears to be no equivalent way to add the following Excel form and associated VBA code to Google Sheets or Scripts or Forms:
Is there some add-in that can be used to pop up this image and its controls? This has to be used many times in an accounting sheet to categorize expenditures at tax time.
It may not look exactly the same but I was able to construct a custom dialog in a short period of time to show how HTML service can be used to produce similar results.
First I construct an HTML template that contains the 2 combo boxes with multiple lines.
HTML_Test.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<?!= include('CSS_Test'); ?>
</head>
<body>
<div id="left">
<label for="expenseCategory">Expense Category</label><br>
<select id="expenseCategory" size="10">
</select>
</div>
<div id="middle">
<label for="expenseSubCategory">Expense Sub Category</label><br>
<select id="expenseSubCategory" size="10">
</select>
</div>
<?!= include('JS_Test'); ?>
</body>
</html>
Then a CSS file to contain all my element formatting.
CSS_Test.html
<style>
#expenseCategory {
width: 90%;
}
#expenseSubCategory {
width: 90%;
}
#left {
width: 25%;
float: left;
}
#middle {
width: 50%;
float: left;
}
</style>
And a javascript file for client side javascript. I've simply hard coded some data to show how the select elements are filled in but this could just as easily be done using template scripting, or google.script.run
<script>
var expenses = [["A","1","2","3"],
["B","4","5"],
["C","6","7","8","9","10"]
];
function expenseCategoryOnClick() {
try {
let expenseCategory = document.getElementById('expenseSubCategory');
expenseCategory.options.length = 0;
expenses[this.selectedIndex].forEach( (expense,index) => {
if( index > 0 ) {
let option = document.createElement("option");
let text = document.createTextNode(expense);
option.appendChild(text);
expenseCategory.appendChild(option);
}
}
);
}
catch(err) {
alert("Error in expenseCategoryOnClick: "+err)
}
}
(function () {
// load first expense
let expenseCategory = document.getElementById('expenseCategory');
expenseCategory.addEventListener("click",expenseCategoryOnClick);
expenses.forEach( expense => {
let option = document.createElement("option");
let text = document.createTextNode(expense[0]);
option.appendChild(text);
expenseCategory.appendChild(option);
}
);
expenseCategory = document.getElementById('expenseSubCategory');
expenses[0].forEach( (expense,index) => {
if( index > 0 ) {
let option = document.createElement("option");
let text = document.createTextNode(expense);
option.appendChild(text);
expenseCategory.appendChild(option);
}
}
);
}
)();
</script>
Then there is the server side code bound to a spreadsheet.
Code.gs
function onOpen(e) {
var menu = SpreadsheetApp.getUi().createMenu("Test");
menu.addItem("Show Test","showTest");
menu.addToUi();
}
// include(filename) required to include html files in the template
function include(filename) {
return HtmlService.createHtmlOutputFromFile(filename)
.getContent();
}
function showTest() {
var html = HtmlService.createTemplateFromFile("HTML_Test");
html = html.evaluate();
html.setWidth(800);
SpreadsheetApp.getUi().showModalDialog(html,"Test");
}
The dialog looks like this. Many more html elements can be added as needed. This just shows the basics. This may be more difficult than an wysiwig html editor but I find I have better control of the appearance and function of my pages this way. Notice I clicked "C" and the sub category is filled in automatically.

DOM changes execute out of order in my Angular2 component

I have an Angular2 component that updates an imported HTML file that I am annotating and allowing the user to edit. To do this I am updating the DOM directly using plain HTML5. On click a class is supposed to be added to the clicked DOM element. In the browser (FF and Chrome on linux) it gets added temporarily - meaning that if I print the node immediately I see the class, but after I exit the function and print it I do not. Inspect Element shows that the element is updated to <.... class> with no value when the element is clicked.
This was working when I implemented it without Angular.
I tried direct DOM manipulation (with classList.add and x.className=y), angular's Renderer, and running the code via NgZone, and all had the same effect. Can someone tell me why this is happening and what I can do to fix it?
Note: this is (supposed to be) a simple internal tool - at this point I need a quick fix (though additional advice is welcome as well).
(A few irrelevant parts of the code that are not fully implemented yet were omitted here.)
import { Component, OnInit, NgZone } from "#angular/core";
import { Http, Headers, RequestOptions } from "#angular/http";
#Component({
selector: "editor",
templateUrl: `editor/starterDemo.html`
})
export class EditorComponent implements OnInit {
ngOnInit() {
this.fixImagePaths();
this.annotate();
}
constructor(public http: Http, public zone: NgZone) { }
// constructor(public http: Http, public renderer: Renderer) { }
fixImagePaths = () => {
const els = document.querySelectorAll('img');
for (let i: number = 0, el: any; (el = els[i]); i++) {
el.src = 'editor/' + el.getAttribute('src');
}
}
annotate = () => {
document.body.addEventListener('dragover', this.drag_over);
document.body.addEventListener('click', this.selectNone);
document.body.addEventListener('keydown', this.keyDown);
const els = document.querySelectorAll('img, .box');
for (let i: number = 0, el: any; (el = els[i]); i++) {
el.draggable = true;
el.addEventListener('dragstart', this.drag_start);
el.addEventListener('click', this.selectItem);
}
}
selectItem = (event) => {
console.log("select item!");
this.selectNone();
// this.zone.run(() => {
event.target.closest('[draggable]').classList.add('dragme');
// });
// this.renderer.setElementClass(event.target.closest('[draggable]').nativeElement, 'dragme', true);
// console.log(event.target.closest('[draggable]'));
// console.log(event.target.closest('[draggable]').classList);
// console.log(event.target.closest('[draggable]').classList.contains('dragme'));
console.log(this.selected());
return false;
}
selectNone = () => {
this.selected() && this.selected().classList.remove('dragme');
}
selected = (): any => document.getElementsByClassName('dragme')[0];
}
HTML:
<div class='box'
style="background-color: white; position: absolute; top: 847px; left: 842px; z-index: 20; height: 118px; width: 351px"></div>
<img src="Screenshot1.png"
style="position: absolute; top: 0px; left: -1px" />
<img src="Screenshot2.png"
style="position: absolute; top: 913px; left: 0px; z-index: 2"/>
<img src="Screenshot3.png"
style="position: absolute; top: 1768px; left: 0px" />
<div style="height: 2000px"> </div>
I just figured out the problem (after hours of anguish). It seems that on linux chrome, return false is not good enough to prevent event propagation, and so the click event was running twice, once before and once after the addClass call. Since the click event has a selectNone() in the beginning (to clear the existing selected item), it ran selectNone() before and after adding the class. I added event.preventDefault(); and event.stopPropagation(); to my event handlers.
I hope this helps someone.

CSS3 animation every time class is applied (Firefox bug?)

There is some problem with CSS3 native animation I've bumped into with Firefox (tried in v17) while Chrome is ok.
The sample is also available on Codepen:
http://codepen.io/anon/pen/yxteC
The code is also duplicated below.
I apply some class to some element (tried to apply that with JS on hovering, but hovering by itself works absolutely the same way (:hover selector instead of .hover class)).
So, the problem is that in Firefox the animation triggers only once: the first time the class had been applied. Any next time it does not.
That works perfectly in Chrome. I did not test any other browsers though (IE still does not widely support that, and Opera... well, it does not bother me much for now).
One more detail: if not to hide/show internal element (display:none/block), then it works ok in Firefox (but that's obviously not a solution).
I would really ask not to add any comments related to general cleanliness and possible improvements of the code. That's just an example.
HTML:
<div>
<span>menu</span>
<ul>
<li>Item1</li>
<li>Item2</li>
<li>Item3</li>
</ul>
</div>
CSS:
ul
{
display: none;
}
div.hover ul
{
display: block;
}
div li
{
position: relative;
top: -10px;
}
div.hover li
{
-webkit-animation: filterAppear 0.5s;
-moz-animation: filterAppear 0.5s;
top: 0;
opacity: 1;
-webkit-animation: filterItemAppear 0.1s;
-moz-animation: filterItemAppear 0.1s;
-webkit-animation-fill-mode: backwards;
-moz-animation-fill-mode: backwards;
}
div.hover li:nth-child(1)
{
-webkit-animation-delay: 0.15s;
-moz-animation-delay: 0.15s;
}
div.hover li:nth-child(2)
{
-webkit-animation-delay: 0.35s;
-moz-animation-delay: 0.35s;
}
div.hover li:nth-child(3)
{
-webkit-animation-delay: 0.55s;
-moz-animation-delay: 0.55s;
}
#-webkit-keyframes filterItemAppear
{
from { opacity:0; top:-25px; }
to { opacity:1; top:0; }
}
#-moz-keyframes filterItemAppear
{
from { opacity:0; top:-25px; }
to { opacity:1; top:0; }
}
I had similar problem and today I'll find the solution.
Firefox must notice the DOM manipulation somehow. So we must add .width() or setTimeout. Width solution is cleaner IMO.
Look at my edited CodePen for your example http://cdpn.io/yhbfB.
(More about it at http://css-tricks.com/restart-css-animation/)

IE9 multiple select overflow while printing

I'm having problems with IE9 ignoring the select borders when printing a multiple select.
Here's how to recreate the problem:
Open IE9 on Windows 7.
Go to w3schools's multiple select edit page.
Now highlight the options and copy/paste until there is a long list of duplicates.
Then remove the size attribute.
Click on "Edit and Click Me" so that the page reloads and you now have your modified select in the second panel.
Now, print the document (even using the XPS viewer).
For me, all of the options are printed on the page, even though the select is only 4 option elements tall. This still happens to some degree if you leave the "size" attribute at the default value of 2, but it's far more obvious when it is changed or removed.
Is anyone else experiencing this? Is this an IE bug? Does anyone know of a workaround?
You can work around this by viewing the site in IE9's compatibility mode. Usually IE will determine that it cannot display a site properly and give you the option to turn on compatibility mode from within the address bar but sometimes you need to explicitly set it.
How to turn on compatibility mode - http://www.sevenforums.com/tutorials/1196-internet-explorer-compatibility-view-turn-off.html - I used the first one in method 2.
There doesn't seem to be any CSS solution for this. Instead, I wrote a small jQuery script that copies the <select multiple> contents into a <div>, so that it can be printed. Then I applied some CSS to make it look like a select, and only show the copy when actually printing.
Script:
//jQuery required
$(function() {
if(!$.browser.msie) return false;
$('select[multiple]').each(function() {
$lPrintableDiv = $('<div data-for="' + this.id + '" />').addClass($(this).attr('class')).addClass('printable');
//update printable on changes
$(this).after($lPrintableDiv).change(function($aEvent){
updatePrintable($aEvent.target);
});
//run once on load
updatePrintable(this);
});
});
function updatePrintable($aTarget) {
var $lSelect = $($aTarget);
var $lSelected = $($aTarget).val();
var $lPrintable = $('[data-for="'+$aTarget.id+'"]');
$($lPrintable).width($lSelect.width()).height($lSelect.height());
$($lPrintable).html('');
$($aTarget).children().each(function($lElm){
$lVal = $(this).val();
$lLabel = $('<label />').text($lVal);
$lOption = $('<input type="checkbox" />').val($lVal);
if($(this).is(':selected'))
$lOption.prop('checked', true);
$lPrintable.append($lOption).append($lLabel);
});
}
CSS:
.printable {
border: 1px solid grey;
display: none;
overflow: auto;
}
.printable label {
display: block;
font: .8em sans-serif;
overflow: hidden;
white-space: nowrap;
}
.printable [type="checkbox"] {
display: none;
}
.printable [type="checkbox"]:checked + label {
background: #3399ff;
color: white;
}
#media print {
select[multiple] { display: none; }
.printable { display: inline-block; }
.printable [type="checkbox"]:checked + label { background: grey; }
}
Also see the jsFiddle and original post about this fix

Display an icon in jQuery UI autocomplete results

I'm using the jQuery UI Autocomplete plugin (version 1.8), and I'd like to customize the way the suggestions show up. Specifically, I want to display not only some text, but an icon as well. However, when I send the <img> tag, it just gets rendered as plain text in the results list.
Is there some way to change this behavior? Alternatively, can you suggest a different way to include images in the returned results and have them show up in the suggestions?
Taken from here
$("#search_input").autocomplete({source: "/search",
minLength: 3,
select: function (event, ui) {
document.location = ui.item.url;
}
})
.data("autocomplete")._renderItem = function (ul, item) {
//As per recent documemtation above line should be
//.autocomplete( "instance" )._renderItem = function (ul, item) {
return $('<li class="ui-menu-item-with-icon"></li>')
.data("item.autocomplete", item)
.append('<a><span class="' + item.type + '-item-icon"></span>' + item.label + '</a>')
.appendTo(ul);
};
And the CSS:
.ui-menu .ui-menu-item-with-icon a {
padding-left: 20px;
}
span.group-item-icon,
span.file-item-icon {
display: inline-block;
height: 16px;
width: 16px;
margin-left: -16px;
}
span.group-item-icon {
background: url("/image/icons/group.png") no-repeat left 4px;
}
span.product-item-icon {
background: url("/image/icons/product.png") no-repeat left 7px;
}