Please help again with adjusting JavaScript for Drag&drop

Good evening, everybody!

I have found a very useful macro which helped me a lot with implementing drag and drop functionality. The macro is built upon ChapelRs simple inventory.

I have two problems which I cannot find solution for, can somebody help me with the Javascript part?

  1. The macro is designed to determine span-elements as droppables. But for my game I would like the droppables to be div elements, as in this prototype (player is supposed to drag files into correspondent folder). The JavaScript part of the macro is:
Macro.add('droppable', {
	tags: ["item", "default"],
	handler: function() {
		var dat = new Map();
		
		for (var i = 1; i < this.payload.length; ++i) {
			var key = this.payload[i].name === "default" ? "default" : "inv-" + this.payload[i].args[0];
			dat.set(key, this.payload[i].contents);
		}
		
		var $wrapper = $('<span class="droppable" ondrop="dropInv(event)" ondragover="allowDrop(event)"></span>');
		var $itemTextWrapper = $('<span class="droppableText"/>');
		$wrapper.data("actions", dat);
		$wrapper.html($itemTextWrapper.wiki(this.args[0]));
		$wrapper.appendTo(this.output);
	}
});

I tried changing $wrapper to $('<div class="droppable" ondrop="dropInv(event)"..., but still get the same error.

  1. Unlike intended in original macro, I do not know items in advance, because I generate them newly for every game (new file names). Because of this, I cannot follow the structure suggested by macro author:
<<droppable "Chicken">>\
  <<item "egg">>\
    <<goto "Egg">>\
  <<item "corn">>\
    <<goto "Corn">>\
  <<default>>\
    The chicken clucks.
<</droppable>>

I need macro to check if dragged item is found in an array or not, like e.g.:

<<droppable "spreadsheets">>\
  <<itemInArray "$xls">>\
       <<  code to be executed if correct file was dragged....>>
  <<default>>\
    <<  code to be executed if incorrect file was dragged>>
<</droppable>>

Full code of drag&drop macro can be found here. Could you guys help me? Thank you very much, hopefully you guys have great holidays.

Happy new year! Sorry guys for bothering again, but could somebody maybe help me with my problem?

For first problem (drag stuff onto div instead of span) I still have no idea what problem could be.

For second problem, I think behaviour is controlled here:


setup.dragDrop.dropInv = function(ev) {
	ev.preventDefault();
	var invItemID = ev.dataTransfer.getData("text");
	var $targetWrapper = $(ev.target).parent();
	var actionToPerform = $targetWrapper.data("actions").get(invItemID);
	if(actionToPerform === undefined) {
		actionToPerform = $targetWrapper.data("actions").get("default");
	}
	$targetWrapper.wiki(actionToPerform);
};

I am currently doing codeadacademy javascript course, so knowledge is very limited. But I understand this line:
var actionToPerform = $targetWrapper.data("actions").get(invItemID);

tells macro to do what is inside item code when item is dropped. Soā€¦ do I follow correct way if I fiddle with this line and turn it into something like this?

if (item string finishes with ".doc"){
    var actionToPerform = $targetWrapper.data("actions").get(invItemID);}

Thank you!

Small update from me: First problem (error when dragging items on folders) was mistake on my side, CSS style for folders was put onto .droppable instead of .droppabletext. Works now in new prototype.

For second problem (make makro fire correct behaviour on predefined set of ā€œfileā€ items), I still struggle. As I am not able to change Javascript of macro, I thought I try ā€œstupid print trickā€ to achieve what I need. So I tried with e.g. $docFiles = ["file1.doc", "file2.doc", "file3.doc"]

<<droppable "text files">>
    <<for _i to 0; _i lt $docFiles.length; _i++>>\
 		<<print '<<item "' + $docFiles[_i] + '">>'>>
		<<print '<<run UI.alert("Correct!")>>\'>>
    <</for>>
 	<<default>>\
		<<run UI.alert("Wrong!")>>\
 <</droppable>>

Folder is displayed correcty, but I receive ā€œWrong!ā€ for all items, even the ones in $docFiles.

Youā€™re only seeing the <<default>> case because itā€™s the only one that exists when the macro is invoked. You cannot add cases to the macro post-invocation, which is what youā€™re attempting in your above example. What you would need to do to utilize the Stupid Print Trick is to print the entire macro, so that the cases are already in place when itā€™s invoked.

That said. Hereā€™s a slightly modified version of the macro that should do what you want without needing to use the Stupid Print Trick: (untested)

// Usage:
// <<droppable "TargetText">>
//     <<item "InvItem">>
//         ā€¦your content hereā€¦
//     <<itemsarray Array>>
//         ā€¦your content hereā€¦
//     <<default>>
//         ā€¦your content hereā€¦
// <</droppable>>

Macro.add('droppable', {
	tags    : ['item', 'itemsarray', 'default'],
	handler : function () {
		var dat = new Map();

		for (var i = 1; i < this.payload.length; ++i) {
			var payload = this.payload[i];

			if (payload.name === 'itemsarray') {
				var items = payload.args[0];

				if (!(items instanceof Array)) {
					return this.error('itemsarray argument must yield an Array (received: ' + Util.getType(items) + ')');
				}

				items.forEach(function (item) {
					dat.set('inv-' + item, payload.contents);
				});
			}
			else {
				dat.set(
					payload.name === 'default' ? 'default' : 'inv-' + payload.args[0],
					payload.contents
				);
			}
		}

		var $wrapper = $('<span class="droppable" ondrop="dropInv(event)" ondragover="allowDrop(event)"></span>');
		var $itemTextWrapper = $('<span class="droppableText"></span>');
		$wrapper.data('actions', dat);
		$wrapper.html($itemTextWrapper.wiki(this.args[0]));
		$wrapper.appendTo(this.output);
	}
});

Using that version you should be able to do the following:

<<droppable "text files">>
	<<itemsarray $docFiles>>\
		<<run UI.alert("Correct!")>>\
	<<default>>\
		<<run UI.alert("Wrong!")>>\
<</droppable>>
1 Like

Thank you very much, @TheMadExile for your help. I integrated your macro into new prototype.
For some reason, I still get ā€œWrong!ā€ for all items.
Arrays are populated in the same passage as where macro is called, but before (I printed array contents to prove this). Do they need to be populated in a passage before the passage, where the macro is called?

Could you also help me with one last thing? In order to simulate desktop file system, file needs to disappear after being dragged into folder. How can I < < remove > > the file that has just been dragged into correct folder?

Thank you again very much.

Your current <<positionFiles>> widget does not prefix the element IDs of the various items with inv- as it is supposed to do, based on the examples from the original source. Thus, the IDs of the dropped items do not match the IDs generated within the <<droppable>> macro, which is why the default case is always used.

There are a few ways to address this, however, it would probably be best to have your <<positionFiles>> widget add the prefix as shown in the examples. To do so, for each case do the following:

FIND:
<<print'<div id="' + _fileName + '"

REPLACE WITH:
<<print '<div id="inv-' + _fileName + '"

 

Youā€™ll need to alter your current dropInv() function. Something like the following will remove the itemā€™s element from the DOM. Note: I did not look too deep into your demo to see if youā€™re actually integrating with Chapelā€™s inventory system or not, so this may need to be extended to remove the items from it as well.

setup.dragDrop.dropInv = function(ev) {
	ev.preventDefault();

	var invItemId = ev.dataTransfer.getData('text');
	var actionMap = $(ev.target).parent().data('actions');
	var hasItem   = actionMap.has(invItemId);
	var action    = hasItem ? actionMap.get(invItemId) : actionMap.get('default');

	$.wiki(action);

	if (hasItem) {
		var invItem = document.getElementById(invItemId);

		if (invItem && invItem.parentNode) {
	  		invItem.parentNode.removeChild(invItem);
		}
	}
};
1 Like

Thank you again, @TheMadExile for your time and dedication. Everything works now perfectly. Prototype updated.

While I tested, one thing happened repeatedly, which I cannot understand: When items are dragged to folders, mouse pointer is mostly displaying ā€œĆ˜ā€-symbol. If you release mouse button there, nothing happens - as expected.
But on some occasions, mouse pointer changes to drop symbol (pointer with checkered rectangle at bottom). When I release mouse button there, I get
Error: actionMap is undefined.
But there are no visible elements, nor does inspector show anything besides folders and items.

I assume youā€™re using a Blink-/WebKit-based browser to test, because I didnā€™t observe that behavior in Firefox.

The issue seems to stem from setting your .droppableText class to display as block. While Blink-browsers respect the dimensions you set as far as the borders of the elements go, it does not seem to respect them for the drop target bounding box.

Simply changing the .droppableText class to display as inline-block resolves that issue. For example:

.droppableText {
	display: inline-block;
	width: 100px;
	height: 66px;
	position: relative;
	background-color: khaki;
	border-radius: 0 6px 6px 6px;
	box-shadow: 4px 4px 7px rgba(0, 0, 0, 0.59);
	margin-bottom: 3em;
}

Doing that, however, does cause an issue where your folders are now inline, but that can be fixed by adding a few extra line breaks in your Desktop passage. For example:

<<positionFiles>>
<<print "contents of textfiles array: " + $docFiles>><br>
<<print "contents of spreadsheets array: " + $xlsFiles>><br>
<<print "contents of photos array: " + $jpgFiles>><br>
<br>
<<droppable "text files">>
	<<itemsarray $docFiles>>
		<<run UI.alert("Correct!")>>
	<<default>>
		<<run UI.alert("Wrong!")>>
<</droppable>>
<br>
<<droppable "spreadsheets">>
	<<itemsarray $xlsFiles>>
		<<run UI.alert("Correct!")>>
	<<default>>
		<<run UI.alert("Wrong!")>>
<</droppable>>
<br>
<<droppable "photos">>
	<<itemsarray $jpgFiles>>
		<<run UI.alert("Correct!")>>
	<<default>>
		<<run UI.alert("Wrong!")>>
<</droppable>>

NOTE: Tested in Firefox and Chrome.

1 Like

Thank you again for your time and dedication. It is strange that this behaviour occured on my side, as I use FF exclusively.

In hope that others can make use of your help as well, I uploaded final prototype here.

The adapted macro in StoryJavascript I pasted here.

1 Like