Posts en Javascript

Primeros pasos con Vue.js

Hace algunos meses comencé a estudiar React.js, al ver que muy probablemente sería el próximo rey de los Frameworks y sería adoptado por WordPress.

Sin embargo, durante la gran batalla de Frameworks en WP, existía otro gran contendiente para ser convertido en el Framework oficial, éste se llamaba Vue.

¿Qué es Vue?

Como mencioné antes, Vue.js (pronunciado “view”) es un framework progresivo de Javascript diseñado para construir interfaces de usuario.

Aún cuando es un Framework bastante distinto a React, aún tienen algunas similitudes, ambos:

Para ver más formas en las que éstas librerías se parecen, puedes seguir el artículo en el sitio oficial de Vue.

¿Por qué utilizar Vue?

Si Vue y React son tan parecidos, ¿por qué querría utilizar Que en lugar de ir directo a React?

La respuesta más sencilla es: es tu decisión, no voy a decirte que cambies todo tu ecosistema para utilizar Vue simplemente porque es la librería de moda en la comunidad (o la segunda librería de moda). La realidad es que no hace daño probar nuevas herramientas, y así poder hacer una decisión informada sobre cuál es la que mejor se acomoda a tus necesidades.

Las mayores ventajas que tiene Vue a comparación de sus “rivales” (React, Angular, Ember, etc.) son:

Una vez más, al final es tu decisión, pero si quieres darle una oportunidad como lo hago yo, puedes acompañarme en mi curva de aprendizaje, y veamos lo básico:

¡Hola Mundo!

Para comenzar haré el típico ejercicio que todos nos sabemos de memoria, el ¡Hola mundo!, en versión Vue, así que comencemos, lo primero que necesitamos, es agregar Vue a nuestro sitio, lo cual podemos hacer facilmente agregando esta línea antes de nuestro </body>.

Cómo pueden ver es una estructura bastante simple, tenemos nuestro head, nuestro body, e incluimos nuestros script. Si se fijan bien, verán que también tenemos un div con un id de app, este pronto se convertirá en el contenedor de nuestra aplicación.

Lo que sigue ahora, es inicializar nuestra aplicación con Vue, en nuestro hola-mundo.vue.js

¡Y eso es todo! Así de fácil hemos creado nuestra instancia de Vue, como se puede ver, dentro de esa instancia hemos especificado el el (elemento) que servirá de contenedor para nuestra aplicación, y, como se puede ver, utilizamos selectores como los que siempre hemos utilizado desde los tiempos de jQuery, así, #app significa: /el elemento con un id de “app”/.

Pasando datos a la aplicación

Ahora, ya tenemos nuestra instancia de Vue activa, pero aún no es el /Hola Mundo/ que queremos, ¿Cómo solucionar esto? Podríamos simplemente escribir hola mundo dentro del contenedor, pero eso no es divertido, y no es la forma en que Vue lo hace, así que en lugar de eso, utilizaremos lo que se conoce como template syntax.

Primero modifiquemos nuestro HTML para que se vea así:

Si miran detenidamente, verán que hemos agregado un {{ mensaje }} dentro de nuestra aplicación, esto es conocido como /template syntax/ y sirve como un /placeholder/ que será sustituido por nuestro… mensaje.

Ahora simplemente le decimos a Vue cuál es ese mensaje que queremos que sustituya:

Dentro del ecosistema Vue, podemos agregar un objeto llamado data, Vue tomará todo lo que esté dentro de este objeto como datos de la aplicación, y mantendrá un ojo en ellos mientras la aplicación esté funcionando, esto significa que cualquier cambio que se haga en data.mensaje se verá reflejado en nuestro markup de manera instantánea.

¡Listo! SI ahora recargamos nuestra aplicación podremos ver ¡Hola Mundo! reflejado en ella, nuestra primer aplicación con Vue.

Creating a contact form with PHP and AngularJS

AngularJS seems to be the sensation these days, that’s why I decided to try and learn the basics of it. In fact, this very website is built using AngularJS, however, I did find a couple of issues while working with it, nothing serious, but one of them that surprised me was the contact form, since it took me a while to get the hang of it, I decided to share what I learned with a small tutorial.

The markup

For this tutorial I’m going to use a very basic setup, just a couple fields and a submit button, so lets get started.

