jQuery UI und Knockout – “You cannot apply bindings multiple times to the same element”

Um jQuery UI zusammen mit Knockout zu verwenden habe ich ein eigenes Knockout Binding erstellt. In der Init Methode wird das jQuery Widget erstellt.
Beim folgenden Beispiel handelt es sich um TypeScript Code.

ko.bindingHandlers["dialogBox"] = {
	init(element: any, valueAccessor: any) {
		$(element).dialog({
			autoOpen: false,
			modal: true,
                        
			resizable: false,
			// More Code
		});
		return false;
	},
	update(element: any, valueAccessor: any) {
		// Update Code
	}
}

In diesem Beispiel verwende ich den jQuery UI Dialog. Der Content des Dialogs befindet sich in einem Div. Natürlich wurde der Inhalt dieses Divs über ein Knockout-Template erzeugt (“template”-Binding). Mit dieser Methode lässt sich der Code welcher das DOM kennt, auf die Bindings beschränken.

Bei einigen Widgets, darunter auch der Dialog, tritt jedoch der folgende Fehler auf:
“You cannot apply bindings multiple times to the same element.”

Um die Ursache zu verstehen, muss man wissen, dass jQuery UI beim initialisieren bzw. erstellen des Widgets das Div, welches den Content des Dialogs darstellt, ausschneidet, etwas verpackt und ganz unten in der Seite einfügt.

Vorher:

<html>
	<head>
		<!-- jQuery und jQuery UI includen -->
		<script>
			$(function() {
				$( "#dialog" ).dialog();
			});
		</script>
	</head>
	<body>
		<div id="insideMeIsTheDialog">
			<div id="dialog" title="Basic dialog">
				<p>This is the default dialog which is useful for displaying information. The dialog window can be moved, resized and closed
					with the 'x' icon.</p>
			</div>
		</div>
	</body>
</html>

Nachher:

<html>
	<head>
		<script>
			$(function() {
				$( "#dialog" ).dialog();
			});
		</script>
	</head>
	<body style="cursor: auto;">
		<div id="insideMeIsTheDialog"></div>
		<div aria-labelledby="ui-id-1" aria-describedby="dialog" role="dialog" tabindex="-1" style="position: absolute; height: 143.6px; width: 299.6px; top: 203px; left: 660px; display: block; right: auto; bottom: auto;" class="ui-dialog ui-widget ui-widget-content ui-corner-all ui-front ui-draggable ui-resizable">
			<div class="ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix ui-draggable-handle">
				<span class="ui-dialog-title" id="ui-id-1">Basic dialog</span>
				<button title="Close" role="button" class="ui-button ui-widget ui-state-default ui-corner-all ui-button-icon-only ui-dialog-titlebar-close"
				type="button">
					<span class="ui-button-icon-primary ui-icon ui-icon-closethick"></span>
					<span class="ui-button-text">Close</span>
				</button>
			</div>
			<div style="width: auto; min-height: 105px; max-height: none; height: auto;" class="ui-dialog-content ui-widget-content" id="dialog">
				<p>This is the default dialog which is useful for displaying information. The dialog window can be moved, resized and closed with the 'x' icon.</p>
			</div>
			<div style="z-index: 90;" class="ui-resizable-handle ui-resizable-n"></div>
			<div style="z-index: 90;" class="ui-resizable-handle ui-resizable-e"></div>
			<div style="z-index: 90;" class="ui-resizable-handle ui-resizable-s"></div>
			<div style="z-index: 90;" class="ui-resizable-handle ui-resizable-w"></div>
			<div style="z-index: 90;" class="ui-resizable-handle ui-resizable-se ui-icon ui-icon-gripsmall-diagonal-se"></div>
			<div style="z-index: 90;" class="ui-resizable-handle ui-resizable-sw"></div>
			<div style="z-index: 90;" class="ui-resizable-handle ui-resizable-ne"></div>
			<div style="z-index: 90;" class="ui-resizable-handle ui-resizable-nw"></div>
		</div>
	</body>

</html>

Ist nun der Knockout Binding Scope der Body oder allgemeiner gesamt, der gemeinsame Parent des Dialog Divs und des “ausgeschnittenen” Dialog Divs, so probiert Knockout die Bindings mehrmals auszuführen.

Die Lösung:
1. Den Scope für das Knockout Binding einschränken
2. jQuery UI das Widget ausserhalb dieses Scopes platzieren lassen

<html>
	<head>
		<script>
			$(function() {
				$( "#dialog" ).dialog({
					appendTo: "#container"
				});
			});
		</script>
	</head>
	<body>
		<div id="container">
			<div id="bindingRoot">
				<div id="dialog" title="Basic dialog">
					<p>This is the default dialog which is useful for displaying information. The dialog window can be moved, resized and closed
						with the 'x' icon.</p>
				</div>
			</div>
		</div>
	</body>
</html>

Leave a Reply

Your email address will not be published. Required fields are marked *