Lawrence  Lesch

Lawrence Lesch

1675490460

Ng-devui: Angular UI Component Library based on DevUI Design

DevUI for Angular

The DevUI Design Design system contains a combination of DevUI rules, Design languages, and best practices. DevUI Design allows developers to focus more on application logic, while designers focus on user experience, interactions, and processes.

Features

  • Enterprise components, supporting design specifications, font icon library
  • Out of the box

To see more in devui.design.

Angular Support

Now supports Angular ^14.0.0

Getting Started

Create a new project

ng new New-Project

Installation:

$ cd New-Project
$ npm i ng-devui
# font icon library
# $ npm i @devui-design/icons

Usage:

import { BrowserModule } from '@angular/platform-browser';
// need for animations
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgModule } from '@angular/core';
import { DevUIModule } from 'ng-devui';
import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    DevUIModule
  ],
  bootstrap: [ AppComponent ],
})
export class AppModule { }

Import devui style into angular.json file:

{
  "styles": [
    ...
    "node_modules/ng-devui/devui.min.css"
  ]
}

Debugging

ng serve --open

Contribution

Please feel free to contribute code or discuss your idea!

Please make sure you read the contributing guide before making a pull request.

We appreciate all contributors who helped us build DevUI.

Support

Modern browsers and Internet Explorer 11+.

IE / Edge
IE / Edge
Firefox
Firefox
Chrome
Chrome
Safari
Safari
Opera
Opera
Edgelast 2 versionslast 2 versionslast 2 versionslast 2 versions

Partner project

Who use it

DevCloud Logo

Download Details:

Author: DevCloudFE
Source Code: https://github.com/DevCloudFE/ng-devui 
License: MIT license

#typescript #enterprise #angular #frontend 

Ng-devui: Angular UI Component Library based on DevUI Design
Lawrence  Lesch

Lawrence Lesch

1675482540

Blog: Life is A Moment

Blog

This is my personal blog created with create-react-doc where to write and record some thought daily. It's very thankful to point any wrong place there.

Data Structure

This section we will start tralvel in the world of data strcture together! There is no doubt the travel is so interestring that you'll enjoy it too! Before your travel of data structure, I want to tell two points it's valueble:

  1. Interest and Insist.
  2. Thought is more important than action.

Travel Of LeetCode

algorithm

Design Pattern

React Series

This section will introduce the world of React progressively. It'll tell the principle of React, how to design React Component gracefully and how to build a simple React from scratch.

Deep Into React

Component Design

More

React Stack

Build React from scratch

Modern Testing

TypeScript

FE Cloud

This section will talk about colorful frontend world.

JavaScript

CSS

more

Node.js

FE CLOUD

Project Framework

RaspberryPi

Just do it for fun! You will unlock server, linux, nginx skills from this Raspberry Pi travel.

Thanks for the pi project specially.

  1. 树莓派简介&烧录系统
  2. 内网穿透
  3. 基于树莓派搭建家庭服务器
  4. 给树莓派安装 Docker 环境
  5. 给树莓派部署 RSSHub
  6. HTTPS 协议配置
  7. 基于树莓派部署 code-server
  8. 基于树莓派部署 filebrowser

Sponsor

  • If you like this project, welcome watch or star✨.
  • Or you can buy me a ☕️.

Thanks

Thank my dear rabbit🐰 for her support.

Download Details:

Author: MuYunyun
Source Code: https://github.com/MuYunyun/blog 

#typescript #javascript #react #css #frontend 

Blog: Life is A Moment
Lawrence  Lesch

Lawrence Lesch

1673707320

Hop V1 Monorepo

Hop v1 Monorepo

The Hop Protocol v1 monorepo

Quickstart

Install dependencies & link packages

yarn install
yarn run bootstrap

Run frontend app in development

cd packages/frontend
REACT_APP_NETWORK=mainnet yarn run dev

Visit http://localhost:3000/

Contributing

See ./CONTRIBUTING.md

Packages

LibraryCurrent VersionDescription
@hop-protocol/corenpm versionMetadata and config
@hop-protocol/sdknpm versionTypeScript Hop SDK
ApplicationCurrent VersionDescription
@hop-protocol/frontendnpm versionReact Frontend UI
@hop-protocol/hop-nodenpm versionTypeScript Hop Node

Download Details:

Author: hop-protocol
Source Code: https://github.com/hop-protocol/hop 
License: MIT license

#typescript #javascript #react #npm #sdk #frontend #monorepo 

Hop V1 Monorepo
Hermann  Frami

Hermann Frami

1673489520

Hooks: "Zero" Api / Type Safe / Fullstack Kit / Powerful Backend

Functional Fullstack Framework

"Zero" Api & Type Safe & Fullstack Kit & Powerful Backend

At Alibaba, 2800+ full-stack applications are developed based on Midway Hooks (2022.01)

English | 简体中文

✨ Features

  • ☁️ Maximize productivity and developer experience, support fullstack development & API service
  • ⚡️ Fullstack kit that supports React/Vue/Svelte... and more frameworks
  • 🌈 "Zero" Api data layer, import functions from the backend to call the API directly, without the ajax glue layer
  • ⛑️ Type safe, use the identical type definition from frontend to backend, detect errors in advance
  • 🌍 Functional programming, using Hooks for frontend and backend
  • ⚙️ Support for Webpack / Vite based projects
  • ✈️ Deploy to Server or Serverless
  • 🛡 Based on Midway, a powerful Node.js framework that supports enterprise-level application development

🔨 Preview

Backend(Midway Hooks)Frontend(React)
// src/api/index.ts
import {
  Api,
  Get,
  Post,
  Validate,
  Query,
  useContext,
} from '@midwayjs/hooks';
import { z } from 'zod';
import db from './database';

export const getArticles = Api(
  Get(),
  Query<{ page: string; per_page: string }>(),
  async () => {
    const ctx = useContext();

    const articles = await db.articles.find({
      page: ctx.query.page,
      per_page: ctx.query.per_page,
    });

    return articles;
  }
);

const ArticleSchema = z.object({
  title: z.string().min(3).max(16),
  content: z.string().min(1),
});

export const createArticle = Api(
  Post(),
  Validate(ArticleSchema),
  async (article: z.infer<typeof ArticleSchema>) => {
    const newArticle = await db.articles.create(article);
    return {
      id: newArticle.id,
    };
  }
);
// src/pages/articles.tsx
import { getArticles } from '../api';
import { useRequest } from 'ahooks';
import ArticleList from './components/ArticleList';

export default () => {
  const { data } = useRequest(() =>
    getArticles({
      query: {
        page: '1',
        per_page: '10',
      },
    })
  );

  return <ArticleList articles={data} />;
};

// src/pages/new.tsx
import { createArticle } from '../api';
import Editor from './components/Editor';
import { useState } from 'react';

export default () => {
  const [loading, setLoading] = useState(false);

  const handleSubmit = async (article) => {
    setLoading(true);
    const { id } = await createArticle(article);
    setLoading(false);
    location.href = `/articles/${id}`;
  };

  return <Editor loading={loading} onSubmit={handleSubmit} />;
};

🧩 Templates

Midway Hooks currently provide the following templates:

You can create applications quickly with templates:

npx degit https://github.com/midwayjs/hooks/examples/<name>

For example, create a fullstack application with react:

npx degit https://github.com/midwayjs/hooks/examples/react

Contribute

  1. Fork it!
  2. Create your feature branch: git checkout -b my-new-feature
  3. Commit your changes: git commit -am 'Add some feature'
  4. Push to the branch: git push origin my-new-feature
  5. Submit a pull request :D

We use pnpm to manage the project.

  • install dependencies
$ pnpm install
  • build
$ pnpm build
  • watch
$ pnpm watch
  • test
$ pnpm test

Download Details:

Author: Midwayjs
Source Code: https://github.com/midwayjs/hooks 
License: MIT license

#serverless #react #hook #vue #frontend 

Hooks: "Zero" Api / Type Safe / Fullstack Kit / Powerful Backend
Nigel  Uys

Nigel Uys

1672345140

Generate Host Overview From ansible Fact Gathering Output

Ansible Configuration Management Database

About

Ansible-cmdb takes the output of Ansible's fact gathering and converts it into a static HTML overview page (and other things) containing system configuration information.

It supports multiple types of output (html, csv, sql, etc) and extending information gathered by Ansible with custom data. For each host it also shows the groups, host variables, custom variables and machine-local facts.

HTML example output.

Features

(Not all features are supported by all templates)

  • Multiple formats / templates:
    • Fancy HTML (--template html_fancy), as seen in the screenshots above.
    • Fancy HTML Split (--template html_fancy_split), with each host's details in a separate file (for large number of hosts).
    • CSV (--template csv), the trustworthy and flexible comma-separated format.
    • JSON (--template json), a dump of all facts in JSON format.
    • Markdown (--template markdown), useful for copy-pasting into Wiki's and such.
    • Markdown Split (--template markdown_split), with each host's details in a seperate file (for large number of hosts).
    • SQL (--template sql), for importing host facts into a (My)SQL database.
    • Plain Text table (--template txt_table), for the console gurus.
    • and of course, any custom template you're willing to make.
  • Host overview and detailed host information.
  • Host and group variables.
  • Gathered host facts and manual custom facts.
  • Adding and extending facts of existing hosts and manually adding entirely new hosts.
  • Custom columns

Getting started

Links to the full documentation can be found below, but here's a rough indication of how Ansible-cmdb works to give you an idea:

Install Ansible-cmdb from source, a release package or through pip: pip install ansible-cmdb.

Fetch your host's facts through ansible:

 $ mkdir out
 $ ansible -m setup --tree out/ all

Generate the CMDB HTML with Ansible-cmdb:

 $ ansible-cmdb out/ > overview.html

Open overview.html in your browser.

That's it! Please do read the full documentation on usage, as there are some caveats to how you can use the generated HTML.

Documentation

All documentation can be viewed at readthedocs.io.

Download Details:

Author: fboender
Source Code: https://github.com/fboender/ansible-cmdb 
License: GPL-3.0 license

#ansible #frontend #inventory 

Generate Host Overview From ansible Fact Gathering Output

Jeevi Academy

1672143538

7 Best Chrome Extensions for UI/UX Designers | Jeevisoft |

#chromeextension #chrome #extension #ux #uxbook #contentmarketing #design #principles #gooddesign ##ui #userinterface #services #academy #userflow #userjourney #devops #automation #designer #gestalt #ux #designer #skills #interviewquestions #aws #docker#interviewquestions #interview #aws #scenario #cheatsheet #solutionarchitect #azure #ansibleinterview #questions #Devops #interview #guideline #Terraform #cheatsheet #interview #steps #localbusiness #business #videocreating #containor #devops #interview #opportunities #findabestway #certification #top #digitalmarketing #seo #mail #ppc #socialmediamarketing #shorts #technology #frontend #developer #youtube#programming #learn #tech #technology #trending #beginners #worldnews #creative #knowledge #academy #shorts #youtubeshorts #youtube #aws #docker #ui #website #webdesign #development #developer 

7 Best Chrome Extensions for UI/UX Designers | Jeevisoft |
Sheldon  Grant

Sheldon Grant

1672054380

Learn the Strengths and Benefits of Micro Frontends

