Knockout observableArray包含新项,但UI不会更新

时间:2014-02-26 10:26:18

标签: twitter-bootstrap knockout.js asp.net-mvc-5

在使用bootstrap 3模式时,是否有一个最佳实践(此时我将采取一个不太好的做法)为一个淘汰赛observableArray添加一个新项目?目前我正在使用深度嵌套的数据结构,并且我已经能够将数据添加到某一点。我有一个bootstrap模式,绑定到我的视图模型上的“selectedItem”。该项本身是observableArray的成员。这在我想要编辑现有项目时有效,但在我尝试添加新项目时失败。我正在遵循的过程是新建要添加的对象(所有可观察属性),设置viewModel的SelectedItem属性=新对象,以便模态中的with binding工作,然后我将新项目推送到observableArray和显示模态。在模态中,我将值添加到一些属性中,包括在新项上填充三个observableArrays(此时也失败,可能是同一个问题)并关闭模态。奇怪的是,添加新项目只会在视觉上失败。当我将viewmodel保存回服务器并重新加载页面时,我刚刚添加的项目就在那里并正确呈现。我假设我正在打破observableArray,我正在添加对象,但我最终试图弄清楚如何。我试图把它变成一个小提琴,但由于复杂性,我最终必须将其简化到可行或我不再说明行为的程度。有关Ryan Niemeyer建议here之外的故障排除的任何建议都是受欢迎的!当我使用pre标签时,我可以看到添加到observableArray的新数据,但UI没有响应。什么人要做?

编辑:页面的相关部分发布在下面。

查看:

<div id="SchedulePanel" class="panel panel-default" > @*style="@showSchedule">*@

    <div class="panel-heading">
        <h3>Plan Schedule</h3>
        <a class="btn btn-primary btn-sm" data-bind="visible: scheduleDirty" style="margin-left: 10px; margin-bottom: 7px;">Save All</a>
        <div>
            Add
            <input id="numWeeks" type="text" class="form-control" placeholder="number" value="1" />
            <a id="btnAddWeek" class="btn btn-primary" data-bind="click: addWeek">Week(s)</a>
        </div>
    </div>
    <div class="panel-body">
        <div id="ScheduledInstructions">
            Click the Add Week button to get started adding workouts to your schedule
        </div>
        <hr />
        <div id="weeks" data-bind="foreach: Schedule">
            <div style="margin-left: auto; margin-right: auto;">
                <h4 style="display: inline-block;" >Week <span data-bind="text: Name"></span></h4>
                <span class="glyphicon glyphicon-floppy-disk" style="color: red;" data-bind="visible: IsDirty"></span>&nbsp;
                <span class="glyphicon glyphicon-share" data-bind="visible: !IsDirty()" title="Click to copy this week and add to the end of the schedule"></span>
                <div data-bind="foreach: Days ">
                    <div class="dayBuilder">
                        Day <span data-bind="text: DayNumber"></span>
@*<pre data-bind="text: ko.toJSON($data, null, 2)"></pre>*@
                        <div data-bind="foreach: Workouts ">
                            <div class="workout" title="Edit Workout" style="cursor: pointer;">
                                <span data-bind="text: Type"></span>
                                <span class="glyphicon glyphicon-remove pull-right removeWorkout" title="Remove Workout"></span>
                            </div>
                        </div>
                        <div class="newWorkout addWorkout">
                            <span class="glyphicon glyphicon-plus" title="Add Workout"></span>
                        </div>
                    </div>   
                </div>
            </div>
        </div>
    </div>
