Magic CRUD Angular – Huyendo de escribir código JavaScript con mgCrud (Parte I) – Porqué?

(English version)

No me gusta Javascript … es lo que tiene. Me gustan los lenguajes fuertemente tipados y me gustan los compiladores y los editores que te avisan de las cosas y amo profundamente Resharper.
Dicho esto, a diario trabajo con una aplicación de muuuuchas pantallas, muchas páginas, el 75% escritas en ASP.NET WebForms, las cuales odio y el otro 25%, que crece día a día en detrimento de WebForms, están en MVC, con TypeScript. Como estandar tenemos dos vistas y un fichero .ts por cada entidad, usamos MVVM con knockoutjs, por lo que tengo un par de viewmodels por cada entidad en el fichero TypeScript. Puede ser que en total la aplicación tenga unas 200 páginas y por lo tanto he tenido que tirar de RequireJS para poder cargar los scripts.
Pero lo curioso es que en todos los ficheros de script se repite siempre lo mismo, por supuesto uso herencia para no reescribirlo todo pero…, son todos iguales, solo cambian los campos de las entidades y el nombre de las clases. Lo que me llevaba a pensar diariamente … ¿en realidad esto es necesario?

Hace poco, estuve aprendiendo AngularJS en un curso con el amigo Pedro Hurtado (@_PedroHurtado), sabía ya algo pero me ayudó darle un buen repaso y aprender from the scratch 🙂 En la parte final del curso estuvimos viendo por encima una directiva que se había currado Pedro junto con Xavier Jorge Cerdá (@XaviPaper), se trata de mgAjax, la cual es la principal baza del projecto mgCrud de GitHub.
Básicamente esta directiva te abstrae de la negociación con el servidor, te evita tener que montar la llamada ajax tirarla y esperar su respuesta para poner los datos en tu scope, y poder acceder a ellas con binding desde la vista.

Imagina tener esto:

<div ng-controller="getCountController">
	<span>Have {{count}} comments without read</span>
</div>
function controller($scope, $http) {
	$http.get("http://server/user/getunreadcommentscount")
		.success(function (data) {
			$scope.count = data;
		});
}
myModule.controller("getCountController", controller);

Es una tontería, es una vista que pinta en pantalla un dato que viene del servidor, y has tenido que poner tu código javascript ahí para que llamar al servidor y luego copiar la respuesta de la llamada al scope para que funcione el binding de la vista.

Si todo fuera eso, recoger un valor de una URL y ponerlo en el DOM pues podríamos hacer código genérico, por ejemplo podríamos crearnos una directiva llamada myAjaxGet que se implementara de este modo:

function myAjaxGet() {
	return {
		restrict: 'E',			
		controller: function ($scope, $http, $attrs) {
			$http.get($attrs.path).success(function (data) {
				$scope.value = data;
			});
		}
	}
}
myModule.directive('myAjaxGet', myAjaxGet);

Podríamos escribir esto:

<my-ajax-get path="http://server/user/getunreadcommentscount">
	<span>Have {{value}} comments without read</span>
</my-ajax-get>

Incluso también esto:

<my-ajax-get path="http://server/user/getunreadcomments">
	<ul>
		<li ng-repeat="comment in value">
			<div>Unread comment from {{comment.writer}}</div>
			<div>{{comment.text}}</div>
		</li>
		<hr/>
	</ul>
</my-ajax-get>

El primer ejemplo es tonto, un solo valor, pero un GET al servidor es un GET y lo mismo pides un número que una lista, al final todo depende de como trates el resultado que esperas. Nuestro myAjaxGet nos puede valer para leer un valor del servidor, para mostrar un grid con una colección de objetos o incluso para mostrar la ficha de un cliente o cualquier entidad. Con un poco más de elaboración tendríamos esos temas resueltos, pero claro en una aplicación todo son llamadas GET.

Y si queremos guardar algo en el servidor? podríamos hacernos un myAjaxPost, la directiva podría ser algo así:

function myAjaxGet() {
	return {
		restrict: 'E',			
		controller: function ($scope, $http, $attrs) {
			$http.get($attrs.path).success(function (data) {
				$scope.value = data;
			});
		}
	}
}
myModule.directive('myAjaxGet', myAjaxGet);

Podríamos escribir esto:

function myAjaxPost() {
	return {
		restrict: 'E',			
		controller: function ($scope, $http, $attrs, $window) {
			$scope.accept = function() {
				$http.post($attrs.path, $scope.value).success(function () {
					$window.history.back();
				});				
			};
		}
	}
}
myModule.directive('myAjaxPost', myAjaxPost);

Con eso ya podríamos hacer, por ejemplo, una vista para guardar un comentario en un foro:

<my-ajax-post path="http://server/comments/">
	<input type="text" ng-model="value.userName"></input>
	<input type="text" ng-model="value.text"></input>
	<input type="button" ng-click="accept()">Save</input>	
</my-ajax-post>

