Drag and drop lists based on certain conditions - javascript

I am using angular-drag-and-drop-lists (https://github.com/marceljuenemann/angular-drag-and-drop-lists) for my AngularJS project to create two lists that allow me to do the following:
Drag items from list A to list B
Drag items from list B to list A
Reorder items in list A
Reorder items in list B
Using a simple example on the library's site (http://marceljuenemann.github.io/angular-drag-and-drop-lists/demo/#/simple) I can see that these four conditions are easily achievable. However, things start to get hairy when I want to introduce slightly more complex behaviour:
When dragging items from list A to list B, I want something to occur (easily achieved with the dnd-drop callback. This currently works for me
When dragging items from list B to list A, I don't want anything to happen other than a regular drop (i.e. the item winds up in list A and nothing fancy happens). This currently works for me
When reordering items in list A, nothing should happen when an item is re-dropped into it's new position in the list (even if it is being dropped back into it's original position) This is where I am having issues - see below for further explanation
When reordering items in list B, nothing should happen when an item is re-dropped into it's new position in the list (even if it is being dropped back into it's original position). This currently works for me
I am largely adapting the sample code used in the example link provided above. The problem I am having is that the action I want to take place when moving items from list A to list B is also occurring when I reorder things in list A.
The current setup/pseudocode I have is the following:
My lists:
$scope.models.lists = {
"A": [],
"B": []
}
I populate these lists with information pulled from a database. Each item in the list also has a property that tracks how many children the item has.
My markup looks like the following:
<ul dnd-list="list"
dnd-allowed-types="['itemType']"
dnd-drop="myCallback(event, index, item, external, type, 'itemType')">
<li ng-repeat="item in list | filter:searchText"
dnd-draggable="item"
dnd-moved="list.splice($index, 1)"
dnd-effect-allowed="move"
dnd-selected="models.selected = item"
dnd-type="'itemType'"
ng-class="{'selected': models.selected === item}"
ng-dblclick="editProperties();">
{{item.label + ' - ' + item.description}}
</li>
</ul>
My callback looks like the following:
$scope.myCallback= function(event, index, item, external, type, allowedType) {
// If the item in question has no children then we don't need to do anything special other than move the item.
if (item.children.length == 0) {
return item;
}
// If moving items around list B then just move them.
for (var i = 0; i < $scope.models.lists.B.length; i++) {
if (item.label === $scope.models.lists.B.[i].label) {
return item;
}
}
// I only want this code to execute if we know we are moving from list A to list B and the item in question has > 0 children.
if (item.children.length > 0) {
// Code that I want to execute only when moving from list A to list B goes here
}
If anyone is able to assist me with this I will be very grateful.
Thanks!

If you're not totally committed to that library (which, btw, doesn't work with a touchscreen) RubaXa's Sortable is the truth: Sortable
The documentation is strong, it's quite performant, and I wish I hadn't wasted my time on other DnD libraries before this one.

Looking at the README, the dnd-drop event fires for any drop - regardless of whether it was within the same list or not.
Going by the script as you have it now, you need to check the event (or pass some additional information into your callback) to determine what list the event is firing on.

