Skip to content

Commit 3a433cd

Browse files
committed
add put_scope() to replace output()
1 parent ebc29a1 commit 3a433cd

File tree

9 files changed

+155
-66
lines changed

9 files changed

+155
-66
lines changed

docs/guide.rst

Lines changed: 37 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -297,29 +297,6 @@ In addition, you can use `put_widget() <pywebio.output.put_widget>` to make your
297297

298298
For a full list of functions that accept ``put_xxx()`` calls as content, see :ref:`Output functions list <output_func_list>`
299299

300-
**Placeholder**
301-
302-
When using combination output, if you want to dynamically update the ``put_xxx()`` content after it has been output,
303-
you can use the `output() <pywebio.output.output>` function. `output() <pywebio.output.output>` is like a placeholder,
304-
it can be passed in anywhere that ``put_xxx()`` can passed in. And after being output, the content can also be modified:
305-
306-
.. exportable-codeblock::
307-
:name: output
308-
:summary: Output placeholder——`output()`
309-
310-
hobby = output('Coding') # equal to output(put_text('Coding'))
311-
put_table([
312-
['Name', 'Hobbies'],
313-
['Wang', hobby] # hobby is initialized to Coding
314-
])
315-
## ----
316-
317-
hobby.reset('Movie') # hobby is reset to Movie
318-
## ----
319-
hobby.append('Music', put_text('Drama')) # append Music, Drama to hobby
320-
## ----
321-
hobby.insert(0, put_markdown('**Coding**')) # insert the Coding into the top of the hobby
322-
323300
**Context Manager**
324301

325302
Some output functions that accept ``put_xxx()`` calls as content can be used as context manager:
@@ -525,11 +502,46 @@ The above code will generate the following scope layout::
525502
│ └─────────────────────┘ │
526503
└─────────────────────────┘
527504

505+
.. _put_scope:
506+
507+
**put_scope()**
508+
509+
We already know that the scope is a container of output content. So can we use this container as a sub-item
510+
of a output (like, set a cell in table as a container)? Yes, you can use `put_scope() <pywebio.output.put_scope>` to
511+
create a scope explicitly.
512+
The function name starts with ``put_``, which means it can be pass to the functions that accept ``put_xxx()`` calls.
513+
514+
.. exportable-codeblock::
515+
:name: put_scope
516+
:summary: `put_scope()`
517+
518+
put_table([
519+
['Name', 'Hobbies'],
520+
['Tom', put_scope('hobby', content=put_text('Coding'))] # hobby is initialized to coding
521+
])
522+
523+
## ----
524+
with use_scope('hobby', clear=True):
525+
put_text('Movie') # hobby is reset to Movie
526+
527+
## ----
528+
# append Music, Drama to hobby
529+
with use_scope('hobby'):
530+
put_text('Music')
531+
put_text('Drama')
532+
533+
## ----
534+
# insert the Coding into the top of the hobby
535+
put_markdown('**Coding**', scope='hobby', position=0)
536+
537+
538+
.. caution:: It is not allowed to have two scopes with the same name in the application.
539+
528540
**Scope control**
529541

530-
In addition to `use_scope() <pywebio.output.use_scope>`, PyWebIO also provides the following scope control functions:
542+
In addition to `use_scope() <pywebio.output.use_scope>` and `put_scope() <pywebio.output.put_scope>`,
543+
PyWebIO also provides the following scope control functions:
531544

532-
* `set_scope(name) <pywebio.output.set_scope>` : Create scope at current location(or specified location)
533545
* `clear(scope) <pywebio.output.clear>` : Clear the contents of the scope
534546
* `remove(scope) <pywebio.output.remove>` : Remove scope
535547
* `scroll_to(scope) <pywebio.output.scroll_to>` : Scroll the page to the scope

docs/spec.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,11 @@ Unique attributes of different types:
248248

249249
* input: input spec, same as the item of ``input_group.inputs``
250250

251+
* type: scope
252+
253+
* dom_id: the DOM id need to be set to this widget
254+
* contents list: list of output spec
255+
251256
pin_value
252257
^^^^^^^^^^^^^^^
253258

pywebio/output.py

