Can't make multiple inners draggable object from angular 5 & dragula - javascript

I'm trying since few days and can't make it works...
Little explanation :
I've in this example, an array of object like this :
public containers: Array<object> = [
{
"name": "container 1",
"items": ["1", "2", "3"]
},
{
"name": "container 2",
"items": ["4", "5"]
}
];
Where each object have an array of string.
I'm generating divs to make these object moves (Containers AND Items).
Now, i'm getting something like this :
Where red box is the main div, blacks boxes are containers, and blue boxes are items.
with this html :
<div class="feuille-drag" [dragula]='"containers"' [dragulaModel]='containers'>
<div *ngFor="let container of containers" class="drag-container" [dragula]='"blocks"'[dragulaModel]='container.items'>
<span class="handle">O</span>
<div *ngFor="let item of container.items" class="drag-bloc">
<span class="handleF">X</span>
{{item}}
</div>
</div>
And this typescript, where I only set fews options :
dragulaService.setOptions('containers', {
revertOnSpill: true,
copy: false,
moves: function (el, container, cHandle) {
return cHandle.className === 'handle';
}
});
dragulaService.setOptions('blocks', {
revertOnSpill: true,
copy: false,
moves: function (el, container, bHandle) {
return bHandle.className == 'handleF';
}
});
If you looks well, you can see in the screenshot that, there is an empty blue box. It wasn't here at the beginning, I only dropped a blue box into another one, and it created an undefined element into my object Containers.
An other problem :
If I move a blue box into an other black box (container), the blue box will disappear and an other blue box will move instead.
Example : If I move the blue box 1 into the container 2, the box 1 will disappears, and the box 2 will go into the container 2.
BUT It will not be deleted into the object Containers :
End, last thing, handle elements from containers (O) are being read like draggable object from dragula.
Its maybe just a css problem, but i'm not sure so...
I'm using Angular CLI, Angular 5, Dragula, and i'm pretty new on Angular, (I still was on AngularJS sometimes ago).
I hope it's well explained, hope someone can help me, and I'm sorry if there is already an answer about it, I didn't find it !
Have a nice day !
UPDATE
See this stackbliz

