Escogí un tema algo complejo, pero a la vez fascinante, hablando de una posible evolución de como nos comunicamos del cliente al servidor, gracias a la bendita tecnología de AngularJS y las novedades de HTML 5, las formas en que diseñamos e interactuamos con el usuario es mucho mas dinámica, así que sin mas preámbulo comencemos...
Angular y el adios a los input (CONTENTEDITABLE)
Con la llegada de la nueva tecnologia de HTML 5, la forma en la que una persona se relaciona con el sitio es ahora mas dinamica, visual y divertida. Pero aun asi HTML necesita ayuda para las complejas tareas que queramos hacer con el, para eso llego nuestro fabuloso Angular, que de alguna manera a cambiado la forma en que se construyen las paginas HTML.
Durante mucho tiempo la única manera de que el usuario ingresara datos consiente mente, era gracias nuestros input, ya sea de tipo text, file, number etc.., pero sobre todo con la creciente necesidad de hacer web's mas amigables con el usuario, llega una función (atributo) para los elementos contenedores (Div, span, p...) puedan ser editados por los mismos usuarios. Bajo esa idea se concibió CONTENTEDITABLE un atributo que puede convertir un simple div estático y aburrido, en algo fascinante y maravilloso.
Una de estas bendiciones ha sido el atributo CONTENTEDITABLE, quiero hacer énfasis en que no estoy proponiendo en que las etiquetas con dicho atributo sean una mejor opción que los input (aunque con el tiempo pueda llegar a serlo),
el atributo no fue diseñado para remplazar dichas etiquetas, estas etiquetas no pueden formar un comportamiento con dichos input, ya que no existe un atributo action, method o cualquiera que remplaze a uno de este tipo, pero gracias a Angular (y Javascript en crudo) es posible que estos elementos se comporten de una manera mucho mas eficiente y SEXY en nuestro sitio.
Sin mas palabras, manos a la obra!
Como ya les he mencionado los CONTENTEDITABLE no son naturalmente dinámicos, hay que inyectarles vida con javascript, si puedes observar al definir un elemento con el ya mencionado atributo, no obtendrás, mas que un div vació (si esta vació), pero con una diferencia, cuando haces clic en el podrás observar que el navegador pone el foco en el (la barrita que parpadea) y se comporta como un input, pero con la diferencia de que este crece tanto horizontal, como vertical acoplándose al texto y el espacio disponible. (Una alternativa excelente para responsive design).
He aquí, como se define un elemento con CONTENTEDITABLE:
Código HTML:
<div contenteditable="true">Hola yo soy un div, en el que puedes borrar este texto</div> <p contenteditable="true">Yo también puedo ser editado</p> <span contenteditable="true">Yo también</span>
Código HTML:
<div contenteditable="true">Hola yo soy un div, en el que puedes borrar este texto. <div contenteditable="false">Yo no puedo ser editado, sin embargo puedo ser eliminado</div> </div>
[URL="http://web.ontuts.com/tutoriales/contenteditable-contenido-editable-en-html5/"] Ontuts [/URL]
Bien ahora, a la parte interesante, ¿como puedo convertir esto en un input?, facil con javascript, mejor dicho con Angular, y vamos a ver como obtener el contenido de un div y como convertirlo en elemento de doble-binding.
He aqui el codigo para extraer el codigo:
Código HTML:
<div contenteditable="true"> Hola mundo! </div> <script> //Previamente debes cargar angular, Claro! var element = angular.element(document.querySelector(".miclase")); var content = element.html(); </script>
como puedes ver, un contenteditable no puede tener doble binding, pero el mismo equipo de angular y el usuario de github akatov nos dan la solucion:
Código HTML:
<body ng-app="myapp" ng-controller="general"> <div contenteditable="true" ng-model="texto"> Hola mundo! </div> <p>{{texto}}</p> <script> //Previamente debes cargar angular, Claro! var app = angular.module('myapp', []); app.directive('contenteditable', ['$timeout', function($timeout) { return { restrict: 'A', require: '?ngModel', link: function(scope, element, attrs, ngModel) { // don't do anything unless this is actually bound to a model if (!ngModel) { return } // options var opts = {} angular.forEach([ 'stripBr', 'noLineBreaks', 'selectNonEditable', 'moveCaretToEndOnChange', 'stripTags' ], function(opt) { var o = attrs[opt] opts[opt] = o && o !== 'false' }) // view -> model element.bind('input', function(e) { scope.$apply(function() { var html, html2, rerender html = element.html() rerender = false if (opts.stripBr) { html = html.replace(/<br>$/, '') } if (opts.noLineBreaks) { html2 = html.replace(/<div>/g, '').replace(/<br>/g, '').replace(/<\/div>/g, '') if (html2 !== html) { rerender = true html = html2 } } if (opts.stripTags) { rerender = true html = html.replace(/<\S[^><]*>/g, '') } ngModel.$setViewValue(html) if (rerender) { ngModel.$render() } if (html === '') { // the cursor disappears if the contents is empty // so we need to refocus $timeout(function(){ element[0].blur() element[0].focus() }) } }) }) // model -> view var oldRender = ngModel.$render ngModel.$render = function() { var el, el2, range, sel if (!!oldRender) { oldRender() } var html = ngModel.$viewValue || '' if (opts.stripTags) { html = html.replace(/<\S[^><]*>/g, '') } element.html(html) if (opts.moveCaretToEndOnChange) { el = element[0] range = document.createRange() sel = window.getSelection() if (el.childNodes.length > 0) { el2 = el.childNodes[el.childNodes.length - 1] range.setStartAfter(el2) } else { range.setStartAfter(el) } range.collapse(true) sel.removeAllRanges() sel.addRange(range) } } if (opts.selectNonEditable) { element.bind('click', function(e) { var range, sel, target target = e.toElement if (target !== this && angular.element(target).attr('contenteditable') === 'false') { range = document.createRange() sel = window.getSelection() range.setStartBefore(target) range.setEndAfter(target) sel.removeAllRanges() sel.addRange(range) } }) } } }}]); app.controller('general', ['$scope', function($scope) { $scope.texto="<i>" + s.texto + "</i>" }]) </script> </body>
Bueno les agradezco mucho todo lo que me han enseñado y no olviden darle gracias a Angular.
Gracias!
Thank you very much Akatov!!!