如何实现类似于 Angular 的 ngStyle 的功能,但使用 Mustache?

问题描述 投票:0回答:1

我在 Mustache 网站的问题跟踪器上收到以下问题:

像Angular,ngStyle,有什么办法可以添加动态样式

问题涉及以下表示法,该表示法仅适用于 Angular 模板(请参阅 Angular 文档):

<div [ngStyle]="{'background-color': section.background}"></div>
<!-- or -->
<div [ngStyle]="section.computedStyle"></div>

所谓“有效”,我的意思是,如果上述模板片段的上下文有一个

section
对象,其
background
属性的值为
'#e33'
,则片段的第一行将呈现如下所示:

<div style="background-color: #e33;"></div>

此外,如果

section
对象稍后被
{background: '#d44'}
替换,则实时文档中的上述
<div>
将自动更新为新的背景颜色。

提问者不是使用Angular,而是使用Mustache,并且想知道如何实现类似的效果(如果可能的话)。

html css templates dynamic mustache
1个回答
0
投票

回答问题分为两部分:

  1. 找到一种表示法,可以根据模板上下文中的对象在一段 HTML 中插入样式属性;
  2. 找到一种在包含样式的对象发生更改时更新实时文档的方法。

在 HTML 中插入样式属性

正如问题中提到的,Angular 的

ngStyle
支持两种略有不同的表示法。第一个表示法显式地从上下文中提取每个 CSS 属性的值:

<div [ngStyle]="{'background-color': section.background}"></div>

此变体可以直接移植到等效的 Mustache 表示法:

<div style="background-color: {{section.background}};"></div>

第二种表示法根据模板上下文中对象的所有属性插入任意数量的 CSS 属性:

<div [ngStyle]="section.computedStyle"></div>

这个表示法也可以移植,但是比较复杂。最便携但不方便的方法是使用 partial。我们首先必须定义第二个模板,列出我们想要支持的 CSS 属性,我们称之为

styleAttribute
:

{{! we hide line breaks by moving them inside the tags }}
{{#background-color
}}background-color: {{background-color
}}; {{/background-color
}}{{#font-family
}}font-family: {{font-family
}}; {{/font-family
}}{{! and so forth }}

有了这个额外的模板,我们可以使用它来移植 Angular 的符号,如下所示:

<div style="{{#section.computedStyle}}{{>styleAttribute}}{{/section.computedStyle}}"></div>

您可以通过将以下保存状态复制粘贴到playground的加载/存储框中来查看上述符号的作用:

{"data":{"text":"{\n    section: {\n        computedStyle: {\n            /* feel welcome to edit this (supported \n               attributes are background-color,\n               font-family and font-weight) */\n            'background-color': '#e33',\n            'font-family': 'Georgia'\n        }\n    }\n}"},"templates":[{"name":"main","text":"<div style=\"{{#section.computedStyle}}{{>styleAttribute}}{{/section.computedStyle}}\"></div>"},{"name":"styleAttribute","text":"{{#background-color\n}}background-color: {{background-color\n}}; {{/background-color\n}}{{#font-family\n}}font-family: {{font-family\n}}; {{/font-family\n}}{{#font-weight\n}}font-weight: {{font-weight\n}}; {{/font-weight\n}}{{! and so forth }}"}]}

当然,如果我们不需要列出所有我们想要支持的CSS属性那就更好了。更方便但可移植性较差的方法是用主机编程语言编写一个可以进行转换的函数,然后在模板中将其用作 lambda (您必须从该链接向上滚动一点)。在这种情况下,宿主语言很可能是 JavaScript,这意味着我们可以依靠

this
绑定来获取函数中的 CSS 属性。该函数本身如下所示:

function asStyleAttribute() {
    var attributeText = '';
    for (var cssAttr in this) {
        attributeText += `${cssAttr}: ${this[cssAttr]}; `;
    }
    return attributeText;
}

此函数将支持所有可能的开箱即用的 CSS 属性,无需列出它们。然而,我们仍然需要再跳几圈,以确保

asStyleAttribute
可以作为 lambda 运行,并且
this
指向具有实际样式数据的对象。我们通过创建一个包装器来做到这一点:

function CssAttributes(attributes) {
    for (var attr in attributes) {
        this[attr] = attributes[attr];
    }
}
// add our asStyleAttributes function as a method to the wrapper
CssAttributes.prototype.asStyleAttribute = asStyleAttribute;

