整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

aardio + Ruby 可视化快速开发独立 EX

aardio + Ruby 可视化快速开发独立 EXE 桌面程序

ardio 可以方便地调用 Buby,支持系统自带 Ruby 环境。如果没有安装 Ruby ,aardio 会自动安装。所以 aardio + Ruby 开发的软件可以生成体积较小的独立 EXE 文件。

用 aardio 执行 Ruby 代码

用法很简单,aardio 代码示例:

import win.ui;
var winform=win.form(text="执行Ruby代码")
winform.add(
edit={cls="edit";left=26;top=16;right=737;bottom=435;edge=1;multiline=1}
)
import process.ruby;

//执行 Ruby 代码
var out=process.ruby.exec("puts '测试UTF-8'")
winform.edit.print(out);

//解析 Ruby 表达式并返回为 aardio 对象
var out=process.ruby.eval(`[1, 2, { name: "tanaka", age: 19 }]`)
winform.edit.print(out);

winform.show();
win.loopMessage();

在 aardio 中运行上面的代码:

process.ruby.exe() 以管道方式打开 Ruby 进程执行 Ruby 代码,第一个参数可以是 *.rb 代码文件路径,也可以直接指定 Ruby 代码。可以添加不定个数启动参数,也可以在一个字符串参数中写多个启动参数。

process.ruby.eval() 可以解析第一个字符串参数指定的 Ruby 表达式的值,返回值会自动转换为纯 aardio 对象。

CGI 调用 Ruby

import win.ui;
var winform=win.form(text="Ruby CGI")

var code=/*
require 'cgi'

cgi=CGI.new
puts cgi.header
puts "<html><body>This is a test</body></html>"
*/

//创建测试文件
string.save("/res/index.rb",code);

//启动嵌入式 HTTP 服务器,自动分配空闲服务端口
import process.ruby.simpleHttpServer;
var url=process.ruby.simpleHttpServer.startUrl("/res/index.rb");

//创建浏览器控件显示网页
import web.form;
var wb=web.form(winform);
wb.go(url);

winform.show();
win.loopMessage();

process.ruby.simpleHttpServer 基于 aardio 标准库 wsock.tcp.simpleHttpServer 创建了一个嵌入式的多线程 HTTP 服务器,自动分配空闲服务端口,不会与其他程序冲突,在程序退出时此 HTTP 服务器会自动退出。很省心不需要写多余的代码。

上面的 "/res/index.rb" 可以放到工程资源目录下,发布的时候能生成独立 EXE 文件。


创建 Ruby 交互解释器

aardio 代码示例:

import console
console.setTitle("Ruby 交互解释器");

import process.ruby;
process.ruby.cmd("irb");

按 Ctrl + D 或者输入 exit 以后回车可以退出。

执行 Rake 命令

import win.ui;
/*DSG{{*/
var winform=win.form(text="执行Rake命令")
winform.add(
edit={cls="edit";left=26;top=16;right=737;bottom=435;edge=1;multiline=1;z=1}
)
/*}}*/

var rakefile=/*
task :purchaseAlcohol,[:arg1, :arg2] do |t, args|
  puts "#{args[:arg1].to_i + args[:arg2].to_i}"
end
*/

//创建测试文件
string.save("/rakefile",rakefile )

import process.ruby;
var result,err=process.ruby.rake("purchaseAlcohol[123,2]");
winform.edit.print(result,err);

winform.show();
win.loopMessage();

process.ruby.rake() 创建进程管道运行 rake 命令,返回值分别为:标准输出,错误输出。

第一个参数指定 rake 命令。可选自第二个参数开始指定其他命令行参数。多个命令行参数自动合并,不在双引号内、且包含空白或需要转义的参数转义处理后首尾自动添加双引号。

需要先在工作目录下创建 rakefile 。可以用 process.ruby.workDir 指定 Ruby 工作目录。默认工作目录为 "/" ,"/" 在 aardio 中表示应用程序根目录(开发时为工程根目录,发布后为 EXE 所在目录 )。

用 aardio 可视化开发图形界面

用 aardio + Ruby 混合开发的主要目的是为了使用 aardio 的可视化开发图形界面。

如果不熟悉 aardio ,建议先看一遍 aardio 开始页的《 aardio 编程语言快速入门——语法速览 》。aardio 开发界面很简单,相关教程、范例、开源项目也很多,这里就不多讲了。

