Hype Data Decorator (reactive live mapping)

Project for getting reactivity and feedback directly in Tumult Hype 4 using the additional HTML-attributes panel for options.

Usage: After registering the dataset keys with HypeDataDecorator.mapDataAttribute for example HypeDataDecorator.mapDataAttribute('headline'); every element below an element that has a data-headline and has the class .headline inside will have the content defined under data-headline.

Code repository on GitHub

Version history
1.0 Initial release under MIT-license
1.1 Added option to set initial value
1.2.0 Inspired by Symbol Override I added a callback
1.2.1 Also updating when class is modified (only in IDE)
1.2.2 Minor bugfix on preview, refactored names (breaking change)
1.2.3 Remove the possibility for recursive loops in IDE and console.log
1.2.4 Added hypeDocument, symbolInstance to callback and setContent
1.2.5 Renamed and refactored to Hype Data Decorator
1.2.6 Another refactor, comments in code, cleanup and direct observer

Documentation

There is a JSDoc based documentation of the functions at https://doxdox.org/worldoptimizer/HypeDataDecorator

Content Delivery Network (CDN)

Latest version can be linked into your project using the following in the head section of your project:

<script src="https://cdn.jsdelivr.net/gh/worldoptimizer/HypeDataDecorator/HypeDataDecorator.min.js"></script>

Optionally you can also link a SRI version or specific releases.
Read more about that on the JsDelivr (CDN) page for this extension at https://www.jsdelivr.com/package/gh/worldoptimizer/HypeDataDecorator

Learn how to use the latest extension version and how to combine extensions into one file at

3 Likes

Little sidenote: Hype by default sets the dataset values on every scene load. I am using that fact as it triggers the observer and creates all values. Downside is though, the Hype-Runtime always sets the values you "hardcoded" in the IDE. For most simple cases it's no problem but if you want to use the reactive nature of this beyond the scene scope you currently need to refrain from defining the value in the IDE. I added an example on how to create a persistent value "user". In the long run some built in persistent option would be cool with IDE preview.

Still a valid conclusion but workaround is now baked in to Hype DataDecorator 1.1

↑ look at project
1.1 Added option to set initial value
Example also updated…

Preface: This is only a concern for consistency across scenes… if you only plan to update values in a scene or update them on scene load anyway… this 1.1 update and notes are nothing you have to think about.

Example: You mapped data-user to the class .user with HypeDataDecorator.mapDataAttribute('user'); in your Head HTML. Whenever you assign data-user on a group or symbol all children with the class .user will be updated. If you are doing this assignment in the IDE it will be set by Hype on every scene load. To avoid that just set .user-initial instead.

Explanation of initial-clause: All values set with the attribute panel in Hype are persistent duo to the Hype runtime refreshing them on each scene load. This little "genie" at work might be what people expect using the IDE but it certainly isn't how programmers updating values per script would expect things to behave. Hype DataDecorator 1.1 now has a baked in workaround for this… just add "-initial" to your attribute entry (for example data-user-initial given your key is normally user). Then this value will only be set as an initial value and honor updates done via script like yourElement.dataset.user = "Max Musterman"; across scene transition. They are anyway honored in a scene context either way.

Future thoughts: Maybe mappings hypeDocument specific as all mappings are currently on the Hype Document level and affect every Hype-Document on that page the same way.

1 Like

Uuups, the link to the zip file seems to be broken…

Fixed

1 Like

↑ look at project
1.2.0 Inspired by Symbol Override I added a callback
Switched to semantic versioning

Notes for version 1.2.0

Usage with callback:
Allowing for things like mapping data-bgcolor to the class .bgcolor and fireing a callback.

HypeDataDecorator.mapDataAttribute(
	'bgcolor', 
	function(hypeDocument, element, event){
		element.style.backgroundColor = event.value;
	}
);

Default callback is still only setting the content.


Sidenote, on the differences to @MarkHunte great Symbol Override. Hype DataDecorator obviously needs you to map every override in your project. The extension could pre declare all of them, but the goal back in 2019 was a minimal footprint and reactivity on innerHTML. So, it's aimed at developers and not an "out of the box" solution. It has an IDE preview, though. This release was only inspired by Marks release and puts in the capabilities of Symbol Override with the callback, but it is and will not try to be as "convenient" as Marks solution. If the IDE preview messes up something just close the document and reopen it. Furthermore, the live preview only currently updates in the IDE when the dataset value changes not the class. Something to keep in mind (could be improved) but doesn't affect final exports or previews. Trick: Minor edits in head HTML sometimes help to force Hype in repainting. Also, use reactivity! Assigning new values through code (button clicks etc.) also updates.


Update: Had to purge the CDN. Now, the new version is actually live :sleeping::

1 Like

↑ look at project
1.2.1 Also updating when class is modified (only in IDE)

Now also updates in the IDE if you assign or change classes. Only tiny inconvenience left is that your changes like background color remain even if you remove the class causing them (only in the IDE) but that is solved by switching from one scene to another. This could be solved if we have access to an IDE version of refreshIfNecesssary(). But that is no big issue as it is only a visual glitch.

PS: I just realized I made a little naming error back in 2019 when developing this. The method should have been called HypeDataDecorator.mapDataAttribute as it's a selector we are targeting and not a class. It could be something CSS style complex and not only a simple class definition. I might just refactor it in the next minor version, but it would be a breaking change. But given the low adoption I don't think this would be a problem.

