Fermer

juillet 25, 2019

Conception et construction d'une application Web progressive sans cadre (2e partie)


Dans le premier article de cette série, votre auteur, un novice du langage JavaScript, s'était fixé pour objectif de concevoir et de coder une application Web de base. L’application devait s’appeler ‘In / Out’, une application permettant d’organiser des jeux en équipe. Dans cet article, nous allons nous concentrer sur la réalisation de l'application 'In / Out'.

La raison d'être de cette aventure était de pousser un peu votre humble auteur dans les disciplines de la conception visuelle et du codage JavaScript. . La fonctionnalité de l’application que j’avais décidé de construire n’était pas différente de celle d’une application «à faire». Il est important de souligner que ce n’était pas un exercice de pensée originale. La destination était beaucoup moins importante que le voyage.

Vous voulez savoir comment l'application a abouti? Pointez le navigateur de votre téléphone à l'adresse https://io.benfrain.com .

Lisez la première partie de Conception et construction d'une application Web progressive sans cadre .

Voici: un résumé de ce que nous allons couvrir dans cet article:

  • La structure du projet et les raisons pour lesquelles j'ai choisi Gulp comme outil de construction;
  • Les modèles de conception des applications et leur signification dans la pratique;
  • Comment stocker et visualisez l'état de l'application;
  • comment CSS a été défini sur les composants;
  • les subtilités d'UI / UX ont été utilisées pour rendre les choses plus "similaires à celles d'une application";
  • comment le mandat a changé par itération.

Commençons avec les outils de compilation.

Outils de compilation

Pour pouvoir utiliser mes outils de base TypeScipt et PostCSS et créer une expérience de développement décente, il me faudrait un système de construction.

Depuis environ cinq ans, je construis des prototypes d’interface en HTML / CSS et, dans une moindre mesure, en JavaScript. Jusqu'à récemment, j'utilisais presque exclusivement Gulp avec un certain nombre de plugins pour répondre à mes besoins de construction relativement modestes.

Je dois généralement traiter CSS, convertir JavaScript ou TypeScript en JavaScript plus largement pris en charge, et occasionnellement effectuer des tâches connexes, telles que minifier la sortie du code et optimiser les actifs. L’utilisation de Gulp m’a toujours permis de résoudre ces problèmes avec aplomb.

Pour ceux qui ne connaissent pas bien, Gulp vous permet d’écrire JavaScript pour «faire quelque chose» aux fichiers de votre système de fichiers local.
Pour utiliser Gulp, vous avez généralement un seul fichier (appelé gulpfile.js ) à la racine de votre projet. Ce fichier JavaScript vous permet de définir des tâches en tant que fonctions. Vous pouvez ajouter des "plugins" tiers, qui sont essentiellement des fonctions JavaScript supplémentaires, qui traitent de tâches spécifiques.

Exemple de tâche Gulp

