You are currently browsing the category archive for the ‘sw.dev’ category.

Validar

Implementar una función valid para que lance un error si se llama sin argumentos o un argumento no está definido. De lo contrario debe devolver el valor dado.

Remover una propiedad

Escribir una función removeProperty que recibe un objeto y el nombre de una propiedad realizando lo siguiente: si el objeto obj tiene una propiedad prop, la función elimina la propiedad del objeto y devuelve true; en los demás casos, devuelve false.

function removeProperty(obj, prop) {
  return null;
}

Fecha

Codear una función que convierta la fecha formateada como “M/D/AAAA” en un formato requerido “AAAAMMDD”. El parámetro “date” y el valor de retorno son string’s.

Por ejemplo, “31/12/2014” a “20141231”.

function format(date) {
  // M/D/YYYY => YYYYMMDD
}

console.log(format('12/31/2014'));

Galería de imágenes

El siguiente ejemplo es una galería de dos imágenes con botones de eliminación correspondientes:

class="image"> src="https://goo.gl/2oZU2S" alt="First"> class="remove">X
class="image"> src="https://goo.gl/tniGAc" alt="Second"> class="remove">X

Implementar una función registerClickHandler: cuando se hace clic en el botón de la clase remove, su elemento *

  • principal debe ser eliminado de la galería.

Por ejemplo, después de que la primera imagen haya sido eliminada de la galería anterior, su código HTML debería verse así:

class="image"> src="https://goo.gl/tniGAc" alt="Second"> class="remove">X
function registerClickHandler () {
  // implementar el clic para el botón 'remove'
}

Cerraduras

Corregir los errores en la función registerHandlers. Un alert debe mostrar el índice del en lugar de seguir el enlace.

Por ejemplo, en el documento de abajo, la alerta debe mostrar “2” cuando se hace clic en Google.


  My web search engines:
<a href="//www.yahoo.com">Yahoo!
<a href="//www.altavista.com">AltaVista
<a href="//www.google.com">Google
function registerHandlers() {
  var as = document.getElementsByTagName('a');
  for (var i = 0; i < as.length; i++) {
    as[i].onclick = function() {
      alert(i);
      return false;
    }
  }
}

Bucle

La función appendChildren debería agregar un nuevo div a cada div existente.

Por ejemplo, después de ejecutar appendChildren en

id="a">
id="b">

debería tomar la siguiente forma:

id="a">
id="b">

Por alguna razón el siguiente código tiene un bucle infinito:

function appendChildren() {
  var allDivs = document.getElementsByTagName('div');
  for (var i = 0; i < allDivs.length; i++) {
    var newDiv = document.createElement('div');
    allDivs[i].appendChild(newDiv);
  }
}
Anuncios

Usar el objeto $rootScope es como usar variables globales en Javascript, excepto que este vive en el sistema de inyección de dependencias de AngularJS. Aunque esto sólo lo hace un tanto menos problemático: hace que el estado de un programa sea impredecible.

Imaginemos que tener un par de objetos que utilizan la misma variable global. Suponiendo incluso que no se usa ninguna función aleatoria en los módulos, entonces, el resultado de cualquier método puede predecirse ,y por lo tanto probarse, cuando se conoce el estado desde el cual se ejecuta.

Sin embargo, si un método de alguno de los objetos desencadena un efecto secundario que cambia el valor del estado global, no se sabrá cuál es el estado inicial cuando se ejecuta un método en el otro objeto. Ahora ya no se puede predecir el resultado del método y, por lo tanto, no puede probarse.

Esto no puede parecer tan serio, pero ser incapaz de hace una prueba unitaria dificulta probar la corrección de los objetos. Supongamos que tenemos una clase que asigna valores a una estructura de datos global y otra clase que consume los datos de esa estructura cambiando su estado o destruyéndola en el proceso. Si la clase que procesa ejecuta un método previo a que se complete la asignación, el resultado es que este procesador probablemente tendrá datos incompletos que procesar y la estructura de datos en la que estaba trabajando el asignador podría estar dañada o destruida. El comportamiento es impredecible y dará lugar a pérdida de información.