我们总是将样式数据放在这个包装器中,而不是将它们作为普通对象存储在模板上下文中:

section.computedStyle = new CssAttributes({
    'background-color': '#e33'
});

现在,完成所有这些准备工作后,我们终于可以在模板中编写以下内容:

<div style="{{section.computedStyle.asStyleAttribute}}"></div>

同样,以下保存状态将在playground中演示此符号:

{"data":{"text":"{\n    section: {\n        /* the code looks a bit different here\n           because of the way the playground works */\n        computedStyle: {\n            asStyleAttribute() {\n                var attributeText = '';\n                for (var cssAttr in this) {\n                    if (cssAttr === 'asStyleAttribute') continue;\n                    attributeText += `${cssAttr}: ${this[cssAttr]}; `;\n                }\n                return attributeText;\n            },\n            /* feel welcome to edit this (you\n               can use any attributes you want) */\n            'background-color': '#e33',\n            'font-family': 'Georgia'\n        }\n    }\n}"},"templates":[{"name":"main","text":"<div style=\"{{section.computedStyle.asStyleAttribute}}\"></div>"}]}

更优雅的方法来做到这一点需要对 Mustache 语言进行非标准扩展。我会提到一些已经讨论过的:

更新实时文档

Mustache alone 无法更新实时文档,因为它不知道 HTML 的含义以及未来对数据的任何更改。当您渲染模板时,输出只是一个原始字符串,就 Mustache 而言,它的工作已经完成。

但是,您可以从外部添加动态更新,通过使用另一个知道这些事情的软件。此类软件称为客户端应用程序框架,我知道一个允许您使用 Mustache 模板的软件:Backbone

原理非常简单:将数据包装在

Model
中,将模板包装在
View
中。 Backbone 为您提供了在模型更改时轻松更新视图的工具。

解释 Backbone 的工作原理超出了本答案的范围,但我将在一个片段中演示其原理:

// Our template. There could be more than one.
var template = _.mustache(`
<button type=button>Change it</button>
<button type=reset>Reset</button>
<div style="{{sectionStyle.asStyleAttribute}}">
    Watch this!
</div>
`);

// Our wrapper from before.
function CssAttributes(attributes) {
    for (var attr in attributes) {
        this[attr] = attributes[attr];
    }
}
CssAttributes.prototype.asStyleAttribute = function() {
    var attributeText = '';
    for (var cssAttr in this) {
        attributeText += `${cssAttr}: ${this[cssAttr]}; `;
    }
    return attributeText;
};

// A model. We will add the sectionStyle to it.
var model = new Backbone.Model({
    sectionStyle: null
});

// A view class. Most of the magic will happen here.
var DemoView = Backbone.View.extend({
    // This view uses our template from above.
    template: template,
    
    // Tell the view what to do when a button is clicked.
    events: {
        'click button[type=button]': 'changeIt',
        'click button[type=reset]': 'resetIt'
    },
    
    // Setup when the view is instantiated.
    initialize() {
        this.render();
        // This line enables the dynamic updates.
        this.listenTo(this.model, 'change', this.render);
    },
    
    // How the view uses the template. The contents
    // of the model become the context of the template.
    render() {
        this.$el.html(this.template(this.model.attributes));
        return this;
    },
    
    // Event handlers for the buttons.
    changeIt() {
        this.model.set('sectionStyle', new CssAttributes({
            'font-weight': 'bold',
            'text-decoration': 'underline',
            'background-color': '#e33',
            'border': '2px solid black'
        }));
    },
    resetIt() {
        this.model.unset('sectionStyle');
    }
});

// To use a view class, we create an instance
// and attach it to the DOM.
var theView = new DemoView({model: model});
theView.$el.appendTo('body');
div {
    width: auto;
    padding: 1em;
    margin: 1em;
}
<script src="https://cdn.jsdelivr.net/npm/[email protected]/underscore-umd-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/backbone-min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/mustache-umd.min.js"></script>

想要更高级、更真实的示例代码,可以查看playground的源码。它也是使用 Mustache 和 Backbone 构建的,尽管脚本是用 CoffeeScript 完成的。

© www.soinside.com 2019 - 2024. All rights reserved.