</div>
<div class="modal fade" id="newWorkout" tabindex="-1" role="dialog" aria-labelledby="newWorkoutLabel" aria-hidden="true" data-bind="with: $root.SelectedWorkout">
    <div class="modal-dialog modal-wide">
        <div class="modal-content">
            <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
                <h3 class="modal-title" id="newWorkoutLabel"><span data-bind="text: Type"></span> Workout</h3>
            </div>
            <div class="modal-body">
                <form >
                    <div class="form-group">
                        <label class="control-label">Select workout type</label>                        
                        @Html.EnumDropDownListFor(m => m.WorkoutEnum, new { @class="form-control", data_bind="value: Type"})    
                    </div>

                    <div class="well well-sm">
                        <h3 class="workoutHeader">Warmup</h3><span class="glyphicon glyphicon-plus-sign" data-section="warmup" title="Add Interval"></span>
                        <div id="newWarmupInterval" style="display: none;">
                            <label class="radio-inline">
                                <input type="radio" name="WUDistance" id="WUDistanceBasedT" class="wuDistance" value="true" checked> Distance Based
                            </label>
                            <label class="radio-inline">
                                <input type="radio" name="WUDistance" id="WUDistanceBasedF" class="wuDistance"  value="false"> Time Based
                            </label>
                            <div id="timeBasedWU" style="display: none;" class="tp-intervalInputContainer form-inline">
                                <div style="margin-bottom:10px;">Enter a new time based interval to be added to the Warmup portion of this workout, in the style of '15 minutes @(Model.IsRPE ? "at RPE5'" : "easy'") </div>
                                <input type="text" id="timeValueWU" class="form-control" placeholder="Enter Time" style="width: 60px;"/>
                                @Html.EnumDropDownListFor(m => m.TimeUnits, new { @class="form-control", id="timeUnitWU"})
                                @(Model.IsRPE ? Html.EnumDropDownListFor( m => m.RPEUnitsEnum, new {@class = "form-control", id="rpeUnitTimeWU"} ) : Html.EnumDropDownListFor( m => m.HeartRateZoneEnum, new {@class = "form-control", id="hrUnitTimeWU"} ))
                                <div id="btnSaveWU" class="btn btn-success btn-sm">Save</div>
                                <div id="btnCancelWU" class="btn btn-danger btn-sm">Cancel</div>
                            </div>
                            <div id="distanceBasedWU" class="tp-intervalInputContainer form-inline">
                                <div style="margin-bottom:10px;">Enter a new distance based interval to be added to the Warmup portion of this workout, in the style of '10 miles @(Model.IsRPE ? "at RPE5'" : "easy'") </div>
                                <input type="text" id="distValueWU" class="form-control" placeholder="Enter Distance" style="width: 60px;"/>
                                @Html.EnumDropDownListFor(m => m.DistanceUnitsEnum, new { @class="form-control", id="distUnitWU"})
                                @(Model.IsRPE ? Html.EnumDropDownListFor( m => m.RPEUnitsEnum, new {@class = "form-control", id="rpeUnitDistWU"} ) : Html.EnumDropDownListFor( m => m.HeartRateZoneEnum, new {@class = "form-control", id="hrUnitDistWU"} ))
                                <div id="btnSaveWU" class="btn btn-success btn-sm btnSaveWU">Save</div>
                                <div id="btnCancelWU" class="btn btn-danger btn-sm">Cancel</div>
                            </div>
                            <hr />
                        </div>

                        <ul id="WarmupIntervals" data-bind="template: { name: 'WorkoutTemplate', foreach: WarmUp }">
                            <li>
                                @if ( Model.IsTimeBased ) {
                                    <span data-bind="Text: TimeValue"></span>
                                    <span data-bind="Text: TimeUnit"></span>
                                    <span data-bind="Text: RPEValue, visible: $root.IsRPE"></span>
                                    <span data-bind="Text: HRValue, visible: !$root.IsRPE()"></span>
                                }
                                else {
                                    <span data-bind="Text: DistanceValue"></span>
                                    <span data-bind="Text: DistanceUnit"></span>
                                    <span data-bind="Text: RPEValue, visible: $root.IsRPE"></span>
                                    <span data-bind="Text: HRValue, visible: !$root.IsRPE()"></span>
                                }
                            </li>
                        </ul>
                    </div>

                    <div class="well well-sm">
                        <h3 class="workoutHeader">Main</h3><span class="glyphicon glyphicon-plus-sign"  data-section="main" title="Add Interval"></span>
                        <div id="newMainInterval" style="display: none;">
                            Need to get some content in here.
                        </div>
                        <hr />
                        <ul id="MainIntervals" data-bind="template: { name: 'WorkoutTemplate', foreach: Main }">
                        </ul>
                    </div>

                    <div class="well well-sm">
                        <h3 class="workoutHeader">Cool Down</h3><span class="glyphicon glyphicon-plus-sign"  data-section="cooldown" title="Add Interval"></span>
                        <div id="newCooldownInterval" style="display: none;">
                            Need to get some content in here.
                        </div>
                        <hr />
                        <ul id="CoolDownIntervals" data-bind="template: { name: 'WorkoutTemplate', foreach: CoolDown }">
                        </ul>
                    </div>
                </form>
            </div>
            <div class="modal-footer">
                @*<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>*@
                <button type="button" class="btn btn-primary saveWorkout">Save</button>
            </div>
        </div>
    </div>
