Here's the JSFiddle.
The array of objects looks like below:
var data = [{
"ConferenceUsers": [{
"Id": 3006,
"ConferenceId": 8,
"Name": null,
"Email": "mail@lala.com",
"UserName": null,
"PhoneNumber": "234234234234"
}],
"Id": 8,
"Subject": "YYYhaaaaa",
"StartTime": "2016-05-29T18:30:00",
"EndTime": "2016-05-29T19:30:00",
"OrganizerEmail": "elpas@live.com",
"OrganizerName": "dasdasd",
}, {
"ConferenceUsers": [{
"Id": 3013,
"ConferenceId": 12,
"Name": null,
"Email": "dsfdfsdfdsf@dfdfdf.com",
"UserName": null,
"PhoneNumber": null
}],
"Id": 12,
"Subject": "dsfsdfdsfsdf",
"StartTime": "2016-05-31T22:00:00",
"EndTime": "2016-05-31T23:00:00",
"OrganizerEmail": "d@adssad.com",
"OrganizerName": "dsfdsfsdf"
}];
I need to be able to add and edit new ConferenceUsers
.
Adding new users works but I can't edit them.
ConferenceUser ViewModel:
var ConferenceUser = function (user) {
this.ConferenceId = ko.observable(user.ConferenceId);
this.Email = ko.observable(user.Email);
this.Id = ko.observable(user.Id);
this.Name = ko.observable(user.Name);
this.PhoneNumber = ko.observable(user.PhoneNumber);
};
ConferenceList ViewModel and mappings:
var createConferenceUser = function (user) {
return new ConferenceUser(user);
};
var ConferenceList = function(conferencesJSON) {
var self = this;
var users = [];
for (i = 0; i < conferencesJSON.length; i++) {
users.push(conferencesJSON[i].ConferenceUsers);
}
this.conferences = ko.observableArray(conferencesJSON.map(createConference));
this.conferenceUsers = ko.observableArray(users.map(createConferenceUser));
this.addConference = function(conferenceJSON) {
self.conferences.push(createConference(conferenceJSON));
};
};
ko.applyBindings(new ConferenceList(data));
Q: How can I update the existing / newly added ConferenceUsers
and the DOM also, like in the jsFiddle?
Like I commented: if you were to actually use the ConferenceUser
view models with the observable properties, you would probable able to figure out how to both create new, and edit existing users. I do see some other points of improvement though, so I think it's still worth to formulate an answer to your question:
- The edit/save/cancel logic is cluttering your
Conference
view model and can easily be separated into its own little widget
- The way you're switching between the Add and Update button UI doesn't work well with knockout
I've written an answer that's more about the architecture because I feel that the answer from your question follows naturally from a well defined separation of concerns. And because I enjoy refactoring :)
Here's my suggestion after having moved stuff around. You can decide for yourself how much of it you'll use, but at least the answer to your question can be found in the code!
ConferenceList
Only serves to manage a list of conferences. Could easily be extended with removeConference
or getConferencesFromServer
etc.
var ConferenceList = function(conferencesJSON) {
this.conferences = ko.observableArray(conferencesJSON.map(Conference.create));
this.addConference = function(conferenceJSON) {
self.conferences.push(Conference.create(conferenceJSON));
};
};
Conference
Holds a list of ConferenceUser
instances and a widget to edit and create new users. This is where your question is answered:
var Conference = function(conferenceJSON) {
var self = this;
this.Id = conferenceJSON.Id;
// Note that we're mapping the plain objects from the json to
// ConferenceUser instances!
this.users = ko.observableArray(
conferenceJSON.ConferenceUsers.map(ConferenceUser.create));
this.userEditor = new UserEditor(this.users, this.Id);
this.onUserClick = function(user, event) {
self.userEditor.edit(user);
};
};
ConferenceUser
This is where we make sure the UI is updated after edits: note that the Email
and PhoneNumber
properties are observable. I didn't create observables for all properties to indicate not all properties are meant to be changed in the UI.
var ConferenceUser = function(user) {
this.Email = ko.observable(user.Email);
this.PhoneNumber = ko.observable(user.PhoneNumber);
this.ConferenceId = user.ConferenceId;
this.Id = user.Id;
this.Name = user.Name;
};
I've created a static create method for two reasons:
- It keeps track of an
id
inside a closure to make sure it's unique
- It's easy to use inside an
Array.prototype.map
method.
The code:
ConferenceUser.create = (function() {
var id = 0;
return function(userOptions) {
// Add an ID if not present in options
return new ConferenceUser(Object.assign(userOptions, {
Id: userOptions.Id || id++
}));
};
}());
UserEditor
This is the biggest improvement (I believe) to your code: an editor widget that helps you create, edit and save new users. The methods it exposes are much easier to understand and write because they're not inside your Conference
viewmodel.
var UserEditor = function(users, conferenceId) {
var self = this;
// Holds the user that's being edited, is null when
// creating a new user
this.editing = ko.observable(null);
this.phoneInput = ko.observable("");
this.emailInput = ko.observable("");
this.clear = function() {
self.phoneInput("");
self.emailInput("");
};
this.add = function() {
var newUserOptions = {
Email: self.emailInput(),
PhoneNumber: self.phoneInput(),
ConferenceId: conferenceId
};
users.push(ConferenceUser.create(newUserOptions));
self.clear();
};
this.edit = function(user) {
self.editing(user);
self.phoneInput(user.PhoneNumber());
self.emailInput(user.Email());
};
this.reset = function() {
self.editing(null);
self.clear();
};
this.save = function() {
var currentUser = self.editing();
currentUser.Email(self.emailInput());
currentUser.PhoneNumber(self.phoneInput());
self.reset();
};
};
Now, after all this code, you'll see your HTML is very straight forward. Here's your edit widget:
<div data-bind="with: userEditor">
<input type="tel" placeholder="phone number"
data-bind="value: phoneInput" />
<input type="email " placeholder="email"
data-bind="value: emailInput" />
<!-- ko ifnot: editing -->
<button data-bind="click: add">Add</button>
<!-- /ko -->
<!-- ko if: editing -->
<button data-bind="click: save">Save</button>
<button data-bind="click: reset">Cancel</button>
<!-- /ko -->
</div>
Kind of a long story to get to the fix, but if you've made it this far: here's an updated fiddle! https://jsfiddle.net/e2ox4doj/