diff --git a/js/examples/mixin/Makefile b/js/examples/mixin/Makefile
new file mode 100644
index 0000000..6b0a114
--- /dev/null
+++ b/js/examples/mixin/Makefile
@@ -0,0 +1,7 @@
+all: index.expanded.js
+
+%.expanded.js: %.js
+ ../../bin/syndicatec $< > $@ || (rm -f $@; false)
+
+clean:
+ rm -f *.expanded.js
diff --git a/js/examples/mixin/index.html b/js/examples/mixin/index.html
new file mode 100644
index 0000000..c7c8b0a
--- /dev/null
+++ b/js/examples/mixin/index.html
@@ -0,0 +1,38 @@
+
+
+
+ Syndicate: Mixin
+
+
+
+
+
+
+
+
Mixin example
+
Source code: index.js
+
Drag shapes. Click for rectangles. Shift-click to delete a rectangle.
+
+
+
+
+
diff --git a/js/examples/mixin/index.js b/js/examples/mixin/index.js
new file mode 100644
index 0000000..38133c7
--- /dev/null
+++ b/js/examples/mixin/index.js
@@ -0,0 +1,80 @@
+ground dataspace G {
+ message type shiftClicked(fragmentId);
+
+ Syndicate.UI.spawnUIDriver();
+
+ spawn {
+ var uiRoot = new Syndicate.UI.Anchor();
+
+ assert uiRoot.html('#place', '');
+
+ spawn {
+ var ui = new Syndicate.UI.Anchor();
+ assert ui.html('#svgroot',
+ '',
+ -1);
+ on message ui.event('.', 'click', $e) {
+ var svg = document.getElementById('svgroot');
+ var pt = svg.createSVGPoint();
+ pt.x = e.clientX;
+ pt.y = e.clientY;
+ pt = pt.matrixTransform(svg.getScreenCTM().inverse());
+ spawnRectangle(pt.x, pt.y);
+ }
+ }
+
+ spawn {
+ field this.x = 50;
+ field this.y = 50;
+ var ui = new Syndicate.UI.Anchor();
+ assert ui.html('#svgroot',
+ '',
+ 0);
+ draggableMixin(this, ui);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+
+ function spawnRectangle(x0, y0) {
+ var length = 90;
+ spawn {
+ field this.x = x0 - length / 2;
+ field this.y = y0 - length / 2;
+ var ui = new Syndicate.UI.Anchor();
+ assert ui.html('#svgroot',
+ '',
+ 0);
+ draggableMixin(this, ui);
+ on message ui.event('.', 'mousedown', $e) {
+ if (e.shiftKey) { :: shiftClicked(ui.fragmentId); }
+ }
+ stop on message shiftClicked(ui.fragmentId);
+ }
+ }
+
+ function draggableMixin(obj, ui) {
+ idle();
+
+ function idle() {
+ react {
+ stop on message ui.event('.', 'mousedown', $e) {
+ dragging(e.clientX - obj.x, e.clientY - obj.y);
+ }
+ }
+ }
+
+ function dragging(dx, dy) {
+ react {
+ on message uiRoot.event('.', 'mousemove', $e) {
+ obj.x = e.clientX - dx;
+ obj.y = e.clientY - dy;
+ }
+ stop on message uiRoot.event('.', 'mouseup', _) {
+ idle();
+ }
+ }
+ }
+ }
+ }
+}