65.9K
CodeProject 正在变化。 阅读更多。
Home

BootBrander - Bootstrap .less 生成器 UI(第 3 部分 / UI 优化)

2015年3月2日

CPOL

7分钟阅读

viewsIcon

14321

downloadIcon

97

创建一个 MVC 站点,用户可以通过输入更改 bootstrap 变量,并生成自定义品牌的 bootstrap.css。

引言

这是本系列的第三部分。如果您还没有阅读第 1 部分第 2 部分,您应该先阅读它们。 

在上一篇文章中,我们让我们的 Bootstrap 颜色选择器 UI 处于工作状态。但是,仍然缺少一些东西。并非所有代表颜色的变量都暴露给了用户。

这是因为在解析 variables.less 时,我们得出结论,所有值以“#”开头的变量都是颜色。这是正确的,但这并不意味着它们是所有的颜色。

如果您愿意,请加入我们。下载第二篇文章是一个很好的起点。

文章索引

其他变量类型

如果我们查看 variables.less,我们可以找到更多类型的变量。有些不代表颜色。有些是其他变量的引用,有些包含颜色函数。让我们看几个例子。

只是一种颜色

@brand-primary:         #428bca;

非颜色

@font-family-sans-serif:  "Helvetica Neue", Helvetica, Arial, sans-serif;
@font-size-base:          14px;
@font-size-large:         ceil(@font-size-base * 1.25); // ~18px

对另一个变量的引用。它可能是一种颜色

@link-color:            @brand-primary;

一个颜色函数

