[Agenda] Créer et gérer un Agenda dans une application, avec un Plug-in AngularJs ou Jquery

Introductionvecteur calendrier


Si il est bien un domaine que le développeur d’application bureautique Web doit maitriser, c’est l’intégration d’un agenda dans une application Web.

Qu’il s’agisse d’une application commerciale, d’une app de garage de voitures, d’une app médicale, d’une application Crm ou de finances; l’agenda est omniprésent et son intégration est assez complexe et critique.

Le clic sur un évènement ouvre généralement une fenêtre pop-up répertoriant les caractéristiques de l’évènement.

Sommaire


  1.  Introduction(Suite)
  2. Tour d’horizon des plug-ins disponibles
  3. l’objet JSON « évènement »
  4. Testing, démonstration, explication et exploitation du plug-in Ng Simple Calendar « for the nuls »
  5. Testing, démonstration, explication et exploitation du plug-in Angular UI Ui-Calendar
  6. Testing, démonstration, explication et exploitation du plug-in Angular Bootstrap Calendar
  7. Travaux dirigés : Création d’une application qui stocke des points GPS cliqués sur google Map par date dans un calendrier

Introduction (suite)


L’enregistrement d’évènements dans le temps ouvre la voie à la création de calculs mathématiques statistiques et de calculs de probabilités dans notre application, l’univers omega étant l’ensemble des évènements. Par exemple, si l’évènement de type A apparait sur une période d’une semaine à 2 reprises, on peut calculer avec la loi de poisson le taux de probabilité que l’évènement A apparaitra au moins à 2 reprises la semaine suivante et proposer ce type de résultats primordiaux aux décideurs, automatiquement. Pour la formule, on a besoin de la factorielle et de l’exponentielle, or, Javascript propose des fonctions de calcul de ce type.

Par le passé, j’ai pu voir des exemples  mélant le Langage JAVA à AngularJs qui m’ont fait frémir de frayeur de par leurs complexité, bien que j’aime bien Java. Bien sur, il s’agit de s’éloigner au maximum de ce type de coding extrêmement complexe dans mon cas, et de fournir un code épuré et le plus simple possible, tout en Js avec un format de données de tableaux d’objets JSON, ou chaque évènement est un objet Json, et préférablement avec un back-end en noSql.

Mon choix se porte donc sur l’étude des plugs-ins de calendriers AngularJs ou JQuery, et la façon d’enregistrer un évènement sur une date puis de le sauvegarder en base de données, puis les récupérer à loisir, quand bon nous chante.

Bien sur, j’évite de coder un Agenda de zéro, on ne ré-invente pas la roue, c’est inutile, dans la mesure ou des développeurs plus expérimentés et talentueux ont déjà fait le truc.

Des différences …

Certains implémentent le Drag n Drop ou pas, d’autres permettent « l’allongement de la durée d’un évènement » par clic de souris ou pas.

En somme, certains sont très sophistiqués, d’autres non.

Un calendrier AngularJS, proposant le 2 way binding :

clendrier

Très important : Certains sont traduisibles en Français, d’autres non.

AngularJs 1.x vs Jquery…

Jquery étant présent depuis plus longtemps que AngularJs, on trouve les plus jolis en JQuery. Par contre, les plugs ins Angular de calendriers utilisent le 2  ways binding, et ça, c’est forcément très très intéressant vis à vis de la simplification du code…

Tour d’horizon

Les agendas en AngularJs 1.x :

L’objet JSON « évènement ».


Le développeur du plug in aura dans la plupart des cas formalisé un évènement sous la forme d’un objet JSON similaire à ce type :

{
  "titre": "Changement du carburateur",
  "type": "info",
  "commenceA": "2016-03-17T21:18:02.870Z",
  "termineA": "2016-03-23T21:18:02.870Z",
  "draggable": true,
  "resizable": true
}

Il ne s’agit que d’un seul évènement, et bien sur, tous les évènements seront stockés, comme d’hab dans un tableau d’objets JSON.

On remarque de suite le plus important : La date de début et la date de fin. Son format est souvent sujet à problèmes lors du mapping en bdd relationnelle (Mais pas en noSql, puisque c’est mieux, logique puisque noSql stocke l’intégralité de l’objet Json sans se soucier de rien, lol, c’est donc incroyablement plus facile.)

