One place for hosting & domains

      Application

      How To Use EJS to Template Your Node Application


      Introduction

      When creating quick on-the-fly Node applications, an easy and fast way to template our application is sometimes necessary.

      Jade comes as the view engine for Express by default but Jade syntax can be overly complex for many use cases. EJS is one alternative does that job well and is very easy to set up. Let’s take a look at how we can create a simple application and use EJS to include repeatable parts of our site (partials) and pass data to our views.

      Setting up the Demo App

      We will be making two pages for our application with one page with full width and the other with a sidebar.

      Get the code: You can find a git repo of the complete demo code on GitHub here

      File Structure

      Here are the files we’ll need for our application. We’ll do our templating inside of the views folder and the rest is pretty standard Node practices.

      - views
      ----- partials
      ---------- footer.ejs
      ---------- head.ejs
      ---------- header.ejs
      ----- pages
      ---------- index.ejs
      ---------- about.ejs
      - package.json
      - server.js
      

      package.json will hold our Node application information and the dependencies we need (express and EJS). server.js will hold our Express server setup, configuration. We’ll define our routes to our pages here.

      Node Setup

      Let’s go into our package.json file and set up our project there.

      package.json

      {
        "name": "node-ejs",
        "main": "server.js",
        "dependencies": {
          "ejs": "^3.1.5",
          "express": "^4.17.1"
        }
      }
      

      All we will need is Express and EJS. Now we have to install the dependencies we just defined. Go ahead and run:

      With all of our dependencies installed, let’s configure our application to use EJS and set up our routes for the two pages we need: the index page (full width) and the about page (sidebar). We will do all of this inside our server.js file.

      server.js

      // load the things we need
      var express = require('express');
      var app = express();
      
      // set the view engine to ejs
      app.set('view engine', 'ejs');
      
      // use res.render to load up an ejs view file
      
      // index page 
      app.get("https://www.digitalocean.com/", function(req, res) {
          res.render('pages/index');
      });
      
      // about page 
      app.get('/about', function(req, res) {
          res.render('pages/about');
      });
      
      app.listen(8080);
      console.log('8080 is the magic port');
      

      Here we define our application and set it to show on port 8080. We also have to set EJS as the view engine for our Express application using app.set('view engine', 'ejs');. Notice how we send a view to the user by using res.render(). It is important to note that res.render() will look in a views folder for the view. So we only have to define pages/index since the full path is views/pages/index.

      Start Up our Server

      Go ahead and start the server using:

      Now we can see our application in the browser at http://localhost:8080 and http://localhost:8080/about. Our application is set up and we have to define our view files and see how EJS works there.

      Create the EJS Partials

      Like a lot of the applications we build, there will be a lot of code that is reused. We’ll call those partials and define three files we’ll use across all of our site: head.ejs, header.ejs, and footer.ejs. Let’s make those files now.

      views/partials/head.ejs

      <meta charset="UTF-8">
      <title>EJS Is Fun</title>
      
      <!-- CSS (load bootstrap from a CDN) -->
      <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.2/css/bootstrap.min.css">
      <style>
          body { padding-top:50px; }
      </style>
      

      views/partials/header.ejs

      <nav class="navbar navbar-expand-lg navbar-light bg-light">
        <a class="navbar-brand" href="https://www.digitalocean.com/">EJS Is Fun</a>
        <ul class="navbar-nav mr-auto">
          <li class="nav-item">
            <a class="nav-link" href="https://www.digitalocean.com/">Home</a>
          </li>
          <li class="nav-item">
            <a class="nav-link" href="http://www.digitalocean.com/about">About</a>
          </li>
        </ul>
      </nav>
      

      views/partials/footer.ejs

      <p class="text-center text-muted">© Copyright 2020 The Awesome People</p>
      

      Add the EJS Partials to Views

      We have our partials defined now. All we have to do is include them in our views. Let’s go into index.ejs and about.ejs and use the include syntax to add the partials.

      Syntax for including an EJS Partial

      Use <%- include('RELATIVE/PATH/TO/FILE') %> to embed an EJS partial in another file.

      • The hyphen <%- instead of just <% to tell EJS to render raw HTML.
      • The path to the partial is relative to the current file.

      views/pages/index.ejs

      <!DOCTYPE html>
      <html lang="en">
      <head>
          <%- include('../partials/head'); %>
      </head>
      <body class="container">
      
      <header>
          <%- include('../partials/header'); %>
      </header>
      
      <main>
          <div class="jumbotron">
              <h1>This is great</h1>
              <p>Welcome to templating using EJS</p>
          </div>
      </main>
      
      <footer>
          <%- include('../partials/footer'); %>
      </footer>
      
      </body>
      </html>
      

      Now we can see our defined view in the browser at http://localhost:8080. node-ejs-templating-index

      For the about page, we also add a bootstrap sidebar to demonstrate how partials can be structured to reuse across different templates and pages.

      views/pages/about.ejs

      <!DOCTYPE html>
      <html lang="en">
      <head>
          <%- include('../partials/head'); %>
      </head>
      <body class="container">
      
      <header>
          <%- include('../partials/header'); %>
      </header>
      
      <main>
      <div class="row">
          <div class="col-sm-8">
              <div class="jumbotron">
                  <h1>This is great</h1>
                  <p>Welcome to templating using EJS</p>
              </div>
          </div>
      
          <div class="col-sm-4">
              <div class="well">
                  <h3>Look I'm A Sidebar!</h3>
              </div>
          </div>
      
      </div>
      </main>
      
      <footer>
          <%- include('../partials/footer'); %>
      </footer>
      
      </body>
      </html>
      

      If we visit http://localhost:8080/about, we can see our about page with a sidebar! node-ejs-templating-about

      Now we can start using EJS for passing data from our Node application to our views.

      Pass Data to Views and Partials

      Let’s define some basic variables and a list to pass to our home page. Go back into your server.js file and add the following inside your app.get("https://www.digitalocean.com/") route.

      server.js

      // index page 
      app.get("https://www.digitalocean.com/", function(req, res) {
          var mascots = [
              { name: 'Sammy', organization: "DigitalOcean", birth_year: 2012},
              { name: 'Tux', organization: "Linux", birth_year: 1996},
              { name: 'Moby Dock', organization: "Docker", birth_year: 2013}
          ];
          var tagline = "No programming concept is complete without a cute animal mascot.";
      
          res.render('pages/index', {
              mascots: mascots,
              tagline: tagline
          });
      });
      

      We have created a list called mascots and a simple string called tagline. Let’s go into our index.ejs file and use them.

      Render a Single Variable in EJS

      To echo a single variable, we just use <%= tagline %>. Let’s add this to our index.ejs file:

      views/pages/index.ejs

      ...
      <h2>Variable</h2>
      <p><%= tagline %></p>
      ...
      

      Loop Over Data in EJS

      To loop over our data, we will use .forEach. Let’s add this to our view file:

      views/pages/index.ejs

      ...
      <ul>
          <% mascots.forEach(function(mascot) { %>
              <li>
                  <strong><%= mascot.name %></strong>
                  representing <%= mascot.organization %>, born <%= mascot.birth_year %>
              </li>
          <% }); %>
      </ul>
      ...
      

      Now we can see in our browser the new information we have added!

      node-ejs-templating-rendered

      Pass Data to a Partial in EJS

      The EJS partial has access to all the same data as the parent view. But be careful: If you are referencing a variable in a partial, it needs to be defined in every view that uses the partial or it will throw an error.

      You can also define and pass variables to an EJS partial in the include syntax like this:

      views/pages/about.ejs

      ...
      <header>
          <%- include('../partials/header', {variant:'compact'}); %>
      </header>
      ...
      

      But you need to again be careful about assuming a variable has been defined.

      If you want to reference a variable in a partial that may not always be defined, and give it a default value, you can do so like this:

      views/partials/header.ejs

      ...
      <em>Variant: <%= typeof variant != 'undefined' ? variant : 'default' %></em>
      ...
      

      In the line above, the EJS code is rendering the value of variant if it’s defined, and default if not.

      Conclusion

      EJS lets us spin up quick applications when we don’t need anything too complex. By using partials and having the ability to easily pass variables to our views, we can build some great applications quickly.

      For more reference on EJS see the official docs here.



      Source link

      Build a To-Do application Using Django and React


      Introduction

      In this tutorial, we build a Todo application using Django and React.

      React is a JS framework that is great for developing SPAs (single page applications) and it has solid documentation and a vibrant ecosystem around it.

      Django is a Python web framework that simplifies common practices in web development. Django has been around for a while, meaning most gotcha’s and problems have been solved, and there’s a set of stable libraries supporting common development needs.

      For this application, React serves as the front-end or client side framework, handling UI and getting and setting data via requests to the Django back-end, which is an API built using the Django REST framework (DRF).

      At the end of this tutorial, we will have the final application that looks like this:

      The source code for this tutorial is available here on GitHub.

      Prerequisites

      To follow along with this tutorial, you will need to:

      1. Install and set up a local programming environment for Python 3
      2. Install Node.js and Create a Local Development Environment

      Setting up the Backend

      In this section, we will set up the backend and create all the folders that we need to get things up and running, so launch a new instance of a terminal and create the project’s directory by running this command:

      $ mkdir django-todo-react
      

      Next, we will navigate into the directory:

       $ cd django-todo-react
      

      Now we will install Pipenv using pip and activate a new virtual environment:

      $ pip install pipenv
      $ pipenv shell
      

      Note: You should skip the first command if you already have Pipenv installed.

      Let’s install Django using Pipenv then create a new project called backend:

      $ pipenv install django
      $ django-admin startproject backend
      

      Next, we will navigate into the newly created backend folder and start a new application called todo. We will also run migrations and start up the server:

      $ cd backend
      $ python manage.py startapp todo
      $ python manage.py migrate
      $ python manage.py runserver
      

      At this point, if all the commands were entered correctly, we should see an instance of a Django application running on this address — http://localhost:8000

      Registering the Todo application

      We are done with the basic setup for the backend, let’s start with the more advanced things like registering the todo application as an installed app so that Django can recognise it. Open the backend/settings.py file and update the INSTALLED_APPS section as so:

          # backend/settings.py
      
          # Application definition
          INSTALLED_APPS = [
              'django.contrib.admin',
              'django.contrib.auth',
              'django.contrib.contenttypes',
              'django.contrib.sessions',
              'django.contrib.messages',
              'django.contrib.staticfiles',
              'todo' # add this 
            ]
      

      Defining the Todo model

      Let’s create a model to define how the Todo items should be stored in the database, open the todo/models.py file and update it with this snippet:

          # todo/models.py
      
          from django.db import models
          # Create your models here.
      
          # add this
          class Todo(models.Model):
            title = models.CharField(max_length=120)
            description = models.TextField()
            completed = models.BooleanField(default=False)
      
            def _str_(self):
              return self.title
      

      The code snippet above describes three properties on the Todo model:

      • Title

      • Description

      • Completed

      The completed property is the status of a task; a task will either be completed or not completed at any time. Because we have created a Todo model, we need to create a migration file and apply the changes to the database, so let’s run these commands:

      $ python manage.py makemigrations todo
      $ python manage.py migrate todo
      

      We can test to see that CRUD operations work on the Todo model we created using the admin interface that Django provides out of the box, but first, we will do a little configuration.

      Open the todo/admin.py file and update it accordingly:

          # todo/admin.py
      
          from django.contrib import admin
          from .models import Todo # add this
      
          class TodoAdmin(admin.ModelAdmin):  # add this
            list_display = ('title', 'description', 'completed') # add this
      
          # Register your models here.
          admin.site.register(Todo, TodoAdmin) # add this
      

      We will create a superuser account to access the admin interface with this command:

      $ python manage.py createsuperuser
      

      You will be prompted to enter a username, email and password for the superuser. Be sure to enter details that you can remember because you will need them to log in to the admin dashboard shortly.

      Let’s start the server once more and log in on the address — http://localhost:8000/admin:

      $ python manage.py runserver
      

      We can create, edit and delete Todo items using this interface. Let’s go ahead and create some:

      Awesome work so far, be proud of what you’ve done! In the next section, we will see how we can create the API using the Django REST framework.

      Setting up the APIs

      Now, we will quit the server (CONTROL-C) then install the djangorestframework and django-cors-headers using Pipenv:

      $ pipenv install djangorestframework django-cors-headers
      

      We need to add rest_framework and corsheaders to the list of installed applications, so open the backend/settings.py file and update the INSTALLED_APPS and MIDDLEWARE sections accordingly:

          # backend/settings.py
      
          # Application definition
          INSTALLED_APPS = [
              'django.contrib.admin',
              'django.contrib.auth',
              'django.contrib.contenttypes',
              'django.contrib.sessions',
              'django.contrib.messages',
              'django.contrib.staticfiles',
              'corsheaders',            # add this
              'rest_framework',         # add this 
              'todo',
            ]
      
          MIDDLEWARE = [
              'corsheaders.middleware.CorsMiddleware',    # add this
              'django.middleware.security.SecurityMiddleware',
              'django.contrib.sessions.middleware.SessionMiddleware',
              'django.middleware.common.CommonMiddleware',
              'django.middleware.csrf.CsrfViewMiddleware',
              'django.contrib.auth.middleware.AuthenticationMiddleware',
              'django.contrib.messages.middleware.MessageMiddleware',
              'django.middleware.clickjacking.XFrameOptionsMiddleware',
          ]
      

      Add this code snippet to the bottom of the backend/settings.py file:

          # we whitelist localhost:3000 because that's where frontend will be served
          CORS_ORIGIN_WHITELIST = (
               'localhost:3000/'
           )
      

      Django-cors-headers is a python library that will help in preventing the errors that we would normally get due to CORS. rules. In the CORS_ORIGIN_WHITELIST snippet, we whitelisted localhost:3000 because we want the frontend (which will be served on that port) of the application to interact with the API.

      Creating serializers for the Todo model

      We need serializers to convert model instances to JSON so that the frontend can work with the received data easily. We will create a todo/serializers.py file:

      $ touch todo/serializers.py
      

      Open the serializers.py file and update it with the following code.

          # todo/serializers.py
      
          from rest_framework import serializers
          from .models import Todo
      
          class TodoSerializer(serializers.ModelSerializer):
            class Meta:
              model = Todo
              fields = ('id', 'title', 'description', 'completed')
      

      In the code snippet above, we specified the model to work with and the fields we want to be converted to JSON.

      Creating the View

      We will create a TodoView class in the todo/views.py file, so update it with the following code:

          # todo/views.py
      
          from django.shortcuts import render
          from rest_framework import viewsets          # add this
          from .serializers import TodoSerializer      # add this
          from .models import Todo                     # add this
      
          class TodoView(viewsets.ModelViewSet):       # add this
            serializer_class = TodoSerializer          # add this
            queryset = Todo.objects.all()              # add this
      

      The viewsets base class provides the implementation for CRUD operations by default, what we had to do was specify the serializer class and the query set.

      Head over to the backend/urls.py file and completely replace it with the code below. This code specifies the URL path for the API:

          # backend/urls.py
      
          from django.contrib import admin
          from django.urls import path, include                 # add this
          from rest_framework import routers                    # add this
          from todo import views                            # add this
      
          router = routers.DefaultRouter()                      # add this
          router.register(r'todos', views.TodoView, 'todo')     # add this
      
          urlpatterns = [
              path('admin/', admin.site.urls),         path('api/', include(router.urls))                # add this
          ]
      

      This is the final step that completes the building of the API, we can now perform CRUD operations on the Todo model. The router class allows us to make the following queries:

      • /todos/ – This returns a list of all the Todo items (Create and Read operations can be done here).

      • /todos/id – this returns a single Todo item using the id primary key (Update and Delete operations can be done here).

      Let’s restart the server and visit this address — http://localhost:8000/api/todos:

      $ python manage.py runserver
      

      We can create a new todo item using the interface:

      If the Todo item is created successfully, you will see a screen like this:

      We can also perform DELETE and UPDATE operations on specific Todo items using their id primary keys. To do this, we will visit an address with this structure /api/todos/id. Let’s try with this address — http://localhost:8000/api/todos/1:

      That’s all for the backend of the application, now we can move on to fleshing out the frontend.

      Setting up the frontend

      We have our backend running as it should, now we will create our frontend and make it communicate with the backend over the interface that we created.

      Since we are building our frontend using React, we want to use the create-react-app CLI tool because it registers optimal settings and several benefits such as Hot reloading and Service workers. We will install the create-react-app CLI (command line interface) tool globally with this command:

      $ npm install -g create-react-app
      

      Let’s navigate back into the parent working directory — django-todo-react — of our application and create a new React application called frontend:

      $ create-react-app frontend
      

      It will probably take a while for all of the dependencies to be installed, once it’s over, your terminal should look something like this:

      Run the following commands to navigate into the working directory and start the frontend server

      $ cd frontend
      $ yarn start
      

      Note: If you don’t have Yarn installed, you can find installation instructions here.

      We can now visit this address — http://localhost:3000 — and we will see the default React screen:

      We will pull in bootstrap and reactstrap to spice the UI up a bit:

      $ yarn add bootstrap reactstrap
      

      Let’s open the src/index.css file and replace the styles there with this one:

        /__ frontend/src/index.css  __/
      
          body {
            margin: 0;
            padding: 0;
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
              "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
              sans-serif;
            -webkit-font-smoothing: antialiased;
            -moz-osx-font-smoothing: grayscale;
            background-color: #282c34;
          }
          .todo-title {
            cursor: pointer;
          }
          .completed-todo {
            text-decoration: line-through;
          }
          .tab-list > span {
            padding: 5px 8px;
            border: 1px solid #282c34;
            border-radius: 10px;
            margin-right: 5px;
            cursor: pointer;
          }
          .tab-list > span.active {
            background-color: #282c34;
            color: #ffffff;
          }
      

      We will import Bootstrap’s stylesheet in src/index.js so that we can use Bootstrap’s classes:

      
            // frontend/src/index.js
      
            import React from 'react';
            import ReactDOM from 'react-dom';
            import 'bootstrap/dist/css/bootstrap.min.css';       // add this
            import './index.css';
            import App from './App';
            import * as serviceWorker from './serviceWorker';
      
            ReactDOM.render(<App />, document.getElementById('root'));
            // If you want your app to work offline and load faster, you can change
            // unregister() to register() below. Note this comes with some pitfalls.
            // Learn more about service workers: http://bit.ly/CRA-PWA
            serviceWorker.unregister();
      

      Let’s replace the code in src/App.js with this one:

        // frontend/src/App.js
      
          import React, { Component } from "react";
          const todoItems = [
            {
              id: 1,
              title: "Go to Market",
              description: "Buy ingredients to prepare dinner",
              completed: true
            },
            {
              id: 2,
              title: "Study",
              description: "Read Algebra and History textbook for upcoming test",
              completed: false
            },
            {
              id: 3,
              title: "Sally's books",
              description: "Go to library to rent sally's books",
              completed: true
            },
            {
              id: 4,
              title: "Article",
              description: "Write article on how to use django with react",
              completed: false
            }
          ];
          class App extends Component {
            constructor(props) {
              super(props);
              this.state = {
                viewCompleted: false,
                todoList: todoItems
              };
            }
            displayCompleted = status => {
              if (status) {
                return this.setState({ viewCompleted: true });
              }
              return this.setState({ viewCompleted: false });
            };
            renderTabList = () => {
              return (
                <div className="my-5 tab-list">
                  <span
                    onClick={() => this.displayCompleted(true)}
                    className={this.state.viewCompleted ? "active" : ""}
                  >
                    complete
                  </span>
                  <span
                    onClick={() => this.displayCompleted(false)}
                    className={this.state.viewCompleted ? "" : "active"}
                  >
                    Incomplete
                  </span>
                </div>
              );
            };
            renderItems = () => {
              const { viewCompleted } = this.state;
              const newItems = this.state.todoList.filter(
                item => item.completed == viewCompleted
              );
              return newItems.map(item => (
                <li
                  key={item.id}
                  className="list-group-item d-flex justify-content-between align-items-center"
                >
                  <span
                    className={`todo-title mr-2 ${
                      this.state.viewCompleted ? "completed-todo" : ""
                    }`}
                    title={item.description}
                  >
                    {item.title}
                  </span>
                  <span>
                    <button className="btn btn-secondary mr-2"> Edit </button>
                    <button className="btn btn-danger">Delete </button>
                  </span>
                </li>
              ));
            };
            render() {
              return (
                <main className="content">
                  <h1 className="text-white text-uppercase text-center my-4">Todo app</h1>
                  <div className="row ">
                    <div className="col-md-6 col-sm-10 mx-auto p-0">
                      <div className="card p-3">
                        <div className="">
                          <button className="btn btn-primary">Add task</button>
                        </div>
                        {this.renderTabList()}
                        <ul className="list-group list-group-flush">
                          {this.renderItems()}
                        </ul>
                      </div>
                    </div>
                  </div>
                </main>
              );
            }
          }
          export default App;
      

      Okay, that’s a lot of code ?, but there’s no need to be afraid now, we haven’t started interacting with the backend API, so we included default values to populate the Todo list. The `renderTabList()` function renders two spans which help control which set of items are displayed i.e clicking on the completed tab shows completed tasks and the same for the incomplete tab.

      If we visit the React frontend application now, it will look like this:

      To handle actions such as adding and editing tasks, we will use a modal, so let’s create a Modal component in a components folder.

      Create a components folder in the src directory:

      $ mkdir src/components
      

      Create a Modal.js file in the components folder:

      $ touch src/components/Modal.js
      

      Open the Modal.js file and populate it with the code snippet below:

       // frontend/src/components/Modal.js
      
          import React, { Component } from "react";
          import {
            Button,
            Modal,
            ModalHeader,
            ModalBody,
            ModalFooter,
            Form,
            FormGroup,
            Input,
            Label
          } from "reactstrap";
      
          export default class CustomModal extends Component {
            constructor(props) {
              super(props);
              this.state = {
                activeItem: this.props.activeItem
              };
            }
            handleChange = e => {
              let { name, value } = e.target;
              if (e.target.type === "checkbox") {
                value = e.target.checked;
              }
              const activeItem = { ...this.state.activeItem, [name]: value };
              this.setState({ activeItem });
            };
            render() {
              const { toggle, onSave } = this.props;
              return (
                <Modal isOpen={true} toggle={toggle}>
                  <ModalHeader toggle={toggle}> Todo Item </ModalHeader>
                  <ModalBody>
                    <Form>
                      <FormGroup>
                        <Label for="title">Title</Label>
                        <Input
                          type="text"
                          name="title"
                          value={this.state.activeItem.title}
                          onChange={this.handleChange}
                          placeholder="Enter Todo Title"
                        />
                      </FormGroup>
                      <FormGroup>
                        <Label for="description">Description</Label>
                        <Input
                          type="text"
                          name="description"
                          value={this.state.activeItem.description}
                          onChange={this.handleChange}
                          placeholder="Enter Todo description"
                        />
                      </FormGroup>
                      <FormGroup check>
                        <Label for="completed">
                          <Input
                            type="checkbox"
                            name="completed"
                            checked={this.state.activeItem.completed}
                            onChange={this.handleChange}
                          />
                          Completed
                        </Label>
                      </FormGroup>
                    </Form>
                  </ModalBody>
                  <ModalFooter>
                    <Button color="success" onClick={() => onSave(this.state.activeItem)}>
                      Save
                    </Button>
                  </ModalFooter>
                </Modal>
              );
            }
          }
      

      We created a CustomModal class and it nests the Modal component that is derived from the reactstrap library. We also defined three fields in the form:

      • Title

      • Description

      • Completed

      These are the same fields that we defined as properties on the Todo model in the backend.

      Here’s how the CustomModal works, it receives activeItem, toggle and onSave as props.

      1. activeItem represents the Todo item to be edited.
      2. toggle is a function used to control the Modal’s state i.e open or close the modal.
      3. onSave is a function that is called to save the edited values of the Todo item.

      Next, we will import the CustomModal component into the App.js file. Head over to the src/App.js file and replace it completely with this updated version:

        // frontend/src/App.js
      
          import React, { Component } from "react";
          import Modal from "./components/Modal";
      
          const todoItems = [
            {
              id: 1,
              title: "Go to Market",
              description: "Buy ingredients to prepare dinner",
              completed: true
            },
            {
              id: 2,
              title: "Study",
              description: "Read Algebra and History textbook for upcoming test",
              completed: false
            },
            {
              id: 3,
              title: "Sally's books",
              description: "Go to library to rent sally's books",
              completed: true
            },
            {
              id: 4,
              title: "Article",
              description: "Write article on how to use django with react",
              completed: false
            }
          ];
          class App extends Component {
            constructor(props) {
              super(props);
              this.state = {
                modal: false,
                viewCompleted: false,
                activeItem: {
                  title: "",
                  description: "",
                  completed: false
                },
                todoList: todoItems
              };
            }
            toggle = () => {
              this.setState({ modal: !this.state.modal });
            };
            handleSubmit = item => {
              this.toggle();
              alert("save" + JSON.stringify(item));
            };
            handleDelete = item => {
              alert("delete" + JSON.stringify(item));
            };
            createItem = () => {
              const item = { title: "", description: "", completed: false };
              this.setState({ activeItem: item, modal: !this.state.modal });
            };
            editItem = item => {
              this.setState({ activeItem: item, modal: !this.state.modal });
            };
            displayCompleted = status => {
              if (status) {
                return this.setState({ viewCompleted: true });
              }
              return this.setState({ viewCompleted: false });
            };
            renderTabList = () => {
              return (
                <div className="my-5 tab-list">
                  <span
                    onClick={() => this.displayCompleted(true)}
                    className={this.state.viewCompleted ? "active" : ""}
                  >
                    complete
                  </span>
                  <span
                    onClick={() => this.displayCompleted(false)}
                    className={this.state.viewCompleted ? "" : "active"}
                  >
                    Incomplete
                  </span>
                </div>
              );
            };
            renderItems = () => {
              const { viewCompleted } = this.state;
              const newItems = this.state.todoList.filter(
                item => item.completed === viewCompleted
              );
              return newItems.map(item => (
                <li
                  key={item.id}
                  className="list-group-item d-flex justify-content-between align-items-center"
                >
                  <span
                    className={`todo-title mr-2 ${
                      this.state.viewCompleted ? "completed-todo" : ""
                    }`}
                    title={item.description}
                  >
                    {item.title}
                  </span>
                  <span>
                    <button
                      onClick={() => this.editItem(item)}
                      className="btn btn-secondary mr-2"
                    >
                      Edit
                    </button>
                    <button
                      onClick={() => this.handleDelete(item)}
                      className="btn btn-danger"
                    >
                      Delete
                    </button>
                  </span>
                </li>
              ));
            };
            render() {
              return (
                <main className="content">
                  <h1 className="text-white text-uppercase text-center my-4">Todo app</h1>
                  <div className="row ">
                    <div className="col-md-6 col-sm-10 mx-auto p-0">
                      <div className="card p-3">
                        <div className="">
                          <button onClick={this.createItem} className="btn btn-primary">
                            Add task
                          </button>
                        </div>
                        {this.renderTabList()}
                        <ul className="list-group list-group-flush">
                          {this.renderItems()}
                        </ul>
                      </div>
                    </div>
                  </div>
                  {this.state.modal ? (
                    <Modal
                      activeItem={this.state.activeItem}
                      toggle={this.toggle}
                      onSave={this.handleSubmit}
                    />
                  ) : null}
                </main>
              );
            }
          }
          export default App;
      

      We can now revisit the React frontend, this is what the application should resemble at this point:

      If we attempt to edit and save a Todo item, we will get an alert showing the Todo item’s object. Clicking on save, and delete will perform the fitting actions on the Todo item.

      We will now modify the application so that it interacts with the Django API we built in the previous section. Let’s start by starting up the backend server (on a different instance of the terminal) if it isn’t already running:

      $ python manage.py runserver
      

      Note: This command has to be run in the `backend` directory in a virtual Pipenv shell.

      For us to make requests to the API endpoints on the backend server, we will install a JavaScript library called axios. Let’s pull in `axios using Yarn:

      $ yarn add axios
      

      Once axios is successfully installed, head over to the frontend/package.json file and add a proxy like so:

            // frontend/package.json
      
            [...]       "name": "frontend",
            "version": "0.1.0",
            "private": true,
            "proxy": "http://localhost:8000",
            "dependencies": {
              "axios": "^0.18.0",
              "bootstrap": "^4.1.3",
              "react": "^16.5.2",
              "react-dom": "^16.5.2",
              "react-scripts": "2.0.5",
              "reactstrap": "^6.5.0"
            },
            [...]
      

      The proxy will help in tunnelling API requests to http://localhost:8000 where the Django application will handle them, so we can write the requests like this in the frontend:

      axios.get("/api/todos/")
      

      Instead of this:

      axios.get("http://localhost:8000/api/todos/")
      

      Note: You might need to restart the development server for the proxy to register with the application.

      We will modify the frontend/src/App.js one last time so that it doesn’t use the hardcoded items from the array anymore, but requests data from the backend server and lists them instead. We want to also ensure that all CRUD operations send requests to the backend server instead of interacting with the dummy data.

      Open the file and replace it with this final version:

       // frontend/src/App.js
      
          import React, { Component } from "react";
          import Modal from "./components/Modal";
          import axios from "axios";
      
          class App extends Component {
            constructor(props) {
              super(props);
              this.state = {
                viewCompleted: false,
                activeItem: {
                  title: "",
                  description: "",
                  completed: false
                },
                todoList: []
              };
            }
            componentDidMount() {
              this.refreshList();
            }
            refreshList = () => {
              axios
                .get("http://localhost:8000/api/todos/")
                .then(res => this.setState({ todoList: res.data }))
                .catch(err => console.log(err));
            };
            displayCompleted = status => {
              if (status) {
                return this.setState({ viewCompleted: true });
              }
              return this.setState({ viewCompleted: false });
            };
            renderTabList = () => {
              return (
                <div className="my-5 tab-list">
                  <span
                    onClick={() => this.displayCompleted(true)}
                    className={this.state.viewCompleted ? "active" : ""}
                  >
                    complete
                  </span>
                  <span
                    onClick={() => this.displayCompleted(false)}
                    className={this.state.viewCompleted ? "" : "active"}
                  >
                    Incomplete
                  </span>
                </div>
              );
            };
            renderItems = () => {
              const { viewCompleted } = this.state;
              const newItems = this.state.todoList.filter(
                item => item.completed === viewCompleted
              );
              return newItems.map(item => (
                <li
                  key={item.id}
                  className="list-group-item d-flex justify-content-between align-items-center"
                >
                  <span
                    className={`todo-title mr-2 ${
                      this.state.viewCompleted ? "completed-todo" : ""
                    }`}
                    title={item.description}
                  >
                    {item.title}
                  </span>
                  <span>
                    <button
                      onClick={() => this.editItem(item)}
                      className="btn btn-secondary mr-2"
                    >
                      {" "}
                      Edit{" "}
                    </button>
                    <button
                      onClick={() => this.handleDelete(item)}
                      className="btn btn-danger"
                    >
                      Delete{" "}
                    </button>
                  </span>
                </li>
              ));
            };
            toggle = () => {
              this.setState({ modal: !this.state.modal });
            };
            handleSubmit = item => {
              this.toggle();
              if (item.id) {
                axios
                  .put(`http://localhost:8000/api/todos/${item.id}/`, item)
                  .then(res => this.refreshList());
                return;
              }
              axios
                .post("http://localhost:8000/api/todos/", item)
                .then(res => this.refreshList());
            };
            handleDelete = item => {
              axios
                .delete(`http://localhost:8000/api/todos/${item.id}`)
                .then(res => this.refreshList());
            };
            createItem = () => {
              const item = { title: "", description: "", completed: false };
              this.setState({ activeItem: item, modal: !this.state.modal });
            };
            editItem = item => {
              this.setState({ activeItem: item, modal: !this.state.modal });
            };
            render() {
              return (
                <main className="content">
                  <h1 className="text-white text-uppercase text-center my-4">Todo app</h1>
                  <div className="row ">
                    <div className="col-md-6 col-sm-10 mx-auto p-0">
                      <div className="card p-3">
                        <div className="">
                          <button onClick={this.createItem} className="btn btn-primary">
                            Add task
                          </button>
                        </div>
                        {this.renderTabList()}
                        <ul className="list-group list-group-flush">
                          {this.renderItems()}
                        </ul>
                      </div>
                    </div>
                  </div>
                  {this.state.modal ? (
                    <Modal
                      activeItem={this.state.activeItem}
                      toggle={this.toggle}
                      onSave={this.handleSubmit}
                    />
                  ) : null}
                </main>
              );
            }
          }
          export default App;
      

      The refreshList() function is reusable that is called each time an API request is completed. It updates the Todo list to display the most recent list of added items.

      The handleSubmit() function takes care of both the create and update operations. If the item passed as the parameter doesn’t have an id, then it has probably not been created, so the function creates it.

      Congratulations! We have just built the fontend successfully.

      Testing the application

      Let’s start the backend server on a terminal instance that’s sourced into the Pipenv virtual shell and pointed to the backend directory:

      $ python manage.py runserver
      

      We also need to start the frontend development server:

      $ yarn start
      

      We can visit the application on this address — http://localhost:3000 — to see that it works:

      We’ve come to the end of this tutorial and learnt how to configure Django and React to interact correctly with each other. We also saw some of the benefits that come with bootstrapping a React application using the create-react-app tool, such as Hot-reloading which is basically the feature that makes it possible for the web app to reload on its own whenever a change is detected.

      The source code for this tutorial is available here on GitHub.



      Source link

      How To Create Your First Cross-Platform Desktop Application with Electron on macOS


      The author selected the Apache Software Foundation to receive a donation as part of the Write for DOnations program.

      Introduction

      Electron is an open source framework for creating native applications with web technologies like JavaScript, HTML, and CSS. It combines support for building and running applications cross platform on Mac, Windows, and Linux. Many popular desktop applications have been built using Electron, such as Visual Studio Code, WhatsApp, Twitch, Slack, and Figma.

      Electron facilitates designing more complex application features like automatic updates or native menus, which means that developers can focus on the core design of their application. Further, Electron is an open source project maintained by GitHub with an active community of contributors.

      In this tutorial, you’ll first set up a project and install Electron. After that you’ll create your first “Hello World!” application using Electron and customize it. You’ll implement graceful window setup and create new windows for the application. After following all of these steps, you will have an Electron cross-platform desktop application on macOS.

      Prerequisites

      To complete this tutorial, you will need:

      Note: This tutorial was tested on macOS 10.15.3.

      Step 1 — Creating the Project

      First you’ll install Electron to your machine and create the project folder to build the desktop application.

      To start the Electron installation process, create the project folder called hello-world and navigate to the folder with the following commands:

      • mkdir hello-world
      • cd hello-world

      Next, you’ll initiate your project by creating the package.json file.

      The package.json file is an essential part of a Node.js application, it performs the following:

      • Lists the packages that your project depends on.
      • Specifies the package version your project can use.

      To create the package.json file, run the following command:

      You will be asked a series of questions, starting with the package name. You can use the default application name, hello-world, as your package name.

      Then it asks for the version. To use v1.0.0, which comes as default, press ENTER.

      After that, it asks for a description. There you can add a description of your project, something like: hello world application on Electron.js.

      Next, for the entry point, enter main.js.

      The file invoked at the initial run time of application is known as the entry point. In this case, main.js is the entry point of the package.json file.

      For the remaining questions, accept the defaults with ENTER.

      Note: In this tutorial we’re leaving the author and license empty, but you can use your first and last name as the author depending on your project status. The license of your package specifies how others are permitted to use the application, and any restrictions you’re placing on it. The most common licenses are: MIT, BSD-2-Clause, and ISC. For more details, check the full list of SPDX license IDs. From there you can use a preferred license for your project, but this is not mandatory.

      Having followed the prompts you’ll receive the following output:

      Output:

      . . . Use `npm install <pkg>` afterwards to install a package and save it as a dependency in the package.json file. Press ^C at any time to quit. package name: (hello-world) version: (1.0.0) description: hello world application on Electron.js entry point: (index.js) main.js test command: git repository: keywords: author: license: (ISC)

      After that, you’ll be asked to confirm the configuration:

      Output:

      About to write to /hello-world/package.json: { "name": "hello-world", "version": "1.0.0", "description": "hello world application on Electron.js", "main": "main.js", "scripts": { "test": "echo "Error: no test specified" && exit 1" }, "author": "", "license": "ISC" } Is this OK? (yes)

      You’ll now have the newly generated package.json file inside your hello-world project directory. Next you’ll install Electron.

      Step 2 — Installing Electron

      Now you’ll implement the configuration of the package.json file and install Electron.

      For that, open the package.json file in your preferred text editor:

      Add the following highlighted line inside the scripts object:

      package.json

      {
        "name": "hello-world",  "version": "1.0.0",
        "description": "hello world application on Electron.js",
        "main": "main.js",
        "scripts": {
          "start": "electron .",
          "test": "echo "Error: no test specified" && exit 1"
       },
        "author": "",
        "license": "ISC"
      }
      

      The scripts property takes an object with as many key/value pairs as desired. Each one of the keys in these key/value pairs is the name of a command that can be run. The corresponding value of each key is the actual command that can be run. Scripts are frequently used for testing, building, and streamlining of the needed commands.

      In this project, you’ll use start as a key and electron . as a value.

      Once you’re done, save and exit the file.

      Next, you’ll install Electron as a development dependency in your project. Run the following command inside your hello-world project directory:

      • npm install --save-dev electron

      After successfully installing the Electron dependency to your project, the package.json file will be similar to this:

      package.json

      {
        "name": "hello-world",
        "version": "1.0.0",
        "description": "hello world application on Electron.js",
        "main": "main.js",
        "scripts": {
          "start": "electron .",
          "test": "echo "Error: no test specified" && exit 1"
        },
        "author": "",
        "license": "ISC",
        "devDependencies": {
          "electron": "^8.2.1"
        }
      }
      

      The dependency property takes an object that has the name and version for each dependency.

      There are two dependency properties dependencies and devDependencies that can be identified with a key difference. The dependencies property is used to define the dependencies that a module needs to run in production. The devDependencies property is usually used to define the dependencies the module needs to run in development. To install the package as devDependencies use the --save-dev flag with your command.

      You’ve installed Electron to your machine and created the project folder to build your application. Now you’ll write your first hello-world application using the Electron framework.

      Step 3 — Writing the “Hello World!” Application

      Let’s start writing your first Electron application.

      Electron operates with two types of processes: the main process (server-side) and the renderer process (client-side). The Electron main process is run on the Node.js runtime.

      For that, you’ll be working with two files: main.js and index.html.

      main.js is your application’s main process and index.html is your Electron application renderer process.

      hello-world
      +-- package.json
      +-- main.js
      +-- index.html
      

      Next, we create a manual browser window and load the content using Electron API calls, which you can use to execute HTML, CSS, JavaScript, and so on.

      First open your main.js file:

      Then add the following line of code to implement the BrowserWindow module:

      main.js

      const { app, BrowserWindow } = require('electron')
      

      This contains two destructuring assignments called app and BrowserWindow, which are required for an Electron module. The Browserwindow module is used to create a new window in your Electron application.

      Next, add the following code to your main.js file:

      main.js

      . . .
      function createWindow () {
        const mainWindow = new BrowserWindow({
          width: 800,
          height: 600
        })
      
        mainWindow.loadFile('index.html')
      }
      
      app.whenReady().then(createWindow)
      

      You add an Electron createWindow function to your hello-world application. In this function, you create a new BrowserWindow renderer process and pass the width and height parameters. The width and height will set the application window size.

      The mainWindow.loadFile() method renders some contents into the BrowserWindow. The index.html file will load the content.

      The main process will be started when the app.whenReady().then(windowName) method is ready. At this point, the main process calls the createWindow function. This function creates a new renderer process, or browser window instance, with a width of 800px and height of 600px. Then the renderer process proceeds to load content using mainWindow.loadFile('index.html') method. In this tutorial, you use index.html as the filename.

      Next add the following events code to your file:

      main.js

      . . .
      app.on('window-all-closed', () => {
        if (process.platform !== 'darwin') {
          app.quit()
        }
      })
      
      app.on('activate', () => {
        if (BrowserWindow.getAllWindows().length === 0) {
          createWindow()
        }
      })
      

      You add the two main system events into the project—window-all-closed and activate events:

      • window-all-closed: Quits the application when all windows are closed. On macOS it is common for applications and their menu bar to stay active until the user quits explicitly with CMD+Q.
      • activate: Various actions can trigger this event, such as launching the application for the first time, attempting to re-launch the application when it’s already running, or clicking on the application’s dock (macOS) or taskbar icon.

      After adding these code blocks, your final output of the main.js file will be similar to this:

      main.js

      const { app, BrowserWindow } = require('electron')
      
      function createWindow () {
        const mainWindow = new BrowserWindow({
          width: 800,
          height: 600
        })
      
        mainWindow.loadFile('index.html')
      
      }
      
      app.whenReady().then(createWindow)
      
      app.on('window-all-closed', () => {
        if (process.platform !== 'darwin') {
          app.quit()
        }
      })
      
      app.on('activate', () => {
        if (BrowserWindow.getAllWindows().length === 0) {
          createWindow()
        }
      })
      

      Once you’re done, save and exit this file.

      Next, create and open the index.html file:

      Add the following code, which is sent as the final output:

      index.html

      <!DOCTYPE html>
      <html>
        <head>
          <meta charset="UTF-8">
          <title>Hello World!</title>
        </head>
        <body
          <h1>Hello World!</h1>
        </body>
      </html>
      

      Here you create a static HTML web page. The Electron application renderer process supports all HTML syntax since Electron uses Chromium for the rendering process.

      Now that you’re done, you can run your application:

      You will get an application window as an output.

      Hello world printed output window of the application

      You’ve created your first cross-platform application with the Electron framework. Next you’ll work with some customizations, which you can add for interactivity.

      Step 4 — Customizing Your “Hello World!”“ Application

      Now you have completed the initial setup of your first cross-platform application using the Electron framework. Let’s see what else you can do to improve the native behavior of the application.

      Electron has a number of built-in features such as dialog boxes, windows options, new windows, menus, shortcuts, notifications, touch bars, session controls, and so on, that improve the user experience of your desktop application. Let’s add some features to customize the hello-world application.

      Graceful Window Setup of the Application

      When you load a page into the window directly, at the startup of your application you may see the page does not load at once. This isn’t a great experience in native applications. Let’s fix this issue in a few steps.

      To do this, you need to hide the BrowserWindow by passing new configuration parameters at the time it gets created.

      For that, open the main.js file:

      Add the show: false parameter to the body of the BrowserWindow object:

      main.js

      const mainWindow = new BrowserWindow({
         width: 800,
         height: 600,
         show: false
       })
      

      Next, you’ll add a new listener to the BrowserWindow instance by adding the highlighted code line inside the createWindow function body. You’ll also add new configuration parameters into the BrowserWindow to change the background color of the initially built window.

      For that, you have to add the following code line of backgroundColor object, inside the BrowserWindow function. Feel free to change the hex color code as you wish.

      backgroundColor: '#Your hex color code'
      

      Add this line like the following highlighted code to your createWindow function:

      main.js

      function createWindow () {
       const mainWindow = new BrowserWindow({
         width: 800,
         height: 600,
         show: false,
         backgroundColor: '#ffff00'
       })
       mainWindow.loadFile('index.html')
      
       mainWindow.once('ready-to-show', mainWindow.show)
      
      }
      

      To reduce the garbage collection, you need to execute this listener one time by using the once keyword. Therefore, the mainWindow.show method executes only once at the run time of this application.

      Now save your file and run your application using the terminal:

      Your application will show with a yellow background.

      Hello world printed output window with the background color of yellow

      Finally, you will see the application window loading gracefully.

      Creating a New Window for the Application

      The use of more than one window is a common feature of basic to advanced applications. Let’s add that feature to your newly created application.

      Electron can create multiple renderer processes (multiple windows) from a single main process.

      First, open main.js:

      Create a new method called secWindow and set the width and height parameters of the newly created window by adding the highlighted code:

      main.js

      function createWindow () {
      const mainWindow = new BrowserWindow({
        width: 800,
        height: 600,
        show: false,
        backgroundColor: '#ffff00'
      })
      
      const secWindow = new BrowserWindow({
        width: 600,
        height: 400,
       })
      . . .
      }
      

      Now load content to the newly created BrowserWindow renderer process. At this time you’ll load some remote URL (Website) content.

      In this tutorial, you’re using https://www.digitalocean.com web content for the second window of the application. For that, in the second window initialization secWindow.loadURL, you add the following code line to the body of the createWindow function:

      main.js

       function createWindow () {
       const mainWindow = new BrowserWindow({
         width: 800,
         height: 600,
         show: false,
         backgroundColor: '#ffff00'
       })
      
      const secWindow = new BrowserWindow({
         width: 600,
         height: 400,
       })
      
       mainWindow.loadFile('index.html')
      
       secWindow.loadURL('https://www.digitalocean.com/')
      
       mainWindow.once('ready-to-show', mainWindow.show)
      }
      

      Now save and exit your file and run your application in the terminal:

      You will get your initial window with the yellow background and a new application with the loaded URL.

      New desktop application window of loaded URL with previously hello world printed output window

      You’ve made customizations to your newly created application to make it more interactive for users. Now it’s time to build your Electron application.

      Step 5 — Building Your First Application

      After adding some features to your application, you need to build it for the purpose of distribution. In this section, you will learn how to build the application for various platforms.

      The build process of the Electron application is considered somewhat hard because it needs a lot of tools. However, here you’ll use the electron-builder CLI tool that provides the best way to build your application for any platform.

      First, you’ll install the electron-builder CLI tools globally. To do this run the following command:

      • sudo npm install -g electron-builder

      Note: You can use either npm or yarn to install electron-builder—there are no noted differences in performance. If you intend to develop your application in the long term, the makers of electron-builder recommend yarn to avoid potential compatibility issues. To install using yarn, you’ll need to ensure it’s installed on your computer and then run yarn add electron-builder --dev to install electron-builder with yarn.

      After completing the installation of the electron-builder, you can verify the success of it by running the following command in your terminal:

      • electron-builder --version

      You’ll receive the current version of Electron in your output.

      Now you can build your first cross-platform application. To do this open your terminal and run the following command in your project directory:

      You use the flags -mwl to build applications for macOS, Windows, and Linux respectively.

      Note: Only macOS users can build for all platforms. Windows users can build the application for Windows and Linux platforms only. Linux users can build only for Linux platforms. For more details, you can refer to the documentation.

      To build applications for separate operating systems use the following:

      Build applications for macOS:

      Build applications for Windows:

      Build applications for Linux:

      This process takes some time while dependencies download and your application builds.

      Your project directory creates a new folder called dist. All your built applications and unzip versions of the application are located in that folder.

      As an example, if you build your application for all platforms, your project dist folder is similar to the following file structure:

      hello-world
        +-- hello-world-1.0.0-mac.zip
        +-- hello-world-1.0.0.dmg
        +-- hello-world Setup 1.0.0.exe
        +-- win-unpacked
        +-- mac
        +-- linux-unpacked
        +-- hello-world_1.0.0_amd64.snap
      

      electron-builder builds the Electron app for the current platform and current architecture as the default target.

      • macOS: DMG and ZIP for Squirrel.Mac
      • Windows: NSIS (.exe)
      • Linux: If you build on Windows or macOS, Snap and AppImage for x64 will be the output. Otherwise if you build on Linux, the output will be Snap and AppImage files for the current architecture.

      You’ve now built your application for all platforms.

      Conclusion

      In this tutorial, you created your first cross-platform application with the Electron framework, added native features, and built it for distribution, on macOS.

      To learn more about Electron, you can check out their documentation. Now you can also share your newly created desktop application with anyone by creating an installer.



      Source link