2016年2月9日 星期二

如何在 GAE 上佈署 jQuery EasyUI 專案 (九) : EasyUI CMS on GAE 之 4

做完標頭連結後, 打鐵趁熱, 接著實作版面左方的導覽列的管理. 以下測試是在前一篇的基礎上繼續添加功能, 參考 :

如何在 GAE 上佈署 jQuery EasyUI 專案 (七) : EasyUI CMS on GAE 之 3

此文實作了 GAE 上完整的 CRUD 操作, 所以套用此模型應該可以快速搞定導覽列的部分. 以前實作 PHP 版 CMS 時, 我把導覽區塊跟導覽連結管理放在一個頁籤中顯示與管理, 雖然這兩個資料表有連動, 但總覺得有點彆扭. 在 GAE 版我想把它們拆開到兩個頁籤去分開管理.

首先在 model.py 中新增導覽區塊的資料表模型 Navblocks 如下 :

class Navblocks(db.Model):
    name=db.StringProperty()
    title=db.StringProperty()
    sequence=db.IntegerProperty()
    display=db.BooleanProperty()

class Navlinks(db.Model):
    name=db.StringProperty()
    title=db.StringProperty()
    url=db.StringProperty()
    target=db.StringProperty()
    sequence=db.IntegerProperty()
    block_name=db.StringProperty()
    hint=db.StringProperty()

navblock=Navblocks(key_name="main",name="main",title=u"主功能",
    sequence=0,display=True)
navblock.put()

navlink=Navlinks(key_name="home",name="home",title=u"首頁",
    url="javascript:gohome()",target="_self",sequence=0,block_name="main",
    hint=u"首頁")
navlink.put()
navlink=Navlinks(key_name="logout",name="logout",title=u"登出",
    url="javascript:logout()",target="_self",sequence=0,block_name="main",
    hint=u"登出")
navlink.put()

資料表 Navblocks 裡的 name 欄位用來與 key_name 同步作為主鍵, display 則用來控制導覽區塊是否要顯示, 預設先建立一個名為 main 的主功能導覽區塊. 而 Navlinks 資料表則是從上一篇文章的 Headerlinks 複製過來修改的, 主要是新增了 block_name 欄位來表示此連結屬於哪一個導覽區塊, 預設先建立 home 與 logout 兩個資料實體.

有了這兩張資料表後, 就可以在主要網頁版面中以程式控制左方導覽列, 首先在 main.py 中增加 main_5 的路徑處理類別 :

class main_5(BaseHandler):
    def get(self):
        #save visitor info
        ip=self.request.remote_addr
        user_agent=self.request.headers.get("User-Agent")
        visitor=m.Visitors()
        visitor.ip=ip
        visitor.visit_time=datetime.datetime.now() + timedelta(hours=+8)
        visitor.user_agent=user_agent
        visitor.ip_list=list(ip)
        visitor.user_agent_list=list(user_agent)
        visitor.put()
        #check login session
        info={}  #for storing parameters
        account=self.session.get('account')
        if account: #already logged in
            info["account"]=account
            s=m.Settings.get_by_key_name("settings")
            info["site_title"]=s.site_title
            theme=self.session.get('theme')
            info["theme"]=theme
            #create param: greeting
            today=datetime.date.today()
            week=[u"一",u"二",u"三",u"四",u"五",u"六",u"日"]
            info["greeting"]=u"您好! " + account + u", 今天是 " + \
                str(today.year) +  u" 年 " + str(today.month) + u" 月 " + \
                str(today.day) + u" 日 星期" + week[today.weekday()]
            #create param: themes
            theme_list=[]
            themes=m.Themes.all()
            for t in themes:
                theme_list.append(t.theme)
            info["themes"]=theme_list
            #create param: headerlinks
            headerlinks=m.Headerlinks.all()
            headerlinks.order("-sequence")  #sort by sequence (reverse)
            link_list=[]  #for storing headerlinks objects
            for h in headerlinks:
                link={}
                link["title"]=h.title
                link["url"]=h.url
                link["target"]=h.target
                link["sequence"]=h.sequence
                link["hint"]=h.hint
                link_list.append(link)
            info["headerlinks"]=link_list
            #create param: navblocks & navlinks
            navblocks=m.Navblocks.all()
            navblocks.filter("display =",True)
            navblocks.order("sequence")  #sort by sequence            
            navblock_list=[]  #for storing navblocks objects
            for nb in navblocks:
                navblock={}
                navblock["title"]=nb.title  #store block title
                #query nvavlinks belongs to this block
                query=m.Navlinks.all()
                navlinks=query.filter("block_name =",nb.name)
                navlinks.order("sequence")
                navlink_list=[]  #for storing navblinks objects 
                for nl in navlinks:
                    navlink={}
                    navlink["title"]=nl.title
                    navlink["url"]=nl.url
                    navlink["target"]=nl.target
                    navlink["hint"]=nl.hint
                    navlink_list.append(navlink) #store this link
                navblock["navlinks"]=navlink_list #store block links 
                navblock_list.append(navblock)  #store this block
            info["navblocks"]=navblock_list
            url="templates/main_5.htm"
        else:  #not logged in
            s=m.Settings.get_by_key_name("settings")
            info["site_title"]=s.site_title
            info["site_theme"]=s.site_theme
            url="templates/login.htm"
        path=os.path.join(os.path.dirname(__file__), url)
        content=template.render(path,{'info':info})
        self.response.out.write(content)