Micro-frontend architectures decompose a front-end app into individual, semi-independent “microapps” working loosely together. This can help make large projects more manageable, e.g. when transitioning from legacy codebases.

Micro-frontend architecture is a design approach in which a front-end app is decomposed into individual, semi-independent “microapps” working loosely together. The micro-frontend concept is vaguely inspired by, and named after, microservices.

The benefits of the micro-frontend pattern include:

  1. Micro-frontend architectures may be simpler, and thus easier to reason about and manage.
  2. Independent development teams can collaborate on a front-end app more easily.
  3. They can provide a means for migrating from an “old” app by having a “new” app running side by side with it.

Although micro frontends have been getting a lot of attention lately, as of yet there is no single dominant implementation and no clear “best” micro-frontend framework. In fact, there is a variety of approaches depending on the objectives and requirements. See the bibliography for some of the better-known implementations.

In this article, we will skip over much of the theory of micro frontends. Here’s what we won’t cover:

  • “Slicing” an app into microapps
  • Deployment issues, including how micro frontends fit into a CI/CD model
  • Testing
  • Whether microapps should be in one-to-one alignment with microservices on the backend
  • Criticisms of the micro-frontend concept
  • The difference between micro frontends and a plain old component architecture

Instead, we will present a micro-frontend tutorial focusing on a concrete implementation, highlighting the important issues in micro-frontend architecture and their possible solutions.

Our implementation is called Yumcha. The literal meaning of “yum cha” in Cantonese is “drinking tea,” but its everyday meaning is “going out for dim sum.” The idea here is that the individual microapps within a macroapp (as we shall call the composed, top-level app) are analogous to the various baskets of bite-size portions brought out at a dim sum lunch.

 

Overview illustration of an example micro frontend–based app as described above.

 

We will sometimes refer to Yumcha as a “micro-frontend framework.” In today’s world, the term “framework” is usually used to refer to Angular, React, Vue.js, or other similar superstructures for web apps. We are not talking about a framework in that sense at all. We call Yumcha a framework just for the sake of convenience: It is actually more of a set of tools and a few thin layers for building micro frontend–based apps.

Micro-frontend Tutorial First Steps: Markup for a Composed App

Let’s dive in by thinking about how we might define a macroapp and the microapps that make it up. Markup has always been at the heart of the web. Our macroapp will therefore be specified by nothing more complicated than this markup:

<html>
  <head>
    <script src="/yumcha.js"></script>
  </head>
  <body>
    <h1>Hello, micro-frontend app.</h1>

    <!-- HERE ARE THE MICROAPPS! -->
    <yumcha-portal name="microapp1" src="https://microapp1.example.com"></yumcha-portal>
    <yumcha-portal name="microapp2" src="https://microapp2.example.com"></yumcha-portal>

  </body>
</html>

Defining our macroapp using markup gives us full access to the power of HTML and CSS to lay out and manage our microapps. For example, one microapp could sit on top of another one, or to its side, or be up in the corner of the page, or be in one pane of an accordion, or remain hidden until something happens, or stay in the background permanently.

We have named the custom element used for microapps <yumcha-portal> because “portal” is a promising term for microapps used in the portal proposal, an early attempt at defining a standard HTML element for use in micro frontends.

Implementing the <yumcha-portal> Custom Element

How should we implement <yumcha-portal>? Since it’s a custom element, as a web component, of course! We can choose from among a number of strong contenders for writing and compiling micro-frontend web components; here we will use LitElement, the latest iteration of the Polymer Project. LitElement supports TypeScript-based syntactic sugar, which handles most of the custom element boilerplate for us. To make <yumcha-portal> available to our page, we have to include the relevant code as a <script>, as we did above.

But what does <yumcha-portal> actually do? A first approximation would be for it to just create an iframe with the specified source:

  render() {
    return html`<iframe src=${this.src}></iframe>`;
  }

…where render is the standard LitElement rendering hook, using its html tagged template literal. This minimal functionality might be almost enough for some trivial use cases.

Embedding Microapps in iframes

iframes are the HTML element that everyone loves to hate, but actually they provide extremely useful, rock-solid sandboxing behavior. However, there is still a long laundry list of issues to be aware of when using iframes, with potential impact on the behavior and functionality of our app:

  • First, iframes have well-known quirks in terms of how they size and lay themselves out.
  • CSS will of course be completely isolated to the iframe, for better or worse.
  • The browser’s “back” button will work reasonably well, although the current navigational status of the iframe will not be reflected in the page’s URL, so we could neither cut and paste URLs to get to the same state of the composed app, nor deep link to them.
  • Communication with the iframe from the outside, depending on our CORS setup, might need to go through the postMessage protocol.
  • Arrangements will have to be made for authentication across iframe boundaries.
  • Some screen readers may stumble at the iframe boundary or need the iframe to have a title they can announce to the user.

Some of these issues can be avoided or mitigated by not using iframes, an alternative we discuss later in the article.

On the plus side, the iframe will have its own, independent Content-Security-Policy (CSP). Also, if the microapp that the iframe points to uses a service worker or implements server-side rendering, everything will work as expected. We can also specify various sandboxing options to the iframe to limit its capabilities, such as being able to navigate to the top frame.

Some browsers have shipped or are planning to ship a loading=lazy attribute for iframes, which defers loading below-the-fold iframes until the user scrolls near them, but this doesn’t provide the fine-grained control of lazy loading that we want.

The real problem with iframes is that the content of the iframe will take multiple network requests to retrieve. The top-level index.html is received, its scripts are loaded, and its HTML is parsed—but then the browser must initiate another request for the iframe’s HTML, wait to receive it, parse and load its scripts, and render the iframe’s contents. In many cases, the iframe’s JavaScript would then still have to spin up, make its own API calls, and show meaningful data only after those API calls return and the data is processed for viewing.

This will likely result in undesirable delays and rendering artifacts, especially when several microapps are involved. If the iframe’s app implements SSR, that will help but still not avoid the necessity for additional round trips.

So one of the key challenges we face in designing our portal implementation is how to deal with this round-trip issue. Our goal is that a single network request should bring down the entire page with all of its microapps, including whatever content each of them is able to prepopulate. The solution to this problem lies in the Yumcha server.

The Yumcha Server

A key element of the micro-frontend solution presented here is to set up a dedicated server to handle microapp composition. This server proxies requests to the servers where each microapp is hosted. Granted, it will require some effort to set up and manage this server. Some micro-frontend approaches (e.g., single-spa) attempt to dispense with the need for such special server setups, in the name of ease of deployment and configuration.

However, the cost of setting up this reverse proxy is more than offset by the benefits we gain; in fact, there are important behaviors of micro frontend–based apps that we simply can’t achieve without it. There are many commercial and free alternatives to setting up such a reverse proxy.

The reverse proxy, in addition to routing microapp requests to the appropriate server, also routes macroapp requests to a macroapp server. That server dishes up the HTML for the composed app in a special way. Upon receiving a request for index.html from the browser by way of the proxy server at a URL such as http://macroapp.example.com, it retrieves the index.html and then subjects it to a simple but crucial transformation before returning it.

Specifically, the HTML is parsed for <yumcha-portal> tags, which can be done easily with one of the competent HTML parsers available in the Node.js ecosystem. Using the src attribute to <yumcha-portal>, the server running the microapp is contacted and its index.html is retrieved—including server-side rendered content, if any. The result is inserted into the HTML response as a <script> or <template> tag, so as not to be executed by the browser.

 

Illustration of Yumcha's server architecture. The browser communicates with the reverse proxy, which in turn communicates with the macroapp and each of the microapps. The macroapp step transforms and prepopulates the app's main index.html file.

 

The advantages of this setup include, first and foremost, that on the very first request for the index.html for the composed page, the server can retrieve the individual pages from the individual microapp servers in their entirety—including SSR-rendered content, if any—and deliver a single, complete page to the browser, including the content which can be used to populate the iframe with no additional server round trips (using the underused srcdoc attribute). The proxy server also ensures that any details of where the microapps are being served from are cloaked from prying eyes. Finally, it simplifies CORS issues, since application requests are all being made to the same origin.

Back at the client, the <yumcha-portal> tag gets instantiated and finds the content where it was placed in the response document by the server, and at the appropriate time renders the iframe and assigns the content to its srcdoc attribute. If we are not using iframes (see below), then the content corresponding to that <yumcha-portal> tag is inserted either into the custom element’s shadow DOM, if we are using that, or directly inline in the document.

At this point, we already have a partially functioning micro frontend–based app.

This is just the tip of the iceberg in terms of interesting functionality for the Yumcha server. For example, we would want to add features to control how HTTP error responses from the microapp servers are handled, or how to deal with microapps that respond very slowly—we don’t want to wait forever to serve the page if one microapp is not responding! These and other topics we will leave for another post.

The Yumcha macroapp index.html transformation logic could easily be implemented in a serverless lambda-function fashion, or as middleware for server frameworks such as Express or Koa.

Stub-based Microapp Control

Moving back to the client side, there is another aspect to how we implement microapps that is important for efficiency, lazy loading, and jank-free rendering. We could generate the iframe tag for each microapp, either with a src attribute—which makes another network request—or with the srcdoc attribute filled in with the content populated for us by the server. But in both those cases, the code in that iframe will kick off immediately, including loading all its script and link tags, bootstrapping, and any initial API calls and related data processing—even if the user never even accesses the microapp in question.

Our solution to this problem is to initially represent microapps on the page as tiny inactivated stubs, which can then be activated. Activation can be driven either by the microapp’s region coming into view, using the underused IntersectionObserver API, or more commonly by pre-notifications sent from the outside. Of course, we can also specify that the microapp be activated immediately.

In any case, when and only when the microapp is activated is the iframe actually rendered and its code loaded and executed. In terms of our implementation using LitElement, and assuming the activation status is represented by an activated instance variable, we would have something like:

render() {
  if (!this.activated) return html`{this.placeholder}`;
  else return html`
    <iframe srcdoc="${this.content}" @load="${this.markLoaded}"></iframe>`;
}

Inter-microapp Communication

Although the microapps making up a macroapp are by definition loosely coupled, they still need to be able to communicate with each other. For example, a navigation microapp would need to send out a notification that some other microapp just selected by the user should be activated, and the app to be activated needs to receive such notifications.

In line with our minimalist mindset, we want to avoid introducing a lot of message-passing machinery. Instead, in the spirit of web components, we will use DOM events. We provide a trivial broadcast API that pre-notifies all stubs of an impending event, waits for any which have requested to be activated for that event type to be activated, and then dispatches the event against the document, on which any microapp can listen for it. Given that all our iframes are same-origin, we can reach out from the iframe to the page and vice-versa to find elements against which to fire events.

Routing

In this day and age, we have all come to expect the URL bar in SPAs to represent the application’s view state, so we can cut, paste, mail, text, and link to it to jump directly to a page within the app. In a micro-frontend app, however, the application state is actually a combination of states, one for each microapp. How are we to represent and control this?

The solution is to encode each microapp’s state into a single composite URL and using a small macroapp router which knows how to put that composite URL together and pick it apart. Unfortunately, this requires Yumcha-specific logic in each microapp: to receive messages from the macroapp router and update the microapp’s state, and conversely to advise the macroapp router of changes in that state so the composite URL can be updated. For example, one could imagine a YumchaLocationStrategy for Angular, or a <YumchaRouter> element for React.

 

