0

我已经使用 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 容器之前调用 overscroll 正在运行。

由于 overscroll 调用在接收到数据后运行的 ajax 回调中,我认为它可以。Knockout 是否异步更新 DOM?我不知道从这里去哪里......有什么建议吗?

4

1 回答 1

0

我解决了这个问题:

在 jQuery 就绪函数中,我在加载任何数据之前缓存 DOM 元素,然后使用容器元素内没有任何内容的缓存元素。

所以,我改变了这个:

var panel1 = $('#panel1>section');

至:

var panel1 = '#panel1>section';

然后在 ShiftsViewModel 中,我更改了这一行:

element.overscroll();

至:

$(element).overscroll();

它现在似乎正在工作......

于 2012-08-10T15:12:10.810 回答