Access Html element from ko.computed

2019-05-10 21:34发布

Is it possible to access bound element from ko.computed function?

Something like this pseudo code (simplified for clarity):

<h1 id="id1" data-bind="visible: myComputed">
<h1 id="id2" data-bind="visible: myComputed">
<h1 id="id3" data-bind="visible: myComputed">

...

self.myComputed = ko.computed(function(){
        return <BOUND_ELEMNT>.id == 'id2';
    });

Resulting in only the second element showing.

Note: I'm aware I can have a separate computed for every element, but it is not possible in my case.

EDIT:

Ok - I'll give a more accurate example. The following is similar to what I have:

<section id="id1" data-bind="visible: myComputed1">A lot of code</section>
<section id="id2" data-bind="visible: myComputed2">different lots of code</section>
<section id="id3" data-bind="visible: myComputed3">Other lots of code</section>

...

// This field's value changes over time (between 'id1', 'id2' and 'id3').
// Some complex logic changes this field,
// and as a result only one h1 is showing at a time.
self.myField = ko.observable();
self.myComputed1 = ko.computed(function(){
        return self.myField() == 'id1';
    });
self.myComputed2 = ko.computed(function(){
        return self.myField() == 'id2';
    });
self.myComputed3 = ko.computed(function(){
        return self.myField() == 'id3';
    });

This is an ugly violation of the DRY principle, and I would like to find a way to refactor it. The pseudo code above may solve it, but I'm open for suggestions...

3条回答
Bombasti
2楼-- · 2019-05-10 22:16

Make a custom binding handler that uses your observable to trigger the visibility. Something like this:

ko.bindingHandlers.idVisible = {
    update: function(element, valueAccessor) {
        var idUnwrapped = ko.utils.unwrapObservable(valueAccessor());
        if(idUnwrapped == $(element).attr('id'))
        {
            $(element).show();
        }
        else
        {
            $(element).hide();
        }
    }
};

Change your HTML:

<h1 id="id1" data-bind="idVisible: headerId">Header 1</h1>
<h1 id="id2" data-bind="idVisible: headerId">Header 2</h1>
<h1 id="id3" data-bind="idVisible: headerId">Header 3</h1>

And a sample view model:

function ViewModel() {
    var self = this;

    self.headerId = ko.observable('id1');
}
var vm = new ViewModel();
ko.applyBindings(vm);

Here's a jsFiddle with a demo that changes the headerId after two seconds: http://jsfiddle.net/psteele/cq9GU/

查看更多
甜甜的少女心
3楼-- · 2019-05-10 22:28

You've created a short, simplified example, which is normally great. However, it feels like you've introduced an XY-problem instead. As such, this answer may or may not be helpful.

You're trying to introduce a dependency on the View in your ViewModel. It should be the other way around! Something like this would make more sense:

<h1 data-bind="visible: myComputed, attr { id: myId }"></h1>

Note the use of the attr binding to set the id. Your ViewModel should be constructed accordingly:

var activeHeaderId = 'id2';

var viewModel = function(id) {
    var self = this;

    self.myId = ko.observable(id);

    self.myComputed = ko.computed(function() {
        return self.myId() === activeHeaderId;
    });
}
查看更多
beautiful°
4楼-- · 2019-05-10 22:29

Note: I'm leaving my other answer as an answer to the first bit of the question, maybe it'll help other users stumbling upon this question.

The question in the update is:

This is an ugly violation of the DRY principle, and I would like to find a way to refactor it.

OP indicates in comments that answers focussing on the given example are preferred. The sample code can be easily refactored such that it doesn't violate DRY anymore. (PS. I still think the "why" behind wanting this structure is very important, but that doesn't prevent me from answering the question in the context of the given sample.)


Method 1 - One h1 in the View - One item in the ViewModel

Use the following View:

<h1 data-bind="attr: {id: myField}">

With this ViewModel:

self.myField = ko.observable();

Very DRY, functionally almost equivalent (except that the other 2 h1 elements aren't just invisible, they're not even in the DOM).


Method 2 - Multiple h1s in the View - Multiple items in the ViewModel

Refactor the View to a list structure (see note 4 in the foreach binding):

<!-- ko foreach: items -->
<h1 data-bind="attr: {id: myId}, 
               text: itemName, 
               visible: $root.currentItem().myId() === myId()">
</h1>
<!-- /ko -->

With the following ViewModel:

var item = function(nr) {
    this.itemName = ko.observable("Item number " + nr);
    this.myId = ko.observable("id" + nr);
}

var viewModel = function() {
    this.items = ko.observableArray([new item(1), new item(2), new item(3)]);
    this.currentItem = ko.observable();
}

See this fiddle for a demo.


Method 3 - One h1 in the View - Multiple items in the ViewModel

With this method you use the list like setup from method 2, but only render the currentitem. The View utilizes the with binding:

<!-- ko with: currentItem -->
<h1 data-bind="attr: {id: myId}, text: itemName"></h1>
<!-- /ko -->

The ViewModel is the same as in method 2. See this fiddle for a demo.

查看更多
登录 后发表回答