Categories
Vue 3

Vue 3 — Dynamic Transitions and State Animations

Vue 3 is the up and coming version of Vue front end framework.

It builds on the popularity and ease of use of Vue 2.

In this article, we’ll look at creating dynamic transitions and state change animation.

Dynamic Transitions

We can create dynamic transitions by passing in a dynamic string to the name prop.

For example, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js"></script>
  </head>
  <body>
    <div id="app">
      Fade in
      <input type="range" v-model="fadeDuration" min="0" :max="5000" /> <button @click="show = !show">
        toggle
      </button>
      <transition
        :css="false"
        @before-enter="beforeEnter"
        @enter="enter"
        @leave="leave"
      >
        <p v-if="show">hello</p>
      </transition>
    </div>
    <script>
      const app = Vue.createApp({
        data() {
          return {
            show: false,
            fadeDuration: 1000
          };
        },
        methods: {
          beforeEnter(el) {
            el.style.opacity = 0;
          },
          enter(el, done) {
            Velocity(
              el,
              { opacity: 1 },
              {
                duration: this.fadeDuration,
                complete: done
              }
            );
          },
          leave(el, done) {
            Velocity(
              el,
              { opacity: 0 },
              {
                duration: this.fadeDuration,
                complete: done
              }
            );
          }
        }
      }); app.mount("#app");
    </script>
  </body>
</html>

We added the Velocity library to add transitions effects of our choice.

The fadeDuration can be adjusted so that we change the length of the enter and leave effects.

We just called Velocity to change the opacity to what we want.

State Transitions

Vue can animate state changes.

They include things like numbers and calculations, colors displayed, SVG nodes, and other properties.

To do that, we can animate state change with watches.

For instance, we can make a simple state transition by writing:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.2.4/gsap.min.js"></script>
  </head>
  <body>
    <div id="app">
      <input v-model.number="number" type="number" step="20" />
      <p>{{ animatedNumber }}</p>
    </div>
    <script>
      const app = Vue.createApp({
        data() {
          return {
            number: 0,
            tweenedNumber: 0
          };
        },
        computed: {
          animatedNumber() {
            return this.tweenedNumber.toFixed(0);
          }
        },
        watch: {
          number(newValue) {
            gsap.to(this.$data, { duration: 1.5, tweenedNumber: newValue });
          }
        }
      }); app.mount("#app");
    </script>
  </body>
</html>

When we type in a number, then it’ll be animated.

The animation is don’t in the number watcher.

We call Greensock’s to method to animate towards the final value.

this.$data has the value of the final number.

tweenedNumber is the value displayed as the animation is being done.

animatedNumber takes the tweenedNumber value, return it and display it.

Organizing Transitions into Components

We can organize animations into their own components.

For instance, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.2.4/gsap.min.js"></script>
  </head>
  <body>
    <div id="app">
      <input v-model.number="x" type="number" step="20" /> +
      <input v-model.number="y" type="number" step="20" /> = {{ result }}
      <p>
        <animated-integer :value="x"></animated-integer> +
        <animated-integer :value="y"></animated-integer> =
        <animated-integer :value="result"></animated-integer>
      </p>
    </div> <script>
      const app = Vue.createApp({
        data() {
          return {
            x: 400,
            y: 373
          };
        },
        computed: {
          result() {
            return this.x + this.y;
          }
        }
      }); app.component("animated-integer", {
        template: "<span>{{ fullValue }}</span>",
        props: {
          value: {
            type: Number,
            required: true
          }
        },
        data() {
          return {
            tweeningValue: 0
          };
        },
        computed: {
          fullValue() {
            return Math.floor(this.tweeningValue);
          }
        },
        methods: {
          tween(newValue, oldValue) {
            gsap.to(this.$data, {
              duration: 0.5,
              tweeningValue: newValue,
              ease: "sine"
            });
          }
        },
        watch: {
          value(newValue, oldValue) {
            this.tween(newValue, oldValue);
          }
        },
        mounted() {
          this.tween(this.value, 0);
        }
      }); app.mount("#app");
    </script>
  </body>
</html>

We created the animated-integer component to animate our number.

It still animates the number, but we have a tween method to create the animation so that we can simplify our value watcher method.

fullValue is a computed property to round this.tweeningValue down to the nearest integer.

this.tweeningValue is the values that’ll be displayed during the animation.

So when we type in our numbers, we’ll see all the numbers being animated.

Conclusion

One good use of watchers is to animate state changes.

