Angular : How to change dropped element dynamically on dragula ng2? - javascript

I'm setting up an Ionic (4) app, where you can drag and drop elements from a toolbar into a window. I want to change the dropped elements depending on their types. I'm using ng2-dragula.
For exmaple I want to drop an element <ion-chip></ion-chip> and when it's dropped it should be something like <ion-card dragula="DRAGGABLE"></ion-card>.
-I tried changing the DOM outerHTML during an event ( https://github.com/valor-software/ng2-dragula/blob/master/modules/demo/src/app/examples/02-events.component.ts ), but then the dragula inside the new created element is not active.
-I tried *ngIf but this also seems not to load dynamically.
What other possibilities do I have?
<div dragula="DRAGULA_EVENTS">
<div>content</div>
</div>
BAG = "DRAGULA_EVENTS";
subs = new Subscription();
export class EventsComponent{
public constructor(private dragulaService: DragulaService){
this.subs.add(this.dragulaService.drop(this.BAG)
.subscribe(({el,source,target})=>{
el.outerHTML = "<ion-card dragula='DRAGULA_EVENTS'>content</ion-card>"
}
}
})
}
I expect that the new DOM element has the property dragula like i set it on the outerHTML-tag.

I figured it out (works during dragging or dropping):
Nest the dragula element in a new
Create inside page.component.ts :
this.subs.add(this.dragulaService.cloned(this.BAG).subscribe(({clone})=>{
Inside of this, with DOM methods, creating a new Element var new_El = document.createElement('ion-card')
Remove all Childs of the clone 'clone.removeChild(..)`
Bind the new Element inside the clone Element clone.appendChild(new_El)

Related

Bootstrap popover on dynamic elements with container being the parent element

There are a multiple questions here on how to bind Bootstrap popovers on dynamic elements, but none of them solves my issue.
At the moment I am initializing Popovers like this:
const popovers = this.element.querySelectorAll('[data-bs-toggle="popover"]')
const popoverTriggerList = [].slice.call(popovers)
popoverTriggerList.map(function (popoverTriggerEl: HTMLElement) {
return new Popover(popoverTriggerEl, {
container: popoverTriggerEl.parentElement as HTMLElement, // Render the popover next to the trigger element (needed when there are stimulus actions/targets inside the content which need to remain within the scope of the controller)
})
})
This however doesn't work for dynamically added elements on the page.
So, I'm trying the documented way to achieve this:
new Popover(document.body, {
selector: '[data-bs-toggle="popover"]',
// container: popoverTriggerEl.parentElement as HTMLElement, // How do I set that?
})
My problem is - Is there a way to make popovers render inside the parent element, not at the end of the body?

How can we append childnodes to ng-content without rerendering the Angular Elements?

The child nodes which Iam appending are not properly positioned inside the ng-content area of the custom element I created using Angular element.
I tried to reporduce my issue in this Stackblitz
Current solution
It will work if I add the child nodes to the created custom element instance before adding to dom .
Expected Solution
Is there any way I can easily add a childnode to my custom angular
element using javascript(appendChild) without rerendering the whole
component again.
Thanks.
You could use getElementsByClassName:
onAddChild2(e: string) {
let div = document.createElement("div");
div.innerHTML = e;
const eHost = document.getElementsByClassName('container')[0];
eHost.appendChild(div);
}
Check the Stackblitz I forked from you code: https://stackblitz.com/edit/angular-elements-exp-v3r4b3
This is not the Angular way. You should not be manipulating the DOM directly like this. You should be binding an array to your template.
children = ['New Child'];
onAddChild() {
this.children.push('New child');
}
and in the template
<div *ngFor="let child of children">{{child}}</div>

Get vue component from event

I am building a drag and drop feature in my application where the drop area looks like this:
<template>
<div class="layers-panel" #dragover="onDragOver" #drop="onDragDrop">
<layer
v-for="(layer, index) in layers"
:key="layer.name"
:info="layer"
:offset="index"
ref="layer"
></layer>
</div>
</template>
Then the draggable items look like this:
<template>
<div class="layer" draggable="true" #dragstart="onDragStart">
<!-- Content of draggable element -->
</div>
</template>
In my drag over event, I want to get the current component that the element relates to, so when I drag an item over the <div class="layer"...> element I would like to get that component. I am not sure how to get access to this since an event is passed to the #dragover="onDragOver" event which only gives me access to html elements and not Vue components.
This is what my current dragover event looks like:
public onDragOver(evt: DragEvent) {
evt.preventDefault()
if (!evt.target) return
let layer = (evt.target as HTMLElement).closest('.layer') as HTMLElement
}
How can I get the component that that element belongs to?
Vue certainly allows you to access a component instance however I don't think it's necessary to reaching your actual goal: reordering of components via drag-and-drop.
Drag-and-drop means we're limited by what's afforded by DragEvent:
We can access the target element e.g. the element being dragged or dropped on
We can set arbitrary data via DataTransfer
You can't determine where the element is dropped because your drop element contains all the layers. Instead if each layer is a drop element, then you can determine which one it is via dragover via event.target.
We also need to identify which element corresponding to the layers array we're dragging/dropping on. This can be achieved by binding the index of each layer as an attribute so we can access it via event.target.
To keep it simple, I'll use the id attribute so I can just access it later via event.target.id:
<div
v-for="(layer, idx) in layers"
:key="idx"
:id="idx"
draggable="true"
#dragstart="dragstart"
>
<div
:id="idx"
class="draggable"
#dragover="dragover"
#drop="drop"
>
{{ layer }}
</div>
</div>
Note I bind the same index to id so I can correctly identify the target layer.
We'll also need to ensure we can identify the element that was dragged since on drop we'll only have the drop element via event.target. We can do this by setting data in DataTransfer when we first start the drag:
dragstart(event) {
event.dataTransfer.setData("selectedIdx", event.target.id);
},
Now on drop we have access to both and can manipulate layers as we desire:
drop(event) {
var selectedIdx = event.dataTransfer.getData("selectedIdx");
var targetIdx = event.target.id;
var selected = this.layers.splice(selectedIdx, 1)[0];
this.layers.splice(targetIdx, 0, selected);
},
Example codepen

Late "manual" upgrading of an element towards a customized-built-in web component

I have a jQuery plugin (which I don't want to modify) that is dynamically creating a div. Aside from that, I have a webcomponent scrollable-div, which is a customized built-in extended from HTMLDivElement. As I have no control over how that div is created by the jQuery plugin, I need to upgrade it after creation and after it has already been added to the DOM.
class myDiv extends HTMLDivElement {
constructor(...args) {
const self = super(...args);
self.addEventListener('click', (e) => {
e.target.textContent = 'clicked'
})
return self;
}
}
customElements.define('my-div', myDiv, { extends: 'div' });
document.addEventListener('DOMContentLoaded', () => {
// this is where I'm trying to turn the div#upgradeMe into a my-div
upgradeMe.setAttribute('is', 'my-div');
});
<div id="upgradeMe">Click me</div>
Simply adding the is="my-div" attribute obviously does not do the trick, the div simply stays a regular HTMLDivElement. How can I programmatically upgrade a native element that is already in the DOM to a customized built-in web component?
It's not possible because the element is already created as a standard <div> element and not identified when parsed as upgradable (extendable) due to the lack of the is attribute.
If the custom element is already defined, the only possible workaround is to replace the existing by a clone (as suggested in the comments by #barbsan).
The short way:
create a <template> element
copy the div's outerHTML into its innerHTML property
replace the orginal element with the template's content with replaceChild()
class myDiv extends HTMLDivElement {
constructor(...args) {
const self = super(...args);
self.addEventListener('click', (e) => {
e.target.textContent = 'clicked'
})
return self;
}
}
customElements.define('my-div', myDiv, { extends: 'div' });
document.addEventListener('DOMContentLoaded', () => {
// this is where I'm trying to turn the div#upgradeMe into a my-div
upgradeMe.setAttribute('is', 'my-div');
var t = document.createElement( 'template' )
t.innerHTML = upgradeMe.outerHTML
upgradeMe.parentElement.replaceChild( t.content, upgradeMe )
});
<div id="upgradeMe">Click me</div>
Précisions
When an element is parsed, an is value is affected according to the DOM spec:
Elements have an associated namespace, namespace prefix, local name, custom element state, custom element definition, is value. When an element is created, all of these values are initialized.
Only elements with a valid is attribute are identified as customizable:
An element’s custom element state is one of "undefined", "failed", "uncustomized", or "custom". An element whose custom element state is "uncustomized" or "custom" is said to be defined. An element whose custom element state is "custom" is said to be custom.
Therefore if the element has no is attribute at parse time, it will not be customizable. That's why you cannot add the is attribute afterward.
Also in the HTML specs:
After a custom element is created, changing the value of the is attribute does not change the element's behavior, as it is saved on the element as its is value.
The is attribute is used only at element creation (at parse time) to initialize the is value and has no effect if changed when the element is already created. In that sense is value is read-only.
If you want to support all modern browser's you can't customize built in components, Apple said they will never support is="" https://github.com/w3c/webcomponents/issues/509#issuecomment-222860736

How to access subitems of a div that's copied to a variable

On my webpage I have a DIV of the class "editor" that I copy into a variable.
editorTemplate = $('.editor');
The DIV looks like this (simplified):
<div class="editor">
<div>
Title: <span class="title" id="title"> the title goes here </span><br />
<select class="recording_list" id="recording_list">
<option value="1">Pos 1</option>
<option value="2">Pos 2</option>
...
</select>
</div> <!-- class=editor -->
Later I want to create a series from that div by adding it to the page:
$(editArea).append(editorTemplate);
So far so good.
But I want to change some attributes - like the IDs of the fields, some text and the selected element of the option box - before pasting the editor template onto the page.
I can change the ID of the edit template with
$(myEdit).attr("id", "edit" + nEditors);
But I don't know how to access the INNER elements of the template, e.g. the ID and the text of the "title" field.
After the template is pasted into the page I can say
$('#title').attr("id", "title" + nEditors);
$('#title').html("the new text");
...
Is it possible to make these changes BEFORE I paste the template into the page?
You can make use of the JQuery.children() method.
var editorTemplate = $('.editor');
editorTemplate.children('<selectors to uniquely identify the child object>').<method to update the content accordingly>
So then we could do something like this...
count=1;
editorTemplate.children('span#title').html('<Update HTML here>').attr('id','title_'+count);
UPDATE:
I just noticed that your elements are at multiple levels so using .find() would be ideal as it can traverse down multiple levels to select descendant elements (grandchildren, etc.) as well.
You are not copying the elements into a variable.
editorTemplate = $('.editor');
The above creates a jQuery wrapper with a set of pointers which point to the DOM elements. The wrapper allows you to execute jQuery methods targeting the DOM elements.
If you do editorTemplate.find("#title").attr("id", "newId") on that it changes the id attribute of the element you are currently pointing at in the DOM not a new copy.
When you are planning on doing this later:
$(editArea).append(editorTemplate);
The above will not append a new copy of the DOM elements but instead will be moving the elements you point at through the editorTemplate wrapper from their original location in the DOM to the new location in the DOM editArea is referencing.
If you planning on making duplicates of some elements within editorTemplate to append them later you would use jQuery clone(), similar to this:
// use clone(true, true) to also clone any attached events
var editorTemplate = $('.editor').clone();
// Using .find you can change attribute in your new copy/clone
editorTemplate.find("#title").attr("id", "title" + nEditors).html("the new text");
// append your new copy/clone
$(editArea).append(editorTemplate);
You cam use find method to get access to your elements:
var editorTemplate = $('.editor');
$(editorTemplate).find('#title').attr('id', 'title' + nEditors).html('the new text');
editorTemplate.clone()
.attr({})
.find("select").attr({id:"whatever"}).end()
.find("span").....end()
.appendTo($(editarea))
i hope you get the idea

Categories

Resources