Escreva componentes HTML diretamente no Python e você terá uma mistura bonita, mas controversa.
Sim, controverso .
Se você não gosta, ignore-o (mas pode usar isso sem a parte HTML-in-Python, veja abaixo;))
Baseado em Pyxl. Python 3.6+ somente e use a digitação para validação de dados.
Depois de ter seu HTML, você pode fazer o que quiser com ele. Pense nisso como um substituto para o seu mecanismo de modelo clássico.
Código fonte : https://github.com/twidi/mixt/
Documentação : https://twidi.github.io/mixt/
Pypi : https://pypi.org/project/mixt/
CI (Circleci): https://circleci.com/gh/twidi/workflows/mixt/
Vamos criar um example.py de arquivo.py
# coding: mixt
from mixt import html , Element , Required
class Hello ( Element ):
class PropTypes :
name : Required [ str ]
def render ( self , context ):
return < div > Hello , { self . name } < / div >
print ( < Hello name = "World" / > )E executá -lo:
$ python example.py
< div > Hello, World < /div >Se você não gosta de escrever HTML em Python, ainda pode usá -lo:
from mixt import html , Element , Required
class Hello ( Element ):
class PropTypes :
name : Required [ str ]
def render ( self , context ):
return html . Div ()( "Hello, " , self . name )
print ( Hello ( name = "World" ))Sim, é inspirado pelo React (de fato, principalmente JSX) e emprestamos parte do conceito:
E acrescentamos:
Execute esses dois comandos. O segundo dirá ao Python como entender os arquivos com html dentro.
pip install mixt
mixt-post-installPara verificar se tudo está pronto, execute:
python -m mixt.examples.simpleVocê deve ter esta saída:
< div title =" Greeting " > Hello, World </ div > Se você não quiser usar as coisas HTML-in-Python, não execute mixt-post-install . E depois teste com (para ter a mesma saída):
python -m mixt.examples.simple_pure_pythonClone o projeto Git então:
make devPara verificar se tudo está pronto, execute:
python -m mixt.examples.simpleVocê deve ter esta saída:
< div title =" Greeting " > Hello, World </ div >Depois de ter feito algum código:
make testsmake lint Se você tocar nas coisas no diretório codec , precisará executar make dev (ou pelo menos make full-clean ) para limpar os arquivos pyc Python.
Observe que nosso CI verificará se todo comprometimento passa o make lint , make tests e make check-doc . Portanto, não se esqueça de executá -las para cada compromisso.
Uma maneira de fazer isso antes de pressionar é:
git rebase develop --exec ' git log -n 1; make checks ' NOTA: Você pode encontrar o código final deste guia do usuário em src/mixt/examples/user_guide (você encontrará mixt.py e pure_python.py ).
Execute -o com:
python -m mixt.examples.user_guideVamos criar uma ... lista de tarefas, sim!
Mas antes, lembre -se. Isso não é reagir, não está no navegador e não há JavaScript envolvido aqui. Só falamos sobre renderizar um pouco de HTML.
Mas você pode fazer o que quiser com ele. Adicione manipuladores JavaScript, formas simples ...
Falando sobre formas ...
Em uma lista de tarefas, queremos poder adicionar um TODO. É uma entrada de texto simples.
Então, vamos criar nosso primeiro componente, o TodoForm . Queremos um formulário com um texto de entrada e um botão.
Um componente é uma subclasse da classe Element , com um método render que você precisa escrever.
# coding: mixt
from mixt import Element , html # html is mandatory to resolve html tags
class TodoForm ( Element ):
def render ( self , context ): # Ignore the ``context`` argument for now.
return # The ```` is only for a better indentation below
< form method = "post" action = "???" >
< label > New Todo : < / label > < itext name = "todo" / >
< button type = "submit" > Add < / button >
< / form >Observe que isso poderia ter sido escrito como uma função simples:
# coding: mixt
from mixt import Element , html
def TodoForm ():
return
< form method = "post" action = "???" >
< label > New Todo : < / label > < itext name = "todo" / >
< button type = "submit" > Add < / button >
< / form >Ao imprimir o componente, esses dois darão o mesmo resultado:
print ( < TodoForm / > ) < form method =" post " action =" ??? " > < label > New Todo: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form >Observe como é formatado: sem espaço entre as tags. De fato, é como em JSX:
O JSX remove o espaço em branco no início e no final de uma linha. Ele também remove linhas em branco. Novas linhas adjacentes às tags são removidas; Novas linhas que ocorrem no meio dos literais de cordas são condensadas em um único espaço
Para adicionar um espaço, ou uma nova linha, você pode passar um pouco de python. Vamos, como exemplo, adicionar uma nova linha antes do rótulo:
#...
< form method = "post" action = "???" >
{ ' n ' } < label > New Todo : < / label > < itext name = "todo" / >
#...Agora temos esta saída:
< form method =" post " action =" /todo/add " >
< label > New Todo: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form >Agora vamos mais longe.
Observe o atributo action do formulário. Precisamos passar algo. Mas o codificador não parece certo. A WWE precisa passar para o componente.
Mixt tem, como React , o conceito de propriedades, também conhecido como "adereços".
No Mixt , definimos -os com um tipo, em uma classe dentro do nosso componente, denominada PropTypes :
class TodoForm ( Element ):
class PropTypes :
add_url : str
def render ( self , context ):
return
< form method = "post" action = { self . add_url } >
< label > New Todo : < / label > < itext name = "todo" / >
< button type = "submit" > Add < / button >
< / form > Aqui, definimos um suporte chamado add_url que deve ser uma string ( str ). Isso usa a sintaxe de digitação python.
E observe como alteramos o atributo action da tag form . Agora é {self.add_url} em vez de "???" .
Quando os atributos são passados entre os aparelhos encaracolados, eles são interpretados como python puro em tempo de execução. De fato, como o analisador mixt converterá todo o arquivo em python puro antes de deixar o intérprete do python que o executará, ele permanecerá o mesmo, apenas o HTML em torno será convertido. Portanto, não há penalidade para fazer isso.
Veja como isso seria se nosso componente fosse escrito em python puro:
from mixt import Element , html
class TodoForm ( Element ):
class PropTypes :
add_url : str
def render ( self , context ):
return html . Form ( method = 'post' , action = self . add_url )(
html . Label ()(
html . Raw ( "New Todo: " )
),
html . InputText ( name = 'todo' ),
html . Button ( type = 'submit' )(
html . Raw ( "Add" ) # or html.Rawhtml(text="Add")
),
) Observe como os adereços são passados para um componente como argumentos nomeados e como action é aprovada: action=self.add_url .
Este componente python puro também mostra como funciona: os adereços são passados como argumento nomeado para a classe de componentes; em seguida, esse componente é chamado, passando componentes infantis como argumentos posicionais para a chamada:
ComponentClass ( prop1 = "val1" , prop2 = "val2" )(
Children1 (),
Children2 (),
)O que são crianças? As crianças são tags dentro de outras tags.
Em <div id="divid"><span /><p>foo</p></div> , temos:
html.Div , com um id de suporte e dois filhos:html.Span , sem filhoshtml.P com uma criança:html.RawHtml com o texto "foo"Observe que você pode brincar com adereços e crianças. Primeiro, a versão em python pura para mostrar como funciona:
def render ( self , context ):
props = { "prop1" : "val1" , "prop2" : "val2" }
children = [ Children1 (), Children2 ()]
return ComponentClass ( ** props )( * children )
# You can pass a list of children to to the call, so this would produce the same result:
# ComponentClass(**props)(children) Então a versão mixt :
def render ( self , context ):
props = { "prop1" : "val1" , "prop2" : "val2" }
children = [ < Children1 / > , < Children2 / > ]
return < ComponentClass { ** props } > { * children } < / ComponentClass >
# or, the same, passing the children as a list:
# return <ComponentClass {**props}>{children}</ComponentClass> Agora vamos voltar aos nossos adereços add_url .
Como passar para o componente?
Exatamente da mesma maneira que passamos atributos para tags HTML: eles são de fato acessórios definidos nos componentes HTML (definidos no mixt.html ). Apoiamos todas as tags HTML que, no momento da redação, são válidas (não depreciadas) no HTML5, com seus atributos (excluindo os depreciados).
Então, vamos fazer isso:
print ( < TodoForm add_url = "/todo/add" / > ) < form method =" post " action =" /todo/add " > < label > New Todo: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form >OK, temos nosso suporte presente no HTML renderizado.
E se não passarmos uma corda? Dissemos nos PropTypes que queríamos uma string ...
Vamos tentar:
print ( < TodoForm add_url = 1 / > ) < form method =" post " action =" 1 " > < label > New Todo: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form >Funciona! Mas ... não é uma corda !! De fato, há um caso especial para números, você pode passar como números em vez de strings e eles são convertidos se necessário ...
Então, vamos tentar outra coisa.
print ( < TodoForm add_url = True / > ) mixt . exceptions . InvalidPropValueError :
< TodoForm > . add_url : `True` is not a valid value for this prop ( type : < class 'bool' > , expected : < class 'str' > ) E é o mesmo se passarmos True em Python
print ( < TodoForm add_url = { True } / > ) mixt . exceptions . InvalidPropValueError :
< TodoForm > . add_url : `True` is not a valid value for this prop ( type : < class 'bool' > , expected : < class 'str' > ) Ok, vamos enganar o sistema e passar "True" , como uma string.
print ( < TodoForm add_url = "True" / > ) mixt . exceptions . InvalidPropValueError :
< TodoForm > . add_url : `True` is not a valid value for this prop ( type : < class 'bool' > , expected : < class 'str' > )Ainda assim, mas aqui passamos uma corda! Sim, mas existem 4 valores que sempre são avaliados quanto ao que parecem ser:
None )A única maneira de passar um desses valores como uma string é passá -los via Python, como uma string:
print ( < TodoForm add_url = { "True" } / > ) < form method =" post " action =" True " > < label > New Todo: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form > Exceto esses 4 valores e números, todo valor que é passado para um atributo é considerado uma string. Mesmo que não haja citações, como no HTML no HTML5, onde as citações não são obrigatórias para strings sem alguns caracteres (sem espaços, não / ...).
Para passar outra coisa, você deve envolver o valor em aparelhos encaracolados (e, nesse caso, não há necessidade de citações ao redor dos aparelhos encaracolados.
Ok, agora temos certeza de que aceitamos apenas string .... Mas e se eu não passar nada? E ... o que é "nada"?
Vamos começar com uma corda vazia no Python:
print ( < TodoForm add_url = { "" } / > ) < form method =" post " action ="" > < label > New Todo: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form >Ok, funciona, queríamos uma string, temos uma string.
Agora vamos passar esta string vazia diretamente:
print ( < TodoForm add_url = "" / > ) < form method =" post " action ="" > < label > New Todo: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form >Ainda funciona, porque ainda é uma string. Vamos remover as citações, para ver.
print ( < TodoForm add_url = / > ) mixt . exceptions . GeneralParserError : < mixt parser > Unclosed Tags : < TodoForm > Hum sim, isso não é html válido. Então, vamos remover o = :
print ( < TodoForm add_url / > ) mixt . exceptions . InvalidPropValueError :
< TodoForm > . add_url : `True` is not a valid value for this prop ( type : < class 'bool' > , expected : < class 'str' > ) O QUE? Sim, pense em atributos HTML5, como required , checked ... Eles só precisam estar presentes como um atributo, sem valor, para serem considerados True . Portanto, quando um atributo não tem nenhum valor, é um booleano e é True .
Além de não passar um valor, essas duas outras maneiras são válidas em HTML5 para um booleano para True :
required=""required="required"Para sua conveniência, adicionamos de outra maneira:
True (o caso não importa), como python ou como uma string: required=True , required={True} , required="true" E sua contraparte, para passar False :
False (caso não importa), como python ou como uma string: required=False , required={False} , required="false" Ok para os atributos booleanos. Não é o nosso caso. A última coisa que podemos fazer é não definir o atributo:
print ( < TodoForm / > )
# this is the same: ``print(<TodoForm add_url=NotProvided />)```
# (``NotProvided`` must be imported from ``mixt``) mixt . exceptions . UnsetPropError : < TodoForm > . add_url : prop is not setÉ compreensível: tentamos acessar um suporte que não está definido, é claro que não podemos usá -lo.
Mas e se não o acessarmos? Se não imprimirmos o componente, ele não será renderizado:
< TodoForm / > < TodoForm at 0x7fbd18ea5630 >Para que possamos criar uma instância, mas ela falhará no tempo de renderização. Mas há uma maneira de impedir isso.
Por padrão, todas as propriedades são opcionais. E você não precisa usar o tipo Optional do módulo typing Python para isso, seria pesado fazê -lo para cada suporte.
Em vez disso, mixt fornece um tipo nomeado Required que você use da mesma maneira que Optionnal .
from mixt import Element , Required , html
class TodoForm ( Element ):
class PropTypes :
add_url : Required [ str ]
def render ( self , context ):
# ...Então, apenas dissemos que queríamos uma corda e que ela é necessária.
Vamos tentar novamente criá -lo sem o suporte:
< TodoForm / > mixt . exceptions . RequiredPropError : < TodoForm > . add_url : is a required prop but is not setAgora temos a exceção levantada no início do nosso programa.
Para ver outras possibilidades nos adereços, vamos adicionar um novo para alterar o rótulo de texto. Mas não queremos torná -lo necessário e, em vez disso, ter um valor padrão.
Para isso, é tão fácil quanto agregar um valor ao suporte na classe PropTypes :
class TodoForm ( Element ):
class PropTypes :
add_url : Required [ str ]
label : str = 'New Todo'
def render ( self , context ):
return
< form method = "post" action = { self . add_url } >
< label > { self . label }: < / label > < itext name = "todo" / >
< button type = "submit" > Add < / button >
< / form >Agora vamos tentar sem passar o suporte:
print ( < TodoForm add_url = "/todo/add" / > ) < form method =" post " action ="" > < label > New Todo: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form >E se passarmos um:
print ( < TodoForm add_url = "/todo/add" label = "Thing to do" / > ) < form method =" post " action =" /todo/add " > < label > Thing to do: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form >Funciona como esperado.
Observe que você não pode fornecer um valor padrão enquanto possui o suporte Required . Não faz sentido, por isso é verificado o mais rápido possível, enquanto a class é construída:
class TodoForm ( Element ):
class PropTypes :
add_url : Required [ str ]
label : Required [ str ] = 'New Todo' mixt . exceptions . PropTypeRequiredError : < TodoForm > . label : a prop with a default value cannot be requiredE, é claro, o valor padrão deve corresponder ao tipo!
class TodoForm ( Element ):
class PropTypes :
add_url : Required [ str ]
label : str = { 'label' : 'foo' } mixt . exceptions . InvalidPropValueError :
< TodoForm > . label : `{'label': 'foo'}` is not a valid value for this prop ( type : < class 'dict' > , expected : < class 'str' > ) Outra coisa que queremos fazer em nosso componente é deixá -lo construir o rótulo, passando um "tipo" de TODO, mas limitando as opções. Para isso, podemos usar o tipo Choices :
from mixt import Choices , Element , Required , html
class TodoForm ( Element ):
class PropTypes :
add_url : Required [ str ]
type : Choices = [ 'todo' , 'thing' ]
def render ( self , context ):
return
< form method = "post" action = { self . add_url } >
< label > New { self . type }: < / label > < itext name = "todo" / >
< button type = "submit" > Add < / button >
< / form >Vamos tentar:
print ( < TodoForm add_url = "/todo/add" type = "todo" / > )
print ( < TodoForm add_url = "/todo/add" type = "thing" / > ) < form method =" post " action =" /todo/add " > < label > New todo: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form >
< form method =" post " action =" /todo/add " > < label > New thing: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form >E se tentarmos passar outra coisa que as opções disponíveis? Falha, como esperado:
print ( < TodoForm add_url = "/todo/add" type = "stuff" / > ) mixt . exceptions . InvalidPropChoiceError : < TodoForm > . type : `stuff` is not a valid choice for this prop ( must be in [ 'todo' , 'thing' ])Mas talvez não queira passar e usar um valor padrão. Qual seria o resultado?
print ( < TodoForm add_url = "/todo/add" / > ) mixt . exceptions . UnsetPropError : < TodoForm > . type : prop is not set Portanto, temos que marcar o type de suporte conforme necessário:
class PropTypes :
add_url : Required [ str ]
type : Required [ Choices ] = [ 'todo' , 'thing' ]Então, se não passarmos, falha antes:
print ( < TodoForm add_url = "/todo/add" / > ) mixt . exceptions . RequiredPropError : < TodoForm > . type : is a required prop but is not setMas não é o que queremos, queremos um valor padrão.
De fato, você percebeu que, para outros tipos que não Choices , definir um valor no PropTypes nos fornece um valor padrão. Mas, para Choices , é diferente, pois o valor é a lista de opções.
Para isso, temos DefaultChoices : ele funciona da mesma forma que Choices , mas use a primeira entrada na lista como o valor padrão. E, é claro, como com outros tipos com padrão, não pode ser Required .
Vamos tentar:
from mixt import DefaultChoices , Element , Required , html
class TodoForm ( Element ):
class PropTypes :
add_url : Required [ str ]
type : DefaultChoices = [ 'todo' , 'thing' ] print ( < TodoForm add_url = "/todo/add" / > ) < form method =" post " action =" /todo/add " > < label > New todo: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form >Funciona como esperado.
Até então, usamos tipos simples, mas você pode usar os mais complicados.
Por exemplo, faremos com que o add_url aponte para aceitar uma função que calculará o URL para nós com base no suporte type . Mas também queremos permitir strings e com um valor padrão.
Podemos fazer isso, com a digitação. Nossa função pegará uma string, o type e retornará uma string, o URL.
Portanto, a sintaxe é Callable[[str], str] para o chamável, e usamos Union para aceitar coisas do tipo Callable ou str :
from typing import Union , Callable
from mixt import DefaultChoices , Element , Required , html
class TodoForm ( Element ):
class PropTypes :
add_url : Union [ Callable [[ str ], str ], str ] = "/todo/add"
type : DefaultChoices = [ 'todo' , 'thing' ]
def render ( self , context ):
if callable ( self . add_url ):
add_url = self . add_url ( self . type )
else :
add_url = self . add_url
return
< form method = "post" action = { add_url } >
< label > New { self . type }: < / label > < itext name = "todo" / >
< button type = "submit" > Add < / button >
< / form > Primeiro, vamos tentar sem o suporte add_url , pois temos um padrão:
print ( < TodoForm / > ) < form method =" post " action =" /todo/add " > < label > New todo: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form >Deve funcionar também se passarmos uma string:
print ( < TodoForm add_url = "/todolist/add" / > ) < form method =" post " action =" /todolist/add " > < label > New todo: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form >E agora podemos passar uma função:
def make_url ( type ):
return f"/ { type } /add"
print ( < TodoForm add_url = { make_url } / > ) mixt . exceptions . InvalidPropValueError : < TodoForm > . add_url :
`<function make_url at 0x7fe2ae87be18>` is not a valid value for this prop ( type : < class 'function' > , expected : Union [ Callable [[ str ], str ], str ])Oh? Por que? Passei uma função aceitando uma string como argumento e retornando uma string. Sim, mas não se esqueça que os tipos são verificados! Portanto, devemos adicionar tipos à nossa função:
def make_url ( type : str ) -> str :
return f"/ { type } /add"
print ( < TodoForm add_url = { make_url } / > ) < form method =" post " action =" /todo/add " > < label > New todo: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form >E se passarmos outro tipo, o URL deve mudar de acordo:
print ( < TodoForm add_url = { make_url } type = "thing" / > ) < form method =" post " action =" /thing/add " > < label > New todo: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form >Podemos até fazer essa função o valor padrão para o nosso suporte:
from typing import Union , Callable
from mixt import DefaultChoices , Element , Required , html
def make_url ( type : str ) -> str :
return f"/ { type } /add"
class TodoForm ( Element ):
class PropTypes :
add_url : Union [ Callable [[ str ], str ], str ] = make_url
type : DefaultChoices = [ 'todo' , 'thing' ]
def render ( self , context ):
if callable ( self . add_url ):
add_url = self . add_url ( self . type )
else :
add_url = self . add_url
return
< form method = "post" action = { add_url } >
< label > New { self . type }: < / label > < itext name = "todo" / >
< button type = "submit" > Add < / button >
< / form > print ( < TodoForm / > ) < form method =" post " action =" /todo/add " > < label > New todo: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form >Agora você pode começar a se perguntar ... A digitação em python é pesada e a validação pode tirar parte do nosso precioso tempo.
Vamos responder:
Por padrão, mixt é executado no "Modo dev". E no modo de desenvolvimento, os adereços são validados quando passados para um componente. Quando você não está no "modo de dev", a validação é ignorada. Portanto, na produção, você pode desativar o modo dev (veremos como em um minuto) e passar dependência muito rápida:
Choices está, de fato, na lista de opçõesrender . Mas você pode dizer que está em produção que a validação é importante. De fato. Mas é claro que seu código é totalmente coberto por testes, que você executa no modo dev e, portanto, na produção, você não precisa dessa validação! E observe que é como o React funciona, a propósito, com NODE_ENV=production .
Como alterar o modo de dev? Não aplicamos nenhuma variável de ambiente, mas propomos algumas funções. Cabe a você chamá -los:
from mixt import set_dev_mode , unset_dev_mode , override_dev_mode , in_dev_mode
# by default, dev-mode is active
assert in_dev_mode ()
# you can unset the dev-mode
unset_dev_mode ()
assert not in_dev_mode ()
# and set it back
set_dev_mode ()
assert in_dev_mode ()
# set_dev_mode can take a boolean
set_dev_mode ( False )
assert not in_dev_mode ()
set_dev_mode ( True )
assert in_dev_mode ()
# and we have a context manager to override for a block
with override_dev_mode ( False ):
assert not in_dev_mode ()
with override_dev_mode ( True ):
assert in_dev_mode ()
assert not in_dev_mode ()
assert in_dev_mode () Então, vamos tentar isso com o prop type . Lembre -se, parece:
type : DefaultChoices = [ 'todo' , 'thing' ]Tentamos aprovar outra opção, primeiro no modo dev:
with override_dev_mode ( True ):
print ( < TodoForm type = "stuff" / > ) mixt . exceptions . InvalidPropChoiceError : < TodoForm > . type : `stuff` is not a valid choice for this prop ( must be in [ 'todo' , 'thing' ])Falha como esperado.
E agora desativando o modo dev:
with override_dev_mode ( False ):
print ( < TodoForm type = "stuff" / > ) < form method =" post " action =" /stuff/add " > < label > New stuff: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form > Funciona, temos um tipo de TODO que não foi de nossas escolhas que é usado e também está na action . É o trabalho de seus testes para garantir que você nunca passasse adereços inválidos, para que você possa ter confiança na produção e desativar o modo de desenvolvimento.
Agora temos nossa forma. Que outros componentes precisamos para o nosso aplicativo de lista de tarefas?
Obviamente, precisamos de uma maneira de exibir uma entrada para TODO.
Mas o que é uma entrada de TODO? Vamos criar um TodoObject básico:
class TodoObject :
def __init__ ( self , text ):
self . text = textÉ uma aula muito simples, mas você pode usar o que deseja, é claro. Poderia ser modelos Django, etc ...
Assim, podemos criar nosso componente Todo , fazendo com que ele aceite um TodoObject como prop:
class Todo ( Element ):
class PropTypes :
todo : Required [ TodoObject ]
def render ( self , context ):
return < li > { self . todo . text } < / li >E podemos usá -lo:
todo = TodoObject ( "foo" )
print ( < Todo todo = { todo } / > ) < li > foo </ li > Agora queremos ter uma lista de Todos. Vamos criar um componente TodoList que aceite como adereços uma lista de TodoObject .
Mas o que é diferente dos nossos dois outros componentes, que usam apenas tags HTML em seu método render , é que agora encapsularemos um componente em outro. Vamos ver como.
class TodoList ( Element ):
class PropTypes :
todos : Required [ List [ TodoObject ]]
def render ( self , context ):
return < ul > {[ < Todo todo = { todo } / > for todo in self . todos ]} < / ul > Sim, é tão simples assim: você usa <Todo...> para o componente Todo , como usaria uma tag HTML. A única diferença é que, para as tags HTML, você não precisa importá-las diretamente (importação simples html do mixt ) e, por convenção, as escrevemos em casos mais baixos. Para componentes normais, você deve importá -los (ainda pode fazer from mylib import components e <components.MyComponent ...> ) e usar o caso exato.
Observe como exigimos uma lista e passamos para o <ul> por meio de uma compreensão de lista nos braços encaracolados.
Você pode fazer as coisas de maneira diferente, se quiser.
Como separar a compreensão da lista do HTML:
def render ( self , context ):
todos = [
< Todo todo = { todo } / >
for todo
in self . todos
]
return < ul > { todos } < / ul >Ou em um método dedicado (que seria útil para testes):
def render_todos ( self , todos ):
return [
< Todo todo = { todo } / >
for todo
in todos
]
def render ( self , context ):
return < ul > { self . render_todos ( self . todos )} < / ul >Depende de você: no final, é apenas Python.
Vamos ver o que é renderizado por este componente:
todos = [ TodoObject ( "foo" ), TodoObject ( "bar" ), TodoObject ( "baz" )]
print ( < TodoList todos = { todos } / > ) < ul > < li > foo </ li > < li > bar </ li > < li > baz </ li > </ ul > E, finalmente, temos nosso componente TodoApp que encapsulam o formulário e a lista:
class TodoApp ( Element ):
class PropTypes :
todos : Required [ List [ TodoObject ]]
type : DefaultChoices = [ 'todo' , 'thing' ]
def render ( self , context ):
return
< div >
< h1 > The "{self.type}" list < / h1 >
< TodoForm type = { self . type } / >
< TodoList todos = { self . todos } / >
< / div > todos = [ TodoObject ( "foo" ), TodoObject ( "bar" ), TodoObject ( "baz" )]
print ( < TodoList todos = { todos } type = "thing" / > ) < div > < h1 > The "thing" list </ h1 > < form > ... </ form > < ul > < li > foo </ li > < li > bar </ li > < li > baz </ li > </ ul > </ div >Vamos passar este HTML para um HTML Beautifier:
< div >
< h1 > The "thing" list </ h1 >
< form method =" post " action =" /thing/add " >
< label > New thing: </ label >
< input type =" text " name =" todo " />
< button type =" submit " > Add </ button >
</ form >
< ul >
< li > foo </ li >
< li > bar </ li >
< li > baz </ li >
</ ul >
</ div > E é isso, temos o nosso aplicativo de lista de tarefas! Para usá -lo em uma página, basta criar um componente que renderizará a marcação base HTML e integrará o componente TodoApp nele. Você nem precisa de um componente:
todos = [ TodoObject ( "foo" ), TodoObject ( "bar" ), TodoObject ( "baz" )]
print (
< html >
< body >
< TodoApp todos = { todos } type = "thing" / >
< / body >
< / html >
)A produção embelezada seria:
< html >
< body >
< div >
< h1 > The "thing" list </ h1 >
< form method =" post " action =" /thing/add " >
< label > New thing: </ label >
< input type =" text " name =" todo " />
< button type =" submit " > Add </ button >
</ form >
< ul >
< li > foo </ li >
< li > bar </ li >
< li > baz </ li >
</ ul >
</ div >
</ body >
</ html >Temos uma lista genérica, mas, seguindo os tipos disponíveis de TODO, podemos querer ter uma "lista de tarefas" e uma "lista de coisas".
Já temos a lista de TODO porque nosso TodoApp tem um tipo de todo por padrão.
Então, vamos criar um ThingApp .
A primeira maneira de fazer isso é herdar do nosso TodoApp . Mas, ao herdar, não podemos remover adereços do pai (não é realmente verdade, veremos isso mais tarde), então ainda temos o type de suporte por padrão. Mas não queremos aceitar mais nada além de "coisa". Para que possamos redefinir o type de tipo de tipo:
class ThingApp ( TodoApp ):
class PropTypes :
type : DefaultChoices = [ 'thing' ]Vamos usar este componente:
print ( < ThingApp todos = {[ TodoObject ( "foo" )]} / > ) < div > < h1 > The "thing" list </ h1 > < form method =" post " action =" /thing/add " > < label > New todo: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form > < ul > < li > foo </ li > </ ul > </ div > Se tentarmos passar "TODO" para os adereços type , ele não funcionará:
print ( < ThingApp todos = {[ TodoObject ( "foo" )]} type = "todo" / > ) mixt . exceptions . InvalidPropChoiceError :
< ThingApp > . type : `todo` is not a valid choice for this prop ( must be in [ 'thing' ])Mas ainda assim, é estranho poder passar um tipo.
Vamos tentar de outra maneira: um componente pai. Um componente que não faz mais nada que faça as coisas com seus filhos e devolva. O que queremos aqui é um componente que retornará um TodoApp com o type de suporte forçado a "coisa".
Vamos fazer isso
class ThingApp ( Element ):
class PropTypes :
todos : Required [ List [ TodoObject ]]
def render ( self , context ):
return < TodoApp todos = { self . todos } type = "thing" / > print ( < ThingApp todos = {[ TodoObject ( "foo" )]} / > ) < div > < h1 > The "thing" list </ h1 > < form method =" post " action =" /thing/add " > < label > New todo: </ label > < input type =" text " name =" todo " /> < button type =" submit " > Add </ button > </ form > < ul > < li > foo </ li > </ ul > </ div > Funciona e, desta vez, não podemos passar no type prop:
print ( < ThingApp todos = {[ TodoObject ( "foo" )]} / > ) mixt . exceptions . InvalidPropNameError : < ThingApp > . type : is not an allowed prop Observe como tivemos que definir o tipo para os adereços todos . Tanto em TodoApp quanto TodoThing .
Existem muitas maneiras de lidar com isso.
O primeiro seria ignorar o tipo em ThingApp , pois será verificado em TodoApp . Então, usaremos o tipo: Any :
from typing import Any
#...
class ThingApp ( Element ):
class PropTypes :
todos : Any
#...Vamos tentar com uma lista válida de Todos:
print ( < ThingApp todos = {[ TodoObject ( "foo" )]} / > ) < div > < h1 > The "thing" list </ h1 > < form > ... </ form > < ul > < li > foo </ li > </ ul > </ div >Mas e se passarmos outra coisa?
print ( < ThingApp todos = "foo, bar" / > ) mixt . exceptions . InvalidPropValueError :
< TodoApp > . todos : `foo, bar` is not a valid value for this prop ( type : < class 'str' > , expected : List [ TodoObject ]) Funciona como esperado, mas o erro é relatado no nível TodoApp , que é perfeitamente normal.
Outra maneira seria definir o tipo em um nível superior:
TodoObjects = Required [ List [ TodoObject ]]
class TodoApp ( Element ):
class PropTypes :
todos : TodoObjects
# ...
class ThingApp ( Element ):
class PropTypes :
todos : TodoObjects
# ...Agora, se passarmos outra coisa, temos o erro relatado no nível correto:
print ( < ThingApp todos = "foo, bar" / > ) mixt . exceptions . InvalidPropValueError :
< TodoThing > . todos : `foo, bar` is not a valid value for this prop ( type : < class 'str' > , expected : List [ TodoObject ]) Mas se você não puder ou não quiser fazer isso, pode manter o tipo definido no TodoApp ET Use o método da classe prop_type de um componente para obter o tipo de um suporte:
class ThingApp ( Element ):
class PropTypes :
todos : TodoApp . prop_type ( "todos" )
# ... Mas realmente importa ter o erro levantado para ThingApp ou TodoApp ? Porque no final, é realmente TodoApp que precisa fazer o cheque.
Portanto, essa deve ser uma maneira de fazer isso de uma maneira mais genérica ..
Vimos anteriormente que um componente pode ser uma única função para renderizar um componente. Ele só tem que retornar um componente, uma tag HTML. Uma diferença com os componentes da classe é que não há PropTypes , portanto, não há validação. Mas ... é exatamente o que precisamos.
Queremos que nosso ThingApp aceite alguns adereços (o Prop todos ) e retorne um TodoApp com um suporte type específico.
Então poderíamos fazer:
def ThingApp ( todos ):
return < TodoApp type = "thing" todos = { todos } / > Aqui podemos ver que não podemos passar type para ThingsApp , não é um argumento válido.
Vamos tentar:
print ( < ThingApp todos = {[ TodoObject ( "foo" )]} / > ) < div > < h1 > The "thing" list </ h1 > < form > ... </ form > < ul > < li > foo </ li > </ ul > </ div > Aqui temos apenas um suporte a passar, por isso é fácil. Mas imagine se tivermos muitos. Podemos usar a sintaxe {**props} :
def ThingApp ( ** props ):
return < TodoApp type = "thing" { ** props } / >E você pode fazer com menos caracteres (se contar):
ThingApp = lambda ** props : < TodoApp type = "thing" { ** props } / >Essas duas foções se comportam exatamente da mesma forma.
E você não pode passar um suporte type porque seria um erro de Python, pois seria passado duas vezes para TodoApp :
print ( < ThingApp todos = {[ TodoObject ( "foo" )]} type = "thing" / > ) TypeError : BaseMetaclass object got multiple values for keyword argument 'type' (Sim, fala sobre BaseMetaclass , que é o metaclasse que cria nossas aulas de componentes)
E quaisquer outros adereços errados seriam validados pelo TodoApp :
print ( < ThingApp todos = {[ TodoObject ( "foo" )]} foo = "bar" / > ) mixt . exceptions . InvalidPropNameError : < TodoApp > . foo : is not an allowed prop Com isso em mente, poderíamos ter criado uma fonação genérica que force o tipo de qualquer componente que aceite um suporte type :
Thingify = lambda component , ** props : < component type = "thing" { ** props } / > print ( < Thingify component = { TodoApp } todos = {[ TodoObject ( "foo" )]} / > ) < div > < h1 > The "thing" list </ h1 > < form > ... </ form > < ul > < li > foo </ li > </ ul > </ div > O componente renderizado é TodoApp , o type é "coisa" e os outros adereços (aqui apenas todos ) são passados corretamente.
Agora estenda esse conceito a um caso mais genérico: "componentes de ordem superior". No React um "componente de alta ordem", é "uma função que pega um componente e retorna um novo componente".
A ideia é:
EnhancedComponent = higherOrderComponent ( WrappedComponent )Uma maneira clássica de fazer isso é devolver uma nova classe de componentes:
def higherOrderComponent ( WrappedComponent ):
class HOC ( Element ):
__display_name__ = f"higherOrderComponent( { WrappedComponent . __display_name__ } )"
class PropTypes ( WrappedComponent . PropTypes ):
pass
def render ( self , context ):
return < WrappedComponent { ** self . props } > { self . childre ()} < / WrappedComponent >
return HOC Observe como configuramos a classe PropTypes para herdar do componente embrulhado e como passamos todos os adereços para o componente embrulhado, junto com as crianças. Com o componente retornado, aceitará os mesmos adereços, com os mesmos tipos, que o embrulhado.
E também observe o __display_name__ . Ele será usado em exceções para permitir que você agora o componente que o criou. Aqui, sem forçá -lo, teria sido definido para HOC , o que não é útil. Em vez disso, indicamos que é uma versão transformada do componente passado.
Aqui está uma função que não faz nada útil.
Em nosso exemplo, poderíamos ter feito isso:
def thingify ( WrappedComponent ):
class HOC ( Element ):
__display_name__ = f"thingify( { WrappedComponent . __display_name__ } )"
class PropTypes ( WrappedComponent . PropTypes ):
__exclude__ = { 'type' }
def render ( self , context ):
return < WrappedComponent type = "thing" { ** self . props } > { self . children ()} < / WrappedComponent >
return HOCDuas coisas importantes aqui:
__exclude__ = {'type'} para remover o prop type daqueles que herdamos do WrappedComponent.PropTypes . Portanto, o componente retornado esperará exatamente os mesmos adereços que o embrulhado, exceto o type .{self.children()} no componente embrulhado renderizado, porque mesmo se realmente sabemos que o componente que iremos embrulhar, TodoApp , não toma crianças (isso poderia, mas não faz nada com eles), não podemos dizer com antecedência que sempre será o caso, e também que esse componente de ordem superior não será usada para envolver outro componente que TodoApp . Portanto, é melhor sempre fazer isso. E agora podemos criar nosso ThingApp :
ThingApp = thingify ( TodoApp )E use -o:
print ( < ThingApp todos = {[ TodoObject ( "foo" )]} / > ) < div > < h1 > The "thing" list </ h1 > < form > ... </ form > < ul > < li > foo </ li > </ ul > </ div >Se tentarmos passar o tipo:
print ( < ThingApp todos = {[ TodoObject ( "foo" )]} type = "thing" / > ) mixt . exceptions . InvalidPropNameError : < thingify ( TodoApp ) > . type : is not an allowed prop Assim como planejado, não podemos passar o tipo. E observe como o __display_name__ é usado.
Vamos pensar sobre o quão poderoso isso é.
Digamos que queira manter nosso TodoApp fazer uma lista de TodoObject . Mas queremos obtê -los de uma "fonte".
Podemos até escrever diretamente este novo componente de ordem superior de maneira genérica:
def from_data_source ( WrappedComponent , prop_name , get_source ):
class HOC ( Element ):
__display_name__ = f"from_data_source( { WrappedComponent . __display_name__ } )"
class PropTypes ( WrappedComponent . PropTypes ):
__exclude__ = { prop_name }
def render ( self , context ):
props = self . props . copy ()
props [ prop_name ] = get_source ( props , context )
return < WrappedComponent { ** props } > { self . children ()} < / WrappedComponent >
return HOC Desta vez, a função from_data_source leva dois argumentos, além do WrappedComponent :
prop_name : é o nome do suporte do componente embrulhado para preencher alguns dadosget_source : é uma função que será chamada para obter os dados Veja como herdamos os PropTypes do componente embrulhado e como excluímos prop_name . Portanto, não temos (e não podemos) passar os dados para o nosso novo componente.
E então, na render , definimos um suporte para passar para WrappedComponent com o resultado de uma chamada para get_source .
Então, vamos escrever uma função muito simples (isso pode ser complicado com armazenamento em cache, filtragem ...) que pegam os adereços e o contexto e retorna alguns dados:
def get_todos ( props , context ):
# here it could be a call to a database
return [
TodoObject ( "fooooo" ),
TodoObject ( "baaaar" ),
]E podemos compor nosso componente:
SourcedTodoApp = from_data_source ( TodoApp , 'todos' , get_todos )
ThingApp = thingify ( SourcedTodoApp )E execute:
print ( < ThingApp / > ) < div > < h1 > The "thing" list </ h1 > < form > ... </ form > < ul > < li > fooooo </ li > < li > baaaar </ li > </ ul > </ div >Funciona como esperado, e os dados são buscados apenas quando o componente precisa ser renderizado.
Portanto, temos uma lista de tarefas, que pode buscar dados de uma fonte externa. Mas podemos querer que os dados sejam diferentes, dependendo do usuário.
O que podemos fazer, está no nível principal, levar nosso usuário e passá -lo em todos os componentes para ter certeza de que cada componente é capaz de obter o usuário logado atual.
Não seria pesado?
Resolver este caso de uso é o objetivo exato do conceito Context fornecido pelo mixt . É claro que é inspirado no conceito de contexto em React.
E como eles disseram:
O contexto foi projetado para compartilhar dados que podem ser considerados "globais" para uma árvore de componentes do React, como o usuário, tema ou idioma preferencial autenticado atual.
Criar um contexto é tão simples quanto criar um componente, exceto que ele herdará do BaseContext e não precisa de um método render (ele renderizará seus filhos).
E é preciso uma classe PropTypes , que define os tipos de dados que o contexto aceitará e passará a árvore.
Então, vamos criar nosso contexto que manterá o ID do usuário autenticado.
from mixt import BaseContext
class UserContext ( BaseContext ):
class PropTypes :
authenticated_user_id : Required [ int ] Agora, queremos atualizar nosso método get_todos para levar em consideração o authenticated_user_id .
Lembre -se de que passamos pelos adereços e ao contexto. O contexto será útil aqui:
def get_todos ( props , context ):
return {
1 :[
TodoObject ( "1-1" ),
TodoObject ( "1-2" ),
],
2 : [
TodoObject ( "2-1" ),
TodoObject ( "2-2" ),
]
}[ context . authenticated_user_id ]E agora podemos renderizar nosso aplicativo com o contexto:
print (
< UserContext authenticated_user_id = 1 >
< ThingApp / >
< / UserContext >
) < div > < h1 > The "thing" list < / h1 > < form > ... < / form > < ul > < li > 1 - 1 < / li > < li > 1 - 2 < / li > < / ul > < / div >Podemos ver as entradas para o usuário 1.
Vamos tentar com o usuário 2:
print (
< UserContext authenticated_user_id = 2 >
< ThingApp / >
< / UserContext >
) < div > < h1 > The "thing" list < / h1 > < form > ... < / form > < ul > < li > 2 - 1 < / li > < li > 2 - 2 < / li > < / ul > < / div >Podemos ver as entradas para o usuário 2.
Nesse caso, é claro que poderíamos ter passado o ID do usuário como um suporte. Mas imagine que o aplicativo TODO esteja no fundo da árvore dos componentes, é muito mais fácil passar dessa maneira.
Mas, como dito na documentação do React:
Não use o contexto apenas para evitar a passagem de adereços alguns níveis. Atenha -se aos casos em que os mesmos dados precisam ser acessados em muitos componentes em vários níveis.
Quando não há contexto, o argumento context do método render é definido como EmptyContext e None . Assim, você pode usar diretamente o método has_prop para verificar se um suporte está disponível através do contexto.
Vamos atualizar as funções get_todos para retornar uma lista vazia de objetos TODO se não houver usuário autenticado.
def get_todos ( props , context ):
if not context . has_prop ( 'authenticated_user_id' ) or not context . authenticated_user_id :
return []
return {
1 :[
TodoObject ( "1-1" ),
TodoObject ( "1-2" ),
],
2 : [
TodoObject ( "2-1" ),
TodoObject ( "2-2" ),
]
}[ context . authenticated_user_id ]Vamos tentar o seguinte:
print ( < ThingApp / > ) < div > < h1 > The "thing" list < / h1 > < form > ... < / form > < ul > < / ul > < / div >E ainda funciona com um usuário no contexto:
print (
< UserContext authenticated_user_id = 1 >
< ThingApp / >
< / UserContext >
) < div > < h1 > The "thing" list < / h1 > < form > ... < / form > < ul > < li > 1 - 1 < / li > < li > 1 - 2 < / li > < / ul > < / div >Nota importante sobre os contextos : você pode ter muitos contextos! Mas definir o mesmo suporte em muitos contextos pode levar a um comportamento indefinido.
Todo mundo adora um belo design, e talvez alguma interação.
É facilmente factível: geramos HTML e HTML podem contém alguns CSs e JS.
Vamos adicionar alguma interação primeiro: ao adicionar um item na TodoForm , vamos adicioná -lo à lista.
Primeiro, adicionamos nosso componente TodoForm A render_javascript Method que hospedará nosso (ruim, poderíamos fazer melhor, mas não é o ponto) JavaScript:
class TodoForm ( Element ):
# ...
def render_javascript ( self , context ):
return html . Raw ( """
function on_todo_add_submit(form) {
var text = form.todo.value;
alert(text);
}
""" )Para começar, exibimos apenas o novo texto do TODO.
Agora atualize nosso método render para retornar este JavaScript (observe que o uso de um método render_javascript é apenas para separar as preocupações, ele poderia estar diretamente no método render .
class TodoForm ( Element ):
# ...
def render ( self , context ):
# ...
return
< Fragment >
< script > { self . render_javascript ( context )} < / script >
< form method = "post" action = { add_url } onsubmit = "return on_todo_add_submit(this);" >
< label > New { self . type }: < / label > < itext name = "todo" / >
< button type = "submit" > Add < / button >
< / form >
< / Fragment > Observe a etiqueta Fragment . É uma maneira de encapsular muitos elementos a serem devolvidos, como no React. Poderia ter sido uma lista simples, mas com comas no final:
return [
< script > ... < / script > ,
< form >
...
< / form >
] Agora queremos adicionar um item à lista. Não é o papel do TodoForm fazer isso, mas para a lista. Portanto, adicionaremos alguns JS no componente TodoList : uma função que recebe algum texto e crie uma nova entrada.
Quanto ao TodoForm , adicionamos um método render_javascript com (ainda ruim) JavaScript:
class TodoList ( Element ):
# ...
def render_javascript ( self , context ):
todo_placeholder = < Todo todo = { TodoObject ( text = 'placeholder' )} / >
return html . Raw ( """
TODO_TEMPLATE = "%s";
function add_todo(text) {
var html = TODO_TEMPLATE.replace("placeholder", text);
var ul = document.querySelector('#todo-items');
ul.innerHTML = html + ul.innerHTML;
}
""" % ( todo_placeholder )) E atualizamos nosso método render para adicionar a tag <script> e um id à tag ul , usada no JavaScript:
class TodoList ( Element ):
# ...
def render ( self , context ):
return
< Fragment >
< script > { self . render_javascript ( context )} < / script >
< ul id = "todo-items" > {[ < Todo todo = { todo } / > for todo in self . todos ]} < / ul >
< / Fragment > E agora podemos atualizar o método render_javascript do componente TodoForm para usar nossa nova função add_toto javascript:
class TodoForm ( Element ):
# ...
def render_javascript ( self , context ):
return html . Raw ( """
function on_todo_add_submit(form) {
var text = form.todo.value;
add_todo(text);
}
""" )E isso é tudo. Nada de especial, de fato.
Mas vamos dar uma olhada na saída de OU TodoApp :
print (
< UserContext authenticated_user_id = 1 >
< ThingApp / >
< / User >
)A saída embelezada é:
< div >
< h1 > The "thing" list </ h1 >
< script >
function on_todo_add_submit ( form ) {
var text = form . todo . value ;
add_todo ( text ) ;
}
</ script >
< form method =" post " action =" /thing/add " onsubmit =" return on_todo_add_submit(this); " >
< label > New thing: </ label >
< input type =" text " name =" todo " />
< button type =" submit " > Add </ button >
</ form >
< script >
TODO_TEMPLATE = "<li>placeholder</li>" ;
function add_todo ( text ) {
var html = TODO_TEMPLATE . replace ( "placeholder" , text ) ;
var ul = document . querySelector ( '#todo-items' ) ;
ul . innerHTML = html + ul . innerHTML ;
}
</ script >
< ul id =" todo-items " >
< li > 1-1 </ li >
< li > 1-2 </ li >
</ ul >
</ div > Então, temos muitas tags script . Pode ser ótimo ter apenas um.
mixt vem com uma maneira de "coletar" partes do que é renderizado para colocá -las em outro lugar. Temos à nossa disposição dois colecionadores simples, a serem usados como componentes: JSCollector e CSSCollector .
Esses componentes coletam partes da árvore dos filhos.
A primeira maneira é usar o coletor Collect .
Primeiro vamos mudar nossa chamada principal:
from mixt import JSCollector
print (
< JSCollector render_position = "after" >
< UserContext authenticated_user_id = 1 >
< ThingApp / >
< / User >
< / JSCollector >
) Isso coletará o conteúdo de toda a tag JSCollector.Collect .
Vamos atualizar nossa TodoForm e substituir nossa tag script por uma tag JSCollector.Collect :
class TodoForm ( Element ):
# ...
def render ( self , context ):
if callable ( self . add_url ):
add_url = self . add_url ( self . type )
else :
add_url = self . add_url
return
< JSCollector . Collect > { self . render_javascript ( context )} < / JSCollector . Collect >
< form method = "post" action = { add_url } onsubmit = "return on_todo_add_submit(this);" >
< label > New { self . type }: < / label > < itext name = "todo" / >
< button type = "submit" > Add < / button >
< / form >
< / Fragment > Podemos fazer o mesmo com o TodoList :
class TodoList ( Element ):
# ...
def render ( self , context ):
return
< Fragment >
< JSCollector . Collect > { self . render_javascript ( context )} < / JSCollector . Collect >
< ul id = "todo-items" > {[ < Todo todo = { todo } / > for todo in self . todos ]} < / ul >
< / Fragment >Agora vamos executar nosso código atualizado:
print (
< JSCollector render_position = "after" >
< UserContext authenticated_user_id = 1 >
< ThingApp / >
< / User >
< / JSCollector >
)A saída embelezada é:
< div >
< h1 > The "thing" list </ h1 >
< form method =" post " action =" /thing/add " onsubmit =" return on_todo_add_submit(this); " >
< label > New thing: </ label >
< input type =" text " name =" todo " />
< button type =" submit " > Add </ button >
</ form >
< ul id =" todo-items " >
< li > 1-1 </ li >
< li > 1-2 </ li >
</ ul >
</ div >
< script type =" text/javascript " >
function on_todo_add_submit ( form ) {
var text = form . todo . value ;
add_todo ( text ) ;
}
TODO_TEMPLATE = "<li>placeholder</li>" ;
function add_todo ( text ) {
var html = TODO_TEMPLATE . replace ( "placeholder" , text ) ;
var ul = document . querySelector ( '#todo-items' ) ;
ul . innerHTML = html + ul . innerHTML ;
}
</ script > Como você pode ver, todos os scripts estão em uma única tag script , no final. Mais precisamente, no final de onde estava a tag JSCollector , porque usamos render_position="after" . Outra possibilidade é render_position="before" para colocar isso onde a tag JSCollector foi iniciada.
Tudo isso funciona exatamente da mesma maneira para a tag CSSCollector , onde o conteúdo é colocado em uma tag <style type="text/css> .
Como o uso de JS/CSS é bastante comum no mundo HTML, adicionamos um pouco de açúcar para tornar tudo isso ainda mais fácil de fazer.
Se você possui um método render_js , o JSCollector coletará automaticamente o resultado desse método. O mesmo para o método CSSSelector e render_css .
Com isso, não há necessidade de uma tag JSCollector.Collect .
Para fazer isso funcionar em nosso exemplo, em TodoForm e TodoList :
JSCollector.CollectFragment agora desnecessáriasrender_javascript para render_js .html.Raw em render_js , pois não é necessário quando o coletor chama render_js em si: se a saída for uma string, é considerada "cru"Dessa forma, temos exatamente o mesmo resultado.
Funciona agora porque temos apenas uma instância de uma criança com um método render_js .
Mas se tivermos muitos filhos, esse método será chamado para cada criança. Se o fato, ele deve contém apenas um código muito específico para esta instância.
Para servir JS/CSS apenas uma vez para uma classe de componentes, temos que usar render_js_global ou render_css_global (espera -se que seja classmethod )
Ele será coletado na primeira vez e apenas a primeira vez, uma instância é encontrada, antes de coletar o método render_js .
Então, aqui, podemos alterar nosso render_js para render_js_global , decorá -los com @classmethod e ainda funcionará o mesmo.
Agora somos capazes de reagrupar o javascript ou estilo. Mas e se quisermos colocá -lo em outro lugar, como na etiqueta head ou no final da etiqueta body ?
É possível com referências, também conhecidas como "árbitros". É o mesmo contexto que no React, sem a parte do DOM, é claro.
Você cria uma referência, passa para um componente e pode usá -lo em qualquer lugar.
Vamos atualizar nosso código principal para fazer isso.
Primeiro, criamos uma ref.
from mixt import Ref
js_ref = Ref () Isso criará um novo objeto que manterá uma referência a um componente. Em um componente, você não precisa importar Ref e pode usar js_ref = self.add_ref() , mas não estamos em um componente aqui.
Para salvar um ref, simplesmente o passamos para o suporte ref :
< JSCollector ref = { js_ref } > ... < / JSCollector > Observe que removemos o suporte render_position , porque agora não queremos que o JS seja colocado antes ou depois da tag, mas em outros lugares.
Para acessar o componente referenciado por um REF, use o atributo current :
js_collector = js_ref . currentClaro que isso pode ser feito somente após a renderização.
Como podemos usar isso para adicionar uma tag script em nossa head .
Atualize primeiro nosso HTML para incluir as tags clássicas html , head e body :
return str (
< html >
< head >
< / head >
< body >
< JSCollector ref = { js_ref } >
< UserContext authenticated_user_id = 1 >
< ThingApp / >
< / UserContext >
< / JSCollector >
< / body >
< / html >
) Neste ponto, não temos nenhuma tag script na saída:
< html >
< head > </ head >
< body >
< div >
< h1 > The "thing" list </ h1 >
< form method =" post " action =" /thing/add " onsubmit =" return on_todo_add_submit(this); " >
< label > New thing: </ label >
< input type =" text " name =" todo " />
< button type =" submit " > Add </ button >
</ form >
< ul id =" todo-items " >
< li > 1-1 </ li >
< li > 1-2 </ li >
</ ul >
</ div >
</ body >
</ html > Primeira coisa a saber: um colecionador é capaz de renderizar todas as coisas que coletou chamando seu método render_collected .
E lembrando que já inclui a etiqueta script , podemos querer fazer isso:
# ...
< head >
{ js_ref . current . render_collected ()}
< / head >
# ...Mas isso não funciona:
AttributeError : 'NoneType' object has no attribute 'render_collected'É porque tentamos acessar o valor atual no tempo de renderização. Deve ser feito depois.
Para isso, podemos usar um recurso do mixt : se algo adicionado à árvore for um chamável, ele será chamado após a renderização, ao converter em string.
Então, podemos usar, por exemplo, um lambda:
# ...
< head >
{ lambda : js_ref . current . render_collected ()}
< / head >
# ...E agora funciona:
< html >
< head >
< script type =" text/javascript " >
function on_todo_add_submit ( form ) {
var text = form . todo . value ;
add_todo ( text ) ;
}
TODO_TEMPLATE = "<li>placeholder</li>" ;
function add_todo ( text ) {
var html = TODO_TEMPLATE . replace ( "placeholder" , text ) ;
var ul = document . querySelector ( '#todo-items' ) ;
ul . innerHTML = html + ul . innerHTML ;
}
</ script >
</ head >
< body >
< div >
< h1 > The "thing" list </ h1 >
< form method =" post " action =" /thing/add " onsubmit =" return on_todo_add_submit(this); " >
< label > New thing: </ label >
< input type =" text " name =" todo " />
< button type =" submit " > Add </ button >
</ form >
< ul id =" todo-items " >
< li > 1-1 </ li >
< li > 1-2 </ li >
</ ul >
</ div >
</ body >
</ html > Viva nós fizemos isso! Todas as principais características do mixt explicadas. Agora você pode usar mixt em seus próprios projetos.
Como próximo passo, convém ler a documentação da API.