↑ look at project
1.2.2 Minor bugfix on preview, refactored names (breaking change)

Refactored interface so here is the new usage:

// map based on class hence data-headline --> .headline with default innerHTML callback
HypeDataDecorator.mapDataAttribute('headline');

// map based on class hence data-bgcolor --> .bgcolor with custom callback
HypeDataDecorator.mapDataAttribute(
	'bgcolor', 
	function(hypeDocument, element, event){
		elm.style.backgroundColor = event.value;
	}
});

// map based on more complex selector with custom currency callback
HypeDataDecorator.mapAttributeToSelector(
	'data-price', 
	'.currency.formatted', 
	function(hypeDocument, element, event){
		var currency = new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' });
		HypeDataDecorator.setContent(element, currency.format(event.value)); 
	}
);

↑ look at project
1.2.3 Remove the possibility for recursive loops in IDE and console.log
This could be done by assigning or modifying classList in a callback but now is prevented by a 10ms debounce. Humans are not so fast entering a class name twice :wink:

If you want to use it for preset override rather than set up all possible variations you can use a data-preset to set the preset and the switch statement and do something like this:

// preset based overrides
HypeDataDecorator.mapDataAttribute(
	'preset',
	function(hypeDocument, element, event){
		switch (event.value){
			case "invalid":
				element.style.backgroundColor = 'red';
				element.style.color = 'yellow';
				element.innerHTML = 'Broken!';
				//...
				break;
	
			case "valid":
				element.style.backgroundColor = 'green';
				element.style.color = 'white';
				element.innerHTML = 'Fixed';
				//...
				break;
		}
	}
);

3 Likes

↑ look at project
1.2.4 Added hypeDocument, symbolInstance to callback and setContent
1.2.5 Renamed and refactored to Hype Data Decorator


Some thoughts after a good nights sleep for 1.2.6 (just as a reminder to myself as I have to start work today, so to be done soon, thoughts in progress):

  • Unify signature of callbacks to the known (hypeDocument, element, event) and the value would then reside at event.value. Allows mapping to Hype Functions and passing more complex data using event and maybe faking a limited hypeDocument API down the line for the IDE. done
  • Create a HypeDataDecorator.registerDecorator('NAME', (hypeDocument, element, event) {/* your code */} … option for named decorators independent of mapping. done
  • Refactor HypeDataDecorator.mapDatasetToClass to HypeDataDecorator.mapDecoratorToClass done
  • Refactor HypeDataDecorator.mapDatasetToSelector to HypeDataDecorator.mapDecoratorToSelector done
  • introduce data-decorator with optional pipe syntax allowing for order
  • Maybe even drop the Decorator in the method names as we are already aware through HypeDataDecorator.methodName that we are dealing with decorators. done
  • Add a special optional container class that (if present on stage) is used for displaying WebKit errors for easy debugging of live previews without enabling WebKitDeveloperExtras (maybe .webkitConsole or .sceneConsole).

Any chance you could make a getting started tutorial that shows some primary (real word) use cases for this one? I feel it could be quite powerful but it would be good to outline common flows so it can be specifically recommended as a solution.

3 Likes

I agree, real word examples would be great here. I should also mention some of what @MaxZieb does is absolutely out of this world amazing but complex if one is not that into coding.

1 Like

Valid requests and I will do some tutorials but for now I am pretty busy these days. In the meantime v1.2.6 is about to drop this weekend. It refactors some of the names and introduces single node observation by selector with an attached decorator callback. Sneak peak:

HypeDataDecorator.observeBySelector(
	'.progress', 
	function(hypeDocument, element, event){
		element.innerHTML = element.style.width;
	}
);

This simple example allows for counter and many more stuff. So, I guess it answers the request to a certain point


About the new signature of the decorator callbacks. They are the same as regular Hype functions now. The only difference is that hypeDocument is obviously not defined in the IDE. Hence you can use that fact in such a manner:

HypeDataDecorator.observeBySelector(
	'.progress', 
	function(hypeDocument, element, event){
		element.innerHTML = element.style.width;
		if(!hypeDocument) return;
		// everything below here will only fire in previews 
		// but not in the IDE
		hypeDocument.doSomething();
	}
);

Another new thing is that decorator callbacks can now be created without assigning them right away. This way you can name them and chain them into further decorator callbacks like this:

HypeDataDecorator.registerElementDecorator(
	'inner', 
	function(hypeDocument, element, event){
		element.innerHTML = event.value;
	}
);
		
HypeDataDecorator.registerElementDecorator(
	'upper',
	function(hypeDocument, element, event){
		event.value = event.value.toUpperCase();
		return event;
	}
);
		
HypeDataDecorator.mapDataAttribute('headline', 'upper|inner');
4 Likes

↑ look at project
1.2.6 Another refactor, comments in code, cleanup and direct observer

↑ look at project
1.2.7 Minor update: Adding the hypeDocumentElm and sceneElm to event

Added a Documentation

There is a JSDoc based documentation of the functions at https://doxdox.org/worldoptimizer/HypeDataDecorator

The testcase (rather only function testing) example file has the code in an unminifed manner in the resources. Use the minifed version on GitHub or CDN for production (much smaller).

HypeDataDecorator-testing-v1.2.7.hype.zip (76,3 KB)

2 Likes

i really like the approach and one usecase could be a template regarding barcharts for elections. simply bind some chooseable electiondata, change some attributes for an instancename, coloring, value ... that'll be an easy generic setup :slight_smile:

2 Likes