flask
flask_ext
python
xeditable
這並不是Flask的擴展,屬前端的工具搭配應用
有些時候,一些基本資料的編輯我們並不想要再產生一個新的表單來處理,希望可以利用inplace-edit
的方式來直接編輯,但這必需透過前端的幫助才有辦法做的到,一起看看如何利用現在的簡便工具X-editable
來完成這個需求。
This library allows you to create editable elements on your page. It can be used with any engine (bootstrap, jquery-ui, jquery only) and includes both popup and inline modes. Please try out demo to see how it works.
X-editable
可以搭配jquery, bootstrap, jquery-ui
一起使用,而我們還要再搭配Flask_wtf
一起使用,並且如果您需求搭配Bootstrap 4
的話,可以另外下載相對應的文件。(見相關資源連結)
範例取自個人自己寫的記帳網頁片段,因此看了可能會覺得混亂,但是精神抓的到相信可以應用在自己的需求上,敬請見諒。
widgets
實作上我們會利用flask_wtf
的widgets
來設置表單屬性,再利用該widgets
來建立表單,見註解說明。
from wtforms.widgets import HTMLString, html_params
class WidgetError(Exception):
"""方便debug,設置一個Exception"""
pass
class XEditableWidget:
def __call__(self, field, **kwargs):
# 從FieldList取得Field,並且建立x-editable連結
subfield = field.pop_entry()
value = kwargs.pop("value", "")
kwargs.setdefault('data-role', 'x-editable')
# 判斷是否存在url,如不存在就拋出異常
# 該url主要是這個ajax的目標網址
if not kwargs.get('url'):
raise WidgetError('url required')
kwargs['data-url'] = kwargs.pop("url")
kwargs.setdefault('id', field.id)
kwargs.setdefault('name', field.name)
kwargs.setdefault('href', '#')
# pk value,ajax更新資料的時候用的到
if not kwargs.get('pk'):
raise WidgetError('pk required')
kwargs['data-pk'] = kwargs.pop("pk")
# 判斷欄位格式,如有不支援的就拋出異常
if isinstance(subfield, StringField):
kwargs['data-type'] = 'text'
elif isinstance(subfield, BooleanField):
kwargs['data-type'] = 'select'
elif isinstance(subfield, RadioField):
kwargs['data-type'] = 'select'
elif isinstance(subfield, SelectField):
kwargs['data-type'] = 'select'
elif isinstance(subfield, DateField):
kwargs['data-type'] = 'date'
elif isinstance(subfield, DateTimeField):
kwargs['data-type'] = 'datetime'
elif isinstance(subfield, IntegerField):
kwargs['data-type'] = 'number'
elif isinstance(subfield, TextAreaField):
kwargs['data-type'] = 'textarea'
else:
raise WidgetError('Unsupported field type: %s' % (type(subfield),))
return HTMLString('<a %s>%s</a>' % (html_params(**kwargs), value))
Form
我們利用FieldList
來建立表單,注意到min_entries
設置為1,因為x-editable
最少需要一個entries
class XEditableFormItems(FlaskForm):
"""建立項目清單_for x-editable"""
# 務必設置min_entries=1, 這是 x-editable的最低需求
item_category = FieldList(StringField(
), widget=XEditableWidget(), min_entries=1)
item_name = FieldList(StringField(
), widget=XEditableWidget(), min_entries=1)
item_remark = FieldList(StringField(
), widget=XEditableWidget(), min_entries=1)
配合你欄位的實際格式,寫入FieldList
,並且指定widget
為稍早所設置的XEditableWidget
,範例中設置的格式皆為StringField
Route, ViewFunction
路由會分為顯示以及編輯,編輯的路由我們會另外搭配ajax
實作即時更新,首先是顯示項目清單的路由:
@daily_cost_bp.route('/items/cr/', methods=['GET'])
def items_cr():
"""建立項目清單
function:
單純表單呈現,實際寫入路由寫至其它地方
寫入花費項目的部份寫入`items_c`
更新花費項目的部份寫入'items_u'
"""
form_items = FormItems()
form_category = FormItemCategory()
items = Items.query.order_by(Items.item_category).all()
item_number = len(items)
xform = XEditableFormItems()
for i in range(item_number):
xform.item_name.append_entry()
xform.item_category.append_entry()
xform.item_remark.append_entry()
return render_template('daily/items_cu.html',
form_items=form_items,
items=items,
xform=xform,
form_category=form_category)
第14行:取得清單總筆數
第16行:產生與清單總筆數相對應的欄位數目
註:這部份是在下測試多次得到的成功方式,如果有更好的方式請指導
現在設置更新的路由,搭配照片說明,照片操作為點擊item_category
更新,如下:
@daily_cost_bp.route('/items/u/', methods=['POST'])
@login_required
def items_u():
"""提供花費項目更新的路由"""
xform = XEditableFormItems(request.form)
if xform.validate_on_submit():
for x in xform:
if getattr(x, 'last_index', None):
items = Items.query.get(x.last_index)
setattr(items, x.name, x.data.pop())
db.session.commit()
return json.dumps({'success': True}), 200, {'ContentType': 'application/json'}
第1行:methods設置為POST
第5行:request.form
內容ImmutableMultiDict([('item_remark-7', 'i.e. 高速公路過路費 ')])
第5行:xform
內容{'item_category': [''], 'item_name': [''], 'item_remark': ['i.e. 高速公路過路費 '], 'csrf_token': ''}
第6行:迴圈所看的就是item_category, item_name, item_remark
這三個欄位的資料
第7行:x
是<class 'wtforms.fields.core.FieldList'>
物件
第8行:last_index
取得的就是pk
值,僅於有修正的欄位會取得
第9行:利用pk
值來取得資料
第10行:x.name
就是欄位名稱,x.data
就是這次編輯的內容
上面搭配個人所寫的記帳網頁為例說明,整個迴圈所做的事情就是取得該筆資料的FieldList
內的所有欄位StringField
,然後判斷有編輯的是那一個欄位,再將那個欄位的值更新,因此以畫面範例為例,迴圈會執行3+1次,1指的是判斷csrf_token
。
前端的部份,主要是利用jquery
來實作,如下:
{% extends "base.html" %}
{% from 'bootstrap/form.html' import render_form, render_field %}
{% block page_content %}
{# 移除部份不需要的程式碼 #}
<div>
<table class="table table-striped">
<thead>
<tr>
<td>item_category</td>
<td>item_name</td>
<td>item_remark</td>
</tr>
</thead>
<tbody id="mybody">
{% for item in items %}
<tr>
<td>{{ xform.item_category(pk=item.id, value=item.item_category, url=url_for('daily_cost.items_u')) }}</td>
<td>{{ xform.item_name(pk=item.id, value=item.item_name, url=url_for('daily_cost.items_u')) }}</td>
<td>{{ xform.item_remark(pk=item.id, value=item.item_remark, url=url_for('daily_cost.items_u')) }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock page_content %}
{% block scripts %}
{{ super() }}
<script type="text/javascript" src="{{ url_for('static', filename='js/bootstrap-editable.js') }}"></script>
<link rel="stylesheet" href="{{ url_for('static', filename='css/bootstrap-editable.css') }}" type="text/css">
<script></script>
{% endblock %}
第16行:利用迴圈渲染表單,並且在迴圈中綁定欄任pk值、資料、以及更新路由
第29、30行:引用需求的前端文件
第32~39行:Flask_wtf
標準利用ajax POST
資料至後端的方式,帶csrf_token
,多一層保護。
第40~50行:實作x-editable
,主要將表單內所有的超連結一次性的註冊
實作結果如下圖:
我們發現到每一個欄位都變成一個超連結,點擊之後會開一小窗(這取決於你使用的模式),如下:
在編輯之後再點擊藍色小按鈕就可以無跳頁更新,這對使用者感受會有不錯的加分效果。
很抱歉這次的範例是利用自己寫的記帳網頁來做說明,這可能造成在閱讀的時候不是那麼直觀,但是相信有前面打下來的基礎,一定可以很快的上手,瞭解如何實作inplace-edit
。