<!DOCTYPE html>
<html>
    <head>
        <link rel="stylesheet" href="../assets/css/bootstrap.min.css">
        <title>Contact Form using AngularJS :: Mario Aguiar demo</title>
    </head>
    <body>
        <div>
            <form method="post" role="form" novalidate>
                <legend>Contact</legend>
                <fieldset>
                    <div>
                        <label for="name">Name:</label>
                        <input type="text" id="name" name="name" required>
                    </div>
                    <div>
                        <label for="email">Email:</label>
                        <input type="email" id="email" name="email" required>
                    </div>
                    <div>
                        <label for="messsage">Message:</label>
                        <textarea id="messsage" name="message" required></textarea>
                    </div>
                    <div>
                        <label for="honeypot">I promise I'm not a bot</label>
                        <input type="text" id="honeypot" name="honeypot">
                    </div>
                </fieldset>
                <button type="submit" name="submit" value="submit">Submit</button>
            </form>
        </div>
    </body>
</html>

Alright, I’d say that’s pretty simple, we have a form, with three input fields, nameemail, and message, we also have a fourth field used only to catch spam bots, this will be hidden from our human visitors and in fact we need it to be empty, we’ll hide it using css.

The jQuery way

Before I start with AngularJS, let’s have a look at how we would do this using jQuery, like in the good old days:

(function($) {
  var contactForm = {
    $form: $( 'form' ),
    init: function() {
      this.$form.on( 'submit', this.validateForm );
    },
    validateField: function( $field ) {
      if ( $field.val() === '' ) {
        return false;
      }
      if ( $field.attr( 'type' ) === 'email' ) {
        var emailRegex = /^\w+@[a-zA-Z_]+?\.[a-zA-Z]{2,3}$/;
        if ( ! emailRegex.test( $field.val() ) ) {
          return false;
        }
      }

      return true;
    },
    validateForm: function(e) {
      e.preventDefault();
      var $form = $(this);
      var fields = $form.find( 'input, textarea' ),
          hasError = false;
      fields.each(function() {
        var $field = $(this);
        if ( $field.attr('required') ) {
          if ( ! contactForm.validateField( $field ) ) {
            console.log('error');
            hasError = true;
          }
        }
      });
      if ( ! hasError ) {
        contactForm.processForm( $form );
      } else {
        contactForm.errorFn();
      }
    },
    processForm: function( $form ) {
      var formData = $form.serialize();
      $.ajax({
        url: 'processForm.php',
        type: 'POST',
        dataType: 'json',
        data: formData + '&submit=' + $form.find('button').val(),
      })
      .done( contactForm.successFn )
      .fail( contactForm.errorFn );
    },
    successFn: function(data) {
      if ( data.success === true ) {
        contactForm.$form.prepend('<p>Thanks for getting in touch!</p>');
      } else {
        contactForm.errorFn();
      }
    },
    errorFn: function() {
      contactForm.$form.prepend('<p>Something wrong happened!, please try again.</p>');
    }
  }

  contactForm.init();
})(jQuery);

I set up some basic code, we select the form, add a callback to the submit process, validate the fields, make the AJAX request and wait for the result, however simple it is, it takes a few lines of code.

Now let’s see how we could achieve the same using AngularJS.

The AngularJS way

First of all, one of the things I like the most about Angular is the way it integrates with our html code, did you notice how when using jQuery we did almost everything in our js file? the html code remained pretty much unchanged.

AngularJS requires a bit more configuration on the markup side, so let’s change it to this:

<!DOCTYPE html>
<html ng-app="contact">
    <head>
        <link rel="stylesheet" href="../assets/css/bootstrap.min.css">
        <title>Contact Form using AngularJS :: Mario Aguiar demo</title>
    </head>
    <body>
        <div>
            <form method="post" name="form" role="form" ng-controller="contactForm" ng-submit="form.$valid && sendMessage(input)" novalidate>
                <p ng-show="success">Thanks for getting in touch!</p>
                <p ng-show="error">Something wrong happened!, please try again.</p>
                <legend>Contact</legend>
                <fieldset>
                    <div>
                        <label for="name">Name:</label>
                        <input type="text" id="name" name="name" ng-model="input.name" required>
                    </div>
                    <div>
                        <label for="email">Email:</label>
                        <input type="email" id="email" name="email" ng-model="input.email" required>
                    </div>
                    <div>
                        <label for="messsage">Message:</label>
                        <textarea id="messsage" name="message" ng-model="input.message" required></textarea>
                    </div>
                    <div>
                        <label for="honeypot">I promise I'm not a bot</label>
                        <input type="text" id="honeypot" name="honeypot" ng-model="input.honeyPot">
                    </div>
                </fieldset>
                <button type="submit" name="submit" value="submit">Submit</button>
            </form>
        </div>
         <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.21/angular.min.js"></script>
         <script src="angularForm.js"></script>
    </body>