</div>

JS:

var trainingPlan;
var schedule;
var workoutNumber = 0;

$(document).ready(function() {

    // the plan object is a training plan with a few properties that describe the plan
    // along with a list of weeks that contain the workouts.  The weeks are broken down into days,
    // each day has a list of workouts, each workout has a list of intervals for its warm up, main and cool down
    // properties. 

    var planID = { id: $('#ID').val() };

    var Week = function() {
        var self = this;
        self.ID = '';
        self.IsDirty = ko.observable(false);
        self.PlanID = ko.observable();
        self.StartDate = ko.observable();
        self.EndDate = ko.observable();
        self.Name = ko.observable();
        self.Days = ko.observableArray([]); // a list of day objects
    };
    var Day = function() {
        var self = this;
        self.ID = '';
        self.DayNumber = ko.observable();
        self.TodaysDate = ko.observable();
        self.Name = ko.observable();
        self.Workouts = ko.observableArray([]); // a list of workout objects
    };
    var Workout = function() {
        var self = this;
        self.ID = '';
        self.Type = ko.observable(); //need to figure out what/how to handle enums
        self.WarmUp = ko.observableArray([]); // a list of intervals
        self.Main = ko.observableArray([]); // a list of intervals
        self.CoolDown = ko.observableArray([]); // a list of intervals
        self.Status = ko.observable();
        self.Completed = ko.observable();
    };
    var Interval = function() {
        var self = this;
        self.IsTimeBased = ko.computed(function() {
            return !self.IsDistanceBased;
        });
        self.IsDistanceBased = ko.observable();
        self.TimeValue = ko.observable();
        self.TimeUnit = ko.observable();
        self.RPEUnits = ko.observable();
        self.HeartRateZone = ko.observable();
        self.Description = ko.observable();
        self.DistanceValue = ko.observable();
        self.DistanceUnit = ko.observable();
    };

    $.getJSON('/PlanBuilder/GetPlanJson', planID, function(model) {

        // map model into a knockout viewModel
        trainingPlan = ko.mapping.fromJSON(model.Message);

        // setup regular and computed observables
        trainingPlan.SelectedWorkout = ko.observable();
        trainingPlan.SelectedInterval = ko.observable();
        trainingPlan.SelectedDay = ko.observable();
        trainingPlan.Weeks = ko.computed(function() {
            return trainingPlan.Schedule().length;
        });
        trainingPlan.TotalWeeks = ko.computed(function() {
            trainingPlan.Weeks = trainingPlan.Schedule.length || 0;
            return trainingPlan.Schedule.length || 0;
        }, this);
        trainingPlan.TotalDays = ko.computed(function() {
            return trainingPlan.Schedule.length * 7 || 0;
        }, this);

        // setup $root methods
        trainingPlan.addWeek = function() {
            var numberOfWeeks = $('#numWeeks').val();

            if (numberOfWeeks < 1)
                numberOfWeeks = 1;

            for (var w = 1; w <= numberOfWeeks; w++) {

                var wk = new Week();
                wk.IsDirty(true);
                var wkNum = this.Schedule().length + 1;
                var firstDay = wkNum > 1 ? (wkNum - 1) * 7 + 1 : 1; // day number of first day of new week

                wk.Name = wkNum;
                wk.PlanID = $('#ID').val();

                for (var i = firstDay; i < firstDay + 7; i++) {
                    var dy = new Day();
                    dy.DayNumber = i;
                    wk.Days.push(dy);
                }

                //var summ = new day();
                //summ.DayNumber = "Summary";
                //wk.Days.push(summ);

                this.Schedule.push(wk);
            }

            // update # of weeks in training plan
            trainingPlan.Weeks = this.Schedule().length + 1;
        };
        trainingPlan.addWorkout = function() {
            var wrk = new Workout();
            trainingPlan.SelectedWorkout(wrk);
            $('#newWorkout').modal('show');
        };
        trainingPlan.saveWorkout = function (wrk) {
            trainingPlan.SelectedDay().Workouts().push(wrk);
            $('#newWorkout').modal('hide');
        };
        trainingPlan.addInterval = function (workoutSection) {
            var intr = new Interval();
            trainingPlan.SelectedInterval(intr);

            var container = $(this).parent();
            intr.IsDistanceBased($('#WUDistanceBasedT').is(':checked'));

            if (intr.IsDistanceBased()) {
                intr.DistanceUnit(container.find('#distUnitWU').val());
                intr.DistanceValue(container.find('#distValueWU').val());
                if (!trainingPlan.IsRPE()) {
                    intr.HeartRateZone(container.find('#hrUnitDistWU').val());
                } else {
                    intr.RPEUnits(container.find('#rpeUnitDistWU').val());
                }
            } else {
                intr.TimeUnit(container.find('#timeUnitWU').val());
                intr.TimeValue(container.find('#timeValueWU').val());
                if (!trainingPlan.IsRPE()) {
                    intr.HeartRateZone(container.find('#hrUnitTimeWU').val());
                } else {
                    intr.RPEUnits(container.find('#rpeUnitTimeWU').val());
                }
            }

            trainingPlan.SelectedWorkout().WarmUp().push(intr);
        };
        trainingPlan.copyWeek = function(index) {
            var weeks = trainingPlan.Schedule().slice(index, index + 1);
            var week = weeks[0];

            var newWeek = new Week();
            var weekNum = parseInt(trainingPlan.Schedule().length) + 1;

            newWeek.EndDate = ko.observable(ko.utils.unwrapObservable(week.EndDate));
            newWeek.IsDirty = ko.observable(true);
            newWeek.Name = ko.observable(weekNum);
            newWeek.PlanID = ko.observable(ko.utils.unwrapObservable(week.PlanID));
            newWeek.StartDate = ko.observable(ko.utils.unwrapObservable(week.StartDate));

            var dayNumber = 1;

            week.Days().forEach(function(day, dayIndex) {
                var newDay = new Day();
                var daysBase = trainingPlan.Schedule().length * 7;
                var currentDay = daysBase + dayIndex + 1;

                newDay.Name = ko.observable(ko.utils.unwrapObservable(day.Name));
                newDay.DayNumber = ko.observable(currentDay);
                newDay.Name = ko.observable("Day " + currentDay);

                day.Workouts().forEach(function(workout) {
                    var newWorkout = new Workout();
                    newWorkout.Completed = false;

                    workout.WarmUp().forEach(function(interval) {
                        var newInterval = new Interval();
                        newInterval.Description = ko.observable(ko.utils.unwrapObservable(interval.Description));
                    });

                    newWorkout.Type = ko.observable(ko.utils.unwrapObservable(workout.Type));

                    newDay.Workouts.push(newWorkout);
                });

                newWeek.Days.push(newDay);
            });

            trainingPlan.Schedule.push(newWeek);
        };
        trainingPlan.scheduleDirty = ko.computed(function() {
            var dirty = false;

            for (var i = 0; i < trainingPlan.Schedule().length; i++) {
                if (trainingPlan.Schedule()[i].IsDirty())
                    dirty = true;
            }
            ;
            return dirty;
        }, this);

        // lets get it on
        ko.applyBindings(trainingPlan);
    });

    $('#weeks').on('click', '.removeWorkout', function() {
        var context = ko.contextFor(this);
        var workouts = context.$parent.Workouts;
        workouts.remove(context.$data);
        context.$parents[1].IsDirty(true);
    });

    $('#weeks').on('click', '.addWorkout', function() {
        var context = ko.contextFor(this);
        context.$parent.IsDirty(true);
        var wrk = new Workout();
        wrk.Type("Flying");
        //trainingPlan.SelectedWorkout(wrk);
        context.$data.Workouts().push(wrk);
        //$('#newWorkout').modal('show');
    });

    $('#newWorkout').on('click', '.saveWorkout', function () {
        $('#newWorkout').modal('hide');
    });

    $('#weeks').on('click', '.workout', function() {
        var context = ko.contextFor(this);
        context.$root.SelectedWorkout(context.$data);
        $('#newWorkout').modal('show');
    });


    // Week functions
    $('#weeks').on('click', 'span.glyphicon-floppy-disk', function() {
        var context = ko.contextFor(this);
        var weekData = ko.mapping.toJSON(context.$data);

        var $btn = $(this);

        // save week
        $.ajax({
            url: "/PlanBuilder/SaveWeek",
            type: "POST",
            data: weekData,
            contentType: 'application/json',
            error: function(data) {
                console.log(data.responseText);
                $btn.find('span.glyphicon-floppy-disk').addClass('btn-danger');
            },
            success: function(data) {
                if (data.IsError) {
                    $btn.find('span.glyphicon-floppy-disk').addClass('btn-danger');
                    $btn.text('Error');
                } else {
                    $btn.removeClass('glyphicon-floppy-disk').addClass('glyphicon-floppy-saved');
                    $btn.css('color', 'green');
                    // add newly generated ID to knockout week object
                    context.$data.ID = data.Message;
                    context.$data.IsDirty(false);
                    $btn.css('color', 'red');

                    // after 3 seconds fadeOut save button and reset the glyphicon classes
                    setTimeout(function() {
                        $btn.removeClass('glyphicon-floppy-saved').addClass('glyphicon-floppy-disk');
                    }, 3000);
                }
            }
        });

    });
    $('#weeks').on('click', 'span.glyphicon-share', function() {
        var context = ko.contextFor(this);
        var index = context.$index();
        context.$root.copyWeek(index);
    });

     //Workout functions
    $('#newWorkout').on('click', 'span.glyphicon-plus-sign', function () {

        var section = $(this).attr('data-section');

        if (section === 'warmup') {
           $('#newWarmupInterval').slideToggle();
        }

        if (section === 'main') {
            $('#newMainInterval').slideToggle();
        }

        if (section === 'cooldown') {
            $('#newCoolDownInterval').slideToggle();
        }


    });

    $('#newWorkout').on('click', '.wuDistance', function () {
        if ($(this).attr('id') == 'WUDistanceBasedT') {
            $('#timeBasedWU').fadeOut(function () {
                $('#distanceBasedWU').fadeIn();
            });
        } else {
            $('#distanceBasedWU').fadeOut(function () {
                $('#timeBasedWU').fadeIn();
            });
        }
    });

    $('#newWorkout').on('click', '.btnSaveWU', function () {
        var context = ko.contextFor(this);



    });

    $('#btnSaveM').click(function () { });
    $('#btnSaveCD').click(function () { });



    $('#btnSavePlan').click(function() {

        // if form is valid, let's save this shiz
        if ($('#NewPlanForm').valid()) {

            var mappingOptions = {
                'ignore': ["addWorkout", "removeWorkout", "Schedule"]
            };

            var datum = ko.mapping.toJSON(trainingPlan, mappingOptions);

            var $btn = $(this);

            // save plan
            $.ajax({
                url: "/PlanBuilder/SavePlan",
                type: "POST",
                data: datum,
                contentType: 'application/json',
                error: function(data) {
                    console.log(data.responseText);
                    $btn.firstChild().addClass('btn-danger');
                },
                success: function(data) {
                    if (data.IsError) {
                        $btn.firstChild().addClass('btn-danger');
                        $btn.text('Error');
                    } else {
                        $btn.addClass('btn-success');
                        $btn.text('Success');
                        $('#ID').val(data.Message);

                        $('#SchedulePanel').show();

                        setTimeout(function() {
                            $btn.removeClass('btn-success').removeClass('btn-danger').addClass('btn-primary');
                        }, 4000);
                    }
                }
            });
        }
    });
});

1 个答案:

答案 0 :(得分:1)

要解析的代码很多,但我注意到你正在对observableArray的值进行大量的推动,而不是observableArray本身。修复您的JavaScript可能是您的问题。

    trainingPlan.saveWorkout = function (wrk) {
            //trainingPlan.SelectedDay().Workouts().push(wrk);
            trainingPlan.SelectedDay().Workouts.push(wrk);
            $('#newWorkout').modal('hide');