问题
I am new to Javascript and trying to use Gridster with Knockout. I have a database with items, and I use knockout foreach to bind them to a UL. The UL is styled with the Gridster library. Everything works great unless I try to add additional elements to the UL via the ObservableArray in the viewmodel.
Can anyone help me understand the scope and order of operations here? It feels like the Gridster library isn't doing its styling to the new widgets.
This jsfiddle shows a working demo of the issue. Notice when you double click on a widget, it creates a new one but doesn't place it in the grid. Instead, it just kind of hangs out behind.
Here is the HTML
<div class="gridster">
<ul data-bind="foreach: myData">
<li data-bind="attr:{
'data-row':datarow,
'data-col':datacol,
'data-sizex':datasizex,
'data-sizey':datasizey
},text:text, doubleClick: $parent.AddOne"></li>
</ul>
</div>
Here is the Javascript
//This is some widget data to start the process
var gridData = [ {text:'Widget #1', datarow:1, datacol:1, datasizex:1, datasizey:1},
{text:'Widget #2', datarow:2, datacol:1, datasizex:1, datasizey:1},
{text:'Widget #3', datarow:1, datacol:2, datasizex:1, datasizey:1},
{text:'Widget #4', datarow:2, datacol:2, datasizex:1, datasizey:1}];
// The viewmodel contains an observable array of widget data to be
// displayed on the gridster
var viewmodel = function () {
var self = this;
self.myData = ko.observableArray(gridData);
//AddOne adds an element to the observable array
// (called at runtime from doubleClick events)
self.AddOne = function () {
var self = this;
myViewModel.myData.push({
text: 'Widget Added After!',
datarow: 1,
datacol: 1,
datasizex: 1,
datasizey: 1
});
};
};
var myViewModel = new viewmodel();
ko.applyBindings(myViewModel);
$(".gridster ul").gridster({
widget_margins: [5, 5],
widget_base_dimensions: [140, 140]
});
回答1:
Here is a full example in JSfiddle. Here, I have highlighted just the delete function
self.deleteOne = function (item) { console.log(item); var widget = $("#" + item.id); console.log(widget); var column = widget.attr("data-col"); if (column) { console.log('Removing '); // if this is commented out then the widgets won't re-arrange self.gridster.remove_widget(widget, function(){ self.myData.remove(item); console.log('Tiles: '+self.myData().length); }); } };
The work of removing the element from the observable array is done inside the remove_widget callback. See gridster's documentation. Consequently, the removeGridster hook executed before a widget is removed, does no longer need to do the actual remove_widget call.
回答2:
Here's a working solution that I think is more in line with the MVVM pattern:
http://jsfiddle.net/Be4cf/4/
//This is some widget data to start the process
var gridData = [
{id: "1", text:'Widget #1', datarow:1, datacol:1, datasizex:1, datasizey:1},
{id: "2", text:'Widget #2', datarow:1, datacol:2, datasizex:2, datasizey:1},
{id: "3", text:'Widget #3', datarow:1, datacol:4, datasizex:1, datasizey:1},
{id: "4", text:'Widget #4', datarow:2, datacol:1, datasizex:1, datasizey:2}];
// The viewmodel contains an observable array of widget data to be
// displayed on the gridster
var viewmodel = function () {
var self = this;
self.myData = ko.observableArray(gridData);
self.nextId = 5;
self.gridster = undefined;
// AddOne adds an element to the observable array.
// Notice how I'm not adding the element to gridster by hand here. This means
// that whatever the source of the add is (click, callback, web sockets event),
// the element will be added to gridster.
self.addOne = function () {
myViewModel.myData.push({
text: 'Widget Added After!',
datarow: 1,
datacol: 1,
datasizex: 1,
datasizey: 1,
id: self.nextId++
});
};
// Called after the render of the initial list.
// Gridster will add the existing widgets to its internal model.
self.initializeGridster = function() {
self.gridster = $(".gridster ul").gridster({
widget_margins: [5, 5],
widget_base_dimensions: [140, 140]
}).data('gridster');
};
// Called after the render of the new element.
self.addGridster = function(data, object) {
// This bypasses the add if gridster has not been initialized.
if (self.gridster) {
var $item = $(data[0].parentNode);
// The first afterRender event is fired 2 times. It appears to be a bug in knockout.
// I'm pretty new to knockout myself, so it might be a feature too! :)
// This skips the second call from the initial fired event.
if (!$item.hasClass("gs-w"))
{
// This removes the binding from the new node, since gridster will re-add the element.
ko.cleanNode(data[0].parentNode);
// Adds the widget to gridster.
self.gridster.add_widget($item);
// We must update the model with the position determined by gridster
object.datarow = parseInt($item.attr("data-row"));
object.datacol = parseInt($item.attr("data-col"));
}
}
};
};
var myViewModel = new viewmodel();
ko.applyBindings(myViewModel);
I still need to think about the remove and move events (a move in gridster should update the item's x and y values in the viewmodel). I started using knockout yesterday, so any help would be appreciated.
I couldn't find a cdn for the latest version of gridster. JSFiddle points to a temporary website I've added in Azure, I'll leave it up for a few days, but feel free to update it with your own link.
/------------------------------ UPDATE ----------------------------------/
I've updated my code to support deletions and moving widgets (http://jsfiddle.net/Be4cf/11/) but there's a small caveat: there's an open issue (https://github.com/knockout/knockout/issues/1130) that knockout cleans out jquery data before calling the beforeRemove event. This causes gridster to crash since the data needed to move the other items around is kept in a data element. A workaround could be to keep a copy of the data and to re-add it to the element later, but I've chosen the lazy way and commented the offending line in knockout.
回答3:
Add class="gs_w" to ur li in gridster it should work
回答4:
You should do something like below. addNewGridElement
is called - with the rendered DOM element which is important in Gridster's case as gridster.add_widget
accepts a DOM element as its first argument - once you've added something to the Knockout observable. After this, it's just a matter of then adding domNode
to Gridster.
view.html:
<div class="gridster">
<ul data-bind="foreach: { myData, afterAdd: $root.addNewGridElement }">
<li data-bind="attr:{
'data-row':datarow,
'data-col':datacol,
'data-sizex':datasizex,
'data-sizey':datasizey
},text:text, doubleClick: $parent.AddOne"></li>
</ul>
</div>
view.js:
self.addNewGridElement = function (domNode, index, newTile) {
// Filter non li items - this will remove comments etc. dom nodes.
var liItem = $(domNode).filter('li');
if ( liItem.length > 0 ) {
// Add new Widget to Gridster
self.gridster.add_widget(domNode, newTile.x, newTile.y, newTile.row, newTile.col);
}
};
来源:https://stackoverflow.com/questions/19549122/widget-binding-with-gridster-and-knockout