A composite URL representing a macroapp state. Its query string decodes to two separate (doubly encoded) query strings that are then to be passed to the microapps whose ids are specified as their keys.

 

The Non-iframe Case

As mentioned above, hosting microapps in iframes does have some downsides. There are two alternatives: include them directly inline in the page’s HTML, or place them in the shadow DOM. Both alternatives mirror the pros and cons of iframes somewhat, but sometimes in different ways.

For example, individual microapp CSP policies would have to be somehow merged. Assistive technologies such as screen readers should work better than with iframes, assuming they support the shadow DOM (which not all do yet). It should be straightforward to arrange to register a microapp’s service workers using the service worker concept of “scope,” although the app would have to ensure that its service worker is registered under the app’s name, not "/". None of the layout issues associated with iframe apply to the inlined or shadow DOM methods.

However, applications built using frameworks such as Angular and React are likely to be unhappy living inline or in the shadow DOM. For those, we are likely going to want to use iframes.

The inline and shadow DOM methods differ when it comes to CSS. CSS will be cleanly encapsulated in the shadow DOM. If for some reason we did want to share outside CSS with the shadow DOM, we would have to use constructable stylesheets or something similar. With inlined microapps, all CSS would be shared throughout the page.


In the end, implementing the logic for inline and shadow DOM microapps in <yumcha-portal> is straightforward. We retrieve the content for a given microapp from where it was inserted into the page by the server logic as an HTML <template> element, clone it, then append it to what LitElement calls renderRoot, which is normally the element’s shadow DOM, but can also be set to the element itself (this) for the inline (non–shadow DOM) case.

But wait! The content served up by the microapp server is an entire HTML page. We cannot insert the HTML page for the microapp, complete with html, head, and body tags, into the middle of the one for the macroapp, can we?

We solve this problem by taking advantage of a quirk of the template tag in which the microapp content retrieved from the microapp server is wrapped. It turns out that when modern browsers encounter a template tag, although they do not “execute” it, they do parse it, and in doing so they remove invalid content such as the <html>, <head>, and <body> tags, while preserving their inner content. So the <script> and <link> tags in the <head>, as well as the content of the <body>, are preserved. This is precisely what we want for purposes of inserting microapp content into our page.

Micro-frontend Architecture: The Devil Is in the Details

Micro frontends will take root in the webapp ecosystem if (a) they turn out to be a better architectural approach, and (b) we can figure out how to implement them in ways which satisfy the myriad practical requirements of today’s web.

In terms of the first question, no one claims that micro frontends are the right architecture for all use cases. In particular, there would be little reason for greenfield development by a single team to adopt micro frontends. I’ll leave the question of what types of apps in what types of contexts could benefit most from a micro-frontend pattern to other commentators.

In terms of implementation and feasibility, we have seen there are manifold details to be concerned with, including several not even mentioned in this article—notably authentication and security, code duplication, and SEO. Nonetheless, I hope that this article lays out a basic implementation approach for micro frontends which, with further refinement, can stand up to real-world requirements.

Original article source at: https://www.toptal.com/

#micro #frontend #node 

Learn the Strengths and Benefits of Micro Frontends

Top 15 Useful vs Code Extensions for Front-End Development

Intro to 15 useful VS Code extensions for front-end development

Since some time Visual Studio Code becomes very popular IDE for Javascript developers. I started using it two years ago, and in my opinion, it’s a fantastic code editor. It allows me to customize it just as I want. VS Code also has a build-in git integration and terminal, so you don’t have to jump from one window to another.There are tones of plugins and even themes, where everyone can find something that he or she needs.

The proper setup of VSC can improve our productivity; also, there are some plugins which will help developers to create better, clean code. Because there are so many plugins which can be used for Visual Studio Code, it’s easy to get lost and forget about some useful extensions. This is the reason I would like to share with you ma favorite extensions for VSC for front-end development.

Let’s start!

1. HTML snippets (Visual Studio Code HTML Snippets)

This is an essential extension for every front-end developer. You don’t have to waste more time for writing every HTML tag manually; it’s enough to put only tag name like div and press enter. Or you can even add a few tags which you would like to be nested like ul>li>a and press enter. What’s important, this extension has all HTML5 snippets.

2. JavaScript (ES6) code snippets

In the previous point, you could notice that snippets are handy and can help to prevent lots of spelling bugs and can make coding much faster. Each front-end developer works mostly with JavaScript. To speed up my Javascript coding, I use Javascript code snippets. It also supports .ts, .tsx and .jsx files. Here it works similar, for example, to create export default class _ClassName_ {} code it’s enough to put ecl and press tab. Easy, right? To find out more code shortcuts take a look at the extension documentation.

3. CSS Peek

As we have something for HTML and something for Javascript, something for CSS would be useful as well for front-end development. CSS Peek is an extension supporting .html and .js files. It helps to quickly find and check styles applied for selected class or id. It’s beneficial for developers who don’t like to switch between different files or split the screen.

4. Angular/React/Vue

If we are in the code snippets area, it would also be good to mention about extensions for the selected framework. A lot depends on which framework you are using. – For Angular, there is an extension called Angular Snippets (Version 8) because currently, we have Angular 8, but Angular has a new release for every version of the framework. It provides us code snippets for Typescript and HTML.

– For React.js, there is an excellent extension ES7 React/Redux/GraphQL/React-Native snippets. It provides code snippets for React and Redux using ES7, and it works in a similar way to JavaScript snippets with tab button.

– For Vue.js development, there is a great extension called Vetur. It has almost 20mln downloads, and it brings a lot of functionalities like code snippets, linking and errors checking, formatting, debugging or highlighting the syntax. It looks very impressive.

5. ESLint

If you want to create a friendly, readable, clean code, it’s a great idea to install ESLint into your VS Code. This will help you to stick to standard practices like indentation, for example.

6. Prettier – Code formatter

If we talk about pretty code, it’s worth to install the Prettier extension in your code editor. Prettier is excellent, especially if you are working on the project with other developers. It removes original styling and puts on the consistent code style. Thanks to consistent formatting the code is much more readable.

7. GitLens

As I mentioned at the beginning, the Visual Studio Code has a git integration. We can make it even better installing GitLens extension. It allows checking who created each line of code when it was created, and it allows us to go to commit details quickly. It’s beneficial in case of working in a team of developers to understand the code history easily.

8. Auto import

Auto import is a great extension which automatically imports files; you don’t have to do it manually anymore. It’s excellent primarily if you work on a component-based project. It’s enough to put the component name, and the plugin will import it.

9. Path autocomplete

If we are in the area of imports, there is another great extension which will help you if you need to import something manually or place a link to a different file. Path autocomplete extension provides paths completion. While you start typing your path probably with ./ you will notice a dropdown with folders to select. It’s crazy helpful because you don’t have to dig in your files and search the correct path.

10. Final newline

Sometimes you have to remember about adding a new line to your document, and final-newline comes with a helping hand here. Every time you will save the file, it will insert a new line at the end of the document.

11. Bracket pair colorizer

Bracket pair colorizer helps us to find the closing bracket of the current block of code. It sometimes happens that at the end of your file or function, you have more than one or two closing brackets, and it’s not so easy to find the correct one then. If you are using the Bracket Pair Colorizer plugin, every starting and closing bracket has the same color for one block of code. So if your opening tag is blue, your closing tag will be blue as well.

12. Indenticator

Indeticator is an extension for VS Code which visually highlights current intend depth. It allows distinguishing easily different levels of a different block of codes. Depth is marked with small dots and lines.

13. Debugger for Chrome

And at the end an excellent plugin for debugging. Wouldn’t it be perfect if we could debug in the console like in the Chrome browser? It’s possible with Debugger for Chrome plugin; it supports setting breakpoints, stepping, debugging evil scripts, and more. If you are tired of switching from files in the code editor to debugging console in the browser, it’s a great plugin for you.

14. Import cost

That’s a great extension that will let you control your js bundle size, and it’s especially helpful when you plan to split your code into small chunks. Some of the libraries you import can weigh a lot, sometimes, one library can be heavier than the whole framework, and you need to be aware of that. Import cost extension will show you the library’s exact size, full-size, and the gzipped one.

15. Sort imports

Clean code is one of the most important factors that you should care about when building software. And how you manage imports will have a huge impact on your code quality.

Luckily we can use a very helpful extension named Sort Imports, which manages your imports simply and correctly. That extension will sort all of your imports in order and put all of them on the top of the file. What’s better, that extension will group all of your imports as well.

Conclusion of 15 useful VS Code extensions for front-end development

In the above article, I shared with you my favorite extensions for Visual Studio Code. I hope you it’s a great tip on how to set your code editor and improve your performance of your development. Also, if you are a beginner, it may help you to focus on learning programming then on looking for a closing tag, or closing bracket. When your VS Code is configured you are ready to practice Javascript.

Have a nice coding!

Thank you for reading!

Related readings 📖

What is the difference between Shadow DOM and Virtual DOM?

How to increase your website speed and score 100 on Google PageSpeed Insights

11 React.js projects for beginners, that will help you to build an amazing portfolio and get hired

And if you prefer video, here is the youtube version:

Original article source at: https://www.blog.duomly.com/

#frontend #vscode #javascript 

Top 15 Useful vs Code Extensions for Front-End Development

How to introduce Cypress Into Your Test-driven Development Workflow

Cypress is a modern web automation test framework designed to simplify browser testing. While it's best known as a Selenium replacement, it's much more than just an end-to-end test automation tool. Cypress is a developer tool made to be used proactively by developers rather than a non-technical QA team focused on after-the-fact testing.

This post looks at how to introduce Cypress into your test-driven development workflow as you build out an app with Flask and React.

Sample Application

We'll be building a basic todo application with Flask and React based on the following user stories:

  1. As a user I can see all the todos in the list
  2. As a user I can add new todos to the list
  3. As a user I can toggle the completed state of each todo

Let's assume that our focus is on the client-side only. In other words, we need to create a React todo app that interacts with a Flask back-end via AJAX to get and add todos. If you'd like to code along, clone down the flask-react-cypress repo, and then check out the v1 tag to the master branch:

$ git clone https://github.com/testdrivenio/flask-react-cypress --branch v1 --single-branch
$ cd flask-react-cypress
$ git checkout tags/v1 -b master

Workflow

This workflow focuses on integration testing, where development and testing happen simultaneously using a TDD-like approach:

  1. Convert user stories, requirements, and acceptance criteria into partial test specs
  2. Add fixtures and stub out network calls
  3. Run the Cypress GUI and keep it open next to your code editor
  4. Use .only to focus, and iterate, on a single test
  5. Ensure the test fails
  6. Code until that test passes (red, green, refactor)
  7. Repeat the previous three steps until all tests are green
  8. Optional: Convert the integration tests to end-to-end tests by removing the network stubs

Want to see this workflow in action? Check out the My Cypress Workflow video.

Initial Setup

Steps:

  1. Convert user stories, requirements, and acceptance criteria into partial test specs
  2. Add fixtures and stub out network calls
  3. Run the Cypress GUI and keep it open next to your code editor

Create partial test specs

Add the partial test specs to a new file called client/cypress/integration/todos.spec.js:

