jQuery UI autocomplete widget - how to get a reference to the menu? - jquery-ui

I want to be able to get a reference to the menu object that autocomplete builds, (so I can get the .attr("id") for example), but I'm not very familiar with jQuery/javascript. In the source, I found this:
https://github.com/jquery/jquery-ui/blob/1-9-stable/ui/jquery.ui.autocomplete.js#L182
so there is an object flying around, I just can't seem to find how to get hold of it.
So, for example, if I've got an input with an autocomplete bound to it like this:
// input = reference to the input text box on the form
input.autocomplete({
select: function(event, ui) {
// how to get the reference here?
// some things I've tried
// return input.menu
// return input.data("menu")
// and a few others but they didn't work either
}
});
I tried looking at the data object itself, but there were so many options I could spend all day looking at it and still not find what I'm looking for.

You can get the widget's reference by looking into dataset assigned to its root element (input). Then fetching menu property (and its underlying element) is kinda trivial. )
select: function(event, ui) {
// that's how get the menu reference:
var widget = $(this).data('ui-autocomplete'),
menu = widget.menu,
$ul = menu.element,
id = $ul.attr('id'); // or $ul[0].id
}
... as this within select function refers to the <input> when this function called as an event handler.

A simpler way to do this:
$(this).autocomplete('widget');
It does the same as:
select: function(event, ui) {
// that's how get the menu reference:
var widget = $(this).data('ui-autocomplete'),
menu = widget.menu,
$ul = menu.element,
id = $ul.attr('id'); // or $ul[0].id
}
It gives the ul list
$(this).autocomplete('widget').attr('id');

Related

How to sync model watched by ngRepeat when the repeated DOMs are modified externally?

I have two lists which are rendered by my directive. The requirement is that user can move an item from one list to another. I have a simplified implementation of this below:-
http://jsfiddle.net/yK7Lt/
The above shows a demo of how it should behave. Notice in this I manipulate the model and the DOM auto-syncs with it.
However, the problem is I am using jquery-ui-sortable plugin. So, the user can drag and drop the item from one list to another. Since jQuery is unaware of AngularJs so it modified the DOM. Now in my directive I have placed the code to sync the underlying model with the changed DOM.
The below jsfiddle code is a simplified version of my code.
http://jsfiddle.net/5Xuz2/1/
The relevant code snippet is:-
$('#btn').on('click', function () {
var li = $('#left li').first().detach();
$('#right').prepend(li);
console.log('moved top DOM to right list');
angular.element('#left').scope().$apply(function () {
// The moment this code runs, the DOM related to i is
// marked with $$NG_REMOVED, and is removed from page.
// Also somehow the DOM related to item D too is removed.
i = itemsl.shift(); // i is global variable.
});
angular.element('#right').scope().$apply(function () {
itemsr.unshift(i);
console.log('synced data with DOM');
});
});
The problem I am facing with my implementation is that the right list empties out as soon as I sync my left list model.
What is wrong with my implementation?
Is there a better approach?
the problem here is you are manipulating DOM with both Angular and jQuery... if you remove this piece of code
var li = $('#left li').first().detach();
$('#right').prepend(li);
it is working as expected
btw. I suggest trying angular-UI instead of jQueryUI
edit: OR you can try to refactor your code to something like this
var itemsl, itemsr, i, move;
function Model(name) {
this.name = name;
}
function Ctrl($scope) {
itemsl = $scope.itemsl = [new Model('A'), new Model('B'), new Model('C')];
itemsr = $scope.itemsr = [new Model('D')];
move = function() {
$scope.$apply(function() {
i = itemsl.slice(0,1);
itemsl.splice(0,1);
itemsr.unshift(i[0]);
i = null;
});
}
}
$(function () {
$('#btn').on('click', function () {
console.log('moved top DOM to right list');
move();
});
});

Drag and Drop with Angular JS and JQuery

Couple of days ago I found this interesting post at http://www.smartjava.org/content/drag-and-drop-angularjs-using-jquery-ui and applied it into my website. However when I progressively using it there is a bug I identified, basically you can not move an item directly from one div to another's bottom, it has to go through the parts above and progress to the bottom. Anyone can suggest where does it goes wrong? The example is at http://www.smartjava.org/examples/dnd/double.html
Troubling me for days already.....
I did this a bit differently. Instead of attaching a jquery ui element inside the directive's controller, I instead did it inside the directive's link function. I came up with my solution, based on a blog post by Ben Farrell.
Note, that this is a Rails app, and I am using the acts_as_list gem to calculate positioning.
app.directive('sortable', function() {
return {
restrict: 'A',
link: function(scope, elt, attrs) {
// the card that will be moved
scope.movedCard = {};
return elt.sortable({
connectWith: ".deck",
revert: true,
items: '.card',
stop: function(evt, ui) {
return scope.$apply(function() {
// the deck the card is being moved to
// deck-id is an element attribute I defined
scope.movedCard.toDeck = parseInt(ui.item[0].parentElement.attributes['deck-id'].value);
// the id of the card being moved
// the card id is an attribute I definied
scope.movedCard.id = parseInt(ui.item[0].attributes['card-id'].value);
// edge case that handles a card being added to the end of the list
if (ui.item[0].nextElementSibling !== null) {
scope.movedCard.pos = parseInt(ui.item[0].nextElementSibling.attributes['card-pos'].value - 1);
} else {
// the card is being added to the very end of the list
scope.movedCard.pos = parseInt(ui.item[0].previousElementSibling.attributes['card-pos'].value + 1);
}
// broadcast to child scopes the movedCard event
return scope.$broadcast('movedCardEvent', scope.movedCard);
});
}
});
}
};
});
Important points
I utilize card attributes to store a card's id, deck, and position, in order to allow the jQuery sortable widget to grab onto.
After the stop event is called, I immediately execute a scope.$apply function to get back into, what Misko Hevery call,s the angular execution context.
I have a working example of this in action, up in a GitHub Repo of mine.

