用 HVML 写一个计算器

本文用一个实际的例子描写了如何使用 HVML 一步步编写一个简单的计算器。

Web 版本

我们以 CSDN 上一个 Web 版计算器示例作为开头,以方便理解需求。

这一 Web 版本的计算器,其运行原理如下:

  1. 维护一个用户输入的运算表达式字符串,如 50 * 3 - 20

  2. 当用户点击 = 按钮时,使用 JavaScript 的内置 eval 函数来对运算表达式求值。

该示例在单个 HTML 文件中同时包含了 CSS 和 JavaScript 脚本代码,我们将其复制到本文中,以方便读者阅读,希望原作者不要告侵权。

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>计算器</title>

        <style type="text/css">
            body{
                padding: 0;margin: 0;
                background-color:#49C593 ;
            }
            #calculator{
                position: absolute;
                width: 1200px;height: 620px;
                left: 50%;top: 50%;
                margin-left: -600px;
                margin-top: -310px;
            }
            #calculator #c_title {
                margin: auto;
                /*margin-left: 300px;*/
                width: 800px;
                height: 80px;
            }
            #calculator #c_title h2{
                text-align: center;
                font-size: 33px;font-family: "微软雅黑";color: #666;
                line-height: 30px;
            }
            #c_text{
                width: 1000px;
                margin: auto;
            }
            #calculator #c_text #text{
                /*margin-left: 200px;*/
                padding-right: 10px;
                width: 1000px;
                height: 50px;
                font-size: 25px;font-family: "微软雅黑";color: #666666;
                text-align: right;border: 1px white;
                border: double 1px;
            }
            #calculator #c_value{
                width: 1080px;height: 408px;
                /*margin-left: 160px;*/
                margin: 20px auto;
            }
            #calculator #c_value ul{
                margin: 0;
            }
            #calculator #c_value ul li{
                margin: 10px;
                list-style: none;float: left;
                width: 180px;height: 80px;line-height: 80px;
                text-align: center;background-color: chartreuse;
                border: 1px solid black;
                font-size: 30px;font-family: "微软雅黑";color: #666;
                box-shadow: 5px 5px 30px rgba(0,0,0,0.4);
                -webkit-user-select: none;
                -ms-user-select: none;
                -moz-user-select: none;
            }
            #calculator #c_value ul li:active{
                background-color: white;
            }
            #calculator #c_value ul li:hover{
                opacity:0.8;
                cursor:pointer;
            }
            #calculator #c_value ul .c_blue{
                background-color: cornflowerblue;color: #000000;
            }
            #calculator #c_value ul .c_yellow{
                background-color: #f9f900;color: #000000;
            }
        </style>

        <script type="text/javascript">
            var IsClear = false;
            var cal = "";
            function get(key){
                var str = document.getElementById("text").value;
                if(IsClear){
                    str = "0";
                    IsClear = false;
                }
                if(str.length < 20){
                    str = (str == "0" ? "" : str);
                    if(str == "" && key == '00'){
                        str = "0";
                    }else{
                        str += key;
                    }
                }
                document.getElementById("text").value = str;
            }
            function goBack(){
                var str = document.getElementById("text").value;
                str = str.subSTR.0,str.length-1);
                if(str=="") str="0";
                document.getElementById("text").value = str;
            }
            function clearText(){
                document.getElementById("text").value = "0";
            }
            function eq(){
                IsClear = true;
                var str = document.getElementById("text").value;
                var result = eval(str)
                if(result == "Infinity"){
                    result = "输入有误";
                }
                document.getElementById("text").value = result;
            }
        </script>
    </head>

    <body>
        <div id="calculator">
            <div id="c_title"><h2>计算器</h2></div>
            <div id="c_text">
                <input type="text" id="text" value="0" readonly="readonly" />
            </div>
            <div id="c_value">
                <ul>
                    <li onclick="get(7)">7</li>
                    <li onclick="get(8)">8</li>
                    <li onclick="get(9)">9</li>
                    <li onclick="goBack()" class="c_blue">←</li>
                    <li onclick="clearText()" class="c_blue">C</li>
                    <li onclick="get(4)">4</li>
                    <li onclick="get(5)">5</li>
                    <li onclick="get(6)">6</li>
                    <li onclick="get('*')" class="c_blue">×</li>
                    <li onclick="get('/')" class="c_blue">÷</li>
                    <li onclick="get(1)">1</li>
                    <li onclick="get(2)">2</li>
                    <li onclick="get(3)">3</li>
                    <li onclick="get('+')" class="c_blue">+</li>
                    <li onclick="get('-')" class="c_blue">-</li>
                    <li onclick="get(0)">0</li>
                    <li onclick="get('00')">00</li>
                    <li onclick="get('.')">.</li>
                    <li onclick="get('%')" class="c_blue">%</li>
                    <li onclick="eq()" class="c_yellow">=</li>
                </ul>
            </div>
        </div>
    </body>
</html>

该页面被渲染后的效果如下图所示:

计算器渲染效果

一些说明

上面的 HTML 代码,编写得算不上特别优美,比如大量使用了 id 属性,而其实使用 class 属性更为合适。

我们不打算仔细改造这些细节之处,所以最终的 HVML 程序中,会保留在头部声明的 style 标签内容,但由于内容过多,我们会省略其中的样式信息。

数据驱动生成 HTML 内容

首先,我们看到原始 HTML 中有 12 个用来输入数字的按钮,以及八个用于表示四则运算、删除字符、清零的功能按钮。我们可以使用 HVML 的 iterate 动作标签完成这些重复性的 HTML 内容的生成。为此,我们首先准备一个全局的数据:

我们在全局的 buttons 这个 JSON 对象数组上执行迭代,即可生成所有的按钮。注意,为方便其后的编程,我们为不同种类的按钮新增了一个用于功能的类名,如 numberplus 等。

对应的 HVML body 标签内容如下:

注意,在上述代码中,除了使用了 HVML iterate 动作标签之外,我们还使用了 archetype 标签,用于定义一个 HTML 片段模板。

处理表达式输入

随着用户点击按钮,解释器输出框中的内容将随之改变。为此,我们设定一个全局的字符串变量,用于保存计算器输出框中的字符串,当用户点击按钮时,该字符串的值将发生变化,而计算器输出框中显示的内容也将相应发生变化。

这一改变涉及两处:

  • 在头部新增一个全局的 myResult 变量。

  • 监听具有类名 .btn 元素之 click 事件,更新表达式并重置输入框的文本内容。

如下所示:

处理其他按钮事件

接下来,我们使用 HVML 的 observe 标签处理其他按钮上的事件。其中清除(C)按钮是最容易处理的:

回退(backspace)按钮的处理,要稍微麻烦一些。对字符串操作,我们通常使用由 HVML 解释器实现的内置动态对象(如 $STR),通过调用该对象提供的动态方法 substr 实现:

= 按钮的处理,我们直接使用预定义变量 $MATH 提供的 eval 方法:

完整的计算器程序源代码

见下面的代码清单。

注:为节省篇幅,我们把 CSS 部分保存到了单独的外部文件中。

Last updated