We made this easy with the Greensock library.

Also, we can change the settings of the transition on the fly.

Categories
JavaScript APIs

Using Google Translate API in our Node.js App with google-translate-api

The google-translate-api module lets us use the Google Translate API in our server-side JavaScript app.

In this article, we’ll look at how to use this package in our Node.js app.

Installation

We can install the package by running:

npm install @vitalets/google-translate-api

Usage

We can use the translate function that comes with this module to do our translation.

For example, we can write:

const translate = require('@vitalets/google-translate-api');

(async ()=>{
  try {
    const res = await translate('je parle français', { to: 'en' })
    console.log(res.text);
    console.log(res.from.language.iso);
  }
  catch (error){
    console.log(error)
  }
})()

translate returns a promise with the result.

The first argument is the text we want to translate.

The 2nd argument has the to property which is the code of the language we want to translate to.

res.text has the resulting text.

And res.from.language.iso has the code of the language of the text in the first argument.

The Google Translate API works with text that have typos.

For example, we can write:

const translate = require('@vitalets/google-translate-api');

(async ()=>{
  try {
    const res = await translate('I spea French', { from: 'en', to: 'fr' })
    console.log(res.text);
    console.log(res.from.text.autoCorrected);
    console.log(res.from.text.value);
    console.log(res.from.text.didYouMean);
  }
  catch (error){
    console.log(error)
  }
})()

We pass in ‘I spea French’ which has a typo.

from has the language oif the original text.

res.text has the translated result without the typo.

res.from.text.autoCorrected has the boolean to indicate whether the original text was autocorrected.

res.from.text.value has the autocorrected version of the text.

res.from.text.didYouMean is true means the API suggested a correction in the source language.

So we get:

je parle français
true
I [speak] French
false

from the 4 console logs.

We can also add our own language to the list.

For example, we can write:

const translate = require('@vitalets/google-translate-api');
translate.languages['sr-Latn'] = 'Serbian Latin';

(async ()=>{
  try {
    const res = await translate('I spea French', { to: 'sr-Latn' })
    console.log(res.text);
  }
  catch (error){
    console.log(error)
  }
})()

Then we get:

Говорим француски

from the console log.

Proxy

We can make requests via a proxy with this library.

To do this, we write:

const translate = require('@vitalets/google-translate-api');
const tunnel = require('tunnel');

(async ()=>{
  try {
    const res = await translate('I spea French', {
      to: 'fr',
      agent: tunnel.httpsOverHttp({
        proxy: {
          host: '138.68.60.8',
          proxyAuth: 'user:pass',
          port: '8080',
          headers: {
            'User-Agent': 'Node'
          }
        }
      })
    })
    console.log(res.text);
  }
  catch (error){
    console.log(error)
  }
})()

We use the tunnel library to make requests over a proxy.

We call the tunnel.httpOverHttp method with the proxy options and credentials to make the translate request over a proxy.

Conclusion

We can use the google-translate-api module lets us use the Google Translate API in our server-side JavaScript app.

Categories
Hapi

Server-Side Development with Hapi.js — Sessions and Advanced HTTP Requests

Hapi.js is a small Node framework for developing back end web apps.

In this article, we’ll look at how to create back end apps with Hapi.js.

Advanced HTTP Requests

We can make complex HTTP requests with the @hapi/wreck module.

For example, we can write:

const Wreck = require('@hapi/wreck');
const Https = require('https')
const Http = require('http')

const method = 'GET';
const uri = '/';
const readableStream = Wreck.toReadableStream('foo=bar');

const wreck = Wreck.defaults({
  headers: { 'x-foo-bar': 123 },
  agents: {
    https: new Https.Agent({ maxSockets: 100 }),
    http: new Http.Agent({ maxSockets: 1000 }),
    httpsAllowUnauthorized: new Https.Agent({ maxSockets: 100, rejectUnauthorized: false })
  }
});

const wreckWithTimeout = wreck.defaults({
  timeout: 5
});

const options = {
  baseUrl: 'https://www.example.com',
  payload: readableStream || 'foo=bar' || Buffer.from('foo=bar'),
  headers: {},
  redirects: 3,
  beforeRedirect: (redirectMethod, statusCode, location, resHeaders, redirectOptions, next) => next(),
  redirected: function (statusCode, location, req) {},
  timeout: 1000,
  maxBytes: 1048576,
  rejectUnauthorized: false,
  agent: null,
};