這是從 main_4 複製過來修改的, 藍色部分就是增加的部分, 先從 Navblocks 讀取 display 欄位為 True 的全部實體, 並建立一個 navblock_list 串列來儲存要傳遞給網頁模板的參數, 然後逐一拜訪每一個 Navblocks 實體, 在 for 迴圈中首先設立一個 navblock 字典物件來記錄這個區塊的 title 與所擁有的連結. 首先是將每一個瀏覽區塊的 title 屬性值紀錄在 navblock 字典的 title 屬性裡, 然後去查詢 Navlinks 資料表, 把 block_name 欄位值為目前區塊名稱的資料實體過濾出來, 並設立一個 navlink_list 串列來記錄屬於此區塊之所有連結. 接著以第二層 for 迴圈拜訪過濾後的查詢物件, 並設立一個 navlink 字典物件來記錄屬於這個區塊的所有連結. 每一個區塊的連結組成的串列會被記錄在 navblock 字典的 navlinks 屬性中; 而所有的區塊串列則記錄在 info 字典的 navblocks 屬性裡, 當作參數傳給此類別所渲染的 main_5.htm 網頁.

以預設的資料儲存設定來說, 上面這個程序所產生的 navblock_list 串列的結構如下 :

[{"title":"主功能",
   "navlinks":[{"title":"首頁",
                        "url":"javascript:gohome()",
                        "target":"_self",
                        "hint":"首頁"},
                      {"title":"登出",
                        "url":"javascript:logout()",
                        "target":"_self",
                        "hint":"登出"}
                       ]
   }]

這個串列會被設為 info 字典的 navblocks 屬性傳遞給 main_5.htm, 此網頁模板是從 main_4.htm 複製修改而來, 內容如下 :

