1
0

io_ctrl.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. import asyncio
  2. import inspect
  3. import logging
  4. from .framework import Global, Task
  5. from .framework import WebIOFuture
  6. from .ioloop import run_async
  7. logger = logging.getLogger(__name__)
  8. def send_msg(cmd, spec=None):
  9. msg = dict(command=cmd, spec=spec, coro_id=Global.active_coro_id)
  10. Global.active_ws.send_coro_msg(msg)
  11. async def next_event():
  12. res = await WebIOFuture()
  13. return res
  14. async def single_input(item_spec, valid_func, preprocess_func):
  15. """
  16. Note: 鲁棒性在上层完成
  17. 将单个input构造成input_group,并获取返回值
  18. :param item_spec: 单个输入项的参数 'name' must in item_spec, 参数一定已经验证通过
  19. :param valid_func: Not None
  20. :param preprocess_func: Not None
  21. """
  22. label = item_spec['label']
  23. name = item_spec['name']
  24. # todo 是否可以原地修改spec
  25. item_spec['label'] = ''
  26. item_spec.setdefault('auto_focus', True) # 如果没有设置autofocus参数,则开启参数 todo CHECKBOX, RADIO 特殊处理
  27. spec = dict(label=label, inputs=[item_spec])
  28. data = await input_control(spec, {name: preprocess_func}, {name: valid_func})
  29. return data[name]
  30. async def input_control(spec, preprocess_funcs, item_valid_funcs, form_valid_funcs=None):
  31. """
  32. 发送input命令,监听事件,验证输入项,返回结果
  33. :param spec:
  34. :param preprocess_funcs: keys 严格等于 spec中的name集合
  35. :param item_valid_funcs: keys 严格等于 spec中的name集合
  36. :param form_valid_funcs:
  37. :return:
  38. """
  39. send_msg('input_group', spec)
  40. data = await input_event_handle(item_valid_funcs, form_valid_funcs, preprocess_funcs)
  41. send_msg('destroy_form')
  42. return data
  43. def check_item(name, data, valid_func, preprocess_func):
  44. try:
  45. data = preprocess_func(data)
  46. error_msg = valid_func(data)
  47. except:
  48. # todo log warning
  49. error_msg = '字段内容不合法'
  50. if error_msg is not None:
  51. send_msg('update_input', dict(target_name=name, attributes={
  52. 'valid_status': False,
  53. 'invalid_feedback': error_msg
  54. }))
  55. return False
  56. return True
  57. async def input_event_handle(item_valid_funcs, form_valid_funcs, preprocess_funcs):
  58. """
  59. 根据提供的校验函数处理表单事件
  60. :param item_valid_funcs: map(name -> valid_func) valid_func 为 None 时,不进行验证
  61. valid_func: callback(data) -> error_msg or None
  62. :param form_valid_funcs: callback(data) -> (name, error_msg) or None
  63. :param preprocess_funcs:
  64. :return:
  65. """
  66. while True:
  67. event = await next_event()
  68. event_name, event_data = event['event'], event['data']
  69. if event_name == 'input_event':
  70. input_event = event_data['event_name']
  71. if input_event == 'blur':
  72. onblur_name = event_data['name']
  73. check_item(onblur_name, event_data['value'], item_valid_funcs[onblur_name],
  74. preprocess_funcs[onblur_name])
  75. elif event_name == 'from_submit':
  76. all_valid = True
  77. # 调用输入项验证函数进行校验
  78. for name, valid_func in item_valid_funcs.items():
  79. if not check_item(name, event_data[name], valid_func, preprocess_funcs[name]):
  80. all_valid = False
  81. if all_valid: # todo 减少preprocess_funcs[name]调用次数
  82. data = {name: preprocess_funcs[name](val) for name, val in event_data.items()}
  83. # 调用表单验证函数进行校验
  84. if form_valid_funcs:
  85. v_res = form_valid_funcs(data)
  86. if v_res is not None:
  87. all_valid = False
  88. onblur_name, error_msg = v_res
  89. send_msg('update_input', dict(target_name=onblur_name, attributes={
  90. 'valid_status': False,
  91. 'invalid_feedback': error_msg
  92. }))
  93. if all_valid:
  94. break
  95. else:
  96. logger.warning("Unhandled Event: %s", event)
  97. return data
  98. def output_register_callback(callback, save, mutex_mode):
  99. """
  100. 为输出区显示的控件注册回调函数
  101. 原理:
  102. 向框架注册一个新协程,在协程内对回调函数进行调用 callback(widget_data, save)
  103. 协程会在用户与控件交互时触发
  104. :return: 协程id
  105. """
  106. async def callback_coro():
  107. while True:
  108. event = await next_event()
  109. assert event['event'] == 'callback'
  110. coro = None
  111. if asyncio.iscoroutinefunction(callback):
  112. coro = callback(event['data'], save)
  113. elif inspect.isgeneratorfunction(callback):
  114. coro = asyncio.coroutine(callback)(save, event['data'])
  115. else:
  116. try:
  117. callback(event['data'], save)
  118. except:
  119. Global.active_ws.on_coro_error()
  120. if coro is not None:
  121. if mutex_mode:
  122. await coro
  123. else:
  124. run_async(coro)
  125. callback_task = Task(callback_coro(), Global.active_ws)
  126. callback_task.coro.send(None) # 激活,Non't callback.step() ,导致嵌套调用step todo 与inactive_coro_instances整合
  127. Global.active_ws.coros[callback_task.coro_id] = callback_task
  128. return callback_task.coro_id