Traitement Back-end PHP Try Catch avec réception et traitement Front-end AngularJs 1.6.5

INTRODUCTION :


Gérer les TRY CATCH dans les web service PHP, et communiquer le message contenu dans un THROW directement au FRONT END ANGULARJS

Le système de try catch PHP est puissant et pratique à utiliser, en particulier pour les problèmes de droits par exemple mais cela peut être pour n’importe quoi d’autre !!

Seulement, il faut savoir le faire communiquer avec le FRONT END !!

DETAIL


Pour info, voici le look d’une méthode de ma classe, dans le BACKEND PHP, fichier backend.php , qui enregistre les permissions, on peut voir un test de permission avant l’exécution  de la requête qui enregistre un tableau d’objets JSON décomposé grâce au foreach, l’important ici est de regarder le THROW et son message STRING ( Les contrôles de sécurité ne sont pas affichés dans cet exemple):

    /**
     * Update Permissions after front end management
     * 
     * Required Permission : $_SESSION['auth'] = true | UPDATE_PERMISSIONS
     * 
     * @return true
     * 
     * 
     */
    public function updatePermissions($jsonArray)
    {

        // Check User Permissions
        if (!$this->has_permission('UPDATE_PERMISSIONS', $_SESSION['permissions'])) {
            throw new Exception("Error : You are restricted to do this.");
        };


        foreach ($jsonArray as $index => $row) {
            $row                      = $this->escape_data_object($row);
            $id                       = $row['id'];
            $name                     = $row['name'];
            $permissions              = strval($row['permissions']);
           

            $sql = "UPDATE Job SET name='$name',permissions='$permissions' WHERE id = '$id'";
            $this->conn->query($sql);

        }

        mysqli_close($this->conn);
        return true;
    }

Le Web Service en PHP qui catche une éventuelle erreur de permission et renvoie le message d’erreur STRING du THROW pour pouvoir l’afficher directement dans le front end, (json_encode() permet de créer du JSON, compréhensible par angularJs, ou vue.js, ou angular 5 …) :

 if(!empty($_POST["updatePermissions"]) AND $_SESSION['auth']) {

    $jsonArray = $_POST["updatePermissions"];
    $jsonArray = json_decode($jsonArray,true); 
    $status = 200;

    try{
        $db->updatePermissions($jsonArray);
    } 
    catch(Exception $e){
         echo json_encode(array("response"=>$e->getMessage()));
         exit();
    } 

    echo json_encode(array("response"=>$status));

} 

Et enfin le FRONT END en ANGULARJS 1.6.5 qui traite le message d’erreur dans le .then() et affiche le STRING du THROW originel directement dans le FRONT END (Remarquez que la requête POST ajax s’adressait bien à mon WEBSERVICE en php ):

//~ AJAX CALL -> Sending whole $scope.permissions array of json objects
        $http.post(wsUrl, {updatePermissions: JSON.stringify($scope.permissions)})
            .then(function(data, headers, config) {
                console.log(data.data);

                $scope.ajaxCall = false;
                $scope.record_button_disable = false;

                // Errors management Responses
                if (data.data.response == 200) {
                    Notification.success($translate.instant('REQUESTSUCCESS'));
                    console.log("Réponse Serveur: " + data.data);

                }
                if (data.data.response != 200) {
                    Notification.error(data.data.response);
                }
            })
        }

Et enfin… Juste pour info, la petite fonction privée PHP has_permission() du cru qui permet d’ejecter les gars qui tentent de faire un truc alors qu’ils n’ont pas la permission dans leur array de permissions (séparé par ;), par le back end, hi hi . Par ecemple si il na pas la permission DISPLAY_USERS, le back end throw une erreur, et angularjs(ou vue.js) affiche le message d’erreur:

/**
	 * This function is looking for the permission inside user PHP SESSION permissions variables, if the permission is not present, then it returns false !
	 * 

	 * @return false|true
	 * 

	 */

	private function has_permission($permission, $permissions)
	{

		$permissions = explode(";", $permissions);

		if (in_array($permission, $permissions)) {
			return true;
		}

		return false;
	}

 

240px-Sheep_Shaf_Mouton.JPG‘Bhee, t »es un ouf pourquoi t’utilises pas que Php pour simplifier plutôt que de faire tout ça bhee?’