{% extends "jqueryeasyui.htm" %}
{% block style %}
    a {text-decoration:none;}
    a:hover {text-decoration:underline;background-color:yellow;}
    #west {width:150px;}
    #west-inner {border-top:0px;border-right:0px;border-bottom:0px;}
    .nav {padding:5px;}
    .tab {padding:10px;}
    #north {height:55px;overflow:hidden;}
    #north-table {width:100%;border-spacing:0px}
    #north-left {text-align:left;padding:5px;}
    #north-right {text-align:right;padding:5px;}
{% endblock%}
{% block body %}
  <div id="north" title="{{ info.site_title }}" data-options="region:'north',border:false,collapsible:true,tools:'#tools'">
    <form id="header-form" method="post">
      <table id="north-table">
        <tr>
          <td id="north-left" style="vertical-align:middle">
            {{ info.greeting }}
          </td>
          <td id="north-right" style="vertical-align:middle">
            <span id="header_links">
{% for h in info.headerlinks %}
              <a href="{{h.url}}" target="{{h.target}}" title="{{h.hint}}">{{h.title}}</a>.
{% endfor %}
            </span>
            <select id="theme_sel" name="theme" class="easyui-combobox" style="width:120px;height:18px" panelHeight="auto">
              <option value="default">主題布景</option>
{% for t in info.themes %}
              <option value="{{t}}"{% ifequal t info.theme %} selected{% endifequal %}>{{t}}</option>
{% endfor %}
            </select>
          </td>
        </tr>
      </table>
    </form>
  </div>
  <div id="tools">
    <a href="javascript:logout()" class="icon-remove" title="登出"></a>
  </div>
  <div title="導覽" data-options="region:'west',border:true" id="west">
    <div class="easyui-accordion" id="west-inner">
{% for block in info.navblocks %}
      <div title="{{ block.title }}" class="nav">
    {% for link in block.navlinks %}
        .<a href="{{ link.url }}" target="{{ link.target }}" title="{{ link.hint }}">{{ link.title }}</a><br>
    {% endfor %}
      </div>
{% endfor %}
    </div>
  </div>
  <div data-options="region:'center',border:false,href:'/systabs'" id="center">
  </div>
  <script>
    $("body").attr("class", "easyui-layout");
    $(document).ready(function(){    
      //var css="http://www.jeasyui.com/easyui/themes/{{ info.theme }}/easyui.css";
      var css="/static/easyui/themes/{{ info.theme }}/easyui.css";
      $("#theme").attr("href", css);
      $("#theme_sel").combobox({
        onSelect:function(rec){
          //var css="http://www.jeasyui.com/easyui/themes/" + rec.value + "/easyui.css";
          var css="/static/easyui/themes/" + rec.value + "/easyui.css";
          $("#theme").attr("href", css);
          $.get("/change_theme",{theme:rec.value});
          }
        });
      });
    <!-- 系統的載入與登出函式 -->
    function gohome(){
      $(function(){
        var p=$("body").layout("panel","center");
        p.panel({href:"/systabs"});
        });
      }
    function logout(){
      $(function(){
        $.messager.confirm("確認","確定要登出系統嗎?",function(btn){
          if (btn) {window.location.href="/logout";}
          });
        });
      }
  </script>
{% endblock%}

上面藍色部份就是修改的地方, 從 info 字典的 navblocks 屬性取得導覽區塊串列後以 for 迴圈來遍歷, 然後從每一個區塊的 navlinks 屬性取出此區塊所有連結之串列, 以此作為第二層迴圈來產生超連結, 這樣便完成了左方導覽區的顯示了.

接下來是導覽區塊的管理, 我先在 model.py 增加一個 Systabs 模型的頁籤實體 list_navblocks :

systab=Systabs(key_name="list_navblocks",tab_name="list_navblocks",
    tab_label=u"導覽區塊",tab_link="/list_navblocks",tab_order=4,tab_admin=True)
systab.put()

此頁籤會從路徑 list_navblocks 載入資料, 然後在 main.py 中新增此路徑 list_navblocks 的處理類別 :

class list_navblocks(webapp2.RequestHandler):
    def get(self):
        url="templates/list_navblocks.htm"
        path=os.path.join(os.path.dirname(__file__), url)
        content=template.render(path,{})
        self.response.out.write(content)

它所渲染的網頁 list_navblocks.htm 內容如下, 係參考上一篇文章中 list_headerlinks.htm 修改而來 :

