2012-04-19 34 views
18

Tôi có một observableArray đơn giản chứa rất nhiều mô hình người dùng. Trong đánh dấu, có một mẫu với vòng lặp foreach vòng lặp người dùng và xuất chúng trong một bảng đơn giản. Tôi bổ sung phong cách bảng với một thanh cuộn tùy chỉnh và một số javascript khác. Vì vậy, bây giờ tôi phải biết khi vòng lặp foreach kết thúc và tất cả các mô hình được thêm vào DOM.Knockout afterRender, nhưng chỉ một lần

Sự cố với cuộc gọi lại sau cuộc gọi là nó được gọi mỗi khi có gì đó được thêm vào, nhưng tôi cần kiểu gọi lại chỉ kích hoạt một lần.

+0

Tại sao bạn cần phải biết khi nào họ được trả lại vào DOM? Nếu bạn đang áp dụng kiểu, CSS sẽ chỉ khớp với quy tắc và áp dụng khi các mục được thêm vào. – arb

Trả lời

21

đặt cược tốt nhất của bạn này là sử dụng một tùy chỉnh ràng buộc. Bạn có thể đặt liên kết tùy chỉnh của mình sau foreach trong danh sách các liên kết trong số data-bind hoặc bạn có thể thực thi mã của mình trong một setTimeout để cho phép foreach tạo nội dung trước khi mã của bạn được thực thi.

Đây là một mẫu cho thấy chạy mã một lần duy nhất và chạy mã mỗi khi cập nhật observableArray của bạn: http://jsfiddle.net/rniemeyer/Ampng/

HTML:

<table data-bind="foreach: items, updateTableOnce: true"> 
    <tr> 
     <td data-bind="text: id"></td> 
     <td data-bind="text: name"></td> 
    </tr> 
</table> 

<hr/> 

<table data-bind="foreach: items, updateTableEachTimeItChanges: true"> 
    <tr> 
     <td data-bind="text: id"></td> 
     <td data-bind="text: name"></td> 
    </tr> 
</table> 

<button data-bind="click: addItem">Add Item</button> 

JS:

var getRandomColor = function() { 
    return 'rgb(' + (Math.floor(Math.random() * 256)) + ',' + (Math.floor(Math.random() * 256)) + ',' + (Math.floor(Math.random() * 256)) + ')'; 
}; 

ko.bindingHandlers.updateTableOnce = { 
    init: function(element) { 
     $(element).css("color", getRandomColor());    
    }  
}; 

//this binding currently takes advantage of the fact that all bindings in a data-bind will be triggered together, so it can use the "foreach" dependencies 
ko.bindingHandlers.updateTableEachTimeItChanges = { 
    update: function(element) {  
     $(element).css("color", getRandomColor()); 
    }  
}; 


var viewModel = { 
    items: ko.observableArray([ 
     { id: 1, name: "one" }, 
     { id: 1, name: "one" }, 
     { id: 1, name: "one" } 
    ]), 
    addItem: function() { 
     this.items.push({ id: 0, name: "new" }); 
    } 
}; 

ko.applyBindings(viewModel); 
+0

tuyệt vời! đây là chính xác những gì tôi đang tìm kiếm;) –

+0

Có cách nào để làm việc này trong một cuộc gọi 'template: {foreach: ...}'? –

+1

@JulesCopeland bạn có thể đi với một cái gì đó như thế này: http://jsfiddle.net/rniemeyer/Ampng/37/ nơi ràng buộc lấy một sự phụ thuộc vào mảng. –

0

Chỉ cần ra khỏi đỉnh đầu của tôi bạn có thể một trong hai:

  • Thay vì hooking vào sự kiện afterRender, chỉ cần gọi hàm của bạn sau khi bạn có push/popped một mục trên mảng.
  • Hoặc có thể bọc ObservableArray trong một quan sát mà bản thân nó có các mục con bên dưới với sự kiện afterRender của riêng nó. Vòng lặp foreach sẽ cần phải tham khảo các mẹ quan sát được như vậy:

Ví dụ:

<div> 
    <div data-bind="with: parentItem(), template: { afterRender: myRenderFunc }" > 
     <div data-bind="foreach: observableArrayItems"> 
      ... 
     </div> 
    </div> 
    </div> 

Không thử nghiệm vì vậy chỉ cần đoán ...

+1

Nhận lỗi: Thông báo: 'Nhiều ràng buộc (với và mẫu) đang cố gắng kiểm soát các liên kết hậu duệ của cùng một phần tử. Bạn không thể sử dụng các liên kết này cùng nhau trên cùng một phần tử.' – Airn5475

7

Một cách nhanh chóng và cách đơn giản là, trong trình xử lý afterRender của bạn, hãy so sánh mục hiện tại với mục cuối cùng trong danh sách của bạn. Nếu nó khớp, thì đây là lần cuối cùng afterRender được chạy.

+0

Điều này là thông minh. Tôi không nghĩ về điều này! – kamranicus

+0

Điều này làm việc hoàn hảo cho tôi, cảm ơn cho đề xuất! Điều này cắt giảm chức năng cuộn của tôi được gọi một lần cho mỗi tin nhắn (có thể là một nghìn lần) một lần. Giữ nó đơn giản ngu ngốc, hoàn hảo! –

0

Để tìm hiểu khi mẫu foreach đã hoàn thành hiển thị, bạn có thể tạo liên kết "giả" và chuyển chỉ mục mục được hiển thị hiện tại cho trình xử lý của bạn và kiểm tra xem nó có phù hợp với độ dài mảng hay không.

HTML:

<ul data-bind="foreach: list"> 
    <li> 
    \\ <!-- Your foreach template here --> 
    <div data-bind="if: $root.listLoaded($index())"></div> 
    </li> 
</ul> 

ViewModel - Handler:

this.listLoaded = function(index){ 
    if(index === list().length - 1) 
     console.log("this is the last item"); 
} 

Giữ thêm cờ nếu bạn có ý định thêm các mục vào danh sách.

8

Tôi đã nghĩ ra một trò gian lận thanh lịch.Ngay sau khi khối template/foreach của bạn, thêm mã này:

<!--ko foreach: { data: ['1'], afterRender: YourAfterRenderFunction } --> 
<!--/ko--> 
+0

Làm việc một cách hoàn hảo và cứu tôi rất nhiều rắc rối, cảm ơn! – Smegger

+2

+1 Điều này thực sự gọn gàng! nhưng có lẽ tốt nhất để sử dụng nó một cách tiết kiệm ... – easuter

0

Tôi không chắc chắn nếu câu trả lời chấp nhận sẽ làm việc trong knock-out-3.x (như dữ liệu ràng buộc không còn được chạy theo thứ tự bạn khai báo).

Đây là một tùy chọn khác, nó sẽ chỉ kích hoạt chính xác một lần.

ko.bindingHandlers.mybinding { 
     init: function(element, valueAccessor, allBindings, viewModel, bindingContext) { 
      var foreach = allBindings().foreach, 
       subscription = foreach.subscribe(function (newValue) { 
       // do something here 
       subscription.dispose(); // dispose it if you want this to happen exactly once and never again. 
      }); 
     } 
} 
0

Làm thế nào để sử dụng trình gỡ lỗi?

var afterRender = _.debounce(function(){  

    // code that should only fire 50ms after all the calls to it have stopped 

}, 50, { leading: false, trailing: true}) 
2

Câu trả lời của Jaffa có lỗi nên tôi quyết định tạo câu trả lời mới thay vì nhận xét. Không thể sử dụng cùng với và mẫu cùng một lúc. Vì vậy, chỉ di chuyển mô hình của bạn vào mẫu của data thẻ

Html

<div data-bind="template: {data: myModel, afterRender: onAfterRenderFunc }" > 
    <div data-bind="foreach: observableArrayItems"> 
     ... 
    </div> 
</div> 

Javascript

var myModel = {  
    observableArrayItems : ko.observableArray(), 

    onAfterRenderFunc: function(){ 
    console.log('onAfterRenderFunc') 
    } 

} 
ko.applyBinding(myModel); 
Các vấn đề liên quan