describe('todo app', () => {
  beforeEach(() => {
    cy.visit('/');
    cy.get('h1').contains('Todo List');
  });

  it('should display the todo list', () => {});

  it('should add a new todo to the list', () => {});

  it('should toggle a todo correctly', () => {});
});

Then, add the baseUrl along with the serverUrl--the URL for the server-side Flask app--under an env key so it will be accessible as an environment variable to client/cypress.json:

{
  "baseUrl": "http://localhost:3000",
  "env": {
    "serverUrl": "http://localhost:5009"
  }
}

Add fixtures

Before we stub out the requests, let's add the fixture files, which will be used to simulate the data returned from the following server-side endpoints:

  1. GET - /todos - get all todos
  2. POST - /todos - add a todo

client/cypress/fixtures/todos/all_before.json:

{
  "data": {
    "todos": [
      {
        "complete": false,
        "created_date": "Mon, 28 Jan 2019 15:32:28 GMT",
        "id": 1,
        "name": "go for a walk"
      },
      {
        "complete": false,
        "created_date": "Mon, 28 Jan 2019 15:32:28 GMT",
        "id": 2,
        "name": "go for a short run"
      },
      {
        "complete": true,
        "created_date": "Mon, 28 Jan 2019 15:32:28 GMT",
        "id": 3,
        "name": "clean the stereo"
      }
    ]
  },
  "status": "success"
}

client/cypress/fixtures/todos/add.json:

{
  "name": "make coffee"
}

This final fixture is for the getting of all todos after a new todo has been added.

client/cypress/fixtures/todos/all_after.json:

{
  "data": {
    "todos": [
      {
        "complete": false,
        "created_date": "Mon, 28 Jan 2019 15:32:28 GMT",
        "id": 1,
        "name": "go for a walk"
      },
      {
        "complete": false,
        "created_date": "Mon, 28 Jan 2019 15:32:28 GMT",
        "id": 2,
        "name": "go for a short run"
      },
      {
        "complete": true,
        "created_date": "Mon, 28 Jan 2019 15:32:28 GMT",
        "id": 3,
        "name": "clean the stereo"
      },
      {
        "complete": false,
        "created_date": "Mon, 28 Jan 2019 17:22:35 GMT",
        "id": 4,
        "name": "drink a beverage"
      }
    ]
  },
  "status": "success"
}

Then, add the fixtures to the beforeEach in the test spec:

beforeEach(() => {
  // fixtures
  cy.fixture('todos/all_before.json').as('todosJSON');
  cy.fixture('todos/add.json').as('addTodoJSON');
  cy.fixture('todos/all_after.json').as('updatedJSON');

  cy.visit('/');
  cy.get('h1').contains('Todo List');
});

Stub network calls

Update the beforeEach again, adding a stub and an explicit wait:

beforeEach(() => {
  // fixtures
  cy.fixture('todos/all_before.json').as('todosJSON');
  cy.fixture('todos/add.json').as('addTodoJSON');
  cy.fixture('todos/all_after.json').as('updatedJSON');

  // network stub
  cy.server();
  cy.route('GET', `${serverUrl}/todos`, '@todosJSON').as('getAllTodos');


  cy.visit('/');
  cy.wait('@getAllTodos');
  cy.get('h1').contains('Todo List');
});

Assign the value of the serverUrl environment variable to a variable:

const serverUrl = Cypress.env('serverUrl');

Add stubs to the should add a new todo to the list test:

it('should add a new todo to the list', () => {
  // network stubs
  cy.server();
  cy.route('GET', `${serverUrl}/todos`, '@updatedJSON').as('getAllTodos');
  cy.route('POST', `${serverUrl}/todos`, '@addTodoJSON').as('addTodo');
});

Open Cypress

Run the React app in one terminal window:

$ cd client
$ npm install
$ npm start

Then, open the Cypress GUI in a different window:

$ cd client
$ ./node_modules/.bin/cypress open

cypress gui

cypress gui

Development

Steps:

  1. Use .only to focus, and iterate, on a single test
  2. Ensure the test fails
  3. Code until that test passes (red, green, refactor)
  4. Repeat the previous three steps until all tests are green

Displays all todos

Update the test:

it.only('should display the todo list', () => {
  cy.get('li').its('length').should('eq', 3);
  cy.get('li').eq(0).contains('go for a walk');
});

cypress gui

Then, update the App component:

import React, { Component } from 'react';
import axios from 'axios';

class App extends Component {
  constructor() {
    super();
    this.state = {
      todos: []
    };
  };
  componentDidMount() {
    this.getTodos();
  };
  getTodos() {
    axios.get('http://localhost:5009/todos')
    .then((res) => { this.setState({ todos: res.data.data.todos }); })
    .catch((err) => { });
  };
  render() {
    return (
      <div className="App">
        <section className="section">
          <div className="container">
            <div className="columns">
              <div className="column is-half">
                <h1 className="title is-1">Todo List</h1>
                <hr/>
                <ul type="1">
                  {this.state.todos.map(todo =>
                    <li key={ todo.id } style={{ fontSize: '1.5rem' }}>{ todo.name }</li>
                  )}
                </ul>
              </div>
            </div>
          </div>
        </section>
      </div>
    );
  };
};

export default App;

Within componentDidMount, an AJAX request is sent to the server-side to get all todos. When the response comes back, setState is called inside the success handler, which re-renders the component, displaying the todo list.

The test should now pass:

cypress gui

Add a todo

Remove the .only from the passing test, and update the next test:

it.only('should add a new todo to the list', () => {
  // network stubs
  cy.server();
  cy.route('GET', 'http://localhost:5009/todos', '@updatedJSON').as('getAllTodos');
  cy.route('POST', 'http://localhost:5009/todos', '@addTodoJSON').as('addTodo');

  // asserts
  cy.get('.input').type('drink a beverage');
  cy.get('.button').contains('Submit').click();
  cy.wait('@addTodo');
  cy.wait('@getAllTodos');
  cy.get('li').its('length').should('eq', 4);
  cy.get('li').eq(0).contains('go for a walk');
  cy.get('li').eq(3).contains('drink a beverage');
});

cypress gui

Again, update the component:

import React, { Component } from 'react';
import axios from 'axios';

class App extends Component {
  constructor() {
    super();
    this.state = {
      todos: [],
      input: ''
    };
    this.handleChange = this.handleChange.bind(this);
    this.addTodo = this.addTodo.bind(this);
  };
  componentDidMount() {
    this.getTodos();
  };
  getTodos() {
    axios.get('http://localhost:5009/todos')
    .then((res) => { this.setState({ todos: res.data.data.todos }); })
    .catch((err) => { });
  };
  handleChange(e) {
    this.setState({ input: e.target.value });
  };
  addTodo() {
    if(this.state.input.length) {
      axios.post('http://localhost:5009/todos', { name: this.state.input })
      .then((res) => { this.getTodos(); })
      .catch((err) => { });
    }
  };
  render() {
    return (
      <div className="App">
        <section className="section">
          <div className="container">
            <div className="columns">
              <div className="column is-half">
                <h1 className="title is-1">Todo List</h1>
                <hr/>
                <div className="content">
                  <div className="field has-addons">
                    <div className="control">
                      <input
                        className="input"
                        type="text"
                        placeholder="Add a todo"
                        onChange={ this.handleChange }
                      />
                    </div>
                    <div className="control" onClick={ this.addTodo }>
                      <button className="button is-info">Submit</button>
                    </div>
                  </div>
                  <ul type="1">
                    {this.state.todos.map(todo =>
                      <li key={ todo.id } style={{ fontSize: '1.5rem' }}>{ todo.name }</li>
                    )}
                  </ul>
                </div>
              </div>
            </div>
          </div>
        </section>
      </div>
    );
  };
};

export default App;

Todos can now be added via an input field. The onChange event is fired, which updates the state, anytime the input value is changed. After the submit button is clicked, an AJAX POST request is sent to the server along with the value from the input field. The todo list is updated when a successful response is returned.

cypress gui

Toggle completed state

Test:

it.only('should toggle a todo correctly', () => {
  cy
    .get('li')
    .eq(0)
    .contains('go for a walk')
    .should('have.css', 'text-decoration', 'none solid rgb(74, 74, 74)');
  cy.get('li').eq(0).contains('go for a walk').click();
  cy
    .get('li')
    .eq(0).contains('go for a walk')
    .should('have.css', 'text-decoration', 'line-through solid rgb(74, 74, 74)');
});

cypress gui

Component:

import React, { Component } from 'react';
import axios from 'axios';

class App extends Component {
  constructor() {
    super();
    this.state = {
      todos: [],
      input: ''
    };
    this.handleChange = this.handleChange.bind(this);
    this.addTodo = this.addTodo.bind(this);
    this.handleClick = this.handleClick.bind(this);
  };
  componentDidMount() {
    this.getTodos();
  };
  getTodos() {
    axios.get('http://localhost:5009/todos')
    .then((res) => { this.setState({ todos: res.data.data.todos }); })
    .catch((err) => { });
  };
  handleChange(e) {
    this.setState({ input: e.target.value });
  };
  addTodo() {
    if(this.state.input.length) {
      axios.post('http://localhost:5009/todos', { name: this.state.input })
      .then((res) => { this.getTodos(); })
      .catch((err) => { });
    }
  };
  handleClick(id) {
    this.setState ({
      todos: this.state.todos.map (todo => {
        if (todo.id === id) {
          todo.complete = !todo.complete;
        }
        return todo;
      }),
    });
  };
  render() {
    return (
      <div className="App">
        <section className="section">
          <div className="container">
            <div className="columns">
              <div className="column is-half">
                <h1 className="title is-1">Todo List</h1>
                <hr/>
                <div className="content">
                  <div className="field has-addons">
                    <div className="control">
                      <input
                        className="input"
                        type="text"
                        placeholder="Add a todo"
                        onChange={ this.handleChange }
                      />
                    </div>
                    <div className="control" onClick={ this.addTodo }>
                      <button className="button is-info">Submit</button>
                    </div>
                  </div>
                  <ul type="1">
                    {this.state.todos.map(todo =>
                      <li
                        key={ todo.id }
                        style={{
                          textDecoration: todo.complete ? 'line-through' : 'none',
                          fontSize: '1.5rem',
                        }}
                        onClick={() => this.handleClick(todo.id)}
                      >
                        { todo.name }
                      </li>
                    )}
                  </ul>
                </div>
              </div>
            </div>
          </div>
        </section>
      </div>
    );
  };
};

export default App;

When a todo li is clicked, handleClick is fired, which then toggles the value of the todo.complete boolean. If the boolean is true then the todo's text-direction will be set to line-through.

cypress gui

Remove the .only from the final test.

End-to-End Tests

Finally, let's convert the integration tests to end-to-end tests by removing the network stubs and fixtures. We'll do a bit of refactoring as well.

client/cypress/integration/todos-e2e.spec.js:

const serverUrl = Cypress.env('serverUrl');

describe('todo app - e2e', () => {
  beforeEach(() => {
    // network call
    cy.server();
    cy.route('GET', `${serverUrl}/todos`).as('getAllTodos');

    cy.visit('/');
    cy.wait('@getAllTodos');
    cy.get('h1').contains('Todo List');
  });

  it('should display the todo list', () => {
    cy.get('li').its('length').should('eq', 2);
    cy.get('li').eq(0).contains('walk');
  });

  it('should toggle a todo correctly', () => {
    cy
      .get('li')
      .eq(0)
      .contains('walk')
      .should('have.css', 'text-decoration', 'none solid rgb(74, 74, 74)');
    cy.get('li').eq(0).contains('walk').click();
    cy
      .get('li')
      .eq(0).contains('walk')
      .should('have.css', 'text-decoration', 'line-through solid rgb(74, 74, 74)');
  });
});