{% extends "jqueryeasyui.htm" %}
{% block style %}
  body {font: 80% "Trebuchet MS", sans-serif; margin: 50px;}
{% endblock%}
{% block body %}
  <!--導覽區塊 Navblocks 列表-->
  <table id="sys_nav_blocks" title="導覽區塊列表" style="width:auto" data-options="tools:'#nav_blocks_tools'"></table>
  <div id="nav_blocks_tools">  
    <a href="#" id="add_nav_block" class="icon-add" title="新增"></a>
    <a href="#" id="edit_nav_block" class="icon-edit" title="編輯"></a>
    <a href="#" id="remove_nav_block" class="icon-remove" title="刪除"></a>
    <a href="#" id="reload_nav_blocks" class="icon-reload" title="重新載入"></a>
  </div>
  <!--新增&編輯 Navblocks 表單對話框-->
  <div id="nav_block_dialog" class="easyui-dialog" title="新增區塊" style="width:360px;height:220px;" data-options="closed:'true',buttons:'#nav_block_buttons'">
    <form id="nav_block_form" method="post" style="padding:10px">
      <div style="margin:5px">
        <label style="width:60px;display:inline-block;">名稱 : </label>
        <input name="name" id="block_name" type="text" class="easyui-textbox" data-options="missingMessage:'此欄位必須為英數字組合',required:true,readonly:false" style="width:230px">
      </div>
      <div style="margin:5px">
        <label style="width:60px;display:inline-block;">標題 : </label>
        <input name="title" type="text" class="easyui-textbox" data-options="missingMessage:'此欄位為必填',required:true" style="width:230px">
      </div>
      <div style="margin:5px">
        <label style="width:60px;display:inline-block;">順序 : </label>
        <input id="nav_block_sequence" data-options="missingMessage:'此欄位為必填',required:true" name="sequence">
      </div>
      <div style="margin:5px">
        <label style="width:60px;display:inline-block;">顯示 : </label>
        <select id="display" name="display" class="easyui-combobox" data-options="panelHeight:'auto'">
          <option value="True">True</option>
          <option value="False">False</option>
        </select>
        <input type="hidden" id="nav_block_op" value="">
      </div>
    </form>
  </div>
  <div id="nav_block_buttons" style="padding-right:15px;">
    <a href="#" id="clear_nav_block" class="easyui-linkbutton" iconCls="icon-clear" style="width:90px">重設</a>
    <a href="#" id="save_nav_block" class="easyui-linkbutton" iconCls="icon-ok" style="width:90px">確定</a>
  </div>
  <script>
    $(function(){
      //標頭連結 sys_nav_blocks
      $('#sys_nav_blocks').datagrid({
        columns:[[
          {field:'name',title:'name',sortable:true},
          {field:'title',title:'title',sortable:true},
          {field:'sequence',title:'sequence',align:'center',sortable:true},
          {field:'display',title:'display',sortable:true}
          ]],
        url:"/get_navblocks",
        method:"post",
        singleSelect:true,
        rownumbers:true
        });
      $("#nav_block_sequence").numberspinner({
        min:0,
        max:99,
        increment:1,
        value:"0",
        required:true,
        missingMessage:'此欄位為必填'
        });
      $("#clear_nav_block").bind("click",function(){
        $("#nav_block_form")[0].reset();
        });
      $("#save_nav_block").bind("click",function(){      
        var op=$("#nav_block_op").val();  //判斷是新增或修改
        if (op=="update") {
          var row=$("#sys_nav_blocks").datagrid("getSelected");
          if (row.name=="home" || row.name=="logout") {
            $("#nav_block_dialog").dialog('close');
            $.messager.alert("訊息","此為系統導覽區塊不可更改!","error");
            return;
            }
          var url="/update_navblock";
          }
        else {var url="/add_navblock";}
        $("#nav_block_form").form("submit",{
          url:url,
          method:"post",
          success:function(data){
            var data=eval('(' + data + ')');
            $("#nav_block_dialog").dialog("close");
            if (data.status==="success") {
              $("#sys_nav_blocks").datagrid("reload");
              }
            else {
              $.messager.alert("訊息",data.reason,"error");  
              }        
            }
          });
        });
      $("#add_nav_block").bind("click",function(){
        $("#nav_block_dialog").dialog("open").dialog("setTitle","新增區塊");
        $("#block_name").textbox({"readonly":false}); //for adding
        $("#nav_block_form").form("clear");
        $("#nav_block_op").val("add");
        $('#display').combobox('setValue','True');
        });
      $("#edit_nav_block").bind("click",function(){
        var row=$("#sys_nav_blocks").datagrid("getSelected");
        if (row) {
          $("#nav_block_dialog").dialog("open").dialog("setTitle","編輯區塊");
          $("#nav_block_form").form("load",row);
          $("#block_name").textbox({"readonly":true});
          $("#nav_block_op").val("update");
          }
        else {$.messager.alert("訊息","請先選取要編輯的區塊!","error");}
        });
      $("#remove_nav_block").bind("click",function(){
        var row=$("#sys_nav_blocks").datagrid("getSelected");
        if (row) {
          var params={name:row.name};
          var msg="確定要刪除這個區塊嗎?<br>這樣會刪除此區塊全部連結."
          $.messager.confirm("確認",msg,function(btn){
            if (btn){
              if (row.name=="main") {
                $.messager.alert("訊息","此為系統導覽區塊不可刪除!","error");
                return;
                }
              var callback=function(data){
                if (data.status==="success"){
                  $("#sys_nav_blocks").datagrid("reload");
                  }
                else {$.messager.alert("訊息",data.reason,"error");}          
                };              
              $.post("/remove_navblock",params,callback,"json");
              }
            })
          }
        });
      $("#reload_nav_blocks").bind("click",function(){
        $("#sys_nav_blocks").datagrid("load");
        });
      });
  </script>
{% endblock%}