Parce que AngularJs et Javascript disposent de l’atout temps réel, d’énormément de librairies et sont très puissants, voir par exemple le jeux LARA CROFT http://xproger.info/projects/OpenLara/ avec une lib dédiée graphique js , voir Three.js, ou alors ne serait ce que D3.JS qui permet une modélisation innovante pour analyse de données, bon courage pour faire ça avec php !!!

Publicités

[AngularJs + Php MYSQL ] Une table Html infinite scroll simple, sur base de donnée

angularjsphp_1_

Introduction


Avec Angularjs 1.6 , on ne télécharge pas tout un modèle de données lorsqu’il est très grand. Par exemple, pour, afficher toute une table de log de 150 MO, évidemment, on ne charge pas toute la table d’un seul coup dans la mémoire vive, ce serait bien trop couteux. Dans d’autres cas, dans le cadre d’une vue spéciale dans une application, on peut envisager de télécharger tout le modèle de données ( Par exemple, 500 utilisateurs)

L’idée de l’infinite Scroll sur base de donnée


L’idée étant d’afficher les 20 premiers enregistrements de la table, puis ensuite, lorsque l’on atteints le bas de la page, charger les 20 suivants, puis ainsi de suite, de cette manière, l’utilisateur a la sensation d’être en temps réel.

Le problème


Le problème vient lorsque l’on commence à utiliser des filtres dans le Back End, en effet, cela complique le système car les enregistrements arrivent alors dans le désordre, et nous verront cela dans une seconde partie.

Petit Rappel de l’architecture MVC de AngularJs 1.6 ( != Angular 5) :


file-page1.jpg

UNE IDEE DE LINFINITE SCROLL EN ACTION :

infitie.jpg

PHASE 1 Le code pour une table auto incrémentale, dont aucun enregistrement n’a jamais été supprimé

LA VUE HTML

On commence à créer la table dans la vue comme ceci :

<table class="table table-hover sttable table-responsive table-bordered ">
   <thead>
      <th>Id</th>
      <th>identifiant principal</th>
      <th>sessionId</th>
      <th>ip</th>
      <th>message</th>
      <th>timestamp</th>
      <th>url</th>
      <th>data</th>
      </tr>
   </thead>
   <tbody>
      <tr ng-repeat="row in logs track by $index">
         <td>{{::row.id}}</td>
         <td>{{::row.identifiantP}}</td>
         <td>{{::row.sessionId}}</td>
         <td>{{::row.ip}}</td>
         <td>{{::row.message}}</td>
         <td>{{::row.timestamp}}</td>
         <td>{{::row.url}}</td>
         <td>{{::row.data}}</td>
      </tr>
   </tbody>
</table>

Notes :

  • Remarquer qu’il faut ajouter ‘track by $index’ au ng-repeat pour que cela fonctionne.
  • Les :: sont utiles pour debinder afin de récupérer de la mémoire, en effet, pas besoin de 2 ways binding dans un simple reporting.

Ensuite , dans mon controleur ANGULARJS 1.6, je tape les instructions suivantes :

var startRowId = 0;
$scope.read_logs = function() {

    $http.post(wsUrl, {
            read_logs: JSON.stringify({
                'startRowId': startRowId
            })
        })
        .then(function(data) {

            if (startRowId == 0) {
                $scope.logs = data.data;
            }

            if (startRowId >= 20) {

                angular.forEach(data.data, function(obj) {
                    $scope.logs.push(obj);
                })

            }

        })

}

$scope.read_logs();

angular.element($window).bind("scroll", function() {
    var windowHeight = "innerHeight" in window ? window.innerHeight : document.documentElement.offsetHeight;
    var body = document.body,
        html = document.documentElement;
    var docHeight = Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight);
    windowBottom = windowHeight + window.pageYOffset;
    if (windowBottom >= docHeight) {
        startRowId += 20;
        $scope.read_logs();
    }
});

