在使用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>
<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">×</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);
}
}
});
}
});
});
答案 0 :(得分:1)
要解析的代码很多,但我注意到你正在对observableArray的值进行大量的推动,而不是observableArray本身。修复您的JavaScript可能是您的问题。
trainingPlan.saveWorkout = function (wrk) {
//trainingPlan.SelectedDay().Workouts().push(wrk);
trainingPlan.SelectedDay().Workouts.push(wrk);
$('#newWorkout').modal('hide');