這裡刪除區塊時, 彈出訊息加上了會同時刪除所屬連結的警語. CRUD 操作部分, 此網頁模板會從 /get_navblocks 路徑取得 json 資料來源, 其路徑處理類別如下, 是從上一篇文章的 /get_headerlinks  複製來修改的 :

class get_navblocks(webapp2.RequestHandler):
    def post(self):
        page=self.request.get("page")
        rows=self.request.get("rows")
        sort=self.request.get("sort")
        order=self.request.get("order")
        if len(page):
            page=int(page)
        else:
            page=1
        if len(rows):
            rows=int(rows)
        else:
            rows=10
        if not len(sort):
            sort="sequence"
        if not len(order):
            order="asc"
        query=m.Navblocks.gql("ORDER BY %s %s" % (sort, order))
        count=query.count()
        blocks=query.fetch(rows, (page-1)*rows)
        rows=[]  #for storing objects
        for b in blocks:
            block={"name":b.name,
                   "title":b.title,
                   "sequence":b.sequence,
                   "display":b.display}
            rows.append(block)
        obj={"total":count,"rows":rows}  #Easyui datagrid json format
        self.response.headers["Content-Type"]="application/json"
        self.response.out.write(json.dumps(obj))

很簡單, 就是以分頁方式查詢 Navblocks 資料實體, 然後將其四個欄位資料包裝成 json 傳回前端而已. 增修刪部分由 /add_navblock, /update_navblock, /remove_navblock 路徑負責, 其類別如下 :

class add_navblock(webapp2.RequestHandler):
    def post(self):
        name=self.request.get("name")
        display=self.request.get("display")
        if display=="True":
            display=True
        else:
            display=False
        #check entity if exist
        block=m.Navblocks.get_by_key_name(name)
        if block: #already exist
            result='{"status":"failure","reason":"導覽區塊已存在!"}'
        else:  #new block
            navblock=m.Navblocks(key_name=name,name=name,
                title=self.request.get("title"),
                sequence=int(self.request.get("sequence")),
                display=display
                )
            navblock.put()
            result='{"status":"success"}'      
        self.response.out.write(result)

class update_navblock(webapp2.RequestHandler):
    def post(self):
        name=self.request.get("name")
        display=self.request.get("display")
        if display=="True":
            display=True
        else:
            display=False
        #get entity from store
        block=m.Navblocks.get_by_key_name(name)
        if block: #entity exist
            block.title=self.request.get("title")
            block.sequence=int(self.request.get("sequence"))
            block.display=display
            block.put()
            result='{"status":"success"}'
        else:  #block not existed
            result='{"status":"failure","reason":"導覽區塊不存在!"}'      
        self.response.out.write(result)

class remove_navblock(webapp2.RequestHandler):
    def post(self):
        name=self.request.get("name")
        #get entity from store
        block=m.Navblocks.get_by_key_name(name)
        if block: #entity exist
            db.delete(block)
            #delete navlinks belong to this navblock
            query=m.Navlinks.all()
            links=query.filter("block_name",name) 
            for link in links:  
                db.delete(link)
            result='{"status":"success"}'
        else:  #block not existed
            result='{"status":"failure","reason":"導覽區塊不存在!"}'      
        self.response.out.write(result)

