Эх сурвалжийг харах

-Dashboard: Améliorations UX/UI et correctifs mineurs :
- Placeholder de la taille du widget en cours de move
- Placeholder dynamique au resize d'un widget
- Correctifs des overflows
- Scrollbar custom pour composant dashboard
- Refactor fonctionnement de l'objet Dashboard + ajout de méthodes (addWidget, renderContent)

vmorreel 2 жил өмнө
parent
commit
98e876dcc8

+ 3 - 5
plugin/dashboard/Dashboard.class.php

@@ -12,19 +12,17 @@ class Dashboard extends Entity{
 	public $slug; //Slug (Texte)
 	public $scope; //Scope (Texte)
 	public $uid; //Uid (Texte)
-	
+
 	protected $TABLE_NAME = 'dashboard';
 	public $entityLabel = 'Dasboard';
 	public $fields = array(
 		'id' => array('type'=>'key', 'label' => 'Identifiant'),
 		'label' => array('type'=>'text','label' => 'Libellé'),
 		'slug' => array('type'=>'text','label' => 'Slug'),
-		'scope' => array('type'=>'text','label' => 'Scope'),
-		'uid' => array('type'=>'text','label' => 'Uid')
+		'scope' => array('type'=>'text','label' => 'Portée'),
+		'uid' => array('type'=>'text','label' => 'Identifiant unique')
 	);
 
 	//Colonnes indexées
 	public $indexes = array();
-	
 }
-?>

+ 6 - 6
plugin/dashboard/DashboardWidget.class.php

