javascript - example - ¿Cómo crear una directiva de envoltorio angularJs para un selector de fechas ui-bootstrap?
directivas personalizadas angular 5 (8)
Aquí está mi parche de mono de tu plunker,
http://plnkr.co/edit/9Up2QeHTpPvey6jd4ntJ?p=preview
Básicamente, lo que hice fue cambiar tu modelo, que es una fecha, para devolver una cadena con formato usando una directiva
.directive(''dateFormat'', function (dateFilter) {
return {
require:''^ngModel'',
restrict:''A'',
link:function (scope, elm, attrs, ctrl) {
ctrl.$parsers.unshift(function (viewValue) {
viewValue.toString = function() {
return dateFilter(this, attrs.dateFormat);
};
return viewValue;
});
}
};
});
Debe pasar el atributo de date-format
para su etiqueta de input
.
Si yo fuera tú, no iría tan lejos para hacer una directiva compleja. Simplemente agregaría un <datepicker>
anexado a su etiqueta de input
con el mismo modelo ng, y controlaría mostrar / ocultar con un botón. Puedes experimentar tu opción partiendo de mi plunker
Estoy usando la directiva ui.bootstrap.datepicker para mostrar algún campo de fecha. Sin embargo, la mayoría de las veces necesito la misma configuración: quiero que venga con una ventana emergente y un botón emergente, y también quiero nombres en alemán para los textos. Eso crea el mismo código para el botón y los textos y el formato una y otra vez, así que escribí mi propia directiva para evitar que me repitiera.
Aquí hay un plunkr con mi directiva. Sin embargo parece que lo estoy haciendo mal. Si elige una fecha con el selector de fecha utilizando el selector de fecha "Fecha 1" que no usa mi directiva, todo funciona bien. Esperaría lo mismo para la Fecha 2, pero en lugar de mostrar la fecha según la plantilla que proporcioné en el campo de entrada (o cualquier otro valor que esperaba), muestra la representación .toString()
del objeto de la fecha (por ejemplo, el Fri Apr 03 2015 00:00:00 GMT+0200 (CEST)
).
Aquí está mi directiva:
angular.module(''ui.bootstrap.demo'').directive(''myDatepicker'', function($compile) {
var controllerName = ''dateEditCtrl'';
return {
restrict: ''A'',
require: ''?ngModel'',
scope: true,
link: function(scope, element) {
var wrapper = angular.element(
''<div class="input-group">'' +
''<span class="input-group-btn">'' +
''<button type="button" class="btn btn-default" ng-click="'' + controllerName + ''.openPopup($event)"><i class="glyphicon glyphicon-calendar"></i></button>'' +
''</span>'' +
''</div>'');
function setAttributeIfNotExists(name, value) {
var oldValue = element.attr(name);
if (!angular.isDefined(oldValue) || oldValue === false) {
element.attr(name, value);
}
}
setAttributeIfNotExists(''type'', ''text'');
setAttributeIfNotExists(''is-open'', controllerName + ''.popupOpen'');
setAttributeIfNotExists(''datepicker-popup'', ''dd.MM.yyyy'');
setAttributeIfNotExists(''close-text'', ''Schließen'');
setAttributeIfNotExists(''clear-text'', ''Löschen'');
setAttributeIfNotExists(''current-text'', ''Heute'');
element.addClass(''form-control'');
element.removeAttr(''my-datepicker'');
element.after(wrapper);
wrapper.prepend(element);
$compile(wrapper)(scope);
scope.$on(''$destroy'', function () {
wrapper.after(element);
wrapper.remove();
});
},
controller: function() {
this.popupOpen = false;
this.openPopup = function($event) {
$event.preventDefault();
$event.stopPropagation();
this.popupOpen = true;
};
},
controllerAs: controllerName
};
});
Y así es como lo uso:
<input my-datepicker="" type="text" ng-model="container.two" id="myDP" />
(El concepto fue inspirado de esta respuesta )
Estoy usando angular 1.3 (el plunker está en 1.2 porque acabo de bifurcar el plunker de la documentación del selector de fechas de angular-ui-bootstrap ). Espero que esto no haga ninguna diferencia.
¿Por qué la salida de texto en mi entrada es incorrecta y cómo se hace correctamente?
Actualizar
Mientras tanto hice un pequeño progreso. Después de leer más sobre los detalles sobre compilación y enlace, en este plunkr uso la función de compilación en lugar de la función de enlace para hacer mi manipulación DOM. Todavía estoy un poco confundido por este extracto de los documentos:
Nota: la instancia de la plantilla y la instancia del enlace pueden ser objetos diferentes si la plantilla se ha clonado. Por esta razón, no es seguro hacer nada más que transformaciones de DOM que se apliquen a todos los nodos de DOM clonados dentro de la función de compilación. Específicamente, el registro del oyente DOM se debe hacer en una función de enlace en lugar de en una función de compilación.
Especialmente me pregunto qué significa "que se aplique a todos los nodos DOM clonados". Originalmente pensé que esto significa "que se aplican a todos los clones de la plantilla DOM", pero ese no parece ser el caso.
De todos modos: mi nueva versión de compilación funciona bien en cromo. En Firefox, primero tengo que seleccionar una fecha con un selector de fechas y, después, todo funciona bien (el problema con Firefox se resolvió solo si cambio undefined a null ( plunkr ) en el analizador de fechas del selector de fechas). Así que esto tampoco es lo último. Y, además, uso ng-model2
lugar de ng-model
que cambio el nombre durante la compilación. Si no hago esto todo sigue roto. Todavía no tengo idea de por qué.
Creo que la respuesta de @ omri-aharon es la mejor, pero me gustaría señalar algunas mejoras que no se han mencionado aquí:
Plunkr actualizado
Puede usar la configuración para configurar de manera uniforme sus opciones, como el formato y las opciones de texto, de la siguiente manera:
angular.module(''ui.bootstrap.demo'', [''ui.bootstrap''])
.config(function (datepickerConfig, datepickerPopupConfig) {
datepickerConfig.formatYear=''yy'';
datepickerConfig.startingDay = 1;
datepickerConfig.showWeeks = false;
datepickerPopupConfig.datepickerPopup = "shortDate";
datepickerPopupConfig.currentText = "Heute";
datepickerPopupConfig.clearText = "Löschen";
datepickerPopupConfig.closeText = "Schließen";
});
Me parece que esto es más claro y más fácil de actualizar. Esto también le permite simplificar enormemente la directiva, la plantilla y el marcado.
Directiva personalizada
angular.module(''ui.bootstrap.demo'').directive(''myDatepicker'', function() {
return {
restrict: ''E'',
scope: {
model: "=",
myid: "@"
},
templateUrl: ''datepicker-template.html'',
link: function(scope, element) {
scope.popupOpen = false;
scope.openPopup = function($event) {
$event.preventDefault();
$event.stopPropagation();
scope.popupOpen = true;
};
scope.open = function($event) {
$event.preventDefault();
$event.stopPropagation();
scope.opened = true;
};
}
};
});
Modelo
<div class="row">
<div class="col-md-6">
<p class="input-group">
<input type="text" class="form-control" id="{{myid}}" datepicker-popup ng-model="model" is-open="opened" ng-required="true" />
<span class="input-group-btn">
<button type="button" class="btn btn-default" ng-click="open($event)"><i class="glyphicon glyphicon-calendar"></i></button>
</span>
</p>
</div>
</div>
Cómo usarlo
<my-datepicker model="some.model" myid="someid"></my-datepicker>
Además, si desea imponer el uso de un formato de configuración regional alemana, puede agregar angular-locale_de.js. Esto garantiza la uniformidad en el uso de constantes de fecha como ''shortDate''
y obliga al uso de los nombres de mes y día alemanes.
He intentado hacer que esto funcione (algo así como pirateo), lo que podría no ser exactamente lo que quieres, solo algunas ideas aproximadas Así que todavía necesitas pellizcarlo un poco. El plunker es:
`http://plnkr.co/edit/aNiL2wFz4S0WPti3w1VG?p=preview''
Básicamente, cambié el ámbito de la directiva, y también agregué watch for scope var container.two.
Para ser honesto, no estoy muy seguro de por qué se causó y qué está causando que su fecha sea "toString-ed" antes de mostrarla en la entrada.
Sin embargo, sí encontré lugares para reestructurar su directiva y eliminé muchos códigos innecesarios, como el servicio de $compile
, los cambios de atributos, la herencia del alcance, los require
de la directiva, etc. Utilicé un alcance aislado, ya que no creo que todas las directivas el uso debe conocer el ámbito principal, ya que esto podría causar errores viciosos en el futuro. Esta es mi directiva modificada:
angular.module(''ui.bootstrap.demo'').directive(''myDatepicker'', function() {
return {
restrict: ''A'',
scope: {
model: "=",
format: "@",
options: "=datepickerOptions",
myid: "@"
},
templateUrl: ''datepicker-template.html'',
link: function(scope, element) {
scope.popupOpen = false;
scope.openPopup = function($event) {
$event.preventDefault();
$event.stopPropagation();
scope.popupOpen = true;
};
scope.open = function($event) {
$event.preventDefault();
$event.stopPropagation();
scope.opened = true;
};
}
};
});
Y tu uso de HTML se convierte en:
<div my-datepicker model="container.two"
datepicker-options="dateOptions"
format="{{format}}"
myid="myDP">
</div>
Editar : Agregó el id
como un parámetro a la directiva. Plunker ha sido actualizado.
Si alguien está interesado en una implementación de Typescript (basada libremente en el código de @ jme11):
Directiva:
''use strict'';
export class DatePickerDirective implements angular.IDirective {
restrict = ''E'';
scope={
model: "=",
myid: "@"
};
template = require(''../../templates/datepicker.tpl.html'');
link = function (scope, element) {
scope.altInputFormats = [''M!/d!/yyyy'', ''yyyy-M!-d!''];
scope.popupOpen = false;
scope.openPopup = function ($event) {
$event.preventDefault();
$event.stopPropagation();
scope.popupOpen = true;
};
scope.open = function ($event) {
$event.preventDefault();
$event.stopPropagation();
scope.opened = true;
};
};
public static Factory() : angular.IDirectiveFactory {
return () => new DatePickerDirective();
}
}
angular.module(''...'').directive(''datepicker'', DatePickerDirective.Factory())
Modelo:
<p class="input-group">
<input type="text" class="form-control" id="{{myid}}"
uib-datepicker-popup="MM/dd/yyyy" model-view-value="true"
ng-model="model" ng-model-options="{ getterSetter: true, updateOn: ''blur'' }"
close-text="Close" alt-input-formats="altInputFormats"
is-open="opened" ng-required="true"/><span class="input-group-btn"><button type="button" class="btn btn-default" ng-click="open($event)"><i
class="glyphicon glyphicon-calendar"></i></button>
</span>
</p>
Uso:
<datepicker model="vm.FinishDate" myid="txtFinishDate"></datepicker>
Si crear la directiva es conveniente para agregar los atributos, puede tener las 2 directivas en la entrada original:
<input my-datepicker="" datepicker-popup="{{ format }}" type="text" ng-model="container.two" id="myDP" />
Luego, evite los múltiples ámbitos aislados al cambiar scope: true
a scope: false
en la directiva myDatepicker
.
Esto funciona y creo que es preferible crear una directiva adicional para cambiar la fecha de entrada al formato deseado:
http://plnkr.co/edit/23QJ0tjPy4zN16Sa7svB?p=preview
No tengo idea de por qué agregar el atributo desde dentro de la directiva. No tengo idea, es casi como si tuviera 2 recolectores de fecha en la misma entrada, uno con su formato y otro con el valor predeterminado que se aplica después.
Su directiva funcionará cuando agregue estas 2 líneas a su definición de directiva:
return {
priority: 1,
terminal: true,
...
}
Esto tiene que ver con el orden en que se ejecutan las directivas.
Así que en tu código
<input my-datepicker="" type="text" ng-model="container.two" id="myDP" />
Hay dos directivas: ngModel
y myDatepicker
. Con prioridad, puede hacer que su propia directiva se ejecute antes que ngModel.
Utilice moment.js con el componente datepicker de ui-bootstrap para crear la directiva y proporcionar un conjunto completo de patrones para los formatos de fecha y hora. Puede aceptar cualquier formato de tiempo dentro del alcance aislado.