hi i have add a dragstartCallback that set variable in event.dataTransfer
for the information of the origin/source of my dragged obj:
$scope.dragstartCallback = function(event){
var id = event.target.id;
var parent = $('#' + event.target.id).closest('ul')[0].id;
var group = $('#' + event.target.id).closest('div')[0].id;
event.dataTransfer.setData("id", id);
event.dataTransfer.setData("parent", parent);
event.dataTransfer.setData("group", group);
return true;
}
And in the dropCallback i just check if is List A or List B
$scope.dropCallback = function(event, index, item, external, type, allowedType) {
$scope.logListEvent('dropped at', event, index, external, type);
var idDivSource = event.dataTransfer.getData("parent");
var idDivDestination = $(event.target).closest('ul')[0].id;
var groupSource = event.dataTransfer.getData("group");
var groupDestination = $('#' + event.target.id).closest('div')[0].id;
if(groupSource == groupDestination){
if(idDivSource != idDivDestination){
if(idDivDestination == 'IDLISTB'){
return item?
}
if(idDivDestination == 'IDLISTA'){
DO Something else
}
DO Something else
}
}
}
Html code:
<div class="associated-drag-and-drop DnD" id="GROUP-MyD&D1" ng-repeat="(listName, list) in models.lists" flex="">
<ul dnd-list="list" id="{{listName}}" dnd-drop="dropCallback(event, index, item, external, type, 'containerType')" flex="">
<md-list-item class="associated-list__item" ng-repeat="item in list | filter:searchFilter" id="{{item.id}}"
dnd-dragover="dragoverCallback(event, index, external, type)"
dnd-dragstart="dragstartCallback(event)"
dnd-draggable="item"
dnd-moved="list.splice($index, 1)"
dnd-effect-allowed="move"
dnd-selected="models.selected = item"
ng-class="{'selected': models.selected === item}"
class="noright associated-list__item"
>
CONTENT
</md-list-item>
</ul>
</div>
Do not use this type of filter "| filter:searchFilter"
But use ng-show="And put here the condition"
Alternative change the reference to $index in dnd-moved="list.splice($index, 1)"
If you dont make this change you will have a problem with the filtered list when drag and drop
Exemple
NOT FILTERED LIST
you will have 2 array in 2 ng-repeat
ListA ListB
index1 of value index2 of value
ng-repeat of list ng-repeat of list
0 aaa 0 ppp
1 bbb 1 qqq
2 ccc 2 www
3 dddaaa 3 eeerrr
4 eeeccc 4 mmmwww
ecc... ecc...
The drag and drop lists work on the index of ng-repeat
FILTERED LIST
Now if we make a filter for 'a' with "| filter:searchFilter" code
angular will make this list
List A List B
0 aaa
1 dddaaa
the index when you drag "dddaaa" will be 1 and not 3
so it will not be removed from listA when dropped in listB
becouse the index is not the same as the non filtered list
instead if you use the ng-show="condition"
it will keep the original index of list not filtered
<md-list-item ng-repeat="item in list"
ng-show="condition">
</md-list-item>
My item list:
$scope.models = {
lists: {
"LISTA": [{1},{2},{3}],
"LISTB": [{1},{2},{3}]
}
};

Related

On click select first item on a list

