html form submit

最近在做的部分与 HTML 的 form 表单相关,记录下以下几个问题。

用 form 表单外的 button 提交

一般情况下,form 表单的提交按钮会写在 form 表单内,而且非常方便,如:

1
2
3
4
5
<form action="/userlist/add" method="POST">
用户名:<input type="text" id="username" required="required" />
密码:<input type="text" id="password" required="required" />
<input type="submit" value="提交" />
</form>

这样,就会有一个叫提交的按钮,点击后会执行 form 的 action。

但有时我们需要在 form 标签以外的某一个 button 上执行表单的提交。比如在另外一个 div 上有一个 button。这时就让这个 button 去执行一个 js 方法,用 js 去调用这个 form 的submit 方法即可,如:

1
2
3
4
5
6
7
8
9
10
<div class="modal-body">
<form action="/userlist/add" method="POST" id="formNewUser">
用户名:<input type="text" id="username" />
密码:<input type="text" id="password" />
<input type="submit" value="提交" />
</form>
</div>
<div class="modal-footer">
<button onclick="$('#formNewUser').submit()">提交</button>
</div>

注意,直接调用 submit 方法的方式提交,即使给 username 和 password 的 input 标签添加了 required 属性,也不会自动校验,而是直接执行 action 的方法。所以如果需要校验必填项,就在 js 方法里面校验,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<div class="modal-footer">
<button onclick="submitValidate()">提交</button>
</div>
<script type="text/javascript">
function submitValidate() {
if ($("#username").val().length == 0) {
alert("请输入用户名");
} else if ($("#password").val().length == 0) {
alert("请输入密码");
} else {
$('#formNewUser').submit()
}
}
</script>

form 表单内的 button 会默认自动提交

如果一个 button 在 form 表单里,那他默认就是一个 submit 按钮,不管你有没有写成 type="submit"

1
2
3
<form>
<button>...</button>
</form>

但有些按钮我们并不想让它执行提交,则可以对其添加属性 type="button" 即可。

提交非由用户填写的参数

有时我们需要在调用接口时,提交一些不是由用户填写的参数。

如编辑一条数据,其 id 是程序在提交时直接填充上的,而不需要由用户填写,也不需要用户看到。

这时,只需要在 form 里增加一个 input,并且将其 hidden 属性设置为 hidden,value 属性设置为所需要提交的值即可。如:

1
2
3
4
5
<form>
<input type="text" value="1234567890abcdef" hidden="hidden" name="id" />
<input type="text" name="username" />
<input type="text" name="address" />
</form>

EJS template

与所有软件开发一样,Web 开发中也会面对着逻辑和数据。将逻辑和数据分开,是良好的工程结构必须做到的,否则代码会混乱不堪。而很不幸的是,用 JavaScript 去生成 HTML 时,往往就会都杂糅到一块儿。

EJS 就是一个 JavaScript 的模版类,可以将逻辑写成一个 HTML 模版,并把数据分离出去。包括 hexo, Express.js 等框架中也都采用了 EJS。

EJS

其最主要的是 <% js代码 %><%= 表达式 %> 两个语句。前者会执行里面的 js 代码,后者会将里面的表达式直接输出到 HTML 中。

如:

1
2
3
4
5
6
7
<% if (gender==0) { %>
<%= 'Male' %>
<% } else if (gender==1) { %>
<%= 'Female' %>
<% } else { %>
<%= 'Unknown' %>
<% } %>

简单的判断,也可用问号表达式:

1
<%= a>10 ? 'Success' : 'Failed' %>

需要注意,开括号<%的右侧和内容之间、闭括号%>的左侧和内容之间,一定要留有空格,否则会报错。

Android 收回软键盘

在 iOS 里,如果要收回软键盘,只需要调用当前持有焦点的 TextField 的 resignFirstResponder 方法即可。所以很自然的,在 Android 里,就会想到调用 EditText 的 clearFocus() 方法。但发现这样并没有让键盘收回,这与 iOS 不同。

在 Android 中,应借助 InputMethodManager 可隐藏键盘,方法如下:

1
2
3
4
5
6
7
private void hideKeyboard() {
View viewFocus = this.getCurrentFocus();
if (viewFocus != null) {
InputMethodManager imManager = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
imManager.hideSoftInputFromWindow(viewFocus.getWindowToken(), 0);
}
}

要注意,就像刚才所说,虽然有 viewFocus.clearFocus() 这个方法,但调用它是无法让软键盘收回的。

另外,用上面这个方法能够收回键盘,但输入框依然持有着焦点。也就是说,焦点与软键盘是否显示,并无关系。

Android 单选框

与 iOS 不同,Android 提供了单选框,含有两个组件:RadioButtonRadioGroup

把若干个 RadioButton 放到一个 RadioGroup 内,则这个 RadioGroup 内的 RadioButton 自动完成只能选择一个的功能。

但是需要注意,如果想要提供默认值,如默认选择第一个 RadioButton,不能在布局文件中把第一个 RadioButton 的 checked 属性设为 true,否则它会一直处于被选中状态。如果要实现默认值,在 java 代码中实现,如可以在 onCreate 时调用 RadioGroup 的 check 方法,传入第一个 RadioButton 的 id 即可,如:

1
radioGroup.check(R.id.radioButtonFirst);

另外,如果要取得 RadioGroup 中被选择的项,可通过 RadioGroup 的 getCheckedRadioButtonId 方法得到被选择的 RadioButton 的 id。然后可通过 findViewById 得到这个 RadioButton,再以此得到它的 tag 确定选择了第几项。

Android pop to parent activity with status preserving

用 Android Studio 创建的 Blank Activity,在新建向导里会问你它是否有上级页面,可以在里面填上它上一级的 Activity,那么生成的 Activity 顶部的导航栏左侧就有返回按钮,并且点击后可以返回上一级页面。

但是这个返回按钮是有问题的。正常情况下,点击返回应该直接返回到之前创建的上层 Activity,也就是直接 pop 到上一层。但是这个返回按钮,是把上一层 Activity 重新创建了一遍显示出来的,导致无法显示之前的状态。

比如上层 Activity 有一个 ViewPager,里面有两个 Fragment。在第二个 Fragment 里点击进入了详情的 Activity,然后点击返回按钮,发现返回到了上层 Activity 的初始状态-默认选择的第一个 Fragment。

可以在 AndroidManifest.xml 中看到,实现方式是定义了一个 PARMENT_ACTIVITY:

AndroidManifest.xml
1
2
3
4
5
6
7
8
<activity android:name=".ProductListActivity"
android:label="@string/title_activity_product_list"
android:parentActivityName=".MainActivity"
android:theme="@style/AppTheme.NoActionBar" >
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.stoneark.doorsbylin.MainActivity" />
</activity>

解决方法也很简单,在 AndroidManifest.xml 中把上一层 Activity 的属性 android:launchMode 改为 singleTop 即可,即:

AndroidManifest.xml
1
2
3
4
5
6
<activity
android:name=".MainActivity"
android:label="@string/title_activity_main"
android:theme="@style/AppTheme.NoActionBar"
android:launchMode="singleTop" >
</activity>

launchMode 的默认值是 standard,每次都会强制重新创建一个 activity 的实例,所以导致了问题。根据 Android 文档中的描述,
android:launchMode 的取值有:standardsingleTopsingleTasksingleInstance,默认值是 standard:

  • standard 允许该 activity 有多个实例,并且每当显示这个 activity 时就总是重新创建一个。
  • singleTop 允许该 activity 有多个实例,但如果它当前在栈顶,则不重新创建。
  • singleTask 只允许该 activity 有一个实例,并且它只能存在于栈底。
  • singleInstance 与 singleTask 相同,另外还要求栈中只能有它自己,而不能再启动另外任何 activity。

Android 用 ID 字符串取到对应的 View

我们经常会需要在一个 for 循环中,取到多个 View,这时就需要用拼接得到的字符串作为 ID 来取 View。
但 findViewById 的参数是一个 Int 类型,一般用 R.id.xxx 来直接得到这个 Int 值。但如何用字符串来取 View 呢?

