Services Blog English

Ryzom : écrire du CSS et des WebComponents en Python

| par jpic | python django ryzom

Cet article fait suite au précédent, Replacing templates with Python components. Assurez-vous de l’avoir lu avant de lire celui-ci !

Rappel des bases HTML

Cette section rappelle comment nous pouvions déjà générer du HTML avec une API Python élégante.

Contenu

Les composants sont des classes Python chargées de rendre une balise HTML. À ce titre, elles peuvent avoir du contenu, c’est-à-dire des enfants :

from ryzom.html import *

yourdiv = Div('some', P('content'))
yourdiv.render() == '<div>some <p>content</p></div>'

La plupart des composants devraient s’instancier avec *content comme premier argument, et vous pouvez y passer autant d’enfants que nécessaire. Ils vont dans self.content, que vous pouvez aussi modifier après instanciation.

Attributs

Les balises HTML ont aussi des attributs, pour lesquels nous avons une API pythonesque :

Div('hi', cls='x', data_y='z').render() == '<div class="x" data-y="z">hi</div>'

Le déclaratif et l’héritage sont également pris en charge :

class Something(Div):
    attrs = dict(cls='something', data_something='foo')


class SomethingNew(Something):
    attrs = dict(addcls='new')  # how to add a class without re-defining


yourdiv = SomethingNew('hi')
yourdiv.render() == '<div class="something new" data-something="foo">hi</div>'

Styles CSS

Les styles peuvent être déclarés dans attrs, ou bien séparément.

class Foo(Div):
    style = dict(margin_top='1px')

# is the same as:

class Foo(Div):
    style = 'margin-top: 1px'

# is the same as:

class Foo(Div):
    attrs = dict(style='margin-top: 1px')

Ce qui précède produira un bundle ressemblant à ceci :

.Foo {
  margin-top: 1px;
}

Et Foo("bar") rendra :

<div class="Foo">bar</div>
  • Les attributs de style de classe seront extraits dans un bundle CSS.
  • Les attributs de style d’instance seront rendus inline.
  • Chaque composant qui a un style rendra aussi un attribut class.

JavaScript

Ce dépôt fournit un fork de py2js que vous pouvez utiliser pour écrire du JavaScript en Python. Il y a deux façons d’écrire du JS en Python : la façon “jQuery” et la façon WebComponent.

Vous devez cependant comprendre que notre objectif est d’écrire du JS en Python, plutôt que de prendre en charge Python dans JS comme le projet Transcrypt. Dans notre cas, nous nous limiterons à un sous-ensemble des langages JS et Python, donc des choses comme __mro__ en Python, ou même l’héritage multiple, ne seront pas du tout prises en charge.

Cependant, vous pouvez tout de même écrire du JS en Python et générer un bundle JS.

WebComponent : HTMLElement

Ce qui suit définit un HTMLElement personnalisé avec une classe JS HTMLElement ; cela générera un web component de base.

class DeleteButton(Component):
    tag = 'delete-button'

    class HTMLElement:
        def connectedCallback(self):
            this.addEventListener('click', this.delete.bind(this))

        async def delete(self, event):
            csrf = document.querySelector('[name="csrfmiddlewaretoken"]')
            await fetch(this.attributes['delete-url'].value, {
                method: 'delete',
                headers: {'X-CSRFTOKEN': csrf.value},
                redirect: 'manual',
            }).then(lambda response: print(response))

Cela générera le JS suivant, qui laissera le navigateur responsable du cycle de vie des composants. Consultez la documentation de window.customElement.define pour les détails.

class DeleteButton extends HTMLElement {
    connectedCallback() {
        this.addEventListener('click',this.delete.bind(this));
    }
    async delete() {
        var csrf = document.querySelector('[name="csrfmiddlewaretoken"]');
        await fetch(this.attributes['delete-url'].value,{
            method: 'delete',
            headers: {'X-CSRFTOKEN': csrf.value},
            redirect: 'manual'
        }).then(
            (response) => {return console.log(response)}
        );
    }
}

window.customElements.define("delete-button", DeleteButton);

Et c’est plutôt rock’n’roll, si vous voulez mon avis.

MAIS il y a un piège : actuellement, vous devez définir le premier argument à self comme en Python, afin que le transpileur sache que cette fonction est une méthode de classe et qu’elle ne doit pas être rendue avec le préfixe function , qui ne fonctionne pas dans les classes ES6.

La façon jQuery

Vous pouvez le faire “à la jQuery” en définissant une méthode py2js dans votre composant avec py2js.Mixin :

class YourComponent(py2js.Mixin, Div):
    def on_form_submit():
        alert('submit!')

    def py2js(self):
        getElementByUuid(self._id).addEventListener('submit', self.on_form_submit)

Ainsi, votre composant rendra aussi l’instruction addEventListener dans une balise script, et le bundle empaquettera la fonction on_form_submit.

Bundles

Le composant dépendra de ses bundles CSS et JS. Sans Django, vous pouvez le faire manuellement ainsi :

from ryzom import bundle

your_components_modules = [
    'ryzom_mdc.html',
    'your.html',
]

css_bundle = bundle.css(*your_components_modules)
js_bundle = bundle.js(*your_components_modules)

Ils nous font confiance

Contact

logo