一篇文章Stimulus 状态管理,幻灯片显示讲述了Stimulus的状态管理,接下来我们看看如何跟踪外部资源的状态。

有时候我们的controllers需要跟踪外部的资源的状态,这里的外部指的是不在DOM或不在Stimulus中的任何东西。例如,我们可能需要发出HTTP请求,并在请求状态变化时进行响应。或者我们希望启动一个定时器,然后当controller断开连接时停止定时器。在本文,我们将解决这些问题。


接下来,我们学习一下,如何通过加载和插入远程HTML片段,来异步填充页面的各个部分。


我们要创建一个通用的用于加载内容的controller,所有的HTML内容都是从服务器获取的。然后我们将用它来加载一系列未读的消息,就像你在邮箱里看到的那样。


打开public/index.html:

<div data-controller="content-loader"
    data-content-loader-url-value="/messages.html"></div>

然后创建一个public/messages.html

<ol>
    <li>New Message: Stimulus Launch Party</li>
    <li>Overdue: Finish Stimulus 1.0</li>
</ol>

在真实应用中,这个内容是服务器动态生成的,但是这里出于示范的目的,我们就用一个静态文件。


现在我们实现一下我们的controller:

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
    static values={ url: String }

    connect() {
        this.load()
    }

    load() {
        fetch(this.urlValue)
            .then(response=> response.text())
            .then(html=> this.element.innerHTML=html)
    }
}

当controller连接元素时,会根据data-content-loader-url-value属性值设置的url,发起请求。然后把请求得到的HTML内容,赋值给连接元素的innerHTML。


打开浏览器的开发者工具,点开网络查看tab页,然后刷新页面。您将看到一个表示初始页面加载的请求,随后是controller对messages.html的后续请求。


我们继续优化一下controller,隔段时间就刷新div内的内容,让它一直显示最新的内容。


我们使用data-content-loader-refresh-interval-value属性值来设定刷新的时间间隔,单位是毫秒,

<div data-controller="content-loader"
    data-content-loader-url-value="/messages.html"
    data-content-loader-refresh-interval-value="5000"></div>

现在我们修改一下controller,检查间隔,如果间隔值存在,就启动一个定时器来刷新。


在controller里添加一个static values,然后定义一个新方法startRefreshing():

export default class extends Controller {
    static values={ url: String, refreshInterval: Number }
    
    startRefreshing() {
        setInterval(()=> {
            this.load()
        }, this.refreshIntervalValue)
    }

    // …
}

然后修改connect()方法,如果refreshInterval值存在的话,就调用startRefreshing()方法。

connect() {
    this.load()

    if (this.hasRefreshIntervalValue) {
        this.startRefreshing()
    }
}

刷新页面,然后通过开发者工具,观察一下,是不是每5秒钟就会有一个新请求。然后可以尝试修改public/messages.html,所有的改变都会出现在div内。



当controller连接元素时,我们启动了定时器,但是我们没有停止它。这意味着,如果我们的controller连接的元素消失的话,controller将在后台继续发起HTTP请求。


我们修复这个问题,修改startRefreshing()方法,保存一个对定时器的引用:

startRefreshing() {
    this.refreshTimer=setInterval(()=> {
        this.load()
    }, this.refreshIntervalValue)
}

然后添加一个对应的stopRefreshing()方法,来取消定时器:

stopRefreshing() {
    if (this.refreshTimer) {
        clearInterval(this.refreshTimer)
    }
}

最终,我们告诉Stimulus当controller失去连接时,取消定时器,好,我们添加一个disconnect()方法:

disconnect() {
    this.stopRefreshing()
}

现在我们可以确定,内容加载器controller只会在连接到DOM时才会发出请求。


我们来看一下最终的controller类:

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
    static values={ url: String, refreshInterval: Number }

    connect() {
        this.load()

        if (this.hasRefreshIntervalValue) {
            this.startRefreshing()
        }
    }

    disconnect() {
        this.stopRefreshing()
    }

    load() {
        fetch(this.urlValue)
            .then(response=> response.text())
            .then(html=> this.element.innerHTML=html)
    }

    startRefreshing() {
        this.refreshTimer=setInterval(()=> {
            this.load()
        }, this.refreshIntervalValue)
    }

    stopRefreshing() {
        if (this.refreshTimer) {
            clearInterval(this.refreshTimer)
        }
    }
}

