Knockout: the best way to bind visibility to both element and parent property?

I am creating an edit screen where I want people to remove items from the list. The list is displayed normally until the controller object enters edit mode. The user can then delete the items. The items should be marked for deletion and displayed as such, then when the user saves the edit, they are deleted and the server is notified.

I actually have it all working, but the only way to do it is to use literal conditions in the bindings, which looks ugly and I don't really like it. Is there a better way to do this?

Working fiddle: http://jsfiddle.net/L1e7zwyv/

Markup:

<div id="test">
        <a data-bind="visible: IsViewMode, click: edit">Edit</a>
        <a data-bind="visible: IsEditMode, click: cancel">Cancel</a>
        <hr/>
        <ul data-bind="foreach: Items">
                <li data-bind="css: CssClass">
                        <span data-bind="visible: $parent.IsViewMode() || $data._Deleting(), text: Value"></span>
                        <!-- ko if: $parent.IsEditMode() && !$data._Deleting() -->
                                <input type="text" data-bind="value: Value"/>
                                <a data-bind="click: $parent.deleteItem">Del</a>
                        <!-- /ko -->
                </li>
        </ul>
</div>

      

code:

function ItemModel(val)
{
        var _this = this;

        this.Value = ko.observable(val);
        this._Deleting = ko.observable();

        this.CssClass = ko.computed(
                function()
                {
                        return _this._Deleting() ? 'deleting' : '';
                }
        );
}

function ManagerModel()
{
        var _this = this;

        this.Items = ko.observableArray([
                new ItemModel('Hell'),
                new ItemModel('Broke'),
                new ItemModel('Luce')
        ]);

        this.IsEditMode = ko.observable();
        this.IsViewMode = ko.computed(function() { return !_this.IsEditMode(); });

        this.edit = function(model, e)
        {
                this.IsEditMode(true);
        };

        this.cancel = function(model, e)
        {
                for(var i = 0; i < _this.Items().length; i++)
                        _this.Items()[i]._Deleting(false);

                this.IsEditMode(false);
        };

        this.deleteItem = function(model, e)
        {
                model._Deleting(true);
        };
}

ko.applyBindings(new ManagerModel(), document.getElementById('test'));

      

+3


source to share


2 answers


You can:

  • wrap another range to separate the bindings, but this will be less efficient.
  • Use as an anchor visible:

    , and if:

    for one and the same element to achieve the same functionality,
  • write a function in itemModel isVisible()

    to take the parent as an argument making your binding visible: $data.isVisible($parent)

    .
  • Afterthought: if this appears in multiple places you can write a helper function to combine the visibility bindings
    // reprisent variables from models
    var v1 = false;
    var v2 = false;
    var v3 = false;
    
    
    // Helper functions defined in main script body - globally accessible
    function VisibilityFromAny() {
      var result = false;
      for(var i = 0; i < arguments.length; i++ ) result |= arguments[i];
      return Boolean(result);
    }
    
    function VisibilityFromAll() {
      var result = true;
      for(var i = 0; i < arguments.length; i++ ) result &= arguments[i];
      return Boolean(result);
    }
    
    
    // represent bindings
    alert(VisibilityFromAny(v1, v2, v3));
    alert(VisibilityFromAll(v1, v2, v3));
          

    Run codeHide result




The third option is the most popular method with MVVM aficionados like you to combine variables into one binding from what I've seen, it makes sense and keeps all the logic from the view markup in view models.

Personally, I like the syntax you have at the moment (although I consider myself among the group of MVVM fans), it clearly shows in the markup of the view that the visibility of this element is bound to two elements, and does not hide this data in the function.

0


source


I am trying to represent view models as a model for my view, not just a place where logic resides. Whenever possible, I also try to move the complex logic back into the view model and use descriptive names for my variables to make the code more readable.

I would suggest adding this to your viewmodel -

var isViewable = ko.computed(function () { return IsViewMode() || _Deleting(); });
var isEditable = ko.computed(function() { return IsEditMode() && !_Deleting(); });

      

And in your opinion -

<li data-bind="css: CssClass">
        <span data-bind="visible: isViewable, text: Value"></span>
        <!-- ko if: isEditable -->
                <input type="text" data-bind="value: Value"/>
                <a data-bind="click: $parent.deleteItem">Del</a>
        <!-- /ko -->
</li>

      



This clears up the bindings and allows you to customize your logic more easily without doing a lot of sanity checks on your view and treating the model as. Also, I personally name variables that return a boolean value such as this, however, to help describe in more detail.

The advantage is that as your view model and view grows, you can keep the DOM clean of clutter and also your view model becomes testable .

Here is a consolidated version of your fiddle with a full addition -

http://jsfiddle.net/L1e7zwyv/3/

0


source







All Articles