这时需要先用 getResources().getIdentifier 由 String 得到对应的 Int,比如下面将会获取 btnItem0 到 btnItem5 这六个按钮:

1
2
3
4
5
6
for (int i=0;i<=5;i++) {
String strButtonID = "btnItem" + i;
int buttonID = getResources().getIdentifier(strButtonID,"id","org.stoneark.packagename");
Button btnItem = (Button) findViewById(buttonID);
...
}

Android Button with both text and image

最近帮朋友做一个不大不小的项目,开发上需要完成 iOS、Android、后台管理三部分。Android 和后台管理的开发虽然之前也都了解一些,但一直没有真正做过什么东西。于是决定把 iOS 交给 Ran,Android 和后台管理也不再找别人做了,自己尝试一下。

Android 的开发在有些地方还是比 iOS 更加方便的。比如实现一个同时有文字和图片的按钮,在 Android 上,Button 有这样四个属性:
drawableLeft, drawableRight, drawableTop, drawableBottom
分别可以在文字的左侧、右侧、顶部、下部放置图片,简直不能再简单。

当然,还是那句话,不存在 iOS 开发更简单或者 Android 开发更简单这样的说法。它们各自都有实现起来相对另一个更方便的地方。所谓简单与难,其实只是精通与知道的差别。永远不要说别人的工作比自己的简单--只是别人比你知道的更多。

Android Ripple Button with Background color

自从引入了 Meterial design,Android 变得漂亮了许多,比如从 Lollipop 开始 Button 默认就带有了 Ripple 效果。但如果给这个 Button 赋了 background 属性,改变了它的背景色,则会发现 Ripple 效果消失了。

如果要改变它的背景色,还想要有 Ripple 效果,那么不要改它的 background 属性,而是将它的 style 属性设置为 @style/Widget.AppCompat.Button.Colored,然后在 Colors.xml 中更改 colorAccent 的颜色值即可。但是要注意,这种方式类似改变“主题”,程序中所有的按钮、单选框等控件的颜色都会一同改变。

除了 colorAccent,还有 colorPrimary, colorPrimaryDark 等可以改变的主题颜色。

曾经十分出名的软件公司

今天看到一则新闻:瑞星也冲刺新三板了 还记得曾经的小狮子吗,我是一个怀旧的人,就突然想看看之前那些十分出名的软件公司,现在都变成什么样了。

瑞星,国内非常有名的杀毒软件,我自己从来没用过,给人留下印象最深的就是那个小狮子。我对它的感觉是没感觉,没有特别喜欢也没有特别不喜欢。但自从东方微点案被爆出后,对它就变得特别不喜欢了。

江民,DOS 时代杀毒绝对的王者,没有之一。现在我脑海里还能回忆起 KV300 的包装,白色、软质的包装,里面是一个 3.5 寸的软盘:
KV300

金山,这是一家很厉害的公司,至今如此。当年 DOS 平台下 WPS 是中文文字处理绝对的王者,记得上小学的时候计算机课程很主要的一个内容就是 WPS。后来的 WPS Office、金山词霸、金山快译、金山毒霸,以及西山居游戏、猎豹移动,金山一直没有退出公众的视野。其创始人求伯君也是中国软件行业神话一般的存在。
WPS

洪恩,当年计算机教育软件绝对的老大,在电脑刚刚开始在中国普及的时候,可以说洪恩让国人学会了使用电脑。没经历过那个年代的可能感受不到,但只要在九十年代末二十一世纪初接触了电脑的人来说,很多人会记得洪恩。开天辟地、万事无忧、畅通无阻这三个系列软件,成为了电脑学习三部曲,尤其是开天辟地的知名度非常高。另外还有从零开始学英语、天问等等一系列的教育软件。我最早买的正版软件都是出自洪恩之手。另外,洪恩还有一个叫做“洪恩在线”的 BBS,当年人气也非常旺。可惜的是随着计算机的普及,计算机教育软件的需求就不再旺盛了。洪恩经历几次调整,如今已经转型做幼教了。话说 MacTalk 的池老也是出自洪恩。
开天辟地(图片来自网络)

