|
1 | 1 | import sys |
2 | 2 | import logging |
| 3 | +import inspect |
| 4 | + |
3 | 5 | from ..utils.defaults import TrameDefault |
4 | 6 | from ..utils.formatter import to_pretty_html |
5 | 7 |
|
|
67 | 69 | logger = logging.getLogger(__name__) |
68 | 70 |
|
69 | 71 |
|
| 72 | +def can_be_decorated(x): |
| 73 | + return inspect.ismethod(x) or inspect.isfunction(x) |
| 74 | + |
| 75 | + |
70 | 76 | def py2js_key(key): |
71 | 77 | return key.replace("_", "-") |
72 | 78 |
|
@@ -211,7 +217,102 @@ def __call__(self, layout=None, **kwargs): |
211 | 217 | HTML_CTX.add_child(self) |
212 | 218 |
|
213 | 219 |
|
214 | | -class AbstractElement: |
| 220 | +class TrameComponent: |
| 221 | + """ |
| 222 | + Base trame class that has access to a trame server instance |
| 223 | + on which we provide simple accessor and method decoration capabilities. |
| 224 | + """ |
| 225 | + |
| 226 | + def __init__(self, server, ctx_name=None, **_): |
| 227 | + """ |
| 228 | + Initialize TrameComponent with its server. |
| 229 | +
|
| 230 | + Keyword arguments: |
| 231 | + server -- the server to link to (default None) |
| 232 | + ctx_name -- name to use to bind current instance to server.context (default None) |
| 233 | + """ |
| 234 | + self._server = server |
| 235 | + |
| 236 | + if ctx_name: |
| 237 | + self.ctx[ctx_name] = self |
| 238 | + |
| 239 | + self._bind_annotated_methods() |
| 240 | + |
| 241 | + @property |
| 242 | + def server(self): |
| 243 | + """Return the associated trame server instance""" |
| 244 | + return self._server |
| 245 | + |
| 246 | + @property |
| 247 | + def state(self): |
| 248 | + """Return the associated server state""" |
| 249 | + return self.server.state |
| 250 | + |
| 251 | + @property |
| 252 | + def ctrl(self): |
| 253 | + """Return the associated server controller""" |
| 254 | + return self.server.controller |
| 255 | + |
| 256 | + @property |
| 257 | + def ctx(self): |
| 258 | + """Return the associated server context""" |
| 259 | + return self.server.context |
| 260 | + |
| 261 | + def _bind_annotated_methods(self): |
| 262 | + # Look for method decorator |
| 263 | + for k in inspect.getmembers(self.__class__, can_be_decorated): |
| 264 | + fn = getattr(self, k[0]) |
| 265 | + |
| 266 | + # Handle @state.change |
| 267 | + s_translator = self.state.translator |
| 268 | + if "_trame_state_change" in fn.__dict__: |
| 269 | + state_change_names = fn.__dict__["_trame_state_change"] |
| 270 | + logger.debug( |
| 271 | + f"state.change({[f'{s_translator.translate_key(v)}' for v in state_change_names]})({k[0]})" |
| 272 | + ) |
| 273 | + self.state.change(*[f"{v}" for v in state_change_names])(fn) |
| 274 | + |
| 275 | + # Handle @trigger |
| 276 | + if "_trame_trigger_names" in fn.__dict__: |
| 277 | + trigger_names = fn.__dict__["_trame_trigger_names"] |
| 278 | + for trigger_name in trigger_names: |
| 279 | + logger.debug(f"trigger({trigger_name})({k[0]})") |
| 280 | + self.server.trigger(f"{trigger_name}")(fn) |
| 281 | + |
| 282 | + # Handle @ctrl.[add, once, add_task, set] |
| 283 | + if "_trame_controller" in fn.__dict__: |
| 284 | + actions = fn.__dict__["_trame_controller"] |
| 285 | + for action in actions: |
| 286 | + name = action.get("name") |
| 287 | + method = action.get("method") |
| 288 | + decorate = getattr(self.ctrl, method) |
| 289 | + logger.debug(f"ctrl.{method}({name})({k[0]})") |
| 290 | + decorate(name)(fn) |
| 291 | + |
| 292 | + def _unbind_annotated_methods(self): |
| 293 | + # Look for method decorator |
| 294 | + for k in inspect.getmembers(self.__class__, can_be_decorated): |
| 295 | + fn = getattr(self, k[0]) |
| 296 | + |
| 297 | + # Handle @state.change |
| 298 | + methods_to_detach = {} |
| 299 | + if "_trame_state_change" in fn.__dict__: |
| 300 | + methods_to_detach.add(fn) |
| 301 | + |
| 302 | + if methods_to_detach: |
| 303 | + for fn_list in self.state._change_callbacks.values(): |
| 304 | + to_remove = set(fn_list) | methods_to_detach |
| 305 | + for fn in to_remove: |
| 306 | + fn_list.remove(fn) |
| 307 | + |
| 308 | + # Handle @trigger |
| 309 | + # TODO |
| 310 | + |
| 311 | + # Handle @ctrl |
| 312 | + # TODO |
| 313 | + |
| 314 | + |
| 315 | +class AbstractElement(TrameComponent): |
215 | 316 | """ |
216 | 317 | A Vue component which can integrate with the rest of trame |
217 | 318 |
|
@@ -266,12 +367,19 @@ class AbstractElement: |
266 | 367 |
|
267 | 368 | >>> print(html.Template(raw_attrs=["v-slot:item.1", 'class="bg-red"', '@click.stop="a=2"'])) |
268 | 369 | ... <Template v-slot:item.1 class="bg-red" @click.stop="a=2" /> |
| 370 | +
|
| 371 | + Context Name: |
| 372 | +
|
| 373 | + :param ctx_name: name to attach instance to server.context if provided |
| 374 | +
|
269 | 375 | """ |
270 | 376 |
|
271 | 377 | _next_id = 1 |
272 | 378 | _debug = "--debug" in sys.argv or "-d" in sys.argv |
273 | 379 |
|
274 | | - def __init__(self, _elem_name, children=None, raw_attrs=None, **kwargs): |
| 380 | + def __init__( |
| 381 | + self, _elem_name, children=None, raw_attrs=None, ctx_name=None, **kwargs |
| 382 | + ): |
275 | 383 | AbstractElement._next_id += 1 |
276 | 384 | self._id = AbstractElement._next_id |
277 | 385 | self._server = kwargs.get("trame_server") |
@@ -304,6 +412,8 @@ def __init__(self, _elem_name, children=None, raw_attrs=None, **kwargs): |
304 | 412 | # Add ourself to context if any |
305 | 413 | HTML_CTX.add_child(self) |
306 | 414 |
|
| 415 | + super().__init__(self._server, ctx_name=ctx_name) |
| 416 | + |
307 | 417 | def _attr_str(self): |
308 | 418 | return " ".join(self._attributes.values()) |
309 | 419 |
|
@@ -332,30 +442,10 @@ def register_directive(py_name, js_name=None): |
332 | 442 | # App associated to HTML element |
333 | 443 | # ------------------------------------------------------------------------- |
334 | 444 |
|
335 | | - @property |
336 | | - def server(self): |
337 | | - """Return the associated server""" |
338 | | - return self._server |
339 | | - |
340 | 445 | def set_server(self, v): |
341 | 446 | """Update the associated server""" |
342 | 447 | self._server = v |
343 | 448 |
|
344 | | - @property |
345 | | - def state(self): |
346 | | - """Return the associated server state""" |
347 | | - return self.server.state |
348 | | - |
349 | | - @property |
350 | | - def ctrl(self): |
351 | | - """Return the associated server controller""" |
352 | | - return self.server.controller |
353 | | - |
354 | | - @property |
355 | | - def ctx(self): |
356 | | - """Return the associated server context""" |
357 | | - return self.server.context |
358 | | - |
359 | 449 | # ------------------------------------------------------------------------- |
360 | 450 | # Buildin API |
361 | 451 | # ------------------------------------------------------------------------- |
|
0 commit comments