早上在家看以前寫的 GAE 程式, 已經很久沒摸 Python 了有點生疏 (我是為了要用 GAE 服務才接觸 Python 的), 不過複習一下馬上就能恢復戰力. 參考去年十月底寫的這篇 :
# 如何在 GAE 佈署 jQuery EasyUI 專案
當時我的作法是採用自備框架資源的方式, 也就是自行下載 jQuery 與 EasyUI 檔案到 GAE 的專案目錄下, 但由於 GAE 對於免費帳戶有檔案數目限制, 而 EasyUI 又包含了一堆 UI 排版的圖檔與樣式檔, 上傳到 GAE 後就佔硬碟空間; 比較理想的方式是採用 CDN 供檔方式, 反正 GAE 專案本來就是要佈署到 Internet 上才有意義的. 因此我把 jqueryeasyui.htm 修改為 jqueryeasyui_1.htm 如下 :
{% extends "html5.htm" %}
{% block link %}
<link rel="stylesheet" href="http://www.jeasyui.com/easyui/themes/default/easyui.css">
<link rel="stylesheet" href="http://www.jeasyui.com/easyui/themes/icon.css">
{% endblock %}
{% block javascript %}
<script type="text/javascript" src="http://www.jeasyui.com/easyui/jquery.min.js"></script>
<script type="text/javascript" src="http://www.jeasyui.com/easyui/jquery.easyui.min.js"></script>
<script type="text/javascript" src="http://www.jeasyui.com/easyui/locale/easyui-lang-zh_TW.js"></script>
{% endblock %}
關於 Template 用法可參考這篇 :
# Google App Engine 初學(8) – 共用 Template (Layout)
然後就可以寫一個 easyui_1.htm 來測試 Datagrid 了 :
{% extends "jqueryeasyui_1.htm" %}
{% block body %}
<table class="easyui-datagrid" title="台股" style="width:600px;height:230px"
data-options="singleSelect:true,collapsible:true,rownumbers:true">
<thead>
<tr>
<th data-options="field:'name',width:80">股票名稱</th>
<th data-options="field:'id',width:80">股票代號</th>
<th data-options="field:'close',width:100,align:'right'">收盤價 (元)</th>
<th data-options="field:'volumn',width:100,align:'right'">成交量 (張)</th>
<th data-options="field:'meeting',align:'left'">股東會日期</th>
<th data-options="field:'election',width:80,align:'center'">董監改選</th>
<th data-options="field:'category',width:60,align:'center'">類股</th>
</tr>
</thead>
<tbody>
<tr>
<td>台積電</td>
<td>2330</td>
<td>123</td>
<td>4425119</td>
<td>2014-06-04</td>
<td>0</td>
<td>半導體</td>
</tr>
<tr>
<td>中華電</td>
<td>2412</td>
<td>96.4</td>
<td>5249</td>
<td>2014-06-15</td>
<td>0</td>
<td>通信</td>
</tr>
<tr>
<td>中碳</td>
<td>1723</td>
<td>192.5</td>
<td>918</td>
<td>2014-07-05</td>
<td>0</td>
<td>塑化</td>
</tr>
<tr>
<td>創見</td>
<td>2451</td>
<td>108</td>
<td>733</td>
<td>2014-06-30</td>
<td>0</td>
<td>模組</td>
</tr>
<tr>
<td>華擎</td>
<td>3515</td>
<td>118.5</td>
<td>175</td>
<td>2014-07-20</td>
<td>0</td>
<td>主機板</td>
</tr>
<tr>
<td>訊連</td>
<td>5203</td>
<td>97</td>
<td>235</td>
<td>2014-05-31</td>
<td>0</td>
<td>軟體</td>
</tr>
</tbody>
</table>
{% endblock %}
但是為了符合 MTV 設計要求, 我在專案目錄下面建了一個 templates 目錄, 把上面的 htm 模板 (Template) 檔案通通放到 templates 下, 這樣檔案結構比較不會凌亂, 如下圖所示 :
所以路由分派程式 main.py 中的 url 必須加上 templates 路徑 :
# -*- coding: utf-8 -*-
import os
from google.appengine.ext.webapp import template
import webapp2
class easyui_1(webapp2.RequestHandler):
def get(self):
url="templates/easyui_1.htm"
path=os.path.join(os.path.dirname(__file__), url)
content=template.render(path,{})
self.response.out.write(content)
class MainHandler(webapp2.RequestHandler):
def get(self):
url="templates/default.htm"
path=os.path.join(os.path.dirname(__file__), url)
content=template.render(path,{})
self.response.out.write(content)
app = webapp2.WSGIApplication([
('/', MainHandler),
('/easyui_1', easyui_1)
], debug=True)
這裡 templates 下還有一個 default.htm 是做為程式目錄列表的, 當存取網站根目錄 (即上面的 "/") 時, 會執行 MainHandler 類別, 抓取 templates 下的 default.htm 來渲染 (輸出). 其內容暫時如下, 當程式增加時就增加 li 項目即可 :
{% extends "jqueryeasyui_1.htm" %}
{% block style %}
body {font: 80% "Trebuchet MS", sans-serif; margin: 50px;}
{% endblock%}
{% block body %}
<b><p>EasyUI on GAE</p></b>
<ol>
<li>
<a href="easyui_1" target="_blank">easyui_1</a>
(Datagrid : 顯示股價資訊)
</li>
</ol>
{% endblock%}
這樣就大功告成了, 在 SDK 上執行 OK, 結果與之前第一篇文章一樣. 上傳到 GAE 時顯示不超過十個檔案, 使用 CDN 的好處就是可以節省硬碟容量, 檔案結構也很清爽.
接下來我想測試 EasyUI 主題布景的切換, 直覺是用一個下拉式選單來選取主題, 然後提交時傳一個參數給 GAE, 用來置換樣式表路徑中預設的 default 主題:
<link rel="stylesheet" href="http://www.jeasyui.com/easyui/themes/default/easyui.css">
這可以用 template 引擎來傳送參數, 在上面 main.py 程式中呼叫 render 方法時, 所傳入的第二參數為一個空串列 template.render(path,{}), 那是因為我們沒有變數要傳給模板檔案的緣故; 我們可以用 self.request.get() 方法擷取網頁中下拉式選單傳來的主題布景參數, 然後透過此串列變數傳給模板.
我把 jqueryeasyui_1.htm 模板加入變數 theme, 改成 jqueryeasyui_2.htm 如下
{% extends "html5.htm" %}
{% block link %}
<link rel="stylesheet" href="http://www.jeasyui.com/easyui/themes/{{theme}}/easyui.css">
<link rel="stylesheet" href="http://www.jeasyui.com/easyui/themes/icon.css">
{% endblock %}
{% block javascript %}
<script type="text/javascript" src="http://www.jeasyui.com/easyui/jquery.min.js"></script>
<script type="text/javascript" src="http://www.jeasyui.com/easyui/jquery.easyui.min.js"></script>
<script type="text/javascript" src="http://www.jeasyui.com/easyui/locale/easyui-lang-zh_TW.js"></script>
{% endblock %}
注意這裡樣式表路徑中, 用兩個大括弧括起來的 theme 就是從 main.py 程式傳來的模板變數, 而 main.py 則是從 url 處理中從前端網頁取得我們傳送之樣式參數. 而前端網頁則繼承 jqueryeasyui_2.htm, 加入 Combobox 選單, 改為 easyui_2.htm 如下 :
{% extends "jqueryeasyui_2.htm" %}
{% block body %}
<form id="theme_form" method="get" action="/easyui_2">
<select id="theme_sel" name="theme" class="easyui-combobox" panelHeight="auto">
<option value="default">主題布景</option>
<option value="gray">gray</option>
<option value="black">black</option>
<option value="bootstrap">bootstrap</option>
<option value="metro">metro</option>
<option value="metro-blue">metro-blue</option>
<option value="metro-gray">metro-gray</option>
<option value="metro-green">metro-green</option>
<option value="metro-orange">metro-orange</option>
<option value="metro-red">metro-red</option>
<option value="ui-cupertino">ui-cupertino</option>
<option value="ui-dark-hive">ui-dark-hive</option>
<option value="ui-pepper-grinder">ui-pepper-grinder</option>
<option value="ui-sunny">ui-sunny</option>
</select><br>
</form><br>
<table class="easyui-datagrid" title="台股" style="width:600px;height:230px"
data-options="singleSelect:true,collapsible:true,rownumbers:true">
<thead>
<tr>
<th data-options="field:'name',width:80">股票名稱</th>
<th data-options="field:'id',width:80">股票代號</th>
<th data-options="field:'close',width:100,align:'right'">收盤價 (元)</th>
<th data-options="field:'volumn',width:100,align:'right'">成交量 (張)</th>
<th data-options="field:'meeting',align:'left'">股東會日期</th>
<th data-options="field:'election',width:80,align:'center'">董監改選</th>
<th data-options="field:'category',width:60,align:'center'">類股</th>
</tr>
</thead>
<tbody>
<tr>
<td>台積電</td>
<td>2330</td>
<td>123</td>
<td>4425119</td>
<td>2014-06-04</td>
<td>0</td>
<td>半導體</td>
</tr>
<tr>
<td>中華電</td>
<td>2412</td>
<td>96.4</td>
<td>5249</td>
<td>2014-06-15</td>
<td>0</td>
<td>通信</td>
</tr>
<tr>
<td>中碳</td>
<td>1723</td>
<td>192.5</td>
<td>918</td>
<td>2014-07-05</td>
<td>0</td>
<td>塑化</td>
</tr>
<tr>
<td>創見</td>
<td>2451</td>
<td>108</td>
<td>733</td>
<td>2014-06-30</td>
<td>0</td>
<td>模組</td>
</tr>
<tr>
<td>華擎</td>
<td>3515</td>
<td>118.5</td>
<td>175</td>
<td>2014-07-20</td>
<td>0</td>
<td>主機板</td>
</tr>
<tr>
<td>訊連</td>
<td>5203</td>
<td>97</td>
<td>235</td>
<td>2014-05-31</td>
<td>0</td>
<td>軟體</td>
</tr>
</tbody>
</table>
<script>
$(document).ready(function(){
$("#theme_sel").combobox({
onSelect:function(rec){
$("#theme_form").submit();
}
});
});
</script>
{% endblock %}
這裡下拉式選單列舉了 EasyUI 的各種主題, 注意 select 元素須給予 name 屬性, 當有選取動作時呼叫 submit() 方法將表單傳送給自己 (/easyui_2). 其次, 為了簡單起見, 表單用 get 方法提交, 因為若使用 post, 則 main.py 裡面也要有處理 post 的方法 (否則會出現 405 Method Not Allowed : The method POST is not allowed for this resource. 錯誤). 以下是修改路徑處理之 main.py :
# -*- coding: utf-8 -*-
import os
from google.appengine.ext.webapp import template
import webapp2
class easyui_1(webapp2.RequestHandler):
def get(self):
url="templates/easyui_1.htm"
path=os.path.join(os.path.dirname(__file__), url)
content=template.render(path,{})
self.response.out.write(content)
class easyui_2(webapp2.RequestHandler):
def get(self):
theme=self.request.get('theme',default_value='default')
url="templates/easyui_2.htm"
path=os.path.join(os.path.dirname(__file__), url)
content=template.render(path,{'theme':theme})
self.response.out.write(content)
class MainHandler(webapp2.RequestHandler):
def get(self):
url="templates/default.htm"
path=os.path.join(os.path.dirname(__file__), url)
content=template.render(path,{})
self.response.out.write(content)
app = webapp2.WSGIApplication([
('/', MainHandler),
('/easyui_1', easyui_1),
('/easyui_2', easyui_2)
], debug=True)
此處我們用 self.request.get() 取得前端傳來的 theme 參數 (第一次存取網頁時不會傳參數, 因此用 default_value 設置預設為 default), 然後在呼叫 render() 渲染網頁時, 將此 theme 變數傳給 easyui_2.htm 模板, 從而代換了 jqueryeasyui_2.htm 中的決定主題布景之樣式表路徑.
不過實際操作會發現, 這種每次選取就提交表單的做法不好, 因為這樣會重新載入頁面, 導致前端會短暫看到 UI 尚未套用前的原始面貌. 理想上應該不須重載頁面, 直接利用 jQuery 去操作 DOM 裡的 link 元素即可, 因此我修改模板, 在 link 元素中加入 id 屬性方便 jQuery 操作, 同時恢復預設之 default 主題, 改為如下的 jqueryeasyui_3.htm 模板 :
{% extends "html5.htm" %}
{% block link %}
<link id="theme" rel="stylesheet" href="http://www.jeasyui.com/easyui/themes/default/easyui.css">
<link rel="stylesheet" href="http://www.jeasyui.com/easyui/themes/icon.css">
{% endblock %}
{% block javascript %}
<script type="text/javascript" src="http://www.jeasyui.com/easyui/jquery.min.js"></script>
<script type="text/javascript" src="http://www.jeasyui.com/easyui/jquery.easyui.min.js"></script>
<script type="text/javascript" src="http://www.jeasyui.com/easyui/locale/easyui-lang-zh_TW.js"></script>
{% endblock %}
{% extends "jqueryeasyui_3.htm" %}
{% block body %}
<form id="theme_form">
<select id="theme_sel" name="theme" class="easyui-combobox" panelHeight="auto">
<option value="default">主題布景</option>
<option value="gray">gray</option>
<option value="black">black</option>
<option value="bootstrap">bootstrap</option>
<option value="metro">metro</option>
<option value="metro-blue">metro-blue</option>
<option value="metro-gray">metro-gray</option>
<option value="metro-green">metro-green</option>
<option value="metro-orange">metro-orange</option>
<option value="metro-red">metro-red</option>
<option value="ui-cupertino">ui-cupertino</option>
<option value="ui-dark-hive">ui-dark-hive</option>
<option value="ui-pepper-grinder">ui-pepper-grinder</option>
<option value="ui-sunny">ui-sunny</option>
</select><br>
</form><br>
<table class="easyui-datagrid" title="台股" style="width:600px;height:230px"
data-options="singleSelect:true,collapsible:true,rownumbers:true">
<thead>
<tr>
<th data-options="field:'name',width:80">股票名稱</th>
<th data-options="field:'id',width:80">股票代號</th>
<th data-options="field:'close',width:100,align:'right'">收盤價 (元)</th>
<th data-options="field:'volumn',width:100,align:'right'">成交量 (張)</th>
<th data-options="field:'meeting',align:'left'">股東會日期</th>
<th data-options="field:'election',width:80,align:'center'">董監改選</th>
<th data-options="field:'category',width:60,align:'center'">類股</th>
</tr>
</thead>
<tbody>
<tr>
<td>台積電</td>
<td>2330</td>
<td>123</td>
<td>4425119</td>
<td>2014-06-04</td>
<td>0</td>
<td>半導體</td>
</tr>
<tr>
<td>中華電</td>
<td>2412</td>
<td>96.4</td>
<td>5249</td>
<td>2014-06-15</td>
<td>0</td>
<td>通信</td>
</tr>
<tr>
<td>中碳</td>
<td>1723</td>
<td>192.5</td>
<td>918</td>
<td>2014-07-05</td>
<td>0</td>
<td>塑化</td>
</tr>
<tr>
<td>創見</td>
<td>2451</td>
<td>108</td>
<td>733</td>
<td>2014-06-30</td>
<td>0</td>
<td>模組</td>
</tr>
<tr>
<td>華擎</td>
<td>3515</td>
<td>118.5</td>
<td>175</td>
<td>2014-07-20</td>
<td>0</td>
<td>主機板</td>
</tr>
<tr>
<td>訊連</td>
<td>5203</td>
<td>97</td>
<td>235</td>
<td>2014-05-31</td>
<td>0</td>
<td>軟體</td>
</tr>
</tbody>
</table>
<script>
$(document).ready(function(){
$("#theme_sel").combobox({
onSelect:function(rec){
var css="http://www.jeasyui.com/easyui/themes/" + rec.value + "/easyui.css";
$("#theme").attr("href", css);
}
});
});
</script>
{% endblock %}
這裡重點是在 onSelect() 方法中, 透過傳回值的 value 屬性取得所選取之值, 然後重組主題樣式的路徑, 再用 jQuery 的 attr() 方法將 href 屬性更換掉. 當然控制程式 main.py 也要添加 easyui_3 的路徑處理器, 但其實只要複製 easyui_1 的過來改即可, 如下所示 :
# -*- coding: utf-8 -*-
import os
from google.appengine.ext.webapp import template
import webapp2
class easyui_1(webapp2.RequestHandler):
def get(self):
url="templates/easyui_1.htm"
path=os.path.join(os.path.dirname(__file__), url)
content=template.render(path,{})
self.response.out.write(content)
class easyui_2(webapp2.RequestHandler):
def get(self):
theme=self.request.get('theme',default_value='default')
url="templates/easyui_2.htm"
path=os.path.join(os.path.dirname(__file__), url)
content=template.render(path,{'theme':theme})
self.response.out.write(content)
class easyui_3(webapp2.RequestHandler):
def get(self):
url="templates/easyui_3.htm"
path=os.path.join(os.path.dirname(__file__), url)
content=template.render(path,{})
self.response.out.write(content)
class MainHandler(webapp2.RequestHandler):
def get(self):
url="templates/default.htm"
path=os.path.join(os.path.dirname(__file__), url)
content=template.render(path,{})
self.response.out.write(content)
app = webapp2.WSGIApplication([
('/', MainHandler),
('/easyui_1', easyui_1),
('/easyui_2', easyui_2),
('/easyui_3', easyui_3)
], debug=True)
這個做法比較好, 切換主題時完全不會閃動, 可說是無縫變換, 只有在第一次載入頁面時可能會看到尚未化妝完畢的原始頁面. 後續的測試將以此作法所使用的模板 jqueryeasyui_3.htm 為標準, 故複製一份為 jqueryeasyui.htm 做後續繼承之用.
以上網頁可以在下列網址看到實際效果 :
# http://jqueryeasyui.appspot.com/
OK, 今天先做到這邊, 今天是休假耶, 我卻寫了一整天的程式. 昨晚睡覺前菁菁問我說, 今天我休假可不可以先煮菜, 放學後回來洗完澡吃完趕去補習班. 所以我三點半就開始準備, 趁四點菁菁回來前煮了一小鍋豬肉丼, 她吃第一口就說讚讚讚, 這就是讓我愛上烹飪的原因啦.
以上範例程式碼可在下列網址下載 :
# 下載原始碼