JQGrid ContextMenu - Dynamic Menus

I have a page, which is used for building queries and running them against different entities (Kind of a query builder/generic search).
The results are displayed in JQGrid, so effectively the same grid will be used for rendering results from different entities.
This results grid has to support context menus, which will differ for each entity. So I need a way to change the context menu as per the entity. Each entity may have different number of menu items in context menu and each item may respond in a different manner (sometimes an alert, sometimes an action spawning in a different tab).
Rendering different menus (through li) is not an issue but attaching the methods to the li is proving to be a challenge. Any pointers will be highly appreciated.
I am using jquery.contextmenu-ui.js .
Following is from a sample that I picked from their (JQGrid) site
function initGrid() {
$("#EntityGrid").contextMenu('cMenu'
,{
bindings: { /* I would like to avoid this and pass all the actions to one method*/
'edit': function (t) {
editRow();
},
'add': function (t) {
addRow();
},
'del': function (t) {
delRow();
}
},
onContextMenu: function (event, menu) {
var rowId = $(event.target).parent("tr").attr("id")
var grid = $("#EntityGrid");
grid.setSelection(rowId);
return true;
}
}
);
}
Thanks,
Avinash
You can use onShowMenu callback of contextMenu instead of static binding using bindings. In the same way the menuId used as the first parameter of contextMenu could be the id of dynamically created div with empty <ul>. The onShowMenu has the form
onShowMenu: function (e, $menu) {
// here one can clear `<ul>` child of $menu
// and append it with "<li>" items
return $menu;
}
In the answer you will find an example of the code which build menu dynamically.

jQuery UI – draggable 'snap' event