Lines changed: 45 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,17 @@
1717
+--------------------+---------------------------+------------------------------------------------------------+
1818
| | **Name** | **Description** |
1919
+--------------------+---------------------------+------------------------------------------------------------+
20-
| Output Scope | `set_scope` | Create a new scope |
20+
| Output Scope | `put_scope` | Create a new scope |
2121
| +---------------------------+------------------------------------------------------------+
22-
| | `get_scope` | Get the scope name in the runtime scope stack |
22+
| | `use_scope`:sup:`†` | Enter a scope |
23+
| +---------------------------+------------------------------------------------------------+
24+
| | `get_scope` | Get the current scope name in the runtime scope stack |
2325
| +---------------------------+------------------------------------------------------------+
2426
| | `clear` | Clear the content of scope |
2527
| +---------------------------+------------------------------------------------------------+
2628
| | `remove` | Remove the scope |
2729
| +---------------------------+------------------------------------------------------------+
2830
| | `scroll_to` | Scroll the page to the scope |
29-
| +---------------------------+------------------------------------------------------------+
30-
| | `use_scope`:sup:`†` | Open or enter a scope |
3131
+--------------------+---------------------------+------------------------------------------------------------+
3232
| Content Outputting | `put_text` | Output plain text |
3333
| +---------------------------+------------------------------------------------------------+
@@ -85,8 +85,6 @@
8585
| +---------------------------+------------------------------------------------------------+
8686
| | `style`:sup:`*` | Customize the css style of output content |
8787
+--------------------+---------------------------+------------------------------------------------------------+
88-
| Placeholder | `output`:sup:`*` | Placeholder of output |
89-
+--------------------+---------------------------+------------------------------------------------------------+
9088
9189
Output Scope
9290
--------------
@@ -95,12 +93,12 @@
9593
9694
* :ref:`Use Guide: Output Scope <output_scope>`
9795
98-
.. autofunction:: set_scope
96+
.. autofunction:: put_scope
97+
.. autofunction:: use_scope
9998
.. autofunction:: get_scope
10099
.. autofunction:: clear
101100
.. autofunction:: remove
102101
.. autofunction:: scroll_to
103-
.. autofunction:: use_scope
104102
105103
Content Outputting
106104
-----------------------
@@ -207,9 +205,6 @@
207205
.. autofunction:: put_grid
208206
.. autofunction:: style
209207
210-
Placeholder
211-
--------------
212-
.. autofunction:: output
213208
214209
"""
215210
import html
@@ -232,7 +227,7 @@
232227

233228
logger = logging.getLogger(__name__)
234229

235-
__all__ = ['Position', 'remove', 'scroll_to', 'put_tabs',
230+
__all__ = ['Position', 'remove', 'scroll_to', 'put_tabs', 'put_scope',
236231
'put_text', 'put_html', 'put_code', 'put_markdown', 'use_scope', 'set_scope', 'clear', 'remove',
237232
'put_table', 'put_buttons', 'put_image', 'put_file', 'PopupSize', 'popup', 'put_button',
238233
'close_popup', 'put_widget', 'put_collapse', 'put_link', 'put_scrollable', 'style', 'put_column',
@@ -1390,10 +1385,31 @@ def put_grid(content, cell_width='auto', cell_height='auto', cell_widths=None, c
13901385
return put_widget(template=tpl, data=dict(contents=content), scope=scope, position=position)
13911386

13921387

1388+
@safely_destruct_output_when_exp('content')
1389+
def put_scope(name, content=[], scope=None, position=OutputPosition.BOTTOM) -> Output:
1390+
"""Output a scope
1391+
1392+
:param str name:
1393+
:param list/put_xxx() content: The initial content of the scope, can be ``put_xxx()`` or a list of it.
1394+
:param int scope, position: Those arguments have the same meaning as for `put_text()`
1395+
"""
1396+
if not isinstance(content, list):
1397+
content = [content]
1398+
1399+
assert is_html_safe_value(name), "Scope name only allow letter/digit/'_'/'-' char."
1400+
dom_id = scope2dom(name, no_css_selector=True)
1401+
1402+
spec = _get_output_spec('scope', dom_id=dom_id, contents=content, scope=scope, position=position)
1403+
return Output(spec)
1404+
1405+
13931406
@safely_destruct_output_when_exp('contents')
13941407
def output(*contents):
13951408
"""Placeholder of output
13961409
1410+
.. deprecated:: 1.5
1411+
See :ref:`User Guide <put_scope>` for new way to set css style for output.
1412+
13971413
``output()`` can be passed in anywhere that ``put_xxx()`` can passed in. A handler it returned by ``output()``,
13981414
and after being output, the content can also be modified by the handler (See code example below).
13991415
@@ -1431,6 +1447,10 @@ def output(*contents):
14311447
14321448
"""
14331449

1450+
import warnings
1451+
warnings.warn("`pywebio.output.output()` is deprecated since v1.5 and will remove in the future version, "
1452+
"use `pywebio.output.put_scope()` instead", DeprecationWarning, stacklevel=2)
1453+
14341454
class OutputHandler(Output):
14351455
"""
14361456
与 `Output` 的不同在于, 不会在销毁时(__del__)自动输出
@@ -1687,17 +1707,16 @@ def show_msg():
16871707
clear_scope = clear
16881708

16891709

