public TwitterViewModel(List <TweetGroup> lists, string selectedList) { var self = this; this.SavedLists = Knockout.ObservableArray(lists); this.EditingList = new TweetGroup( name: Knockout.Observable(selectedList), userNames: Knockout.ObservableArray <string>() ); this.UserNameToAdd = Knockout.Observable(""); this.CurrentTweets = Knockout.ObservableArray(new object[0]); this.FindSavedList = name => KnockoutUtils.ArrayFirst(self.SavedLists.Value, grp => grp.Name.Value == name, self); this.AddUser = () => { if (self.UserNameToAdd.Value != null && self.UserNameToAddIsValid.Value) { self.EditingList.UserNames.Push(self.UserNameToAdd.Value); self.UserNameToAdd.Value = ""; } }; this.RemoveUser = userName => self.EditingList.UserNames.Remove(userName); this.SaveChanges = new Action(OnSaveChanges); this.DeleteList = () => { var nameToDelete = self.EditingList.Name.Value; var savedListsExceptOneToDelete = self.SavedLists.Value.Filter(grp => grp.Name.Value != nameToDelete); self.EditingList.Name.Value = savedListsExceptOneToDelete.Length == 0 ? null : savedListsExceptOneToDelete[0].Name.Value; self.SavedLists.Value = savedListsExceptOneToDelete; }; Knockout.Computed(() => { // Observe viewModel.editingList.name(), so when it changes // (i.e., user selects a different list) we know to copy the // saved list into the editing list var savedList = self.FindSavedList(self.EditingList.Name.Value); if (savedList != null) { var userNamesCopy = savedList.UserNames.Slice(0); self.EditingList.UserNames.Value = userNamesCopy; } else { self.EditingList.UserNames.Value = new string[0]; } }); this.HasUnsavedChanges = Knockout.Computed(() => { if (self.EditingList.Name.Value == null) { return(self.EditingList.UserNames.Value.Length > 0); } var savedData = self.FindSavedList(self.EditingList.Name.Value).UserNames; var editingData = self.EditingList.UserNames.Value; return(savedData.Value.Join("|") != editingData.Join("|")); }); this.UserNameToAddIsValid = Knockout.Computed(() => { var pattern = @"^\s*[a-zA-Z0-9_]{1,15}\s*$"; return(self.UserNameToAdd.Value == "" || self.UserNameToAdd.Value.Match(new Regex(pattern, "g")) != null); }); this.CanAddUserName = Knockout.Computed(() => { return(self.UserNameToAddIsValid.Value && self.UserNameToAdd.Value != ""); }); // The active user tweets are (asynchronously) computed from editingList.userNames Knockout.Computed(() => { TwitterApi.GetTweetsForUsers <object[]>( self.EditingList.UserNames.Value, result => { self.CurrentTweets.Value = result; }); }); }