I have a list, I want to select the first item of my list when I select the last item.
Is there a way to do it ?
Is there a way to handle this please ?
<ion-select interface="popover" [ngModel]="selecteduser._id" (ngModelChange)="selectUser($event)" (ionChange)="onChange($event)">
<ion-option *ngFor="let user of users" [value]="user._id">
</ion-content>{{ getUserNickname(user }} </ion-option>
<ion-option>Configuration</ion-option>
</ion-select>
So I want when I click on my configuration, it will select the first item of my list
There is my ts
openConfigPage() {
this.selectedUser = this.users[0];
this.navCtrl.push(ConfigPage)
}
selectUser(userId: string) {
this.selectedUser = this.users.find(b => b._id === userId)
this.onSelect.emit(this.selectedUser)
}
Is there a way to do it with the DOM ?
To me, it seems like not much information is being present here. Don't let this discourage you from asking questions in the future, just please make sure to be specific when asking your question and making sure to tag it appropriately.
Since you've asked how to accomplish this in javascript, here is an answer on how to do this in javascript: https://jsfiddle.net/gqvfx850/24/
The idea is that the items are being added in a to the page in a loop, we then access the last item in the loop:
//if this is the last item in the loop
if(x === listItems.length - 1)
{
And add an event listener for when it's been clicked
//add an event listener that also checks the first box when checking
//this one
radioBtn.addEventListener("click", function()
{
//grab the class of all radio buttons
var firstRadioBtn = document.getElementsByClassName("radio");
//but you only want to change the first one
firstRadioBtn = firstRadioBtn[0];
we then access the first item in the list, and check it
//set the first button to being checked
firstRadioBtn.checked = true;
}, false)
}
This idea works assuming that your HTML is being set up by javascript as well, if that's not the case let me know and I can create another fiddle for you.
I'm not familiar with angular, so if this approach doesn't suit you just please let me know why and I can see if I can provide any other assistance on the matter.
Template
Select Last One <select [(ngModel)]="selectedItem" (ngModelChange)="onSelect($event)">
<option *ngFor="let item of items" [value]="item">{{item}}</option>
</select>
<br><br>
You have selected : {{selectedItem}}
Component
export class AppComponent {
selectedItem:string;
items = [
'Purab', 'Pashchim', 'Uttar', 'Dakshin'
];
onSelect(selectedItem) {
let len = this.items && this.items.length;
this.selectedItem = len && selectedItem === this.items[len-1]?
this.items[0]:selectedItem;
}
}
Demo : https://ng-on-lastselect-checkfirst.stackblitz.io
Source : https://stackblitz.com/edit/ng-on-lastselect-checkfirst

Ng-repeat and directive expand one list item at a time

I have an ng-repeat code with expandable list items. I would like to expand one item at a time.
The way I am trying to do it is
in the html file
<div data-ng-repeat="parts in data track by $index">
<li id="titleHelp" ng-click='setItem($index);">
and in the directive in the setItem function I want to collapse the previously expanded item and expand the new one. Is it possible to access one repeat element in the directive using index?
thanks!
How do you currently expand the list item?
What I would do is set a variable as soon as an item is clicked and in your repeated list do a
<div data-ng-repeat="parts in data track by $index">
<li id="titleHelp" ng-click='setItem($index);">
<div ng-show="$index = selected_item"> <!-- contents --></div>
In your setItem function:
$scope.setItem = function(i) {
$scope.selected_item = i;
}
Declare a object
$scope.obj={selected:null};
After that add a method in the ng repeat,
$scope.isHide = function (id) {
if (id == $scope.obj.selected)
return $scope.obj.selected = "all";
return $scope.obj.selected = id;
}
If you want to hide div, call this method with the id. Do the same thing for the li if you need.

Apply multiple client-side filters with jQuery

I need some help with jQuery filtering and selection.
I've got a list of elements
<ul>
<li data-cat="1 4 6" data-level="1 2" data-price="500">Element 1</li>
<li data-cat="1 2 5" data-level="1 3" data-price="300">Element 2</li>
<li data-cat="2 4 6" data-level="3" data-price="450">Element 3</li>
</ul>
which I must filter client-side (show/hide).
Category filter is a radiobutton (single selection), price filter is a jQuery UI slider, level filter is a set of checkboxes (multiple selection). These 3 controls affect the following variabiles:
var filterCategory = 0;
var filterPriceFrom = 0; var filterPriceTo = 0;
var filterLevel = [];
On them changing I call the following listener, which applies the filters (leve filter not implemented yet).
var list = $('ul > li');
list.hide();
if (filterCategory > 0){
list = $('ul > li[data-cat~=\"' + filterCategory + '\"]');
}
list = list.filter(function() {
var value = parseInt($(this).data('price'), 10);
return value >= filterPriceFrom && value <= filterPriceTo;
});
list.show();
Now I have to add level filtering, which is a set of checkboxes (multiple selection). I think I have 2 options
1: add a block like category filter. The problem with this solution is that my filtered result are within "list" variabile. I would like to use the power of jQuery data() selector. How can I take "list" variabile (and not the entire DOM) in input and apply filters again?
$(list > data-level[...]) /* ??? */
2: Put everything inside list.filter() block. Again: how can I keep using jQuery data() selector?
list = list.filter(function() {
/* check if $(this) has filterCategory within data-cat attribute */
/* check if $(this) has at least one of filterLevel elements within data-level attribute */
var value = parseInt($(this).data('price'), 10);
return value >= filterPriceFrom && value <= filterPriceTo;
});
Thank you.

How to create row selector for recursive template in angularjs

From recursive list of items
<script type="text/ng-template" id="menu_sublevel.html">
id:{{item.id}}
<ul ng-if="item.subs">
<li ng-repeat="item in item.subs" ng-click="openItem(item)" ng-include="'menu_sublevel.html'">
id:{{item.id}}
</li>
</ul>
</script>
<ul>
<li ng-repeat="item in menu.items" ng-click="$event.stopPropagation()" ng-include="'menu_sublevel.html'"></li>
</ul>
and effect
id:0
...id:4
...id:5
...id:16
...id:17
...id:18
...id:6
...id:20
...id:21
...id:22
I want to have selected one at time item.
When i write nested list without recursion i use id and on every level I have method for item selection and i chceck `
levelOneItemSelected.id === item.id
How to select child with id 16 and have his parent with id 5 opened and next parent with id 0 opened while changing selection closes opened items.
If, upon invocation of openItem(item), you also want to select/open its ancestors, then its best to have the reference from item to its parent, for example, item.$$parent. That would enable you to traverse the item's ancestors and modify them. Conceptually speaking, it would look like so:
$scope.openItem(item){
item.isOpen = true;
while (item.$$parent){
item = item.$$parent;
item.isOpen = true;
}
}
So, one way is to pre-process your items and set the .$$parent property accordingly.
If you don't like the idea of changing the item object (could be your domain model), you could always pre-process your domain model and produce a view model that wraps a domain model. It would look like so (in concept):
$scope.menu = [
{ $$parent: null,
item: {id: 0, subs: [
{ $$parent: parentObj, // points to its parent
item: {id: 10, subs: [...]}
}
]}
},
// etc ...
]
But if you don't want to modify either, you could use the fact that ng-repeat creates a child scope and instantiate the $$ancestors property at each scope level. (Notice also, that ng-click should be on the displayed item, not on the <li> for subitems):
<script type="text/ng-template" id="menu_sublevel.html">
<span ng-click="openItem(item, $$ancestors)"
ng-class="{'open': item.isOpen}">id:{{item.id}}</span>
<ul ng-if="item.subs"
ng-init="$$p = $$ancestors.slice(); $$p.push(item)">
<li ng-repeat="item in item.subs"
ng-init="$$ancestors = $$p"
ng-include="'menu_sublevel.html'">
id:{{item.id}}
</li>
</ul>
</script>
<ul>
<li ng-repeat="item in menu.items"
ng-init="$$ancestors = []"
ng-include="'menu_sublevel.html'"></li>
</ul>
Then, in the controller, openItem needs to change:
var currentOpenItem = null,
currentOpenItemAncestors = [];
$scope.openItem = function(item, ancestors){
// closes the currently open item and its ancestors
closeItem(currentOpenItem, currentOpenItemAncestors);
currentOpenItem = item;
currentOpenItemAncestors = ancestors;
openItem(item, ancestors);
}
Demo
The drawback of this approach is that it offloads some of the logic to the View and makes the View more complex and your controller less testable:

Comparing 2 arrays in jQuery

I am trying to compare 2 arrays to see if an element in one array is in another array.
But i can't seem to get it to work
//if a categories checkbox is clicked
$j('#cat-dropdown li label input').click(function(e) {
//Get all selected categories
var categories = new Array;
$j('#cat-dropdown li label input').each(function(index, element) {
if($j(this).is(':checked')){
categories.push($j(this).val());
}
}); // Categories variable now has all selected cats
$j('.products-grid li.item').each(function(index, element) {
//get all the cateroies of a product
console.log('//////////////////////'+$j(this).val())
product_cats = $j(this).attr('data-product-categories').split(",");
//Now check if the product categories is in the selected categories
var inCategoryList = false;
for (i=0;i<categories.length;i++){
if($j.inArray(categories[i],product_cats)){
console.log('test '+categories[i])
inCategoryList = true;
}
}
if(inCategoryList == false){
$j(this).hide();
}
});//end each on product-grid li.item
});//end click function
But it is not working , nothing gets hidden.. But also the inArray method seems not be working properly, is there a different way to compare 2 arrays and check if one element is in another.
The basic flow is, user selects a checkbox, the li item has a list of categories attached to it. If the checkbox matches a category then keep the item displayed if there are no matches then hide it

Categories

Resources