這都是從上一篇關於標頭連結 headerlinks 的文章中複製而來修改的, 比較不同的有兩處, 一是在 /add_navblock 部分, 因為前端傳來的 display 是 "True" 與 "False" 字串, 因此使用 if 判斷將其轉為 boolean 值. 二是在 /remove_navblock 部分, 除了刪除此區塊外, 還要同時刪除屬於此區塊的 Navlinks 資料實體.

而 Navlinks 管理部分, 同樣先在 model.py 新增一個 list_navlinks 實體 :

systab=Systabs(key_name="list_navlinks",tab_name="list_navlinks",
    tab_label=u"導覽連結",tab_link="/list_navlinks",tab_order=5,tab_admin=True)
systab.put()

此頁籤會從路徑 list_navlinks 載入資料, 然後在 main.py 中新增此路徑 list_navlinks 的處理類別 :

class list_navlinks(webapp2.RequestHandler):
    def get(self):
        blocks=m.Navblocks.all()
        info=[]
        for b in blocks:
            block={}
            block["block_name"]=b.name
            block["block_title"]=b.title
            #block["block_title"]=b.title + " (" + b.name + ")"
            info.append(block)
        url="templates/list_navlinks.htm"
        path=os.path.join(os.path.dirname(__file__), url)
        content=template.render(path,{"info":info})
        self.response.out.write(content)

它所渲染的網頁 list_navlinks.htm 內容如下, 係參考上一篇文章中的 list_headerlinks.htm 修改而來 :

