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

使用 KnockoutJS 进行用户凭证入门

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.64/5 (7投票s)

2013 年 8 月 6 日

CPOL

5分钟阅读

viewsIcon

23557

downloadIcon

369

使用 KnockoutJS 批量上传新用户凭证到 Web 应用程序的酷炫方法。

引言

我最近在阅读一篇关于为企业增值的文章,其中一个关键点是自动化。概念很简单,如果你发现自己一遍又一遍地做某件事,就找到一种自动化它的方法,这样你就可以节省持续的时间,从而为企业增值。将一批新用户引入系统是扼杀 DevOps 时间的事情之一,所以我决定尝试 KnockoutJS,看看它是否能提供帮助。事实证明是可以的!代码以 C# MVC 项目的形式呈现和下载,但可以轻松剥离并独立使用。网上有足够多的教程可以解释如何使用 Knockout - 去谷歌搜索细节。本文重点介绍了我如何使用 Knockout 提供一个简单的入门验证机制,希望它能帮助到处于类似情况的人。

下面的截图显示了完成的项目,红色线条表示需要修复的主要问题。我还包含了一些正则表达式代码来对电子邮件地址进行基本验证。

这是示例 CSV 文件,请注意,我们所需的所有数据都不在那里,这就是问题所在!

背景

项目的目标是允许用户上传 CSV 文件,在客户端(在发送到服务器之前)解析它,并向用户显示在上传文件进行正确集成之前需要更正的数据。

工作流程如下

  1. 用户选择一个 CSV 文件(提供样本)
  2. 用户点击一个按钮(哇哦……)
  3. 客户端代码获取 CSV,解析它,并将其加载到 KnockoutJS 数组中
  4. KnockoutJS 可观察对象发挥其魔力,向用户显示绿色/前进、红色/停止

使用代码

为了保持整洁,我将 JavaScript 和 CSS 保留在各自独立的文件中。我们将处理的三个主要文件是 HTML (index.cshtml)、JavaScript (ViewScript.js) 和 CSS (KnockoutStyles.css)。

在 KO 中要设置的第一件事是模型。在这种情况下,我正在捕获基本用户信息。您会注意到我有一个密码字段 - 在生产版本中,我没有导入明文密码,而是使用了哈希(样本数据有所不同,但用于说明目的)。

function userModel() {
    this.UserName = ko.observable();
    this.Password = ko.observable();
    this.FirstName = ko.observable();
    this.LastName = ko.observable();
    this.Email = ko.observable();

下一步是定义 ViewModel 数组,并链接绑定

// Define ViewModel which is an array of user details model
var userVM = {
    userModel: ko.observableArray([])
    }
};   

// Bind the VM to the UI
ko.applyBindings(userVM); 

然后将 KO data-bind 标记添加到 HTML

<table border="1">
    <thead>
    <tr>
        <th class="Pad">Username</th>
        <th class="Pad">Password</th>
        <th class="Pad">First name</th>
        <th class="Pad">Last name</th>
        <th class="Pad">Email address</th>
        <th class="Pad">  </th>
    </tr>
    </thead>
    <tbody data-bind="foreach: userModel">
    <tr data-bind="css: ValidCredentials">
        <td class="Pad"><input data-bind="value: UserName" class="Standard"/></td>
        <td class="Pad"><input data-bind="value: Password" class="Standard" /></td>
        <td class="Pad"><input data-bind="value: FirstName" class="Standard"/></td>
        <td class="Pad"><input data-bind="value: LastName"  class="Standard"/></td>
        <td class="Pad"><input data-bind="value: Email" class="Wide"/> <br /></td>
        <td class="Pad"></td>
    </tr>
    </tbody>
</table>

在文件顶部,我添加了一个 FILE 输入控件和一个锚链接。

<form>
    <input type="file" id="UserFile" />
    <a href="#" id="lnkUpload">Upload CSV</a> 
</form>

下一步是在锚的 `Click` 事件中放置一些代码,以(在客户端)获取用户选择的 CSV 文件,并将其解析到 KO 数组中。

$('#lnkUpload').click(function () {
    var FileToRead = document.getElementById('UserFile');
    if (FileToRead.files.length > 0) {
        var reader = new FileReader();
        // assign function to the OnLoad event of the FileReader
        // non synchronous event, therefore assign to other method
        reader.onload = Load_CSVData;
        // call the reader to capture the file
        reader.readAsText(FileToRead.files.item(0));
    }
}); 

有一个让我抓耳挠腮的陷阱,我曾尝试使用 jQuery 选择器来获取 `FileToRead` 变量 - 但这不起作用,所以我回到了纯 JavaScript,一切又顺利进行。

读取文件内容的 `FileReader` 的 "`OnLoad`" 事件不是同步的,所以我将其分配给另一个函数(`Load_CSVData`),然后调用 `readAsText` 事件,该事件会反馈回该函数。

现在,这里有一点需要注意,`FileReader` **只**在您从 Web 服务器运行(或在 IDE 中使用集成 Web 服务器等)时才有效;如果您只是从桌面上运行 txt 文件,它则**无效**。