Explication :

  • startRowId est la valeur de la première ligne de notre tableau et dans la table SQL simultanément (C’est ‘synchro’).
  • $scope.read_logs() est la fonction qui lit et récupère en AJAX le tableau json habituel, si startRowId est = à 0 alors, il crée tout simplement les données dans la table HTML, par contre, si startRowId >= 20, alors il sait qu’il doit AJOUTER(push) au modèle de données les données qui arrivent, et qui sont forcément les 20 suivantes dans la table logs !.
  • angular.element($window).bind(« scroll », function() est la function qui détecte quand on arrive à la fin de la page, et exécute de nouveau $scope.read_logs() tout en incrémentant startRowId de 20 , de manière à dire au back end : ‘Lit moi les 20 suivants ‘
  • angular.forEach lui ajoute au tableau d’objet JSON $scope.logs les nouveaux objets récupérés.

Le Back End qui traite la requête SQL :

// Le web Service
if(!empty($_POST["read_logs"]) AND $_SESSION['auth']) {

	$json = $_POST["read_logs"];;
	$data = json_decode($json);
	echo json_encode($db->read_logs($data),JSON_NUMERIC_CHECK); // Renvoie le tableau JSON
} 

 //~ La fonction, normalement située dans une Classe PHP, je ne peux pas tout mettre ..
 public function read_logs($data) {
     $sql = " SELECT * FROM Log ";
     if ($data - > startRowId) {
         $sql. = " WHERE id >= ".$data - > startRowId;
     }

     $sql. = " LIMIT 20";

     $responseArray = array();
     if ($result = $this - > conn2 - > query($sql)) {
         while ($obj = $result - > fetch_object()) {
             array_push($responseArray, $obj);
         }
         mysqli_free_result($result); // Free result set
     }
     mysqli_close($this - > conn2);
     return $responseArray;
 }

Explication : Si le back end reçois la variable startRowId, alors il crée la requête SQL en conséquence, à chaque fois donc que l’on atteint la fin de la page…

Conclusion :

Voilà , on a un infinite scroll qui fonctionne seulement sur une table auto incrémentale et ou aucun enregistrement n’aura été supprimé.

Cependant, comment faire si l’on veut ajouter des filtres simultanés dans la requête SQL Back End , ou si on a supprimé des lignes (row) dans notre table SQL, du coup l’id auto increment ne respecte plus le n+1 ? Cela complique bien, puisque les ‘rows’ lignes n’arrivent pas forcément dans l’ordre…
C’est ce que nous allons analyser et voir dans la seconde partie.

PHASE 2 Le code pour une table auto incrémentale, dont des enregistrements ont été supprimés

ON voit que si l’on supprime 3 enregistrements au début de notre table SQL, par exemple eux qui ont respectivement les ids 3,4 et 5 , notre script de la phase 1 ne fonctionne plus correctement, et veut toujout nous afficher le slignes à partir de l’id 20 , puis de l’id 40 puis de l’id 60 etc …. Pourquoi ? et bien c’est à cause du ’20’ statique qui est ajouté à chaque fois dans le WHERE de la requête SQL et qui fausse alors notre résultat, si les 20 premiers résultats ne sont pas une suite en n+1 !

La solution est donc de passer l’id du dernier enregistrement acquis par le front endà notre requête SQL en lieu et place du chiffre statique (20 ou 40 ou 60 etc ..), afin qu’elle ne plante jamais ! Autrement dit, à chaque fois qu’une nouvelle page s’affiche, je récupère l’ID de la dernière ligne affichée, puis l’envoie au back end, qui lui, reconstitue la requête avec le dernièr ID obtenu (PAr exemple 23), puis renvoie les 20 prochaines données !

On modifie le code en conséquence :

Le controleur

var startRowId = 0;
$scope.read_logs = function() {

    $http.post(wsUrl, {
            read_logs: JSON.stringify({
                'startRowId': startRowId
            })
        })
        .then(function(data) {

            if (startRowId == 0) {
                $scope.logs = data.data;
            }

            if (startRowId != 0) {

                angular.forEach(data.data, function(obj) {
                    $scope.logs.push(obj);
                })

            }

        })

}

$scope.read_logs();

angular.element($window).bind("scroll", function() {
    var windowHeight = "innerHeight" in window ? window.innerHeight : document.documentElement.offsetHeight;
    var body = document.body,
        html = document.documentElement;
    var docHeight = Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight);
    windowBottom = windowHeight + window.pageYOffset;
    if (windowBottom >= docHeight) {
       startRowId = $scope.hk_logs[$scope.hk_logs.length - 1].id;
       $scope.read_hk_logs();
    }
});

Explication : On récupère lorsqu’on a scrollé l’id de la dernière ligne affichée dans le tableau, afin de l’envoyer à la requête SQL

