# Perspective Scheduler - Drag and Drop From Table

**URL:** <https://forum.inductiveautomation.com/t/perspective-scheduler-drag-and-drop-from-table/71853>\
**Category:** Ignition\
**Tags:** perspective\
**Created:** [March 30, 2023, 6:31pm UTC](https://forum.inductiveautomation.com/t/perspective-scheduler-drag-and-drop-from-table/71853 "2023-03-30T18:31:59Z")\
**Posts on this page:** 17\
**Page:** 1

<div class="post-metadata">

**Author:** ![alex.chmelka](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/alex.chmelka/32/26672_2.png) [@alex.chmelka](https://forum.inductiveautomation.com/u/alex.chmelka)\
**Post date:** [March 30, 2023, 6:31pm UTC](https://forum.inductiveautomation.com/t/perspective-scheduler-drag-and-drop-from-table/71853/1 "2023-03-30T18:31:59Z")

</div>

I'm trying something I'm not 100% is possible to do but wanted to throw the question in case others have attempted it. We are experimenting with the Perspective Scheduler. A big potential GUI goal we have can be broken into a few steps:

1. Have a table with rows of items that need to be put into the schedule. This is assumed to be working.

2. Be able to click and drag a row from the table and fire an event when this occurs. This forum post by @victordcq below does enough to fulfill my goal. I am able to fire an event script and interact with the scheduler when a row is dropped.  
[Dragging rows in Perspective Table - Ignition - Inductive Automation Forum](https://forum.inductiveautomation.com/t/dragging-rows-in-perspective-table/68922/8)

3. Create a new scheduledEvent in the scheduler depending on where the user dropped the row of data. This is the part I'm struggling on. There are a few JS extension scripts tacked onto the perspective scheduler, but these are only triggerable by clicking into a square. Is it possible to, on an onClick event, to know where the mouse is located inside the scheduler? If I could somehow figure out where the mouse is located, perhaps I could locate the start and end date or the itemID needed to create a new scheduledEvent.

Thanks for any ideas.

---

<div class="post-metadata">

**Author:** ![YF129701](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/yf129701/32/60498_2.png) [@YF129701](https://forum.inductiveautomation.com/u/YF129701)\
**Post date:** [March 30, 2023, 8:14pm UTC](https://forum.inductiveautomation.com/t/perspective-scheduler-drag-and-drop-from-table/71853/2 "2023-03-30T20:14:43Z")

</div>

I'm assuming when you say Perspective Scheduler, you're talking about the Equipment Schedule component...

There probably is a way to do this pretty easily. I would think the hardest part of accomplishing what you want is the dragging and dropping. The Equipment Schedule component has a `props.scheduleEvents` property (if I remember correctly) and you'll need to persist that data across sessions. The easiest way to do this is to use a database, of course, which is how I've done it in the past. I suppose you could also create a Dataset or Document tag to hold those schedule events, or use a session custom property. I would think a database would be most useful, since you'll be able to do reporting on current and historical events easier.

Your task 3 becomes trivial - as long as you can trigger an action (ie. when you "drop" the table row onto the schedule), all you'd need to do is to insert a database record and refresh the binding on `props.scheduleEvents` and your event will appear in the schedule.

Last thing: something that tripped me up when I began using this component are the concepts of `scheduleEvents` and `items` - schedule events are your events on the schedule but items are the Lines or Areas you display (the rows on the left side of the Equipment Schedule component).

---

<div class="post-metadata">

**Author:** ![alex.chmelka](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/alex.chmelka/32/26672_2.png) [@alex.chmelka](https://forum.inductiveautomation.com/u/alex.chmelka)\
**Post date:** [March 30, 2023, 8:57pm UTC](https://forum.inductiveautomation.com/t/perspective-scheduler-drag-and-drop-from-table/71853/3 "2023-03-30T20:57:16Z")

</div>

Apologies for the confusion, yes, I meant the equipment scheduler. These are good pointers, especially with the `items`. That was confusing me slightly.

I have two issues with the drag and drop capabilities so far. I can't seem to find a good way to trigger an event if a row is dropped into the scheduler. I also want to take into WHERE the user 'drops' the row. This way you could utilize the scheduler's structure. This means somehow getting knowledge of where the cursor is inside the scheduler or some way to trigger the add extension function. Not sure if this is possible.

---

<div class="post-metadata">

**Author:** ![victordcq](https://avatars.discourse-cdn.com/v4/letter/v/f6c823/32.png) [@victordcq](https://forum.inductiveautomation.com/u/victordcq)\
**Post date:** [March 31, 2023, 6:54am UTC](https://forum.inductiveautomation.com/t/perspective-scheduler-drag-and-drop-from-table/71853/4 "2023-03-31T06:54:43Z")

</div>

similar to the script i provided here, you should also be able to add on drop events on the scheduler;

the cells have a attribute data-column and data-row, which you could use to determine where it was dropped (column 0 is start date of the scheduler, row 0 is the first item)

you can probably lso trigger an onclick event on these cells, but it might be tricky to combine this drag and drop and on click event handelrs if you do, probably best to just write the new scheduled event directly into the component. So that perspective can handle the moving and editing.  
I have not checked if its easy to trigger an add event. ill have to take a deeper look next week

(not working just what you have to change: )  
the selector to appy these 2 event too: `.schedule-grid-cell.ia_equipmentScheduleComponent __gridSpace__ gridCell`

```auto
function ondrop(ev) {
	ev.preventDefault(); 
	const draggedId = ev.dataTransfer.getData('text');
	const droppedId = this.getAttribute('data-row-id'); 
	view.custom.write('"""+propName+"""',{'draggedId':draggedId,'droppedId':droppedId});
};
function ondragover(ev){
	ev.preventDefault();
};	
	
document.querySelectorAll('.schedule-grid-cell.ia_equipmentScheduleComponent __gridSpace__ gridCell').forEach(e => {

```

i can make a working test project next week if i have the time

---

<div class="post-metadata">

**Author:** ![alex.chmelka](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/alex.chmelka/32/26672_2.png) [@alex.chmelka](https://forum.inductiveautomation.com/u/alex.chmelka)\
**Post date:** [March 31, 2023, 2:05pm UTC](https://forum.inductiveautomation.com/t/perspective-scheduler-drag-and-drop-from-table/71853/5 "2023-03-31T14:05:40Z")

</div>

Thanks for the information @victordcq. I'm still fairly green on perspective development, let alone getting fancy with js injection. Would be really happy to see what you could throw together for this if you have the time.

As for an event to tie into, whenever I attempt to drag a row of data into the scheduler component, I cannot get an onClick event to trigger. Or even any event involving the mouse moving. I might just not have a great understanding of what I need to do for the drag to trigger properly. Just to make note, we are on **8.1.21** if we are missing some updates. Thanks for the help.

---

<div class="post-metadata">

**Author:** ![victordcq](https://avatars.discourse-cdn.com/v4/letter/v/f6c823/32.png) [@victordcq](https://forum.inductiveautomation.com/u/victordcq)\
**Post date:** [April 3, 2023, 7:45am UTC](https://forum.inductiveautomation.com/t/perspective-scheduler-drag-and-drop-from-table/71853/6 "2023-04-03T07:45:00Z")

</div>

You can trigger an event using componentEvents. fireComponentEvent  
It takes a bit of knowledge to find where to use it...

The problem is you need to fill in the event yourself (you can add whatever) So you will need to calculate the start date using the column id

might need some more insight on how ignition gets the start date, only way i see this to be possible using only info of the frontend is when dateRange prop is filled in, so you can calculate it... you might also have to grab the zoom level i suppsoe

(this is not complete and only half tested)

```auto
    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');

  #the zoom interval (Month, Day, 6 hours ...) use to calcualte the date, 
  const interval = this.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.querySelector(".ia_equipmentScheduleComponent __actionBar__ dropdown .ia_dropdown__valueSingle").getAttribute("data-label")
  
  #it will be easier to calculute the start/end date in python using the dateRange together with the found interval and column
  const addEvent = {'end':'?','start':'?', 'itemId':this.getAttribute('data-row'), 'draggedId ':draggedId, 'interval':interval,'droppedColumn':this.getAttribute('data-column') }

  #assuming the schedule is child directly under the root 
  const scheduleComponent = view.root.childComponents.find((component)=> component.def.meta.name=='EquipmentSchedule')
  scheduleComponent.componentEvents.fireComponentEvent('onAddEvent',{'event':addEvent})
};

```

---

<div class="post-metadata">

**Author:** ![victordcq](https://avatars.discourse-cdn.com/v4/letter/v/f6c823/32.png) [@victordcq](https://forum.inductiveautomation.com/u/victordcq)\
**Post date:** [April 5, 2023, 7:03am UTC](https://forum.inductiveautomation.com/t/perspective-scheduler-drag-and-drop-from-table/71853/7 "2023-04-05T07:03:40Z")

</div>

Hmm not all the cells are loaded on page load, i'll need to modify it to apply the on drop events on dom changes.

But i made a proof on concept that is working so far, ill get back to it when i have some time

---

<div class="post-metadata">

**Author:** ![alex.chmelka](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/alex.chmelka/32/26672_2.png) [@alex.chmelka](https://forum.inductiveautomation.com/u/alex.chmelka)\
**Post date:** [April 18, 2023, 10:17pm UTC](https://forum.inductiveautomation.com/t/perspective-scheduler-drag-and-drop-from-table/71853/9 "2023-04-18T22:17:39Z")

</div>

I actually got the drag and drop feature working, but I did not resolve your issue with the start and end dates completely. I just assumed each cell was one hour. I have been trying to do what you were doing and drill down to the selected interval with this line:

```auto
const interval = this.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.querySelector(".ia_equipmentScheduleComponent __actionBar__ dropdown .ia_dropdown__valueSingle").getAttribute("data-label")

```

However, I cannot seem to get the markdown to function when adding this. I've been trying to do some JS reading to figure out why that line isn't working, but no luck. If someone finds a way to get access to that selected scale, I will have everything I need to get this project up and running.

---

<div class="post-metadata">

**Author:** ![victordcq](https://avatars.discourse-cdn.com/v4/letter/v/f6c823/32.png) [@victordcq](https://forum.inductiveautomation.com/u/victordcq)\
**Post date:** [April 19, 2023, 6:42am UTC](https://forum.inductiveautomation.com/t/perspective-scheduler-drag-and-drop-from-table/71853/10 "2023-04-19T06:42:19Z")

</div>

i'll find some time to finish this tmw.

the js injection is senstive to ";" at the end of each line, which i did not add everywhere in the snipet above as i wasnt testing that in ignition

---

<div class="post-metadata">

**Author:** ![alex.chmelka](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/alex.chmelka/32/26672_2.png) [@alex.chmelka](https://forum.inductiveautomation.com/u/alex.chmelka)\
**Post date:** [April 19, 2023, 5:02pm UTC](https://forum.inductiveautomation.com/t/perspective-scheduler-drag-and-drop-from-table/71853/11 "2023-04-19T17:02:05Z")

</div>

I was able to grab the interval and am able to set the start time dynamic to the zoom level. Figured out the issue. However, as you mentioned earlier, the javascript is only applied to the cells that are loaded on the page. Since not all cells are loaded initially, once you scroll around, the drag and drop no longer functions. Is there a way to constantly apply the component binding to trigger whenever a new cell is loaded?

Update: This was something I was trying with my rudimentary JS skills. I was trying to use the testTwo function to print out to the console whenever the scroll bar is used, but the onchange is not working. I just don't know how to latch until a change in the grid object:

```auto
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 droppedRow = this.getAttribute('data-row');
				const droppedColumn = this.getAttribute('data-column')
				const droppedId = this.getAttribute('data-row-id');
				const interval = this.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.querySelector('.ia_equipmentScheduleComponent __actionBar__ dropdown .ia_dropdown__valueSingle').getAttribute('data-label');
				const addEvent = {'draggedId':draggedId,'itemId':droppedRow,'droppedColumn':droppedColumn,'interval':interval};
				view.custom.write('"""+propName+"""',addEvent);
				const scheduleComponent = view.root.childComponents.find((component)=> component.def.meta.name=='EquipmentSchedule');
				scheduleComponent.componentEvents.fireComponentEvent('onAddEvent',{'event':addEvent});
			};
			function ondragstart(ev){
				ev.dataTransfer.setData('text', ev.target.getAttribute('data-row-id'));
			};
			function ondragover(ev){
				ev.preventDefault();
			};
			function test(){
			document.querySelectorAll('.schedule-grid-cell.ia_equipmentScheduleComponent __gridSpace__ gridCell').forEach(e => {
				e.setAttribute('draggable',true);
				e.ondragstart = ondragstart;
				e.ondragover = ondragover;
				e.ondrop = ondrop;
			});
			}
			test();
			function testTwo(){
				view.custom.write('"""+propName+"""',{'test':'helloWorld'});
			};
			document.querySelectorAll('.ReactVirtualized __Grid__ innerScrollContainer').onchange = function(){testTwo()};
		\"></img>""".replace("\n", "").replace("\t", "")
		return code

```

---

<div class="post-metadata">

**Author:** ![victordcq](https://avatars.discourse-cdn.com/v4/letter/v/f6c823/32.png) [@victordcq](https://forum.inductiveautomation.com/u/victordcq)\
**Post date:** [April 20, 2023, 6:15am UTC](https://forum.inductiveautomation.com/t/perspective-scheduler-drag-and-drop-from-table/71853/12 "2023-04-20T06:15:28Z")

</div>

yes you can setup a mutation observer that triggers on domchanges  
ive done something similar here:

> [@Power Chart - Showing transformed value in x-trace instead of integer value?](https://forum.inductiveautomation.com/t/power-chart-showing-transformed-value-in-x-trace-instead-of-integer-value/68527/3):
>
> it would be possible with js injection, but seems like a lot of work for what a legend could solve edit... ... and ofc i do a lot of work stuck_out_tongue it works... but i'm a bit worried about client performance on big charts, so test it out! And let us know if its doable. the onhover event didnt trigger enough to match the chart, so im doing a mutationobserver on it, but that kinda triggers to much... give your chart a domid and setup your value mapping this binding goes into the mar…

i'll fix up the script i have in a bit

edit: here you go!

a table , a schedule and the markdown are in here, with the dragdrop in de markdown and an (example of) AddEvent in de schedule.  
[tabledropschedule.zip](https://forum.inductiveautomation.com/uploads/short-url/vraW8MysI6UtTLmks7yMcZEDSM8.zip) (12.6 KB)

---

<div class="post-metadata">

**Author:** ![victordcq](https://avatars.discourse-cdn.com/v4/letter/v/f6c823/32.png) [@victordcq](https://forum.inductiveautomation.com/u/victordcq)\
**Post date:** [April 20, 2023, 6:58am UTC](https://forum.inductiveautomation.com/t/perspective-scheduler-drag-and-drop-from-table/71853/13 "2023-04-20T06:58:29Z")

</div>

(linking it with my js topic)

> [@Javascript inject in Perspective... have fun](https://forum.inductiveautomation.com/t/javascript-inject-in-perspective-have-fun/65805/5):
>
> Gonna try to keep this topic updated with all my cool injectings stuck_out_tongue Version 8.1.22 svg clicker wink

---

<div class="post-metadata">

**Author:** ![alex.chmelka](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/alex.chmelka/32/26672_2.png) [@alex.chmelka](https://forum.inductiveautomation.com/u/alex.chmelka)\
**Post date:** [April 20, 2023, 1:11pm UTC](https://forum.inductiveautomation.com/t/perspective-scheduler-drag-and-drop-from-table/71853/14 "2023-04-20T13:11:55Z")

</div>

Man, you are a lifesaver. This would've taken me an absurd amount of time to figure out how to build that mutation observer. Thanks!

---

<div class="post-metadata">

**Author:** ![victordcq](https://avatars.discourse-cdn.com/v4/letter/v/f6c823/32.png) [@victordcq](https://forum.inductiveautomation.com/u/victordcq)\
**Post date:** [April 20, 2023, 1:26pm UTC](https://forum.inductiveautomation.com/t/perspective-scheduler-drag-and-drop-from-table/71853/15 "2023-04-20T13:26:39Z")

</div>

hehe yeah its not something people usually use, its basicly only used to hack your way in other components xD

---

<div class="post-metadata">

**Author:** ![mmadariaga](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/mmadariaga/32/63201_2.png) [@mmadariaga](https://forum.inductiveautomation.com/u/mmadariaga)\
**Post date:** [July 4, 2024, 1:45pm UTC](https://forum.inductiveautomation.com/t/perspective-scheduler-drag-and-drop-from-table/71853/16 "2024-07-04T13:45:21Z")

</div>

Thanks for this work, it helps me a lot. I was trying to understand the markdown source binding with no sucess...

```python
def transform(self, value, quality, timestamp):
#make the propName the key to write too in the view.custom 
#const callback = (mutationList, observer) => {let textLabels = document.querySelectorAll('#"+chartDomId+" .ia_powerChartComponent __labelText.ia_powerChartComponent__ xTrace __box__ label > tspan'); textLabels.forEach(textLabel => {if(textLabel.textContent in mappingStatus){textLabel.textContent = mappingStatus[textLabel.textContent]; }})}; const observer = new MutationObserver(callback); observer.observe(chart, { attributes: false, childList: true, subtree: true })
	scheduleName = "EquipmentSchedule"
	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 interval = this.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.querySelector('.ia_equipmentScheduleComponent __actionBar__ dropdown .ia_dropdown__valueSingle').getAttribute('data-label');		  		  
		  const addEvent = {'end':'?','start':'?', 'itemId':parseInt(this.getAttribute('data-row')), 'draggedId':parseInt(draggedId), 'interval':interval,'droppedColumn':parseInt(this.getAttribute('data-column')) };
		  const scheduleComponent = view.root.childComponents.find((component)=> component.def.meta.name=='"""+scheduleName+"""');
		  scheduleComponent.componentEvents.fireComponentEvent('onAddEvent',addEvent);
		};
		function ondragstart(ev){			
			ev.dataTransfer.setData('text', ev.target.getAttribute('data-row-id'));
		};
		function ondragover(ev){
			ev.preventDefault();
		};
		const callbackTable = (mutationList, observer) => {
		document.querySelectorAll('.ia_tableComponent .tr-group.ia_table__rowGroup').forEach(e => {
					e.setAttribute('draggable',true);
					e.ondragstart = ondragstart;			
				});
		};
		
		const callbackSchedule = (mutationList, observer) =>{
			document.querySelectorAll('.ia_equipmentScheduleComponent .schedule-grid-cell.ia_equipmentScheduleComponent __gridSpace__ gridCell').forEach(e => {			
						e.ondragover = ondragover;
						e.ondrop = ondrop;
					});	
		};
							
		const observerTable = new MutationObserver(callbackTable); 
		const tables = document.querySelectorAll('.ia_tableComponent .tb.ia_table__body');
		tables.forEach(table => observerTable.observe(table, { attributes: false, childList: true, subtree: true }));		
		const observerSchedule = new MutationObserver(callbackSchedule); 
		const schedules = document.querySelectorAll('.ia_equipmentScheduleComponent .ReactVirtualized__Grid');
		schedules.forEach(schedule => observerSchedule.observe(schedule, { attributes: false, childList: true, subtree: true }));
		document.querySelectorAll('.schedule-grid-cell.ia_equipmentScheduleComponent __gridSpace__ gridCell').forEach(e => {			
												e.ondragover = ondragover;
												e.ondrop = ondrop;
											});
		document.querySelectorAll('.ia_tableComponent .tr-group.ia_table__rowGroup').forEach(e => {
													e.setAttribute('draggable',true);
													e.ondragstart = ondragstart;			
												});			
	\"></img>""".replace("\n", "").replace("\t", "")
	return code
	

```

For example, I want to place the table and the equipment schedule insde another flex container. I understand that I should change the _const view_ adding another _.parentNode_, but I guess that's not it. Could you give me any advice?

---

<div class="post-metadata">

**Author:** ![victordcq](https://avatars.discourse-cdn.com/v4/letter/v/f6c823/32.png) [@victordcq](https://forum.inductiveautomation.com/u/victordcq)\
**Post date:** [July 4, 2024, 2:28pm UTC](https://forum.inductiveautomation.com/t/perspective-scheduler-drag-and-drop-from-table/71853/17 "2024-07-04T14:28:45Z")

</div>

I have actually updated this code to be this recently, though it does exactly the same.

```python
const view = [...window.__client.page.views._data.values()].find(view => view.value.mountPath == this.closest('[data-component-path]').getAttributeNode('data-component-path').value.split('.')[0]).value; 

```

The **this** is refering to the generated img html inside the markdown component. So you never really need to change it.  
It is looking for the markdowns path attribute, something perspective gives all its components. With part of this this path it is possible to find the view the markdown is located it through the windows.\_\_client.page.views.

Perspective generates these random letters and numbers based on the position a component is in.

 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/1/7/17ec7d0d4ce01b48e45a10bb3644ec24c91bccdc.png)

You can open a console in the chrome browser console and poke around in the windows.client a bit to find this, here the mount path is on the 3th view "C" coming from the markdown path "C.0:0"

 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/a/f/afa8dec7faefeec13a7d841cdd3acddb78b4f15f.png)

For elements inside an embeded view this might look a little different (more complex) than just the one letter, but should still work the same

> [@mmadariaga](#):
>
> insde another flex container.

You might change somethiing to find the views properties here, you probably will have to first find the container in the root view, and than in there its childeren will be the schedule

```python
 const scheduleComponent = view.root.childComponents.find((component)=> component.def.meta.name=='"""+scheduleName+"""');

```

---

<div class="post-metadata">

**Author:** ![mmadariaga](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/mmadariaga/32/63201_2.png) [@mmadariaga](https://forum.inductiveautomation.com/u/mmadariaga)\
**Post date:** [July 4, 2024, 3:12pm UTC](https://forum.inductiveautomation.com/t/perspective-scheduler-drag-and-drop-from-table/71853/18 "2024-07-04T15:12:34Z")

</div>

Nice, thank you!!

```python
		  const flexContainer = view.root.childComponents.find((component) => component.def.meta.name === 'FlexContainer');
		  const scheduleComponent = flexContainer.childComponents.find((component) => component.def.meta.name === '"""+scheduleName+"""');
		  scheduleComponent.componentEvents.fireComponentEvent('onAddEvent',addEvent);

```