Además, los estados globales dificultan la legibilidad del código. Hay una dependencia externa que no se introduce explícitamente en el código. Así, cualquier responsable de mantener el código tendrá que averiguar de dónde vino determinado valor.

En cuanto a alternativas, es imposible no tener ningún estado global en absoluto, pero es posible restringirlos a un sólo objeto que engloba a todos los demás y que nunca debe referenciarse en base a las reglas de alcance del lenguaje. Si un objeto en particular necesita un estado particular, debería pedirlo explícitamente haciéndolo para como un argumento a su constructor o por un método setter. Esto se conoce como inyección de dependencia.

Puede parecer tonto pasar un estado al que ya se puede acceder mediante as reglas de alcance del lenguaje, pero tiene sus ventajas. Si alguien mira el código de forma aislada, queda claro qué necesita y de dónde viene. También tiene beneficios respecto a la flexibilidad del código mejorando las oportunidades de reuso. Si el estado se transmite y los cambios en el estado son locales al bloque de código, podemos pasar a cualquier estado que deseemos (con el tipo de dato correcto) y hacer que nuestro código lo procese. Con este estilo se tiende a tener la apariencia de una colección de componentes ligeramente asociados que pueden intercambiarse fácilmente. Al código de un módulo no debería importarle de dónde proviene el estado, sino cómo procesarlo. Si pasamos el estado a un bloque de código, este bloque puede existir de forma aislada, lo que no sucede si confiamos en la existencia de un estado global.

Resumen

  • Los errores del estado global mutable. Los errores puede ser causados por el cambio en cualquier lugar del programa que son difíciles de rastrear.

  • Pobreza de las pruebas. Cualquier prueba necesitará configurar el estado global. Esto complica escribir las pruebas, por ejemplo, las credenciales para acceder a una base de datos de toda la aplicación.

  • Poco flexible. Cuando una parte del código requiere determinado valor en el estado global, pero otra parte requiere otro valor. Se ve una desagradable refactorización.

  • Funciones impuras. Las funciones puras son aquellas cuyo resultado depende únicamente de sus argumentos, las mismas que son más sencillas de analizar y componer para crear grandes aplicaciones. Un estado global hace que sea impura.

  • Legibilidad. Un código con una cantidad considerable de variables globales se vuelve difícil de comprender haciendo incluso intratable su mantenimiento.

  • Problemas de concurrencia. El estado global requiere algún tipo de bloqueo, lo que es muy complicado y costoso de mantener.

  • Rendimiento. Si el mismo estado es usado globalmente por varios subprocesos, provoca la contención de caché y lentitud del proceso.

Alternativas

  • Parámetro de función. Tener una estructura de datos como contexto de la función. Nos ayuda a mantenerla pura y puede ser usado para pasar ese contexto a otras funciones que envuelvan toda la información relevante.

  • Inyección de dependencias. Como en el caso de los parámetros. Debemos tener cuidado si las dependencias son mutables (que pueden causar el mismo problema que las variables globales).

  • Estado global inmutable. Una constante que no debe ser mutable en ningún momento posterior.

  • Singletons inmutables. Similar al anterior, pero se les tiene que definir un momento de instanciación. Son útiles cuando grandes estructuras requieres un precálculo costoso. En su versión mutable son tan nocivos con el estado global mutable.

  • Enlazado dinámico. No se aplica a todos los lenguajes. Aisla las variables de determinado proceso haciendo que se actúe sólo sobre el hilo asignado. Es útil cuando se manejan múltiples subprocesos de transacciones independientes.