{% extends "jqueryeasyui.htm" %}
{% block style %}
  body {font: 80% "Trebuchet MS", sans-serif; margin: 50px;}
{% endblock%}
{% block body %}
  <!--導覽連結 sys_nav_links 列表-->
  <table id="sys_nav_links" title="導覽連結列表" style="width:auto" data-options="tools:'#nav_links_tools'"></table>
  <div id="nav_links_tools">  
    <a href="#" id="add_nav_link" class="icon-add" title="新增"></a>
    <a href="#" id="edit_nav_link" class="icon-edit" title="編輯"></a>
    <a href="#" id="remove_nav_link" class="icon-remove" title="刪除"></a>
    <a href="#" id="reload_nav_links" class="icon-reload" title="重新載入"></a>
  </div>
  <!--新增&編輯 Navlinks 表單對話框-->
  <div id="nav_link_dialog" class="easyui-dialog" title="新增連結" style="width:360px;height:290px;" data-options="closed:'true',buttons:'#nav_link_buttons'">
    <form id="nav_link_form" method="post" style="padding:10px">
      <div style="margin:5px">
        <label style="width:60px;display:inline-block;">名稱 : </label>
        <input name="name" id="link_name" type="text" class="easyui-textbox" data-options="missingMessage:'此欄位必須為英數字組合',required:true,readonly:false" style="width:230px">
      </div>
      <div style="margin:5px">
        <label style="width:60px;display:inline-block;">標題 : </label>
        <input name="title" type="text" class="easyui-textbox" data-options="missingMessage:'此欄位為必填',required:true"  style="width:230px">
      </div>
      <div style="margin:5px">
        <label style="width:60px;display:inline-block;">網址 : </label>
        <input name="url" type="text" class="easyui-textbox" data-options="missingMessage:'此欄位為必填',required:true"  style="width:230px">
      </div>
      <div style="margin:5px">
        <label style="width:60px;display:inline-block;">目標 : </label>
        <select id="target" name="target" class="easyui-combobox" data-options="panelHeight:'auto'">
          <option value="_self">_self</option>
          <option value="_blank">_blank</option>
        </select>
      </div>
      <div style="margin:5px">
        <label style="width:60px;display:inline-block;">順序 : </label>
        <input id="nav_link_sequence" name="sequence">
      </div>
      <div style="margin:5px">
        <label style="width:60px;display:inline-block;">區塊 : </label>
        <select name="block_name" class="easyui-combobox" data-options="missingMessage:'此欄位為必填',required:true,panelHeight:'auto'">
{% for b in info %}
          <option value="{{ b.block_name }}">{{ b.block_title }}</option>
{% endfor %}
        </select>
      </div>
      <div style="margin:5px">
        <label style="width:60px;display:inline-block;">提示 : </label>
        <input name="hint" type="text" class="easyui-textbox" style="width:230px">
        <input type="hidden" id="nav_link_op" value="">
      </div>
    </form>
  </div>
  <div id="nav_link_buttons" style="padding-right:15px;">
    <a href="#" id="clear_nav_link" class="easyui-linkbutton" iconCls="icon-clear" style="width:90px">重設</a>
    <a href="#" id="save_nav_link" class="easyui-linkbutton" iconCls="icon-ok" style="width:90px">確定</a>
  </div>
  <script>
    $(function(){
      //導覽連結 sys_nav_links
      $('#sys_nav_links').datagrid({
        columns:[[
          {field:'name',title:'name',sortable:true},
          {field:'title',title:'title',sortable:true},
          {field:'url',title:'url',sortable:true},
          {field:'target',title:'target',sortable:true},
          {field:'sequence',title:'sequence',align:'center',sortable:true},
          {field:'block_name',title:'block_name',align:'center',sortable:true},
          {field:'hint',title:'hint',sortable:true}
          ]],
        url:"/get_navlinks",
        method:"post",
        singleSelect:true,
        rownumbers:true
        });
      $("#nav_link_sequence").numberspinner({
        min:0,
        max:99,
        increment:1,
        value:"0",
        required:true,
        missingMessage:'此欄位為必填'
        });
      $("#clear_nav_link").bind("click",function(){
        $("#nav_link_form")[0].reset();
        });
      $("#save_nav_link").bind("click",function(){      
        var op=$("#nav_link_op").val();  //判斷是新增或修改
        if (op=="update") {
          var row=$("#sys_nav_links").datagrid("getSelected");
          if (row.name=="home" || row.name=="logout") {
            $("#nav_link_dialog").dialog('close');
            $.messager.alert("訊息","此為系統連結不可更改!","error");
            return;
            }
          var url="/update_navlink";
          }
        else {var url="/add_navlink";}
        $("#nav_link_form").form("submit",{
          url:url,
          method:"post",
          success:function(data){
            var data=eval('(' + data + ')');
            $("#nav_link_dialog").dialog("close");
            if (data.status==="success") {
              $("#sys_nav_links").datagrid("reload");
              }
            else {
              $.messager.alert("訊息",data.reason,"error");  
              }        
            }
          });
        });
      $("#add_nav_link").bind("click",function(){
        $("#nav_link_dialog").dialog("open").dialog("setTitle","新增連結");
        $("#link_name").textbox({"readonly":false}); //for adding
        $("#nav_link_form").form("clear");
        $("#nav_link_op").val("add");
        $('#target').combobox('setValue','_self');
        });
      $("#edit_nav_link").bind("click",function(){
        var row=$("#sys_nav_links").datagrid("getSelected");
        if (row) {
          $("#nav_link_dialog").dialog("open").dialog("setTitle","編輯連結");
          $("#nav_link_form").form("load",row);
          $("#link_name").textbox({"readonly":true});
          $("#nav_link_op").val("update");
          }
        else {$.messager.alert("訊息","請先選取要編輯的連結!","error");}
        });
      $("#remove_nav_link").bind("click",function(){
        var row=$("#sys_nav_links").datagrid("getSelected");
        if (row) {
          var params={name:row.name};
          $.messager.confirm("確認","確定要刪除這個連結嗎?",function(btn){
            if (btn){
              if (row.title=="首頁" || row.title=="登出") {
                $.messager.alert("訊息","此為系統連結不可刪除!","error");
                return;
                }
              var callback=function(data){
                if (data.status==="success"){
                  $("#sys_nav_links").datagrid("reload");
                  }
                else {$.messager.alert("訊息",data.reason,"error");}          
                };              
              $.post("/remove_navlink",params,callback,"json");
              }
            })
          }
        });
      $("#reload_nav_links").bind("click",function(){
        $("#sys_nav_links").datagrid("load");
        });
      });
  </script>
{% endblock%}

這跟 list_headerlinks.htm 較不一樣的是多了一個 block_name 欄位, 用來標示此連結所屬之區塊. CRUD 操作部分, 此網頁模板會從 /get_navlinks 路徑取得 json 資料來源, 其路徑處理類別如下, 是從上一篇文章的 /get_headerlinks  複製來修改的 :