</html>

A few directives here and there and our markup is ready to be integrated with AngularJS, let’s break it down a bit:

Now let’s look at our js file:

(function(angular) {
  var app = angular.module("contact", []);

  app.controller("contactForm", ['$scope', '$http', function($scope, $http) {
    $scope.success = false;
    $scope.error = false;

    $scope.sendMessage = function( input ) {
      $http({
          method: 'POST',
          url: '/processForm.php',
          data: input,
          headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
      })
      .success( function(data) {
        if ( data.success ) {
          $scope.success = true;
        } else {
          $scope.error = true;
        }
      } );
    }
  }]);
})(angular);

So much smaller than our jQuery counterpart, this is because we don’t have any validation functions going on in here, why’s that? Simple, because AngularJS by default validates the form before submitting it. Not only that, but it automatically adds css classes for valid and invalid fields, thanks Angular.

So the only thing we need to do is make the AJAX request using the $http component, and keep an eye open for the result, let’s break it down:

The problem

At this point, our contact form should be working, but if you try to submit it, you’ll find that it always returns an error, why’s that?

AngularJS send the data as JSON.

To be fair, the problem is not caused by AngularJS. The problem really is that PHP does not unserialize this format. We were able to get around this with jQuery by making use of the .serialize() method, but AngularJS does not provide an equivalent for this, so what can we do?

The solution

There’s always a couple of ways to almost everything, and this is not the exception, here I give you two suggestions, you can choose the one that best fit your needs.

The “save me jQuery” way

We can use jQuery to get around this problem. Although AngularJS already has a tiny version of jQuery in it, it does not provide the method we need, this of course means that we need to include jQuery along AngularJS.  That doesn’t make me very happy, but it is a workaround. Thanks to the $.param method.

(function(angular) {
  var app = angular.module("contact", []);

  app.controller("contactForm", ['$scope', '$http', function($scope, $http) {
    $scope.success = false;
    $scope.error = false;

    $scope.sendMessage = function( input ) {
      input.submit = true;
      $http({
          method: 'POST',
          url: 'processForm.php',
          data: angular.element.param(input),
          headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
      })
      .success( function(data) {
        if ( data.success ) {
          $scope.success = true;
        } else {
          $scope.error = true;
        }
      } );
    }
  }]);
})(angular);

Inside AngularJS, jQuery is known as angular.element, so in order to use the method we need, we need to include jQuery in our HTML, and call it using angular.element. Now we test our form again, and it works!

The “fix it PHP” way

As I said before, this is more of a problem with PHP, so why not let it fix it? It’s actually really simple, we can’t access our $_POST variables the usual way, but we can access them using file_get_contents, so we’ll change our PHP to this:

<?php
    $response = array( 'success' => false );
    $formData = file_get_contents( 'php://input' );
    $data = json_decode( $formData );
    if ( $data->submit && empty( $data->honeypot ) ) {
        $name = $data->name;
        $email = $data->email;
        $message = $data->message;

        if ( $name != '' && $email != '' && $message != '' ) {
            $mailTo = 'example@mydomain.com';
            $subject = 'New contact form submission';
            $body  = 'From: ' . $name . "\n";
            $body .= 'Email: ' . $email . "\n";
            $body .= "Message:\n" . $message . "\n\n";

            $success = mail( $mailTo, $subject, $body );

            if ( $success ) {
                $response[ 'success' ] = true;
            }
        }
    }

    echo json_encode( $response );
?>

And there! Now we can access our data, and our AngularJS code wasn’t changed.

Conclusion

Forms in AngularJS can be a little tricky, or perhaps it’s time to move on to newer technologies, I’m pretty sure this kind of problem wouldn’t happen if I were to use NodeJS instead of PHP.