@@ -16,7 +16,7 @@ class DashboardWidget extends Entity{
 	public $width = 1; //Largeur (Nombre Entier)
 	public $height = 1; //Hauteur (Nombre Entier)
 	public $dashboard; //Tableau de bord (Nombre Entier)
-	
+
 	protected $TABLE_NAME = 'dashboard_widget';
 	public $entityLabel = 'Bloc de tableau de bord';
 	public $fields = array(
@@ -33,7 +33,7 @@ class DashboardWidget extends Entity{
 
 	//Colonnes indexées
 	public $indexes = array();
-	
+
 	//liste des Type possibles
 	public static function types($key=null){
 		$items = array(
@@ -46,7 +46,7 @@ class DashboardWidget extends Entity{
 
 
 	public static function model($slug = null){
-		
+
 		/*$model = array();
 		Plugin::callHook('widget',array(&$models));
 
@@ -54,19 +54,19 @@ class DashboardWidget extends Entity{
 			$models[$model->model] = $model;
 
 		if(!isset($modelUid)) return $models;*/
-		
+
 
 		//todo
 		$model = array(
 			'icon' => 'far fa-user',
 			'headerBackground' => 'rgb(0, 123, 255)',
 			'label' => 'Nouveau widget',
-			'content' => '<p class="text-center p-5">Cliquez sur l\'icone <i class="fas fa-ellipsis-v text-muted"></i> en haut à droite pour configurer votre widget</p>',
+			'content' => '<div class="text-center m-auto">Cliquez sur l\'icone <i class="fas fa-ellipsis-v text-muted mx-1"></i> en haut à droite pour configurer votre widget</div>',
 			'width' => 3,
 			'height' => 2
 		);
 		/*return isset($models[$modelUid]) ? $models[$modelUid] : $model;*/
-	
+
 		return $model;
 	}
 }

+ 23 - 26
plugin/dashboard/action.php

@@ -8,17 +8,14 @@
 		User::check_access('dashboard','read');
 		require_once(__DIR__.SLASH.'DashboardWidget.class.php');
 		require_once(__DIR__.SLASH.'Dashboard.class.php');
-		
-		$filters = array();
-
-		if(empty($_['uid']))  throw new Exception('Scope non spécifié');
 
+		$filters = array();
+		if(empty($_['scope']))  throw new Exception('Portée non spécifiée');
 		$filters['scope'] = $_['scope'];
 		if(empty($_['uid'])) $filters['uid'] = $_['uid'];
 
 		$dashboard = Dashboard::load($filters);
-
-		$widgets = DashboardWidget::loadAll(array('dashboard'=>$dashboard->id)); 
+		$widgets = DashboardWidget::loadAll(array('dashboard'=>$dashboard->id));
 		$response['widgets'] = array();
 
 		foreach($widgets as $widget){
@@ -28,7 +25,7 @@
 
 			$response['widgets'][] = $row ;
 		}
-		
+
 	});
 
 	Action::register('dashboard_widget_add',function(&$response){
@@ -36,14 +33,14 @@
 		User::check_access('dashboard','edit');
 		require_once(__DIR__.SLASH.'DashboardWidget.class.php');
 		require_once(__DIR__.SLASH.'Dashboard.class.php');
-		
-		if(empty($_['scope'])) throw new Exception('Scope non sépcifié');
-		$filters = array('scope'=>$_['scope']);
 
+		$filters = array();
+		if(empty($_['scope'])) throw new Exception('Portée non sépcifiée');
+		$filters = array('scope'=>$_['scope']);
 		if(!empty($_['uid'])) $filters['uid'] = $_['uid'];
 
 		$dashboard = Dashboard::load($filters);
-		if(!$dashboard) throw new Exception("Dashboard introuvable");
+		if(!$dashboard) throw new Exception("Tableau de bord introuvable");
 
 		$widget = new DashboardWidget();
 		$widget->dashboard = $dashboard->id;
@@ -63,32 +60,32 @@
 		User::check_access('dashboard','edit');
 		require_once(__DIR__.SLASH.'DashboardWidget.class.php');
 		require_once(__DIR__.SLASH.'Dashboard.class.php');
-		
-		if(empty($_['widget']['id'])) throw new Exception('Id non spécifié');
-		if(!isset($_['widget']['column'])) throw new Exception('Colonne non spécifié');
-		if(!isset($_['widget']['row'])) throw new Exception('Ligne non spécifié');
+
+		if(empty($_['widget']['id'])) throw new Exception('Aucun identifiant renseigné');
+		if(!isset($_['widget']['column'])) throw new Exception('Colonne non spécifiée');
+		if(!isset($_['widget']['row'])) throw new Exception('Ligne non spécifiée');
 
 		$widget = DashboardWidget::getById($_['widget']['id'],1);
 		if(!$widget) throw new Exception('Widget introuvable');
-		
+
 		$widget->column = $_['widget']['column'];
 		$widget->row = $_['widget']['row'];
 		$widget->save();
 	});
-	
+
 	Action::register('dashboard_widget_resize',function(&$response){
 		global $_;
 		User::check_access('dashboard','edit');
 		require_once(__DIR__.SLASH.'DashboardWidget.class.php');
 		require_once(__DIR__.SLASH.'Dashboard.class.php');
-		
-		if(empty($_['widget']['id'])) throw new Exception('Id non spécifié');
+
+		if(empty($_['widget']['id'])) throw new Exception('Aucun identifiant renseigné');
 		if(!isset($_['widget']['width'])) throw new Exception('Largeur non spécifiée');
 		if(!isset($_['widget']['height'])) throw new Exception('Hauteur non spécifiée');
 
 		$widget = DashboardWidget::getById($_['widget']['id'],1);
 		if(!$widget) throw new Exception('Widget introuvable');
-		
+
 		$widget->width = $_['widget']['width'];
 		$widget->height = $_['widget']['height'];
 		$widget->save();
@@ -99,12 +96,12 @@
 		User::check_access('dashboard','delete');
 		require_once(__DIR__.SLASH.'DashboardWidget.class.php');
 		require_once(__DIR__.SLASH.'Dashboard.class.php');
-		
-		if(empty($_['widget']['id'])) throw new Exception('Id non spécifié');
+
+		if(empty($_['widget']['id'])) throw new Exception('Aucun identifiant renseigné');
 
 		$widget = DashboardWidget::getById($_['widget']['id'],1);
 		if(!$widget) throw new Exception('Widget introuvable');
-		
+
 		DashboardWidget::deleteById($widget->id);
 	});
 
@@ -114,12 +111,12 @@
 		User::check_access('dashboard','edit');
 		require_once(__DIR__.SLASH.'DashboardWidget.class.php');
 		require_once(__DIR__.SLASH.'Dashboard.class.php');
-		
-		if(empty($_['id'])) throw new Exception('Id non spécifié');
+
+		if(empty($_['id'])) throw new Exception('Aucun identifiant renseigné');
 
 		$widget = DashboardWidget::getById($_['id'],1);
 		if(!$widget) throw new Exception('Widget introuvable');
-		
+
 		$response['widget'] = $widget->toArray();
 	});
 

+ 138 - 83
plugin/dashboard/css/component.css

@@ -1,132 +1,187 @@
+/* Scrollbar */
+.dashboard-container *::-webkit-scrollbar {
+    width: 0.5rem;
+}
+.dashboard-container *::-webkit-scrollbar {
+    width: 5px;
+    height: 5px;
+}
+.dashboard-container *::-webkit-scrollbar-button {
+    width: 0px;
+    height: 0px;
+}
+.dashboard-container *::-webkit-scrollbar-thumb {
+    background: #bbbbbb;
+    border: 0px none #ffffff;
+    border-radius: 50px;
+}
+.dashboard-container *::-webkit-scrollbar-thumb:hover {
+    background: #949494;
+}
+.dashboard-container *::-webkit-scrollbar-thumb:active {
+    background: #707070;
+}
+.dashboard-container *::-webkit-scrollbar-track {
+    background: inherit;
+    border: 0px none #ffffff;
+    border-radius: 50px;
+}
+.dashboard-container *::-webkit-scrollbar-track:hover {
+    background: #cecece;
+}
+.dashboard-container *::-webkit-scrollbar-track:active {
+    background: #d3d3d3;
+}
+.dashboard-container *::-webkit-scrollbar-corner {
+    background: transparent;
+}
+
 
 .dashboard-container {
-  background-color: #efefef;
+	background-color: #efefef;
 }
-
 .dashboard-container .dashboard-grid{
-  display: grid;
-  list-style-type: none;
-  margin: 0;
-  padding: 0;
-  grid-column-gap: 0px;
-  grid-row-gap: 0px;
-  position: relative;
+	display: grid;
+	list-style-type: none;
+	margin: 0;
+	padding: 0;
+	grid-column-gap: 0px;
+	grid-row-gap: 0px;
+	position: relative;
 }
-
 .dashboard-container .dashboard-placeholder{
-  box-shadow: inset 0px 0px 1px 2px transparent;
-  list-style-type: none;
-  margin: 0;
-  min-height: 100px;
-  transition: all 0.2s ease-in-out;
+	box-shadow: inset 0px 0px 1px 2px transparent;
+	border-radius: 10px;
+	list-style-type: none;
+	margin: 0;
+	min-height: 100px;
+	transition: all 0.15s ease-in;
+}
+.dashboard-placeholder{
+	transition: background 0.15s linear;
+	cursor: pointer;
+	position: relative;
+}
+.dashboard-placeholder:hover{
+	background-color: #e6e6e6;
+}
+.dashboard-placeholder .placeholder-add{
+	display: inline-block;
+	opacity: 0;
+	text-align: center;
+	height: 100%;
+	vertical-align: bottom;
+	line-height: 100%;
+	position: absolute;
+	color: #adadad;
+	top: calc(50% - 15px);
+	left: calc(50% - 15px);
+	font-size: 30px;
+	transition: opacity 0.15s linear;
+}
+.dashboard-placeholder:hover .placeholder-add{
+	opacity: 1;
 }
-
 .dashboard-container .dashboard-placeholder.bordered{
-  box-shadow: inset 0px 0px 1px 1px #bbbbbb;
+	border-radius: 0;
+	box-shadow: inset 0px 0px 1px 1px #bbbbbb;
 }
-
 .dashboard-container .dashboard-placeholder.drag-over{
-  box-shadow: inset 0px 0px 1px 2px #bbbbbb;
+	background-color: #dfeeff;
+	box-shadow: inset 0px 0px 1px 2px #9fcdff;
 }
-
 .dashboard-container .dashboard-widget{
-  position: absolute;
-  background-color: #ffffff;
-  border-radius: 10px;
-  box-shadow: 0 0 20px 0 #cecece!important;
-  transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
+	position: absolute;
+	background-color: #ffffff;
+	border-radius: 10px;
+	box-shadow: 0 0 20px 0 #cecece!important;
+	transition: transform 0.15s ease-in, box-shadow 0.15s ease-in;
 }
-
 .dashboard-widget.ui-draggable-dragging{
-  transform: scale(1.03);
-  box-shadow: 0 0 40px 10px rgba(0,0,0,0.1)!important;
+	transform: scale(1.03);
+	box-shadow: 0 0 40px 10px rgba(0,0,0,0.1)!important;
 }
-
 .dashboard-widget .dashboard-widget-header{
-  padding: 5px;
-  background-color: #222222;
-  color: #ffffff;
-  border-radius: 10px 10px 0 0;
-  display: flex;
+	padding: 5px;
+	background-color: #222222;
+	color: #ffffff;
+	border-radius: 10px 10px 0 0;
+	display: flex;
 }
-
 .dashboard-widget .dashboard-widget-content{
-  padding: 5px;
-  max-height: 100%;
-  overflow: auto;
+	padding: 5px;
+	height: calc(100% - 34px);
+	max-height: calc(100% - 34px);
+	overflow: auto;
+	display: flex;
 }
-
 .dashboard-widget .dashboard-widget-header .widget-header-icon,
 .dashboard-widget .dashboard-widget-header .widget-header-title,
 .dashboard-widget .dashboard-widget-header .widget-header-options{
-  display: inline-block;
-  vertical-align: top;
+	display: inline-block;
+	vertical-align: top;
 }
-
 .dashboard-widget .dashboard-widget-header .widget-header-icon{
-  padding-left: 5px;
+	padding-left: 5px;
 }
-
 .dashboard-widget .dashboard-widget-header .widget-header-title{
-  padding-left: 5px;
-  flex-grow: 1;
+	padding-left: 5px;
+	flex-grow: 1;
 }
-
 .dashboard-widget.widget-draggable .dashboard-widget-header .widget-header-title{
-  cursor: move;
+	cursor: move;
 }
-
-
 .dashboard-widget .dashboard-widget-header .widget-header-options{
-  list-style-type: none;
-  margin: 0;
-  cursor: pointer;
-  float: right;
-  padding: 0;
+	list-style-type: none;
+	margin: 0;
+	cursor: pointer;
+	float: right;
+	padding: 0;
 }
-
 .dashboard-widget .dashboard-widget-header .widget-header-options:after{
-  content :'';
-  display: block;
-  clear: both;
+	content :'';
+	display: block;
+	clear: both;
 }
-
 .dashboard-widget .widget-header-options li{
-  display: inline-block;
+	display: inline-block;
 }
-
-
 .dashboard-widget .dashboard-widget-resize{
-  opacity: 0;
-  position: absolute;
-  bottom: -3px;
-  right: 3px;
-  cursor: all-scroll;
-  transform: rotate(45deg);
-  transition: all 0.2s ease-in-out;
-  cursor: nw-resize;
+	opacity: 0;
+	position: absolute;
+	bottom: -2px;
+	right: 0px;
+	cursor: all-scroll;
+	transform: rotate(45deg);
+	transition: all 0.2s ease-in-out;
+	cursor: nw-resize;
 }
-
 .dashboard-widget:hover .dashboard-widget-resize {
-  opacity: 0.3;
+	opacity: 0.3;
 }
-
 .dashboard-widget .widget-header-options li{
-  padding-left: 3px;
-  padding-right: 3px;
-  opacity: 0.5;
-  transition: opacity 0.2s ease-in-out;
+	padding: 0 0.5rem;
+	opacity: 0.5;
+	transition: opacity 0.2s ease-in-out;
 }
-
 .dashboard-widget .widget-header-options .widget-option-delete,
 .dashboard-widget .dashboard-widget-resize{
-  display: none;
+	display: none;
 }
 .dashboard-widget.widget-removable .widget-header-options .widget-option-delete,
 .dashboard-widget.widget-resizeable .dashboard-widget-resize{
-  display: inline-block;
+	display: inline-block;
 }
-
 .dashboard-widget .widget-header-options li:hover{
-  opacity: 1;
+	opacity: 1;
+}
+.dashboard-configure-menu li.active i.text-muted{
+	color: #ffffff!important;
+}
+.dashboard-configure-menu li{
+	cursor: pointer;
+	transition: background 0.2s ease-in-out;
+}
+.dashboard-configure-menu li:hover{
+	background: #f3f3f3;
 }

+ 0 - 48
plugin/dashboard/css/main.css

@@ -6,55 +6,7 @@
 	padding:0;
 }
 
