I made an example code without IDomTree, an easier observer pattern in appcode change and for the moment, no task kill.
An other difference is that in my initial code, component where created at each render call. Component cache is under work. I think IDomTree has lot of circular reference.
I will work with Custom Pickler to determine which object are serialized / deserialized.
Thank you for your help Alain.
To hide debug refresh button and debug border, put @debug_tools( activate_border = False, activate_refresh_button = False )
To add appcode, find xrange and modify second argument. In this example, only 2 app codes
To modify random entity number, find random and modify second argument. In this example, random max is 20.
On click on "Poke", a validation is asked.
If you modify appcode, and return back, validation waiting. It's here I want to add kill task.
And for that, I need father - child relations to determine which task to kill on father change.
def force_wrapper_to_generate( f ):
@functools.wraps( f )
def wrapped( self, h, comp, *args, **kwargs ):
h.wrapper_to_generate = True
result = f( self, h, comp, *args, **kwargs )
def get_id( self ):
return self._id
comp.__class__.id_ = property( get_id, None, None, None )
return result
return wrapped
def create_debug_color_generator():
debug_colors = [ '#FF0000', '#FFFF00', '#FF00FF', '#00FF00', '#00FFFF' , '#0000FF' ]
while True:
for color in debug_colors:
yield color
debug_color_generator = create_debug_color_generator()
def fake_debug_action():
pass
def _debug_tools_add_border_to_render( activate, color ):
def wrapper( f ):
@functools.wraps( f )
def wrapped( self, h, *args, **kwargs ):
if activate:
return h.div(
f( self, h, *args, **kwargs ),
style = 'border: dashed 2px %s; padding: 1px; margin: 3px ;' % color
)
return f( self, h, *args, **kwargs )
return wrapped
return wrapper
def _debug_tools_add_refresh_button_to_render( activate, color ):
def wrapper( f ):
@functools.wraps( f )
def wrapped( self, h, *args, **kwargs ):
if activate:
with h.form():
h << h.input(
type = 'submit',
value = 'ajax refresh component %s %s' % ( self.__class__.__name__, id_ ),
style = 'background-color: %s' % color
).action( lambda *args: None )
return f( self, h, *args, **kwargs )
return wrapped
return wrapper
def debug_tools( activate_border, activate_refresh_button ):
def wrapper( f ):
@functools.wraps( f )
def wrapped( self, h, *args, **kwargs ):
color = debug_color_generator.next()
return _debug_tools_add_border_to_render( activate_border, color )(
_debug_tools_add_refresh_button_to_render( activate_refresh_button, color )( f )
)( self, h, *args, **kwargs )
return wrapped
return wrapper
class UpdatesWithLambdaUpdate(
ajax.Update,
):
def __init__(
self,
*updates_or_le,
**kw
):
self._updates_or_le = updates_or_le
self._with_request = kw.get('with_request', False)
self._action = kw.get('action', lambda *args: None)
super(UpdatesWithLambdaUpdate, self).__init__(action=self.action, with_request=True)
def action(self, request, response, *args):
if self._with_request:
self._action(request, response, *args)
else:
self._action(*args)
updates = []
for e in self._updates_or_le:
if callable( e ):
e = e()
if isinstance(e, collections.Iterable):
for update in e:
updates.append( update )
else:
updates.append( e )
else:
updates.append( e )
for update in updates:
if update.with_request:
update.action(request, response, *args)
else:
update.action(*args)
def _generate_render( self, renderer ):
renders = [ super( UpdatesWithLambdaUpdate, self )._generate_render( renderer ) ]
def ViewsToJs( r ):
updates = []
for e in self._updates_or_le:
if callable( e ):
e = e()
if isinstance(e, collections.Iterable):
for update in e:
updates.append( update )
else:
updates.append( e )
for update in updates:
renders.append( update._generate_render( r ) )
return ajax.ViewsToJs( [ render( r ) for render in renders ] )
return lambda r: ViewsToJs( r )
class Demo(object):
def __init__( self ):
self._cp_upper = component.Component( Upper() )
def get_cp_upper( self ):
return self._cp_upper
cp_upper = property( get_cp_upper, None, None, None )
@presentation.render_for(Demo)
@force_wrapper_to_generate
def render(self, h, comp, *args, **kwargs):
h << self.cp_upper.render( xhtml.AsyncRenderer( h ) )
return h.root
class Upper( object ):
def __init__( self ):
self._cp_appcode_selector = component.Component( AppcodeSelector( 'A0' ) )
self._cp_entities = component.Component(
Entities(
'A0',
lambda: [
appcode
for appcode, select_option
in self.cp_appcode_selector.o.appcodes
]
)
)
def get_cp_appcode_selector( self ):
return self._cp_appcode_selector
cp_appcode_selector = property( get_cp_appcode_selector, None, None, None )
def get_cp_entities( self ):
return self._cp_entities
cp_entities = property( get_cp_entities, None, None, None )
@presentation.render_for( Upper )
@force_wrapper_to_generate
@debug_tools( activate_border = True, activate_refresh_button = True )
def render( self, h, comp, *args, **kwargs ):
self.cp_appcode_selector.o.clean(
AppcodeSelector.EVT_APPCODE_UPDATED
)
def update_selected_appcode_for_entities( appcode ):
self.cp_entities.o.set_selected_appcode( appcode )
with h.form():
h << h.input( type = 'submit', value = 'Full page refresh' )
h << 'Distributions'
with h.div( style = 'display: table ; border-spacing: 15px 0px; border: solid 2px ;' ):
for appcode, select_option in self.cp_appcode_selector.o.appcodes:
h << h.div(
'%s : %s' % ( appcode, Entities.get_multiplicity( appcode ) ),
style = 'display: table-cell ;'
)
ar_cp_appcode_selector = xhtml.AsyncRenderer( h )
h << self.cp_appcode_selector.render( ar_cp_appcode_selector )
ar_cp_entities = xhtml.AsyncRenderer( h )
h << self.cp_entities.render( xhtml.AsyncRenderer( h ) )
self.cp_appcode_selector.o.register(
AppcodeSelector.EVT_APPCODE_UPDATED,
self.cp_entities,
update_selected_appcode_for_entities
)
return h.root
class AppcodeSelector(object):
EVT_APPCODE_UPDATED = 'EVT_APPCODE_UPDATED'
EVTS = [ EVT_APPCODE_UPDATED ]
def __init__( self, appcode, *args, **kwargs ):
self._index_generator = xrange( 0, 2 )
self._selected_appcode = appcode
self._d_evt = dict( ( evt_name, [] ) for evt_name in AppcodeSelector.EVTS )
def get_selected_appcode( self ):
return self._selected_appcode
def set_selected_appcode( self, appcode ):
self._selected_appcode = appcode
# Call to registred lambda expression
for cp, le in self.d_evt[ AppcodeSelector.EVT_APPCODE_UPDATED ]:
le( appcode )
selected_appcode = property( get_selected_appcode, set_selected_appcode, None, None )
def get_d_evt( self ):
return self._d_evt
d_evt = property( get_d_evt, None, None, None )
def get_appcodes( self ):
def format_to_appcode( index ):
return 'A%s' % index
return [
(
format_to_appcode( index ),
{
'selected': None
}
if self.selected_appcode == format_to_appcode( index )
else
{}
)
for index
in self._index_generator
]
appcodes = property( get_appcodes, None, None, None )
def register( self, evt_name, cp, le ):
assert( evt_name in AppcodeSelector.EVTS ), u'''%s must be part of %s''' % ( evt_name, AppcodeSelector.EVTS )
self.d_evt[ evt_name ].append( ( cp, le ) )
def clean( self, evt_name ):
assert( evt_name in AppcodeSelector.EVTS ), u'''%s must be part of %s''' % ( evt_name, AppcodeSelector.EVTS )
del( self.d_evt[ evt_name ][ : ] )
@presentation.render_for(AppcodeSelector)
@force_wrapper_to_generate
@debug_tools( activate_border = True, activate_refresh_button = True )
def render(self, h, comp, *args, **kwargs):
v_selected_appcode = var.Var()
def update_selected_appcode():
self.selected_appcode = v_selected_appcode()
h << u'''Current Appcode %s''' % self.selected_appcode
h << u'''Select another Appcode'''
with h.form():
with h.select() as selected_appcode:
for appcode in self.appcodes:
h << h.option( appcode[0], **appcode[1] )
selected_appcode.action( v_selected_appcode )
#h << h.input(
# type = 'submit',
# value = u'''Load with action'''
#).action( update_selected_appcode )
def get_associated_updates():
return [
ajax.Update(
lambda r: cp.render( xhtml.AsyncRenderer( r ) ),
lambda *args: None,
cp.id_
)
for cp, le
in self.d_evt[ AppcodeSelector.EVT_APPCODE_UPDATED ]
]
h << h.input(
type = 'submit',
value = u'''Load with Updates'''
).action(
UpdatesWithLambdaUpdate(
ajax.Update(
lambda r: comp.render( xhtml.AsyncRenderer( r ) ),
lambda *args: None,
None
),
get_associated_updates,
action = update_selected_appcode
)
)
return h.root
class Entities( object ):
_d_multiplicity_by_appcode = {}
def __init__( self, selected_appcode, le_authorized_appcodes ):
self._selected_appcode = selected_appcode
self._le_authorized_appcodes = le_authorized_appcodes
self._d_task = dict(
[
(
appcode,
[
component.Component( PokeTask( appcode, i ) )
for i in range( 1, Entities.get_multiplicity( appcode ) + 1 )
]
) for appcode in self._le_authorized_appcodes()
]
)
def get_selected_appcode( self ):
return self._selected_appcode
def set_selected_appcode( self, appcode ):
self._selected_appcode = appcode
selected_appcode = property( get_selected_appcode, set_selected_appcode, None, None )
@classmethod
def get_multiplicity( cls, appcode ):
return cls._d_multiplicity_by_appcode.setdefault( appcode, random.randint( 1, 20 ) )
def get_cp_tasks( self ):
if self._d_task.has_key( self.selected_appcode ):
return self._d_task[ self.selected_appcode ]
else:
return self._d_task.setdefault(
self.selected_appcode,
[
component.Component( PokeTask( self.selected_appcode, i )
for i
in range( 1, Entities.get_multiplicity( appcode ) + 1 ) )
]
)
cp_tasks = property( get_cp_tasks, None, None, None )
@presentation.render_for(Entities)
@force_wrapper_to_generate
@debug_tools( activate_border = True, activate_refresh_button = True )
def render(self, h, comp, *args, **kwargs):
h << h.div( 'Current appcode : %s' % self.selected_appcode )
h << h.div( 'Current entities count : %s' % Entities.get_multiplicity( self.selected_appcode ) )
with h.div( style = 'display: table ;' ):
for cp_task in self.cp_tasks:
h << h.div(
cp_task.render( xhtml.AsyncRenderer( h ) ),
style = 'display: table-cell ;'
)
return h.root
class PokeTask(
component.Task
):
def __init__( self, appcode, index ):
self._appcode = appcode
if callable(index):
self._index = index()
else:
self._index = index
self._pokeit = PokeIt( self.appcode, self.index )
self._cp_pokeit = component.Component( self._pokeit )
def get_appcode( self ):
return self._appcode
appcode = property( get_appcode, None, None, None )
def get_index( self ):
return self._index
index = property( get_index, None, None, None )
def get_cp_pokeit( self ):
return self._cp_pokeit
cp_pokeit = property( get_cp_pokeit, None, None, None )
def go( self, comp ):
while True:
comp.call( self.cp_pokeit )
if comp.call( self.cp_pokeit, model = 'validate' ):
print True
else:
print False
class PokeIt( object ):
def __init__( self, appcode, index ):
self._appcode = appcode
self._index = index
def get_appcode( self ):
return self._appcode
appcode = property( get_appcode, None, None, None )
def get_index( self ):
return self._index
index = property( get_index, None, None, None )
@presentation.render_for(PokeIt)
@force_wrapper_to_generate
@debug_tools( activate_border = True, activate_refresh_button = True )
def render(self, h, comp, *args, **kwargs):
with h.form():
h << h.input(
type = 'submit',
value = 'Poke %s %s' % ( self.appcode, self.index )
).action( lambda: comp.answer() )
return h.root
@presentation.render_for(PokeIt, model = 'validate' )
@force_wrapper_to_generate
@debug_tools( activate_border = True, activate_refresh_button = True )
def render(self, h, comp, *args, **kwargs):
with h.form( style = 'display: table ;' ):
h << h.input(
type = 'submit',
value = 'Validate',
style = 'display: table-cell'
).action( lambda: comp.answer( True ) )
h << h.input(
type = 'submit',
value = 'Cancel',
style = 'display: table-cell'
).action( lambda: comp.answer( False ) )
return h.root
# ---------------------------------------------------------------
app = Demo