乐者为王

Do one thing, and do it well.

使用CSS创建可伸缩的面包屑

面包屑是一个用来描述层次性链接的术语,告诉用户当前处于网站的什么位置。简单的来说,就是显示从主页到当前页面路径的导航模式。其最简单的形式通常是这样的:

有许多种不同的方法来实现面包屑。在这里,我们仅使用无序列表和基本的CSS样式。

以下是HTML代码:

1
2
3
4
5
6
7
<ul id="breadcrumbs">
  <li><a href="#">Home</a> > </li>
  <li><a href="#">Main</a> > </li>
  <li><a href="#">Sub</a> > </li>
  <li><a href="#">Sub sub</a> > </li>
  <li>Current page</li>
</ul>

在上述的例子中,除了用户当前所在的页面——即最后一个条目外,其它所有的条目都是链接,指向路径中的某个页面。

如果你试着在浏览器中查看上面的这个例子,你会发现它只是一个简单的无序列表。必须给它添加一些CSS样式才能符合我们的预期。

下面是CSS代码:

1
2
3
4
5
ul, li {
  list-style-type: none;
  padding: 0;
  margin: 0;
}

首先我们要重置基本列表样式的行为:把默认的圆形条目符号隐藏掉,并移除缩进。

1
2
3
4
5
6
7
8
9
10
#breadcrumbs {
  height: 2.3em;
}

#breadcrumbs li {
  float: left;
  line-height: 2.3em;
  padding-left: .25em;
  color: #777;
}

然后,给面包屑设置一个具体的高度。因为所有的碎屑需要保持在同一水平线,所以我们把每个li元素设置成float: left。为保证条目的垂直居中对齐,需要把条目的行高设置成和列表相等。碎屑之间需要留些空间,不然的话就会太拥挤,因此我们给每个碎屑左边加上0.25em的填充。

1
2
3
4
5
6
7
8
#breadcrumbs li a:link, #breadcrumbs li a:visited {
  text-decoration: none;
  color: #777;
}

#breadcrumbs li a:hover, #breadcrumbs li a:focus {
  color: #dd2c0d;
}

最后,给文本链接添加样式,使用text-decoration: none隐藏掉下划线。

Android下显示本地Contact Number信息(续)

前文的代码只能显示联系人的第一个号码,很不方便。如果要列出联系人的所有号码,需要将People.CONTENT_URI改为Phones.CONTENT_URI。具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import android.app.Activity;
import android.database.Cursor;
import android.os.Bundle;
import android.provider.Contacts.People;
import android.widget.ListView;
import android.widget.SimpleCursorAdapter;

public class MainActivity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.contact_list);

        ListView contactList = (ListView)findViewById(R.id.contactList);

        Cursor contactsCursor = managedQuery(Phones.CONTENT_URI,
                null, null, null, null);
        startManagingCursor(contactsCursor);

        String[] columnsToMap = new String[] {Phones.NAME, Phones.NUMBER};
        int[] mapTo = new int[] {R.id.contactName, R.id.contactNumber};

        SimpleCursorAdapter mAdapter = new SimpleCursorAdapter(this,
                R.layout.contact_entry, contactsCursor,
                columnsToMap, mapTo);
        contactList.setAdapter(mAdapter);
    }
}

如何获取Android手机的型号

怎么样才能获的Android手机的型号呢?例如HTC Hero或HTC Magic等名字。android.os.Build类有很多静态属性可以获得机器的信息,其中的MODEL属性就指代手机型号。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import android.app.Activity;
import android.os.Build;
import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        TextView tv = new TextView(this);
        tv.setText("Product Model: " + Build.MODEL);
        setContentView(tv);
    }
}

在我的机器上显示的是:

1
Product Model: HTC Hero

Android下显示本地Contact Number信息

首先要在AndroidManifest.xml中设置允许读取联系人的权限:

1
<uses-permission android:name="android.permission.READ_CONTACTS" />

联系人列表布局文件contact_list.xml:

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
      android:orientation="vertical"
      android:layout_width="fill_parent"
      android:layout_height="fill_parent">
    <ListView android:id="@+id/contactList"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" />
</LinearLayout>

联系人列表项布局文件contact_entry.xml:

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
      android:layout_width="fill_parent"
      android:layout_height="fill_parent">
    <TextView android:id="@+id/contactName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <TextView android:id="@+id/contactNumber"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</LinearLayout>

MainActivity.java文件内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import android.app.Activity;
import android.database.Cursor;
import android.os.Bundle;
import android.provider.Contacts.People;
import android.widget.ListView;
import android.widget.SimpleCursorAdapter;

