私は数日間 Knockout を使用してきましたが、ビュー モデルと JavaScript モデルを整理するために思いついたのは次のとおりです。
//******************************************************************************
// jQUERY START:
//******************************************************************************
$(document).ready(function()
{
var panel1 = $('#panel1>section');
var panel2 = $('#panel2>section');
$('#loader').ajaxStart(function()
{
$(this).fadeIn();
}).ajaxComplete(function()
{
$(this).fadeOut();
});
ko.applyBindings(new ViewModel(panel1));
});
//******************************************************************************
// VIEW MODEL:
//******************************************************************************
function ViewModel(_panel1)
{
var self = this;
this.vmHeader = new HeaderViewModel();
this.vmPanel1 = new Panel1ViewModel(_panel1);
}
//******************************************************************************
// Panel1ViewModel:
//******************************************************************************
function Panel1ViewModel(_element)
{
var self = this;
self.element = _element;
self.filters = ['Operations', 'Jobs', 'Shifts', 'Hours'];
self.selectedFilter = ko.observable();
self.vmOperations = new OperationsViewModel();
self.vmJobs = new JobsViewModel();
self.vmShifts = new ShiftsViewModel();
self.vmHours = new HoursViewModel();
//PUBLIC METHODS:
self.clickFilter = function(filter)
{
self.selectedFilter(filter);
switch(filter)
{
case 'Operations':
self.vmOperations.load(self.clear, self.element);
break;
case 'Jobs':
self.vmJobs.load(self.clear, self.element);
break;
case 'Shifts':
self.vmShifts.load(self.clear, self.element);
break;
case 'Hours':
self.vmHours.load(self.clear, self.element);
break;
}
}
self.clear = function()
{
//test each view model to see if it currently has items loaded and empty them.
if (self.vmOperations.operations() != null) { self.vmOperations.operations(null); }
if (self.vmJobs.jobs() != null) { self.vmJobs.jobs(null); }
if (self.vmShifts.shifts() != null) { self.vmShifts.shifts(null); }
if (self.vmHours.hours() != null) { self.vmHours.hours(null); }
};
}
//******************************************************************************
// ShiftsViewModel:
//******************************************************************************
function ShiftsViewModel()
{
var self = this;
self.shifts = ko.observableArray(null);
self.selected = ko.observable();
//PUBLIC METHODS:
self.load = function (callback, element)
{
var options = {
url: '/api/shifts',
type: 'GET',
data: {
operationID: 1
}
};
async_load(options, function (data)
{
callback();
self.shifts(
$.map(data.Items, function (item, index)
{
return new Shift(item);
})
);
element.overscroll(); <--- PROBLEM IS HERE!
});
}
self.click = function(shift)
{
self.selected(shift.id);
};
}
//******************************************************************************
// SHIFT MODEL:
//******************************************************************************
function Shift(data)
{
var self = this;
this.operation = data.Operation;
this.shopOrder = data.ShopOrder;
this.date = data.Date;
this.id = data.ID;
this.number = data.Number;
this.start = ko.observable(data.Start);
this.end = ko.observable(data.End);
this.isRunning = ko.observable(data.IsRunning);
//computed properties.
this.shiftDate = ko.computed(function()
{
var date = (this.end() == '') ? new Date() : new Date(this.end());
return date.toLocaleDateString();
}, this);
this.startTime = ko.computed(function()
{
return (new Date(this.start())).toLocaleTimeString();
}, this);
this.endTime = ko.computed(function()
{
var time = '---';
if (this.isRunning() == 'False')
{
time = (new Date(this.end())).toLocaleTimeString();
}
return time;
}, this);
}
//******************************************************************************
// GLOBAL FUNCTIONS:
//******************************************************************************
function async_load(options, callback)
{
$.ajax({
url: options.url,
async: true,
cache: false,
type: options.type,
data: options.data,
dataType: 'json',
contentType: 'application/json',
success: callback,
error: function (request, type, errorThrown)
{
error_handler(options.url, request, type, errorThrown);
}
});
}
function sync_load(options)
{
var error = false;
var data = $.ajax({
url: options.url,
async: false,
cache: false,
type: options.type,
data: options.data,
dataType: 'json',
contentType: 'application/json',
error: function (request, type, errorThrown)
{
error = true;
error_handler(options.url, request, type, errorThrown);
}
}).responseText;
if (!error)
{
data = eval('(' + data + ')');
}
return (error) ? null : data;
}
function error_handler(name, request, type, errorThrown)
{
switch (request.status)
{
case 404:
alert('The ' + name + ' could not be found.');
break;
case 500:
alert('There was an internal server error loading ' + name + '.');
//redirect the user to a page with further instructions.
break;
default:
alert('An error occurred: (' + request.status + ' ' + request.statusText + ').');
}
}
HTML は次のとおりです。
<section id="panel1" data-bind="with: vmPanel1">
<nav data-bind="foreach: filters">
<a href="#" data-bind="text: $data, css: { selected: $data == $root.vmPanel1.selectedFilter() }, click: $root.vmPanel1.clickFilter"></a>
</nav>
<section>
<section id="panel1Data">
<!-- ko foreach: vmOperations.operations -->
<article class="operation" data-bind="css: { selected: $data.operationID == $root.vmPanel1.vmOperations.selected() }, click: $root.vmPanel1.vmOperations.click">
<div class="name" data-bind="text: name"></div>
<div class="number" data-bind="text: number"></div>
<div class="sequence" data-bind="text: sequence"></div>
</article>
<!-- /ko -->
<!-- ko foreach: vmJobs.jobs -->
<article data-bind="pageBreak: operation, label: 'operation', level: 1"></article>
<article class="job" data-bind="css: { selected: $data.jobID == $root.vmPanel1.vmJobs.selected() }, click: $root.vmPanel1.vmJobs.click">
<div class="start date">
<label data-bind="text: startMonth"></label>
<div data-bind="text: startDay"></div>
<span data-bind="text: startTime"></span>
</div>
<div class="end date">
<label data-bind="text: endMonth"></label>
<div data-bind="text: endDay"></div>
<span data-bind="text: endTime"></span>
</div>
<div class="shoporder" data-bind="text: shopOrder"></div>
<div class="toolconfig" data-bind="toolConfig: $data"></div>
<div class="lot" data-bind="text: lot"></div>
</article>
<!-- /ko -->
<!-- ko foreach: vmShifts.shifts -->
<article data-bind="pageBreak: operation, label: 'operation', level: 1"></article>
<article data-bind="pageBreak: shopOrder, label: 'job', level: 2"></article>
<article data-bind="pageBreak: shiftDate(), level: 3"></article>
<article class="shift" data-bind="css: { selected: $data.id == $root.vmPanel1.vmShifts.selected() }, click: $root.vmPanel1.vmShifts.click">
<div class="shift" data-bind="text: number"></div>
<div class="start time" data-bind="text: startTime()"></div>
<div class="end time" data-bind="text: endTime()"></div>
</article>
<!-- /ko -->
選択したフィルターに応じて、適切なデータ (操作、ジョブ、シフト、または時間) のリストがパネル 1 に読み込まれ、jQuery Overscroll プラグインを使用して IPad のスクロール効果が作成されます。
問題は、Overscroll プラグインが呼び出されている行の ShiftsViewModel にあります。デバッグ中に、コンテナー要素に幅/高さが指定されていないため、プラグインが何もしていないことに気付きました。プログラムを実行すると、コンテナーは正しいデータで更新されるため、データが Knockout によって DOM コンテナーに書き込まれる前にオーバースクロールの呼び出しが実行されているようです。
オーバースクロールの呼び出しは、データを受信した後に実行される ajax コールバック内にあるので、問題ないと思いました。Knockout は非同期的に DOM を更新していますか? ここからどこに行けばいいのかわからない...何か提案はありますか?