Phase 3 : Mais moi je veux utiliser des filtres aussi, quand je fais de l’infinite Scroll !

En fait, maintenant que l’on a résolu le problème de la dernière ligne (Qu’on aurait également pu traiter dans le back end directement), Ajouter des filtres est plutôt facile, il faut ajouter un select dans le front end, contenant les critères de filtres, puis, envoyer le critère au back end qui l’ajoute à la fin de la requête SQL, dans un WHERE :

Ma requête $http.post envoie désormais des filtres (dans le front end):


$http.post(wsUrl, {
read_logs: JSON.stringify({
'startRowId': startRowId,
'start_month_date': $scope.start_month_date,
'start_year_date': $scope.start_year_date,
'kitIdFilter': $scope.kitIdFilter,
'hkmessageFilter':$scope.hkmessageFilter,
'hkIpFilter':$scope.hkIpFilter
})
})

Puis, mon back end crée la requête SQL en conséquence :

 //~ Read LOGS
    public function read_logs($data)

    {
        $sql  = " SELECT * FROM Log WHERE 1=1";

		if($data->startRowId){

			$sql .= " AND id >= ".$data->startRowId ;
		} 

		if ($data->start_month_date) {

            $sql .= " AND MONTH(timestamp) = '" . $data->start_month_date . "'";
        }
        if ($data->start_year_date)  {

            $sql .= " AND YEAR(timestamp) = '" . $data->start_year_date . "'";
        }

		 if ($data->homekitIdFilter)  {

            $sql .= " AND kitId = '" . $data-><span 				data-mce-type="bookmark" 				id="mce_SELREST_start" 				data-mce-style="overflow:hidden;line-height:0" 				style="overflow:hidden;line-height:0" 			></span>kitIdFilter . "'";
        }

         if ($data->hkmessageFilter)  {

            $sql .= " AND message = '" . $data->hkmessageFilter . "'";
        }

        if ($data->hkIpFilter)  {

            $sql .= " AND ip = '" . $data->hkIpFilter . "'";
        }

		$sql.= " LIMIT 20";

        $responseArray = array();
        if ($result = $this->conn2->query($sql)) {
            while ($obj = $result->fetch_object()) {
                array_push($responseArray, $obj);
            }
            mysqli_free_result($result); // Free result set
        }
         mysqli_close($this->conn2);
        return $responseArray;
    }

[AngularJs] un autocomplete asynchrone sur base de données avec Js-Custom-Select

Introduction :


Avec AngularJs, on est vite tenté de charger tout un modèle de données en mémoire vive, même pour l’afficher dans un select dropdown, c’est tellement facile … Par exemple comme ici :https://desgeeksetdeslettres.com/programmation-java/auto-completion-avec-angularjs

Pourtant, c’est forcément couteux dès que l’on a des centaines d’objets, d’ou l’idée de réserver la partie recherche au BACK END dès que les résultats dépassent la centaine de cas, en gros .

Qu’entends–t-on par modèle de données ? Par exemple : Toutes les villes de France (Des milliers), tous les utilisateurs de facebook (Des millions). Alors évidemment, on ne va pas tout charger dans le Front End et donc faire du asynchrone.

Pour parler basiquement, il faut qu’ à chaque fois que l’on tape une touche dans un champs INPUT, une requête SQL recherche les occurences dans la base de données, et remontent les resultats au format tableau d’objets JSON  (ARRAY [{},{},{},etc ….]). Ces résultats s’affichent alors dans notre select dropdown, afin de pouvoir faire notre choix.

C’est ce que permet de faire https://github.com/axel-zarate/js-custom-select.

Photo exemple de l’autocomplete sur base de données :

moumoute

Le code :

Je passe sur l’installation de js-custom-select qui est bien expliquée dans le github…


Dans la vue HTML, je mets mon SELECT :

<div custom-select="personne.lastName  for personne in searchAsync($searchTerm)" custom-select-options="{ 'async': true }" ng-model="custom2">
    <div class="pull-left" style="width: 40px">
        <img ng-src="{{ personne.image || 'img_app/gender_neutral_user1600_g.png' }}" style="width: 30px" /></div>
    <div class="pull-left">
        <strong>{{ personne.firstName}}</strong>

        <span>{{ personne.lastName}}</span></div>
    <div class="clearfix"></div>