Before you run these, you will need to spin up the server-side Flask app along with Postgres and create and seed the database:

$ cd server
$ docker-compose up -d --build
$ docker-compose exec web python manage.py recreate_db
$ docker-compose exec web python manage.py seed_db

cypress gui

Now, since the full end-to-end tests require the Flask app to be running, you may not want to run them locally as you're developing. To ignore them, add the ignoreTestFiles config variable to the cypress.json file:

{
  "baseUrl": "http://localhost:3000",
  "env": {
    "serverUrl": "http://localhost:5009"
  },
  "ignoreTestFiles": "*e2e*"
}

You can then run them on other environments by using a different configuration file.

Conclusion

Cypress is a powerful tool that makes it easy to set up, write, run, and debug tests. Hopefully, this post showed you how easy it is to incorporate Cypress into your development workflow.

Resources:

  1. Final code
  2. My Cypress Workflow video
  3. Cypress'ing Your Way to a Better Night's Sleep slides
  4. Sliding Down the Testing Pyramid blog post

Original article source at: https://testdriven.io/

#cypress #frontend #testing 

How to introduce Cypress Into Your Test-driven Development Workflow

Building a Single Page Application with Python and Pyodide - Part 2

In the first tutorial in this series, we built a Single Page Application using Python and Pyodide to load Pandas, fetch a Netflix Dataset, and perform basic computations on the data. We also looked at how Pyodide can be used to manipulate the DOM directly with Python. In the application that we built, we passed processed Netflix data to a JavaScript component and rendered it directly from Python code.

As mentioned in the conclusion to part one, the application is missing some features and we need to address a number of issues. In this second part, we'll:

  1. Better analyze and manipulate the data with Pandas
  2. Use a web worker to speed up the application

--

Python Single Page Application Series:

  1. Part 1: Learn the basics of Pyodide and create the base application
  2. Part 2 (this tutorial!): Analyze and manipulate the data with Pandas and use a web worker to speed up the application
  3. Part 3: Create a Python package, add additional features, and add a persistent data layer

Objectives

By the end of this tutorial, you should be able to:

  1. Use more advanced features of Pandas to analyze and manipulate the data
  2. Improve the user experience and performance with web workers

What We're Building

First, we'll improve the user experience and application performance by using a web worker. We'll also dive deeper into the Pandas library for analyzing and manipulating the Netflix data in order to create recommendations based on the given titles as well as add random movie and show facts.

Sample App

You can find a live demo of the application here.

Analyzing Netflix Dataset with Pandas

In part one, after loading the Netflix CSV file, we dropped a few unnecessary columns and returned the results as JSON. As you can see, we haven't done much analysis or manipulation of the data yet. We'll look at that now.

If you need the code from part one, you can find it here.

Create Recommendation List

The sanitized DataFrame has the following columns:

  • id
  • title
  • release_year
  • genres
  • production_countries
  • imdb_score
  • imdb_votes
  • tmdb_score
  • tmdb_popularity

Let's create a recommendation list for movies and shows using Pandas. To do so, we'll add a new column to the DataFrame called recommendation_score, with the value being the weighted sum of imdb_votes, imdb_score, tmdb_score, and tmdb_popularity:

recommended_titles["recommendation_score"] = (
    sanitized_titles["imdb_votes"] * 0.3 +
    sanitized_titles["imdb_score"] * 0.3 +
    sanitized_titles["tmdb_score"] * 0.2 +
    sanitized_titles["tmdb_popularity"] * 0.2
)

Open the index.html file in your code editor of choice, and add the following code after titles_list = sanitized_titles.head(10).to_json(orient="records")

# 4. Create recommendation list for Shows and Movies
# 4.1 Copy the sanitized_titles to add new column to it
recommended_titles = sanitized_titles.copy()

# 4.2 Add new column to the sanitized_titles
recommended_titles["recommendation_score"] = (
    sanitized_titles["imdb_votes"] * 0.3 +
    sanitized_titles["imdb_score"] * 0.3 +
    sanitized_titles["tmdb_score"] * 0.2 +
    sanitized_titles["tmdb_popularity"] * 0.2
)
print(recommended_titles.head(5))

Open the file in your browser. Then, within the console in your browser's developer tools, you should see the first five titles. Take note of the recommendation_score column:

      id                            title  ... tmdb_score recommendation_score
 tm84618                      Taxi Driver  ...        8.2          238576.2524
tm127384  Monty Python and the Holy Grail  ...        7.8          159270.7632
 tm70993                    Life of Brian  ...        7.8          117733.1610
tm190788                     The Exorcist  ...        7.7          117605.6374
 ts22164     Monty Python's Flying Circus  ...        8.3           21875.3838

With that, let's create two new DataFrames, one for movies and another for shows, and then sort them by recommendation_score in descending order:

recommended_movies = (
    recommended_titles.loc[recommended_titles["type"] == "MOVIE"]
    .sort_values(by="recommendation_score", ascending=False)
    .head(5)
    .to_json(orient="records")
)

recommended_shows = (
    recommended_titles.loc[recommended_titles["type"] == "SHOW"]
    .sort_values(by="recommendation_score", ascending=False)
    .head(5)
    .to_json(orient="records")
)

Here, we used the loc and sort_values methods to filter the titles by the type column and sort by recommendation_score in descending order, respectively.

Replace print(recommended_titles.head(5)) with these new lists:

# 4. Create recommendation list for Shows and Movies
# 4.1 Copy the sanitized_titles to add new column to it
recommended_titles = sanitized_titles.copy()

# 4.2 Add new column to the sanitized_titles
recommended_titles["recommendation_score"] = (
    sanitized_titles["imdb_votes"] * 0.3 +
    sanitized_titles["imdb_score"] * 0.3 +
    sanitized_titles["tmdb_score"] * 0.2 +
    sanitized_titles["tmdb_popularity"] * 0.2
)
recommended_movies = (
    recommended_titles.loc[recommended_titles["type"] == "MOVIE"]
    .sort_values(by="recommendation_score", ascending=False)
    .head(5)
    .to_json(orient="records")
)
recommended_shows = (
    recommended_titles.loc[recommended_titles["type"] == "SHOW"]
    .sort_values(by="recommendation_score", ascending=False)
    .head(5)
    .to_json(orient="records")
)

To use these lists in our application, first we need to add new keys to the App's state to be able to save and manipulate the data:

state = {
  titles: [],
  recommendedMovies: [],
  recommendedShows: [],
}

Now, to update the state, add the following, right after js.window.appComponent.state.titles = titles_list:

js.window.appComponent.state.recommendedMovies = recommended_movies
js.window.appComponent.state.recommendedShows = recommended_shows

Finally, to display the recommendations to the end user, add the following to view(), just below the <!-- End of Titles --!> line:

<!-- Start of Recommended title --!>
<div class="flex">
  <!-- Start of Recommended title --!>
  <div class="px-4 sm:px-6 lg:px-8 my-8 w-1/2">
    <p class="text-4xl font-semibold text-slate-100">Recommended Movies</p>
    <ul role="list" class="divide-y divide-gray-200">
      ${this.state.recommendedMovies.length > 0 ? JSON.parse(this.state.recommendedMovies).map(function (movie) {
          return `
            <li class="relative bg-white py-5 px-4 hover:bg-gray-50 focus-within:ring-2 focus-within:ring-inset focus-within:ring-indigo-600 rounded-md my-2">
              <div class="flex justify-between space-x-3">
                <div class="min-w-0 flex-1">
                  <p class="text-sm font-semibold text-gray-900 truncate">${movie.title}</p>
                  <p class="text-sm text-gray-500 truncate">${movie.description}</p>
                </div>
                <time datetime="" class="flex-shrink-0 whitespace-nowrap text-sm text-gray-500">${movie.release_year}</time>
              </div>
            </li>
            `
        }).join('') : `
          <li class="relative bg-white py-5 px-4 hover:bg-gray-50 focus-within:ring-2 focus-within:ring-inset focus-within:ring-indigo-600">
            <div class="flex justify-between space-x-3">
              <div class="min-w-0 flex-1">
                <p class="text-sm font-medium text-gray-900 truncate">Loading...</p>
              </div>
            </div>
          </li>
        </ul>
        `
      }
    </div>
    <!-- End of Recommended titles --!>

    <!-- Start of Recommended Shows --!>
    <div class="px-4 sm:px-6 lg:px-8 my-8 w-1/2">
      <p class="text-4xl font-semibold text-slate-100">Recommended Shows</p>
      <ul role="list" class="divide-y divide-gray-200">
        ${this.state.recommendedShows.length > 0 ? JSON.parse(this.state.recommendedShows).map(function (show) {
          return `
            <li class="relative bg-white py-5 px-4 hover:bg-gray-50 focus-within:ring-2 focus-within:ring-inset focus-within:ring-indigo-600 rounded-md my-2">
              <div class="flex justify-between space-x-3">
                <div class="min-w-0 flex-1">
                  <p class="text-sm font-semibold text-gray-900 truncate">${show.title}</p>
                  <p class="text-sm text-gray-500 truncate">${show.description}</p>
                </div>
                <time datetime="" class="flex-shrink-0 whitespace-nowrap text-sm text-gray-500">${show.release_year}</time>
                </div>
              </li>
              `
        }).join('') : `
          <li class="relative bg-white py-5 px-4 hover:bg-gray-50 focus-within:ring-2 focus-within:ring-inset focus-within:ring-indigo-600">
            <div class="flex justify-between space-x-3">
              <div class="min-w-0 flex-1">
                <p class="text-sm font-medium text-gray-900 truncate">Loading...</p>
              </div>
            </div>
          </li>
        </ul>
      `}
    </div>
    <!-- Start of Recommended shows --!>
</div>
<!-- End of Recommended titles --!>

Back in your browser, you should now see the recommended movies and shows.

Movie and Show Facts

In this section, we'll find the year that produced the most movies and shows, starting with the Python code:

# 5. Movie and Show Facts

facts_movies = (
    sanitized_titles.loc[sanitized_titles["type"] == "MOVIE"]
    .groupby("release_year")
    .count()["id"]
    .sort_values(ascending=False)
    .head(1)
    .to_json(orient="table")
)

facts_shows = (
    sanitized_titles.loc[sanitized_titles["type"] == "SHOW"]
    .groupby("release_year")
    .count()["id"]
    .sort_values(ascending=False)
    .head(1)
    .to_json(orient="table")
)

Here, we used the:

  1. groupby method to group the titles by release_year.
  2. count to count the number of titles per year.
  3. sort_values to sort the titles by the number of titles per year in descending order.

Add the above code to the index.html file, just below the recommendation section.

Update the App's state again:

state = {
  titles: [],
  recommendedMovies: [],
  recommendedShows: [],
  factsMovies: [],
  factsShows: [],
}

Update the state:

