Album edit interface
http://knockoutjs.com/examples/contactsEditor.html
HTML
<script src="http://cloud.github.com/downloads/SteveSanderson/knockout/knockout-2.1.0.js"></script>
<script src="https://raw.github.com/SteveSanderson/knockout.mapping/master/build/output/knockout.mapping-latest.debug.js"></script>
<div class="figure">
<div class="g_top_normal_wrap js_album_edit_ui" >
<form data-bind="foreach: albums, submit: save" class="form_album_edit_wrap" action="#" >
<div class="form_album_edit_row form_album_edit_row_title">
<label>
<div class="form_album_edit_label_wrap">
<span class="form_album_edit_label">Title</span>
</div>
<div class="form_album_edit_input_wrap">
<input data-bind='value: title' class="form_album_edit_input js_validate_required" name="title" type="text" />
</div>
</label>
</div>
<div class="form_album_edit_row form_album_edit_row_genre">
<div class="form_album_edit_label_wrap">
<span class="form_album_edit_label">Genres</span>
</div>
<div class="form_album_edit_input_wrap form_album_edit_input_wrap_tags clearfix">
<div data-bind="foreach: genres">
<span class="btn_wrap btn_wrap_deletable">
<a data-bind="text: title" class="btn_text js_auto_helper" data-type="artists" href="#">
</a>
<a data-bind='click: $root.removeGenre' href="#" class="btn_del">+</a>
</span>
</div>
<span data-bind='click: $root.addGenre' class="btn_wrap" >
<a data-id="2" class="btn_text btn_text_add js_auto_helper" data-type="artists" href="#">
+
</a>
</span>
...
CSS
.g_top_normal_wrap {
background: #F7F7F5;
display: block;
width: 908px;
border-radius: 0 8px 8px 8px;
padding: 30px;
}
.form_album_edit_wrap {
width: 50%;
/*padding: 25px 0;*/
}
.form_album_edit_row {
padding-left: 115px;
min-height: 26px;
}
.form_album_edit_row + .form_album_edit_row {
margin-top: 10px;
}
.form_album_edit_label_wrap {
width: 115px;
margin-left: -115px;
float: left;
text-align: right;
}
.form_album_edit_label,
.form_album_edit_input {
font-size: 12px;
line-height: 2;
color: #333;
font-family: Helvetica, sans-serif;
}
.form_album_edit_row.invalid .form_album_edit_label {
color: red
}
.form_album_edit_row.invalid .form_album_edit_input {
border-color: red;
}
.form_album_edit_label {
display: inline-block;
height: 26px;
cursor: pointer;
padding-right: 10px;
}
.form_album_edit_input {
height: 24px;
line-height: 24px;
background: #f7f7f7;
position: relative;
bottom: -1px;
width: 100%;
padding: 0 10px;
-webkit-box-shadow: 0 2px 5px #e5e5e5 inset;
box-shadow: 0 2px 5px #e5e5e5 inset;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.form_album_edit_input_small {
width: 150px;
}
.form_album_edit_textarea {
min-height: 114px;
resize: vertical;
}
.form_album_edit_input,
.form_album_edit_input:active,
.form_album_edit_input:focus {
border: 1px solid #e3e3e3;
}
.form_album_edit_art_input {
display: none;
}
.form_album_edit_art_wrap {
padding: 9px;
width: 135px;
background: #ecf3f5;
cursor: pointer;
text-align: right;
-webkit-border-radius: 8px;
border-radius: 8px;
-webkit-box-shadow: 0 8px 13px #e2e9eb inset;
box-shadow: 0 8px 13px #e2e9eb inset;
}
.form_album_edit_art_wrap,
.form_album_edit_art_img_wrap {
width: 135px;
}
.form_album_edit_art_img {
width:...
JavaScript
//js
var initialData = [{
title: 'Danny',
genres: [
{
id: 21,
title: 'Noise'},
{
id: 22,
title: 'EBM'}],
tracks: [
{
title: 'Pony',
genres: [
{
id: 21,
title: 'Noise'},
{
id: 22,
title: 'EBM'}]},
{
title: 'Hungry',
genres: [
{
id: 21,
title: 'Noise'},
{
id: 22,
title: 'EBM'}]}
]
}];
function rand() {
return Math.floor((Math.random() * 1000) + 1);
}
var AlbumsModel = function(album) {
var self = this;
self.albums = ko.mapping.fromJS(album);
// ALBUM FUNCS
self.addGenre = function(album) {
var id = rand(),
item = {
id: id,
title: ' genre #' + id
};
album.genres.push(item);
if (album.tracks()) {
$.each(album.tracks(), function(index, track) {
track.genres.push(item);
});
}
};
self.removeGenre = function(genre) {
$.each(self.albums(), function() {
this.genres.remove(genre);
if (this.tracks()) {
$.each(this.tracks(), function(index, track) {
var toRemove = ko.utils.arrayFirst(track.genres(), function(item) {
return ko.utils.unwrapObservable(genre.id)== ko.utils.unwrapObservable(item.id);
});
track.genres.remove(toRemove);
});
} else {
console.log('HAVE NO TRACKS, MOTHERFUCKER!');
}
});
};
self.addTrack = function(album) {
var id = rand();
album.tracks.push({
id: id,
title: ' track #' + id
});
};
self.removeTrack = function(track) {
$.each(self.albums(), function() {
this.tracks.remove(track)
...