2014-07-25 13 views
8

Tôi đang sử dụng knockout.js tuyệt vời để ràng buộc tính ViewModel vào DOM. Bây giờ, một phần GUI của tôi được hiển thị trên phần tử canvas. Tôi sử dụng fabric.js để vẽ các phần tử trên canvas. Từ những yếu tố này không nằm trong dom (họ là hàm bao quanh phương pháp vẽ canvas), tôi không thể sử dụng loại trực tiếp để ràng buộc đối với họ. Tuy nhiên, tôi cần phải theo dõi vị trí/màu sắc/nhãn của họ trong ViewModel.Bind to một tổ chức phi DOM ui yếu tố

Tôi nghĩ rằng tôi có thể tạo ra một ràng buộc đối với mỗi loại nguyên thủy vải tùy chỉnh và sau đó ràng buộc họ giống như một nút dom. Tuy nhiên, một ràng buộc tùy chỉnh mong đợi một phần tử DOM làm tham số đầu tiên của nó. Thứ hai, tôi không thể (dễ dàng) thêm một ràng buộc lập trình. Tôi cần để có thể làm điều này vì tôi không thể viết các ràng buộc trong HTML.

tôi vẫn đang suy nghĩ về điều này, nhưng tôi là kinda khó khăn cho thời điểm này. Bất kỳ ý tưởng?

+0

bạn có phiền khi chia sẻ một câu đố hoặc bản trình diễn khác với nguồn có liên kết dữ liệu hai chiều không? Tôi có thể thấy một cách ràng buộc trong fiddle trong câu trả lời dưới đây. Ngoài ra, bạn đã phải đối mặt với bất kỳ vấn đề khác với vải và loại trực tiếp? –

+1

Chúng tôi không tiếp tục với vải. Chúng tôi bắt đầu sử dụng jointjs.com, dựa trên svg (thay vì canvas), cho phép chúng tôi nhúng html vào bên trong biểu đồ và ràng buộc với các biểu đồ đó. Tôi không đề xuất thư viện chính xác này nhưng mỗi thứ tương tự sẽ là một lựa chọn của tôi. Đó là một chút khó khăn để nhanh chóng tạo ra một fiddle sau tất cả thời gian này. – bertvh

+0

cảm ơn bạn đã chia sẻ. Tôi đang tạo một PoC với JointJS và knockoutjs. Bạn có thể chia sẻ bất kỳ fiddle cho điều này? Tôi đang xem xét nhúng html bên trong các phần tử jointjs. –

Trả lời

4

Các ràng buộc tùy chỉnh được thực hiện bên trong các quan sát được tính toán, để các phụ thuộc được theo dõi và chức năng update có thể được kích hoạt lại.

Có vẻ như đối với chức năng của mình, bạn có thể muốn xem xét sử dụng quan sát tính toán theo dõi đối tượng của bạn, truy cập phụ thuộc, và thực hiện bất kỳ cập nhật/cuộc gọi api cần thiết.

Trước đây tôi chưa từng sử dụng vải, nhưng đây sẽ là nơi bạn xác định mô hình xem để thể hiện hình chữ nhật và tạo tính liên tục cập nhật đối tượng vải khi bất kỳ giá trị nào thay đổi.

// create a wrapper around native canvas element (with id="c") 
var canvas = new fabric.Canvas('c'); 

var RectangleViewModel = function (canvas) { 
    this.left = ko.observable(100); 
    this.top = ko.observable(100); 
    this.fill = ko.observable("red"); 
    this.width = ko.observable(100); 
    this.height = ko.observable(100); 

    this.rect = new fabric.Rect(this.getParams()); 
    canvas.add(this.rect); 

    this.rectTracker = ko.computed(function() { 
    this.rect.set(this.getParams()); 

    canvas.renderAll();  
    }, this); 

}; 

RectangleViewModel.prototype.getParams = function() { 
    return { 
     left: +this.left(), 
     top: +this.top(), 
     fill: this.fill(), 
     width: +this.width(), 
     height: +this.height() 
    }; 
}; 

var vm = new RectangleViewModel(canvas); 

ko.applyBindings(vm); 

Một ý tưởng ngắn gọn khác, nếu bạn muốn giữ một số vải/canvas gọi ngoài kiểu xem của bạn (tôi có thể sẽ). Bạn có thể tạo một ràng buộc fabric mà mất trong một loạt các hình dạng để thêm vào canvas. Bạn cũng sẽ vượt qua một trình xử lý truy lục các tham số để truyền cho nó. Các ràng buộc sau đó sẽ tạo ra hình dạng, thêm nó vào vải, và sau đó tạo ra một tính toán để cập nhật hình dạng trên thay đổi. Một cái gì đó như:

