Просмотр исходного кода

support output before/after anchor

wangweimin 5 лет назад
Родитель
Сommit
c58bad488f
2 измененных файлов с 102 добавлено и 45 удалено
  1. 46 27
      pywebio/html/js/form.js
  2. 56 18
      pywebio/output.py

+ 46 - 27
pywebio/html/js/form.js

@@ -133,14 +133,19 @@
 
     OutputController.prototype.handle_message = function (msg) {
         if (msg.command === 'output') {
-            if (msg.spec.type === 'text')
-                this.container_elem.append(this.md_parser.parse(msg.spec.content));  // 直接更改innerHtml会导致事件绑定失效
-            else if (msg.spec.type === 'buttons')
-                this.handle_buttons(msg);
-            else if (msg.spec.type === 'file')
-                this.handle_file(msg);
-            else
-                console.warn('Unknown output type:%s', msg.spec.type);
+            const func_name = `get_${msg.spec.type}_element`;
+            if (!(func_name in OutputController.prototype)){
+                return console.error('Unknown output type:%s', msg.spec.type);
+            }
+
+            var elem = OutputController.prototype[func_name].call(this, msg.spec);
+            if(msg.spec.before!==undefined){
+                this.container_elem.find('#'+msg.spec.before).before(elem);
+            }else if(msg.spec.after!==undefined){
+                this.container_elem.find('#'+msg.spec.after).after(elem);
+            }else{
+                this.container_elem.append(elem);
+            }
         } else if (msg.command === 'output_ctl') {
             this.handle_output_ctl(msg);
         }
@@ -149,6 +154,39 @@
             this.scroll_bottom();
     };
 
+    OutputController.prototype.get_text_element = function(spec){
+        var elem  = $('<p></p>');
+        elem.text(spec.content);
+        return elem;
+    };
+
+    OutputController.prototype.get_markdown_element = function(spec){
+        return $(this.md_parser.parse(spec.content));
+    };
+
+    OutputController.prototype.get_html_element = function(spec){
+        return $($.parseHTML(spec.content));
+    };
+
+    OutputController.prototype.get_buttons_element = function(spec){
+        const btns_tpl = `<div class="form-group">{{#buttons}}
+                             <button value="{{value}}" onclick="WebIO.DisplayAreaButtonOnClick(this, '{{callback_id}}')" class="btn btn-primary {{#small}}btn-sm{{/small}}">{{label}}</button> 
+                          {{/buttons}}</div>`;
+        var html = Mustache.render(btns_tpl, spec);
+        return $(html);
+    };
+
+    OutputController.prototype.get_file_element = function(spec){
+        const html = `<div class="form-group"><button type="button" class="btn btn-link">${msg.spec.name}</button></div>`;
+        var element = $(html);
+        var blob = b64toBlob(msg.spec.content);
+        element.on('click', 'button', function (e) {
+            saveAs(blob, msg.spec.name, {}, false);
+        });
+
+        return element;
+    };
+
     OutputController.prototype.handle_output_ctl = function (msg) {
         if (msg.spec.title)
             $('#title').text(msg.spec.title);  // 直接使用#title不规范 todo
@@ -183,25 +221,6 @@
         }
     };
 
