Shayna  Lowe

Shayna Lowe

1656880980

Comment Configurer Flask Avec htmx et Tailwind CSS

Dans ce didacticiel, vous apprendrez à configurer Flask avec htmx et Tailwind CSS . L'objectif de htmx et de Tailwind est de simplifier le développement Web moderne afin que vous puissiez concevoir et activer l'interactivité sans jamais quitter le confort et la facilité du HTML. Nous verrons également comment utiliser Flask-Assets pour regrouper et minimiser les actifs statiques dans une application Flask.

htmx

htmx est une bibliothèque qui vous permet d'accéder à des fonctionnalités de navigateur modernes telles que AJAX, CSS Transitions, WebSockets et Server-Sent Events directement à partir de HTML, plutôt que d'utiliser JavaScript. Il vous permet de créer rapidement des interfaces utilisateur directement dans le balisage.

htmx étend plusieurs fonctionnalités déjà intégrées au navigateur, telles que les requêtes HTTP et la réponse aux événements. Par exemple, plutôt que de pouvoir uniquement effectuer des requêtes GET et POST via des éléments aet form, vous pouvez utiliser des attributs HTML pour envoyer des requêtes GET, POST, PUT, PATCH ou DELETE sur n'importe quel élément HTML :

<button hx-delete="/user/1">Delete</button>

Vous pouvez également mettre à jour des parties d'une page pour créer une application monopage (SPA) : lien CodePen

Ouvrez l'onglet réseau dans les outils de développement du navigateur. Lorsque le bouton est cliqué, une demande XHR est envoyée au point de https://v2.jokeapi.dev/joke/Any?format=txt&safe-modeterminaison. La réponse est ensuite ajoutée à l' pélément avec une idsortie of.

Pour plus d'exemples, consultez la page Exemples d'interface utilisateur de la documentation officielle htmx.

Avantages et inconvénients