本文介绍了如何使用Stimulus生命周期回调来获取和释放外部资源。

一篇文章Stimulus:连接HTML和JavaScript的桥梁,实现简单的controller,并学习了Stimulus是如何连接HTML与JavaScript的。现在我们使用Stimulus来实现复制文本到粘贴板的按钮。

比如说,我们现在有一个需求,就是帮助用户生成密码,在密码旁边放置一个按钮,点击按钮后密码就被拷贝到粘贴板上了,这样就方便用户使用这个密码了。

打开public/index.html,修改body内容,填充一个简单的按钮,如下:

<div>
    PIN: <input type="text" value="1234" readonly>
    <button>Copy to Clipboard</button>
</div>



下一步,创建src/controllers/clipboard_controller.js,然后添加一个copy()方法:

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
    copy() {
    }
}

然后,给div添加data-controller=“clipboard”。只要是给元素添加了data-controller属性,Stimulus就会连接一个controller实例。

<div data-controller="clipboard">

我们还需要一个对输入框的引用,这样我们就可以在调用粘贴板API之前获取输入框的内容。给文本框添加data-clipboard-target=“source“:

PIN: <input data-clipboard-target="source" type="text" value="1234" readonly>

在controller中定义一个target,然后就可以通过this.sourceTarget访问文本框了。

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
    static targets=[ "source" ]
    
    copy() {
    }
}


解释一下这个targets:

当Stimulus加载你的controller类时,它会查看静态数组targets的字符串元素,对于每一个字符串,Stimulus会在controller中添加3个属性。在这里,对于“source”,会添加如下属性:

this.sourceTarget 在controller的域内的第一个source

this.sourceTargets 在controller的域内所有的source组成的一个数组

this.hasSourceTarget 在controller的域内是否有source


我们希望点击按钮时调用controller中的copy()方法,所以我们需要添加data-action=“clipboard#copy“

<button data-action="clipboard#copy">Copy to Clipboard</button>

你可以已经注意到在上面的动作描述符中省略了click->。那是因为Stimulus给button设置了click作为它默认的事件。


某些其他元素也有默认事件。下面是个全部列表:

元素

默认事件

a

click

button

click

details

toggle

form

submit

input

input

input type=“submit”

click

select

change

textarea

input

最终,在copy()方法中,我们获取输入框的内容,调用粘贴板API

copy() {
    navigator.clipboard.writeText(this.sourceTarget.value)
}


刷新页面,点击按钮,然后快捷键粘贴到Greet按钮前到输入框,可以看到1234。



到目前为止,在页面上同一时间只有一个controller实例。在页面上同时有一个controller的多个实例也是很正常的。


我们的controller是可以复用的,只要你需要在页面上添加复制内容的按钮,无论是哪个页面,只要把对应的属性值写好,我们的controller都是生效的。


还是上面的例子,再添加另外一个复制按钮:

<div data-controller="clipboard">
    PIN: <input data-clipboard-target="source" type="text" value="3737" readonly>
    <button data-action="clipboard#copy" class="clipboard-button">Copy to Clipboard</button>
</div>


刷新页面,验证一下两个复制按钮是否都生效。

我们再添加一个可以复制的元素,不用button,我们用a标签,

<div data-controller="clipboard">
    PIN: <input data-clipboard-target="source" type="text" value="6666" readonly>
    <a href="#" data-action="clipboard#copy" class="clipboard-button">Copy to Clipboard</a>
</div>



Stimulus允许我们使用任何元素,只要它设置了合适的data-action属性,就可以触发复制。

这个例子里,要注意一点,点击链接会使浏览器追踪a标签内的href属性跳转,可以取消这种默认行为,只需要在action中调用 event.preventDefault()就可以了。

copy(event) {
    event.preventDefault()    
    navigator.clipboard.writeText(this.sourceTarget.value)
}


还有另外一个方法,拷贝粘贴板上

copy(event) {
    event.preventDefault()    
    this.sourceTarget.select()
    document.execCommand("copy")
}


在本文中,我们看了一个在现实中把浏览器API包装在Stimulus的controller中的例子。还有一个controller的多个实例如何同时出现在页面上,我们还探索了actions和targets如何保持HTML和JavaScript的松散耦合。


下一篇文章,我们将优化一下这个复制粘贴板的功能,让它运行起来更加健壮。

Stimulus:浏览器不支持复制或者弱网条件下,怎么办?