-    OutputController.prototype.handle_file = function (msg) {
-        const html = `<div class="form-group"><button type="button" class="btn btn-link">${msg.spec.name}</button></div>`;
-        var element = $(html);
-        this.container_elem.append(element);
-        var blob = b64toBlob(msg.spec.content);
-        element.on('click', 'button', function (e) {
-            saveAs(blob, msg.spec.name, {}, false);
-        });
-    };
-
-    OutputController.prototype.handle_buttons = function (msg) {
-        const btns_tpl = `<div class="form-group">{{#buttons}}
-                             <button value="{{value}}" onclick="WebIO.DisplayAreaButtonOnClick(this, '{{callback_id}}')" class="btn btn-primary {{#small}}btn-sm{{/small}}">{{label}}</button> 
-                          {{/buttons}}</div>`;
-        var html = Mustache.render(btns_tpl, msg.spec);
-        var element = $(html);
-        this.container_elem.append(element);
-    };
-
     // 显示区按钮点击回调函数
     function DisplayAreaButtonOnClick(this_ele, callback_id) {
         if (WSClient === undefined)

+ 56 - 18
pywebio/output.py

@@ -21,23 +21,26 @@ def set_auto_scroll_bottom(enabled=True):
     send_msg('output_ctl', dict(auto_scroll_bottom=enabled))
 
 
+_AnchorTPL = 'pywebio-anchor-%s'
+
+
 def set_anchor(name):
     """
     在当前输出处标记锚点。 若已经存在name锚点,则先将旧锚点删除
     """
-    inner_ancher_name = 'pywebio-anchor-%s' % name
+    inner_ancher_name = _AnchorTPL % name
     send_msg('output_ctl', dict(set_anchor=inner_ancher_name))
 
 
 def clear_before(anchor):
     """清除anchor锚点之前输出的内容"""
-    inner_ancher_name = 'pywebio-anchor-%s' % anchor
+    inner_ancher_name = _AnchorTPL % anchor
     send_msg('output_ctl', dict(clear_before=inner_ancher_name))
 
 
 def clear_after(anchor):
     """清除anchor锚点之后输出的内容"""
-    inner_ancher_name = 'pywebio-anchor-%s' % anchor
+    inner_ancher_name = _AnchorTPL % anchor
     send_msg('output_ctl', dict(clear_after=inner_ancher_name))
 
 
@@ -57,19 +60,51 @@ def scroll_to(anchor):
     send_msg('output_ctl', dict(scroll_to=inner_ancher_name))
 
 
-def text_print(text, *, ws=None):
-    if text is None:
-        text = ''
-    msg = dict(command="output", spec=dict(content=text, type='text'))
+def put_content(type, ws=None, before=None, after=None, **other_spec):
+    """
+    向浏览器输出内容
+    :param type:
+    :param content:
+    :param ws:
+    :param before:
+    :param after:
+    :return:
+    """
+    assert not (before and after), "Parameter 'before' and 'after' cannot be specified at the same time"
+
+    spec = dict(type=type)
+    spec.update(other_spec)
+    if before:
+        spec['before'] = _AnchorTPL % before
+    elif after:
+        spec['after'] = _AnchorTPL % after
+
+    msg = dict(command="output", spec=spec)
     (ws or Global.active_ws).write_message(json.dumps(msg))
 
 
-def json_print(obj):
-    text = "```\n%s\n```" % json.dumps(obj, indent=4, ensure_ascii=False)
-    text_print(text)
+def text_print(text, *, ws=None, before=None, after=None):
+    """
+    输出文本内容
+    :param text:
+    :param ws:
+    :param before:
+    :param after:
+    :return:
+    """
+    put_content('text', content=text, ws=ws, before=before, after=after)
+
 
+def put_html(html, before=None, after=None):
+    put_content('html', content=html, before=before, after=after)
 
-def put_markdown(mdcontent, strip_indent=0, lstrip=False):
+
+def put_code(content, langage='', before=None, after=None):
+    code = "```%s\n%s\n```" % (langage, content)
+    put_markdown(code, before=None, after=None)
+
+
+def put_markdown(mdcontent, strip_indent=0, lstrip=False, before=None, after=None):
     """
     输出Markdown内容。当在函数中使用Python的三引号语法输出多行内容时,为了排版美观可能会对Markdown文本进行缩进,
         这时候,可以设置strip_indent或lstrip来防止Markdown错误解析
@@ -87,10 +122,11 @@ def put_markdown(mdcontent, strip_indent=0, lstrip=False):
     if lstrip:
         lines = (i.lstrip() for i in mdcontent.splitlines())
         mdcontent = '\n'.join(lines)
-    text_print(mdcontent)
+
+    put_content('markdown', content=mdcontent, before=before, after=after)
 
 
-def put_table(tdata, header=None):
+def put_table(tdata, header=None, before=None, after=None):
     """
     输出表格
     :param tdata: list of list|dict
@@ -116,7 +152,7 @@ def put_table(tdata, header=None):
     for tr in tdata[1:]:
         t = "|%s|" % "|".join(map(quote, tr))
         res.append(t)
-    text_print('\n'.join(res))
+    put_markdown('\n'.join(res), before=before, after=after)
 
 
 def _format_button(buttons):
@@ -157,7 +193,7 @@ def td_buttons(buttons, onclick, save=None, mutex_mode=False):
     return ' '.join(btns_html)
 
 
-def buttons(buttons, onclick, small=False, save=None, mutex_mode=False):
+def buttons(buttons, onclick, small=False, save=None, mutex_mode=False, before=None, after=None):
     """
     显示一组按钮
     :param buttons: button列表, button可用形式: value 只能为字符串
@@ -169,16 +205,18 @@ def buttons(buttons, onclick, small=False, save=None, mutex_mode=False):
     :param mutex_mode: 互斥模式,回调在运行过程中,无法响应同一回调,仅当onclick为协程函数时有效
     :return:
     """
+    assert not (before and after), "Parameter 'before' and 'after' cannot be specified at the same time"
     btns = _format_button(buttons)
     callback_id = register_callback(onclick, save, mutex_mode)
-    send_msg('output', dict(type='buttons', callback_id=callback_id, buttons=btns, small=small))
+    put_content('buttons', callback_id=callback_id, buttons=btns, small=small, before=before, after=after)
 
 
-def put_file(name, content):
+def put_file(name, content, before=None, after=None):
     """
     :param name: file name
     :param content: bytes-like object
     :return:
     """
+    assert not (before and after), "Parameter 'before' and 'after' cannot be specified at the same time"
     content = b64encode(content).decode('ascii')
-    send_msg('output', dict(type='file', name=name, content=content))
+    put_content('file', name=name, content=content, before=before, after=after)