Ensuite, on comprends que si on utilise un « datepicker »(Choisir une date), le format de date devra être accepté par le plug-in Calendrier pour être compatible.

Testing, démonstration, explication et exploitation du plug-in Ng-Simple-Calendar


Mon premier choix se porte sur : http://ngmodules.org/modules/jobney.ng-simple-calendar

Celui permet l’enregistrement d’évènements par jours(et pas par heure).

Etape 1 : Télécharger le fichier ZIP sur Github

Etape 2 : Ouvrir la lib et traduire les jours en français, Traduire les mois dans app.js.

Etape 3 : Ajouter un formulaire d’ajout d’évènement avec angularJs, c’est très simple, le but étant de pouvoir ajouter un nouvel objet JSON « évènement » dans le tableau d’évènements Json sui s’appelle $scope.events, grâce à push()

Et voilà, notre calendrier est près à être utilisé dans une application. Bien sur il faudra customiser le style CSS, et permettre l’édition à la volée d’un évènement, mais c’est très simple. Si on travaille en noSql, il nous suffit d’enregistrer le tableau d’évènements $scope.events puis de le recharger à loisir lors du login d’un utilisateur. On peut même ajouter sans problème un id utilisateur dans l’objet ou toute autre info!

Pour tester, cliquer ici .  Pour un plunker en Anglais, cliquer ici.

Mon code qui a un peu modifié le programme originel , fait en 7 minutes (Ajout de la fonction ajouterEvenement()) :


/*global angular */

var app = angular.module('plunker', ['envoc.simpleCalendar']);

app.controller('MainCtrl', function($scope, simpleCalendarConfig) {
  simpleCalendarConfig.weekStart = 1;
  simpleCalendarConfig.onDayClick = onDayClick;
  simpleCalendarConfig.onEventClick = onEventClick;

  $scope.date = new Date();

  /* La liste des évènements. */
  $scope.events = [{
    name: 'foo',
    date: '2016-03-11'
  }, {
    name: 'bar',
    date: new Date()
  }, {
    name: 'bar foo',
    date: new Date()
  }, {
    name: 'baz',
    date: '6-12-15'
  }];

  $scope.changeMonth = changeMonth;
  $scope.monthName = monthName;

  function onDayClick(day){
    console.log(day);
  }

  function onEventClick(event, day){
    console.log(event, day);
  }

  function monthName(date) {
    var d = new Date(date);
    var months = [
      'Janvier', 'Fevrier', 'Mars',
      'Avril', 'Mai', 'Juin',
      'Juillet', 'Aout', 'Septembre',
      'Octobre', 'Novembre', 'Decembre'
    ];
    return months[d.getMonth()];
  }

  function changeMonth(offset) {
    var d = new Date($scope.date);
    $scope.date = d.setMonth(d.getMonth() + offset);
  }

  $scope.ajouterEvenement = function(){
	  $scope.events.push({name:$scope.nouvelEvenement.name,date:$scope.nouvelEvenement.date});
  }
});

Puis le HTML :

<!DOCTYPE html>
<html ng-app="plunker">

  <head>
    <meta charset="utf-8" />
    <title>AngularJS Envoc Simple Calendar.</title>
    	<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css" />
    	<link rel="stylesheet" href="../dist/ngSimpleCalendar.css" />
    	<link rel="stylesheet" href="style.css" />
    <script src="//code.angularjs.org/1.3.15/angular.js" data-semver="1.3.15"></script>
    <script src="../dist/ngSimpleCalendar.js"></script>
  </head>

  <body ng-controller="MainCtrl">
    <strong>{{monthName(date)}} {{date|date:'yyyy'}}</strong>
<div>
      <button class="btn btn-default" ng-click="changeMonth(-1)">Précédent</button>
      <button class="btn btn-default" ng-click="changeMonth(1)">Suivant</button></div>
<simple-calendar date="date" events="events">
      <simple-calendar-day>
        <strong>{{$day.day}}</strong>
      </simple-calendar-day>
      <simple-calendar-event>{{$event.name}}</simple-calendar-event>
    </simple-calendar>
<div>
<h2> Ajouter un évènement</h2>
<input ng-model="nouvelEvenement.name" type="varchar" placeholder="Entrez le nom de l'évènement"></input>

	  <input ng-model="nouvelEvenement.date" type="date" placeholder="Entrez la date"></input>

		<button ng-click="ajouterEvenement()">Ajouter</button></div>