Llevo un tiempo pensando en escribir sobre las arquitecturas frontend. Estaba en aquello hasta que recordé que hace ya algunos años atrás, cuando empezaba esto de desacoplar todo el código del cliente, las herramientas se hacían publicidad como el framework X de MVC en el cliente. Comentando este hecho, he visto que hay poca comprensión acerca de la implementación del patrón. Así que eso va esta publicación.

Inicialmente pensé en algo un tanto más elaborado. Así que gracias al amigo Jefferson, el ejemplo que veremos aquí será usado para un próximo artículo en el que será refactorizado.

El patrón

El patrón Model-View-Controller nació en la época Smalltalk. Buscaba desacoplar la interfaz gráfica de una aplicación existente. Esto fue tan importante que se expandió a otros lenguajes orientados a objectos.

El modelo

En MVC, el modelo es el código que realiza alguna acción. Su construcción no conlleva a pensar en cómo ha de lucir cuando la información sea presentada al usuario. Es una interfaz puramente funcional, lo que quiere decir que tiene métodos públicos que pueden ser usado para realizar acciones u obtener información acerca del estado del modelo, así como métodos que pueden cambiar su estado.

No obstante, el modelo debe permitir registrar las vistas que serán notificadas acerca de la actividad del modelo al cambiar de estado.

Varias vistas

Un modelo puede tener múltiples vistas. Una vista provee una interfaz gráfica de usuario (GUI) que obtiene los valores del modelo para mostrarlos mediante la obtención de los datos del mismo.

Cuando un usuario manipula la vista de un modelo, la vista informa al controlador del cambio deseado.

Varios controladores

Las vistas se asocian con los controladores par actualizar al modelo cuando el usuario interactúa con la vista asociada. El controlador puede invocar los métodos del modelo para cambiar su estado. Además, el modelo notificará a toda las vistas registradas que este ha cambiado y que ellas deben actualizar los valores que se muestran al usuario.

Cómo funciona

Empezamos con un modelo capaz de comunicar sus cambios de estado. Por ejemplo, un termómetro,

class TemperatureModel : public QObject...
public:
  double temperature();
  void temperature(double temperature);
signals:
  void changed(double temperature);
...

(Esto es comúnmente llamado Observer Pattern.) Establecemos métodos para obtener la información del estado actual y actualizarlo. Cada vez que se actualiza el estado, se notifica a cada vista registrada emitiendo el mensaje changed().

Así, podemos crear más de una vista que implementen un método que actualicen apropiadamente en los valores en función de los cambios en el modelo y en la propia vista.

La razón por la que se hereda de QObject es porque este nos provee de la infraestructura de notificación y registro de observadores. De esta manera, podemos concentrarnos en la funcionalidad de nuestra aplicación.

Veamos un ejemplo

Ahora veamos cómo trabajan tres vistas distintas de nuestro modelo de temperatura.

 mvc

Cuando iniciamos el programa MVC, creamos y configuramos a los protagonistas de nuestra historia:

TemperatureModel model;

TemperatureView temperatureView;
SliderView sliderView;
DrawingView drawingView;

TemperatureController temperatureController(model, temperatureView);
SliderController sliderController(model, sliderView);
DrawingController drawingController(model, drawingView);

temperatureView.show();
drawingView.show();
sliderView.show();

model.temperature(0);
...

Hay un sólo modelo que mantiene las funcionalidades de nuestra aplicación. Mediante tres controladores configuramos las GUIs de temperaturas para mostrar un formulario, un control horizontal deslizante y un dibujo de un termómetro. Finalmente, inicializamos el modelo.

Las vistas tienen la responsabilidad de presentar la información del modelo. Por ejemplo, la primera vista construye un formulario para ello.

QLabel *label = new QLabel("Temperature");
layout->addWidget(label);

QLineEdit *lineEdit = new QLineEdit();
layout->addWidget(lineEdit);

QPushButton *lowerButton = new QPushButton("-");
QPushButton *raiseButton = new QPushButton("+");

QHBoxLayout *buttonsLayout = new QHBoxLayout();
buttonsLayout->addWidget(raiseButton);
buttonsLayout->addWidget(lowerButton);