There is one html element that breaks your structure:
<span class="handle">O</span>
ng2-dragula gets wrong index when handles drop event
drake.on('drop', (dropElm: any, target: any, source: any) => {
if (!drake.models || !target) {
return;
}
dropIndex = this.domIndexOf(dropElm, target);
https://github.com/valor-software/ng2-dragula/blob/master/src/components/dragula.provider.ts#L78
target here is your div.drag-container that includes container.items.length + 1 elements.
After that new undefined element is added to your container.items,
To fix it I would suggest you wrapping dragging elements in its own container like:
...
<span class="handle">O</span>
<div [dragula]='"blocks"' [dragulaModel]='container.items'>
<div *ngFor="let item of container.items;" class="drag-bloc">
<span class="handleF">X</span> {{item}}
</div>
</div>
Forked stackblitz example

Related

Summernote - Custom dropdown for ordered and unordered list

I am using Summernote editor v0.8.9 for quite a long time. I have created a custom dropdown button for Ordered List and Unordered List by using below code
let orderedList = function (context)
{
let ui = $.summernote.ui;
// create button
let button = ui.buttonGroup([
ui.button({
className: 'dropdown-toggle',
contents: '<i class="fa fa-list-ol"/><span class="note-icon-caret"></span>',
container: false,
tooltip: 'Ordered List',
data: {
toggle: 'dropdown'
}
}),
ui.dropdown({
className: 'dropdown-style',
contents: "<ol style='list-style-type:none' class='ordered-list'><li data-value='1'>1</li><li data-value='1' style='display: none;'>1)</li><li data-value='I'>I</li><li data-value='A'>A</li><li data-value='a)' style='display: none;'>a)</li><li data-value='a'>a</li><li data-value='i'>i</li></ol>",
callback: function ($dropdown) {
$dropdown.find('li').each(function () {
$(this).click(function() {
selectedListType = orderedListMap[$(this).attr('data-value')];
$(context).summernote('editor.insertOrderedList');
});
});
}
})
]);
return button.render(); // return button as jquery object
};
And also I get the dropdown attached to the toolbar as shown in the image
I have changed some code in the summernote.js to change the list style type after clicking on the dropdown item.
I am adding following code in the Bullet.prototype.wrapList method as follows
//for bullets and numbering style
if (selectedListType != 'NA') {
listNode.setAttribute('style', 'list-style-type:' + selectedListType);
}
I have also added the following code in the method "function replace(node, nodeName)" of "dom" object.
//for bullets and numbering style
if ((nodeName == 'OL' || nodeName == 'UL' ) && selectedListType != 'NA') {
$(newNode).css('list-style-type', selectedListType);
}
When I click on the dropdown item I am calling below code.
$(context).summernote('editor.insertOrderedList');
At first instance everything is working fine. I can change ordered list to unordered list as well as to other types of lists. But the problem arises when I am trying to create a new list. When I try to create a new list below the existing list (note : after double entering new line, existing list closes, hence a new list is created after double entering new line),
the focus does not stay on the current line. Instead it goes to the old list and old list style (Ordered/unordered) is getting changed.
I have also kept the default UL/OL in the toolbar for deugging and I can see that the document.selection() in the method WrappedRange.prototype.nativeRange is giving proper selection for default UL/OL but is giving wrong selection for my dropdown UL/OL.
Please help.
Let me know if any info is needed from my side
I solved this problem.
The problem was with the custom dropdown I created.
The dropdown needs to be in the following structure in the 'contents' attribute inside 'ui.dropdown'
<li>
</li>
<li>
</li>
'a' tag inside 'li' tag

Iterate through an array on click and change albums via another click

I have a ngFor loop which iterates over an array of famous people (see below), this is fine but I would now like to add in image gallery which is where things get tricky. Each person will have 2 photo albums, the albums will be simply strings of urls to remote photos - the first problem is that I would like to be able to click the image tag (or element close by) and on each click I want the image src to change to the next image, when it reaches the end ideally I would like it to go back again to the start or just stop.
this is tricky enough - but each person will have a secondary photo album which is much the same as the first i.e Array however - it gets activated when the user clicks the changeAlbum() which is on a thumbnail image below - the idea is that the first image of the array not in use (as clicking changeAlbum() makes it inactive ) populates this thumbnail and the gallery then uses the images from the secondary gallery - so essentially its like a complex toggle() between the 2 galleries combined with a click to iterate over the array of each gallery - it might even be cool if when you iterate through the first gallery that it auto switches to the secondary one and vice versa
Im not entirely sure how to approach it though - the html and the json struct are the only solid things I have in place.
<div class="card" *ngFor="let c of item" >
<div class="card-content">
<div class="card-image">
<img (click)="changePhoto()" [src]="{{ output image here from the click }}" />
</div>
<div class="card-titles">
<h1>
{{ c.name }}
</h1>
</div>
<div class="card-image">
<img (click)="changeAlbum()" [src]="{{ show image from album not in use here}}" />
</div>
</div>
</div>
The data structure I have so far is like this
item = [{
id: 1,
name: "Joe Jimbob",
album_Main: [{
selected: true,
"https://via.placeholder.com/250",
"https://via.placeholder.com/250",
"https://via.placeholder.com/250",
"https://via.placeholder.com/250",
}],
album_Secondary: [{
selected: false,
"https://via.placeholder.com/250",
"https://via.placeholder.com/250",
"https://via.placeholder.com/250",
"https://via.placeholder.com/250",
}]
}]
This is also relatively solid but im very open to any help anyone can give
changeAlbum(item:any) :void {
if( item.album_Main.selected ) {
item.album_Main.selected == false
} else {
item.album_Secondary.selected == false
}
}
and the change...
changePhoto(photos: Array<string>) :void {
// not sure how to interate this one by one on click
console.log( photos.length )
}
Check this Stackblitz example I made based on your code: https://stackblitz.com/edit/angular-pk6cu8
The important changes are these in app.component.ts
photoIndex = 0;
personIndex = 0;
selectedAlbum = this.item[0].album_Main;
altAlbum = this.item[0].album_Secondary;
isMainAlbumActive = true;
changePhoto() {
if (this.photoIndex < this.selectedAlbum.length - 1) {
this.photoIndex++;
} else {
this.photoIndex = 0;
}
}
changeAlbum() {
if (this.isMainAlbumActive) {
this.selectedAlbum = this.item[this.personIndex].album_Secondary;
this.altAlbum = this.item[this.personIndex].album_Main;
} else {
this.selectedAlbum = this.item[this.personIndex].album_Main;
this.altAlbum = this.item[this.personIndex].album_Secondary;
}
this.isMainAlbumActive = !this.isMainAlbumActive;
}

How to associate buttons with new objects (object nesting) in Javascript

So I need something similar to Facebook Messenger's gear button which can be seen only when a conversation box is hovered or is currently active (just to give an image to these words), but how can I associate the button to that object? The object nesting is really confusing me. This is what mine looks like right now.
table_data {
item_1: { fruits: [ {}, ..., {} ]},
item_2: { fruits: [ {}, ..., {} ]},
...
...
item_n: { fruits: [ {}, ..., {} ]},
}
Each item is a div that has 3 buttons:
Delete - deletes item and all content
Options - opens popover options menu
Expand - expands current item to show fruits array of objects
The user enters in a new item and then has the option to add objects to fruits array.
document.getElementById("save").addEventListener("click", function() {
table_data[newItem] = { fruits:[] };
appendHTML(newItem);
// helper function that appends HTML
}
So under options, user can "Add Fruits". How can I associate that "Add Fruits" to the correct item since the "Options" button is just appended HTML.
If it helps, the helper function looks like this.
function appendHTML(item) {
$("#list").append('<li class="enter"><div class="listContent"><span class="itemName">'+item+'</span></a></div></li>');
}
If anyone happens to need an answer to this in the future...
$(document).on('click', "#list > li > .listContent > .gear", function() {
$(this).next().slideToggle();
});
This is a duplicate of this question. There, you will find multiple answers. I think the best would probably be storing the element in a var and then you can easily target it with whatever you want after you append it

What is the most direct way to determine that the inner element should be displayed in AngularJS?

I have a JSON structure which represents as hierarchical elements.
It looks like the following:
{
"url":"http://docsetups.json",
"partnerId":1,
"fieldDefs":
[
{"roleName":"Make","roleId":1,
"children":[{"roleName":"Invoice Number","roleId":11}]
},
{"roleName":"Model","roleId":2,
"children":[
{"roleName":"Manufacturer","roleId":21},
{"roleName":"EquipmentCode","roleId":22},
{"roleName":"EquipmentSSN","roleId":23}
]
}
]
}
Plunker
I've have created a plunker at: http://plnkr.co/edit/betBR2xLmcmuQR1dznUK?p=preview
I am using ng-repeat to display this in elements as a hierarchy of elements like the following:
When I click on either element the entire structure expands and looks like the following:
The code which renders the DOM is nice and easy and looks like the following:
<div class="headerItem"
ng-class="{focus: hover}"
ng-mouseenter="hover = true"
ng-mouseleave="hover = false"
data-ng-click="vm.onClick(item.roleName)"
data-ng-repeat="item in vm.documentSetups.fieldDefs">{{item.roleName}}
<div class="subItem" ng-show="vm.isVisible"
data-ng-repeat="subItem in item.children">[ ] {{subItem.roleName}}
</div>
</div>
vm.isVisible
The thing to focus on here is the subitem which has the ng-show="vm.isVisible" so that it only displays if that value is true.
Show Only The Subitem of the Clicked Parent
However, I'd like to only display the subitem when its parent item is clicked -- instead of showing all subitems like it does now. Can someone offer a good way to do this? I'm hoping to do it without a directive, because I am interested in whether or not this is possible without a directive or if the code is terribly convoluted in that case.
If you have a solution which includes creating a directive, please keep it as simple as possible. Thanks.
I think you should define a flag for every item which determine if the item is open.
Then you pass the item itself into handler:
data-ng-click="vm.onClick(item)
after that - you simply need to invert isOpen flag:
function onClick(item)
{
item.isOpen = !item.isOpen;
}
The whole view snippet:
<div class="headerItem"
ng-class="{focus: hover}"
ng-mouseenter="hover = true"
ng-mouseleave="hover = false"
data-ng-click="vm.onClick(item)" data-ng-repeat="item in vm.documentSetups.fieldDefs">{{item.roleName}}
<div class="subItem" ng-show="item.isOpen" data-ng-repeat="subItem in item.children">[ ] {{subItem.roleName}}</div>
</div>
The plunker: http://plnkr.co/edit/N8mUZaVfmLpnlW4kxzSr?p=preview
#Oleksii You're answer is very close and it did inspire me to develop the following answer so I appreciate your input and I did upvote you. However, there's a bit more to it than what you gave me.
View Solution at Plunker
I forked the previous plunker and you can see the final solution at:
http://plnkr.co/edit/QvyHlLh83bEyvlNkskYJ?p=preview
No Directive Required
Now I can click either or both element and they expand independently. Here's the sample output:
It took a bit of thinking, but what I did first was create a new type which holds a roleName (consider it unique) and a isVisible boolean. I call that type visibleItem and it looks like this:
var visibleItem = function (roleName){
this.isVisible = false;
this.roleName = roleName;
};
After that I created an array to hold all the visibleItems (1 for each node):
var visibleItems = [];
Now when I load the json I go ahead and create 1 visibleItem object for each node and push it into the visibleItems array.
$http.get('items.json')
.success(function(data, status, header, config) {
vm.documentSetups=data;
for (var x = 0; x < vm.documentSetups.fieldDefs.length; x++)
{
visibleItems.push(new visibleItem(vm.documentSetups.fieldDefs[x].roleName));
}
})
They are "keyed" by their roleName (consider it unique).
Next, I had to write two helper methods (setVisibleItem and getVisibleItem)
function setVisibleItem(roleName)
{
for (var x = 0; x < visibleItems.length;x++)
{
if (visibleItems[x].roleName == roleName)
{
visibleItems[x].isVisible = !visibleItems[x].isVisible;
}
}
}
function getVisibleItem(roleName)
{
for (var x = 0; x < visibleItems.length;x++)
{
if (visibleItems[x].roleName == roleName)
{
return visibleItems[x].isVisible;
}
}
return false;
}
Wire Up The Helper Methods
Finally, I wire up the setVisibleItem to the ng-click of the element and I wire up the getVisibleItem to the ng-show directive.
data-ng-click="vm.onClick(item.roleName)"
data-ng-repeat="item in vm.documentSetups.fieldDefs">{{item.roleName}}
<div class="subItem" ng-show="vm.getVisibleItem(item.roleName)"
data-ng-repeat="subItem in item.children">[ ] {{subItem.roleName}}</div>
</div>
Summary Of How It Works
Basically each of those just iterates through the list and checks to insure if the roleName sent in matches the roleName of the item. If it does it sets or gets the value.
Solved Without a Directive and Not Bad
It's a lot more work than you think it'll be, but I didn't have to implement a directive and the code is still fairly basic.

Activate Current Content Part in jQuery UI Accordion Menu

I'm trying to figure out how to dynamically activate the correct content part of a jQuery UI Accordion menu depending on the page currently being viewed. I've searched extensively and it seems like others have had issues with this in the past, but I haven't yet found a solution that works for me. I know that active can be set to open a certain index of the menu, but I need to do this dynamically.
I'm thinking that I can achieve what I want using the activate method, I just can't seem to figure it out. I'd like to stay away from setting cookies as that usually won't work with back/forward buttons and direct navigation via a specific url. Anyone have any ideas? Thanks in advance!
Here is the simplified structure of my menu:
<div id="menu">
<div id="sections">
<div class="grouping accordion">
<a id="heading1" href="#">Heading #1</a>
<div class="sub-items">
Item #1
<br />
Item #2
</div>
</div>
<div class="grouping accordion">
<a id="heading2" href="#">Heading #2</a>
<div class="sub-items">
Item #4
<br />
Item #6
</div>
</div>
</div>
</div>
And here is my jQuery Accordion init:
$('#sections').accordion({
header: '> .accordion > a',
autoHeight: false,
collapsible: true,
active: false,
animated: 'slide'
});
So if you are currently on the /item4 page for example, the group under Heading #2 should be expanded.
EDIT:
I found what seems to be a pretty good solution and posted that as an answer below, hopefully this will help someone with a similar problem!
To activate a specific tab, you'll want to use the accordion('activate', index) method. Example:
$( "#sections" ).accordion('activate', 2);
However, you will need something that defines an index key per page. You can probably even generate this dynamically. I would probably create a Pages object:
Pages = {
"heading1" : {
"id": 1,
"is_active": false,
"items": {
"item1": {
"href": "item1",
"name": "Item #1"
},
"item2": {
"href": "item2",
"name": "Item #2"
}
}
},
"heading2" : {
/* etc*/
},
}
With some hackish jQuery magic, you can loop through your headings
var active_heading_index = null;
$.each(Pages, function(heading) {
$.each(heading.items, function(item) {
if($(location).attr('href') == item.href) {
heading.is_active = true;
// or
active_heading_index = heading.id
}
});
});
if(active_heading_index) $( "#sections" ).accordion('activate', active_heading_index);
Anyhow, I'm sure there are cleaner and more efficient ways of doing it.
While working on some CSS for the active headings on the menu I stumbled on a pretty clean and easy solution. Looks like I might have been overthinking things!
Using the same HTML as in the question, here's the JavaScript that is working for me:
//accordion menu
$('#sections').accordion({
header: '> .accordion > a',
autoHeight: false,
collapsible: true,
active: '.selected',
animated: 'slide'
});
//add currentPage class to current menu item based on url
var url = window.location.href;
url = url.substr(url.lastIndexOf("/") + 1);
$("#sections").find("a[href='" + url + "']").addClass("currentPage");
//get id of header anchor tag
var headerId = $(".currentPage").parents(".accordion").children("a").attr("id");
//check if headerId is set, if so activate that id
if (headerId) {
$('#sections').accordion('option', 'animated', false);
$('#sections').accordion('activate', $('#' + headerId));
$('#sections').accordion('option', 'animated', 'slide');
}
This solution is pretty simple, it gets the current page from the url and compares it against each link in the accordion menu. If it finds a match, it gives that link a class of currentPage (which allows us to then style that link accordingly via css). Then it looks for a parent of that link with a class of .accordion, finds the first child link (the accordion header) and grabs the header's id. Assuming a header id has been found, we can then use the activate method to expand the correct section.
If you are going back to the server for every page click (standard non Ajaxy way), the server can add a "selected" class to the proper node. So you'd get back something like this at the client (I'm only writing the essential code, skipping most of the labels).
<ul id="menu">
<li>
<ul>
<li>
</li>
<li class="selected">
Menu 102
</li>
<ul>
</li>
<li>
...
</li>
<ul>
Then simply find the proper index to give to the activate property of the accordion.
$(document).ready(function() {
var index = $("li.selected").parents("li").last().index();
$("#menu").accordion({
active: index,
collapsible: true
});
});
The parents("li").last() jQuery returns the top most element. This only works if you only have one sub element with the class "selected", which should be the case for your menu.
I did it using this code:
var newsNum = parseInt(window.location.hash.slice(1));
$(document).ready(function(){
$("#menu").accordion('activate', newsNum );
});
And the url looks like example.com/index.html#1

Categories

Resources