const example = async () => {
  const promise = wreck.request(method, uri, options);
  try {
    const res = await promise;
    const body = await Wreck.read(res, options);
    console.log(body.toString());
  }
  catch (err) {
    console.log(err)
  }
};

example()

The headers property has the request headers.

timeout has the request timeout in milliseconds.

maxBytes has the max size of a request.

agent has the user agent.

We can add the secureProtocol is the SSL method to use.

And the ciphers property can be added to choose the TLS cipher.

Then we use Wreck.read to read the response.

We can also call promise.req.abort() to abort the request.

Session Handling

We can handle session data with the @hapi/yar module.

For instance, we can write:

const Hapi = require('@hapi/hapi');

const server = Hapi.Server({ port: 3000 });
const options = {
  storeBlank: false,
  cookieOptions: {
    password: 'the-password-must-be-at-least-32-characters-long'
  }
};

const init = async () => {
  await server.register({
    plugin: require('@hapi/yar'),
    options
  });

  server.route({
    method: 'GET',
    path: '/',
    handler: (request, h) => {
      request.yar.set('example', { key: 'value' });
      return 'hello';
    }
  });

  server.route({
    method: 'GET',
    path: '/session',
    handler: (request, h) => {
      const example = request.yar.get('example');
      return example;
    }
  });

  await server.start();
  console.log('Server running at:', server.info.uri);
};
init();

to register the @hapi/yar plugin with:

await server.register({
  plugin: require('@hapi/yar'),
  options
});

The options property has the cookieOptions.password property to set the password for encrypting the session.

Then in the / route, we call request.yar.set method to add the key and value to the session.

And then in the /session route, we call request.yar.get method with the key we want to get to get the data.

We should get:

{
  "key": "value"
}

as the response.

Conclusion

We can use the @hapi/wreck module to make advanced HTTP requests.

And the @hapi/yar module lets us manage session data across our app.

Categories
Hapi

Server-Side Development with Hapi.js — Rendering Templates and HTTP Requests

Hapi.js is a small Node framework for developing back end web apps.

In this article, we’ll look at how to create back end apps with Hapi.js.

Rendering Templates

We can render Nunjucks templates in our Hapi app with the @hapi/vision plugin.

To do this, we write:

index.js

const Nunjucks = require('nunjucks');
const Hapi = require('@hapi/hapi');
const Vision = require('@hapi/vision');

const server = Hapi.Server({ port: 3000 });

const rootHandler = (request, h) => {
  return h.view('index', {
    title: request.server.version,
    message: 'Hello!'
  });
};

const init = async () => {
  await server.register(Vision);

server.views({
    engines: {
      html: {
        compile: (src, options) => {
          const template = Nunjucks.compile(src, options.environment);
          return (context) => {
            return template.render(context);
          };
        },
        prepare: (options, next) => {
          options.compileOptions.environment = Nunjucks.configure(options.path, { watch : false });
          return next();
        }
      }
    },
    relativeTo: __dirname,
    path: 'templates'
  });

server.route({ method: 'GET', path: '/', handler: rootHandler });

await server.start();
  console.log('Server running at:', server.info.uri);
};

init();

templates/index.html

{{message}}

We require the Nunjucks module.

Then we call Nunjucks.compile to with the src parameter to compile the source to the template.

Then we return a function that calls template.render with the context to render the template.

The context has the 2nd argument of h.view as its value.

The prepare method is called before the compile method and it sets the environment option set environment-specific settings.

index.html has the template content.

@hapi/vision also supports render Twig templates.

To do this, we write:

const Twig = require('twig');
const Hapi = require('@hapi/hapi');
const Vision = require('@hapi/vision');

const server = Hapi.Server({ port: 3000 });

const rootHandler = (request, h) => {
  return h.view('index', {
    title: request.server.version,
    message: 'Hello!'
  });
};

const init = async () => {
  await server.register(Vision);

  server.views({
    engines: {
      twig: {
        compile: (src, options) => {
          const template = Twig.twig({ id: options.filename, data: src });
          return (context) => {
            return template.render(context);
          };
        }
      }
    },
    relativeTo: __dirname,
    path: 'templates'
  });

  server.route({ method: 'GET', path: '/', handler: rootHandler });

  await server.start();
  console.log('Server running at:', server.info.uri);
};

init();

templates/index.twig

{{ message }}

We set the engines.twig.compile property to a function.

In it, we call Twig.twig to set the id and data of the template.