class get_navlinks(webapp2.RequestHandler):
    def post(self):
        page=self.request.get("page")
        rows=self.request.get("rows")
        sort=self.request.get("sort")
        order=self.request.get("order")
        if len(page):
            page=int(page)
        else:
            page=1
        if len(rows):
            rows=int(rows)
        else:
            rows=10
        if not len(sort):
            sort="sequence"
        if not len(order):
            order="asc"
        query=m.Navlinks.gql("ORDER BY %s %s" % (sort, order))
        count=query.count()
        links=query.fetch(rows, (page-1)*rows)
        rows=[]  #for storing objects
        for h in links:
            link={"name":h.name,
                  "title":h.title,
                  "url":h.url,
                  "target":h.target,
                  "sequence":h.sequence,
                  "block_name":h.block_name,
                  "hint":h.hint}
            rows.append(link)
        obj={"total":count,"rows":rows}  #Easyui datagrid json format
        self.response.headers["Content-Type"]="application/json"
        self.response.out.write(json.dumps(obj))

class add_navlink(webapp2.RequestHandler):
    def post(self):
        name=self.request.get("name")
        #check entity if exist
        link=m.Navlinks.get_by_key_name(name)
        if link: #already exist
            result='{"status":"failure","reason":"連結名稱已存在!"}'
        else:  #new link
            navlink=m.Navlinks(key_name=name,name=name,
                title=self.request.get("title"),
                url=self.request.get("url"),
                target=self.request.get("target"),
                sequence=int(self.request.get("sequence")),
                block_name=self.request.get("block_name"),
                hint=self.request.get("hint")
                )
            navlink.put()
            result='{"status":"success"}'      
        self.response.out.write(result)

class update_navlink(webapp2.RequestHandler):
    def post(self):
        name=self.request.get("name")
        #get entity from store
        link=m.Navlinks.get_by_key_name(name)
        if link: #entity exist
            link.title=self.request.get("title")
            link.url=self.request.get("url")
            link.target=self.request.get("target")
            link.sequence=int(self.request.get("sequence"))
            link.block_name=self.request.get("block_name")
            link.hint=self.request.get("hint")
            link.put()
            result='{"status":"success"}'
        else:  #link not existed
            result='{"status":"failure","reason":"連結不存在!"}'      
        self.response.out.write(result)

class remove_navlink(webapp2.RequestHandler):
    def post(self):
        name=self.request.get("name")
        #get entity from store
        link=m.Navlinks.get_by_key_name(name)
        if link: #entity exist
            db.delete(link)          
            result='{"status":"success"}'
        else:  #link not existed
            result='{"status":"failure","reason":"連結不存在!"}'      
        self.response.out.write(result)

OK, 終於大功告成了! 這篇測試紀錄其實在除夕前就開始寫, 過年忙這忙那, 斷斷續續寫到今天年初二才完成. 這樣整個 CMS on GAE 的主體架構移植大致完成, 剩下的就是一些系統功能, 例如檔案上傳下載, 留言板, 系統管理設定等等, 過完年我可能要開始玩 App Inventor, 既然已經證實 GAE 也可以實作 CMS, 剩下的功能就不用急, 可以慢慢做.

實際測試連結如下 :

測試 5 : http://jqueryeasyui.appspot.com/main_5 (下載原始碼(備用下載點)





最後記一下此番測試的一個經驗, 就是在修改 Navblocks 程式時, display 欄位最先沒注意到要轉成布林值而出現錯誤, 改了以後不行, 錯誤訊息變成如下 :

"NeedIndexError: The index for this query is not ready to serve"

NeedIndexError: The index for this query is not ready to serve. See the Datastore Indexes page in the Admin Console.
The suggested index for this query is:
- kind: Navblocks
  properties:
  - name: display
  - name: sequence

解決辦法參考下列這篇 :

How to solve the Index warning on GAE?

就是將 index.yaml 檔案的內容全部殺掉 (保留 remark), 上傳後重新整理網頁即可.

其他參考資料 :

# add “readonly” to <input > (jQuery)
怎么设置combobox的默认值 How to set the default value of combobox


沒有留言 :