</div>

On voit que on va afficher les objets personnes qui vont satisfaire le critère $searchTerm. En fait, dès qu’on va écrire dans le select, angularJs va exécuter searchAsync($searchTerm) , $searchTerm étant ce que l’on vient d’écrire. La recherche va se faire sur le lastName (le nom, en anglais)

Dans mon controleur MVC angularJS , j’ai cette fonction :

var wsUrl = 'web_services/mes_web_services.php';

$scope.searchAsync = function(term) {

  if (!term) {
    return false;
  }

  var putResponse;
  var promise = $http.post(wsUrl, {
    readPersonnes: JSON.stringify({
      'lastName': term
    })
  }).then(function(data, status, headers, config) {
    putResponse = data.data;
    console.log(putResponse); // this gets called after the server responds ( defined )
    return putResponse;

  })
  console.log(putResponse); // this gets called before the server responds ( undefined )
  return promise;

};

On voit qu’une requête HTTP est exécutée vers la base de données lors de chaque changement de la variable ‘term’ dans le select.

Et enfin, mon code BACK END en PHP MYSQLI, qui retourne à chaque fois la ou les réponses au format Array d’objets JSON habituel :

if(!empty($_POST["readPersonnes"])){
       $json = $_POST["readPersonnes"];
	$data = json_decode($json); 
  	echo json_encode($db->readPersonnes($data));
} 
  
  
// Read Personnes
public function readPersonnes($data)
{
	$sql = " SELECT p.* WHERE 1=1 ";
	

	if ($data->lastName) {
	   
		$sql .= " AND p.lastName LIKE '" .$data->lastName. "%'";
	}

	$responseArray = array();
	
	if ($result = $this->conn->query($sql)) {
		while ($obj = $result->fetch_object()) {
			array_push($responseArray, $obj);
		}
		mysqli_free_result($result); // Free result set
	}
	
	mysqli_close($this->conn);
	return $responseArray;
}

Explication : Si le back end detecte la présence de $data->lastName , alors, il filtre avec LIKE « nomDeLaPersone % » sur le nom, le % veut dire qu’il cherche avec toutes les lettres possibles derrière la variable.
Si l’on ne recherche rien, alors aucun modèle de données n’est chargé, grace à

if (!term) {
return false;
}

NOTE : Ici je fait un select *, mais bien sur, je peux restreindre plus afin de soulager encore le traitement, je peux faire un p.lastName, p.id par exemple dans le select à la place de p.*
Pourquoi le WHERE 1 = 1 ? Et bien c’est pour pouvoir créer des requêtes SQL à la volée qu’on doit écrire cela.

Conclusion :

Avec cette méthode, on obtient des applications pas du tout couteuses, qui ne chargent pas des tas de modèles de données lorsqu’il y a plus de 5 champs autocomplete simultanés porant sur des modèles de données différents (Par exemple : Personnes, Villes, Départements …) dans une vue par exemple.

En d’autres terme, dès que l’on peut soulager le front end, on le fait , avec cette méthode, on peut rechercher sur des millions d’occurences sans dommage pour la mémoire vive .

Il ne nous reste plus qu’à afficher un petit LOADER avec ng-show pour rassurer l’utilisateur … Qui comprends bien qu’une requête SQL est en cours même sans être informaticien.

Note : Avec Jquery, c’est plus facile …

[AngularJs + Php-SQL] Astuce : Copier-Coller des CRUDS pour aller plus vite.

angularjsphp_1_logo-mysql-170x170_400x400

Introduction


Dans les applications informatique, le CRUD revient tout le temps.

Il s’agit de faire de la Creation/Lecture/Mise à jour/Suppression.

L’idée est donc de faire du copié-collé d’un crud standard, puis de changer uniquement le nom du modèle de données, en faisant « controle+h » dans notepad, afin d’aller beaucoup plus vite.

Note : Java Hibernate permettrait de faire cela encore plus simplement( méthode comin’ soon ), sans compter les méthodes de création automatiques des POJO, mais ce n’est pas le sujet de ce post … Là on est sur du back end PHP-MYSQL

Un exemple en temps réel dans une App !


Dans mon application RdvJuristes, j’ai un Crud sur les personnes :

http://nicolash.org/rdvj/index.html#/personnes

crd1.jpg

 