I'm looking a way to binding the snap event.
When I'm dragging an element over my surface and the draggable element is snapped to a declared snap position I want to trigger an event.
Something like this:
$(".drag").draggable({
snap: ".grid",
snaped: function( event, ui ) {}
});
Bonus point: with a reference to the .grid element where the draggable element was snapped.
The draggable widget does not expose such an event out of the box (yet). You could modify it and maintain your custom version or, better, derive a new widget from it and implement the new event there. There is, however, a third way.
From this question, we know the widget stores an array of the potentially "snappable" elements in its snapElements property. In turn, each element in this array exposes a snapping property that is true if the draggable helper is currently snapped to this element and false otherwise (the helper can snap to several elements at the same time).
The snapElements array is updated for every drag event, so it is always up-to-date in drag handlers. From there, we only have to obtain the draggable widget instance from the associated element with data(), and call its _trigger() method to raise our own snapped event (actually dragsnapped under the hood). In passing, we can $.extend() the ui object with a jQuery object wrapping the snapped element:
$(".drag").draggable({
drag: function(event, ui) {
var draggable = $(this).data("draggable");
$.each(draggable.snapElements, function(index, element) {
if (element.snapping) {
draggable._trigger("snapped", event, $.extend({}, ui, {
snapElement: $(element.item)
}));
}
});
},
snap: ".grid",
snapped: function(event, ui) {
// Do something with 'ui.snapElement'...
}
});
The code above, however, can still be improved. As it stands, a snapped event will be triggered for every drag event (which occurs a lot) as long as the draggable helper remains snapped to an element. In addition, no event is triggered when snapping ends, which is not very practical, and detracts from the convention for such events to occur in pairs (snapped-in, snapped-out).
Luckily, the snapElements array is persistent, so we can use it to store state. We can add a snappingKnown property to each array element in order to track that we already have triggered a snapped event for that element. Moreover, we can use it to detect that an element has been snapped out since the last call and react accordingly.
Note that rather than introducing another snapped-out event, the code below chooses to pass an additional snapping property (reflecting the element's current state) in the ui object (which is, of course, only a matter of preference):
$(".drag").draggable({
drag: function(event, ui) {
var draggable = $(this).data("draggable");
$.each(draggable.snapElements, function(index, element) {
ui = $.extend({}, ui, {
snapElement: $(element.item),
snapping: element.snapping
});
if (element.snapping) {
if (!element.snappingKnown) {
element.snappingKnown = true;
draggable._trigger("snapped", event, ui);
}
} else if (element.snappingKnown) {
element.snappingKnown = false;
draggable._trigger("snapped", event, ui);
}
});
},
snap: ".grid",
snapped: function(event, ui) {
// Do something with 'ui.snapElement' and 'ui.snapping'...
var snapper = ui.snapElement.attr("id"),snapperPos = ui.snapElement.position(),
snappee = ui.helper.attr("id"), snappeePos = ui.helper.position(),
snapping = ui.snapping;
// ...
}
});
You can test this solution here.
In closing, another improvement might be to make the snapped event cancelable, as the drag event is. To achieve that, we would have to return false from our drag handler if one of the calls to _trigger() returns false. You may want to think twice before implementing this, though, as canceling a drag operation on snap-in or snap-out does not look like a very user-friendly feature in the general case.
Update: From jQuery UI 1.9 onwards, the data() key becomes the widget's fully qualified name, with dots replaced by dashes. Accordingly, the code used above to obtain the widget instance becomes:
var draggable = $(this).data("ui-draggable");
Instead of:
var draggable = $(this).data("draggable");
Using the unqualified name is still supported in 1.9 but is deprecated, and support will be dropped in 1.10.
In jquery-ui 1.10.0, the above code doesn't work. The drag function is instead:
drag: function(event, ui) {
var draggable = $(this).data("ui-draggable")
$.each(draggable.snapElements, function(index, element) {
if(element.snapping) {
draggable._trigger("snapped", event, $.extend({}, ui, {
snapElement: $(element.item)
}));
}
});
}

Moving item to new sortable: How to reference list I am dropping element to?

I have two sortable lists linked together.
I have an event listener on the first list calling function "A" on "sortupdate" to do some functionality when I am sorting items within the list or when receiving items from the other list.
I also have an event listener on the second list calling function "B" on "sortreceive" to do some functionality when it has received an item from another list.
My problem is that whenever I move something from list 1 to list 2, function "A" is called as well, causing errors in my code. I would like to add an 'if' clause to the beginning of function "A" saying to run this code only if the first list is the target, but I can't for the life of me figure out how to reference the target.
Or maybe there is a better way to check if an item was dragged out of this list?
/* adding current code */
$("#divMainMenu").bind("sortupdate", function(event, ui)
{ dropRootCategory(event,ui);})//when the main menu receives a menu item
$("ul.divSubMenu ").bind("sortreceive", function(event, ui)
{ dropSubMenu(event, ui);})//when the main submenu receives a menu item
function dropRootCategory(event, ui)
{/*item dropped on root category*/
//do some different stuff
}
function dropSubCategory(event, ui)
{//item dropped on a sub submenu
//do some stuff
}
I have tried checking the target:
if (event.target.id == 'divMainMenu') { //
which doesn't work because the target id stays 'divMainMenu' no matter where I am dropping to.
Next I tried checking for sender:
if (ui.sender == 'null'){//
However, this only populated with any information after it passed through the sortupdate phase and went to the sortreceive, so again it triggered the code to run.
/*******Updated with answer
Per Keith's idea below, I answered this with the following code:
On initiation of the menu, I added a variable holding the length of the original length of the main menu
var numMenuItems = $('#divMainMenu').children().length;
Then for my if statement:
if ($('#divMainMenu').children().length >= numMenuItems){
//do some stuff
}
Thanks again Keith! I was going nuts on this one :)
You can trying using ui.sender according to the sortable documentation:
"ui.sender - the sortable where the item comes from (only exists if you move from one connected list to another)"
This looks like what you are looking for.
Ok, I really did find another answer to this.
By adding a flag to the start event when defining the sortable, AS WELL AS a function call to the stop event :
$('ul.divSortable').sortable({
items: "li:not(.liEdit)", //cancel: ".liEdit",
connectWith: '.divSortable',
start: function(event, ui) { setOriginalSub(ui); },
stop: function(event, ui) { isBeingSorted = false; sortSorter(event, ui); }
});
and a function which grabs the id before sorting takes place:
function setOriginalSub(ui)
{
originalSub = $(ui.item[0]).parent().attr('id');
}
I can now compare the original ID with the current ID.
function sortSorter(event, ui){
var parentID = $(ui.item[0]).parent().attr('id');
}
Brilliant, and I wouldn't have thought of it without Keith's help!
I've been looking for the same thing and have found the solution that worked for me here ( all credit to phpduck, I have only found this )
http://phpduck.com/jquery-ui-sortable-drag-items-between-two-lists/
$(function () {
var oldList, newList, item;
$(".categories-sortable").sortable({
connectWith: $('.categories-sortable'),
start: function (event, ui) {
item = ui.item;
newList = oldList = ui.item.parent();
},
stop: function (event, ui) {
console.log("Moved " + item.text() + " from " + oldList.attr('id') + " to " + newList.attr('id'));
},
change: function (event, ui) {
console.log(ui.sender);
if (ui.sender) {
newList = ui.placeholder.parent();
}
},
})
.disableSelection();
});
also, there's a fiddle: http://jsfiddle.net/ajberri/hEJF3/

Resources