2014-01-22 9 views
3

У меня есть, вероятно, простой вопрос нокаута, но я начинаю с ним новичком. Я бросил эту страницу кода, над которой работал кто-то еще, но не закончил.KnockoutJS Binding Issue - Невозможно прочитать свойство

Когда эта страница сначала загружается, данные извлекаются и основная сетка загружается должным образом. Проблема возникает, когда я пытаюсь автоматически выбрать первую запись в результатах, чтобы список деталей заполнился ниже сетки.

Когда это произойдет, я получаю следующее сообщение.

Uncaught TypeError: Невозможно обработать связывание "текст: функция() {вернуться selected.RequestLog.Timestamp}", сообщение: Не удается прочитать свойство 'Timestamp' неопределенной

Вот это фрагменты кода, с помощью которых Я работаю. Данные возвращаются из Entity Framework.

var siteLogModel = function() { 
      var self = this; 

      self.errorList = ko.observableArray([]); 
      self.selected = ko.observable(); 

      self.updateErrorList = function (page) { 
       jQuery.ajax({ 
        type: "POST", 
        url: "/Admin/ErrorPage", 
        data: { pageNum: page }, 
        success: function (result) { 
         self.errorList(result); 
         self.selected(result[0]); 

         // Since we have success, add the click handler so we can get the details about a row by id. 
         //addRowHandlers(); 
        }, 
        error: function (result) { 
         jQuery("#status").text = result; 
        } 
       }); 
      }; 
     }; 

Это фактическое обязательство, которое пытается произойти после загрузки данных. RequestLog, похоже, не существует во время привязки, хотя он делает, кажется, нормально, если я установил точку останова в указанной выше функции на строке self.selected (result [0]).

Я думаю, что это проблема с охватом, но я не могу для жизни меня думать о том, как лучше всего это исправить. Любая помощь будет оценена по достоинству.

<div class="param"> 
     <span>Time</span> 
     <label data-bind="text: selected.RequestLog.Timestamp"></label> 
    </div> 

ОБНОВЛЕНИЕ: вот часть документа готова.

jQuery(document).ready(function() { 

      var vm = new siteLogModel(); 
      vm.updateErrorList(0); 
      ko.applyBindings(vm); 
     }); 
+0

Как выглядит ваш объект результата? Кроме того, может возникнуть проблема с тем, как вы используете наблюдаемый массив. Нет необходимости передавать конструктору пустой массив (хотя это, вероятно, не повредит), и вы можете использовать self.errorList.push (result) для добавления значений. – bigbangtheorem

ответ

11

Ваш selected наблюдаемый не имеет .RequestLog свойства в то время ко оценивают выражение привязки. Эта ошибка исходит от javascript, а не ko (хотя ko обертывает исключение в сообщении об ошибке, которое вы видите). При запуске selected.RequestLog === undefined является истинным, и вы не можете вызывать ничего из неопределенного. Это похоже на исключение с нулевой ссылкой.

Это имеет смысл, если вы звоните applyBindings перед завершением вызова ajax.

Один из способов исправить это, делая вычисляемый вместо:

<div class="param"> 
    <span>Time</span> 
    <label data-bind="text: selectedRequestLogTimestamp"></label> 
</div> 

self.selectedRequestLogTimestamp = ko.computed(function() { 
    var selected = self.selected(); 
    return selected && selected.RequestLog 
     ? selected.RequestLog.TimeStamp 
     : 'Still waiting on data...'; 
}); 

С выше, ничего не когда-либо вызывается неопределенной переменной. На вашем ярлыке будет отображаться сообщение «Still waiting on data» до тех пор, пока вызов ajax не завершится, и он будет заполняться меткой времени, как только вы вызовете self.selected(result[0]).

Другим способом решения этой проблемы является сохранение вашей привязки одинаково, но при условии, что выбранное наблюдаемое начальное значение. Вы можете оставить все ваши HTML как есть, и просто так:

self.selected = ko.observable({ 
    RequestLog: { 
     TimeStamp: 'Still waiting on data' 
    } 
}); 

... и вы будете в конечном итоге с тем же результатом.

Почему?

Каждый раз, когда вы инициализируете наблюдаемое, делая что-то вроде self.prop = ko.observable(), фактическое значение наблюдаемого - undefined.Попробуйте это:

self.prop1 = ko.observable(); 
var prop1Value = self.prop1(); 
if (typeof prop1Value === 'undefined') alert('It is undefined'); 
else alert('this alert will not pop up unless you initialize the observable'); 

Итак, подведем итог, что происходит:

  1. Вы инициализировать выбранный наблюдаемым со значением, равным не определено в вашей ViewModel.
  2. Вы вызываете ko.applyBindings против viewmodel.
  3. ko анализирует атрибуты привязки данных и пытается связать их.
  4. ko переходит на привязку text: selected.RequestLog.Timestamp.
  5. ko вызывает selected(), который возвращает undefined.
  6. ko пытается вызвать .RequestLog on undefined.
  7. ko выдает сообщение об ошибке, поскольку undefined не имеет свойства .RequestLog.

Все это происходит до того, как ваш вызов ajax будет возвращен.

Ответить на комментарий # 1

Да, вы можете позвонить applyBindings после АЯКС случае успеха. Однако, как правило, это не всегда то, что вы должно сделать. Если вы хотите, вот один пример того, как это можно сделать:

self.updateErrorList = function (page) { 
    self.updateErrorPromise = jQuery.ajax({ 
     type: "POST", 
     url: "/Admin/ErrorPage", 
     data: { pageNum: page }, 
     success: function (result) { 
      self.errorList(result); 
      self.selected(result[0]); 
     }, 
     error: function (result) { 
      jQuery("#status").text = result; 
     } 
    }); 
}; 

jQuery(document).ready(function() { 
    var vm = new siteLogModel(); 
    vm.updateErrorList(0); 
    vm.updateErrorPromise.done(function() { 
     ko.applyBindings(vm); 
    }); 
}); 

Еще один способ будет идти вперед и нетерпеливое-связывание (applyBindings перед тем заканчивает вызов Ajax), но обернуть разметку в если как таковой:

<div class="param" data-bind="if: selected"> 
    <span>Time</span> 
    <label data-bind="text: selected.RequestLog.Timestamp"></label> 
</div> 
+0

Дэн - это был замечательный ответ! Спасибо! Я добавил функцию document.ready в исходный текст, чтобы показать, как я называю applybindings. Есть ли способ просто сказать «ждать и называть applybindings после завершения вызова», а не делать это свойство по свойству для каждой привязки? Если я правильно прочитаю ваш ответ, тогда мне придется сделать фрагмент кода, как и для self.selectedRequestLogTimestamp для каждой привязки, которая у меня есть. Верный? –

+0

Это правильно, и я обновил свой ответ одним из способов сделать это. Есть десяток различных способов использования нокаута. Вы также можете посмотреть документацию для привязок 'if' и' ifnot'. Вы можете просто обернуть ваши другие привязки в одном из них, чтобы сделать ko задержкой привязки до тех пор, пока значение выбранного вами наблюдаемого значения не будет. – danludwig

+0

Еще раз спасибо за помощь. Чтобы повернуть назад к своему первоначальному предложению, я должен спросить (и показать свою неопытность нокаутом), где точно устанавливается функция self.selectedRequestLogTimestamp? Внутри var siteLogModel = function(), я предполагаю. Я пробовал это, и пока я больше не получал свою первоначальную ошибку, я никогда не видел никаких данных, и текст по умолчанию никогда не отображался. –