So I am using GSAP TweenLite and Draggable, as well as KnockoutJS to create nested resizeable observable elements (A div
with an image
inside). My goal is to resize and reposition those elements by number inputs and dragging and resizing, like a layout generator for Java/Android does. My code currently allows me to monitor changes caused by drag and resize actions, however it does not react to updates on the Knockout bindings or observables.
Javascript:
function object(id) {
var self = this;
self.id = ko.observable(id);
self.parent = ko.observable();
self.childNodes = ko.observableArray([]);
self.x = ko.observable(100);
self.y = ko.observable(50);
self.w = ko.observable(100);
self.h = ko.observable(100);
}
function EditorViewModel() {
this.self = this;
self.Objects = ko.observableArray([new object("1")]);
self.Objects().push(new object("2"));
self.Objects()[0].childNodes.push(new object("1.1"));
self.Objects()[0].childNodes()[0].childNodes.push(new object("1.1.1"));
self.chosenObject = ko.observable();
ko.bindingHandlers.dragResize = {
init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
var val = ko.unwrap(valueAccessor());
var drag = element;
if (element.parentNode.parentNode == $(document.body)) {
parent = element.parentNode;
} else {
var parent = element.parentNode.parentNode;
}
var handle = $("<div class='resize-handle'></div>").appendTo(drag);
TweenLite.set(handle, {bottom: 0, right: 0});
Draggable.create(drag,
{
type: 'top left', bounds: parent,
onDragStart: function ()
{
if (typeof Draggable.get(parent) !== 'undefined') {
untilRange(element, false); //sets all parent Dragagbles to disabled to prevent quirks when dragging child
}
},
onDragEnd: function ()
{
if (typeof Draggable.get(parent) !== 'undefined') {
untilRange(element, true); //same as above, but re-enables them after drag
}
},
onDrag:function (e){
val.x(this.x);
val.y(this.y);
}
});
Draggable.create(handle, {
type: "top,left",
onPress: function (e) {
e.stopPropagation(); // cancel drag
},
onDrag: function (e) {
val.w(this.x);
val.h(this.y);
TweenLite.set(drag,{width:val.w(),height:val.h()});
},
});
},
update: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
var val = ko.unwrap(valueAccessor());
var drag = Draggable.get(element);
element.x=val.x();
drag.update();
}
}
HTML:
<div id="range" data-bind="template:{name: 'elementTemplate',foreach:Objects()}">
</div>
<!--Templates-->
<script type="text/html" id="elementTemplate">
<!--<pre style="position:relative;width: 100px" data-bind="text: ko.toJSON($data, null, 2)"></pre>-->
<div class="draggable" data-bind="attr:{id:id}, dragResize:{x:x,y:y,h:h,w:w}">
<div data-bind="template:{name: 'elementTemplate',foreach:childNodes()}">
</div>
<img src="res/Download.jpg" class="mapClass" alt="If you see this we haven't done well"/>
</div>
</script>
If someone has a different suggestion on how to reach my goal, please let me know, the only part I would like to keep is KnockoutJS, because my teacher recommended it.
Your code looks not too bad. I was not able to run it unmodified - I missed for example the untilRange function. So I stripped it down to the bare essentials to test how to have the observable update the position and vice versa. You will have to reapply to your original code.
The problem you maybe facing is one you run across alot of times, when you try to do a new binding in knockout, that updates observables in two directions: - You drag your object which modifies your observable - You modify your observable, which modifies your object
There are situations where you need to take care, that the events, that modify your observable do not trigger the update of your binding. But this is speculation, as I was not able to run the unmodified code.
Still from making a smaller example from your code, two way update seems to work fine, even without taking cycles into account. I found only that inside the update() function of the binding I would not call the Draggable.update() at all. Apart from that the two way binding should work.
See this fiddle: https://jsfiddle.net/domoran/1dk1hc4v/5/
Javascript:
function object(id) {
var self = this;
self.id = ko.observable(id);
self.x = ko.observable(0);
self.y = ko.observable(0);
}
ko.bindingHandlers.dragResize = {
init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
var val = ko.unwrap(valueAccessor());
var lastSelected = val.last;
Draggable.create(element, {
type: "x,y",
onDrag: function(e) {
lastSelected(ko.dataFor(element));
val.x(this.x);
val.y(this.y);
}
});
},
update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
var updateCoords = function() {
// since this is invoked inside a computed, it will be invoked,
// whenever the observables x and y change
TweenLite.set(element,{x:val.x(),y:val.y()});
};
var val = ko.unwrap(valueAccessor());
// make the updateCoors function call whenever one of its observables
// changes
updateCoords();
}
};
function EditorViewModel() {
var self = this;
self.Objects = ko.observableArray([new object("1"), new object("2")]);
self.lastSelected = ko.observable();
}
ko.applyBindings(new EditorViewModel());
HTML:
<body>
<div id="range" data-bind="template:{name: 'elementTemplate',foreach:Objects }">
</div>
<!--Templates-->
<script type="text/html" id="elementTemplate">
<div data-bind="attr:{id:id}, dragResize:{x:x,y:y,last:$root.lastSelected}">
<img width=80 src="https://upload.wikimedia.org/wikipedia/commons/4/4c/World_borders_lamb_azi.png" class="mapClass" alt="If you see this we haven't done well" /><span data-bind="text: x() + '/' + y()"></span>
</div>
</script>
<div data-bind="with:lastSelected">
<label>x</label><input type="text" data-bind="value: x" />
<label>y</label><input type="text" data-bind="value: y" />
</div>
</body>