layout->addLayout(buttonsLayout);
setGeometry(90, 140, 188, 95);

La segunda es la configuración de un Slider.

setMaximum(100);
setMinimum(0);
setGeometry(90, 80, 270, 30);

Y la última es un canvas con un gráfico que se asemeja a un termómetro. 🙂

p.setBrush(QBrush(Qt::yellow));
p.drawEllipse(10, 100, 50, 50);
p.drawLine(20, 105, 20, 0);
p.drawLine(50, 105, 50, 0);
p.setPen(Qt::PenStyle(Qt::NoPen));
p.drawRect(21, 100 - _temperature, 29, 6 + _temperature);

Por otro lado, los controladores sólo tienen la función de conectar los eventos entre las vistas y el modelo, así como manejar la interacción en el intercambio de datos.

connect(&view, &View::changed, this, &Controller::update);
connect(&view, &View::lower, this, &Controller::down);
connect(&view, &View::raise, this, &Controller::up);

connect(&model, &Model::changed, this, &Controller::change);
connect(this, &Controller::changed, &view, &View::change);

Resumen

Hemos visto cómo interactúan las vistas y sus controladores con el modelo. Este mismo principio ha sido fuente de mucho debate en las arquitecturas del lado cliente. Por eso consideré importante reforzarlo antes de iniciar con otras dos publicaciones: uno sobre cómo refactorizar este ejemplo y la otra sobre las arquitecturas front-end. Dejo el código completo para ser compilado en un repositorio. Saludos a todos. 😉

Antes veíamos una manera de comunicar dos componentes. Ahora presentaremos una forma de validar los campos de un formulario.

Añadimos la directiva de validación al módulo de EvaluationComponentsModule:

const AmountValidatorDirective: ng.IDirective = () => ({
  require: 'ngModel',
  link(scope, elm, attrs, ctrl) {
    ctrl.$validators['amount'] = (modelValue, viewValue) => {
      if (ctrl.$isEmpty(modelValue)) return true;
      return modelValue < 5;
    };
  },
});

Sólo queda usarla en el template de SurveyComponent para empezar a validar que solamente se ingresen números menores que cinco. El ejemplo completo se muestra aquí. 🙂

Como comentaba en una publicación anterior, ahora veremos una forma de comunicación entre dos componentes de AngularJS que forman parte de otro componente intermediario.

Tenemos un componente de evaluación

const EvaluationComponent: ng.IComponentOptions = {
  controller: EvaluationController,
  template: `
  <survey data="$ctrl.data"></survey>
  <chart data="$ctrl.data"></chart>
  `,
}

y su controlador

class EvaluationController implements ng.IController {
  data: EvaluationData;

  constructor() {
    this.data = {
      amount: 0,
    };
  }
}

Notemos que este componente se sirve de otros dos para poder ingresar datos y luego mostrarlos respectivamente. Así pues, la manera más sencilla de pasar información entre componentes es tener un padre común, EvaluationComponent, que inicie este contenedor de datos y lo comparta a su jerarquía de subcomponentes, como es el caso de ChartComponent y SurveyComponent.

const ChartComponent: ng.IComponentOptions = {
  bindings: {
    data: '=',
  },
  controller: ChartController,
  template: `
  <label>chart: {{ $ctrl.data.amount }}</label>
  <button ng-click="$ctrl.next()">Next</button>
  `,
};
const SurveyComponent: ng.IComponentOptions = {
  bindings: {
    data: '=',
  },
  template: `
  survey:
  <input
    ng-model="$ctrl.data.amount"
    value="$ctrl.data.amount"
  >
  `,
};

En el caso de SurveyComponent, el monto se actualiza desde el formulario, mientras que ChartComponent lanza un evento clic

class ChartController {
  data: EvaluationData;

  next() {
    this.data.amount++;
  }
}

El ejemplo completo puede verse aquí.

Categorías