And we return a function that takes the context parameter and call template.render on it.

context has the object that we pass in as the 2nd argument.

index.twig is our Twig template.

Making HTTP Requests

We can make HTTP requests in our Node app with the @hapi/wreck module.

For instance, we can write:

const Wreck = require('@hapi/wreck');

const makeRequest = async () => {
  const { res, payload } = await Wreck.get('http://example.com');
  console.log(payload.toString());
};

try {
  makeRequest();
}
catch (ex) {
  console.error(ex);
}

We require the @hapi/wreck module.

Then we call Wreck.get to make a GET request.

Then we get the response with the payload property.

And we convert that to a string with the toString method.

Conclusion

We can render various kinds of templates with the @hapi/vision module.

Also, we can make HTTP requests with the @hapi/wreck module in our Node app.

Categories
Hapi

Server-Side Development with Hapi.js — Rendering Templates

Hapi.js is a small Node framework for developing back end web apps.

In this article, we’ll look at how to create back end apps with Hapi.js.

Rendering Templates

We can render templates with the @hapi/vision module.

For instance, we can write:

index.js

const Ejs = require('ejs');
const Hapi = require('@hapi/hapi');
const Vision = require('@hapi/vision');

const server = Hapi.Server({ port: 3000 });

const rootHandler = (request, h) => {
  return h.view('index', {
    title: request.server.version,
    message: 'Hello Ejs!'
  });
};

const init = async () => {
  await server.register(Vision);

  server.views({
    engines: { ejs: Ejs },
    relativeTo: __dirname,
    path: 'templates'
  });

  server.route({ method: 'GET', path: '/', handler: rootHandler });

  await server.start();
  console.log('Server running at:', server.info.uri);
};

init();

templates/index.ejs

<%= message %>

We have await server.register(Vision); to register the @hapi/vision plugin.

Then we call h.view to render the view.

title and message will be available in the template.

We set the engines.ejs property to the Ejs module to use it as our rendering engine.

We can use the Handlebars view engine by writing:

index.js

const Handlebars = require('handlebars');
const Hapi = require('@hapi/hapi');
const Vision = require('@hapi/vision');

const server = Hapi.Server({ port: 3000 });

const rootHandler = (request, h) => {
  return h.view('index', {
    title: request.server.version,
    message: 'Hello!'
  });
};

const init = async () => {
  await server.register(Vision);

  server.views({
    engines: { html: Handlebars },
    relativeTo: __dirname,
    path: 'templates'
  });

  server.route({ method: 'GET', path: '/', handler: rootHandler });

  await server.start();
  console.log('Server running at:', server.info.uri);
};

init();

templates/index.html

{{message}}

We change the engines property to { html: Handlebars } and require the handlebars module to use the Handlebars views engine.

To use the Pug view engine with Hapi, we write:

index.js

const Pug = require('pug');
const Hapi = require('@hapi/hapi');
const Vision = require('@hapi/vision');

const server = Hapi.Server({ port: 3000 });

const rootHandler = (request, h) => {
  return h.view('index', {
    title: request.server.version,
    message: 'Hello!'
  });
};

const init = async () => {
  await server.register(Vision);

  server.views({
    engines: { pug: Pug },
    relativeTo: __dirname,
    path: 'templates'
  });

  server.route({ method: 'GET', path: '/', handler: rootHandler });

  await server.start();
  console.log('Server running at:', server.info.uri);
};

init();

templates/index.pug

p #{message}

To use the Mustache view engine, we write:

const Mustache = require('mustache');
const Hapi = require('@hapi/hapi');
const Vision = require('@hapi/vision');

const server = Hapi.Server({ port: 3000 });

const rootHandler = (request, h) => {
  return h.view('index', {
    title: request.server.version,
    message: 'Hello!'
  });
};

const init = async () => {
  await server.register(Vision);

  server.views({
    engines: {
      html: {
        compile: (template) => {
          Mustache.parse(template);
          return (context) => {
            return Mustache.render(template, context);
          };
        }
      }
    },
    relativeTo: __dirname,
    path: 'templates'
  });

  server.route({ method: 'GET', path: '/', handler: rootHandler });

  await server.start();
  console.log('Server running at:', server.info.uri);
};

init();

templates/index.html :

{{message}}

We have the engines.html.compile method to compile the template into rendered HTML.

context has the data we pass into the 2nd argument of h.view .

Conclusion

We can render various kinds of templates into HTML with the @hapi/vision plugin.