-.plugin-dashboard #dashboards {}
-
-/* formulaire d'édition de tableau de bord */
-.plugin-dashboard .dashboard-form {}
-
-
-
 html.module-index,
 .module-index body{
 	background-color: #efefef;
-}
-
-.dashboard-placeholder{
-	transition: background 0.15s linear;
-	cursor: pointer;
-	position: relative;
-	
-}
-.dashboard-placeholder:hover{
-	background-color: #e6e6e6;
-	border-radius: 10px;
-}
-
-.dashboard-placeholder .placeholder-add{
-	display: inline-block;
-	opacity: 0;
-	text-align: center;
-	height: 100%;
-	vertical-align: bottom;
-	line-height: 100%;
-	position: absolute;
-	color: #b5b3b3;
-	top: calc(50% - 15px);
-	left: calc(50% - 15px);
-	font-size: 30px;
-	transition: opacity 0.15s linear;
-}
-.dashboard-placeholder:hover .placeholder-add{
-	opacity: 1;
-}
-
-.dashboard-configure-menu li.active i.text-muted{
-	color: #ffffff!important;
-}
-
-.dashboard-configure-menu li{
-	cursor: pointer;
-	transition: background 0.2s ease-in-out;
-}
-.dashboard-configure-menu li:hover{
-	background: #f3f3f3;
 }

+ 49 - 65
plugin/dashboard/dashboard.plugin.php

@@ -1,7 +1,4 @@
 <?php
-
-
-
 //Déclaration d'un item de menu dans le menu principal
 function dashboard_menu(&$menuItems){
 	global $myUser;
@@ -15,22 +12,22 @@ function dashboard_menu(&$menuItems){
 	);
 }
 
-//Cette fonction va generer une page quand on clique sur Dashboard dans menu
+//Cette fonction va générer une page quand on clique sur Dashboard dans menu
 function dashboard_page(){
-	global $_;
+	global $_, $myUser;
 	if(isset($_['module'])) return;
 	$page = !isset($_['page']) ? 'list.dashboard' : $_['page'];
 	$page = str_replace('..','',$page);
 	$file = __DIR__.SLASH.'page.'.$page.'.php';
 	if(!file_exists($file)) throw new Exception("Page ".$page." inexistante");
-	
+
 	require_once($file);
 }
 
 //Fonction executée lors de l'activation du plugin
 function dashboard_install($id){
 	if($id != 'fr.core.dashboard') return;
-	Entity::install(__DIR__);	
+	Entity::install(__DIR__);
 }
 
 //Fonction executée lors de la désactivation du plugin
@@ -42,15 +39,10 @@ function dashboard_uninstall($id){
 //Déclaration des sections de droits du plugin
 Right::register('dashboard',array('label'=>'Gestion des droits sur le plugin Dashboard'));
 
-
-//cette fonction comprends toutes les actions du plugin qui ne nécessitent pas de vue html
-function dashboard_action(){
-	require_once(__DIR__.SLASH.'action.php');
-}
 //Déclaration du menu de réglages
 function dashboard_menu_setting(&$settingMenu){
 	global $myUser;
-	
+
 	if(!$myUser->can('dashboard','configure')) return;
 	$settingMenu[]= array(
 		'sort' =>1,
@@ -77,74 +69,66 @@ Configuration::setting('dashboard',array(
     "Général",
     //'dashboard_enable' => array("label"=>"Activer","type"=>"boolean"),
 ));
-  
+
 function dashboard_application_bottom(){
 ?>
  <template id="dashboard-widget-template">
-        <div class="dashboard-widget" data-id="{{id}}">
-        	<div class="dashboard-widget-header">
-        		<div class="widget-header-icon"><i class="fas fa-question"></i></div>
-        		<div class="widget-header-title"></div>
-        		<ul class="widget-header-options"></ul>
-        	</div>
-        	<div class="dashboard-widget-content"></div>
-        	<div class="dashboard-widget-resize"><i class="fas fa-chevron-right"></i></div>
+    <div class="dashboard-widget" data-id="{{id}}" data-width="{{width}}" data-height="{{height}}">
+    	<div class="dashboard-widget-header">
+    		<div class="widget-header-icon"><i class="fas fa-question"></i></div>
+    		<div class="widget-header-title">{{label}}</div>
+    		<ul class="widget-header-options"></ul>
     	</div>
+    	<div class="dashboard-widget-content"></div>
+    	<div class="dashboard-widget-resize py-1 px-2"><i class="fas fa-chevron-right"></i></div>
+	</div>
  </template>
 
-
-	<!-- Modal -->
-	<div class="modal fade" id="dashboardModal" tabindex="-1" role="dialog"aria-hidden="true">
-	  <div class="modal-dialog modal-lg" role="document">
-	    <div class="modal-content">
-	      <div class="modal-header">
-	        <h5 class="modal-title" id="exampleModalLabel">Configuration</h5>
-	        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
-	          <span aria-hidden="true">&times;</span>
-	        </button>
-	      </div>
-	      <div class="modal-body p-0">
-	      	<div class="row">
-	      		<div class="col-md-4">
-		      		<ul class="list-group rounded-0 dashboard-configure-menu" id="dashboard-configure-menu">
-					  <li class="list-group-item border-top-0 active"><i class="far fa-window-maximize text-muted"></i> Type</li>
-					  <li class="list-group-item"><i class="fas fa-list-ul text-muted"></i> Propriétés</li>
-					  <li class="list-group-item border-bottom-0"><i class="fas fa-palette text-muted"></i> Style</li>
-					</ul>
-				</div>
-				<div class="col-md-8">
-
+<!-- Modal Dashboard -->
+<div class="modal fade" id="dashboardModal" tabindex="-1" role="dialog"aria-hidden="true">
+	<div class="modal-dialog modal-lg" role="document">
+		<div class="modal-content">
+			<div class="modal-header">
+				<h5 class="modal-title" id="exampleModalLabel">Configuration</h5>
+				<button type="button" class="close" data-dismiss="modal" aria-label="Close">
+					<span aria-hidden="true">&times;</span>
+				</button>
+			</div>
+			<div class="modal-body p-0">
+				<div class="row">
+					<div class="col-md-4">
+				  		<ul class="list-group rounded-0 dashboard-configure-menu" id="dashboard-configure-menu">
+							<li class="list-group-item border-top-0 active"><i class="far fa-window-maximize text-muted"></i> Type</li>
+							<li class="list-group-item"><i class="fas fa-list-ul text-muted"></i> Propriétés</li>
+							<li class="list-group-item border-bottom-0"><i class="fas fa-palette text-muted"></i> Style</li>
+						</ul>
+					</div>
+					<div class="col-md-8">
+
+					</div>
 				</div>
 			</div>
-	      </div>
-	      <div class="modal-footer">
-	        <button type="button" class="btn btn-primary">Enregistrer</button>
-	      </div>
-	    </div>
-	  </div>
+			<div class="modal-footer">
+				<button type="button" class="btn btn-primary">Enregistrer</button>
+			</div>
+		</div>
 	</div>
-
+</div>
    <?php
 }
 
-
-
 //Déclation des assets
-
-
-
-Plugin::addCss("/css/main.css"); 
-Plugin::addJs("/js/main.js"); 
-Plugin::addCss("/css/component.css"); 
-Plugin::addJs("/js/dashboard.js"); 
-Plugin::addJs("/js/component.js"); 
+Plugin::addCss("/css/main.css");
+Plugin::addCss("/css/component.css");
+Plugin::addJs("/js/main.js");
+Plugin::addJs("/js/dashboard.js");
+Plugin::addJs("/js/component.js");
 
 //Mapping hook / fonctions
 Plugin::addHook("install", "dashboard_install");
-Plugin::addHook("uninstall", "dashboard_uninstall"); 
-Plugin::addHook("menu_main", "dashboard_menu"); 
-Plugin::addHook("page", "dashboard_page"); 
+Plugin::addHook("uninstall", "dashboard_uninstall");
+Plugin::addHook("menu_main", "dashboard_menu");
+Plugin::addHook("page", "dashboard_page");
 Plugin::addHook("menu_setting", "dashboard_menu_setting");
 Plugin::addHook("content_setting", "dashboard_content_setting");
 Plugin::addHook("application_bottom", "dashboard_application_bottom");
-?>

+ 29 - 32
plugin/dashboard/js/component.js

@@ -1,23 +1,20 @@
-
 function init_components_dashboard(input){
-
 	var data = $.extend({
-		column : 8,
-		line : 8,
-		onMove : 'dashboard_widget_move',
-		onRemove : 'dashboard_widget_delete',
-		onResize : 'dashboard_widget_resize',
-		onConfigure : 'dashboard_widget_configure',
-		onAdd : 'dashboard_widget_add'
+		column: 8,
+		line: 8,
+		onMove: 'dashboard_widget_move',
+		onRemove: 'dashboard_widget_delete',
+		onResize: 'dashboard_widget_resize',
+		onConfigure: 'dashboard_widget_configure',
+		onAdd: 'dashboard_widget_add'
 	},{
-		column : input.attr('data-column'),
-		line : input.attr('data-line'),
-		scope : input.attr('data-scope'),
-		uid : input.attr('data-uid'),
-		id : input.attr('data-id'),
+		column: input.attr('data-column'),
+		line: input.attr('data-line'),
+		scope: input.attr('data-scope'),
+		uid: input.attr('data-uid'),
+		id: input.attr('data-id'),
 	});
 
-
 	component = input.data('component');
 	if(!component){
 		component = new Dashboard({
@@ -28,12 +25,11 @@ function init_components_dashboard(input){
 		input.data('component',component);
 	}
 
-	
 	if(data.scope){
 		$.action({
-			action : 'dashboard_widget_search',
-			scope : data.scope,
-			uid : data.uid
+			action: 'dashboard_widget_search',
+			scope: data.scope,
+			uid: data.uid
 		},function(response){
 			component.addWidgets(response.widgets);
 		});
@@ -72,40 +68,41 @@ function init_components_dashboard(input){
 			row : placeholder.row,
 			column : placeholder.column
 		},function(widget){
-			
 			component.addWidgets([widget]);
 		});
 	});
 
 	component.on('move',function(widget){
-		console.log('Widget has moved',widget);
+		console.log('Widget has moved', widget);
+
 		$.action({
 			action : data.onMove,
 			widget : widget
 		});
-		
+
 	}).on('resize',function(widget){
-		console.log('Widget has resized',widget);
+		console.log('Widget has resized', widget);
+
 		$.action({
-			action : data.onResize,
-			widget : widget
+			action: data.onResize,
+			widget: widget
 		});
+
 	}).on('delete',function(widget){
-		if(!confirm('Êtes-vous sûr de vouloir supprimer cet item?')) return true;
-		console.log('Widget has removed',widget);
+		if(!confirm('Êtes-vous sûr de vouloir supprimer ce widget?')) return true;
+		console.log('Widget has removed', widget);
+
 		$.action({
 			action : data.onRemove,
 			widget : widget
 		});
+
 	}).on('configure',function(id){
 		$.action({
 			action : data.onConfigure,
 			id : id
 		},function(response){
-			
+
 		});
 	});
-
-
-}
-
+}

+ 272 - 188
plugin/dashboard/js/dashboard.js

@@ -1,72 +1,136 @@
 class Dashboard {
-  constructor(options) {
-    this.options = $.extend({
+	//@TODO:
+	// - Pouvoir indiquer que la dashboard à une taille dynamique :
+	// 		- On laisse toujours 2-3 lignes de placeholder sous le dernier widget
+	// 		- On recalcule le nb de lignes de placeholder en plus à afficher sous le dernier widget à chaque add / resize / move / delete d'un widget
+	constructor(options) {
+		this.options = $.extend({
 			columnNumber : 1,
 			lineNumber : 1,
 			placeHolderPadding : 10
-	},options);
-    this.widgets = {};
+		},options);
+		this.widgets = {};
 
-    var grid  = '<div class="dashboard-grid" >';
-	for(var u =0 ; u<options.lineNumber;u++){
-		for(var i =0 ; i<options.columnNumber;i++){
-			grid += '<div data-row="'+u+'" data-column="'+i+'" style="grid-row: '+(u+1)+" / "+(u+1)+";grid-column: "+(i+1)+" / "+(i+1)+';padding:'+this.options.placeHolderPadding+'px;" class="dashboard-placeholder"><i class="fa fa-plus placeholder-add"></i></div>';
+	  	var grid = '<div class="dashboard-grid">';
+		for(var u=0 ; u<options.lineNumber;u++){
+			for(var i=0 ; i<options.columnNumber;i++){
+				grid += '<div data-row="'+u+'" data-column="'+i+'" style="grid-area: '+(u+1)+" / "+(i+1)+" / "+(u+1)+" / "+(i+1)+';" class="dashboard-placeholder"><i class="fa fa-plus placeholder-add"></i></div>';
+			}
 		}
-	}
-	grid  += '</div>';
-	this.grid = $(grid);
-	this.options.element.addClass('dashboard-container');
-	this.options.element.css({width:'100%',height:'1000px'});
-	this.options.element.append(this.grid);
-	this.widgetTemplate = $('#dashboard-widget-template').html();
-	this.placeHolders = $('.dashboard-placeholder');
-	this.triggers = [];
-	
-
-	var object = this;
-
-	this.placeHolders.droppable({
-	  tolerance : 'pointer',
-      classes: {
-        "ui-droppable-hover": 'drag-over'
-      },
-      drop: function( event, ui ) {
-      	var data = $(this).data();
-        var widgetId = ui.draggable.attr('data-id');
-        object.widgets[widgetId].column = data.column;
-        object.widgets[widgetId].row = data.row;
-        object.renderPositions();
-        object.trigger('move',object.widgetToArray(object.widgets[widgetId]));
-      }
-    });
-
-	$(window).resize(function() {
-	  	object.renderPositions();
-	});
-
-	$(window).mousemove(function(e){
-		if(!object.resizing) return;
-		$('.dashboard-widget').css('z-index','');
-		object.resizing.element.css('z-index',1000);
-		var objectPosition = object.resizing.element.position();
-		object.resizing.element.css('width',(e.clientX - objectPosition.left)+'px');
-		object.resizing.element.css('height',(e.clientY - objectPosition.top - 50)+'px');
-
-	});
-	$(window).mouseup(function(){
-		if(!object.resizing) return;
-		$('.dashboard-placeholder').removeClass('bordered');
-		var firstPlaceHolder = object.placeHolders.eq(0);
-		var placeholderWidth = firstPlaceHolder.outerWidth();
-		var placeholderHeight = firstPlaceHolder.outerHeight();
-		
-		object.widgets[object.resizing.id].width = Math.round(object.resizing.element.outerWidth() / placeholderWidth);
-		object.widgets[object.resizing.id].height = Math.round(object.resizing.element.outerHeight() / placeholderHeight);
-		object.renderPositions();
-		object.trigger('resize',object.widgetToArray(object.widgets[object.resizing.id]) );
-		object.resizing = null;
-	});
+		grid += '</div>';
+
+		this.grid = $(grid);
+		this.options.element.addClass('dashboard-container');
+		this.options.element.css({width:'100%',height:'100%'});
+		this.options.element.append(this.grid);
+		this.widgetTemplate = $('#dashboard-widget-template').html();
+		this.placeHolders = $('.dashboard-placeholder');
+		this.placeHolders.initialWidth = this.placeHolders.eq(0).outerWidth();
+		this.placeHolders.initialHeight = this.placeHolders.eq(0).outerHeight();
+		this.placeHolders.render = function(placeholder, offsets, delay){
+			var delay = delay!=null ? delay : 200;
+			var offsets = $.extend({
+				rowStart: 0,
+				rowEnd: 0,
+				columnStart: 0,
+				columnEnd: 0
+			}, offsets);
+
+			var rowStart = parseInt(placeholder.attr('data-row'))+1+parseInt(offsets.rowStart);
+			var rowEnd = parseInt(placeholder.attr('data-row'))+1+parseInt(offsets.rowEnd);
+			var columnStart = parseInt(placeholder.attr('data-column'))+1+parseInt(offsets.columnStart);
+			var columnEnd = parseInt(placeholder.attr('data-column'))+1+parseInt(offsets.columnEnd);
+
+			setTimeout(function(){
+				placeholder.css({
+					'grid-row': rowStart+'/'+rowEnd,
+					'grid-column': columnStart+'/'+columnEnd,
+				});
+			}, delay);
+		}
+
+		this.triggers = [];
+
+		var object = this;
+		this.placeHolders.droppable({
+			tolerance: 'pointer',
+			classes: { "ui-droppable-hover": 'drag-over' },
+			over: function(event, ui){
+				//@TODO:
+				// - Gérer les overflow de la grid => ex: lacher un widget en bas de gris doit recalculer le placeholder le plus bas possible sans sortir du cadre
+				// - Ne pas pouvoir superposer des widgets
+				var currentWidget = ui.draggable;
+				var offsets = {
+					rowEnd: currentWidget.attr('data-height'),
+					columnEnd: currentWidget.attr('data-width')
+				}
+				//On custom la grid du placeholder avec la taille du widget
+				object.placeHolders.render($(this), offsets, 0);
+			},
+			out: function(event, ui){
+				//On reset la grid du placeholder
+				object.placeHolders.render($(this));
+			},
+			drop: function(event, ui) {
+				var placeholder = $(this);
+				//On reset la grid du placeholder
+				object.placeHolders.render(placeholder);
+
+				var data = placeholder.data();
+				var widgetId = ui.draggable.attr('data-id');
+
+				object.widgets[widgetId].column = data.column;
+				object.widgets[widgetId].row = data.row;
+				object.renderPositions();
+				object.trigger('move', object.widgetToArray(object.widgets[widgetId]));
+			}
+		});
+
+		$(window).resize(function() {
+			object.renderPositions();
+		});
 
+		$(window).mousemove(function(e){
+			if(!object.resizing) return;
+			$('.dashboard-widget').css('z-index', '');
+			object.resizing.element.css('z-index', 1000);
+
+			var objectPosition = object.resizing.element.position();
+			object.resizing.element.css('width',(e.clientX - objectPosition.left)+'px');
+			object.resizing.element.css('height',(e.clientY - objectPosition.top - 50)+'px');
+
+			//On affiche la taille finale du widget avec de la couleur sur les placeholders pour guider le resize
+			object.placeHolders.removeClass('drag-over');
+			var width = Math.round(object.resizing.element.outerWidth() / object.placeHolders.initialWidth);
+			var height = Math.round(object.resizing.element.outerHeight() / object.placeHolders.initialHeight);
+
+			var resizePlaceholder = [];
+			for(var i=parseInt(object.resizing.column); i<parseInt(parseInt(object.resizing.column)+width); i++){
+				for(var j=parseInt(object.resizing.row); j<parseInt(parseInt(object.resizing.row)+height); j++){
+					resizePlaceholder.push($('.dashboard-placeholder[data-row="'+j+'"][data-column="'+i+'"]').get(0));
+				}
+			}
+			$(resizePlaceholder).addClass('drag-over')
+		});
+		$(window).mouseup(function(){
+			if(!object.resizing) return;
+			object.placeHolders.removeClass('drag-over bordered');
+
+			var width = Math.round(object.resizing.element.outerWidth() / object.placeHolders.initialWidth);
+			var height = Math.round(object.resizing.element.outerHeight() / object.placeHolders.initialHeight);
+
+			object.widgets[object.resizing.id].width = width;
+			object.widgets[object.resizing.id].height = height;
+
+			object.widgets[object.resizing.id].element.attr({
+				'data-width': width,
+				'data-height': height,
+			})
+
+			object.renderPositions();
+			object.trigger('resize', object.widgetToArray(object.widgets[object.resizing.id]));
+			object.resizing = null;
+		});
 
 		$('.dashboard-placeholder').click(function(){
 			var placeholder = $(this);
@@ -76,103 +140,113 @@ class Dashboard {
 				column: placeholder.attr('data-column')
 			});
 		});
+	}
 
+	on(event,callback){
+		if(!this.triggers[event]) this.triggers[event] = [];
+		this.triggers[event].push(callback);
 
-  }
-
-  on(event,callback){
-  	if(!this.triggers[event]) this.triggers[event] = [];
-  	this.triggers[event].push(callback);
-  	return this;
-  }
-  trigger(event,data){
-  	if(!this.triggers[event]) return;
-  	if(!data) data = {};
-  	var abort = false;
-  	for(var k in this.triggers[event]){
-  		if(this.triggers[event][k](data) === true) abort = true;
-  	}
-  	return abort;
-  }
-
-  //Ajoute un ou plusieurs widgets
-  addWidgets(widgets){
-  	var object = this;
-  	for(var k in widgets){
-  		var widgetOptions = $.extend({
-  			id : Math.floor(Math.random() * 100)+Date.now(),
-  			draggable : true,
-  			resizeable : true,
-  			removable : true,
-  			width: 1,
-  			height : 1,
-  			row : 0,
-  			column :0,
-  			options : [],
-  			label : 'Sans titre',
-  			content : "Aucun contenu"
-  		},widgets[k]);
-
-
-  		widgetOptions.options.push({icon : 'fas fa-ellipsis-v', class:'widget-option-configure' , label : 'Configurer' , click : function(element){ object.configureWidget($(element).closest('.dashboard-widget').attr('data-id')); }});
-  		widgetOptions.options.push({icon : 'fa fa-times', class:'widget-option-delete' , label : 'Supprimer' , click : function(element){ object.deleteWidget($(element).closest('.dashboard-widget').attr('data-id')); }});
-
-
-	  	var widgetElement = $(object.widgetTemplate);
-	  	widgetOptions.element = widgetElement;
-	  	object.widgets[widgetOptions.id] = widgetOptions;
-	  	object.grid.append(widgetElement);
-	  	
-		 	widgetElement.draggable(
-		 		{ 
-		 			handle: '.widget-header-title', 
-		 			containment: object.grid,
-		 			"ui-draggable-dragging" : "widget-dragging",
-		 			start : function(event){
-		 				
-		 				var element = $(event.currentTarget);
-		 				if(!element.hasClass('widget-draggable'))return false;
-
-		 				$('.dashboard-widget').css('z-index','');
-		 				element.css('z-index',1000);
-		 			}
-		 		}
-		 	);
-		 	widgetElement.find('.dashboard-widget-resize').mousedown(function(){
-		 		var element = $(this).closest('.dashboard-widget');
-		 		if(!element.hasClass('widget-resizeable'))return;
-		 		object.resizing = object.widgets[element.attr('data-id')];
-		 		$('.dashboard-placeholder').addClass('bordered');
-		 	});
-		 	widgetElement.find('.widget-option-delete').click(function(){
-		 		object.deleteWidget($(this).closest('.dashboard-widget').attr('data-id'));
-		 	});
-
-		 	object.trigger('add',object.widgetToArray(widgetOptions));
+		return this;
+	}
+
+	trigger(event,data){
+		if(!this.triggers[event]) return;
+		if(!data) data = {};
+		var abort = false;
+		for(var k in this.triggers[event]){
+			if(this.triggers[event][k](data) === true) abort = true;
+		}
+		return abort;
+	}
+
+	//Ajoute un ou plusieurs widgets
+	addWidgets(widgets){
+		var object = this;
+		for(var k in widgets){
+			var widgetOptions = $.extend({
+				id: Math.floor(Math.random() * 100) + Date.now(),
+				draggable: true,
+				resizeable: true,
+				removable: true,
+				width: 1,
+				height: 1,
+				row: 0,
+				column:0,
+				options: [],
+				label: 'Sans titre',
+				content: "Aucun contenu"
+			}, widgets[k]);
+
+			widgetOptions.options.push({
+				icon: 'fas fa-ellipsis-v',
+				class: 'widget-option-configure',
+				label: 'Configurer',
+				click: function(element){
+					object.configureWidget($(element).closest('.dashboard-widget').attr('data-id'));
+				}
+			});
+			widgetOptions.options.push({
+				icon: 'fa fa-times',
+				class: 'widget-option-delete',
+				label: 'Supprimer',
+				click: function(element){
+					object.deleteWidget($(element).closest('.dashboard-widget').attr('data-id'));
+				}
+			});
+		  	object.addWidget(widgetOptions)
 	 	}
+	 	//Gestion de la disposition des widgets sur la dashboard
+		this.renderPositions();
+	}
+
+	//Ajout d'un widget en fonction de paramètre
+	addWidget(widgetOptions){
+		var object = this;
+		//Ajout du widget au tableau de widgets interne
+		object.widgets[widgetOptions.id] = widgetOptions;
 
+		//Render de la template pour les propriétés du widget courant
+		object.renderContent(widgetOptions.id);
 
-  	this.renderContents();
-  	this.renderPositions();
+	  	var widgetElement = $(object.widgets[widgetOptions.id].element);
+	  	object.grid.append(widgetElement);
 
-  	
-  }
+	 	widgetElement.draggable({
+ 			handle: '.widget-header-title',
+ 			containment: object.grid,
+ 			"ui-draggable-dragging" : "widget-dragging",
+ 			start: function(event){
+ 				var element = $(event.currentTarget);
+ 				if(!element.hasClass('widget-draggable')) return false;
 
-  //Supprime un widget en fonctiond e son id
-  deleteWidget(id){
-  	var widget = this.widgets[id];
-  	if(this.trigger('delete',this.widgetToArray(widget))) return;
-  	this.widgets[id].element.remove();
-  }
+ 				$('.dashboard-widget').css('z-index','');
+ 				element.css('z-index',1000);
+ 			}
+ 		});
+	 	widgetElement.find('.dashboard-widget-resize').mousedown(function(){
+	 		var element = $(this).closest('.dashboard-widget');
+	 		if(!element.hasClass('widget-resizeable'))return;
+	 		object.resizing = object.widgets[element.attr('data-id')];
+	 		$('.dashboard-placeholder').addClass('bordered');
+	 	});
+	 	object.trigger('add', object.widgetToArray(widgetOptions));
+	}
 
-  configureWidget(id){
-  	$('#dashboardModal').modal('show');
-  	this.trigger('configure',id);
-  }
+	//Supprime un widget en fonction de son id
+	deleteWidget(id){
+		var widget = this.widgets[id];
+		if(this.trigger('delete',this.widgetToArray(widget))) return;
+		this.widgets[id].element.remove();
+	}
+
+	configureWidget(id){
+		$('#dashboardModal').modal('show');
+		this.trigger('configure', id);
+	}
 
-  widgetToArray(widget){
-  	if(!widget) return {};
-  	return {
+	widgetToArray(widget){
+		if(!widget) return {};
+		return {
 		    id: widget.id,
 		    draggable: widget.draggable,
 		    resizeable: widget.resizeable,
@@ -186,55 +260,65 @@ class Dashboard {
 		    icon: widget.icon,
 		    headerBackground: widget.headerBackground
 		};
-  }
-
-  //rafraichis le contenu des widgets en fonction du tableaud e widgets interne
-  renderContents() {
-	for(var k in this.widgets){
-		var widget = this.widgets[k];
-		if(widget.id) widget.element.attr('data-id',widget.id);
-		if(widget.label !== null) widget.element.find('.widget-header-title').html(widget.label);
-		if(widget.icon !== null) widget.element.find('.widget-header-icon i').attr('class',widget.icon);
+	}
+
+	//Render d'un widget en fonction de ses paramètres
+	//Gère le refresh du widget si l'élément est déjà existant
+	renderContent(id) {
+		var widget = this.widgets[id];
+		var widgetElement = widget.element!=null ? widget.element : $(Mustache.render(this.widgetTemplate, widget));
+
+		if(widget.label !== null) widgetElement.find('.widget-header-title').html(widget.label);
+		if(widget.icon !== null) widgetElement.find('.widget-header-icon i').attr('class', widget.icon);
+		if(widget.headerBackground !== null) widgetElement.find('.dashboard-widget-header').css('backgroundColor',widget.headerBackground);
+
 		if(widget.options !== null){
 			var tpl = '<li class="{{class}}" title="{{label}}"><i class="{{icon}}"></i></li>';
-			widget.element.find('.widget-header-options').html('');
+			widgetElement.find('.widget-header-options').html('');
 			for(var u in widget.options){
-				var option = $(Mustache.render(tpl,widget.options[u]));
-				
+				var option = $(Mustache.render(tpl, widget.options[u]));
+
 				if(widget.options[u].click){
 					option.data('click',widget.options[u].click);
 					option.click(function(){
 						$(this).data('click')(this);
 					});
 				}
-				widget.element.find('.widget-header-options').append(option);
+				widgetElement.find('.widget-header-options').append(option);
 			}
 		}
-		if(widget.headerBackground !== null) widget.element.find('.dashboard-widget-header').css('backgroundColor',widget.headerBackground);
-		widget.element
-		.toggleClass('widget-draggable',widget.draggable)
-		.toggleClass('widget-resizeable',widget.resizeable)
-		.toggleClass('widget-removable',widget.removable);
-		if(widget.content !== null) widget.element.find('.dashboard-widget-content').html(widget.content);
+		widgetElement
+			.toggleClass('widget-draggable', widget.draggable)
+			.toggleClass('widget-resizeable', widget.resizeable)
+			.toggleClass('widget-removable', widget.removable);
+		if(widget.content !== null) widgetElement.find('.dashboard-widget-content').html(widget.content);
+
+		widget.element = widgetElement;
 	}
-  }
-
-  //resize / replace les wicgets en fonction de la taille de la fenetre et des cellules de grilles
-  renderPositions() {
-	var firstPlaceHolder = this.placeHolders.eq(0);
-	var placeholderWidth = firstPlaceHolder.outerWidth();
-	var placeholderHeight = firstPlaceHolder.outerHeight();
-
-	for(var k in this.widgets){
-		var widget = this.widgets[k];
-		var cell = $('[data-row="'+widget.row+'"][data-column="'+widget.column+'"]').position();
-		if(!cell) continue;
-		widget.element.css({
-			width  : ((widget.width * placeholderWidth)-(this.options.placeHolderPadding*2))+'px',
-			height : ((widget.height * placeholderHeight)-(this.options.placeHolderPadding*2))+'px',
-			top : (cell.top+this.options.placeHolderPadding)+'px',
-			left : (cell.left+this.options.placeHolderPadding)+'px'
-		});
+
+	//Rafraichit le contenu des widgets en fonction du tableau de widgets interne
+	renderContents() {
+		for(var k in this.widgets){
+			var widget = this.widgets[k];
+			this.renderContent(widget.id);
+		}
+	}
+
+	//Resize / Replace les widgets en fonction de la taille de la fenetre et des cellules de grilles
+	renderPositions(){
+		var placeholderWidth = this.placeHolders.initialWidth;
+		var placeholderHeight = this.placeHolders.initialHeight;
+
+		for(var k in this.widgets){
+			var widget = this.widgets[k];
+			var cell = $('[data-row="'+widget.row+'"][data-column="'+widget.column+'"]').position();
+			if(!cell) continue;
+			widget.element.css({
+				width: ((widget.width * placeholderWidth)-(this.options.placeHolderPadding*2))+'px',
+				height: ((widget.height * placeholderHeight)-(this.options.placeHolderPadding*2))+'px',
+				top: (cell.top+this.options.placeHolderPadding)+'px',
+				left: (cell.left+this.options.placeHolderPadding)+'px'
+			});
+		}
 	}
-  }
 }