@gray:                  lighten(#000, 33.5%); // #555
@link-hover-color:      darken(@link-color, 15%);

最后一个显然是最糟糕的。它既是函数又是引用。

让我们暂时忽略如何实际处理颜色函数。但我们需要创建一个函数,可以告诉我们某个东西最终是否会是颜色。

如果一个变量是对另一个变量的引用,我们将不得不向上遍历树到终点。最初的值是颜色。它可能需要一些步骤才能达到。

@top-level-color: #ff0000;
@second-color:    @top-level-color;
@third-color:     @second-color;

当我们找到顶层变量时,它可能根本不是颜色。那么我们应该丢弃它。

isColor 函数

让我们创建一个函数,它将告诉我们一个变量是否是颜色。为了解决我们的引用变量,这将是一个递归函数。我们很幸运,因为我们知道,如果我们找到一个引用,目标变量应该已经在我们的模型中。这是因为 less 编译器不允许引用尚未声明的变量。

    /**
     *  Finds out if a value is color
     *  @function isColor
     *  @returns {boolean}
     */
    function isColor(value) {
    
        if(value.substr(0,1)==="#") {
            return true;
        }
        else {
            if(value.substr(0,1)==="@") {
                if(viewModel.variables.hasOwnProperty(value)) {
                    console.log(viewModel.variables[value]);
                    return isColor(viewModel.variables[value].value());
                }
            } 
        }
        return false;
    }

所以它所做的是;首先查看实际值是否以“#”开头,如果是,我们就完成了。否则,我们需要查看该值是否是引用。如果是,我们将在我们的变量对象中查找它,看看它之前是否已添加到我们的 viewModel 中,以及它是否是颜色。

更改解析器

在解析器中,我们应该更改这一行...

if(value.subtr(0,1)==="#") {

...变成...

if(isColor(value) {

但这并不是我们唯一会做的事情。对于这个例子,我们只关注颜色。但也许我们会在某个时候允许用户更改所有变量。所以我们还会将所有非颜色的内容添加到 viewModel 中。 

这不会造成任何伤害。然后我们可以在以后轻松添加功能。所以,在我们的解析器循环中,我们将首先在 isColor if 的末尾声明继续,并在其下方添加其他类型为隐藏的变量。

整个 parseVariablesLess 函数应该看起来像这样

    /**
     * Parses the variables.less file
     * @function parseVariablesLess
     */
    function parseVariablesLess(responseText) {
        var lines = responseText.split('\n'),
            line,
            i,
            nameValue,
            name,
            value,
            category
        ;

        for (i = 0, j = lines.length; i < j; i++) {

            line = lines[i];

            if (line.substr(0, 4) === "//==") {
                category = line.substr(5, line.length).trim();

                viewModel.categories[category] = {
                    variables: ko.observableArray()
                };
                continue;
            }

            if (line.substr(0, 1) === "@") {
                //this is a variable
                nameValue = line.split(":");
                name = nameValue[0].trim();
                value = nameValue[1].trim();
                //This line has change to! In the previous version we just replaced the ;
                //but there could be comments behind them
                value = value.split(";")[0];

                if (storedViewData.hasOwnProperty(name)) {
                    value = storedViewData[name];
                }

                if (isColor(value)) {
                    //this is color

                    if (value.length === 4) {
                        value += value.substr(1, 3);
                    }

                    //add the name to the categories
                    viewModel.categories[category].variables.push(name);

                    //add the variable to the variables object
                    viewModel.variables[name] = {
                        type: ko.observable("color"),
                        value: ko.observable(value)
                    }
                    continue;
                }

                //add the variable to the variables object
                //storing them for later potential use
                viewModel.variables[name] = {
                    type: ko.observable("hidden"),
                    value: ko.observable(value)
                }
            }
        }
    }

处理引用颜色

如果我现在启动界面,我们将看到: 

如果用户点击 @component-active-bg 的色轮并更改它,它将破坏与原始颜色 @brand-primary 的链接。尽管我觉得这应该是可能的,但我也希望有一种方法可以“跳转”到另一种颜色。

为此,我们需要引入一种新的变量类型“color-reference”,并创建一个新的 knockout 模板。一个对话框和我们 viewModel 中的一个函数。

我们将从检测这种新类型开始。让我们更改 isColor 周围的 if

     if (isColor(value)) {
        //this is color

        if (value.length === 4) {
            value += value.substr(1, 3);
        }

        //add the name to the categories
        viewModel.categories[category].variables.push(name);

        if (value.substr(0, 1) === "@") {
            //add the variable to the variables object
            viewModel.variables[name] = {
                type: ko.observable("color-reference"),
                value: ko.observable(value)
            }
        }
        else {
            //add the variable to the variables object
            viewModel.variables[name] = {
                type: ko.observable("color"),
                value: ko.observable(value)
            }
        }
 
        continue;
    }

测试

运行网站,我们看到并非所有类别都显示。控制台中有一个错误。这来自 knockout; 消息:找不到 ID 为 color-reference 的模板

我们需要提供一个模板才能开始。只需复制颜色模板并添加一些更改

<script type="text/html" id="color-reference">
    <div class="row">
        <div class="col-xs-9" style="padding-right: 0;">
            <a class="form-control" data-bind="text: value" ></a>
        </div>
        <div class="col-xs-3" style="padding-left: 0;">
            <input type="color" 
            class="form-control" data-bind="value: value" />
        </div>
    </div>
</script>

我将输入框更改为一个链接。如果您将鼠标悬停在它上面,您会看到它带有下划线装饰。如果用户点击此链接,我希望显示一个对话框,其中包含此颜色所继承的颜色的 UI。

但首先,我们需要一个函数来绑定到链接。我们将把它添加到 viewModel 中,以便我们可以用 knockout 绑定它。

   var viewModel = window.viewModel = {
        categories: {},
        variables: {},
        gotoReference: function (data) {
            alert(data.value());
        }
    },

然后将它绑定到链接。

<a class="form-control" data-bind="text: value, 
click: $root.gotoReference"></a>

那实际上是什么意思?它表示转到 viewModel 的根目录,找到函数 gotoReference 并将其绑定到 click 事件处理程序。

测试

如果我们运行这个并在脚手架中找到例如 @link-color 并点击表单控件,它应该弹出一个警告,显示“@brand-primary”。

创建参考对话框

如果用户点击这样的链接,我想启动一个带有颜色模板的对话框。如果引用颜色本身是对另一个颜色的引用,它应该启动另一个屏幕。

要创建对话框,我将使用 bootbox。您可以通过 NuGet 包管理器找到它。安装后,我倾向于直接将其添加到 BundleConfig.cs 中的 bootstrap bundle 中。

运行...

bootbox.alert("Hello world!");

...从控制台应该会给您一个漂亮的对话框。

为了填充对话框,我们将使用我们之前创建的 knockout 模板。然后,我们将创建与对话框的新绑定。让我们开始吧!

    var viewModel = {
        categories: {},
        variables: {},
        gotoReference: function (data) {
            var $dialog,
                //This will select the parent variable
                variableModel = viewModel.variables[data.value()]
            ;

            //Create a reference to the main viewModel
            variableModel.gotoReference = viewModel.gotoReference;

             $dialog = bootbox.dialog({
                title: data.value(),
                //By sending in the type, this would create a new reference modal if needed
                message: $("#" + variableModel.type).html(),
                buttons: {
                    ok: {
                        label: "Ok"
                    }
                }
            });

            //Apply binding with our variableModel, but just for the dialog!
            ko.applyBindings(variableModel, $dialog.get(0));

        }
    },

测试

  • 启动网站
  • 找到“下拉菜单”部分
  • 点击 @dropdown-link-active-bg 的值
    • 应该会打开一个对话框,其中包含名称 @component-active-bg 和值 @brand-primary
  • 点击值 @brand-primary
    • 应该会打开一个对话框,其中包含名称 @brand-primary,并且它应该是一种实际的颜色
  • 更改颜色并关闭对话框
    • 更改应该会在整个网站中反映出来。

修复颜色微调器

您可能已经看到,这些引用变量的颜色微调器只是黑色。我们需要做几件事。首先,我们需要能够找到父颜色的真实值。我们将通过函数 getColor 来实现。

    /**
     *  Finds out if a value is color
     *  @function getColor
     *  @param {string} name The name of this variable
     *  @returns {string} The color
     */
    function getColor(name) {
        var value = viewModel.variables[name].value();
        if (value.substr(0, 1) === "@") {
            return getColor(value);
        }
        else {
            return value;
        }
    }

除此之外,我们还需要一个 ko.computed 变量来返回此颜色并设置微调器的值。但首先,我们需要修复一些东西。我将展示我们将在 color-reference 可观察对象周围得到的代码。

    viewModel.variables[name] = {
        type: ko.observable("color-reference"),
        value: ko.observable(value)
    };

    viewModel.variables[name].colorSpinnerValue=ko.computed(function () {
        return getColor(viewModel.variables[name].value());
    });

我们需要在变量实际创建之后设置 colorSpinnerValue。因为否则,当我们尝试获取它时,它还不存在。但这并不是问题。这段代码将更改所有引用字段为最后一个颜色。最初,它会起作用。但我们是在循环中执行此操作。因此,如果计算函数被再次调用,变量“name”将不再具有相同的值。

我们可以通过两种方式修复

  1. 创建一个名为 parseLine 的函数,并传入单行。然后 name 变量将在此闭包中。
  2. 在循环内部创建一个闭包。(这会导致 jshint/jslint 抱怨。)

尽管这不是正确的方法,但我为了简洁起见选择了第二种。 

    (function (name) {

        viewModel.variables[name] = {
            type: ko.observable("color-reference"),
            value: ko.observable(value)
        };

        viewModel.variables[name].colorSpinnerValue = ko.computed(function () {
            return getColor(viewModel.variables[name].value());
        });
 
    })(name);

现在,我们仍然需要更改 knockout 绑定

    <div class="col-xs-3" style="padding-left: 0;">
        <input type="color" class="form-control" 
        data-bind="value: colorSpinnerValue" />
    </div>

重新执行之前的测试现在应该会在所有色轮中反映出更改。

总结

我认为它需要更多的功能。用户应该能够“打破”与引用变量的链接。您可以通过更改变量的 type() 来实现这一点,knockout 将立即替换为正常的颜色对话框。

我们仍然需要处理 darken 和 lighten 函数。

我还觉得应该抑制没有选项的类别。但我将把这个问题留给您。

对我来说,下一步是让用户能够下载反映其选择的 .css 文件。

© . All rights reserved.