Dragging rows in Perspective Table

it should be possible to capture the event and dispatch a new one with the ctrl key enabled i think.
tho not sure if browers block this nowadays

i dont really have time to try something like this out any time soon, ping me again in a month

you shouldnt need the refresh function,

did you try this?

and this ?

like so (untested)


propName = "rowDroppedData"
code =  """<img style='display:none' src='/favicon.ico' onload=\"
	const view = [...window.__client.page.views._data.values()].find(view => view.value.mountPath == this.parentNode.parentNode.parentNode.getAttributeNode('data-component-path').value.split('.')[0]).value; 
	function ondrop(ev) {
		ev.preventDefault(); 
        ev.stopPropagation();
		const draggedData = ev.dataTransfer.getData('text');
      	const obj = JSON.parse(draggedData);
	  	const draggedId = obj.draggedId;
      	const draggedTable = obj.domID;	
  	    const droppedId = this.getAttribute('data-row-id');
	    const droppedTable = this.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.getAttribute('id');
        const droppedEmpty = this.classList.contains('tb');
        const droppedEmptyTable = this.parentNode.parentNode.parentNode.getAttribute('id');
        view.custom.write('"""+propName+"""',{'draggedId':draggedId,'droppedId':droppedId,'draggedTable':draggedTable,'droppedTable':droppedTable,'droppedEmptyTable':droppedEmptyTable,'droppedEmpty':droppedEmpty});
	};
	function ondragstart(ev){
        
		ev.dataTransfer.setData('text', JSON.stringify({draggedId:ev.target.getAttribute('data-row-id'), domID:ev.target.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.id}));
	};
	function ondragover(ev){
		ev.preventDefault();
	};
function ondropEmpty(ev) {
		ev.preventDefault(); 
        ev.stopPropagation();
		const draggedData = ev.dataTransfer.getData('text');
      	const obj = JSON.parse(draggedData);
	  	const draggedId = obj.draggedId;
      	const draggedTable = obj.domID;	
  	 //----add changes to handle empty and no row id----
	   
        const droppedEmpty = this.classList.contains('tb');
        const droppedEmptyTable = this.parentNode.parentNode.parentNode.getAttribute('id');
        view.custom.write('"""+propName+"""',{'draggedId':draggedId,'droppedId':droppedId,'draggedTable':draggedTable,'droppedTable':droppedTable,'droppedEmptyTable':droppedEmptyTable,'droppedEmpty':droppedEmpty});
	};
	
    const callbackTable = (mutationList, observer) => {
    		document.querySelectorAll('.tr-group.ia_table__rowGroup').forEach(e => {
			e.setAttribute('draggable',true);
			e.ondragstart = ondragstart;
			e.ondragover = ondragover;
			e.ondrop = ondrop;
		});	
 document.querySelectorAll('.ia_tableComponent .t.ia_table > .tb').forEach(e => {
                e.ondragover = ondragover;
                e.ondrop = ondropEmpty;
            });
	};
    									
        const observerTable = new MutationObserver(callbackTable); 
        const tables = document.querySelectorAll('.ia_tableComponent .t.ia_table');
        tables.forEach(table => observerTable.observe(table, { attributes: false, childList: true, subtree: true }));	
        document.querySelectorAll('.tr-group.ia_table__rowGroup').forEach(e => {
            e.setAttribute('draggable',true);
            e.ondragstart = ondragstart;
            e.ondragover = ondragover;
            e.ondrop = ondrop;
        });
 document.querySelectorAll('.ia_tableComponent .t.ia_table > .tb').forEach(e => {
                e.ondragover = ondragover;
                e.ondrop = ondropEmpty;
            });
        
        
    
\"></img>""".replace("\n", "").replace("\t", "")
return code
2 Likes

Sorry, I just got back from PTO. I did not try those. I should be working on the project that I needed this for soon so I will give these a try when I do that. Thanks again for the help.

1 Like

I am also trying same on table component using Up button and down button to shift row up and down by 1.but facing problem can help me

I was able to get this working! This post has been great and thank you @Gavin_Tipker for the question and implementing with the feedback and sharing, and @victordcq you've been awesome in writing these up and helping out in the forums with them and sharing as well!

The only thing I needed to figure out myself really was that the domId was a meta property on the table components that you need to add, then just bound that to the name of the table component and that worked no issue.

Is there any idea as to what the chances are that this injection style of programming with the Markdown component gets patched in later versions of Ignition to where any functionality built this way would be unavailable?

1 Like

Glad to be of help :mage:

Not really, this is kinda unsupported. I dont think they will remove this. However with any patch there could be changes to the components itself which might require you to rework the script

Like if they add in something to the table, you might have to add or remove one parentNode here or there
( probably here: const droppedTable = this.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.getAttribute('id'))
or change a query selector

1 Like

Regarding this section, do you have a resource you can share that explains how I would go about finding that path? I've read your other posts saying it's JS Injection, but how do you find the raw JS of a perspective view/component and where do you go from there?

Also posting the test view I made for dragging from one table to another using this post for future readers. For my use case, I only wanted to be able to drag from the top to the bottom and rearrange the bottom table, so that is how it functions. You can't rearrange the top, dragging from the top to the bottom only adds to the bottom and doesn't delete from the top, and you can't drag from the bottom to the top.
(Done on Version 8.1.27) testDraggingView.zip (27.9 KB)

