简体   繁体   中英

How to add animation to Knockout.js' If binding?

I want to use the Knockout.js If binding but include animation. I am using a single view model container to allow for a large number of separate views to be loaded into the same container. If I use the visibility binding on the "template" the bindings are hidden and all throw errors since their view model is not currently loaded. I am fearful these view models will begin to slowdown the page if everything is loaded.

From the knockout If documentation:

The if binding, however, physically adds or removes the contained markup in your DOM, and only applies bindings to descendants if the expression is true.

The knockout animated transitions documentation creates a custom binding using jQuery's Show/Hide functions. However, these also hide/show the DOM elements.

In short I am trying to learn one of two things.

What would be the appropriate way to remove/add DOM elements in jQuery so that it may be used in a custom binding?

Or

How does the if binding in knockout work, so that I may reverse engineer it?

Clarifying Edit:

To clarify more how the code is setup. The admin section of the site that raised this question will contain a place to edit all of the standard content pages, and access reports for the business.

Html "templates" are stored as such (To be clear, these are not knockout templates, but rather html files that contain data-bindings. This could be changed with a compelling reason.)

Admin
  Page Edit
  User Edit
  etc
Reports 
  Product
  User
  etc

Our javascript is similar to this

BaseViewModel.js: 
  Content view model 

AdminEditViewModels.js: 
  UserEditViewModel
  ContentEditViewModel
  [1 view model per item]

AdminReportsViewModels.js
  [similar to above]

When a link is clicked the main page content view model is loaded into the Base view model and is made visible by the binding that inspired this question. Then each view model has it's own Load to trigger the ajax calls.

self.ViewOrders = function () {
  self.Content("Orders");
  self.ContentVM(new AdminOrdersViewModel());
  self.ContentVM().Load();
}

Right now there are only about 9 different "templates" and we have normalized them the best we can, but it is likely they will grow. The binding would just prevent each "template" from throwing errors into the console.

If you are using a template for your separated views, then you could get the afterRender callback by using a factory function to initialize the template.

Here is a simply stub for that purpose:

ko.components.register("ItemTemplate", {
    viewModel:  function(params) {
        function Item(params) {
            var self = this;
            // observables
            self.enhance = function(elements) {
                // enhance/animate here the DOM elements
            };
        }
        Item.prototype.dispose = function() {
            // dispose what has been created inside here
        };
        var item = new Item(params);
        return item;
    },
    template: '<div data-bind="template: {afterRender: enhance}">'+
            // component markup
            '</div>',
    synchronous: true
});

Using the fadeIn/fadeOut example you mentioned I tried to create a binding that performs fading on the element, and then initializes an "if" binding on the element's inner content by wrapping that content in a new div. The if binding is then passed a new observable property that gets set using the callback from jQuery's fade function. It feels a little hacky and probably doesn't work in overly complex scenarios, but perhaps you or the SO community can improve upon it.

 var viewModel = function(){ var self = this; self.showContent = ko.observable(false); self.content = ko.observable("content goes here"); } //Uses the IF binding to remove the element's content from the DOM, but also fades before/after. ko.bindingHandlers.fadedIf = { init: function (element, valueAccessor, allBindingsAccessor, data, bindingContext) { // Initially set the element to be instantly visible/hidden depending on the value var value = valueAccessor(); //If the value is a normal function make it a computed so that it updates properly if (!ko.isObservable(value)) { value = ko.computed({ read: valueAccessor }); } //attach our observable property to the accessor so that it can be used in the update function valueAccessor.domShown = ko.observable(ko.unwrap(value)); //Wrap any contents of the element in a new div, and then bind that div using the "if" binding. //This way the element and its event hooks for fading in/out never leaves the dom, but all content does. //it also prevents applying multiple bindings to the same element. var contentWrapper = $(element).children().wrapAll(document.createElement("div")).parent()[0]; ko.applyBindingAccessorsToNode(contentWrapper, { 'if': function () { return valueAccessor.domShown } }, bindingContext); }, update: function (element, valueAccessor) { // Whenever the value subsequently changes, slowly fade the element in or out var value = valueAccessor(); if (ko.unwrap(value)) { valueAccessor.domShown(true); //restore the element to the DOM $(element).fadeIn(); } else { $(element).fadeOut({ complete: function () { valueAccessor.domShown(false); //remove the element from the DOM } }); } } }; ko.applyBindings(new viewModel()); 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <div style="border: 1px solid blue; width:600px; margin:auto; padding: 32px; text-align:center;"> Show Content<input type="checkbox" data-bind="checked: showContent"> <br/> <div data-bind="fadedIf: showContent"> <div style="background-color: silver; padding: 20px;"> <h3 data-bind="text: content"></h3> </div> </div> </div> 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM