Microsoft graph
Seguimos con la cuarta parte del post-tutorial.
La tercera parte la podemos encontrar en este enlace Microsoft graph - Eventos calendarios Outlook III
En el anterior post dejamos definidas las entidades, cómo las vamos a gestionar y varias herramientas auxiliares.
Hemos ido preparando el terreno para poder mostrar nuestros datos de manera gráfica.
Para ello utilizaremos un addin que además de mostrar los eventos, nos permitirá interactuar con ellos de una manera muy sencilla e intuitiva.
Hemos ido preparando el terreno para poder mostrar nuestros datos de manera gráfica.
Para ello utilizaremos un addin que además de mostrar los eventos, nos permitirá interactuar con ellos de una manera muy sencilla e intuitiva.
El calendario
El elegido es el que uso habitualmente ya que dispone de multitud de posibilidades y opciones además de disponer de licencia MIT. Hablo de Fullcalendar, aquà su web donde existe extensa documentación, ejemplos y el código fuente en repositorio github.
Fullcalendar nos permite disponer de una vista por mes
Por semana
Además nos permite modificar la fecha y/o horas de inicio y fin de cada evento simplemente arrastrando o dimensionado con el ratón.
También nos ofrece la posibilidad de convertir un evento en evento de todo el dÃa arrastrándolo a la parte superior en la vista de semana (Todo el dÃa).
Podemos personalizar colores de los eventos, controlar eventos: click, cambio de fecha/hora, etc.
Pues vamos a comenzar a crear nuestro addin.
En el proyecto, dentro de la carpeta Addin crearemos el fichero que define el addin: calendar.al
En el proyecto, dentro de la carpeta Addin crearemos el fichero que define el addin: calendar.al
controladdin calendar
{
RequestedHeight = 700;
RequestedWidth = 700;
MinimumHeight = 700;
MinimumWidth = 700;
MaximumWidth = 700;
VerticalStretch = true;
VerticalShrink = true;
HorizontalStretch = true;
HorizontalShrink = true;
Scripts = 'https://cdn.jsdelivr.net/npm/fullcalendar@6.1.11/index.global.min.js', 'https://unpkg.com/popper.js/dist/umd/popper.min.js', './Objects/Addin/es.global.min.js', 'https://unpkg.com/tooltip.js/dist/umd/tooltip.min.js';
StartupScript = './Objects/Addin/calendar.js';
StyleSheets = './Objects/Addin/calendar.css';
procedure SetCalendarData(Data: JsonArray; workDate: Text);
event OnStartCalendar();
event EventModified(eventTxt: Text; startDateTime: DateTime; endDateTime: DateTime; eventId: Text; allDay: Boolean);
}
A fecha de esta publicación la última versión disponible de la librerÃa es la 6.1.11
Flujo Business central - Javascript
Disponemos de una función, SetCalendarData que será la que utilicemos para iniciar el calendario, le pasaremos un json que habÃamos preparado en el post anterior al recibir los eventos y la fecha actual.
Flujo Javascript - Business central
También disponemos de 2 eventos, recordemos que los eventos los utilizamos para que el control nos comunique algo y las funciones para comunicar al control:
- OnStartCalendar: nos indicará que el control se ha cargado. Por el momento no la utilizo, pero siempre es conveniente disponer de ella.
- EventModified: nos informa de que un evento ha sido modificado: fecha, hora inicio, fin, ...
En los scripts del addin del calendario hay una referencia que no tenemos y se utiliza para traducir textos a castellano.
El código de este fichero es el siguiente (es.global.min.js):
El código de este fichero es el siguiente (es.global.min.js):
/*!
FullCalendar Core v6.1.11
Docs & License: https://fullcalendar.io
(c) 2023 Adam Shaw
*/
!function(e){"use strict";var t={code:"es",week:{dow:1,doy:4},buttonText:{prev:"Ant",next:"Sig",today:"Hoy",year:"Año",month:"Mes",week:"Semana",day:"DÃa",list:"Agenda"},buttonHints:{prev:"$0 antes",next:"$0 siguiente",today:e=>"DÃa"===e?"Hoy":("Semana"===e?"Esta":"Este")+" "+e.toLocaleLowerCase()},viewHint:e=>"Vista "+("Semana"===e?"de la":"del")+" "+e.toLocaleLowerCase(),weekText:"Sm",weekTextLong:"Semana",allDayText:"Todo el dÃa",moreLinkText:"más",moreLinkHint:e=>`Mostrar ${e} eventos más`,noEventsText:"No hay eventos para mostrar",navLinkHint:"Ir al $0",closeHint:"Cerrar",timeHint:"La hora",eventHint:"Evento"};FullCalendar.globalLocales.push(t)}();
Ahora crearemos un fichero con la hoja de estilos para dar formato a los componentes visuales. Lo llamaremos calendar.css
body {
padding: 0;
font-family: Arial, Helvetica Neue, Helvetica, sans-serif;
font-size: 14px;
}
#calendar {
max-width: 1100px;
margin: 40px auto;
padding: 0 10px;
}
.popper,
.tooltip {
position: absolute;
z-index: 9999;
background: #B2E9ED;
color: black;
width: 150px;
border-radius: 3px;
box-shadow: 0 0 2px rgba(0,0,0,0.5);
padding: 10px;
text-align: center;
}
.style5 .tooltip {
background: #1E252B;
color: #FFFFFF;
max-width: 200px;
width: auto;
font-size: .8rem;
padding: .5em 1em;
}
.popper .popper__arrow,
.tooltip .tooltip-arrow {
width: 0;
height: 0;
border-style: solid;
position: absolute;
margin: 5px;
}
.tooltip .tooltip-arrow,
.popper .popper__arrow {
border-color: #1E252B;
}
.style5 .tooltip .tooltip-arrow {
border-color: #1E252B;
}
.popper[x-placement^="top"],
.tooltip[x-placement^="top"] {
margin-bottom: 5px;
}
.popper[x-placement^="top"] .popper__arrow,
.tooltip[x-placement^="top"] .tooltip-arrow {
border-width: 5px 5px 0 5px;
border-left-color: transparent;
border-right-color: transparent;
border-bottom-color: transparent;
bottom: -5px;
left: calc(50% - 5px);
margin-top: 0;
margin-bottom: 0;
}
.popper[x-placement^="bottom"],
.tooltip[x-placement^="bottom"] {
margin-top: 5px;
}
.tooltip[x-placement^="bottom"] .tooltip-arrow,
.popper[x-placement^="bottom"] .popper__arrow {
border-width: 0 5px 5px 5px;
border-left-color: transparent;
border-right-color: transparent;
border-top-color: transparent;
top: -5px;
left: calc(50% - 5px);
margin-top: 0;
margin-bottom: 0;
}
.tooltip[x-placement^="right"],
.popper[x-placement^="right"] {
margin-left: 5px;
}
.popper[x-placement^="right"] .popper__arrow,
.tooltip[x-placement^="right"] .tooltip-arrow {
border-width: 5px 5px 5px 0;
border-left-color: transparent;
border-top-color: transparent;
border-bottom-color: transparent;
left: -5px;
top: calc(50% - 5px);
margin-left: 0;
margin-right: 0;
}
.popper[x-placement^="left"],
.tooltip[x-placement^="left"] {
margin-right: 5px;
}
.popper[x-placement^="left"] .popper__arrow,
.tooltip[x-placement^="left"] .tooltip-arrow {
border-width: 5px 0 5px 5px;
border-top-color: transparent;
border-right-color: transparent;
border-bottom-color: transparent;
right: -5px;
top: calc(50% - 5px);
margin-left: 0;
margin-right: 0;
}
Microsoft.Dynamics.NAV.InvokeExtensibilityMethod('OnStartCalendar')
window.SetCalendarData = function (data,workdate) {
try {
var div = document.getElementById('controlAddIn');
div.innerHTML += `<div id='calendar-wrap' style="max-width: 1600px;margin: 0 auto;">
<div id='calendar' style="max-width: 1600px;margin: 0 auto;"></div></div>`;
calendarEl = document.getElementById('calendar-wrap');
var calendar = new FullCalendar.Calendar(calendarEl, {
headerToolbar: {
left: 'prev,next today',
center: 'title',
right: 'dayGridMonth,timeGridWeek,listWeek,timeGridDay'
},
initialDate: workdate,
locale: 'es',
buttonIcons: false, // show the prev/next text
weekNumbers: false,
weekNumberCalculation: 'ISO',
navLinks: true, // can click day/week names to navigate views
editable: true,
dayMaxEvents: true, // allow "more" link when too many events
eventDidMount: function(info) {
var tooltip = new Tooltip(info.el, {
title: info.event.extendedProps.description,
placement: 'top',
trigger: 'hover',
container: 'body'
});
},
eventResize: function(info) {
//alert(info.event.title + " was resized on " + info.event.start.toISOString());
if (info.event.allDay)
{
console.log(info);
Microsoft.Dynamics.NAV.InvokeExtensibilityMethod('EventModified',[info.event.title.toString(),info.event.start,info.event.end,info.event.id.toString(),true])
}
else
{
console.log(info);
Microsoft.Dynamics.NAV.InvokeExtensibilityMethod('EventModified',[info.event.title.toString(),info.event.start,info.event.end,info.event.id.toString(),false])
}
},
eventDrop: function(info) {
if (info.event.allDay)
{
if (info.event.endStr != '')
{
console.log(info);
Microsoft.Dynamics.NAV.InvokeExtensibilityMethod('EventModified',[info.event.title.toString(),info.event.start,info.event.end,info.event.id.toString(),true])
}
else
{
console.log(info);
Microsoft.Dynamics.NAV.InvokeExtensibilityMethod('EventModified',[info.event.title.toString(),info.event.start,info.event.extendedProps.bcendevent,info.event.id.toString(),true])
}
}
else
{
console.log(info);
Microsoft.Dynamics.NAV.InvokeExtensibilityMethod('EventModified',[info.event.title.toString(),info.event.start,info.event.end,info.event.id.toString(),false])
}
},
events: data
});
calendar.render();
} catch (err) {
console.log(err);
}
}
Lo primero y antes de nada ... comentar que no soy ningún experto en Javascript, espero que con el tiempo ...
De ahÃ, pedir disculpas si comparto código en Javascript que puede herir la sensibilidad de alguien 😅
De ahÃ, pedir disculpas si comparto código en Javascript que puede herir la sensibilidad de alguien 😅
En este script capturamos el iframe controlAddin y lo rellenamos con un div con id calendar, la librerÃa hará el resto (render).
Definimos qué controles queremos que aparezcan en la barra superior: prev, next, today y vamos dando valores a los parámetros.
Después vienen los eventos del calendario:
- eventDidMount: cuando se van construyendo los eventos. Se añade un tooltip que nos vendrá muy bien para su uso, al pasar el ratón por encima de un evento, parecerá un cartel con la información que le indiquemos en el campo description.
- eventResize: al dimensionar un evento (cambio de hora inicio o fin) nos informará del cambio.
- eventDrop: cuando soltemos un evento nos informará, esto ocurre, por ejemplo cuando cambiamos un evento de dÃa, lo arrastramos de una fecha a otra. Al soltar el botón de ratón se dispara este evento.
Y por último se crea el calendario con calendar.render(). La librerÃa se encarga de capturar el div con id calendar e inyectar en él el diseño y funcionalidad del calendario.
Ahora creamos la página en la que poder gestionar nuestros calendarios. Podremos disponer de la lista de todos los calendarios y ver los eventos de cada uno de ellos simplemente posicionándonos en él
page 60803 OutlookCalendar
{
ApplicationArea = All;
Caption = 'Eventos calendario Outlook';
PageType = List;
SourceTable = Calendars;
UsageCategory = Lists;
InsertAllowed = false;
ModifyAllowed = false;
DeleteAllowed = false;
layout
{
area(content)
{
group(Calendars)
{
Caption = 'Calendarios';
repeater(General)
{
field(Id; Rec.Id)
{
Visible = false;
}
field(Name; Rec.Name)
{ }
field(IsDefault; Rec.IsDefault)
{ }
}
}
group(Events)
{
Caption = 'Eventos';
usercontrol(calendar; calendar)
{
trigger OnStartCalendar()
begin
DrawCalendar();
end;
trigger EventModified(eventTxt: Text; startDateTime: DateTime; endDateTime: DateTime; eventId: Text; allDay: Boolean)
var
Error001: Label 'No pudo ser modificado';
begin
if not GraphConnectorMngt.ModifyEvent(eventId, startDateTime, endDateTime, allDay) then begin
Message(Error001);
DrawCalendar();
end;
end;
}
}
}
}
trigger OnOpenPage()
begin
Rec.SetRange(UserGuid, UserSecurityId());
GraphConnectorMngt.GetAllCalendars();
CurrPage.Update(false);
end;
trigger OnAfterGetCurrRecord()
begin
DrawCalendar();
end;
var
GraphConnectorMngt: Codeunit GraphConnectorMngt;
EventsJArray: JsonArray;
local procedure DrawCalendar()
begin
DrawCalendar(Today);
end;
local procedure DrawCalendar(startingdate: date)
begin
DrawMyCalendar(startingdate);
end;
local procedure DrawMyCalendar(startingdate: date)
begin
EventsJArray := GraphConnectorMngt.GetCalendarEvents(Rec.Id);
CurrPage.calendar.SetCalendarData(EventsJArray, FORMAT(startingdate, 0, 9));
end;
}
Al abrir la página, obtiene todos nuestros calendarios de la tabla calendars y los muestra en una lista.
Una vez se posiciona la página en un registro de la tabla calendars o seleccionamos nosotros un registro, OnAfterGetCurrRecord, dibujamos el calendario pasando como parámetro la fecha que queramos que el calendario tome como fecha de inicio o sin ella, por lo que tomará la fecha actual como fecha de inicio.
GetAllCalendars() obtendrá todos nuestros calendarios.
Recordemos que la conexión a graph, y en este caso al calendario Outlook, se basa en nuestras credenciales, por lo que las operaciones se limitarán a los calendarios y eventos del usuario logueado.
La página creada está separada en 2 zonas, la superior con los calendarios y la inferior con los eventos del calendario seleccionado
Como detalle, mencionar que pasamos el color recibido de Outlook en cada calendario a sus eventos correspondientes por lo que los eventos mostrados o nuevos que se crearán tendrán por defecto el color del calendario definido en Outlook:
Aquà observamos cómo está definido en Outlook
Y aquà vemos el detalle de un cartel informativo o tooltip que hemos añadido a los eventos
Veámoslo de nuevo, pero en acción:
- Carga de calendarios y eventos, tooltip y cambio de fecha de un evento
- Cambio de fecha de un evento, resultado en Outlook
- Cambio de color en un calendario aplicado a eventos
- Cambio de duración de un evento , originalmente el evento tiene una duración de 8:00 a 8:30
Con esto finalizamos este post.
En el siguiente, y último post de la serie, crearemos eventos a partir de las órdenes de fabricación.
Añadiremos las lÃneas de órdenes de producción en esta misma página y conseguiremos que una página hija (listpart) informe a la la página padre de que se ha solicitado una acción, en este caso crear un nuevo evento. Aplicaremos algo visto recientemente en un video-post de Erik Hougaard.
He aprendido muchÃsimo viendo sus videos, aquà podéis ver su blog-sitio y recomiendo echarle un vistazo.
Nos vemos en el siguiente post. Espero que os sea de utilidad.
En el siguiente, y último post de la serie, crearemos eventos a partir de las órdenes de fabricación.
Añadiremos las lÃneas de órdenes de producción en esta misma página y conseguiremos que una página hija (listpart) informe a la la página padre de que se ha solicitado una acción, en este caso crear un nuevo evento. Aplicaremos algo visto recientemente en un video-post de Erik Hougaard.
He aprendido muchÃsimo viendo sus videos, aquà podéis ver su blog-sitio y recomiendo echarle un vistazo.
Nos vemos en el siguiente post. Espero que os sea de utilidad.
Publicar un comentario