豪杰,跟洪恩一样,经历过那个年代的人肯定知道,没经历过的也就没经历过了。曾经,没有暴风影音,没有迅雷看看,有的是豪杰超级解霸:
超级解霸2001

这绝对是播放 VCD 的一霸,当年的装机必备。软件分两个窗口,一个是这个控制窗口,另一个是正在播放的视频。后来其他全能播放软件出现,豪杰虽然也不断推陈出新,推出了 3000、V8 等版本,但颓势终究不可避免。今天再一次打开它的网站,发现已写着 Close Off,可惜。
Herosoft close off

千千静听,从 Winamp 和 Foobar 统治的音乐播放软件中突围,取长补短,更符合中国人的习惯,很快变得家喻户晓。但随着网速越来越快,人们的习惯从之前的下载到本地听歌,逐渐转变为了在线听歌。千千静听逐渐被酷狗、酷我等赶超,之后被百度收购,现在千千静听这个品牌已不复存在。
千千静听

Swift 与 Objective-C 混编

Swift 这门新兴的编程语言,自从 WWDC 2014 被发布以来不断被完善,并获得了越来越多苹果开发者的认可。WWDC 2015 时 2.0 版本的发布以及开源,让我们看到了 Swift 的不断成熟,苹果也在提倡开发者们向 Swift 转移。

我很早就阅读了 Swift 的相关内容,并且感觉它是一个很不错的开发语言。它具有一些现代脚本语言的特征,更重要的是跟我早先常用的 Pascal 语言在很多地方十分相近,感觉很亲切。虽然如此,实际在工作中还并未引进 Swift,我和我的团队依然在用 Objective-C 进行 iOS 软件的开发。

今天突然想在开发新功能时试一把 Swift,于是研究了一下 Swift 和 Objective-C 的混编,在原有 Objective-C 工程中引入了 Swift。

Swift 代码调用 Objective-C 代码

在现有 Objective-C 工程中,新建一个 Cocoa Touch Class,新建时选择 Language 为 Swift,会弹出对话框提示是否需要创建 Bridging Header:
Create Bridging Header

创建后工程中会新建一个名为 ProjectName-Bridging-Header.h 的头文件。编辑这个头文件,Swift 代码需要调用的 Objective-C 头文件,都在这个文件中 import 即可:

ProjectName-Bridging-Header.h
1
2
3
4
5
6
7
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//
#import "BaseViewController.h"
#import "APIHandler.h"
#import <YYModel.h>
#import <MJRefresh.h>

另外,可以在工程属性的 Build Settings 中,可以找到 Objective-C Bridging Header 项,其中已自动填入了这个 ProjectName-Bridging-Header.h 的路径。

Objective-C 代码调用 Swift 代码

在需要调用 Swift 代码的 Objective-C 文件头部,写入:

1
#import “ProductModuleName-Swift.h”

即可,然后就能调用工程中 所有 的 Swift 源码,并不需要单独指定需要调用哪一个。
这个 ProductModuleName,默认就是工程名称,如”YouYouYuEr-Swift.h”。可以在工程属性的 Build Settings 中,找到 Product Module Name 项查看。

这个头文件的全名,也可在工程属性的 Build Settings 中,找到 Objective-C Generated Interface Header Name 项查看,可以看到它的值为 $(SWIFT_MODULE_NAME)-Swift.h。(可见上一小节第 2 张图)

但注意,这个文件在工程中 并不 实际存在。因为就像刚才所说,import 这个文件后能调用工程中 所有 的 swift 源码,所以就不需要由开发者再去编辑这个文件,来告知编译器需要引用哪些 .swift 文件。而反过来则不同,Swift 调用 Objective-C 需要告知编译器需要调用哪些 Obejctive-C 文件,所以需要一个 ProjectName-Bridging-Header.h 供开发者去编辑。

总结

可以看到,Build Settings 中的 Objective-C Bridging HeaderObjective-C Generated Interface Header Name 两项,是 Swift 与 Objective-C 混编的关键,分别为一种语言引用另一种语言提供了桥梁。