【问题标题】:How to add animation to Knockout.js' If binding?如何将动画添加到 Knockout.js 的 If 绑定?
【发布时间】:2017-09-07 04:58:32
【问题描述】:

我想使用 Knockout.js If 绑定但包含动画。我正在使用单个视图模型容器来允许将大量单独的视图加载到同一个容器中。如果我在“模板”上使用visibility 绑定,则绑定被隐藏并且所有抛出错误,因为它们的视图模型当前未加载。如果所有内容都已加载,我担心这些视图模型会开始减慢页面速度。

来自淘汰赛If 文档:

if 绑定,然而,物理上添加或删除包含的 DOM 中的标记,并且仅将绑定应用于后代,如果 表达式为真。

淘汰动画转换文档使用 jQuery 的显示/隐藏函数创建自定义绑定。但是,这些也会隐藏/显示 DOM 元素。

简而言之,我正在尝试学习两件事之一。

在 jQuery 中删除/添加 DOM 元素以便在自定义绑定中使用的适当方法是什么?

或者

淘汰赛中的if 绑定如何工作,以便我可以对其进行逆向工程?

澄清编辑:

为了更清楚地说明代码是如何设置的。提出此问题的网站的管理部分将包含一个编辑所有标准内容页面和访问业务报告的地方。

Html“模板”是这样存储的(需要明确的是,这些不是淘汰模板,而是包含数据绑定的 html 文件。这可以通过令人信服的理由进行更改。)

Admin
  Page Edit
  User Edit
  etc
Reports 
  Product
  User
  etc

我们的javascript和这个类似

BaseViewModel.js: 
  Content view model 

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

AdminReportsViewModels.js
  [similar to above]

单击链接时,主页内容视图模型将加载到基本视图模型中,并通过引发此问题的绑定可见。然后每个视图模型都有自己的 Load 来触发 ajax 调用。

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

目前只有大约 9 种不同的“模板”,我们已尽最大努力将它们标准化,但它们很可能会增长。绑定只会防止每个“模板”向控制台抛出错误。

【问题讨论】:

  • "if" 绑定的源代码在 github 上可以找到,但是看起来有些复杂:github.com/knockout/knockout/blob/master/src/binding/…
  • @deblocker 实际上通过指向另一个 SO 问题的链接提醒我,如果您只是想解决将绑定应用于不存在的对象的问题,您最好将它们包装在 "与”绑定。另一方面,如果担心加载太多未使用的对象,那么最好重新考虑如何加载所有模板。在没有相应视图模型的情况下加载视图模板是不寻常的。你能分享更多关于你的代码结构的信息吗?
  • @JasonSke 我在我的问题上添加了更多内容,希望能够澄清。如果你有一些更好的做法。我有兴趣改进此代码。
  • 您是使用异步模块加载来加载 html 文件还是它们都包含在初始主 html 页面中?听起来他们都在主页上。如果是这样,最好将它们存储在

标签: javascript jquery knockout.js


【解决方案1】:

使用您提到的淡入/淡出示例,我尝试创建一个对元素执行淡入淡出的绑定,然后通过将该内容包装在一个新的 div 中来初始化元素内部内容的“if”绑定。然后 if 绑定被传递一个新的 observable 属性,该属性使用来自 jQuery 的 fade 函数的回调进行设置。感觉有点老套,可能在过于复杂的场景中不起作用,但也许你或 SO 社区可以改进它。

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>

【讨论】:

  • 我非常感谢您的代码,从教学的角度来看,我会好好研究它。我发现您对更复杂情况下可能出现的问题的评论也非常聪明。您是否觉得打开deferUpdatesoption 可能存在一些问题?我问你,在阅读了最近的这篇文章后here
  • @deblocker 我还没有尝试在我的任何生产项目中使用延迟更新,所以我真的不能说。当然可能存在类似的问题,因为评估的顺序可能会导致在从 dom 中删除元素之前对内容进行评估
  • 如果 init 函数能够正常工作,我必须将 return { 'controlsDescendantBindings': true } 添加到末尾。不过谢谢,这是一个聪明的主意。
【解决方案2】:

如果您为分离视图使用模板,则可以通过使用工厂函数初始化模板来获取afterRender 回调。

这是一个用于此目的的简单存根:

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
});

【讨论】:

    猜你喜欢
    • 2013-11-07
    • 1970-01-01
    • 1970-01-01
    • 2011-07-19
    • 2012-05-03
    • 2014-06-13
    • 2012-07-04
    • 2021-10-12
    • 2013-02-10
    相关资源
    最近更新 更多