`Load_CSVData` 方法的逻辑如下

  • 清除 KO 可观察数组中现有的任何项
  • 使用从 `FileReader` 传入的文本数据加载本地数组(`CSVLines`),并使用 "`Split`" 函数(分隔符 = 新行标记)将每一行分开
  • 对于正在加载的每一行,进一步分割(分隔符 = 逗号)
  • 对于 CSV 行中的每一项,将值推送到 VM 数组中的一个新模型

我稍微分解了下面的代码,使其更易于阅读。请注意,如果 CSV 行中没有值,我添加了一个空字符串以避免以后出现问题。

function Load_CSVData(e) {
    userVM.userModel.removeAll();
    CSVLines = e.target.result.split(/\r\n|\n/);
    $.each(CSVLines, function (i, item) {

        var element = item.split(","); // builds an array from comma delimited items
        var LUserName = (element[0] == undefined) ? "" : element[0].trim();
        var LPassword = (element[1] == undefined) ? "" : element[1].trim();
        var LFirstName = (element[2] == undefined) ? "" : element[2].trim();
        var LLastName = (element[3] == undefined) ? "" : element[3].trim();
        var LEmailAddress = (element[4] == undefined) ? "" : element[4].trim();

        userVM.userModel.push(new userModel()
            .UserName(LUserName)
            .Password(LPassword)
            .FirstName(LFirstName)
            .LastName(LLastName)
            .Email(LEmailAddress)

        )
    }); 
} 

这一切都很好,数据完美显示……

下一步是利用可观察模式的力量,根据接收和更改的数据调整 UI……

有许多改进之处

  1. 用红色突出显示有问题的行
  2. 明确数据何时是必需的(我们将使输入框呈黄色)
  3. 用绿色显示已完成或接近完成的行
  4. 如果提供的电子邮件地址无效,则显示一条消息

在每个表行上,我添加了一个 `data-bind` 的 CSS,它调用了一个计算函数。该函数检查所需字段*(用户名、密码、电子邮件)*是否有值,如果没有,则将行的 CSS 背景颜色设置为红色。

.Red {
border: thin dotted #FF6600;
    background-color: red; 
}
<tr data-bind="css: ValidCredentials">
    <td class="Pad"><input...

`ValidCredentials` 是一个计算函数

this.ValidCredentials = ko.computed(function () {
    var ValidCreds = (this.UserName() != "" && 
         this.Password() != "" && this.Email() != "");
    return !ValidCreds ? "Red" : "Green";
}, this);

这工作正常,所以我进一步扩展了逻辑,不仅问题行会显示为红色,而且需要额外数据的输入字段也会显示为黄色。有三个字段,所以我为每个字段创建了单独的定义(CSS:`USR_Required`、`PWD_Required`、`EML_Required`)。

<td class="Pad"><input data-bind="value: UserName, css: USR_Required" class="Standard"/></td>
<td class="Pad"><input data-bind="value: Password, css: PWD_Required" class="Standard" /></td>
<td class="Pad"><input data-bind="value: FirstName" class="Standard"/></td>
<td class="Pad"><input data-bind="value: LastName"  class="Standard"/></td>
<td class="Pad"><input data-bind="value: Email, css: EML_Required"  class="Wide"/>

我创建了三个几乎相同的方法来计算(值得重新审视以进行重构!)

this.USR_Required = ko.computed(function () {
    var rslt = (this.UserName() != "")
    return rslt == true ? "NR" : "Required";
}, this);
this.PWD_Required = ko.computed(function () {
    var rslt = (this.Password() != "")
    return rslt == true ? "NR" : "Required";
}, this);
this.EML_Required = ko.computed(function () {
    var rslt = (this.Email() != "")
    return rslt == true ? "NR" : "Required";
}, this);

并在我的 *KnockoutStyles.css* 文件中添加了相应的 CSS

.Required {
    background-color: #FFFF00;
}
 
.NR {
    background-color: #FFFFFF;
}

到目前为止一切顺利,下一步是添加代码来对电子邮件地址进行简单检查。我为此借用了一些来自 stack 的代码

this.InValidEmail = ko.computed(function () {
        if (this.Email() == "") // dont test if no value present
        {
            return false;
        }
        var rslt = !validateEmail(this.Email());
        return rslt;
    }, this); 

function validateEmail(email) { 
    var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)
      *)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.
      [0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
    return re.test(email);
}

快完成了,最后一步是让用户能够删除整行数据,如果它不相关(想想多年来离开组织的 Active Directory 中的所有账户……)

为此,我在每行的末尾添加了一个 `Click` `data-bind`,它调用了一个 `removeUser` 方法,并在 ViewModel 中添加了代码,将该项从 KO 数组中删除。

<a href="#" data-bind="click: $parent.removeUser"><span class="White">Remove user</span></a>
function RemoveUserFromList(data) {
        userVM.userModel.remove(data);
}  
 var userVM = {
    userModel: ko.observableArray([]),
    removeUser: function (data) {
            RemoveUserFromList(data)
    }
}; 

就是这样……

这里有足够的内容为正在寻找类似解决方案的人提供一个好的开端。可能的改进包括:一个仅在数据更正后启用的按钮,另一个将数据转换为 JSON 并发送到服务器的按钮等。

(附注:如果您喜欢阅读这篇文章,请通过给页面顶部的文章评分来告诉我!)

© . All rights reserved.