# 6. set titles to first 10 titles to the state, update remaining state, and render
js.window.appComponent.state.titles = titles_list
js.window.appComponent.state.recommendedMovies = recommended_movies
js.window.appComponent.state.recommendedShows = recommended_shows
js.window.appComponent.state.factsMovies = facts_movies   # NEW
js.window.appComponent.state.factsShows = facts_shows     # NEW
js.window.appComponent.render()

Update view() again by adding the following, just after <!-- End of Recommended Shows --!>:

<!-- Start of Facts --!>
<div class="px-4 sm:px-6 lg:px-8 my-8">
  <div>
    <h3 class="text-4xl font-semibold text-slate-100">Interesting Facts</h3>
    <dl class="mt-5 grid grid-cols-1 gap-5 sm:grid-cols-3">
      <div class="px-4 py-5 bg-white shadow rounded-lg overflow-hidden sm:p-6">
        ${this.state.factsMovies.length > 0 ?
          `
            <dt class="text-sm font-medium text-gray-500 truncate">Movies produced in ${JSON.parse(this.state.factsMovies).data[0].release_year}</dt>
            <dd class="mt-1 text-3xl font-semibold text-gray-900">${JSON.parse(this.state.factsMovies).data[0].id}</dd>
          ` : `
            Loading...
          `}
      </div>
      <div class="px-4 py-5 bg-white shadow rounded-lg overflow-hidden sm:p-6">
        ${this.state.factsShows.length > 0 ?
          `
            <dt class="text-sm font-medium text-gray-500 truncate">Shows produced in ${JSON.parse(this.state.factsShows).data[0].release_year}</dt>
            <dd class="mt-1 text-3xl font-semibold text-gray-900">${JSON.parse(this.state.factsShows).data[0].id}</dd>
          ` : `
            Loading...
          `}
      </div>
    </dl>
  </div>
</div>
<!-- End of Facts --!>

Reload the index.html page in the browser. You should see the interesting facts section with the number of movies and shows produced in the year that produced the most movies and shows.

Improve Performance

One of the issues with the current implementation is that we're putting expensive operations in the main thread of the browser. The consequence of this is that other operations will be blocked until Pyodide finishes loading and executing the code. This can have negative impact on the performance of the application and user experience.

Web Workers

To resolve this issue we can use web workers to offload the heavy operations -- Pyodide and the Python script, in this case -- to a separate thread in the background to let the browser's main thread continue running other operations without getting slowed down or locked up.

The main components of a web worker are:

  1. Worker() constructor: Creates a new instance of a web worker, which we can pass a script to that will be run in a separate thread
  2. onmessage() event: Triggered when the worker receives a message from another thread
  3. postMessage() method: Sends a message to the worker
  4. terminate() method: Terminates the worker

Let's look at a quick example.

Create a new file called worker.js in the root of your project:

self.onmessage = function(message) {
  console.log(message.data);
}

This file contains the code the worker will run.

Create a new script tag in index.html, just before the closing body tag:

<script>
  const worker = new Worker("./worker.js");
  worker.postMessage("Hello from the main thread!");
</script>

Due to security reasons, the web worker file cannot be imported from your local file system with the file:// protocol. We'll need to run a local web server to run the project. Within your terminal, navigate to your project's root. Then, run Python's http server:

$ python -m http.server

With the server running, navigate to http://localhost:8000/ in your browser. You should see Hello from the main thread! in the developer console.

Move Pyodide to a Web Worker

Our goal in this section is to:

  1. Load and initialize Pyodide and it's packages in a web worker
  2. Run our Python script in the web worker and post the result to the main thread in order to render it

First, remove the function definition and call for main() in index.html. Then, replace all of the code in worker.js with:

// load pyodide.js
importScripts("https://cdn.jsdelivr.net/pyodide/v0.20.0/full/pyodide.js");

// Initialize pyodide and load Pandas
async function initialize(){
  self.pyodide = await loadPyodide();
  await self.pyodide.loadPackage("pandas");
}

let initialized = initialize();

Now, add the following code to the worker.js file to run our script when the worker is initialized:

self.onmessage = async function (e) {
  await initialized;
  response = await fetch(
    "https://raw.githubusercontent.com/amirtds/kaggle-netflix-tv-shows-and-movies/main/titles.csv"
  );
  response.ok && response.status === 200
    ? (titles = await response.text())
    : (titles = "");
  // define global variable called titles to make it accessible by Python
  self.pyodide.globals.set("titlesCSV", titles);
  let titlesList = await self.pyodide.runPythonAsync(`
    import pandas as pd
    import io

    # 1. create csv buffer to make it readable by pandas
    csv_buffer = io.StringIO(titlesCSV)
    # 2. load the csv file
    all_titles = pd.read_csv(csv_buffer)

    # 3. sanitize the data
    # drop unnecessary columns
    all_titles = all_titles.drop(
        columns=[
            "age_certification",
            "seasons",
            "imdb_id",
        ]
    )
    # drop rows with null values for important columns
    sanitized_titles = all_titles.dropna(
        subset=[
            "id",
            "title",
            "release_year",
            "genres",
            "production_countries",
            "imdb_score",
            "imdb_votes",
            "tmdb_score",
            "tmdb_popularity",
        ]
    )
    # Convert the DataFrame to a JSON object. ('orient="records"' returns a list of objects)
    titles_list = sanitized_titles.head(10).to_json(orient="records")
    titles_list
  `);

  let recommendations = await self.pyodide.runPythonAsync(`
    # Create recommendation list for Shows and Movies
    # 1. Copy the sanitized_titles to add new column to it
    recommended_titles = sanitized_titles.copy()

    # 2. Add new column to the sanitized_titles
    recommended_titles["recommendation_score"] = (
        sanitized_titles["imdb_votes"] * 0.3 +
        sanitized_titles["imdb_score"] * 0.3 +
        sanitized_titles["tmdb_score"] * 0.2 +
        sanitized_titles["tmdb_popularity"] * 0.2
    )
    # 3. Create Recommended movies list
    recommended_movies = recommended_titles.loc[recommended_titles["type"] == "MOVIE"].sort_values(
        by="recommendation_score", ascending=False
    ).head(5).to_json(orient="records")
    # 4. Create Recommended shows list
    recommended_shows = recommended_titles.loc[recommended_titles["type"] == "SHOW"].sort_values(
        by="recommendation_score", ascending=False
    ).head(5).to_json(orient="records")
    recommendations = {
        "movies": recommended_movies,
        "shows": recommended_shows
    }
    recommendations
  `);

  let facts = await self.pyodide.runPythonAsync(`
    # Create facts list for Movies and Shows
    facts_movies = sanitized_titles.loc[sanitized_titles["type"] == "MOVIE"].groupby("release_year").count()["id"].sort_values(ascending=False).head(1).to_json(orient="table")
    facts_shows = sanitized_titles.loc[sanitized_titles["type"] == "SHOW"].groupby("release_year").count()["id"].sort_values(ascending=False).head(1).to_json(orient="table")
    facts = {
        "movies": facts_movies,
        "shows": facts_shows
    }
    facts
  `);

  self.postMessage({
    titles: titlesList,
    recommendedMovies: recommendations.toJs({
      dict_converter: Object.fromEntries,
    }).movies,
    recommendedShows: recommendations.toJs({
      dict_converter: Object.fromEntries,
    }).shows,
    factsMovies: facts.toJs({ dict_converter: Object.fromEntries }).movies,
    factsShows: facts.toJs({ dict_converter: Object.fromEntries }).shows,
  });
};

Here, after analyzing the Netflix data as we did before, we posted the results to the main thread using postMessage.

Next, in the index.html file after const worker = new Worker("./worker.js");, add the following code:

worker.postMessage("Running Pyodide");
worker.onmessage = function (event) {
  event.data.titles !== undefined ? appComponent.state.titles = event.data.titles : [];
  event.data.recommendedMovies !== undefined ? appComponent.state.recommendedMovies = event.data.recommendedMovies : [];
  event.data.recommendedShows !== undefined ? appComponent.state.recommendedShows = event.data.recommendedShows : [];
  event.data.factsMovies !== undefined ? appComponent.state.factsMovies = event.data.factsMovies : [];
  event.data.factsShows !== undefined ? appComponent.state.factsShows = event.data.factsShows : [];
  appComponent.render()
}

Stop and restart the Python HTTP server. Refresh the browser.

You should see the same results as before, but with execution of Pyodide and the Python code offloaded to a separate thread.

Conclusion

In this tutorial, we covered how to use Pandas to do data manipulation on our Netflix titles CSV data to create recommendation scores and lists for movies and shows. We also did some data analysis to find in which year most of the movies and shows were produced.

We also improved our application performance by moving Pyodide and the Python code execution to a web worker.

You can find the source code for this tutorial here.

In the next tutorial, we'll add more SPA features to our application, like deleting and editing movies and shows. We'll also add a persistence data layer so that remote data only has be fetched once.

--

Python Single Page Application Series:

  1. Part 1: Learn the basics of Pyodide and create the base application
  2. Part 2 (this tutorial!): Analyze and manipulate the data with Pandas and use a web worker to speed up the application
  3. Part 3: Create a Python package, add additional features, and add a persistent data layer

Original article source at: https://testdriven.io

#python #frontend 

Building a Single Page Application with Python and Pyodide - Part 2

Building A Single Page Application with Python and Pyodide - Part 1

Building a Single Page Application with Python and Pyodide - Part 1

If you talk to a man in a language he understands, that goes to his head. If you talk to him in his own language, that goes to his heart.

— ​Nelson Mandela

WebAssembly (WASM) opened the door for many languages to be used in different environments -- like the browser, cloud, serverless, and blockchain, to name a few -- that they couldn't have been used in before. For example, with Pyodide, which leverages WASM, you can run Python in the browser. You can also use runtimes like Wasmer to compile Python to WebAssembly, containerize it, and then run the container in different destinations like edge, IoT, and operating systems.

In this tutorial, you'll build a Single Page Application using Python and Pyodide to manipulate the DOM and manage state.

--

Python Single Page Application Series:

  1. Part 1 (this tutorial!): Learn the basics of Pyodide and create the base application
  2. Part 2: Analyze and manipulate the data with Pandas and use a web worker to speed up the application
  3. Part 3: Create a Python package, add additional features, and add a persistent data layer

Objectives

By the end of this tutorial, you should be able to:

  1. Use Pyodide alongside JavaScript to share and access objects between the two
  2. Manipulate the DOM directly from Python code
  3. Run Python's powerful data science libraries in the browser
  4. Create a Single Page Application (SPA) application that fetches data from a remote file, manipulates the data with Pandas, and renders it in the browser

Python in the Browser

What does it mean?

Running Python in the browser means that we can execute Python code directly on the client-side without needing to host and execute code on a server. This is made possible by WebAssembly, which allows us to build true "serverless" applications.

Why is it important? Why not just use JavaScript?

The main goal behind running Python in the browser is not to replace JavaScript but to bring both languages together and let each community use each other's powerful tools and libraries. For example, in this tutorial, we'll be using Python's Pandas library alongside JavaScript.

What We're Building

In this tutorial series, we'll be building a serverless Single Page Application (SPA) that fetches a Netflix Movies and Shows Dataset and uses Pandas to read, sanitize, manipulate and analyze the data. The results are then displayed on the DOM for the end user to see.

Our final project is a SPA that displays a list of movies and television shows, recommended movies and shows, and interesting facts. The end user will be able to delete and filter movies and shows. Data persists in PouchDB.