Ya tenemos la lectura de datos y también la creación, que hay de la actualización? podríamos hacernos un myAjaxPut:

function myAjaxPut() {
	return {
		restrict: 'E',			
		controller: function ($scope, $http, $attrs, $window) {
			$scope.accept = function() {
				$http.put($attrs.path, $scope.value).success(function () {
					$window.history.back();
				});				
			};
		}
	}
}
myModule.directive('myAjaxPut', myAjaxPut);

Con esto actualizaríamos un comentario:

<my-ajax-put path="http://server/comments/1">
	<input type="text" ng-model="value.userName"></input>
	<input type="text" ng-model="value.text"></input>
	<input type="button" ng-click="accept()">Save</input>	
</my-ajax-put>

No? bueno, vale, “sería bueno” poder ver lo que estamos actualizando, pero como tenemos nuestro myAjaxGet lo tenemos hecho, que pasaría si ponemos esto:

<my-ajax-get path="http://server/comments/1">
	<my-ajax-put path="http://server/comments/1">
		<input type="text" ng-model="value.userName"></input>
		<input type="text" ng-model="value.text"></input>
		<input type="button" ng-click="accept()">Save</input>	
	</my-ajax-put>
</my-ajax-put>

Con lo que tenemos myAjaxGet leería el comentario del servidor y lo pondría en $scope.value, luego myAjaxPut en el accept() lo enviaría al servidor. Bueno, no, perdón, están en scopes distintos, como el put siempre lo vamos a usar dentro de un get, lo que haríamos sería enviar en el put siempre el value del scope padre, quedaría así:

function myAjaxPut() {
	return {
		restrict: 'E',			
		controller: function ($scope, $http, $attrs, $window) {
			$scope.accept = function() {
				$http.put($attrs.path, $scope.$parent.value).success(function () {
					$window.history.back();
				});				
			};
		}
	}
}
myModule.directive('myAjaxPut', myAjaxPut);

Y así:

<my-ajax-get path="http://server/comments/1">
	<input type="text" ng-model="value.userName"></input>
	<input type="text" ng-model="value.text"></input>
	<my-ajax-put path="http://server/comments/1">
		<input type="button" ng-click="accept()">Save</input>	
	</my-ajax-put>
</my-ajax-put>

Ahora si que sí.

Pero siguiendo en nuestro empeño por escribir poco código podríamos darnos cuenta de que las tres directivas se parecen mucho, solo cambia en el verbo que usamos en la petición al servidor y en el momento en el cual se realiza esta misma. Por lo tanto, ¿porqué no hacer una directiva myAjax que englobe toda la funcionalidad? y entonces poder hacer esto:

<my-ajax verb="post" path="http://server/comments/">
	<input type="text" ng-model="value.userName"></input>
	<input type="text" ng-model="value.text"></input>
	<input type="button" ng-click="accept()">Save</input>
</my-ajax>

Y esto:

<my-ajax verb="get" path="http://server/comments/1">
	<input type="text" ng-model="value.userName"></input>
	<input type="text" ng-model="value.text"></input>
	<my-ajax verb="put" path="http://server/comments/1">
		<input type="button" ng-click="accept()">Save</input>			
	</my-ajax>
</my-ajax>

La directiva sería fácil de hacer:

function myAjax() {
	return {
		restrict: 'E',
		controller: function ($scope, $http, $attrs, $window) {
			var verb = $attrs.verb || 'get';
			$scope.doIt = function() {
				$http[$attr.verb]($attrs.path, verb === 'put' ? $scope.$parent.value : $scope.value).success(function (data) {
					if (verb === 'get')
						$scope.value = data;
					else
						$window.history.back();
				});
			};
			$scope.accept = doIt;
 
			if (verb === 'get'){
				$scope.doIt();
			}
		}
	}
}
myModule.directive('myAjax', myAjax);

Con una sola directiva nos hemos cargado gran parte del código de una pequeña aplicación. Por supuesto habría mucho más que hacer, pero si en tan poco tiempo hemos podido hacer nuestra directiva myAjax seguro que pensándolo bien y con la experiencia de haber realizado ya aplicaciones grandes se podría llegar a algo mucho más completo.

De esto trata mgCrud, se basa en una directiva, mgAjax, instrumentada para contemplar el mayor número de casos que se nos presentan en nuestras aplicaciones para conseguir realizar las vistas y no escribir código para cada una de ellas. También es totalmente escalable y se pueden predefinir nuevos comportamientos a partir de los que ya tiene o desde cero.

En la segunda parte veremos como usar mgCrud y algunas de sus posibilidades.

One comment

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

To create code blocks or other preformatted text, indent by four spaces:

    This will be displayed in a monospaced font. The first four 
    spaces will be stripped off, but all other whitespace
    will be preserved.
    
    Markdown is turned off in code blocks:
     [This is not a link](http://example.com)

To create not a block, but an inline code span, use backticks:

Here is some inline `code`.

For more help see http://daringfireball.net/projects/markdown/syntax

Demuestra que no eres un bot *