ko.bindingHandlers.fabric = { 
    init: function (element, valueAccessor) { 
     var shapes = valueAccessor(), 
      canvas = new fabric.Canvas(element); 

     ko.utils.arrayForEach(shapes, function (shape) { 
      //create the new shape and initialize it 
      var newShape = new fabric[shape.type](shape.params()); 
      canvas.add(newShape); 

      //track changes to the shape (dependencies accessed in the params() function 
      ko.computed(function() { 
       newShape.set(this.params()); 
       canvas.renderAll(); 
      }, shape, { 
       disposeWhenNodeIsRemoved: element 
      }); 

     }); 
    } 
}; 

Bạn có thể đặt nó trên một canvas như:

<canvas data-bind="fabric: [ { type: 'Rect', params: rect.getParams }, { type: 'Rect', params: rect2.getParams } ]"></canvas> 

Vào thời điểm đó, mô hình xem có thể được đơn giản hóa khá một chút để chỉ đại diện cho dữ liệu của hình chữ nhật. Ví dụ ở đây: http://jsfiddle.net/rniemeyer/G6MGm/

+0

Các ràng buộc tùy chỉnh có vẻ như một cách tiếp cận tốt. Tôi sẽ cần phải thực hiện các ràng buộc hai chiều (cần phải theo dõi các hình dạng khi chúng được kéo) nhưng điều đó có vẻ dễ dàng, đủ với các sự kiện vải. Tôi cũng nhận thấy trong jsFiddle của bạn rằng, sau khi tôi thay đổi vị trí trong hộp văn bản, 'mục tiêu nhấp' của hình dạng được vẽ có vẻ không chính xác, có nghĩa là khi tôi nhấp/kéo vào hình dạng, không có gì xảy ra. Khi tôi bấm vào bên cạnh hình dạng, tôi có thể kéo nó. Có lẽ vải cần một cuộc gọi làm mới/vẽ lại hoặc một cái gì đó. – bertvh

+0

@bertvh - vâng - tôi thậm chí không nhận ra bạn có thể nhấp và kéo/thay đổi kích thước hình dạng. Tôi đã không sử dụng vải trước đó fiddle. Bạn chắc chắn sẽ phải đăng ký các sự kiện về vải trong 'init' và cập nhật các quan sát dựa trên dữ liệu do sự kiện cung cấp. –

+0

@Niemeyer Trong khi không phải là một giải pháp hoàn chỉnh, tôi chấp nhận câu trả lời của bạn vì nó thực sự đã cho tôi trên trak phải. Cảm ơn người đàn ông! – bertvh

0

Tôi có vấn đề tương tự trong dự án sở thích, http://simonlikesmaps.appspot.com ... Tôi cần phải liên kết một mảng các điểm tham chiếu trên bản đồ và tôi không thể thực hiện trực tiếp thông qua KO vì các điểm tham chiếu được ẩn bên trong các lớp SVG của OpenLayers . Vì vậy, tôi sử dụng chức năng đăng ký trong mẫu này:

Đầu tiên, một kiểu xem cho những thứ bạn đang hiển thị, cho tôi các điểm tham chiếu, cho bạn các yếu tố; khoảng -

ElementViewModel = function(data) { 
    this.position = ko.observable(data.position) 
    this.label = ko.observable(data.label) 
    this.color = ko.observable(data.color) 
} 

Thứ hai, một mô hình nhằm giữ một danh sách những điều này:

ElementListViewModel = function() { 
    this.elements = ko.observableArray() 
} 

Sau đó, một số logic để nạp các yếu tố của bạn từ webservice của bạn vào mô hình xem, đại khái là:

var element_list = new ELementListViewModel() 
ajax_success_function(ajax_result) { 
    for (element in ajax_result) { 
     element_list.elements.push(new ElementViewModel(element)) 
    } 
} 

Có vẻ như bạn sẽ có bit này được sắp xếp; mục đích là để kết thúc với một danh sách các yếu tố được tổ chức trong một observableArray. Sau đó bạn có thể đăng ký với nó và làm bạn Fabric làm việc; có rất nhiều mã giả trong bit tiếp theo này!

ElementListViewModel = function() { 
    this.elements = ko.observableArray() 
    this.elements.subscribe(function(new_elements) { 
     // Remove everything from Fabric 
     Fabric.removeEverything() // <!-- pseudo code alert! 
     for (element in new_elements) { 
      Fabric.addThing(convertToFabric(element)) 
     } 
    }, this); 
} 

Đây không phải là siêu phức tạp, nhưng không có nghĩa là bất cứ khi nào thay đổi dữ liệu của bạn, và danh sách các yếu tố thay đổi, những thay đổi đó ngay lập tức được phản ánh trong canvas của bạn bằng cách KO gọi hàm đăng ký. 'Đồng bộ hóa' giữa danh sách các phần tử mới và những gì đã có trên canvas khá thô: chỉ đơn giản là phá hủy mọi thứ và tái hiển thị toàn bộ danh sách. Điều này là tốt cho tôi với các điểm tham chiếu của tôi, nhưng có thể là quá đắt đối với bạn.

