I'm trying to implement the sortable-list feature form jquery-ui with aurelia. Whats the best way to update the items (inside aurelia controller) with the new order from the dom? Is "ref" an approach here?
Easy re-sort the list-items in the dom, but how apply the changes to the aurelia-list-object?
<ul class="sortable">
<li repeat.for="item of items">
Stuff.
</li>
</ul>
One Approach would be to attach the $index to the li-item, read them with jquery after the order was changed and create an new array with the order ([0,3,1,2]). Then iterate throigh this array and push the items of the original "item"-array in the controller to a new array according to their index. This seems rather clunky and unperformant though.
Is there a more elegant solution?
We found that Aurelia doesn't really take kindly to "others" rearranging the DOM under its nose. The specific case that was broken for us (from memory) was when you drag the item around and return it to its original position.
What we did was attach an event handler for "sortstop" to the sortable component, and the event handler did this (this is Typescript) -- items is our list of items we have bound to, and refSortable is the sortable element:
onStop( event: JQueryEventObject, ui: any ): boolean
{
let endPos = ui.item.index( );
let startPos = ui.item.data( "start_pos" );
$( this.refSortable ).sortable( "cancel" );
let movedItem = this.items[ startPos ];
if( startPos === endPos )
{
// KLUDGE: even though the item has not moved, we need to create a new one to force Aurelia to rebind
let newItem = new ExportListItem( movedItem.id, movedItem.caption, movedItem.sourceObject );
movedItem = newItem;
}
this.items.splice( startPos, 1 );
this.items.splice( endPos, 0, movedItem );
// We end up with a duplicate DOM element that needs to be removed.
ui.item.remove( );
}
I won't say it's elegant by any stretch, but it gets Aurelia behaving correctly.
Related
I am using angular 4+
Here is my array
i used ngfor to populate data with draggable div
['MODEL', 'PART', 'CUSTOMER'];
what i want whenever i drag/swap one of the div then the new array become something like
['PART', 'MODEL', 'CUSTOMER'];
cant find any solution for this
my plunker
https://plnkr.co/edit/oy2QWJ?p=preview
When you update the html outside the scope of angular, you will need to put extra effort to make sure the state of template/component are in sync. You should directly update the model and as it is 2 way binding, html will be rendered automatically according to that.
You need to make following updates
JS
Add private variable dragIndex
Update drag function (set dragIndex - index of element being dragged)
public drag (index) {
this.dragIndex = index;
}
Update drop function (swap the values in array at the drag and drop indexes)
public drop (ev, index: number) {
ev.preventDefault();
let temp = this.homePageSearchTiles[this.dragIndex];
this.homePageSearchTiles[this.dragIndex] = this.homePageSearchTiles[index];
this.homePageSearchTiles[index] = temp;
console.log(this.homePageSearchTiles);
}
HTML
i passed in function was not defined. Define i as index in ngFor and pass it in both drag and drop functions
<div *ngFor="let tiles of homePageSearchTiles; let i = index; (drop)="drop($event, i)" (dragover)="allowDrop($event)">
<div class="tiles ui-g-12 ui-lg-5 card box-id" id="div{{tiles.id}}" draggable="true" (dragstart)="drag(i)">
{{tiles}}
</div>
</div>
Please find working version, Plunker
I am using this nested sortable plugin mjsarfatti.com/sandbox/nestedSortable and the only issue I have so far is when I dynamically add an item to the "tree", I am not able to expand or collapse the item(s). I am just using the sample code so far, and adding to that.
How I am adding the items dynamically:
$('#new-button').on('click', function() {
var nextId = $('ol.sortable').nestedSortable('nextId');
var $li = $("<li id=\"list_" + nextId + "\"><div><span class=\"disclose\"><span></span>
</span>New Item</div>");
$li.addClass('mjs-nestedSortable-leaf');
$('ol.sortable').append($li);
})
When I add these new items to the tree, they work just fine - I can move them throughout the tree, make them children, etc.
However, when I try to collapse a new item that I have made a parent - there is no response.
I am sure I just haven't added the correct event handler somewhere, but I can't fin where that is happening. I have even triggered a destroy() and _create() of the tree after I add the new item(s), hoping that would "re-configure" all the items again. However, no luck there.
Can anyone tell me how I can properly hook up these new dynamically created items so they will be treated as other items in the tree?
Thanks!
Ok, after 2 days of looking at this, I was able to solve the issue. It is funny - the code I had been looking for was directly above the new code that I had entered. (Right under my nose.) Thanks to the kind people here for introducing me to: Visual Event - that greatly helped me to track down where the events were being created in the first place!
$('#new-button').on('click', function() {
var nextId = $('ol.sortable').nestedSortable('nextId');
//Begin creating dynamic list item
var $li = $("<li id=\"list_" + nextId + "\">");
var $lidiv = $("<div></div>");
var $disli = $("<span class=\"disclose\"><span></span></span>");
$li.addClass('mjs-nestedSortable-leaf');
//Assign event listener to newly created disclose span tag above
$disli.on('click', function() {
$(this).closest('li').toggleClass('mjs-nestedSortable-collapsed').toggleClass('mjs-nestedSortable-expanded');
});
//Now actually start to append to DOM
$lidiv.append($disli);
$lidiv.append("New List Item " + nextId);
$li.append($lidiv);
$('ol.sortable').append($li);
})
Hopefully, this will help someone.
Here is a working copy in case you want to see it in action.
I have used firebug and IE profilers and can see what function in my code is causing the slowness. Being new to jquery, the recommendations that I have read online are not clear to me. I have made an example page that shows the slow behavior when you check or uncheck a check box. No surprise that this is fast using Chrome.
The function that is slow can be found on line 139.
$('.filters input').click( function()
JSFiddle can be found here
The code is 122 KB and can be found here
UPDATE: if you know of any examples online that are similar in function and faster, please share.
i had a brief look through your code, but it was very hard to follow. it seemed as if you were looping through things many many times. i used a much simpler approach to get the list of all states.
your approach was
* make a massive string which contained every class (possibly repeated multiple times)
* chop it up into an array
* loop through the array and remove duplicates
i simply took advantage of the fact that when you select something in jQuery you get a set rather than a single item. you can therefore apply changes to groups of object
$(document).ready(function () {
//this will hold all our states
var allStates = [];
//cache filterable items for future use
var $itemsToFilter = $(".filterThis");
//loop through all items. children() is fast because it searches ONLY immediate children
$itemsToFilter.children("li").each(function() {
//use plain ol' JS, no need for jQuery to get attribute
var cssClass = this.getAttribute("class");
//if we haven't already added the class
//then add to the array
if(!allStates[cssClass]) {
allStates[cssClass] = true;
}
});
//create the container for our filter
$('<ul class="filters"><\/ul>').insertBefore('.filterThis');
//cache the filter container for use in the loop
//otherwise we have to select it every time!
var $filters = $(".filters");
// then build the filter checkboxes based on all the class names
for(var key in allStates) {
//make sure it's a key we added
if(allStates.hasOwnProperty(key)) {
//add our filter
$filters.append('<li><input class="dynamicFilterInput" type="checkbox" checked="checked" value="'+key+'" id="filterID'+key+'" /><label for="filterID'+key+'">'+key+'<\/label><\/li>');
}
}
// now lets give those filters something to do
$filters.find('input').click( function() {
//cache the current checkbox
var $this = $(this);
//select our items to filter
var $targets = $itemsToFilter.children("li." + $this.val());
//if the filter is checked, show them all items, otherwise hide
$this.is(":checked") ? $targets.show() : $targets.hide();
});
});
FIDDLE: http://jsfiddle.net/bSr2X/6/
hope that's helpful :)
i noticed it ran quite a bit slower if you tried to slideup all the targets, this is because so many items are being animated at once. you may as well just hide them, since people will only see the ones at the top of the list slide in and out of view, so it's a waste of processor time :)
EDIT: i didn't add logic for show all, but that should be quite a trivial addition for you to make if you follow how i've done it above
You could use context with your selector:
$('.filters input', '#filters_container').click(function()...
this limits the element that jQuery has to look in when selecting elements. Instead of looking at every element in the page, it only looks inside your $('#filters_container') element.
My query works great, but then I check it on IE and it is a disaster. I am wondering if it is the parent part of the code. The parents are trying to reach the first <table> containing the info. Is there an easier way to get to the table directly?
<script>
if ($('b:contains("Choose a sub category:")').length > 0) {
var newSubnav = document.createElement( 'ul' );
$(newSubnav).addClass("sub_categories_holder");
$('b:contains("Choose a sub category:")').parent().parent().parent().parent().parent().parent().parent().prepend( newSubnav );
$('a.subcategory_link').appendTo($('ul.sub_categories_holder'));
$('a.subcategory_link').wrap("<li class='sub_categories'></li>");
$('li.sub_categories').before("<li class='sub_categories_divider'></li>");
$("li.sub_categories_divider:eq(0)").remove();
$("b:contains('Choose a sub category:')").parent().parent().parent().parent().parent().remove();
$("img[src='/v/vspfiles/templates/GFAR NEW NAV/images/Bullet_SubCategory.gif']").remove();
$("td.colors_backgroundneutral").css("background-color","transparent");
$("td.colors_backgroundneutral").children(':first-child').attr("cellpadding","0");
};
</script>
Unless you provide your markup(at least a dummy one) it's all guess that you are gonna get.
Instead of .parent().parent().parent().parent().parent().parent().parent().prepend( newSubnav ); check if you can use .parents(). This will return you the parents all the way up to HTML. You can even use selectors as parents(selector) to filter. Read more on the jquery api page.
Since you are using jQuery, you can use $("ul") instead of document.createElement("ul")
On eventRender of the fullcalendar, I want to identify the element belongs to which row of the calendar.
eventRender: function(event, element) {
//find calendar row of the element
}
What happens if the event is wrapped over two columns? (i.e. because it is 10 days long)
I'm looking at the source for FullCalendar, and I don't think you're going to find what you want - the element is an absolutely positioned div, and eventRender is called for each "segment" of the event, so if it wraps, you end up with two eventRender calls.
Anyways, if those "gotchas" don't dissuade you, here's a crack at figuring it out. I used the row widths/heights vs the element top to figure it out, I don't really see a more elegant way. Note you'll want to change the .fc-view-month bit to a selector that starts from your actual calendar ID.
eventAfterRender: function(event,element){
var level = undefined;
var elTop = $(element).position().top;
$('.fc-view-month tr[class^="fc-week"]').each(function(){
var rowTop = $(this).position().top;
var height = $(this).height();
if (elTop >= rowTop && elTop < rowTop + height){
level = $(this).attr('class').replace('fc-week','');
return false;
}
});
alert(level);
}
That will alert you with the "row" as defined by the calendar, i.e. the first row is actually "0"
EDIT: just realized that eventRender is called before the element is placed on the calendar, so my idea pretty much won't work. I've changed it to eventAfterRender. I think if it was important enough to you, the thing to do would be to modify FullCalendar so that whenever it sends you the "element" in the eventRender function, it also sends you the placement info (which it has in a structure called segs in the calling function).