Un exemple de tâche Gulp pourrait utiliser un plugin pour exploiter PostCSS afin de le traiter en CSS lorsque vous modifiez une feuille de style de création ( de gulp-postcss ). Ou encore, compilez les fichiers TypeScript en JavaScript ([ de gulp-typescript en vanille) lorsque vous les enregistrez. Voici un exemple simple de la façon dont vous écrivez une tâche dans Gulp. Cette tâche utilise le plug-in «del» gulp pour supprimer tous les fichiers d’un dossier appelé «build»:

 var del = require ("del");

gulp.task ("clean", function () {
  return del (["build/**/*"]);
});

Le require assigne le plug-in del à une variable. Ensuite, la méthode gulp.task est appelée. Nous nommons la tâche avec une chaîne en tant que premier argument («clean»), puis exécutons une fonction qui, dans ce cas, utilise la méthode ‘del’ pour supprimer le dossier qui lui est transmis en tant qu’argument. Les symboles astérisques correspondent à des "glob" qui indiquent essentiellement "n'importe quel fichier dans n'importe quel dossier" du dossier de construction.

Les tâches Gulp peuvent être plus compliquées, mais c’est la mécanique de la façon dont les choses sont gérées. La vérité est que, avec Gulp, vous n’avez pas besoin d’être un assistant JavaScript pour vous en sortir; les compétences de copier et coller de grade 3 sont tout ce dont vous avez besoin.

J’avais bloqué Gulp comme outil de création par défaut / gestionnaire de tâches pendant toutes ces années, avec une politique de «s’il n’était pas cassé; n’essayez pas de le réparer ".

Cependant, j’avais peur de me coincer dans mes habitudes. C’est un piège facile à tomber. Tout d'abord, vous commencez à passer des vacances au même endroit chaque année, puis vous refusez d'adopter de nouvelles tendances de la mode avant de refuser résolument d'essayer de nouveaux outils de création.

J'avais entendu beaucoup parler d'Internet sur 'Webpack' et Je pensais qu'il était de mon devoir d'essayer un projet en utilisant le nouveau toast du développeur front-end cool-kids.

Webpack

Je me souviens très bien d'avoir sauté sur le webpack.js.org . site avec un vif intérêt. La première explication de ce que Webpack est et a réellement commencé ainsi:

 import bar from './bar';

Dis quoi? Selon les mots du Dr Evil, «Lancez-moi un os piquant ici, Scott».

Je sais que c'est mon propre problème à régler, mais j'ai développé une répulsion pour toutes les explications de codage qui mentionnent «foo». , 'bar' ou 'baz'. Ce qui s'ajoute à l'absence totale de description succincte de ce que Webpack était en réalité pour m'avait soupçonné que ce n'était peut-être pas pour moi.

En approfondissant un peu la documentation de Webpack, une explication un peu moins opaque fut proposée, «Webpack est essentiellement un ensemble de modules statiques destiné aux applications JavaScript modernes».

Hmmm. Ensemble de module statique. Était-ce ce que je voulais? Je n’étais pas convaincu. J'ai lu mais plus je lisais, moins j'étais clair. À l’époque, des concepts tels que les graphes de dépendance, le rechargement de modules à chaud et les points d’entrée étaient essentiellement perdus pour moi.

Quelques soirées de recherche sur Webpack plus tard, j’ai abandonné toute idée de l’utiliser.

La bonne situation et des mains plus expérimentées, Webpack est immensément puissant et approprié, mais cela semblait excessif pour mes humbles besoins. Le regroupement de modules, l’agrégation d’arbres et le rechargement à chaud de modules semblaient excellents; Je n'étais tout simplement pas convaincue d'en avoir besoin pour ma petite "application".

Alors, revenons à Gulp à l'époque.

Sur le thème de ne pas changer les choses pour changer, une autre technologie que je voulais évaluer était Yarn. sur NPM pour la gestion des dépendances de projet. Jusque-là, j'avais toujours utilisé NPM et Yarn devenait une alternative meilleure et plus rapide. Je n'ai pas grand chose à dire à propos de Yarn si ce n'est que vous utilisez actuellement NPM et que tout va bien, vous n'avez pas besoin d'essayer Yarn.

Un outil qui est arrivé trop tard pour que je puisse évaluer cette application est Parceljs. Avec aucune configuration et un BrowserSync comme un rechargement de navigateur, j’ai trouvé un excellent utilitaire! De plus, à la défense de Webpack, on me dit que la version 4 de Webpack n’exige pas de fichier de configuration. De façon anecdotique, dans un sondage récent que j'ai publié sur Twitter sur les 87 répondants, plus de la moitié ont choisi Webpack plutôt que Gulp, Parcel ou Grunt.

J'ai créé mon fichier Gulp avec des fonctionnalités de base pour être opérationnel. .

Une tâche 'par défaut' surveillait les dossiers 'source' des feuilles de style et des fichiers TypeScript et les compilait dans un dossier build avec le code HTML de base et les mappes source associées.

I BrowserSync travaille également avec Gulp. Je ne sais peut-être pas quoi faire d’un fichier de configuration Webpack, mais cela ne veut pas dire que j’étais un animal. soooo 2010 et BrowserSync vous donnent cette boucle de rétroaction et d'itération très utile pour le codage frontal.

Voici le . fichier gulp de base du 11.6.2017

Vous pouvez voir comment j'ai peaufiné le fichier Gulpfile plus près de la fin de l'expédition, en ajoutant une minification avec ugilify :

. Structure du projet

de mes choix technologiques, certains éléments de l’organisation du code de l’application se définissaient eux-mêmes. Un gulpfile.js à la racine du projet, un dossier node_modules (où Gulp stocke le code du plugin) un preCSS pour les feuilles de style de création, un ts pour les fichiers TypeScript, et un dossier build pour le code compilé à vivre.

L’idée était d’avoir un index.html contenant le 'shell' de l'application, y compris toute structure HTML non dynamique, puis des liens vers les styles et le fichier JavaScript permettant à l'application de fonctionner. Sur le disque, cela ressemblerait à quelque chose comme ceci:

 build /
node_modules /
préCSS /
    img /
    partiels /
    styles.css
ts /
.gitignore
gulpfile.js
index.html
package.json
tsconfig.json

Configurer BrowserSync pour examiner ce dossier build signifiait que je pouvais pointer mon navigateur sur localhost: 3000 et tout allait bien.

Avec un système de construction de base en place, des fichiers Une organisation installée et quelques concepts de base pour commencer, j'avais épuisé le fourrage de la procrastination que je pouvais légitimement utiliser pour m'empêcher de construire la chose!

Rédaction d'une demande

Principe du fonctionnement de l'application était-ce. Il y aurait un magasin de données. Lorsque le code JavaScript est chargé, il charge ces données, parcourt chaque lecteur de données, créant le code HTML nécessaire pour représenter chaque lecteur sous forme de ligne dans la présentation et les plaçant dans la section in / out appropriée. Ensuite, les interactions de l'utilisateur déplaceraient un joueur d'un état à un autre. Simple.

En ce qui concerne l’écriture de la demande, les deux grands problèmes conceptuels à comprendre étaient les suivants:

  1. Comment représenter les données d’une application de manière à pouvoir être facilement étendues et manipulées; [19659007] Comment faire en sorte que l’interface utilisateur réagisse lorsque les données ont été modifiées à partir de la saisie de l’utilisateur

L’un des moyens les plus simples de représenter une structure de données en JavaScript consiste à utiliser la notation objet. Cette phrase se lit un peu en informatique. Plus simplement, un 'objet' dans JavaScript est une manière pratique de stocker des données.

Considérez cet objet JavaScript affecté à une variable appelée ioState (pour l'état In / Out):

 var ioState. = {
    Nombre: 0, // Total cumulé de joueurs
    RosterCount: 0; // Nombre total de joueurs possibles
    ToolsExposed: false, // Indique si l'interface utilisateur des outils est affichée.
    Joueurs: []// Un détenteur pour les joueurs
}

Si vous ne connaissez pas vraiment le langage JavaScript, vous pouvez probablement au moins comprendre ce qui se passe: chaque ligne située entre les accolades est une propriété (ou une "clé" dans le jargon JavaScript) et une paire valeur. Vous pouvez définir toutes sortes de choses sur une clé JavaScript. Par exemple, des fonctions, des tableaux d’autres données ou des objets imbriqués. Voici un exemple:

 var testObject = {
  testFunction: function () {
    retourner "saucisses";
  },
  testArray: [3,7,9],
  nestedtObject {
    clé1: "valeur1",
    touche 2: 2,
  }
}

Le résultat final est qu'en utilisant ce type de structure de données, vous pouvez obtenir et définir n'importe quelle clé de l'objet. Par exemple, si nous voulons définir le compte de l'objet ioState sur 7:

 ioState.Count = 7;

Si nous voulons définir un morceau de texte sur cette valeur, la notation fonctionne comme suit:

 aTextNode.textContent = ioState.Count;

Vous pouvez voir que l'obtention de valeurs et la définition de valeurs pour cet objet d'état est simple du côté JavaScript des choses. Cependant, refléter ces changements dans l'interface utilisateur l'est moins. C’est là le domaine principal dans lequel les frameworks et les bibliothèques cherchent à résorber la douleur.

En termes généraux, lorsqu’il s’agit de mettre à jour l’interface utilisateur en fonction de l’état, il est préférable d’éviter d’interroger le DOM, ce qui est généralement considéré une approche sous-optimale.

Considérons l'interface In / Out. Il affiche généralement une liste de joueurs potentiels pour un match. Ils sont listés verticalement, l'un sous l'autre, en bas de page.

Peut-être que chaque joueur est représenté dans le DOM avec une étiquette entourant une case à cocher saisie . De cette manière, cliquer sur un joueur le basculerait sur "Dans" en vertu du libellé rendant l’entrée "vérifiée".

Pour mettre à jour notre interface, nous pourrions avoir un "auditeur" sur chaque élément d’entrée dans le code JavaScript. En un clic ou une modification, la fonction interroge le DOM et compte combien d'entrées de notre lecteur sont vérifiées. Sur la base de ce nombre, nous mettrions alors à jour quelque chose d’autre dans le DOM pour montrer à l’utilisateur combien de joueurs sont vérifiés.

Voyons le coût de cette opération de base. Nous écoutons sur plusieurs nœuds du DOM le contrôle / vérification d'une entrée, puis nous interrogeons le DOM pour savoir combien de types de DOM particuliers sont vérifiés, puis nous écrivons quelque chose dans le DOM pour montrer à l'utilisateur, en termes d'interface utilisateur, le nombre de joueurs. nous venons de compter.

L'alternative serait de conserver l'état de l'application en tant qu'objet JavaScript en mémoire. Un clic sur le bouton ou l'entrée dans le DOM pourrait simplement mettre à jour l'objet JavaScript, puis, en fonction de cette modification de l'objet JavaScript, effectuer une mise à jour en une passe de toutes les modifications d'interface nécessaires. Nous pourrions passer à l'interrogation du DOM pour compter les lecteurs, car l'objet JavaScript contiendrait déjà cette information.

. L'utilisation d'une structure d'objet JavaScript pour l'état semblait simple mais suffisamment souple pour encapsuler l'état de l'application à tout moment. La théorie de la façon dont cela pourrait être géré semblait également suffisamment valable – c’est en quoi consistaient des expressions telles que «flux de données à sens unique»? Cependant, le premier véritable truc serait de créer un code qui mettrait automatiquement à jour l'interface utilisateur en fonction des modifications apportées à ces données.

La bonne nouvelle est que les gens plus intelligents que ce que j'ai déjà imaginé ( dieu merci) ! ). Les gens perfectionnent les approches de ce type de défi depuis le début des applications. Cette catégorie de problèmes est le pain et le beurre de «modèles de conception». Le surnom de "motif de conception" me semblait ésotérique au début, mais après avoir creusé un peu, tout a commencé à sonner moins en informatique et plus de bon sens.

Design Patterns

Un motif de conception, dans le lexique informatique, est un manière prédéfinie et éprouvée de résoudre un problème technique commun. Pensez aux modèles de conception comme à l'équivalent de code d'une recette de cuisine.

La ​​littérature la plus célèbre sur les modèles de conception est peut-être "Modèles de conception: éléments d'un logiciel orienté objet réutilisable" datant de 1994. Bien que cela concerne le C ++ et le les concepts sont transférables. Pour "JavaScript", le manuel "Learning JavaScript Design Patterns" d’Addy Osmani couvre un terrain similaire. Vous pouvez également le lire en ligne gratuitement ici .

Motif de l'observateur

Les motifs de conception sont généralement divisés en trois groupes: créationnel, structurel et comportemental. Je recherchais quelque chose de comportement qui aiderait à gérer les changements de communication autour des différentes parties de l'application.

Plus récemment, j'ai vu et lu un très bon rapport approfondi sur la mise en œuvre de la réactivité dans une application de Gregg Pollack. Il existe à la fois un article de blog et une vidéo pour votre plus grand plaisir ici .

Lors de la lecture de la description préliminaire du motif 'Observer' dans Learning JavaScript Design Patterns J'étais sûr que c'était le cas le modèle pour moi. Il est décrit comme suit:

The Observer est un modèle de conception dans lequel un objet (appelé sujet) maintient une liste d'objets en fonction de celui-ci (observateurs), en le notifiant automatiquement de tout changement d'état.

doit informer les observateurs de quelque chose d'intéressant, il diffuse une notification aux observateurs (qui peut inclure des données spécifiques liées au sujet de la notification).

La clé de mon enthousiasme était que cela semblait offrir un moyen de mise à jour. eux-mêmes si nécessaire.

Supposons que l'utilisateur clique sur une joueuse nommée “Betty” pour sélectionner le fait qu'elle était 'In' pour le jeu. Quelques opérations peuvent être nécessaires dans l'interface utilisateur:

  1. Ajoutez 1 au décompte du jeu
  2. Supprimez Betty du groupe de joueurs 'Out'
  3. Ajoutez Betty au groupe de joueurs 'In'

app aurait également besoin de mettre à jour les données qui représentent l'interface utilisateur. Ce que j’avais le plus envie d’éviter, c’était:

 playerName.addEventListener ("click", playerToggle);

fonction playerToggle () {
  if (inPlayers.includes (e.target.textContent)) {
    setPlayerOut (e.target.textContent);
    decrementPlayerCount ();
  } autre {
    setPlayerIn (e.target.textContent);
    incrementPlayerCount ();
  }
}

L'objectif était de disposer d'un élégant flux de données mettant à jour ce qui était nécessaire dans le DOM lorsque et si les données centrales étaient modifiées.

Avec un modèle Observer, il était possible d'envoyer des mises à jour de l'état et donc interface utilisateur assez succinctement. Voici un exemple, la fonction utilisée pour ajouter un nouveau lecteur à la liste:

 function itemAdd (itemString: string) {
  let currentDataSet = getCurrentDataSet ();
  var newPerson = new makePerson (itemString);
  io.items [currentDataSet] .EventData.splice (0, 0, newPerson);
  io.notify ({
    articles: io.items
  });
}

La partie pertinente pour le motif Observer, à savoir la méthode io.notify . Comme cela nous montre que nous modifions la partie items de l’état de l’application, laissez-moi vous montrer l’observateur qui a écouté les modifications apportées aux ‘items’:

 io.addObserver ({
  accessoires: ["items"],
  rappel: fonction renderItems () {
    // Code qui met à jour tout ce qui concerne les éléments ...
  }
});

Nous avons une méthode de notification qui modifie les données, puis les observateurs qui répondent lorsque les propriétés qui les intéressent sont mises à jour.

Avec cette approche, l'application pourrait avoir des observables surveillant les modifications de toute propriété de les données et exécuter une fonction chaque fois qu'un changement se produit.

Si vous êtes intéressé par le modèle d'observateur que j'ai choisi, je le décris plus en détail ici .

Il existait maintenant une approche pour mettre à jour le Interface utilisateur effectivement basée sur l'état. Peachy. Cependant, cela me laissait toujours deux problèmes criants.

L’une consistait à stocker l’état lors des rechargements / sessions de page et le fait que, malgré le bon fonctionnement de l’interface utilisateur, elle ne ressemblait pas vraiment à une application. Par exemple, si vous appuyez sur un bouton, l'interface utilisateur change instantanément à l'écran.

Commençons par le stockage.

Saving State

Mon principal intérêt dans le domaine du développement était de comprendre comment les interfaces d'applications pouvaient être construites et fabriquées. interactif avec JavaScript. Comment stocker et récupérer des données d'un serveur ou s'attaquer à l'authentification de l'utilisateur et aux connexions était «hors de portée».

Par conséquent, au lieu de me connecter à un service Web pour les besoins de stockage de données, j'ai choisi de conserver toutes les données sur le serveur. client. Il existe un certain nombre de méthodes de plate-forme Web pour stocker des données sur un client. J'ai opté pour localStorage .

L'API pour localStorage est incroyablement simple. Vous définissez et obtenez des données comme ceci:

 // Définissez quelque chose
localStorage.setItem ("votre clé", "votre valeur");
// Obtenir quelque chose
localStorage.getItem ("yourKey");

LocalStorage utilise une méthode setItem à laquelle vous passez deux chaînes. Le premier est le nom de la clé avec laquelle vous voulez stocker les données et la deuxième chaîne est la chaîne que vous voulez stocker. La méthode getItem prend une chaîne en tant qu'argument qui vous renvoie tout ce qui est stocké sous cette clé dans localStorage. Sympa et simple.

Cependant, l’une des raisons de ne pas utiliser localStorage est le fait que tout doit être sauvegardé sous forme de "chaîne". Cela signifie que vous ne pouvez pas stocker directement quelque chose comme un tableau ou un objet. Par exemple, essayez d’exécuter ces commandes dans la console de votre navigateur:

 // Définir quelque chose
localStorage.setItem ("myArray", [1, 2, 3, 4]);
// Obtenir quelque chose
localStorage.getItem ("myArray"); // Journaux "1,2,3,4"

Même si nous avons essayé de définir la valeur de ‘myArray’ en tant que tableau; quand nous l'avons récupéré, il avait été stocké sous forme de chaîne (remarquez les guillemets autour de '1,2,3,4').

Vous pouvez certainement stocker des objets et des tableaux avec localStorage, mais vous devez être conscient qu'ils en ont besoin. conversion en chaîne de chaînes.

Ainsi, pour écrire des données d'état dans localStorage, il a été écrit dans une chaîne avec la méthode JSON.stringify () comme ceci:

 const storage = window .stockage local;
storage.setItem ("players", JSON.stringify (io.items));

Lorsque les données devaient être extraites de localStorage, la chaîne a été reconvertie en données utilisables avec la méthode JSON.parse () comme ceci:

 const players = JSON.parse (storage.getItem ( "joueurs"));

L'utilisation de localStorage signifiait que tout appartenait au client, ce qui signifiait qu'aucun service tiers ni aucun problème de stockage de données ne se posait.

Les données persistaient désormais dans les mises à jour et les sessions – Yay! La mauvaise nouvelle est que localStorage ne survit pas à un utilisateur qui vide les données de son navigateur. Quand quelqu'un faisait cela, toutes ses données In / Out seraient perdues. C’est une grave lacune.

Il n’est pas difficile d’apprendre que le «stockage local» n’est probablement pas la meilleure solution pour les applications «appropriées». Outre le problème de chaîne susmentionné, il est également lent pour un travail sérieux, car il bloque le «fil principal». Des alternatives se présentent, comme KV Storage mais pour l’instant, notez-en quelques mots pour en limiter l’utilisation, en fonction de leur pertinence.

Malgré la fragilité de la sauvegarde des données localement sur le périphérique d’un utilisateur, de la connexion à un service ou base de données a été résisté. Au lieu de cela, le problème a été évité en proposant une option «chargement / enregistrement». Cela permettrait à tout utilisateur d’In / Out de sauvegarder ses données sous forme de fichier JSON qui pourrait être chargé dans l’application si besoin est.

Cela fonctionnait bien sous Android, mais beaucoup moins élégant pour iOS. Sur un iPhone, le texte affiché à l'écran était comme ceci:

 Sur un iPhone, il avait provoqué une explosion de texte à l'écran
( Grand aperçu )

As you On peut s’imaginer, j’étais loin d’être le seul à réprimander Apple via WebKit au sujet de cette lacune. Le bogue pertinent était ici .

Au moment de la rédaction de ce bogue, il existe une solution et un correctif, mais il doit encore faire son chemin dans iOS Safari. Apparemment, iOS13 le corrige mais c’est en version bêta au moment où j’écris.

Donc, pour mon produit minimum viable, c’était une adresse de stockage. Il était maintenant temps d'essayer de rendre les choses plus "similaires à une application"!

App-I-Ness

Après de nombreuses discussions avec de nombreuses personnes, il s'avère très difficile de définir exactement ce que signifie "comme une application". [19659003] En fin de compte, j’ai décidé que «app-like» était synonyme d’une fluidité visuelle généralement absente du Web. Quand je pense aux applications qui se sentent bien à utiliser, elles présentent toutes un mouvement. Pas gratuit, mais mouvement qui ajoute à l'histoire de vos actions. Ce peut être les transitions de page entre les écrans, la manière dont les menus apparaissent. C’est difficile à décrire avec des mots mais la plupart d’entre nous le savent quand on le voit.

Le premier élément visuel recherché était de faire passer les noms des joueurs de «In» à «Out» et inversement lorsqu’ils étaient sélectionnés. Faire en sorte que le joueur se déplace instantanément d’une section à l’autre était simple mais n’était certainement pas similaire à une application. Une animation sur laquelle le nom du joueur a été cliqué soulignerait, espérons-le, le résultat de cette interaction – le joueur passant d'une catégorie à une autre.

Comme beaucoup de ces types d'interactions visuelles, leur simplicité apparente dissimule la complexité inhérente à sa réussite. Eh bien.

Il fallait quelques itérations pour que le mouvement soit correct, mais la logique de base était la suivante:

  • Une fois qu'un "joueur" est cliqué, capturez-le, géométriquement, sur la page;
  • Mesurez comment loin du haut de la surface, le joueur doit se déplacer s'il monte ('In') et quelle est la distance du fond, s'il descend ('Out');
  • Si vous montez, placez un espace égal à la hauteur de la rangée du joueur doit être laissée au fur et à mesure que le joueur monte et les joueurs situés au-dessus doivent s'effondrer au même rythme que le temps mis par le joueur pour atterrir dans l'espace laissé vacant par les joueurs "In" existants (le cas échéant) en descendant;
  • Si un joueur s'en va Out 'et en descendant, tout le reste doit se déplacer dans l'espace restant et le joueur doit se retrouver en dessous de tous les joueurs' Out 'actuels.

Ouf! C'était plus compliqué que je ne le pensais en anglais – Jamais l'esprit JavaScript!

Il y avait d'autres complexités à considérer et à essayer, telles que les vitesses de transition. Au début, il n’était pas évident de savoir si une vitesse de déplacement constante (par exemple, 20 px / 20 ms) ou une durée constante pour le mouvement (par exemple, 0,2 seconde) seraient plus esthétiques. Le premier était un peu plus compliqué car la vitesse devait être calculée «à la volée» en fonction de la distance parcourue par le joueur – une plus grande distance nécessitant une plus longue durée de transition.

Cependant, il s'est avéré qu'une durée de transition constante était pas simplement plus simple dans le code; cela produisit effectivement un effet plus favorable. La différence était subtile, mais ce sont les types de choix que vous ne pouvez déterminer qu’une fois que vous avez vu les deux options.

De temps à autre, en essayant de reproduire cet effet, un problème visuel attirerait l’attention mais il était impossible de la déconstruire réellement. temps. J'ai trouvé que le meilleur processus de débogage consistait à créer un enregistrement QuickTime de l'animation, puis à le parcourir image par image. Invariablement, cela révélait le problème plus rapidement que n'importe quel débogage basé sur du code.

En regardant le code maintenant, je peux comprendre que sur quelque chose au-delà de mon humble application, cette fonctionnalité pourrait presque certainement être écrite plus efficacement. Étant donné que l'application connaît le nombre de joueurs et la hauteur fixe des lattes, il devrait être tout à fait possible de faire tous les calculs de distance en JavaScript, sans lecture de DOM.

Ce n'est pas que ce qui a été livré n'a pas été livré. Cela ne fonctionne pas, c’est simplement que ce n’est pas le genre de solution de code que vous présenteriez sur Internet. Oh, attendez.

Il était beaucoup plus facile d’arriver à des interactions similaires à une application. Plutôt que de simplement faire défiler les menus avec quelque chose d'aussi simple que de basculer entre les propriétés d'affichage, vous gagnez beaucoup de temps en les exposant simplement avec un peu plus de finesse. C’était toujours simple, mais CSS faisait le gros du travail:

 .io-EventLoader {
  position: absolue;
  Top 100%;
  marge supérieure: 5px;
  z-index: 100;
  largeur: 100%;
  opacité: 0;
  transition: tous 0.2s;
  événements de pointeur: aucun;
  transformer: translateY (-10px);
  [data-evswitcher-showing="true"] & {
    opacité: 1;
    événements de pointeur: auto;
    transformer: aucun;
  }
}

Là-bas, lorsque l'attribut data-evswitcher-Showing = "true" était basculé sur un élément parent, le menu apparaissait en fondu, revenait dans sa position par défaut et les événements de pointeur étaient réactivés de manière à le menu pourrait recevoir des clics.

Méthodologie des feuilles de style ECSS

Vous remarquerez dans ce code antérieur que, du point de vue de la création, les remplacements CSS sont imbriqués dans un sélecteur parent. C’est ainsi que je préfère toujours écrire des feuilles de style UI; une seule source de vérité pour chaque sélecteur et toute substitution pour ce sélecteur encapsulé dans un seul jeu d'accolades. C'est un modèle qui nécessite l'utilisation d'un processeur CSS (Sass, PostCSS, LESS, Stylus, etc.), mais j'estime que c'est le seul moyen positif d'utiliser la fonctionnalité d'imbrication.

J'avais cimenté cette approche dans mon livre. , CSS durable et malgré la pléthore de méthodes plus complexes disponibles pour écrire des CSS pour les éléments d'interface, ECSS m'a bien servi, ainsi que les grandes équipes de développement avec lesquelles je travaille depuis que cette approche a été documentée pour la première fois en 2014 ! Cela s'est avéré tout aussi efficace en l'occurrence.

Partiellement le TypeScript

Même sans un processeur CSS ou un langage de sur-ensemble comme Sass, CSS avait la possibilité d'importer un ou plusieurs fichiers CSS dans un autre avec la directive import: [19659141] @import "autre-fichier.css";

Lorsque j'ai commencé avec JavaScript, j'ai été surpris par l'absence d'équivalent. Chaque fois que les fichiers de code ont une longueur supérieure à celle d'un écran, il est toujours avantageux de les séparer en petits morceaux.

Un autre avantage supplémentaire de TypeScript est qu'il offre un moyen extrêmement simple de fractionner le code en fichiers et de les importer lorsque

Cette fonctionnalité était antérieure aux modules JavaScript natifs et était très pratique. Lorsque TypeScript a été compilé, il a tout cousu en un seul fichier JavaScript. Cela signifiait qu'il était possible de diviser facilement le code de l'application en fichiers partiels gérables pour la création et l'importation, puis dans le fichier principal facilement. Le sommet des principaux inout.ts se présente comme suit:

 /// 
/// 
/// 
/// 
/// 
/// 
/// 
/// 
/// 
/// 
/// 

Cette simple tâche de ménage et d'organisation a énormément aidé.

Événements multiples

Au début, j'estimais que, du point de vue de la fonctionnalité, un seul événement, tel que "Tuesday Night Football", suffirait. Dans ce scénario, si vous avez chargé In / Out, vous avez simplement ajouté / supprimé ou déplacé des joueurs vers l'intérieur ou l'extérieur et c'est tout. Il n'y avait aucune notion d'événements multiples.

J'ai rapidement décidé que (même en optant pour un produit minimum viable), l'expérience serait plutôt limitée. Et si quelqu'un organisait deux matchs à des jours différents, avec une liste de joueurs différente? Sûrement In / Out pourrait / devrait répondre à ce besoin?
Il n'a pas fallu beaucoup de temps pour remodeler les données afin de rendre cela possible et de modifier les méthodes nécessaires pour charger dans un ensemble différent.

Au départ, l'ensemble de données par défaut ressemblait à quelque chose comme ceci:

 var defaultData = [
  { name: "Daz", paid: false, marked: false, team: "", in: false },
  { name: "Carl", paid: false, marked: false, team: "", in: false },
  { name: "Big Dave", paid: false, marked: false, team: "", in: false },
  { name: "Nick", paid: false, marked: false, team: "", in: false }
];

Un tableau contenant un objet pour chaque joueur.

Après avoir pris en compte plusieurs événements, il a été modifié pour ressembler à ceci:

 var defaultDataV2 = [
  {
    EventName: "Tuesday Night Footy",
    Selected: true,
    EventData: [
      { name: "Jack", marked: false, team: "", in: false },
      { name: "Carl", marked: false, team: "", in: false },
      { name: "Big Dave", marked: false, team: "", in: false },
      { name: "Nick", marked: false, team: "", in: false },
      { name: "Red Boots", marked: false, team: "", in: false },
      { name: "Gaz", marked: false, team: "", in: false },
      { name: "Angry Martin", marked: false, team: "", in: false }
    ]
  },
  {
    EventName: "Friday PM Bank Job",
    Sélectionné: faux,
    EventData: [
      { name: "Mr Pink", marked: false, team: "", in: false },
      { name: "Mr Blonde", marked: false, team: "", in: false },
      { name: "Mr White", marked: false, team: "", in: false },
      { name: "Mr Brown", marked: false, team: "", in: false }
    ]
  },
  {
    EventName: "WWII Ladies Baseball",
    Sélectionné: faux,
    EventData: [
      { name: "C Dottie Hinson", marked: false, team: "", in: false },
      { name: "P Kit Keller", marked: false, team: "", in: false },
      { name: "Mae Mordabito", marked: false, team: "", in: false }
    ]
  }
];

Les nouvelles données étaient un tableau avec un objet pour chaque événement. Ensuite, dans chaque événement, il y avait une propriété EventData qui était un tableau avec des objets joueur comme auparavant.

Il a fallu beaucoup plus de temps pour réexaminer la meilleure façon pour l'interface de gérer cette nouvelle fonctionnalité.

Depuis le début, le dessin a toujours été très stérile. Considérant que c’était aussi supposé être un exercice de design, je n’avais pas l’impression d’être assez courageux. Donc, un peu plus de flair visuel a été ajouté, à commencer par l'en-tête. This is what I mocked up in Sketch:

A mockup of the revised app design
Revised design mockup. (Large preview)

It wasn’t going to win awards but it was certainly more arresting than where it started.

Aesthetics aside, it wasn’t until somebody else pointed it out, that I appreciated the big plus icon in the header was very confusing. Most people thought it was a way to add another event. In reality, it switched to an ‘Add Player’ mode with a fancy transition that let you type in the name of the player in the same place the event name was currently.

This was another instance where fresh eyes were invaluable. It was also an important lesson in letting go. The honest truth was I had held on to the input mode transition in the header because I felt it was cool and clever. However, the fact was it was not serving the design and therefore the application as a whole.

This was changed in the live version. Instead, the header just deals with events — a more common scenario. Meanwhile, adding players is done from a sub-menu. This gives the app a much more understandable hierarchy.

The other lesson learned here was that whenever possible, it’s hugely beneficial to get candid feedback from peers. If they are good and honest people, they won’t let you give yourself a pass!

Summary: My Code Stinks

Right. So far, so normal tech-adventure retrospective piece; these things are ten a penny on Medium! The formula goes something like this: the dev details how they smashed down all obstacles to release a finely tuned piece of software into the Internets and then pick up an interview at Google or got acqui-hired somewhere. However, the truth of the matter is that I was a first-timer at this app-building malarkey so the code ultimately shipped as the ‘finished’ application stunk to high heaven!

For example, the Observer pattern implementation used worked very well. I was organized and methodical at the outset but that approach ‘went south’ as I became more desperate to finish things off. Like a serial dieter, old familiar habits crept back in and the code quality subsequently dropped.

Looking now at the code shipped, it is a less than ideal hodge-bodge of clean observer pattern and bog-standard event listeners calling functions. In the main inout.ts file there are over 20 querySelector method calls; hardly a poster child for modern application development!

I was pretty sore about this at the time, especially as at the outset I was aware this was a trap I didn’t want to fall into. However, in the months that have since passed, I’ve become more philosophical about it.

The final post in this series reflects on finding the balance between silvery-towered code idealism and getting things shipped. It also covers the most important lessons learned during this process and my future aspirations for application development.

Smashing Editorial(dm, yk, il, ra)






Source link