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;
                });
            });
        }