Knockout with jQuery UI
Posted: April 28, 2013 Filed under: JavaScript | Tags: jQuery UI, knockout.js 3 CommentsSample code to demonstrate trivial edits using knockout.js and jQuery UI dialog.
The main things to note are that all item properties are observable, and changes to an observable’s properties can be undone. Depends on Popup.js.
Updated to include localStorage. And to add another property to the model with no muss no fuss.
<head runat="server">
<title></title>
<link href="Content/themes/base/jquery.ui.all.css" rel="stylesheet" />
<script src="Scripts/jquery-2.0.0.min.js"></script>
<script src="Scripts/jquery-ui-1.10.2.min.js"></script>
<script src="Scripts/knockout-2.2.1.js"></script>
<script src="Scripts/knockout.mapping-latest.js"></script>
<script src="Scripts/Popup.js"></script>
<script type="text/javascript">
$(function () {
ko.applyBindings(new ViewModel);
});
function Storage(ctor) {
// Class to handle local storage for observable array with observable item properties.
function load() {
var items = [] // Something to return.
var data = JSON.parse(localStorage.getItem(ctor.name));
if (data) {
data.forEach(function (item) {
var obj = new ctor(); // Spin up one of what was specified.
$.extend(obj, item); // Pound in the properties.
items.push(ko.mapping.fromJS(obj)); // Make all properties live and push it.
});
}
return items;
}
function save(value) {
var items = [] // Something to save.
value.forEach(function (item) {
items.push(ko.mapping.toJS(item)); // Strip off the KO decor.
});
localStorage.setItem(ctor.name, JSON.stringify(items)); // Stringify it and save it.
}
// Return this interface.
return {
load : load,
save : save
};
}
function Statefull() {
// Class to handle saving and restoring observable properties.
var self = this; // Hang onto this to prevent ambiguity later on.
var state = []; // Empty bag to hold properties.
function backup() {
// Clear the array and add items to the bag.
state = [];
for (var prop in this) {
// Add only "descendant" properties that are observable.
if (!self.hasOwnProperty(prop) && ko.isObservable(this[prop]))
state.push({ name: prop, value: this[prop]() });
}
};
function restore() {
// Replace values with saved ones.
state.forEach(function (prop) {
this[prop.name](prop.value);
}, this);
};
// Return this interface.
return {
backup: backup,
restore: restore,
};
}
function Contact() {
// Plain old property bag.
this.firstName = "";
this.lastName = "";
this.email = "";
};
function ViewModel() {
// Class to handle adding and editing items.
// Uses jQuery UI dialog and KO bindings.
Contact.prototype = new Statefull; // Make Contact Statefull
var storage = new Storage(Contact); // localStorage thingy.
function newItem() {
// Create scratch object where all properties are observable.
return (ko.mapping.fromJS(new Contact));
};
function clearItem() {
// Clear out the item being tracked.
thing(newItem());
};
function add() {
// Opens the dialog to allow a new item to be added.
// Saves the item when OK button is pressed.
clearItem(); // Set the tracked item to be a new item.
popup.show(
{
ok: function () {
things.push(thing()); // Add the new item.
storage.save(things()); // Save all things.
clearItem(); // Clear the tracked item.
}
}, { title: Popup.Strings.New }
);
};
function edit(item) {
// Opens the dialog to allow an existing item to be modified.
item.backup(); // Save the items state.
thing(item); // Set tracked item to the exisitng item.
popup.show({
ok: function () {
storage.save(things()); // Save all things.
},
cancel: function () {
item.restore(); // on Cancel restore the items state.
}
}, { title: Popup.Strings.Edit }
);
};
// Set up working variables.
var popup = new Popup("#edit"); // Tie in the dialog.
var thing = ko.observable(newItem()); // The tracked item.
var things = ko.observableArray(storage.load()); // All items.
// Return this interface.
return {
thing: thing,
things: things,
add: add,
edit: edit
};
};
</script>
</head>
<body>
<form id="form1" runat="server">
<div>
<div data-bind="with: thing" id="edit">
First: <input type="text" data-bind="value: firstName" />
Last: <input type="text" data-bind="value: lastName" />
Email: <input type="text" data-bind="value: email" />
</div>
<button data-bind="click: add">Add</button>
<div data-bind="foreach: things">
<span data-bind="text: firstName"></span>
<span data-bind="text: lastName"></span>
<span data-bind="text: email"></span>
<a href="#" data-bind="click: $parent.edit">edit</a>
<br />
</div>
</div>
</form>
</body>