Il est évident que je ne vais pas me retaper tout le code pour créer un CRUD sur les juristes :

http://nicolash.org/rdvj/index.html#/juristes

crd2.jpg

On comprends qu’on a affaire à 2 modèles de données relativement similaires, que l’on peut copier-coller, puis, ensuite adapter à notre convenance.

Lire la suite

[HighCharts] Elaborer une statistique simple avec High Charts dans une application SAAS.

highcharts-logo

Introduction


Dans l’application RdvJuristes, je décide de calculer la somme des notes de frais de tous les rendez vous de la semaine en cours, puis de les afficher dans un graphique High Charts en barres.

Technologie utilisée


  • Highcharts
  • Php-Mysql en technologie PDO pour le back end
  • AngularJs pour le front end

Ordonnancement


  1. Examiner la conception de la base de donnée relationnelle.
  2. Elaborer la requête SQL
  3. Concevoir le Back End
  4. Concevoir le Front End
  5. Tester.

Lire la suite

[Bootstrap 3 ] Un magnifique Template vraiment responsive, avec une sidebar et un barre des tâches .

createiveL’agence Creative TIM

Introduction


Je m’étais égaré ces derniers temps en ne faisant pas assez attention au responsive, ainsi qu’au look de mes apps.

J’ai trouvé un Theme vraiment responsive cette fois ci, avec un look vraiment vraiment sympa par contre c’est pas du bootstrap 4.0.

C’est l’agence CREATIVE TIM qui a des templates vraiment monstrueux en Bootstrap, Matérial et des Widgets !!

J’utiliserais cette base désormais en permanence pour mes futures apps, qui comporte une sidebar qui se contracte parfaitement (togle),et une barre haute.

Lorsque l’on rapetisse l’écran, les panels se contractent parfaitement. j’ai déjà commencé à rajouter et tester des boutons et panels, et je vais rajouter un agenda , et des graphiques qu’on peux changer à la main, et des sliders et des maps, pour bien tester le tout une bonne fois pour toute et que ça passe sur téléphone 5pouces . j’ai testé hier sur un SAMSUNG GALAXY, c’est IMPECCABLE !! Incroyable ! Le template est 100 % parfait sur écran 22 pouces ET sur un téléphone 5 pouces sans rien faire !

Je n’utiliserais plus Material que je ne trouve pas aussi facile que Bootstrap (Voir rdvjuristes ou je galère). Par contre l’agence CREATIVE TIM à un super template Material CSS !

Photo du template


template.jpg

Le lien du template :


http://nicolash.org/db/dashboard.html

[AngularJs + AngularJs Material ] Un framework qui a un bon aspect.

angularjs-materialDesign.png

Introduction


A la recherche d’un bon framework de fonctionnalités pré-designées pour angularJs, autre que angular-ui bootstrap, j’utilise désormais aussi angular matérial, dont les exemples sont plus complexes, mais qui propose vraiment un bon rendu .

Voici la page du framework :

https://material.angularjs.org/latest/

En cliquant sur « demo », on peut voir tout ce que l’on peut faire avec, il s’agit d’un design normalisé et responsive, permettant d’aller plus vite. Pas mal d’exemples utilisent la notation « controller as » mais moi je repasse tout cela en notation normale avec $scope pour normaliser mon code, rien n’empêche de continuer à utiliser controller as…

Du coup, je conserve ici une page html et un controlleur angularJs relativement traduits en Français, avec les fonctionnalités que je considère les plus intéressantes, pour pouvoir les intégrer plus vite. Mon but est vraiment d’accélérer au maximum la mise en place d’idées de base, en maitrisant bien ces frameworks, et aussi de faire du responsive. Le time Picker est vraiment sympa.

Fonctionnement


Dans la plupart des cas, on injecte la fonctionnalité dans le controleur . Si la fonctionnalité s’appelle par exemple $mdBottomSheet, alors on l’injecte comme cela dans le controleur  AngularJs :


.controller('AppCtrl', function($scope,$timeout,$q,$log,$mdDialog,$mdBottomSheet,$mdToast) {

};

Je ne mets pas le code interne dans cet exemple bien sur.
Cette fonctionnalité affiche un super menu qui apparait tout seul en bas d'une fenêtre.

 

Voir la page HTML avec les éléments en action,  en Français .


C’est ici .

 

Lire la suite