1690-
def use_scope(name=None, clear=False, create_scope=True, **scope_params):
1691-
"""Open or enter a scope. Can be used as context manager and decorator.
1710+
def use_scope(name=None, clear=False, **kwargs):
1711+
"""use_scope(name=None, clear=False)
1712+
1713+
Open or enter a scope. Can be used as context manager and decorator.
16921714
16931715
See :ref:`User manual - use_scope() <use_scope>`
16941716
16951717
:param str name: Scope name. If it is None, a globally unique scope name is generated.
16961718
(When used as context manager, the context manager will return the scope name)
16971719
:param bool clear: Whether to clear the contents of the scope before entering the scope.
1698-
:param bool create_scope: Whether to create scope when scope does not exist.
1699-
:param scope_params: Extra parameters passed to `set_scope()` when need to create scope.
1700-
Only available when ``create_scope=True``.
17011720
17021721
:Usage:
17031722
@@ -1711,17 +1730,22 @@ def app():
17111730
put_xxx()
17121731
17131732
"""
1733+
# For backward compatible
1734+
# :param bool create_scope: Whether to create scope when scope does not exist.
1735+
# :param scope_params: Extra parameters passed to `set_scope()` when need to create scope.
1736+
# Only available when ``create_scope=True``.
1737+
create_scope = kwargs.pop('create_scope', True)
1738+
scope_params = kwargs
1739+
17141740
if name is None:
17151741
name = random_str(10)
17161742
else:
17171743
assert is_html_safe_value(name), "Scope name only allow letter/digit/'_'/'-' char."
17181744

17191745
def before_enter():
17201746
if create_scope:
1721-
set_scope(name, **scope_params)
1722-
1723-
if clear:
1724-
clear_scope(name)
1747+
if_exist = 'clear' if clear else None
1748+
set_scope(name, if_exist=if_exist, **scope_params)
17251749

17261750
return use_scope_(name=name, before_enter=before_enter)
17271751

test/template.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,19 @@ def edit_row(choice, row):
297297
hobby.append(put_text('Music'), put_text('Drama'))
298298
hobby.insert(0, put_markdown('**Coding**'))
299299

300+
put_table([
301+
['Name', 'Hobbies'],
302+
['Tom', put_scope('hobby', content=put_text('Coding'))]
303+
])
304+
305+
with use_scope('hobby', clear=True):
306+
put_text('Movie') # hobby is reset to Movie
307+
308+
with use_scope('hobby'):
309+
put_text('Music')
310+
put_text('Drama')
311+
312+
put_markdown('**Coding**', scope='hobby', position=0)
300313

301314

302315
def background_output():

webiojs/src/handlers/output.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,27 @@ import {body_scroll_to} from "../utils";
44

55
import {getWidgetElement} from "../models/output"
66
import {CommandHandler} from "./base";
7-
import {AfterPinShow} from "../models/pin";
87

98
const DISPLAY_NONE_TAGS = ['script', 'style'];
109

10+
let after_show_callbacks: (() => void) [] = [];
11+
12+
// register a callback to execute after the current output widget showing
13+
export function AfterCurrentOutputWidgetShow(callback: () => void){
14+
after_show_callbacks.push(callback);
15+
}
16+
17+
export function trigger_output_widget_show_event() {
18+
for (let cb of after_show_callbacks) {
19+
try {
20+
cb.call(this);
21+
} catch (e) {
22+
console.error('Error in callback of pin widget show event.');
23+
}
24+
}
25+
after_show_callbacks = [];
26+
}
27+
1128
export class OutputHandler implements CommandHandler {
1229
session: Session;
1330

@@ -79,7 +96,7 @@ export class OutputHandler implements CommandHandler {
7996
else if (state.AutoScrollBottom && output_to_root)
8097
this.scroll_bottom();
8198
}
82-
AfterPinShow();
99+
trigger_output_widget_show_event();
83100
} else if (msg.command === 'output_ctl') {
84101
this.handle_output_ctl(msg);
85102
}

webiojs/src/handlers/popup.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import {Command, Session} from "../session";
22

33
import {render_tpl} from "../models/output"
44
import {CommandHandler} from "./base";
5-
import {AfterPinShow} from "../models/pin";
5+
import {trigger_output_widget_show_event} from "./output";
66

77

88
export class PopupHandler implements CommandHandler {
@@ -32,7 +32,7 @@ export class PopupHandler implements CommandHandler {
3232

3333
let elem = PopupHandler.get_element(msg.spec);
3434
this.body.append(elem);
35-
AfterPinShow();
35+
trigger_output_widget_show_event();
3636

3737
// 弹窗关闭后就立即销毁
3838
elem.on('hidden.bs.modal', function (e) {

webiojs/src/i18n.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ const translations: { [lang: string]: { [msgid: string]: string } } = {
1414
"submit": "Submit",
1515
"reset": "Reset",
1616
"cancel": "Cancel",
17-
"duplicated_pin_name": "This pin widget has expired (due to the output of a new pin widget with the same name ).",
17+
"duplicated_pin_name": "This pin widget has expired (due to the output of a new pin widget with the same name).",
1818
"browse_file": "Browse",
19+
"duplicated_scope_name": "Error: The name of this scope is duplicated with the previous one!",
1920
},
2021
"zh": {
2122
"disconnected_with_server": "与服务器连接已断开,请刷新页面重新操作",
@@ -28,6 +29,7 @@ const translations: { [lang: string]: { [msgid: string]: string } } = {
2829
"cancel": "取消",
2930
"duplicated_pin_name": "该 Pin widget 已失效(由于输出了新的同名 pin widget)",
3031
"browse_file": "浏览文件",
32+
"duplicated_scope_name": "错误: 此scope与已有scope重复!",
3133
},
3234
"ru": {
3335
"disconnected_with_server": "Соединение с сервером потеряно, пожалуйста перезагрузите страницу",

0 commit comments

Comments
 (0)