status

正式

ZoomQuiet

完成度:97%

1. KDay 3:使用第3方模块规范化表单

Cheetah 只能输出有配套模板的页面内容,但是……

  • 对于经常变化的表单,有什么更好的方式来生成表单?
  • 小白开始了 Pythonic 重构思考,感觉 VC 格局中的 V ~ view,展示层,少不了人手工对 HTML 为格式的页面模板进行维护, 而且每个对应的功能页面就要写个模板, 再且模板文件的复用非常难...

  • 所以,想起在 CDays 故事演练中使用过的 Karrigell 快速表单模块:

1.1. Karrigell_QuickForm

~即有之,则用之!

  • 示例:

   1 from Karrigell_QuickForm import Karrigell_QuickForm
   2 
   3 p = Karrigell_QuickForm('teste','POST','foo.py','Authentication')
   4 p.addElement('text','login')
   5 p.addElement('text','password')
   6 p.addRule('login','required',"Login is required!")
   7 p.addGroup(["submit","botao_enviar","submit","Send"]
   8            ,["reset","botao_limpar","reset","Clear"])
   9 """根据习惯hack! 原先的自动生成 value 的为指定按钮文字.
  10 p.addGroup(["submit","botao_enviar","Send"]
  11            ,["reset","botao_limpar","Clear"])
  12 """
  13 p.display()

  • 6行完成一个标准的登录表单!!!哈哈哈!赞!图 KDay3-1 使用KQF生成的试验表单

    1.1.1. 改造

    毕竟是 alpha 版本,居然还是全面的table 结构!

       1 def addRadioGroup(self,name,value):
       2         """add a radio element - addRadioGroup('genre',{'male':'Male','female':'Female'})"""
       3         self.form_list.append("<tr><td align='right' valign='top'><b>"+name.title().replace('_',' ')+":</b></td> <td valign='top' align='left'>")
       4         radio = ""
       5         t = value.items()
       6         for a,b in t:
       7             radio = radio + "<input type='radio' name='"+name+"' value='"+a+"'>"+"<label><font face=verdana size=2>"+b+"</font></label><br>"
       8         self.form_list.append(radio+"</td>")
       9         self.form_list.append("</tr>")
    

  • 増补了个基于列表的干净版本的单选框生成器:
       1 def addRadioList(self,name,desc,value,id=""):
       2     """add a radio element export as UL LI group
       3     """
       4     htm = """
       5         <li id='%s'><b>%s:</b>
       6             <ul>"""
       7     self.form_list.append(htm%(id,desc))
       8     radio = ""
       9     t = value.items()
      10     tmpl = """<li>
      11         <input type='radio' 
      12         name='%s' 
      13         value='%s'>  
      14         <label>%s</label>      
      15         """
      16     for a,b in t:
      17         radio = radio + tmpl%(name,a,b)
      18     self.form_list.append(radio+"</ul></li>")
    

    1.1.2. 利用

    直接将昨天的展示函式修改一下子就应该好用的!

    • def expage(dict): 对应的修改为:

       1 def qpubish(dict):
       2     exp = ""
       3     p = Karrigell_QuickForm('fm_kq','POST','#',dict.desc.desc)
       4 
       5     p.addElement('node','<ul>','')
       6     # 深入数据 基本和昨天的一样,仅仅是输出时使用 Karrigell_QuickForm 对象而已
       7     qli = {}
       8     k = [int(i) for i in dict.ask.keys()]
       9     k.sort()
      10     for i in k:
      11         ask = dict.ask[str(i)]
      12         qk = [j for j in ask.keys()]
      13         qk.sort()
      14         for q in qk:
      15             if 1==len(q):
      16                 qli[q] = ask[q]
      17             else:
      18                 pass
      19         p.addRadioList("cr_ask%s"%i
      20                    ,ask["question"]
      21                    ,qli)
      22     p.addElement('node','</ul>','')
      23     p.addGroup(["submit","btn_submit","提交"]
      24                ,["reset","btn_reset","重写"])
    
    • 注意到原先的 Karrigell_QuickForm 只有display(),要求表单页面立即输出, 但是小白现在需要进一步的HTML 处理后再输出

    • 所以有了以下修订

         1 def export(self):
         2     """ export the html form code so people can do something for them self
         3     """
         4     exp = ""
         5     ...
         6     for c in self.css_list:
         7         exp += c+"\n"
         8     for i in self.form_list:
         9         exp += i+"\n"
        10     return exp
      

      基本上就是将原先的 print 替换为 exp+= 记录为字串对象然后返回

    • 还有def addElement(self,element,name,options=None): 中追加更自由的任何HTML 节点输出:

              elif element == 'node':
                  self.form_list.append(name)
      

    1.2. JS 问题

    一切顺心,表单顺利自动生成了,但是,Karrigell_QuickForm 提供的前端表单检验居然不支持Radio列表的!

    • 呜乎!这是个问题哪...
    • 因为:如果不是所有人将所有问题都回答完就提交的话:
      • 不检验这种情况,则成绩统计没有意义;
      • 交给服务端检验,浪费带宽!还要想办法记录上次是谁回答的问题情况, 然后再返回/提示/要求重答等等等等...

    1.2.1. 继续发掘

    现在的问题是有什么现成的可以模式化的定义表单检验的前端JS组件?

    • TiosnG ![ti'aosn'gu]

      • There is one site named Google! -- 哈哈哈!!运用这个流传在行者们中的咒语,翻查了一下,确认 Validation ~ 有效性检查是小白想要的检验的学名,于是得到...

      • JVF~JavaScript Validation Framework -- Javascript 有效检验框架 国人作品!

    图 KDay3-3 2004年当时的JVF作品页面情景

    详细:: 
    JVF是AMOWA社区作品,该社区致力于创建一个实用精巧的Ajax框架;
    当年的官网是 http://www.amowa.net 
    现在迁移到 http://buffalo.sourceforge.net/
    JVF 可以从 http://cosoft.org.cn/projects/jsvalidation 获得;
    精巧地址:http://bit.ly/2icRKJ
    
    相关的动态网页技术,在PCS401 DHTML 进行分享
    

    1.2.2. 迁就,先!

    为了与KarriGell 配合,当前需要:

    1. 在配置文件中,追加声明jvf=%(base)s/karriweb/questionnaire/js 这样的专门虚拟目录发布,以便,其它各种应用也可以享受JVF 支持

    2. JVF 实际运行的 validation-framework.js 本身, 也要声明可访问的目录:var ValidationRoot = "/jvf/";

    3. 小量修订 JVF 声明错误输出的页面元素 #errorDiv

      ValidateMethodFactory.validateRequired = function(field, params) {
              ...
              window.location.replace("#errorDiv");
      
      使用replace 来减少不必要的页面刷新
    4. 使用 p.saveJSRule("../js/validation-config.xml") 声明提交时要检验的表单元素

    注意: 
    测试JS 成功引用否的小技巧:
    `alert("Include KO!");` 
    在JS脚本中加入强制提示,刷新页面,如果见到已经包含的信息就表明引用路径对了!
    

    1.2.2.1. KQF 对JVF 的迁就

    JVF的使用很有个性,使用外部的XML 文件进行检验行为的设置...

    • 所以,对应的增补Karrigell_QuickForm

      1. addJSRule() 追加专门的JVF 规则

           1     def addJSRule(self,name,message):
           2         """add a xml rule for javascript checking
           3         """
           4         exp = self.JSvMXLnode%(name,message)
           5         self.JSvRules.append(exp)
        
      2. addJSValidation() 追加调用JVF的页面行为

           1     def addJSValidation(self):
           2         """add a javascript rule in order to validate a form field  
           3         - addRule('elem_name','required','Name is required!')
           4         """
           5         orig = "enctype='multipart/form-data'"
           6         repl = """
           7             onsubmit='return doValidate("%s");'
           8             """
           9         begin_form=self.form_list[0].replace(orig
          10                                      ,repl%self.name)
          11         self.form_list[0] = begin_form
        
      3. saveJSRule() 记录规则集合为JVF需要的XML

           1     def saveJSRule(self,xml):
           2         """exp and save a xml rule for javascript checking
           3         """
           4         exp = ""
           5         for node in self.JSvRules:
           6             exp+= node
           7         #exp = self.JSvXMLtmpl%(form,exp)
           8         open(xml,'w').write(self.JSvXMLtmpl%(self.name
           9                                              ,exp)
          10                                              )
        
      4. 对应的KQF中追加统一的预设声明

        self.JSvXMLtmpl="""<?xml version="1.0" encoding="utf-8"?>
        <!DOCTYPE validation-config SYSTEM "validation-config.dtd">
        <validation-config lang="auto"> 
                <form id="%s" show-error="errorMessage" onfail="" 
                show-type="first">
            %s
                </form>
        </validation-config>
        """
        self.JSvMXLnode = """
                        <field name="%s" 
                        display-name="%s" onfail="">
                                <depend name="required" />
                        </field>
        """
        self.JSvRules = []
        

    1.2.3. 果然不出所料

    仅仅追加少量代码就完成所想的客户端JS验证功能

    在原先问卷解析函式中,追加准备好的JVF 支持

    def qpubish(dict):
        ...
        ## 具体问题解析
        k.sort()
        for i in k:
            ...
            p.addJSRule("cr_ask%s"%i,"问题%s "%i) # 声明此处要进行JS检验
        ## 整体行为处理
        p.addJSValidation()
        p.saveJSRule("js/validation-config.xml")    #收集检验声明,生成JVF 使用的外部XML设置文件
        ...
    
    • 没有任何悬念的完成任务!

    图 KDay3-4 加载JVF 后,表单有任何项没有提交就报错

    1.3. 页面编码

    从开始使用 Karrigell ,小白在 FireFox 浏览器中就发现有中文乱码问题...

    图 KDay3-4 页面乱码现象

    • 小白努力的检查了源代码,模板代码,将所有应该声明或是本身也另存为了 utf-8; 甚至于发现配置文件中有疑似输出编码声明的地方也逐一打开,比如说:
      [Server]
      ...
      
      outputEncoding = utf-8
      
      ...
      
      # determine if form fields should be encoded
      
      encodeFormData = 1
      ...
      
    • 但是依然是乱码,每次非得手工将浏览器的字符编码设置成 utf-8 才正常...
    • 怎么回事儿呢? 小白不得不到列表中吼,经过行者们的研究,确认是 Karrigell 内部处理编码有问题,不过,想解决也非常简单,使用以下的页面响应编码声明:

    RESPONSE['Content-Type'] = "text/html; charset=utf-8"
    

    脚注: 
    行者们讨论中文乱码问题的原始记要在 啄木鸟社区维基:
    http://wiki.woodpecker.org.cn/moin/MiscItems/2008-08-05
    

    1.4. 合理滥用Leo

    到现在页面都是白板!不能忍了!使用CSS!

    • 小白先不理会 KarriGell 的外部文件引用效率,决定简单些,直接将CSS,写入页面,反正有Leo来维护

    图 KDay3-5 通过Leo 每个页面复用嵌入的css 定义

    • 注意 <<k_base>> 节点前的小红箭头记号 ~ 这表明此节点是从 别处 克隆过来的!

    1.4.1. CSS设计技巧

    复用以前自个儿的积累是非常好的事儿!

    图 KDay3-6 演示 redalt.com 的CSS颜色分析工具

    1.4.2. CSS

    • 每一需要CSS美化的页面都加<<k_base>> 引用,

    • 引用都是从复用代码 中clone 过来的同一节点

    • 包含的就是k_base.css 小白根据 Django 社区的样式修剪成的一个通用CSS设计,套用到当前的系统中

    图 KDay3-7 问卷管理和展示CSS效果

    1.5. 小结

    今天发现并引入了 KQFJVF好象有点复杂的样子……

    图 KDay3-8 最终问卷效果

    • 当然在小白的开发故事中,这不是最终接受的方案,精彩还要继续……
    • 现在所有的基本功能点都有了,接下来的就是要实用化:
      1. 支持多用户,得有用户验证,要登录
      2. 支持多问卷选择/回答/编辑

    1.6. 实例下载

    使用SVN下载地址:

    kday3
    |-- Karrigell_QuickForm.py  KQF快速表单模块
    |-- tryKQF.py           KQF试用页面
    |-- dict4ini.py         Limodou 贡献的 ini 解析模块
    |-- q/                  问卷设计文本收藏目录
    |   |-- easy051201.cfg  第一份问卷设计
    |   |-- easy051201.cfg.080824231959 问卷设计历史存档
    |   `-- easy051201.cfg.080824232025
    |-- mana.pih            问卷设计页面
    |-- qdesign.py          问卷设计实际行为脚本
    |-- qpage.pih           问卷复审页面
    |-- qpage.py            问卷复审实际行为脚本
    |-- qprint.pih          问卷展示页面
    |-- qprint.py           问卷展示实际行为脚本
    `-- questionnaire.tmpl  问卷展示模板
    

    1.7. 练习

    在前面章节练习mysite的基础上, 完成以下功能:

    • (1) 利用Karrigell_QuickForm改写编辑页面(edit), 增加更多的文章属性, 如日期, 标签, 引用链接等;

    • (2) 实现了上述之后, 使用JVF, 依次校验用户输入的有效性;
    • (3) 提示: 可以设计一个文章类, 多个文章对象可以利用pickle保存; 最终应该能够实现新建或修改文章并保存入本地文件的功能.


    返回 KDays实例故事

    ::-- ZoomQuiet [2005-12-11 04:50:15]

    Name Password4deL ;) :( X-( B-)