We dont really have acces to the "raw js" components.

I basicly do this all through google chrome inspector. It allows you to pretty interactivaly type in some js or css which i can use to find the correct selectors.

There is also git repo with some basics for making your own components. Which i gained a lot of knowledge from after making a couple of components. which i used to find some of the nonstandard functions introduced by perspective (like the ones around the view)

Also i have a webdev background

1 Like

Why do you say we need to turn virtualized off on the table? The dataset I'm working with for some of my users is 4000 rows or more. Having virtualized on seems to speed things up quite a bit.

That was for an older version of the script which didnt use MutationObserver yet. (so it only applied the script ones) With virtualized on, not all rows are loaded in at onces, so it didnt work anymore ones you scrolled.
But that already was fixed, i eddited the code, but forget to reread the whole post^^
i'll edit it out of the reply now

2 Likes

The mutation observer was the fix needed to make this work reliably. Thx Victor!

Now, how would you go about using this code on the perspective tree component so a user can drag nodes into different branches? This would make the tree selector so much more useful. I know that 8.3 is hopefully around the corner and that brings the promise of drag n drop to all components

1 Like

I'm not familiar with the perspective tree component but if it works similar to the flex repeater I was able to get the drag and drop to work on the flex repeater. When I generated my flex repeater instances I assigned a unique domID for each then changed up the javascript code to look for that ID when you dragged or dropped into the flex repeater. I'd imagine the tree component would work close to the same. I can post some screenshots tomorrow when I'm back at work if that would help?

This should give you the paths and labels of dragged and dropped. should be enough to move it around accuratly in the props.

	#make the propName the key to write too in the view.custom 
	propName = "rowDroppedData"
	code =  """<img style='display:none' src='/favicon.ico' onload=\"
		const view = [...window.__client.page.views._data.values()].find(view => view.value.mountPath == this.parentNode.parentNode.parentNode.getAttributeNode('data-component-path').value.split('.')[0]).value; 
		function ondrop(ev) {
			ev.preventDefault(); 
			const draggedId = ev.dataTransfer.getData('text');
			const draggedPath = ev.dataTransfer.getData('path');
			const droppedId = this.getAttribute('data-label');
			const droppedPath = this.closest('[data-item-path]').getAttribute('data-item-path') 
			view.custom.write('"""+propName+"""',{'draggedId':draggedId,'droppedId':droppedId,'draggedPath':draggedPath,'droppedPath':droppedPath});
		};
		function ondragstart(ev){
			
			ev.dataTransfer.setData('path', ev.target.closest('[data-item-path]').getAttribute('data-item-path'));
			
			ev.dataTransfer.setData('text', ev.target.getAttribute('data-label'));
		};
		function ondragover(ev){
			ev.preventDefault();
		};
		
		const callbackTable = (mutationList, observer) => {
		document.querySelectorAll('.tree .tree-item').forEach(e => {
					e.setAttribute('draggable',true);
					e.ondragstart = ondragstart;
					e.ondragover = ondragover;
					e.ondrop = ondrop;
				});	
		};
				
			
									
		const observerTable = new MutationObserver(callbackTable); 
		const tables = document.querySelectorAll('.ia.display.tree');
		tables.forEach(table => observerTable.observe(table, { attributes: false, childList: true, subtree: true }));	
		document.querySelectorAll('.tree .tree-item').forEach(e => {
						e.setAttribute('draggable',true);
						e.ondragstart = ondragstart;
						e.ondragover = ondragover;
						e.ondrop = ondrop;
					});		
				
	\"></img>""".replace("\n", "").replace("\t", "")
	return code	
2 Likes

Hi Gavin. Now I need to get drag & drop working for a flex repeater. Can you share what you did to get that to work?

Cheers

Apparently 8.3 does not bring the promise of drag and drop to all components. Perhaps I dreamt that. Will be peppering this javascript throughout our applications.

If you do that be sure you documented it extensivily, because these kinda scripts are bound to get broken on updates.

To make it centered, i believe it should be possible to group all these scripts together in one view (you might need some message event to retrigger this script when navigating or opening popups, so the js gets updated) and add this view in a docked view that is docked on every page.
Then let that one view send out messages depending on which component triggered it (to which view.custom prop is written).
Then listen on the views which use these ondrop features to the correct message instead of looking at the view.custom prop.

Because if you dont, and you add in a bunch of these scripts on different views and someone loads in multiple of these views, some of the query selectors will start to overlap, and it will turn ugly. So its more work now, but it might safe you some headages later, and it will be easier to re-use on other projects. (unless you do think they will add this feature in the future afterall)

@victordcq good call. we'll do the same as we are doing for toast messages.

Do you have an example of the drag and drop javascript for use with a flexRepeater?

We figured it out. Also found a way to make it generic across different perspective components by adding a class to the component and elements. We'll update this post with an updated example.

1 Like

Sorry for the late reply. I've been busy with a non Ignition related project so I haven't been on here as much. Looking forward to seeing what you all did to compare it with what I did!