In this part, we'll focus on:

  1. Learning the basics of Pyodide and Pandas
  2. Sharing objects and methods between Python and JavaScript
  3. Manipulating the DOM from Python code
  4. Building the basic application structure

Sample App

You can find a live demo of the application that you'll create in this first part here.

DOM Manipulation with Python

Before we dive into building the application, let's quickly look at how we can use Python to interact with the DOM API to manipulate it directly.

To begin with, create a new HTML file called index.html:

<!DOCTYPE html>
<html>
  <head>
    <script src="https://cdn.jsdelivr.net/pyodide/v0.20.0/full/pyodide.js"></script>
  </head>

  <body>
    <p id="title">My first Pyodide app</p>

    <script>
      async function main() {
        let pyodide = await loadPyodide();

        pyodide.runPython(`print('Hello world, from the browser!')`);
      };

      main();
    </script>
  </body>
</html>

Here, we loaded the main Pyodide runtime along with Pyodide's built-in packages from a CDN and ran a simple Python script using the runPython method.

Open the file in your browser. Then, within the console in your browser's developer tools, you should see:

Loading distutils
Loaded distutils
Python initialization complete
Hello world, from the browser!

The last line shows that our Python code got executed in the browser. Now let's see how we can access the DOM. To do so, we can import the js library to access the JavaScript scope.

Update the HTML file like so:

<!DOCTYPE html>
<html>
  <head>
    <script src="https://cdn.jsdelivr.net/pyodide/v0.20.0/full/pyodide.js"></script>
  </head>

  <body>
    <p id="title">My first Pyodide app</p>

    <script>
      async function main() {
        let pyodide = await loadPyodide();

        pyodide.runPython(`
          print('Hello world, from the browser!')

          import js
          js.document.title = "Hello from Python"
        `);
      };

      main();
    </script>
  </body>
</html>

So, js represents the global object window that can then be used to directly manipulate the DOM and access global variables and functions. We used it to change the browser / document title to "Hello from Python".

Refresh the browser to ensure it works.

Next, let's update the paragraph's inner text from "My first Pyodide app" to "Replaced by Python":

<!DOCTYPE html>
<html>
  <head>
    <script src="https://cdn.jsdelivr.net/pyodide/v0.20.0/full/pyodide.js"></script>
  </head>

  <body>
    <p id="title">My first Pyodide app</p>

    <script>
      async function main() {
        let pyodide = await loadPyodide();

        pyodide.runPython(`
          print('Hello world, from the browser!')

          import js
          js.document.title = "Hello from Python"

          js.document.getElementById("title").innerText = "Replaced by Python"
        `);
      };

      main();
    </script>
  </body>
</html>

Save the file, and refresh the page again.

Type Translation

One great feature of Pyodide is that you can pass objects between Python and JavaScript. There are two translation methods:

  1. Implicit conversion converts basic data types that exist in both languages -- e.g., converting a Python str to JavaScript String.
  2. Proxy objects convert objects/types that are not shared between the language.

Implicit Conversion

As mentioned, basic data types will be converted directly between Python and JavaScript without a need to create special objects.

PythonJavaScript
intNumber or BigInt
floatNumber
strString
boolBoolean
Noneundefined
JavaScriptPython
Numberint or float
BigIntint
Stringstr
Booleanbool
undefinedNone
nullNone

Let's look at an example.

Start by adding a new variable called name to the script tag:

<script>
  var name = "John Doe";  // NEW!

  async function main() {
    let pyodide = await loadPyodide();

    pyodide.runPython(`
      print('Hello world, from the browser!')

      import js
      js.document.title = "Hello from Python"

      js.document.getElementById("title").innerText = "Replaced by Python"
    `);
  };

  main();
</script>

Next, let's take a look at the type and value using js:

<script>
  var name = "John Doe";

  async function main() {
    let pyodide = await loadPyodide();

    pyodide.runPython(`
      print('Hello world, from the browser!')

      import js
      js.document.title = "Hello from Python"

      js.document.getElementById("title").innerText = "Replaced by Python"
    `);

    // NEW !!
    pyodide.runPython(`
      import js
      name = js.name
      print(name, type(name))
    `);
  };

  main();
</script>

Refresh the browser. Within the console, you should see the following output:

John Doe <class 'str'>

As you can see, we have direct access to this variable's value, and it got converted from a JavaScript String to Python str.

Proxying

As you've seen, basic types can be converted directly to their equivalent in the target language. On the other hand, "non-basic" types need to be converted to a proxy object. There are two types of proxy objects:

  1. JSProxy is a proxy for making JavaScript objects behave like Python objects. In other words, it allows you to reference JavaScript objects in memory from Python code. You can use the to_py() method to convert the proxy to a native Python object.
  2. PyProxy is a proxy for making Python objects behave like JavaScript objects, allowing you to reference Python objects in memory from JavaScript code. You can use the toJs() method to convert the object to a native JavaScript object.

To convert a Python dictionary to JavaScript object, use the dict_converter argument with a value of Object.fromEntries:

dictionary_name.toJs({ dict_converter: Object.fromEntries })

Without this argument, toJs()will convert the dictionary to a JavaScript Map object.

JSProxy Example

Create a new variable called products:

var products = [{
    id: 1,
    name: "product 1",
    price: 100,
}, {
    id: 2,
    name: "Product 2",
    price: 300,
}];

Import it into runPython and check the type:

pyodide.runPython(`
  import js
  print(type(js.products))
`);

Full script tag:

<script>
  var name = "John Doe";

  // NEW !!
  var products = [{
    id: 1,
    name: "product 1",
    price: 100,
  }, {
    id: 2,
    name: "Product 2",
    price: 300,
  }];

  async function main() {
    let pyodide = await loadPyodide();

    pyodide.runPython(`
      print('Hello world, from the browser!')

      import js
      js.document.title = "Hello from Python"

      js.document.getElementById("title").innerText = "Replaced by Python"
    `);
    pyodide.runPython(`
      import js
      name = js.name
      print(name, type(name))
    `);

    // NEW !!
    pyodide.runPython(`
      import js
      print(type(js.products))
    `);
  };

  main();
</script>

After refreshing the page, you should see in the console that the result is <class 'pyodide.JsProxy'>. With this proxy, we have access to the JavaScript object in memory from our Python code.

Update the newly added pyodide.runPython block like so:

pyodide.runPython(`
  import js
  products = js.products
  products.append({
      "id": 3,
      "name": "Product 3",
      "price": 400,
  })
  for p in products:
      print(p)
`);

You should see an AttributeError: append error in the browser since the JSProxy object doesn't have an append method.

What happens if you change .append to .push?

To manipulate this object, you can convert it to a Python object using the to_py() method:

pyodide.runPython(`
  import js
  products = js.products.to_py()
  products.append({
    "id": 3,
    "name": "Product 3",
    "price": 400,
  })
  for p in products:
      print(p)
`);

You should now see:

{'id': 1, 'name': 'product 1', 'price': 100}
{'id': 2, 'name': 'Product 2', 'price': 300}
{'id': 3, 'name': 'Product 3', 'price': 400}

PyProxy Example

Update the script tag like so:

<script>
  async function main() {
    let pyodide = await loadPyodide();

    pyodide.runPython(`
      import js
      products = [
        {
          "id": 1,
          "name": "new name",
          "price": 100,
          "votes": 2
        },
        {
          "id": 2,
          "name": "new name",
          "price": 300,
          "votes": 2
        }
      ]
    `);

    let products = pyodide.globals.get("products");
    console.log(products.toJs({ dict_converter: Object.fromEntries }));
  };

  main();
</script>

Here, we accessed the Python variable and then converted it from a Python dict to a JavaScript object via .toJs({ dict_converter: Object.fromEntries }).

After refreshing the page, you should see the following output in the console:

{id: 1, name: 'new name', price: 100, votes: 2}
{id: 2, name: 'new name', price: 300, votes: 2}

With that, let's put your newfound Pyodide knowledge to use and build an application!

Netflix Dataset

We'll be developing a serverless SPA application that fetches a Netflix dataset. We'll then use Pandas to read, sanitize, manipulate, and analyze the data. Finally, we'll pass the results to the DOM to display the analyzed data to the end user.

The dataset is a CSV, which includes the following columns:

NameDescription
IDThe title ID on JustWatch.
titleThe name of the title.
show typeTV show or movie.
descriptionA brief description.
release yearThe release year.
age certificationThe age certification.
runtimeThe length of the episode (show) or movie.
genresA list of genres.
production countriesA list of countries that produced the title.
seasonsNumber of seasons if it's a show.
IMDB IDThe title ID on IMDB.
IMDB ScoreScore on IMDB.
IMDB VotesVotes on IMDB.
TMDB PopularityPopularity on TMDB.
TMDB ScoreScore on TMDB.

Install Pyodide and TailwindCSS

Update the content of your index.html file like so:

<!DOCTYPE html>
<html>
  <head>
    <script src="https://cdn.jsdelivr.net/pyodide/v0.20.0/full/pyodide.js"></script>
    <script src="https://cdn.tailwindcss.com"></script>
  </head>

  <body class="bg-slate-900">
    <div id="app" class="relative h-full max-w-7xl mx-auto my-16"></div>
    <script>

    </script>
  </body>
</html>

As you can see, we loaded Pyodide from a CDN along with Tailwind CSS for styling. We also defined a <div> element with an id of app to hold the App component we'll be building out next.

Create the App Component

Add the following JavaScript code to the script tag:

class App {
  state = {
    titles:[],
  }

  view() {
    return `<p class="text-slate-100">Hello, World!</p>`
  }

  render() {
    app.innerHTML = this.view();
  }
}

Here, we defined an object called App. We'll refer to this as a component since it's an independent, reusable piece of code.

The App component has a state object for holding data along with two nested functions called view() and render(). render() simply appends the outputted HTML code from view() to the DOM, to the div with an id of app.

Let's create a new instance of App called appComponent and call render() on it. Add the following code after the class declaration of App:

var appComponent = new App();
appComponent.render();

Open the file in your browser. You should see "Hello, World!".

Add Sample Data

Next, let's add sample movies to the state. In the script tag, right before calling appComponent.render();, update the state with the following:

appComponent.state.titles = [
  {
    "id": 1,
    "title": "The Shawshank Redemption",
    "release_year": 1994,
    "type": "MOVIE",
    "genres": [
      "Crime",
      "Drama"
    ],
    "production_countries": [
      "USA"
    ],
    "imdb_score": 9.3,
    "imdb_votes": 93650,
    "tmdb_score": 9.3,
  },
  {
    "id": 2,
    "title": "The Godfather",
    "release_year": 1972,
    "type": "MOVIE",
    "genres": [
      "Crime",
      "Drama"
    ],
    "production_countries": [
      "USA"
    ],
    "imdb_score": 9.2,
    "imdb_votes": 93650,
    "tmdb_score": 9.3,
  }
];

Now, we can construct a table to display the data by updating view() in our App class like so:

view() {
  return (`
    <div class="px-4 sm:px-6 lg:px-8">
      <div class="sm:flex sm:items-center">
        <div class="sm:flex-auto">
          <h1 class="text-4xl font-semibold text-gray-200">Netflix Movies and Shows</h1>
        </div>
      </div>
      <!-- Start of Titles --!>
      <div class="mt-8 flex flex-col">
        <div class="-my-2 -mx-4 overflow-x-auto sm:-mx-6 lg:-mx-8">
          <div class="inline-block min-w-full py-2 align-middle md:px-6 lg:px-8">
            <div class="overflow-hidden shadow ring-1 ring-black ring-opacity-5 md:rounded-lg">
              <table class="min-w-full divide-y divide-gray-300">
                <thead class="bg-gray-50">
                  <tr>
                    <th scope="col" class="whitespace-nowrap py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6">Title</th>
                    <th scope="col" class="whitespace-nowrap px-2 py-3.5 text-left text-sm font-semibold text-gray-900">Type</th>
                    <th scope="col" class="whitespace-nowrap px-2 py-3.5 text-left text-sm font-semibold text-gray-900">Release Year</th>
                    <th scope="col" class="whitespace-nowrap px-2 py-3.5 text-left text-sm font-semibold text-gray-900">Genre</th>
                    <th scope="col" class="whitespace-nowrap px-2 py-3.5 text-left text-sm font-semibold text-gray-900">Production Country</th>
                  </tr>
                </thead>
                <tbody class="divide-y divide-gray-200 bg-white">
                  ${this.state.titles.length > 0 ? this.state.titles.map(function (title) {
                    return (`
                      <tr id=${title.id}>
                        <td class="whitespace-nowrap py-2 pl-4 pr-3 text-sm text-gray-500 sm:pl-6">${title.title}</td>
                        <td class="whitespace-nowrap px-2 py-2 text-sm font-medium text-gray-900">${title.type}</td>
                        <td class="whitespace-nowrap px-2 py-2 text-sm text-gray-500">${title.release_year}</td>
                        <td class="whitespace-nowrap px-2 py-2 text-sm text-gray-500">${title.genres}</td>
                        <td class="whitespace-nowrap px-2 py-2 text-sm text-gray-500">${title.production_countries}</td>
                      </tr>
                    `)
                  }).join('') : (`
                    <tr>
                      <td class="whitespace-nowrap py-2 pl-4 pr-3 text-sm text-gray-500 sm:pl-6">Titles are loading...</td>
                      <td class="whitespace-nowrap px-2 py-2 text-sm font-medium text-gray-900">Titles are loading...</td>
                      <td class="whitespace-nowrap px-2 py-2 text-sm text-gray-500">Titles are loading...</td>
                      <td class="whitespace-nowrap px-2 py-2 text-sm text-gray-500">Titles are loading...</td>
                      <td class="whitespace-nowrap px-2 py-2 text-sm text-gray-500">Titles are loading...</td>
                    </tr>
                  `)
                  }
                </tbody>
              </table>
            <div>
          </div>
        </div>
      </div>
      <!-- End of Titles --!>
    </div>
  `)
}

So, we-

  1. added a table element with columns for title, type, release year, genre(s), and production country.
  2. Checked the state.titles array length to see if it contains any titles. If there are titles, we looped through them and created a table row for each. If not, we created a table row with a loading message.

Refresh the page in the browser.

Pandas Data Manipulation

Installing Pandas

To load Python packages in Pyodide, you can use the loadPackage function right after initializing Pyodide.

For example:

let pyodide = await loadPyodide();
await pyodide.loadPackage("requests");

You can load multiple packages using a list:

await pyodide.loadPackage(["requests", "pandas", "numpy"]);

Back in your HTML file, add a main function after appComponent.render();:

async function main() {
  let pyodide = await loadPyodide();
  await pyodide.loadPackage("pandas");
}

Don't forget to call it:

main();

Refresh the page in your browser. You should see the following in the console:

Loading pandas, numpy, python-dateutil, six, pytz, setuptools, pyparsing
Loaded python-dateutil, six, pytz, pyparsing, setuptools, numpy, pandas

So, Pandas and the relevant sub-dependencies were loaded into the browser!

Reading and Manipulating Data

In this section, we'll fetch a CSV file from the internet, read it into a Pandas DataFrame, sanitize and manipulate it, and finally pass it to the state.

Python code:

import js
import pandas as pd
from pyodide.http import pyfetch

# 1. fetching CSV from and write it to memory
response = await pyfetch("https://raw.githubusercontent.com/amirtds/kaggle-netflix-tv-shows-and-movies/main/titles.csv")
if response.status == 200:
    with open("titles.csv", "wb") as f:
        f.write(await response.bytes())

# 2. load the csv file
all_titles = pd.read_csv("titles.csv")

# 3. sanitize the data
# drop unnecessary columns
all_titles = all_titles.drop(
    columns=[
        "age_certification",
        "seasons",
        "imdb_id",
    ]
)
# drop rows with null values for important columns
sanitized_titles = all_titles.dropna(
    subset=[
        "id",
        "title",
        "release_year",
        "genres",
        "production_countries",
        "imdb_score",
        "imdb_votes",
        "tmdb_score",
        "tmdb_popularity",
    ]
)
# Convert the DataFrame to a JSON object. ('orient="records"' returns a list of objects)
titles_list = sanitized_titles.head(10).to_json(orient="records")

# 4. set titles to first 10 titles to the state
js.window.appComponent.state.titles = titles_list
js.window.appComponent.render()

Take note of the code comments.

Add this code to a runPythonAsync method within main:

async function main() {
  let pyodide = await loadPyodide();
  await pyodide.loadPackage("pandas");
  await pyodide.runPythonAsync(`
    // add the code here
  `);
}

Next, remove appComponent.state.titles. Also, we need to change this line in the view method:

${this.state.titles.length > 0 ? this.state.titles.map(function (title) {

To:

${this.state.titles.length > 0 ? JSON.parse(this.state.titles).map(function (title) {

Why?

titles_list (titles_list = sanitized_titles.head(10).to_json(orient="records")) is a JSON string, so in order to iterate over it, we need to deserialize it.

Refresh the page in your browser. You should first see a loading message in the table. After Pyodide loads, Pandas imports, and, after the script finishes executing, you should see the full movies list.

Conclusion

We covered a lot in this tutorial! We looked at how Pyodide can let you run Python code in the browser, giving you the power to:

  1. Load and use Python packages directly in the browser. (We used Pandas to read and analyze a CSV file.)
  2. Access and manipulate the DOM from Python code. (Importing js in our Python code gave us access to the DOM.)
  3. Share and access objects and namespaces between Python and JavaScript. (In our Javascript code, we created a component that we were able to access in our Python code in order to manage its state and call its methods.)

You can find the source code for this tutorial here.

--

We're still missing a few things and we need to address a few issues, though:

  1. First, We haven't done much with the CSV file we imported. Pandas gives us a lot of power to easily analyze and manipulate data.
  2. Second, Pyodide can take some time to initialize and run the Python script. Since it's currently running in the main thread, it paralyzes the application until it's done running. We should move Pyodide and the Python script to a web worker to prevent this.
  3. Third, We haven't seen full SPA-like behavior yet. We still need to update the component to add event listeners to respond to user actions.
  4. Finally, the Python script section is not syntax highlighted in code editors. Plus, it's starting to get hard to read. We should move this code to a Python package and import it into Pyodide. This will make it easier to maintain and scale.

We'll cover these four things in the next tutorials!

--

Python Single Page Application Series:

  1. Part 1 (this tutorial!): Learn the basics of Pyodide and create the base application
  2. Part 2: Analyze and manipulate the data with Pandas and use a web worker to speed up the application
  3. Part 3: Create a Python package, add additional features, and add a persistent data layer

Original article source at: https://testdriven.io

#python #frontend 

Building A Single Page Application with Python and Pyodide - Part 1
Dexter  Goodwin

Dexter Goodwin

1667422080

Ng-zorro-antd: Angular UI Component Library Based on Ant Design

NG-ZORRO

An enterprise-class Angular UI component library based on Ant Design.

English | 简体中文

✨ Features

  • An enterprise-class UI design system for Angular applications.
  • 60+ high-quality Angular components out of the box.
  • Written in TypeScript with predictable static types.
  • The whole package of development and design resources and tools.
  • Support OnPush mode, high performance.
  • Powerful theme customization in every detail.
  • Internationalization support for dozens of languages.

🖥 Environment Support

IE / Edge
IE / Edge
Firefox
Firefox
Chrome
Chrome
Safari
Safari
Opera
Opera
Electron
Electron
Edgelast 2 versionslast 2 versionslast 2 versionslast 2 versionslast 2 versions

🎨 Design Specification

ng-zorro-antd synchronizes design specification with Ant Design on a regular basis, you can check the log online.

📦 Installation

We recommend using @angular/cli to install. It not only makes development easier, but also allows you to take advantage of the rich ecosystem of angular packages and tooling.

$ ng new PROJECT_NAME
$ cd PROJECT_NAME
$ ng add ng-zorro-antd

More information about @angular/cli here.

You can also install ng-zorro-antd with npm or yarn

$ npm install ng-zorro-antd

🔨 Usage

Import the component modules you want to use into your app.module.ts file and feature modules.

import { NzButtonModule } from 'ng-zorro-antd/button';

@NgModule({
  imports: [ NzButtonModule ]
})
export class AppModule {
}

@angular/cli users won't have to worry about the things below but it's good to know.

And import style and SVG icon assets file link in angular.json.

{
  "assets": [
+   {
+     "glob": "**/*",
+     "input": "./node_modules/@ant-design/icons-angular/src/inline-svg/",
+     "output": "/assets/"
+   }
  ],
  "styles": [
+   "node_modules/ng-zorro-antd/ng-zorro-antd.min.css"
  ]
}

See Getting Started for more details.

🔗 Links

⌨️ Development

$ git clone git@github.com:NG-ZORRO/ng-zorro-antd.git
$ cd ng-zorro-antd
$ npm install
$ npm run start

Browser would open automatically.

🤝 Contributing

We welcome all contributions. Please read our CONTRIBUTING.md first. You can submit any ideas as pull requests or as GitHub issues.

If you're new to posting issues, we ask that you read How To Ask Questions The Smart Way (This guide does not provide actual support services for this project!), How to Ask a Question in Open Source Community and How to Report Bugs Effectively prior to posting. Well written bug reports help us help you!

Thanks to JetBrains for supporting us free open source licenses.

JetBrains

❓ Help from the Community

For questions on how to use ng-zorro-antd, please post questions to Stack Overflow using the ng-zorro-antd tag. If you're not finding what you need on stackoverflow, you can find us on Discord as well.

As always, we encourage experienced users to help those who are not familiar with ng-zorro-antd!

🎉 Users

We list some users here, if your company or product uses NG-ZORRO, let us know here!

Download Details:

Author: NG-ZORRO
Source Code: https://github.com/NG-ZORRO/ng-zorro-antd 
License: MIT license

#typescript #enterprises #angular #frontend 

Ng-zorro-antd: Angular UI Component Library Based on Ant Design

Build a Lottery App with Solidity and Next.js

In this tutorial, you'll learn how to build a Lottery App with Solidity (Infura) and Next.js. This is the BEST tutorial to learn the basics of Solidity Development!

🔗 GitHub Repo: https://github.com/CleverProgrammers/lottery-dapp-youtube 

#web3 #frontend #blockchain #nextjs #solidity 

Build a Lottery App with Solidity and Next.js