Avantages :

  1. Productivité des développeurs : Vous pouvez créer des interfaces utilisateur modernes sans toucher à JavaScript. Pour en savoir plus, consultez An SPA Alternative .
  2. Emballe un coup de poing : la bibliothèque elle-même est petite (~ 10k min.gz'd), sans dépendance et extensible .

Inconvénients :

  1. Maturité de la bibliothèque : Étant donné que la bibliothèque est assez récente, la documentation et les exemples d'implémentation sont rares.
  2. Taille des données transférées : Typiquement, les frameworks SPA (comme React et Vue) fonctionnent en faisant passer des données entre le client et le serveur au format JSON. Les données reçues sont ensuite restituées par le client. htmx, d'autre part, reçoit le rendu HTML du serveur et remplace l'élément cible par la réponse. Le HTML au format rendu est généralement plus grand en termes de taille qu'une réponse JSON.

CSS vent arrière

Tailwind CSS est un framework CSS "utility-first". Plutôt que d'expédier des composants prédéfinis (dans lesquels se spécialisent des frameworks comme Bootstrap et Bulma ), il fournit des blocs de construction sous la forme de classes utilitaires qui permettent de créer des mises en page et des conceptions rapidement et facilement.

Par exemple, prenez le code HTML et CSS suivant :

<style>
.hello {
  height: 5px;
  width: 10px;
  background: gray;
  border-width: 1px;
  border-radius: 3px;
  padding: 5px;
}
</style>

<div class="hello">Hello World</div>

Cela peut être implémenté avec Tailwind comme ceci :

<div class="h-1 w-2 bg-gray-600 border rounded-sm p-1">Hello World</div>

Découvrez le convertisseur CSS Tailwind pour convertir le CSS brut en classes utilitaires équivalentes dans Tailwind. Comparez les résultats.

Avantages et inconvénients

Avantages :

  1. Hautement personnalisable : bien que Tailwind soit fourni avec des classes prédéfinies, elles peuvent être écrasées à l'aide du fichier tailwind.config.js .
  2. Optimisation : vous pouvez configurer Tailwind pour optimiser la sortie CSS en ne chargeant que les classes réellement utilisées.
  3. Mode sombre : il est facile d'implémenter le mode sombre - par exemple, <div class="bg-white dark:bg-black">.

Inconvénients :

  1. Composants : Tailwind ne fournit aucun composant pré-construit officiel comme des boutons, des cartes, des barres de navigation, etc. Les composants doivent être créés à partir de zéro. Il existe quelques ressources communautaires pour des composants tels que Tailwind CSS Components et Tailwind Toolbox , pour n'en nommer que quelques-uns. Il existe également une bibliothèque de composants puissante, bien que payante, créée par les créateurs de Tailwind, appelée Tailwind UI .
  2. Le CSS est en ligne : Cela couple contenu et design, ce qui augmente la taille de la page et encombre le HTML.

Flask-Assets

Flask-Assets est une extension conçue pour gérer les actifs statiques dans une application Flask. Avec lui, vous créez un pipeline d'actifs simple pour :

  1. Compiler Sass et LESS en feuilles de style CSS
  2. Combiner et réduire plusieurs fichiers CSS et JavaScript en un seul fichier pour chacun
  3. Création d'assets bundles à utiliser dans vos modèles

Sur ce, regardons comment travailler avec chacun des outils ci-dessus dans Flask !

Configuration du projet

Pour commencer, créez un nouveau répertoire pour notre projet, créez et activez un nouvel environnement virtuel et installez Flask avec Flask-Assets :

$ mkdir flask-htmx-tailwind && cd flask-htmx-tailwind
$ python3.10 -m venv venv
$ source venv/bin/activate
(venv)$

(venv)$ pip install Flask==2.1.1 Flask-Assets==2.0

Ensuite, installons pytailwindcss et téléchargeons son binaire :

(venv)$ pip install pytailwindcss==0.1.4
(venv)$ tailwindcss

Ensuite, ajoutez un fichier app.py :

# app.py

from flask import Flask
from flask_assets import Bundle, Environment

app = Flask(__name__)

assets = Environment(app)
css = Bundle("src/main.css", output="dist/main.css")

assets.register("css", css)
css.build()

Après avoir importé Bundle et Environment , nous avons créé un nouveau Environmentfichier et y avons enregistré nos actifs CSS via un fichier Bundle.

Le bundle que nous avons créé prend src/main.css en entrée, qui sera ensuite traité et envoyé à dist/main.css lorsque nous exécuterons la CLI Tailwind CSS.

Étant donné que tous les fichiers statiques Flask résident dans le dossier "static" par défaut, les dossiers "src" et "dist" mentionnés ci-dessus résident dans le dossier "static".

Sur ce, configurons Tailwind.

Commencez par créer un fichier de configuration Tailwind :

(venv)$ tailwindcss init

Cette commande a créé un fichier tailwind.config.js à la racine de votre projet. Toutes les personnalisations liées à Tailwind vont dans ce fichier.

Mettez à jour tailwind.config.js comme suit :

module.exports = {
  content: [
    './templates/**/*.html',
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

Prenez note de la section de contenu . Ici, vous configurez les chemins vers les modèles HTML de votre projet. Tailwind CSS analysera vos modèles, recherchant les noms de classe Tailwind. Le fichier CSS de sortie généré contiendra uniquement le CSS pour les noms de classe pertinents trouvés dans vos fichiers de modèle. Cela aide à garder les fichiers CSS générés petits puisqu'ils ne contiendront que les styles qui sont réellement utilisés.

Ajoutez ce qui suit à static/src/main.css :

/* static/src/main.css */

@tailwind base;
@tailwind components;
@tailwind utilities;

Ici, nous avons défini toutes les classes base, componentset utilitiesde Tailwind CSS.

Vous avez maintenant Flask-Assets et Tailwind câblés. Ensuite, nous verrons comment servir un fichier index.html pour voir le CSS en action.

Exemple simple

Ajoutez une route avec un bloc principal pour exécuter le serveur de développement Flask sur app.py comme ceci :

# app.py

from flask import Flask, render_template
from flask_assets import Bundle, Environment

app = Flask(__name__)

assets = Environment(app)
css = Bundle("src/main.css", output="dist/main.css")

assets.register("css", css)
css.build()


@app.route("/")
def homepage():
    return render_template("index.html")


if __name__ == "__main__":
    app.run(debug=True)

Créez un dossier "modèles". Ensuite, ajoutez-y un fichier base.html :

<!-- templates/base.html -->

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    {% assets 'css' %}
      <link rel="stylesheet" href="{{ ASSET_URL }}">
    {% endassets %}

    <title>Flask + htmlx + Tailwind CSS</title>
  </head>
  <body class="bg-blue-100">
    {% block content %}
    {% endblock content %}
  </body>
</html>

Prenez note du {% assets 'css' %}bloc. Étant donné que nous avons enregistré le bundle CSS avec l'environnement de l'application, nous pouvons y accéder en utilisant le nom enregistré css, et {{ ASSET_URL }}utilisera automatiquement le chemin.

De plus, nous avons ajouté de la couleur au corps HTML via bg-blue-100, ce qui change la couleur d'arrière-plan en bleu clair.

Ajoutez le fichier index.html :

<!-- templates/index.html -->

{% extends "base.html" %}

{% block content %}
<h1>Hello World</h1>
{% endblock content %}

Maintenant, exécutez la commande suivante à la racine du projet pour analyser les modèles de classes et générer un fichier CSS :

(venv)$ tailwindcss -i ./static/src/main.css -o ./static/dist/main.css --minify

Vous devriez voir un nouveau répertoire nommé "dist" dans le dossier "static".

Prenez note du fichier static/dist/main.css généré .

Démarrez le serveur de développement via python app.pyet accédez à http://localhost:5000 dans votre navigateur pour voir les résultats.

Avec Tailwind configuré, ajoutons htmx dans le mélange et créons une recherche en direct qui affiche les résultats au fur et à mesure que vous tapez.

Exemple de recherche en direct

Plutôt que de récupérer la bibliothèque htmx à partir d'un CDN, téléchargeons-la et utilisons Flask-Assets pour la regrouper.

Téléchargez la bibliothèque depuis https://unpkg.com/htmx.org@1.7.0/dist/htmx.js et enregistrez-la dans "static/src".

Maintenant, pour créer un nouveau bundle pour nos fichiers JavaScript, mettez à jour app.py comme ceci :

# app.py

from flask import Flask, render_template
from flask_assets import Bundle, Environment

app = Flask(__name__)

assets = Environment(app)
css = Bundle("src/main.css", output="dist/main.css")
js = Bundle("src/*.js", output="dist/main.js") # new

assets.register("css", css)
assets.register("js", js) # new
css.build()
js.build() # new


@app.route("/")
def homepage():
    return render_template("index.html")


if __name__ == "__main__":
    app.run(debug=True)

Ici, nous avons créé un nouveau bundle nommé js, qui sort vers static/dist/main.js . Comme nous n'utilisons aucun filtre ici, les fichiers source et cible seront les mêmes.

Ensuite, ajoutez le nouvel élément à notre fichier base.html :

<!-- templates/base.html -->

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    {% assets 'css' %}
      <link rel="stylesheet" href="{{ ASSET_URL }}">
    {% endassets %}

    <!-- new -->
    {% assets 'js' %}
      <script type="text/javascript" src="{{ ASSET_URL }}"></script>
    {% endassets %}

    <title>Flask + htmlx + Tailwind CSS</title>
  </head>
  <body class="bg-blue-100">
    {% block content %}
    {% endblock content %}
  </body>
</html>

Pour que nous ayons des données avec lesquelles travailler, enregistrez https://github.com/testdriveio/flask-htmx-tailwind/blob/master/todo.py dans un nouveau fichier appelé todo.py .

Nous ajouterons la possibilité de rechercher en fonction du titre de chaque tâche.

Mettez à jour le fichier index.html comme suit :

<!-- templates/index.html -->

{% extends 'base.html' %}

{% block content %}
<div class="w-small w-2/3 mx-auto py-10 text-gray-600">
  <input
    type="text"
    name="search"
    hx-post="/search"
    hx-trigger="keyup changed delay:250ms"
    hx-indicator=".htmx-indicator"
    hx-target="#todo-results"
    placeholder="Search"
    class="bg-white h-10 px-5 pr-10 rounded-full text-2xl focus:outline-none"
  >
  <span class="htmx-indicator">Searching...</span>
</div>

<table class="border-collapse w-small w-2/3 mx-auto">
  <thead>
    <tr>
      <th class="p-3 font-bold uppercase bg-gray-200 text-gray-600 border border-gray-300 hidden lg:table-cell">#</th>
      <th class="p-3 font-bold uppercase bg-gray-200 text-gray-600 border border-gray-300 hidden lg:table-cell">Title</th>
      <th class="p-3 font-bold uppercase bg-gray-200 text-gray-600 border border-gray-300 hidden lg:table-cell">Completed</th>
    </tr>
  </thead>
  <tbody id="todo-results">
    {% include 'todo.html' %}
  </tbody>
</table>
{% endblock content %}

Prenons un moment pour examiner les attributs définis à partir de htmx :

<input
  type="text"
  name="search"
  hx-post="/search"
  hx-trigger="keyup changed delay:250ms"
  hx-indicator=".htmx-indicator"
  hx-target="#todo-results"
  placeholder="Search"
  class="bg-white h-10 px-5 pr-10 rounded-full text-2xl focus:outline-none"
>
  1. L'entrée envoie une demande POST au point de /searchterminaison.
  2. La demande est déclenchée via un événement keyup avec un délai de 250 ms. Ainsi, si un nouvel événement de keyup est entré avant que 250 ms ne se soient écoulés après le dernier keyup, la demande n'est pas déclenchée.
  3. La réponse HTML de la requête est alors affichée dans l' #todo-resultsélément.
  4. Nous avons également un indicateur, un élément de chargement qui apparaît après l'envoi de la requête et disparaît après le retour de la réponse.

Ajoutez le fichier templates/todo.html :

<!-- templates/todo.html -->

{% if todos|length>0 %}
  {% for todo in todos %}
    <tr class="bg-white lg:hover:bg-gray-100 flex lg:table-row flex-row lg:flex-row flex-wrap lg:flex-no-wrap mb-10 lg:mb-0">
      <td class="w-full lg:w-auto p-3 text-gray-800 text-center border border-b block lg:table-cell relative lg:static">{{todo.id}}</td>
      <td class="w-full lg:w-auto p-3 text-gray-800 text-center border border-b block lg:table-cell relative lg:static">{{todo.title}}</td>
      <td class="w-full lg:w-auto p-3 text-gray-800 text-center border border-b block lg:table-cell relative lg:static">
        {% if todo.completed %}
          <span class="rounded bg-green-400 py-1 px-3 text-xs font-bold">Yes</span>
        {% else %}
          <span class="rounded bg-red-400 py-1 px-3 text-xs font-bold">No</span>
        {% endif %}
      </td>
    </tr>
  {% endfor %}
{% endif %}

Ce fichier affiche les tâches qui correspondent à notre requête de recherche.

Enfin, ajoutez le gestionnaire de route à app.py :

@app.route("/search", methods=["POST"])
def search_todo():
    search_term = request.form.get("search")

    if not len(search_term):
        return render_template("todo.html", todos=[])

    res_todos = []
    for todo in todos:
        if search_term in todo["title"]:
            res_todos.append(todo)

    return render_template("todo.html", todos=res_todos)

Le /searchpoint de terminaison recherche les tâches et affiche le modèle todo.html avec tous les résultats.

Mettez à jour les importations en haut :

from flask import Flask, render_template, request
from flask_assets import Bundle, Environment

from todo import todos

Ensuite, mettez à jour le fichier CSS de sortie :

(venv)$ tailwindcss -i ./static/src/main.css -o ./static/dist/main.css --minify

Exécutez l'application à l'aide python app.pyde et accédez à nouveau à http://localhost:5000 pour la tester :

démo

Conclusion

Dans ce tutoriel, nous avons vu comment :

  • Configurer Flask-Assets, htmx et Tailwind CSS
  • Créez une application de recherche en direct à l'aide de Flask, Tailwind CSS et htmx

htmx peut afficher des éléments sans recharger la page. Plus important encore, vous pouvez y parvenir sans écrire de code JavaScript. Bien que cela réduise la quantité de travail requise côté client, les données envoyées depuis le serveur peuvent être plus élevées car il envoie du HTML rendu.

Servir des modèles HTML partiels comme celui-ci était populaire au début des années 2000. htmx apporte une touche moderne à cette approche. En général, la diffusion de modèles partiels redevient populaire en raison de la complexité des frameworks tels que React et Vue. Vous pouvez ajouter WebSockets dans le mix pour apporter également des modifications en temps réel. Cette même approche est utilisée par le célèbre Phoenix LiveView . Vous pouvez en savoir plus sur HTML over WebSockets dans The Future of Web Software Is HTML-over-WebSockets and HTML Over WebSockets .

La bibliothèque est encore jeune, mais l'avenir s'annonce très prometteur.

Source :  https://testdrive.io

#flask #htmx #tailwindcss 

What is GEEK

Buddha Community

Comment Configurer Flask Avec htmx et Tailwind CSS
Shayna  Lowe

Shayna Lowe

1656880980

Comment Configurer Flask Avec htmx et Tailwind CSS

Dans ce didacticiel, vous apprendrez à configurer Flask avec htmx et Tailwind CSS . L'objectif de htmx et de Tailwind est de simplifier le développement Web moderne afin que vous puissiez concevoir et activer l'interactivité sans jamais quitter le confort et la facilité du HTML. Nous verrons également comment utiliser Flask-Assets pour regrouper et minimiser les actifs statiques dans une application Flask.

htmx

htmx est une bibliothèque qui vous permet d'accéder à des fonctionnalités de navigateur modernes telles que AJAX, CSS Transitions, WebSockets et Server-Sent Events directement à partir de HTML, plutôt que d'utiliser JavaScript. Il vous permet de créer rapidement des interfaces utilisateur directement dans le balisage.

htmx étend plusieurs fonctionnalités déjà intégrées au navigateur, telles que les requêtes HTTP et la réponse aux événements. Par exemple, plutôt que de pouvoir uniquement effectuer des requêtes GET et POST via des éléments aet form, vous pouvez utiliser des attributs HTML pour envoyer des requêtes GET, POST, PUT, PATCH ou DELETE sur n'importe quel élément HTML :

<button hx-delete="/user/1">Delete</button>

Vous pouvez également mettre à jour des parties d'une page pour créer une application monopage (SPA) : lien CodePen

Ouvrez l'onglet réseau dans les outils de développement du navigateur. Lorsque le bouton est cliqué, une demande XHR est envoyée au point de https://v2.jokeapi.dev/joke/Any?format=txt&safe-modeterminaison. La réponse est ensuite ajoutée à l' pélément avec une idsortie of.

Pour plus d'exemples, consultez la page Exemples d'interface utilisateur de la documentation officielle htmx.

Avantages et inconvénients

Avantages :

  1. Productivité des développeurs : Vous pouvez créer des interfaces utilisateur modernes sans toucher à JavaScript. Pour en savoir plus, consultez An SPA Alternative .
  2. Emballe un coup de poing : la bibliothèque elle-même est petite (~ 10k min.gz'd), sans dépendance et extensible .

Inconvénients :

  1. Maturité de la bibliothèque : Étant donné que la bibliothèque est assez récente, la documentation et les exemples d'implémentation sont rares.
  2. Taille des données transférées : Typiquement, les frameworks SPA (comme React et Vue) fonctionnent en faisant passer des données entre le client et le serveur au format JSON. Les données reçues sont ensuite restituées par le client. htmx, d'autre part, reçoit le rendu HTML du serveur et remplace l'élément cible par la réponse. Le HTML au format rendu est généralement plus grand en termes de taille qu'une réponse JSON.

CSS vent arrière

Tailwind CSS est un framework CSS "utility-first". Plutôt que d'expédier des composants prédéfinis (dans lesquels se spécialisent des frameworks comme Bootstrap et Bulma ), il fournit des blocs de construction sous la forme de classes utilitaires qui permettent de créer des mises en page et des conceptions rapidement et facilement.

Par exemple, prenez le code HTML et CSS suivant :

<style>
.hello {
  height: 5px;
  width: 10px;
  background: gray;
  border-width: 1px;
  border-radius: 3px;
  padding: 5px;
}
</style>

<div class="hello">Hello World</div>

Cela peut être implémenté avec Tailwind comme ceci :

<div class="h-1 w-2 bg-gray-600 border rounded-sm p-1">Hello World</div>

Découvrez le convertisseur CSS Tailwind pour convertir le CSS brut en classes utilitaires équivalentes dans Tailwind. Comparez les résultats.

Avantages et inconvénients

Avantages :

  1. Hautement personnalisable : bien que Tailwind soit fourni avec des classes prédéfinies, elles peuvent être écrasées à l'aide du fichier tailwind.config.js .
  2. Optimisation : vous pouvez configurer Tailwind pour optimiser la sortie CSS en ne chargeant que les classes réellement utilisées.
  3. Mode sombre : il est facile d'implémenter le mode sombre - par exemple, <div class="bg-white dark:bg-black">.

Inconvénients :

  1. Composants : Tailwind ne fournit aucun composant pré-construit officiel comme des boutons, des cartes, des barres de navigation, etc. Les composants doivent être créés à partir de zéro. Il existe quelques ressources communautaires pour des composants tels que Tailwind CSS Components et Tailwind Toolbox , pour n'en nommer que quelques-uns. Il existe également une bibliothèque de composants puissante, bien que payante, créée par les créateurs de Tailwind, appelée Tailwind UI .
  2. Le CSS est en ligne : Cela couple contenu et design, ce qui augmente la taille de la page et encombre le HTML.

Flask-Assets

Flask-Assets est une extension conçue pour gérer les actifs statiques dans une application Flask. Avec lui, vous créez un pipeline d'actifs simple pour :

  1. Compiler Sass et LESS en feuilles de style CSS
  2. Combiner et réduire plusieurs fichiers CSS et JavaScript en un seul fichier pour chacun
  3. Création d'assets bundles à utiliser dans vos modèles

Sur ce, regardons comment travailler avec chacun des outils ci-dessus dans Flask !

Configuration du projet

Pour commencer, créez un nouveau répertoire pour notre projet, créez et activez un nouvel environnement virtuel et installez Flask avec Flask-Assets :

$ mkdir flask-htmx-tailwind && cd flask-htmx-tailwind
$ python3.10 -m venv venv
$ source venv/bin/activate
(venv)$

(venv)$ pip install Flask==2.1.1 Flask-Assets==2.0

Ensuite, installons pytailwindcss et téléchargeons son binaire :

(venv)$ pip install pytailwindcss==0.1.4
(venv)$ tailwindcss

Ensuite, ajoutez un fichier app.py :

# app.py

from flask import Flask
from flask_assets import Bundle, Environment

app = Flask(__name__)

assets = Environment(app)
css = Bundle("src/main.css", output="dist/main.css")

assets.register("css", css)
css.build()

Après avoir importé Bundle et Environment , nous avons créé un nouveau Environmentfichier et y avons enregistré nos actifs CSS via un fichier Bundle.

Le bundle que nous avons créé prend src/main.css en entrée, qui sera ensuite traité et envoyé à dist/main.css lorsque nous exécuterons la CLI Tailwind CSS.

Étant donné que tous les fichiers statiques Flask résident dans le dossier "static" par défaut, les dossiers "src" et "dist" mentionnés ci-dessus résident dans le dossier "static".

Sur ce, configurons Tailwind.

Commencez par créer un fichier de configuration Tailwind :

(venv)$ tailwindcss init

Cette commande a créé un fichier tailwind.config.js à la racine de votre projet. Toutes les personnalisations liées à Tailwind vont dans ce fichier.

Mettez à jour tailwind.config.js comme suit :

module.exports = {
  content: [
    './templates/**/*.html',
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

Prenez note de la section de contenu . Ici, vous configurez les chemins vers les modèles HTML de votre projet. Tailwind CSS analysera vos modèles, recherchant les noms de classe Tailwind. Le fichier CSS de sortie généré contiendra uniquement le CSS pour les noms de classe pertinents trouvés dans vos fichiers de modèle. Cela aide à garder les fichiers CSS générés petits puisqu'ils ne contiendront que les styles qui sont réellement utilisés.

Ajoutez ce qui suit à static/src/main.css :

/* static/src/main.css */

@tailwind base;
@tailwind components;
@tailwind utilities;

Ici, nous avons défini toutes les classes base, componentset utilitiesde Tailwind CSS.

Vous avez maintenant Flask-Assets et Tailwind câblés. Ensuite, nous verrons comment servir un fichier index.html pour voir le CSS en action.

Exemple simple

Ajoutez une route avec un bloc principal pour exécuter le serveur de développement Flask sur app.py comme ceci :

# app.py

from flask import Flask, render_template
from flask_assets import Bundle, Environment

app = Flask(__name__)

assets = Environment(app)
css = Bundle("src/main.css", output="dist/main.css")

assets.register("css", css)
css.build()


@app.route("/")
def homepage():
    return render_template("index.html")


if __name__ == "__main__":
    app.run(debug=True)

Créez un dossier "modèles". Ensuite, ajoutez-y un fichier base.html :

<!-- templates/base.html -->

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    {% assets 'css' %}
      <link rel="stylesheet" href="{{ ASSET_URL }}">
    {% endassets %}

    <title>Flask + htmlx + Tailwind CSS</title>
  </head>
  <body class="bg-blue-100">
    {% block content %}
    {% endblock content %}
  </body>
</html>

Prenez note du {% assets 'css' %}bloc. Étant donné que nous avons enregistré le bundle CSS avec l'environnement de l'application, nous pouvons y accéder en utilisant le nom enregistré css, et {{ ASSET_URL }}utilisera automatiquement le chemin.

De plus, nous avons ajouté de la couleur au corps HTML via bg-blue-100, ce qui change la couleur d'arrière-plan en bleu clair.

Ajoutez le fichier index.html :

<!-- templates/index.html -->

{% extends "base.html" %}

{% block content %}
<h1>Hello World</h1>
{% endblock content %}

Maintenant, exécutez la commande suivante à la racine du projet pour analyser les modèles de classes et générer un fichier CSS :

(venv)$ tailwindcss -i ./static/src/main.css -o ./static/dist/main.css --minify

Vous devriez voir un nouveau répertoire nommé "dist" dans le dossier "static".

Prenez note du fichier static/dist/main.css généré .

Démarrez le serveur de développement via python app.pyet accédez à http://localhost:5000 dans votre navigateur pour voir les résultats.

Avec Tailwind configuré, ajoutons htmx dans le mélange et créons une recherche en direct qui affiche les résultats au fur et à mesure que vous tapez.

Exemple de recherche en direct

Plutôt que de récupérer la bibliothèque htmx à partir d'un CDN, téléchargeons-la et utilisons Flask-Assets pour la regrouper.

Téléchargez la bibliothèque depuis https://unpkg.com/htmx.org@1.7.0/dist/htmx.js et enregistrez-la dans "static/src".

Maintenant, pour créer un nouveau bundle pour nos fichiers JavaScript, mettez à jour app.py comme ceci :

# app.py

from flask import Flask, render_template
from flask_assets import Bundle, Environment

app = Flask(__name__)

assets = Environment(app)
css = Bundle("src/main.css", output="dist/main.css")
js = Bundle("src/*.js", output="dist/main.js") # new

assets.register("css", css)
assets.register("js", js) # new
css.build()
js.build() # new


@app.route("/")
def homepage():
    return render_template("index.html")


if __name__ == "__main__":
    app.run(debug=True)

Ici, nous avons créé un nouveau bundle nommé js, qui sort vers static/dist/main.js . Comme nous n'utilisons aucun filtre ici, les fichiers source et cible seront les mêmes.

Ensuite, ajoutez le nouvel élément à notre fichier base.html :

<!-- templates/base.html -->

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    {% assets 'css' %}
      <link rel="stylesheet" href="{{ ASSET_URL }}">
    {% endassets %}

    <!-- new -->
    {% assets 'js' %}
      <script type="text/javascript" src="{{ ASSET_URL }}"></script>
    {% endassets %}

    <title>Flask + htmlx + Tailwind CSS</title>
  </head>
  <body class="bg-blue-100">
    {% block content %}
    {% endblock content %}
  </body>
</html>

Pour que nous ayons des données avec lesquelles travailler, enregistrez https://github.com/testdriveio/flask-htmx-tailwind/blob/master/todo.py dans un nouveau fichier appelé todo.py .

Nous ajouterons la possibilité de rechercher en fonction du titre de chaque tâche.

Mettez à jour le fichier index.html comme suit :

<!-- templates/index.html -->

{% extends 'base.html' %}

{% block content %}
<div class="w-small w-2/3 mx-auto py-10 text-gray-600">
  <input
    type="text"
    name="search"
    hx-post="/search"
    hx-trigger="keyup changed delay:250ms"
    hx-indicator=".htmx-indicator"
    hx-target="#todo-results"
    placeholder="Search"
    class="bg-white h-10 px-5 pr-10 rounded-full text-2xl focus:outline-none"
  >
  <span class="htmx-indicator">Searching...</span>
</div>

<table class="border-collapse w-small w-2/3 mx-auto">
  <thead>
    <tr>
      <th class="p-3 font-bold uppercase bg-gray-200 text-gray-600 border border-gray-300 hidden lg:table-cell">#</th>
      <th class="p-3 font-bold uppercase bg-gray-200 text-gray-600 border border-gray-300 hidden lg:table-cell">Title</th>
      <th class="p-3 font-bold uppercase bg-gray-200 text-gray-600 border border-gray-300 hidden lg:table-cell">Completed</th>
    </tr>
  </thead>
  <tbody id="todo-results">
    {% include 'todo.html' %}
  </tbody>
</table>
{% endblock content %}

Prenons un moment pour examiner les attributs définis à partir de htmx :

<input
  type="text"
  name="search"
  hx-post="/search"
  hx-trigger="keyup changed delay:250ms"
  hx-indicator=".htmx-indicator"
  hx-target="#todo-results"
  placeholder="Search"
  class="bg-white h-10 px-5 pr-10 rounded-full text-2xl focus:outline-none"
>
  1. L'entrée envoie une demande POST au point de /searchterminaison.
  2. La demande est déclenchée via un événement keyup avec un délai de 250 ms. Ainsi, si un nouvel événement de keyup est entré avant que 250 ms ne se soient écoulés après le dernier keyup, la demande n'est pas déclenchée.
  3. La réponse HTML de la requête est alors affichée dans l' #todo-resultsélément.
  4. Nous avons également un indicateur, un élément de chargement qui apparaît après l'envoi de la requête et disparaît après le retour de la réponse.

Ajoutez le fichier templates/todo.html :

<!-- templates/todo.html -->

{% if todos|length>0 %}
  {% for todo in todos %}
    <tr class="bg-white lg:hover:bg-gray-100 flex lg:table-row flex-row lg:flex-row flex-wrap lg:flex-no-wrap mb-10 lg:mb-0">
      <td class="w-full lg:w-auto p-3 text-gray-800 text-center border border-b block lg:table-cell relative lg:static">{{todo.id}}</td>
      <td class="w-full lg:w-auto p-3 text-gray-800 text-center border border-b block lg:table-cell relative lg:static">{{todo.title}}</td>
      <td class="w-full lg:w-auto p-3 text-gray-800 text-center border border-b block lg:table-cell relative lg:static">
        {% if todo.completed %}
          <span class="rounded bg-green-400 py-1 px-3 text-xs font-bold">Yes</span>
        {% else %}
          <span class="rounded bg-red-400 py-1 px-3 text-xs font-bold">No</span>
        {% endif %}
      </td>
    </tr>
  {% endfor %}
{% endif %}

Ce fichier affiche les tâches qui correspondent à notre requête de recherche.

Enfin, ajoutez le gestionnaire de route à app.py :

@app.route("/search", methods=["POST"])
def search_todo():
    search_term = request.form.get("search")

    if not len(search_term):
        return render_template("todo.html", todos=[])

    res_todos = []
    for todo in todos:
        if search_term in todo["title"]:
            res_todos.append(todo)

    return render_template("todo.html", todos=res_todos)

Le /searchpoint de terminaison recherche les tâches et affiche le modèle todo.html avec tous les résultats.

Mettez à jour les importations en haut :

from flask import Flask, render_template, request
from flask_assets import Bundle, Environment

from todo import todos

Ensuite, mettez à jour le fichier CSS de sortie :

(venv)$ tailwindcss -i ./static/src/main.css -o ./static/dist/main.css --minify

Exécutez l'application à l'aide python app.pyde et accédez à nouveau à http://localhost:5000 pour la tester :

démo

Conclusion

Dans ce tutoriel, nous avons vu comment :

  • Configurer Flask-Assets, htmx et Tailwind CSS
  • Créez une application de recherche en direct à l'aide de Flask, Tailwind CSS et htmx

htmx peut afficher des éléments sans recharger la page. Plus important encore, vous pouvez y parvenir sans écrire de code JavaScript. Bien que cela réduise la quantité de travail requise côté client, les données envoyées depuis le serveur peuvent être plus élevées car il envoie du HTML rendu.

Servir des modèles HTML partiels comme celui-ci était populaire au début des années 2000. htmx apporte une touche moderne à cette approche. En général, la diffusion de modèles partiels redevient populaire en raison de la complexité des frameworks tels que React et Vue. Vous pouvez ajouter WebSockets dans le mix pour apporter également des modifications en temps réel. Cette même approche est utilisée par le célèbre Phoenix LiveView . Vous pouvez en savoir plus sur HTML over WebSockets dans The Future of Web Software Is HTML-over-WebSockets and HTML Over WebSockets .

La bibliothèque est encore jeune, mais l'avenir s'annonce très prometteur.

Source :  https://testdrive.io

#flask #htmx #tailwindcss 

Background Fetch for React Native Apps

react-native-background-fetch

Background Fetch is a very simple plugin which attempts to awaken an app in the background about every 15 minutes, providing a short period of background running-time. This plugin will execute your provided callbackFn whenever a background-fetch event occurs.

There is no way to increase the rate which a fetch-event occurs and this plugin sets the rate to the most frequent possible — you will never receive an event faster than 15 minutes. The operating-system will automatically throttle the rate the background-fetch events occur based upon usage patterns. Eg: if user hasn't turned on their phone for a long period of time, fetch events will occur less frequently or if an iOS user disables background refresh they may not happen at all.

:new: Background Fetch now provides a scheduleTask method for scheduling arbitrary "one-shot" or periodic tasks.

iOS

  • There is no way to increase the rate which a fetch-event occurs and this plugin sets the rate to the most frequent possible — you will never receive an event faster than 15 minutes. The operating-system will automatically throttle the rate the background-fetch events occur based upon usage patterns. Eg: if user hasn't turned on their phone for a long period of time, fetch events will occur less frequently.
  • scheduleTask seems only to fire when the device is plugged into power.
  • ⚠️ When your app is terminated, iOS no longer fires events — There is no such thing as stopOnTerminate: false for iOS.
  • iOS can take days before Apple's machine-learning algorithm settles in and begins regularly firing events. Do not sit staring at your logs waiting for an event to fire. If your simulated events work, that's all you need to know that everything is correctly configured.
  • If the user doesn't open your iOS app for long periods of time, iOS will stop firing events.

Android

Installing the plugin

⚠️ If you have a previous version of react-native-background-fetch < 2.7.0 installed into react-native >= 0.60, you should first unlink your previous version as react-native link is no longer required.

$ react-native unlink react-native-background-fetch

With yarn

$ yarn add react-native-background-fetch

With npm

$ npm install --save react-native-background-fetch

Setup Guides

iOS Setup

react-native >= 0.60

Android Setup

react-native >= 0.60

Example

ℹ️ This repo contains its own Example App. See /example

import React from 'react';
import {
  SafeAreaView,
  StyleSheet,
  ScrollView,
  View,
  Text,
  FlatList,
  StatusBar,
} from 'react-native';

import {
  Header,
  Colors
} from 'react-native/Libraries/NewAppScreen';

import BackgroundFetch from "react-native-background-fetch";

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      events: []
    };
  }

  componentDidMount() {
    // Initialize BackgroundFetch ONLY ONCE when component mounts.
    this.initBackgroundFetch();
  }

  async initBackgroundFetch() {
    // BackgroundFetch event handler.
    const onEvent = async (taskId) => {
      console.log('[BackgroundFetch] task: ', taskId);
      // Do your background work...
      await this.addEvent(taskId);
      // IMPORTANT:  You must signal to the OS that your task is complete.
      BackgroundFetch.finish(taskId);
    }

    // Timeout callback is executed when your Task has exceeded its allowed running-time.
    // You must stop what you're doing immediately BackgroundFetch.finish(taskId)
    const onTimeout = async (taskId) => {
      console.warn('[BackgroundFetch] TIMEOUT task: ', taskId);
      BackgroundFetch.finish(taskId);
    }

    // Initialize BackgroundFetch only once when component mounts.
    let status = await BackgroundFetch.configure({minimumFetchInterval: 15}, onEvent, onTimeout);

    console.log('[BackgroundFetch] configure status: ', status);
  }

  // Add a BackgroundFetch event to <FlatList>
  addEvent(taskId) {
    // Simulate a possibly long-running asynchronous task with a Promise.
    return new Promise((resolve, reject) => {
      this.setState(state => ({
        events: [...state.events, {
          taskId: taskId,
          timestamp: (new Date()).toString()
        }]
      }));
      resolve();
    });
  }

  render() {
    return (
      <>
        <StatusBar barStyle="dark-content" />
        <SafeAreaView>
          <ScrollView
            contentInsetAdjustmentBehavior="automatic"
            style={styles.scrollView}>
            <Header />

            <View style={styles.body}>
              <View style={styles.sectionContainer}>
                <Text style={styles.sectionTitle}>BackgroundFetch Demo</Text>
              </View>
            </View>
          </ScrollView>
          <View style={styles.sectionContainer}>
            <FlatList
              data={this.state.events}
              renderItem={({item}) => (<Text>[{item.taskId}]: {item.timestamp}</Text>)}
              keyExtractor={item => item.timestamp}
            />
          </View>
        </SafeAreaView>
      </>
    );
  }
}

const styles = StyleSheet.create({
  scrollView: {
    backgroundColor: Colors.lighter,
  },
  body: {
    backgroundColor: Colors.white,
  },
  sectionContainer: {
    marginTop: 32,
    paddingHorizontal: 24,
  },
  sectionTitle: {
    fontSize: 24,
    fontWeight: '600',
    color: Colors.black,
  },
  sectionDescription: {
    marginTop: 8,
    fontSize: 18,
    fontWeight: '400',
    color: Colors.dark,
  },
});

export default App;

Executing Custom Tasks

In addition to the default background-fetch task defined by BackgroundFetch.configure, you may also execute your own arbitrary "oneshot" or periodic tasks (iOS requires additional Setup Instructions). However, all events will be fired into the Callback provided to BackgroundFetch#configure:

⚠️ iOS:

  • scheduleTask on iOS seems only to run when the device is plugged into power.
  • scheduleTask on iOS are designed for low-priority tasks, such as purging cache files — they tend to be unreliable for mission-critical tasks. scheduleTask will never run as frequently as you want.
  • The default fetch event is much more reliable and fires far more often.
  • scheduleTask on iOS stop when the user terminates the app. There is no such thing as stopOnTerminate: false for iOS.
// Step 1:  Configure BackgroundFetch as usual.
let status = await BackgroundFetch.configure({
  minimumFetchInterval: 15
}, async (taskId) => {  // <-- Event callback
  // This is the fetch-event callback.
  console.log("[BackgroundFetch] taskId: ", taskId);

  // Use a switch statement to route task-handling.
  switch (taskId) {
    case 'com.foo.customtask':
      print("Received custom task");
      break;
    default:
      print("Default fetch task");
  }
  // Finish, providing received taskId.
  BackgroundFetch.finish(taskId);
}, async (taskId) => {  // <-- Task timeout callback
  // This task has exceeded its allowed running-time.
  // You must stop what you're doing and immediately .finish(taskId)
  BackgroundFetch.finish(taskId);
});

// Step 2:  Schedule a custom "oneshot" task "com.foo.customtask" to execute 5000ms from now.
BackgroundFetch.scheduleTask({
  taskId: "com.foo.customtask",
  forceAlarmManager: true,
  delay: 5000  // <-- milliseconds
});

API Documentation

Config

Common Options

@param {Integer} minimumFetchInterval [15]

The minimum interval in minutes to execute background fetch events. Defaults to 15 minutes. Note: Background-fetch events will never occur at a frequency higher than every 15 minutes. Apple uses a secret algorithm to adjust the frequency of fetch events, presumably based upon usage patterns of the app. Fetch events can occur less often than your configured minimumFetchInterval.

@param {Integer} delay (milliseconds)

ℹ️ Valid only for BackgroundFetch.scheduleTask. The minimum number of milliseconds in future that task should execute.

@param {Boolean} periodic [false]

ℹ️ Valid only for BackgroundFetch.scheduleTask. Defaults to false. Set true to execute the task repeatedly. When false, the task will execute just once.

Android Options

@config {Boolean} stopOnTerminate [true]

Set false to continue background-fetch events after user terminates the app. Default to true.

@config {Boolean} startOnBoot [false]

Set true to initiate background-fetch events when the device is rebooted. Defaults to false.

NOTE: startOnBoot requires stopOnTerminate: false.

@config {Boolean} forceAlarmManager [false]

By default, the plugin will use Android's JobScheduler when possible. The JobScheduler API prioritizes for battery-life, throttling task-execution based upon device usage and battery level.

Configuring forceAlarmManager: true will bypass JobScheduler to use Android's older AlarmManager API, resulting in more accurate task-execution at the cost of higher battery usage.

let status = await BackgroundFetch.configure({
  minimumFetchInterval: 15,
  forceAlarmManager: true
}, async (taskId) => {  // <-- Event callback
  console.log("[BackgroundFetch] taskId: ", taskId);
  BackgroundFetch.finish(taskId);
}, async (taskId) => {  // <-- Task timeout callback
  // This task has exceeded its allowed running-time.
  // You must stop what you're doing and immediately .finish(taskId)
  BackgroundFetch.finish(taskId);
});
.
.
.
// And with with #scheduleTask
BackgroundFetch.scheduleTask({
  taskId: 'com.foo.customtask',
  delay: 5000,       // milliseconds
  forceAlarmManager: true,
  periodic: false
});

@config {Boolean} enableHeadless [false]

Set true to enable React Native's Headless JS mechanism, for handling fetch events after app termination.

  • 📂 index.js (MUST BE IN index.js):
import BackgroundFetch from "react-native-background-fetch";

let MyHeadlessTask = async (event) => {
  // Get task id from event {}:
  let taskId = event.taskId;
  let isTimeout = event.timeout;  // <-- true when your background-time has expired.
  if (isTimeout) {
    // This task has exceeded its allowed running-time.
    // You must stop what you're doing immediately finish(taskId)
    console.log('[BackgroundFetch] Headless TIMEOUT:', taskId);
    BackgroundFetch.finish(taskId);
    return;
  }
  console.log('[BackgroundFetch HeadlessTask] start: ', taskId);

  // Perform an example HTTP request.
  // Important:  await asychronous tasks when using HeadlessJS.
  let response = await fetch('https://reactnative.dev/movies.json');
  let responseJson = await response.json();
  console.log('[BackgroundFetch HeadlessTask] response: ', responseJson);

  // Required:  Signal to native code that your task is complete.
  // If you don't do this, your app could be terminated and/or assigned
  // battery-blame for consuming too much time in background.
  BackgroundFetch.finish(taskId);
}

// Register your BackgroundFetch HeadlessTask
BackgroundFetch.registerHeadlessTask(MyHeadlessTask);

@config {integer} requiredNetworkType [BackgroundFetch.NETWORK_TYPE_NONE]

Set basic description of the kind of network your job requires.

If your job doesn't need a network connection, you don't need to use this option as the default value is BackgroundFetch.NETWORK_TYPE_NONE.

NetworkTypeDescription
BackgroundFetch.NETWORK_TYPE_NONEThis job doesn't care about network constraints, either any or none.
BackgroundFetch.NETWORK_TYPE_ANYThis job requires network connectivity.
BackgroundFetch.NETWORK_TYPE_CELLULARThis job requires network connectivity that is a cellular network.
BackgroundFetch.NETWORK_TYPE_UNMETEREDThis job requires network connectivity that is unmetered. Most WiFi networks are unmetered, as in "you can upload as much as you like".
BackgroundFetch.NETWORK_TYPE_NOT_ROAMINGThis job requires network connectivity that is not roaming (being outside the country of origin)

@config {Boolean} requiresBatteryNotLow [false]

Specify that to run this job, the device's battery level must not be low.

This defaults to false. If true, the job will only run when the battery level is not low, which is generally the point where the user is given a "low battery" warning.

@config {Boolean} requiresStorageNotLow [false]

Specify that to run this job, the device's available storage must not be low.

This defaults to false. If true, the job will only run when the device is not in a low storage state, which is generally the point where the user is given a "low storage" warning.

@config {Boolean} requiresCharging [false]

Specify that to run this job, the device must be charging (or be a non-battery-powered device connected to permanent power, such as Android TV devices). This defaults to false.

@config {Boolean} requiresDeviceIdle [false]

When set true, ensure that this job will not run if the device is in active use.

The default state is false: that is, the for the job to be runnable even when someone is interacting with the device.

This state is a loose definition provided by the system. In general, it means that the device is not currently being used interactively, and has not been in use for some time. As such, it is a good time to perform resource heavy jobs. Bear in mind that battery usage will still be attributed to your application, and shown to the user in battery stats.


Methods

Method NameArgumentsReturnsNotes
configure{FetchConfig}, callbackFn, timeoutFnPromise<BackgroundFetchStatus>Configures the plugin's callbackFn and timeoutFn. This callback will fire each time a background-fetch event occurs in addition to events from #scheduleTask. The timeoutFn will be called when the OS reports your task is nearing the end of its allowed background-time.
scheduleTask{TaskConfig}Promise<boolean>Executes a custom task. The task will be executed in the same Callback function provided to #configure.
statuscallbackFnPromise<BackgroundFetchStatus>Your callback will be executed with the current status (Integer) 0: Restricted, 1: Denied, 2: Available. These constants are defined as BackgroundFetch.STATUS_RESTRICTED, BackgroundFetch.STATUS_DENIED, BackgroundFetch.STATUS_AVAILABLE (NOTE: Android will always return STATUS_AVAILABLE)
finishString taskIdVoidYou MUST call this method in your callbackFn provided to #configure in order to signal to the OS that your task is complete. iOS provides only 30s of background-time for a fetch-event -- if you exceed this 30s, iOS will kill your app.
startnonePromise<BackgroundFetchStatus>Start the background-fetch API. Your callbackFn provided to #configure will be executed each time a background-fetch event occurs. NOTE the #configure method automatically calls #start. You do not have to call this method after you #configure the plugin
stop[taskId:String]Promise<boolean>Stop the background-fetch API and all #scheduleTask from firing events. Your callbackFn provided to #configure will no longer be executed. If you provide an optional taskId, only that #scheduleTask will be stopped.

Debugging

iOS

🆕 BGTaskScheduler API for iOS 13+

  • ⚠️ At the time of writing, the new task simulator does not yet work in Simulator; Only real devices.
  • See Apple docs Starting and Terminating Tasks During Development
  • After running your app in XCode, Click the [||] button to initiate a Breakpoint.
  • In the console (lldb), paste the following command (Note: use cursor up/down keys to cycle through previously run commands):
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.transistorsoft.fetch"]
  • Click the [ > ] button to continue. The task will execute and the Callback function provided to BackgroundFetch.configure will receive the event.

Simulating task-timeout events

  • Only the new BGTaskScheduler api supports simulated task-timeout events. To simulate a task-timeout, your fetchCallback must not call BackgroundFetch.finish(taskId):
let status = await BackgroundFetch.configure({
  minimumFetchInterval: 15
}, async (taskId) => {  // <-- Event callback.
  // This is the task callback.
  console.log("[BackgroundFetch] taskId", taskId);
  //BackgroundFetch.finish(taskId); // <-- Disable .finish(taskId) when simulating an iOS task timeout
}, async (taskId) => {  // <-- Event timeout callback
  // This task has exceeded its allowed running-time.
  // You must stop what you're doing and immediately .finish(taskId)
  print("[BackgroundFetch] TIMEOUT taskId:", taskId);
  BackgroundFetch.finish(taskId);
});
  • Now simulate an iOS task timeout as follows, in the same manner as simulating an event above:
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateExpirationForTaskWithIdentifier:@"com.transistorsoft.fetch"]

Old BackgroundFetch API

  • Simulate background fetch events in XCode using Debug->Simulate Background Fetch
  • iOS can take some hours or even days to start a consistently scheduling background-fetch events since iOS schedules fetch events based upon the user's patterns of activity. If Simulate Background Fetch works, your can be sure that everything is working fine. You just need to wait.

Android

  • Observe plugin logs in $ adb logcat:
$ adb logcat *:S ReactNative:V ReactNativeJS:V TSBackgroundFetch:V
  • Simulate a background-fetch event on a device (insert <your.application.id>) (only works for sdk 21+:
$ adb shell cmd jobscheduler run -f <your.application.id> 999
  • For devices with sdk <21, simulate a "Headless JS" event with (insert <your.application.id>)
$ adb shell am broadcast -a <your.application.id>.event.BACKGROUND_FETCH

Download Details:
Author: transistorsoft
Source Code: https://github.com/transistorsoft/react-native-background-fetch
License: MIT license

#react  #reactnative  #mobileapp  #javascript 

Hire CSS Developer

Want to develop a website or re-design using CSS Development?

We build a website and we implemented CSS successfully if you are planning to Hire CSS Developer from HourlyDeveloper.io, We can fill your Page with creative colors and attractive Designs. We provide services in Web Designing, Website Redesigning and etc.

For more details…!!
Consult with our experts:- https://bit.ly/3hUdppS

#hire css developer #css development company #css development services #css development #css developer #css

Tailwind CSS tutorial

In this tutorial I would like to introduce you to one of the fastest growing and promising CSS Frameworks at the moment, Tailwind CSS. It is different from other frameworks, such as Bootstrap, because it is built on a new way of building user interfaces using a utility-first CSS classes structure, as opposed to the OOCSS structure from other frameworks.

By the end of this guide you will be able to install, configure and build a responsive hero section (live demo) using the utility-first classes from Tailwind CSS and configure the project using the recommended PostCSS powered Tailwind configuration file for better maintainability and versatility.

Here’s the table of contents for this tutorial for Tailwind CSS:

  • Introducing Tailwind CSS
  • Adding Tailwind CSS to your project via a package manager
  • Creating the configuration file and process your CSS with Tailwind
  • Building a responsive hero section using the utility-first classes from Tailwind
  • Customize fonts, colors and add extra classes using the configuration file
  • Reduce loading time and file size by purging the unused classes from your CSS
  • Conclusion and summary

Read the full tutorial from Themesberg.

#tailwind #tailwind-css #tailwind-css-tutorial #tutorial #open-source

Popular Tailwind CSS Plugins and Extensions - Themesberg Blog

By reading this article you will browse a list of the most popular plugins and extensions for the utility-first CSS framework, Tailwind CSS. Although the default code base of the framework already covers a lot of the needs when building user interfaces, you can never get enough plugins and extensions powered by the open-source community.

One of the requirements for a plugin to appear on this list is to be open-source with no other strings attached so that the developers browsing this list can stay assured that they can use the plugin for their Tailwind CSS powered project.

Check out the list of Tailwind CSS Plugins and Extensions on Themesberg.

#tailwindcss #tailwind #tailwind-css #tailwind-css-plugins #themesberg