<!--   <simple-calendar date="date" events="events"></simple-calendar> -->
    <script src="app.js"></script>
  </body>
</html>

Testing, démonstration, explication et exploitation du plug-in Angular Ui Calendar


Ce calendrier permet d’enregistrer des évènements par heure et a pas mal d’options.

Prérequis :

Avec Bower, installer comme expliqué dans la doc, cela installe les dépendances, puis ajouter angular-locale_fr-fr.js pour traduire en Français automatiquement.

Voir le plug-in en action en cliquant ici.

J’ai ajouté la possibilité d’ajouter un évènement et de visionner les évènements.

Ce n’est qu’une démo, bien sur il faudrait bien faire les CSS et ajouter des fonctionnalités sur Clic. Note : Lors de l’ajout d’un évènement, je n’ai pas fait de séquence de contrôle sur les 4 valeurs, du coup si on ne les renseigne pas cela ne fonctionne pas, c’est juste question de temps que je ne le fais pas.

Le fichier controleur de l’application app.js :

/*global angular */

var app = angular.module('App', ['ui.calendar','ui.bootstrap'])

app.controller('MainCtrl', function($scope) {

	/* Initialisation de la date et de l'heure du jour en cours */
	var date = new Date();
	var d = date.getDate();
    d = ("0" + d).slice(-2)
    var m = date.getMonth();
	m = (m+1);
    m = ("0" + m).slice(-2)
    var y = date.getFullYear();

	/* Tableau de Tous les évenements du calendrier, modifiable en temps réel, c'est une tableu d'objets JSON */
	$scope.events = [
       {
            title: 'Visite chez le docteur',
			type   : 'gms',
            start  : '2016-03-03 09:00:00',
            end    : '2016-03-03 10:00:00',
            allDay : false
        },
		{
            title: 'Visite chez le docteur',
			type   : 'gms',
            start  : '2016-03-04 09:00:00',
            end    : '2016-03-04 10:00:00',
            allDay : false
        }, {
            title  : 'Visite : Carrefour St Herblain',
            type   : 'gms',
            start  : y+'-'+m+'-'+d+' 14:00:00',
            end    : y+'-'+m+'-'+d+' 15:00:00',
            allDay : false
        }

    ];

	 /* alert on eventClick */
    $scope.alertOnEventClick = function( date, jsEvent, view){
        alert(date.title + ' was clicked ');
    };

	/* Nécessaire pour l'interpretation du calendrier */
	$scope.eventSources = [$scope.events];

	  /* configuration du look de l'agenda */
    $scope.uiConfig = {
      calendar:{
        height: 450,
		selectable: true,
        editable: true,
        header:{
          left: 'month basicWeek basicDay agendaWeek agendaDay',
          center: 'title',
          right: 'today prev,next'
        },
        dayClick: dayClick,
        eventDrop: $scope.alertOnDrop,
        eventResize: $scope.alertOnResize,
		eventClick: $scope.alertOnEventClick
      }
    };
	 /* Supprimer evenement */
    $scope.remove = function(index) {
      $scope.events.splice(index,1);
    };

	  function dayClick(date){
    alert(date);
  };

	$scope.alertEventOnClick = function(date) {
		alert('test');
	// change the border color just for fun
       $(this).css('border-color', 'red');
	};

	/* alerte sur eventClick */
    $scope.alertOnEventClick = function( date, jsEvent, view){
        $scope.alertMessage = (date.title + ' was clicked ');
    };
	  /* Ajouter un evenement*/
    $scope.ajouterEvenement = function() {

		var x = $scope.nouvelEvenement.startDate ;
		var y = $scope.nouvelEvenement.endDate;
		var dateDb = getOnlyDate(x);
		var dateFin = getOnlyDate(y);

		var k = $scope.nouvelEvenement.startHeure ;
		var r = $scope.nouvelEvenement.endHeure ;
		var heureDb = getOnlyHours(k);
		var heureFin = getOnlyHours(r);

		function getOnlyDate(date){
			var d = date.getDate();
			d = ("0" + d).slice(-2)
			var m = date.getMonth();
			m = (m+1);
			m = ("0" + m).slice(-2)
			var y = date.getFullYear();
			return y+'-'+m+'-'+d
		}

		function getOnlyHours(date){
			var dateWithouthSecond = date;
			return dateWithouthSecond.toLocaleTimeString(navigator.language, {hour: '2-digit', minute:'2-digit'});

		}

		  $scope.events.push({
			title: $scope.nouvelEvenement.title,
			type   : 'gms',
			start: dateDb +' '+ heureDb +':00',
			end: dateFin +' '+ heureFin +':00',
			allDay : false,
			cssClass: 'a-css-class-name'
		  });
    };

});
<!DOCTYPE html>
<html ng-app="App">
 <head>
    <meta charset="utf-8" />
    <title>ui-Calendar.</title>
		<!-- jquery, moment, and angular have to get included before fullcalendar -->
		<script type="text/javascript" src="bower_components/jquery/dist/jquery.min.js"></script>
		<script type="text/javascript" src="bower_components/moment/min/moment.min.js"></script>
		<script type="text/javascript" src="bower_components/angular/angular.min.js"></script>
		<script src="http://code.angularjs.org/1.0.8/i18n/angular-locale_fr-fr.js"></script>
		<script type="text/javascript" src="bower_components/angular-ui-calendar/src/calendar.js"></script>
		<script type="text/javascript" src="bower_components/fullcalendar/dist/fullcalendar.min.js"></script>
		<link rel="stylesheet" href="bower_components/fullcalendar/dist/fullcalendar.css"/>
		<script type="text/javascript" src="bower_components/fullcalendar/dist/lang/fr.js"></script>
		<script type="text/javascript" src="bower_components/fullcalendar/dist/gcal.js"></script>
		 <!--	APPEL LIB BOOTSTRAP -->
		<script src="bootstrap/js/ui-bootstrap-tpls-0.14.3.min.js"></script>
		<link rel='stylesheet prefetch' href='bootstrap/css/bootstrap.css'>
		<!-- <link rel='stylesheet prefetch' href='http://netdna.bootstrapcdn.com/font-awesome/4.0.0/css/font-awesome.min.css'> -->
	</head>

	<body ng-controller="MainCtrl">

		<div ui-calendar="uiConfig.calendar" ng-model="eventSources"></div>

		<div class="container-fluid " style="background:lightblue">
			<h1>Liste des évènements</h1>
			 <ul class="unstyled">
				<li ng-repeat="e in events">
					<div class="alert alert-info">

						<b> <input ng-model="e.title"></b>
						{{e.start | date:"MMM dd"}} - {{e.end | date:"MMM dd"}}

						<button ng-click="remove($index)" placeholder="supprimer">Supprimer<i class="icon-remove"></i></button>
					</div>
				</li>
			</ul>
		</div>

		<div class="container-fluid " style="background:lightgrey">
			<h1>Ajouter un évènement</h1>
			<form>
				<input ng-model="nouvelEvenement.title" type="varchar" placeholder="Entrez le nom de l'évènement"></input><br>
				<br>

				<div class="alert alert-info"style ="float:left;margin-left:50px;">
					<h2>Date de début</h2><br>
					<uib-datepicker ng-model="nouvelEvenement.startDate" class="well well-sm" placeholder="Entrez la date" required></uib-datepicker>
					<h2>Heure de début</h2>
					<uib-timepicker ng-model="nouvelEvenement.startHeure" class="well well-sm" required></uib-datepicker> <br>
				</div>

				<div class="alert alert-info"style ="float:left;margin-left:50px;">
					<h2>Date de Fin</h2><br>
					<uib-datepicker ng-model="nouvelEvenement.endDate" class="well well-sm" placeholder="Entrez la date" required></uib-datepicker>
					<h2>Heure de Fin</h2>
					<uib-timepicker  ng-model="nouvelEvenement.endHeure" class="well well-sm"required></uib-timepicker><br>

				</div>

				<div class="alert alert-info" style ="float:left;margin-left:50px;">
					Tous les jours ?<input ng-model="nouvelEvenement.allDay" type="checkbox" placeholder="Tous les jours ?"></input><br>
					<button ng-click="ajouterEvenement()">Ajouter</button>
				</div>

			 </form>
		</div>

		<script src="app.js">
		$(document).ready(function() {
			$('#calendar').fullCalendar({
				lang: 'fr'
			});
		});
		</script>
	</body>
</html>
Publicités