BootBrander Bootstrap .less 生成器 UI (第 2 部分 / 解析 variables.less)






4.14/5 (3投票s)
创建一个 MVC 站点,用户可以通过输入更改 bootstrap 变量,并生成自定义品牌的 bootstrap.css。
引言
如果您还没有阅读本系列的第一部分,您应该在这里阅读。
我们正在构建一个用户界面,通过修改 variables.less 文件中的变量颜色来生成自定义的 bootstrap.css 。
在本文中,我们将解析此文件,并将其中所有颜色暴露给用户。我假设他可以更改颜色,但不能更改诸如 font-size 和 padding 之类的内容。
文章索引
变量
我尝试通过 less.js 库查找有哪些变量。但我找不到任何文档。所以我决定获取 variables.less 文件并自己解析它。
编写一个代码解析器非常困难。您必须逐个字符地查看代码才能正确完成。
但我们很幸运。Bootstrap 团队提供的 variables.less 文件格式非常规范。它不会出现不应该出现的空格,并且每次声明一个部分时,其语法都完全相同。
所有变量都按类别声明。我将利用这一点。一个类别看起来像这样
//== Colors
所以如果我正在读取这一行,我可以轻松地查看前 4 个字符来识别它。
一个变量看起来像这样
@gray-base: #000;
这甚至更容易。
开始
如果您有上一篇文章的代码,您应该更改一些内容。首先,更改 viewModel
的声明。
var viewModel = window.viewModel = {
"@body-bg": ko.observable('#ffffff'),
"@text-color": ko.observable('#777777'),
"@brand-primary": ko.observable('#337ab7')
},
改为
var viewModel = {
},
还要找到进行 knockout 绑定的那一行并将其删除。在我们完全完成之前,它会出错。我们稍后会将其放回。
获取和解析
所以,让我们开始编写一个 Ajax 函数来获取 variables.less 文件。
$(document).ready(function () {
$.ajax({
url: "/Content/bootstrap/variables.less",
success: function (responseText) {
var lines = responseText.split('\n'),
line,
i;
for (i = 0, j = lines.length; i < j; i++) {
line = lines[i];
if (line.substr(0, 1) === "@") {
console.log(line);
}
}
}
})
});
测试
如果我们运行此代码,应该会得到以下输出
...Lots up here
main.js:52 @brand-success: #5cb85c;
main.js:52 @brand-info: #5bc0de;
main.js:52 @brand-warning: #f0ad4e;
main.js:52 @brand-danger: #d9534f;
main.js:52 @body-bg: #fff;
... and more down here
它打印了所有变量。其中有许多不同类型的变量。它们可以是像 darken()
这样的函数,可以是像素、数字或对其他变量的引用。
目前,我们将重点关注值以 #
开头的变量。那些肯定是颜色。
我们将在 viewModel
中添加其中任何一个。
$.ajax({
url: "/Content/bootstrap/variables.less",
success: function (responseText) {
var lines = responseText.split('\n'),
line,
i,
nameValue,
name,
value;
for (i = 0, j = lines.length; i < j; i++) {
line = lines[i];
if (line.substr(0, 1) === "@") {
//this is a variable
nameValue = line.split(":");
name = nameValue[0].trim();
value = nameValue[1].trim();
if (value.substr(0, 1) === "#") {
//this is color
viewModel[name] = ko.observable(value);
}
}
}
console.log(viewModel);
}
});
如果我们运行此代码,可以看到 viewModel
的控制台输出现在包含许多变量。
绑定 viewModel
在上一篇文章中,我们为每个单独的颜色变量手动编写了一个带有 knockout 绑定的 HTML 片段。这不会非常有用,我们将替换它。
为此,我们将创建一个 HTML 模板,然后将其提供给 knockout。
单个颜色的模板
我们将使用一个模板,稍后将 knockout 绑定变量发送到其中。在 body 的底部创建以下 HTML 代码。
<script type="text/html" id="color">
<div class="row">
<div class="col-xs-9" style="padding-right: 0;">
<input type="text" class="form-control" data-bind="value: $data" />
</div>
<div class="col-xs-3" style="padding-left: 0;">
<input type="color" class="form-control" data-bind="value: $data" />
</div>
</div>
</script>
现在,在 id 为 toolbar-container
的左侧面板中,我们将遍历 viewModel
的属性。在每次迭代中,我们将设置一个 label
并调用我们的模板;
<div class="col-xs-2" id="toolbar-container">
<div class="form-group" data-bind="foreach: {data: Object.keys($data), as: '_propkey'}">
<label data-bind="text: _propkey"></label>
<div data-bind="template: { name: 'color', data: $root[_propkey] }"></div>
</div>
</div>
现在我们可以重新开始 knockout 绑定了。在 Ajax 调用成功处理程序的末尾执行此操作。就在解析 variables.less 的循环之后。
ko.applyBindings(viewModel);
测试
运行网站,应该看起来像这样
我们得到一个很好的列表,其中包含所有看起来是颜色的项。但有一个问题。或者实际上是两个。
所有颜色选择器都是黑色的,这是因为值。首先,它们以“;”结尾。第二个问题是颜色的简写表示法,例如 white #fff。input type="color" 需要完整地编写它们。
找到这些行
if (value.substr(0, 1) === "#") {
//this is color
viewModel[name] = ko.observable(value);
}
并更改为
if (value.substr(0, 1) === "#") {
//this is color
value = value.replace(";", "");
if (value.length === 4) {
value += value.substr(1, 3);
}
viewModel[name] = ko.observable(value);
}
再次运行,然后
好!
迭代属性似乎确实会破坏 knockout 的双向绑定。但现在我们先不要担心。因为我们首先添加类别,这无论如何都会改变事情。
类别
现在,它只是一长串颜色。一些上下文会很有帮助。如前所述,变量以注释形式声明在 variables.less 的类别中:
//== Colors
所以,首先,我们会看到一个类别,然后是一系列属于这些类别的变量。为了反映这一点,我们首先需要稍微更改一下 viewModel
。它应该具有以下形式
viewModel
categories
category
variableName
variableName
category
variableName
variables
variable
variable
裸 viewModel
声明如下
var viewModel = window.viewModel = {
categories: {},
variables: {}
}
我们还将更改 variable
对象本身。如果您查看我们现在拥有的列表,会缺少几个变量,@text-color
丢失了。这是因为该颜色不以 # 开头,而是声明为继承自另一个 variable
。我们稍后会处理这个问题。但目前,我们将把单个 variable
分割为值和类型。
我们的解析代码现在应该如下所示
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();
//console.log(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();
value = value.replace(";", "");
if (value.substr(0, 1) === "#") {
//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: "color",
value: ko.observable(value)
}
}
}
}
console.log(viewModel);
//Apply the viewModel
ko.applyBindings(viewModel);
}
您可能会注意到,我没有简单地将 variable
保存到类别本身,而是仅保存名称。并且我将 variable
本身添加到对象中。
这是有两个原因。
第一个原因是引用 variables
(其值为另一个 variable
名称的那些)。为了解决这些问题,对象更易于查找。
第二个原因是序列化。我无法将引用保存在 category.variables
列表中,因为这会创建一个循环对象,我们无法将其通过 JSON.stringify。
更新 UI
现在我们需要更改我们的 HTML 和 knockout 绑定以反映此更改。首先是我们的模板
<script type="text/html" id="color">
<div class="row">
<div class="col-xs-9" style="padding-right: 0;">
<input type="text" class="form-control"
data-bind="value: value" />
</div>
<div class="col-xs-3" style="padding-left: 0;">
<input type="color" class="form-control"
data-bind="value: value" />
</div>
</div>
</script>
现在是左侧列。我选择将类别和变量放入 details 部分。但您可以通过使用手风琴来改进它
<div class="col-xs-2" id="toolbar-container">
<div data-bind="foreach: {data: Object.keys(categories), as: '_propkey'}">
<details>
<summary data-bind="text: _propkey"></summary>
<div data-bind="foreach: $root.categories[_propkey].variables">
<div class="form-group">
<label data-bind="text: $data"></label>
<div data-bind="template:
{ name: $root.variables[$data].type, data: $root.variables[$data] }">
</div>
</div>
</div>
</details>
</div>
</div>
测试
运行页面,UI 应该看起来像这样
但是,如果我们运行第一个文章中的测试
- 通过在文本字段中键入“
red
”来更改@body-bg
变量- 您的页面背景应该变为红色
- 通过颜色选择器更改
@body-bg
变量- 您的页面背景应变为选定的颜色
- 通过在文本字段中键入“
red
”来更改@brand-primary
变量- 主按钮应变为“
blue
” - UI 应保留先前设置的
@body-bg
- 主按钮应变为“
- 通过颜色选择器更改
@brand-primary
变量- 主按钮应变为选定的颜色
- UI 应保留先前设置的
@body-bg
所有这些都不起作用。这是因为我们还没有设置对可观察对象的订阅。在上一篇文章中,我们有以下代码:
function onViewModelChanged() {
var viewData = ko.toJS(viewModel);
less.modifyVars(viewData);
localStorage.setItem("viewData", JSON.stringify(viewData));
};
for (var prop in viewModel) {
if (viewModel.hasOwnProperty(prop)) {
viewModel[prop].subscribe(onViewModelChanged);
if (storedViewData.hasOwnProperty(prop)) {
viewModel[prop](storedViewData[prop]);
};
}
}
然后我说这是根本错误的。之所以这样,是因为它为存储的数据设置了值。订阅首先发生。然后对于每个变量,它设置存储的值。这将导致 onViewModelChanged
函数针对 viewModel
中的每个颜色触发。在我们之前的例子中,这影响不大,因为我们只有 3 种颜色。但现在,我们可能会开始注意到这一点。
但首先,让我们将执行订阅的循环包装在一个函数中,我们称之为 applySubscriptions
。
function applySubscriptions() {
var observableVars = viewModel.variables;
for (var prop in observableVars) {
if (observableVars.hasOwnProperty(prop)) {
observableVars[prop].value.subscribe(onViewModelChanged);
}
}
}
现在,我们需要一些东西来反序列化我们的 viewModel
。我们将变量拆分为类型和值。因此,仅仅调用 less.modifyVars(ko.toJS(viewModel))
将不起作用。
function viewModelToJS() {
var obj = {},
observableVars = viewModel.variables
;
for (var prop in observableVars) {
if (observableVars.hasOwnProperty(prop)) {
obj[prop] = observableVars[prop].value();
}
}
return obj;
}
然后,我们更改 onViewModelChanged
函数,使其使用这个而不是 ko.toJS
;
function onViewModelChanged() {
var viewData = viewModelToJS();
less.modifyVars(viewData);
localStorage.setItem("viewData", JSON.stringify(viewData));
};
运行我们的测试
- 通过在文本字段中键入“
red
”来更改@body-bg
变量- 您的页面背景应该变为红色
- 通过颜色选择器更改
@body-bg
变量- 您的页面背景应变为选定的颜色
- 通过在文本字段中键入“
red
”来更改@brand-primary
变量- 主按钮应变为“
blue
” - UI 应保留先前设置的
@body-bg
- 主按钮应变为“
- 通过颜色选择器更改
@brand-primary
变量- 主按钮应变为选定的颜色
- UI 应保留先前设置的
@body-bg
- 在顶部导航中移至其他页面
- UI 应保留先前设置的
@body-bg
- UI 应保留先前设置的
@brand-primary
- UI 应保留先前设置的
我们的测试进行了到切换页面这一步。如前所述,这个 MVC 站点不是 ajax 的,所以它会从服务器拉取页面。
我们需要修复从存储中重置变量的问题。上一篇文章中获取存储变量并将其放入 storedViewData
的代码应该仍然有效,所以我们在解析器循环中,在检查变量类型之前,可以恢复先前的值
value = value.replace(";", "");
if (storedViewData.hasOwnProperty(name)) {
value = storedViewData[name];
}
if (value.substr(0, 1) === "#") {
然后在 Ajax 成功处理程序的末尾,调用 less.modifyVars
来应用所有变量;就在 applyBindings
下方。
//Apply the viewModel
ko.applyBindings(viewModel);
//Set the current values
less.modifyVars(viewModelToJS());
现在执行我们的 testplan
,它应该可以工作。
总结
我现在结束这篇文章。因为我看到它变得相当长。查找我们的引用变量,如 @text-color
,以及处理像 'darken
' 和 'lighten
' 这样的函数将在下一篇文章中介绍。
在下载中,您会找到一个经过优化且带有 jsdoc 注释的版本。它将是我们下一篇文章的起点。