public class MainActivity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.contact_list);

        ListView contactList = (ListView)findViewById(R.id.contactList);

        Cursor contactsCursor = managedQuery(People.CONTENT_URI,
                null, null, null, null);
        startManagingCursor(contactsCursor);

        String[] columnsToMap = new String[] {People.NAME, People.NUMBER};
        int[] mapTo = new int[] {R.id.contactName, R.id.contactNumber};

        SimpleCursorAdapter mAdapter = new SimpleCursorAdapter(this,
                R.layout.contact_entry, contactsCursor,
                columnsToMap, mapTo);
        contactList.setAdapter(mAdapter);
    }
}

用Rails 2.3打造简单记账应用(12)

在这以前我们把tags作为Entry模型的一个字段来实现,这样做有很大的局限性,而且还有好多插件可以实现这个功能,现在我们就来把它替换用插件acts_as_taggable_on_steroids实现。

安装插件

1
2
3
script/plugin install git://github.com/jviney/acts_as_taggable_on_steroids.git
script/generate acts_as_taggable_migration
rake db:migrate

在Entry模型中添加一句代码:

1
2
class Entry < ActiveRecord::Base
  acts_as_taggable

还要将所有视图中原来的tags字段替换成tag_list字段。

1
2
# index.html.erb
<%=h entry.tag_list %>
1
2
# edit.html.erb
<%= f.text_field :tag_list %>
1
2
# new.html.erb
<%= f.text_field :tag_list %>

还要将Entry模型中的验证字段tags改成tag_list。做完这些插件也就安装完成了。

既然用了插件,那么原来的tags字段就不需要了,写个迁移任务把它处理掉吧!

1
script/generate migration remove_tags_from_entries

迁移代码如下:

1
2
3
4
5
6
7
8
9
class RemoveTagsFromEntries < ActiveRecord::Migration
  def self.up
    remove_column :entries, :tags
  end

  def self.down
    add_column :entries, :tags, :string
  end
end

代码下载:https://github.com/dohkoos/qianbao

用Rails 2.3打造简单记账应用(11)

当网站出现问题时,让它发送一份错误报告到你的邮箱是件挺不错的事,这样你就不必再担心不能及时发现问题了。

exception_notification插件的功能就是当你的Rails应用出错时,它会向指定的邮箱地址发送错误日志。

使用很简单,安装插件后配置一下邮件接受者就可以了。

1
script/plugin install git://github.com/rails/exception_notification.git

在config/environment.rb中的最后添加邮件接收者的地址:

1
ExceptionNotifier.exception_recipients = %w(yourname@example.com)

然后告诉exception_notification哪些控制器出错才发送日志。当然是全部啦:

1
2
class ApplicationController < ActionController::Base
  include ExceptionNotifiable

exception_notification采用ActionMailer发送邮件,所以使用的前提是确保ActionMailer可以正常发送邮件。还有就是exception_notification默认只在production环境下才会生效,如果想要在development下也生效,需要将config/environments/development.rb文件中的:

1
config.action_mailer.raise_delivery_errors = false

修改为

1
2
3
config.action_mailer.raise_delivery_errors = true
# Let exception_notification generate email notifications
config.action_controller.consider_all_requests_local = false

代码下载:https://github.com/dohkoos/qianbao

用Rails 2.3打造简单记账应用(10)

从2.2版本起Rails开始内置支持i18n,因此以后实现国际化/本地化就可以不再需要各种各样的插件了。

Rails默认的Locale文件夹是config/locales,假设你要支持中文和英文,那么你需要在这个文件夹下放置zh.yml和en.yml两个文件。

相应的入门教程网上有不少,我也就不多讲了。这里主要说一下如何在记账应用中实现可以让用户指定语言的i18n实现,即当用户选择English,那么界面就切换成英文界面,并且以后打开的页面也是以英文出现,反之亦如此。

首先在header区添加以下代码:

1
2
<%= link_to 'Chinese', :locale => 'zh' %>
<%= link_to 'English', :locale => 'en' %>

点击其中某个链接后,浏览器就会传递对应的参数zh或en到后台。

然后在app/controllers/application_controller.rb中添加以下代码:

1
2
3
4
5
6
before_filter :set_locale

protected
  def set_locale
    I18n.locale = params[:locale]
  end

不过这里有个问题就是url中必须得带着参数,不然的话用户的选择就会失效。要想使选择达到持续的效果,可以考虑把这些信息保存在session中,改进后的代码如下:

1
2
3
4
def set_locale
  I18n.locale = params[:locale] || session[:locale]
  session[:locale] = I18n.locale
end

现在这样就基本上差不多了。剩下的就是处理第一次访问时的语言,这个可以从request的ACCEPT_LANGUAGE参数中获取。最终实现的代码:

1
2
3
4
5
def set_locale
  accept_lang = request.env['HTTP_ACCEPT_LANGUAGE'].scan(/^[a-z]{2}/).first
  I18n.locale = params[:locale] || session[:locale] || accept_lang || 'zh'
  session[:locale] = I18n.locale
end

不过当用户选择中文后,在新建记录时会报以下错误:

1
2
3
4
5
6
7
8
can't convert Symbol into String

Extracted source (around line #8):

    6:   <p>
    7:     <%= t(:effective_date) %><br />
    8:     <%= f.date_select :effective_date %>
    9:   </p>

查找资料后发现可以通过在date_select中加入order来解决,不过日期下拉列表中的月份还有问题,出现的是一些随机数,不是正常月份。这个也可以通过添加month_names参数解决。

1
2
<%= t(:effective_date) %><br />
<%= f.date_select :effective_date, :order => [:year, :month, :day] %>

更好的办法是把它们放到对应的Locale文件里:

1
2
3
4
5
# en.yml
en:
  date:
    month_names: [~, January, February, ..., November, December]
    order: [:year, :month, :day]
1
2
3
4
5
# zh.yml
zh:
  date:
    month_names: [~, 一月, 二月, ..., 十一月, 十二月]
    order: [:year, :month, :day]

代码下载:https://github.com/dohkoos/qianbao

如何导入/导出Heroku中应用的数据

导入/导出Heroku上应用的数据需要安装Taps包:

1
gem install taps

导入数据到Heroku里:

1
heroku db:push

导出数据到本地:

1
heroku db:pull

其中导入/导出数据由config/database.yml配置设定。

Heroku还支持主机到主机的数据库传输,具体细节可以查看 http://docs.heroku.com/taps 文档。

如何联机调试Android应用

  1. 首先你需要一台开发手机,如果不是的话可以刷ROM;
  2. 在Settings -> Applications -> Development下激活USB debugging选项;
  3. 下载并安装USB驱动程序;
  4. 用USB数据线连接手机与电脑;
  5. 在AndroidManifest.xml中的<application>里添加android:debuggable="true";
  6. 然后你就可以像平常一样调试你的Android代码了。

用Rails 2.3打造简单记账应用(9)

没啥多说的,继续开始干活。

修改app/views/entries/index.html.erb为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<h1>Listing entries</h1>

<table>
  <thead>
    <tr>
      <th class="center">Date</th>
      <th class="center">Amount</th>
      <th class="center">Tags</th>
      <th class="center">Comment</th>
      <th class="center">Action</th>
    </tr>
  </thead>

  <tbody>
    <% @entries.each do |entry| %>
    <tr class="<%= cycle('odd', 'even') %>">
      <td class="center"><%=h entry.effective_date %></td>
      <td class="right"><%=h number_to_currency(entry.amount) %></td>
      <td><%=h entry.tags %></td>
      <td><%=h entry.comment %></td>
      <td class="center">
        <%= link_to 'Edit', edit_entry_path(entry) %>
        <%= link_to 'Destroy', entry, :confirm => 'Are you sure?', :method => :delete %>
      </td>
    </tr>
    <% end %>
  </tbody>
</table>

<%= link_to 'New entry', new_entry_path %>

修改app/views/layouts/application.html.erb文件的内容为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!DOCTYPE html PUBLIC
  "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
  <meta http-equiv="content-type" content="text/html; charset=utf-8" />
  <title>Qianbao: <%= controller.action_name %></title>
  <%= stylesheet_link_tag 'scaffold' %>
  <%= stylesheet_link_tag 'qianbao' %>
  <%= stylesheet_link_tag 'table' %>
</head>

<body>
  <div id="container">
    <%= render :partial => 'layouts/header' %>

    <div id="content">
      <p style="color: green"><%= flash[:notice] %></p>

      <%= yield %>
    </div>

    <%= render :partial => 'layouts/footer' %>
  </div>
</body>
</html>

新建app/views/layouts/_header.html.erb文件:

1
2
3
4
5
6
7
8
9
10
11
<div id="header">
  <%= link_to 'Home', root_path %>
  <% if logged_in? %>
    <%= link_to 'All entries', entries_path %>
    <strong>You are logged in as <%= current_user.login %></strong>
    <%= link_to 'Logout', logout_path %>
  <% else %>
    <%= link_to 'Login', login_path %>
    <%= link_to 'Sign Up', signup_path %>
  <% end %>
</div>

新建app/views/layouts/_footer.html.erb文件:

1
<div id="footer">© 2009</div>

在public/stylesheets/qianbao.css的末尾添加:

1
2
3
4
5
6
7
.center {
  text-align: center;
}

.right {
  text-align: right;
}

新建public/stylesheets/table.css,内容为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
table {
  border-collapse: collapse;
  text-align: left;
  width: 90%;
}

thead {
  height: 30px;
}

thead th {
  padding: 5px;
  background: #efefef;
}

tbody td {
  padding: 5px;
}

th, td {
  border: 1px solid #e6e6e6;
}

tr.odd {
  background: #fff;
}

tr.even {
  background: #f9f9f9;
}

代码下载:https://github.com/dohkoos/qianbao