+0

Vâng .. đó thực sự sẽ làm việc cho các trường hợp đơn giản. Tuy nhiên, không có ràng buộc thực sự xảy ra. Các mục trên canvas của tôi có thể kéo được bởi người dùng và tôi cần vị trí được đồng bộ hóa với các kiểu xem của tôi. Tôi không thể làm điều đó với giải pháp này. – bertvh

0

Tôi đã quan tâm đến câu hỏi này vì tôi cũng đã cố gắng tích hợp các đối tượng fabric.js với mô hình loại trực tiếp - tôi đoán tôi yên tâm rằng không có một câu trả lời rõ ràng nào. Đây là mẫu cho thấy suy nghĩ mới nhất của tôi: https://jsfiddle.net/whippet71/aky9af6t/ Nó có liên kết dữ liệu hai chiều giữa các đối tượng DOM (hộp văn bản) và đối tượng canvas hiện được chọn.

HTML:

<div> 
    <canvas id="mycanvas" width="600" height="400"></canvas> 
</div> 

<div class="form form-inline"> 
    <div class="form-group">X: <input data-bind="textInput: x" class="form-control"></div> 
    <div class="form-group">Y: <input data-bind="textInput: y" class="form-control"></div> 
</div> 

Javascript:

var jsonFromServer = [{"type":"rectangle","left":10,"top":100,"width":50,"height":50},{"type":"rectangle","left":85,"top":100,"width":50,"height":50},{"type":"circle","left":25,"top":250,"radius":50}]; 
var selectedObject; 

var canvas = new fabric.Canvas('mycanvas'); 

for (var i=0; i<jsonFromServer.length; i++) { 
    var thisShape = jsonFromServer[i]; 
    if (thisShape.type == 'rectangle') { 
     var rect = new fabric.Rect({ 
      width: thisShape.width, 
      height: thisShape.height, 
      left: thisShape.left, 
      top: thisShape.top, 
      fill: 'blue' 
     }); 
     canvas.add(rect); 
    } else if (thisShape.type == 'circle') { 
     var circle = new fabric.Circle({ 
      radius: thisShape.radius, 
      left: thisShape.left, 
      top: thisShape.top, 
      fill: 'green' 
     }); 
     canvas.add(circle); 
    } 
} 
// Set first object as selected by default 
selectedObject = canvas.getObjects()[0]; 
canvas.setActiveObject(selectedObject); 


// A view model to represent the currently selected canvas object 
function ShapeViewModel(initX, initY) { 
    var self = this; 

    self.x = ko.observable(initX); 
    self.y = ko.observable(initY); 
    // Create a computed observable which we subsribe to, to notice change in x or y position 
    // Use deferred updates to avoid cyclic notifications 
    self.position = ko.computed(function() { 
     return { x: self.x(), y: self.y() }; 
    }).extend({ deferred: true }); 
} 

var vm = new ShapeViewModel(selectedObject.left, selectedObject.top); 
ko.applyBindings(vm); 

// Function to update the knockout observable 
function updateObservable(x, y) { 
    vm.x(x); 
    vm.y(y); 
} 

// Fabric event handler to detect when user moves an object on the canvas 
var myHandler = function (evt) { 
    selectedObject = evt.target; 
    updateObservable(selectedObject.get('left'), selectedObject.get('top')); 
} 
// Bind the event handler to the canvas 
// This does mean it will be triggered by ANY object on the canvas 
canvas.on({ 'object:selected': myHandler, 'object:modified': myHandler }); 

// Make a manual subscription to the computed observable so that we can 
// update the canvas if the user types in new co-ordinates 
vm.position.subscribe(function (newPos) { 
    console.log("new x=" + newPos.x + " new y=" + newPos.y); 
    selectedObject.setLeft(+newPos.x); 
    selectedObject.setTop(+newPos.y); 
    selectedObject.setCoords(); 
    canvas.renderAll(); 
    // Update server... 
}); 

Một số điều cần lưu ý:

  • tôi tạo ra một ko.observable và làm cho một thuê bao tay để nó cho mục đích này cập nhật đối tượng vải
  • Tôi đã thiết lập sự kiện vải h andler cập nhật ko.observable khi lựa chọn/sửa đổi đối tượng. Tôi không cần phải liên kết với bất kỳ thứ gì ngoài đối tượng được chọn hiện tại.
  • tôi đã phải sử dụng cập nhật chậm để tránh cập nhật theo chu kỳ (cập nhật vải ko, sau đó thông báo cho vải ...)

Tôi rất mới để knockout vì vậy bất kỳ ý kiến ​​/ góp ý sẽ được hoan nghênh.

Các vấn đề liên quan