dashboard.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. class Dashboard {
  2. //@TODO:
  3. // - Pouvoir indiquer que la dashboard à une taille dynamique :
  4. // - On laisse toujours 2-3 lignes de placeholder sous le dernier widget
  5. // - On recalcule le nb de lignes de placeholder en plus à afficher sous le dernier widget à chaque add / resize / move / delete d'un widget
  6. constructor(options) {
  7. this.options = $.extend({
  8. columnNumber : 1,
  9. lineNumber : 1,
  10. placeHolderPadding : 10
  11. },options);
  12. this.widgets = {};
  13. var grid = '<div class="dashboard-grid">';
  14. for(var u=0 ; u<options.lineNumber;u++){
  15. for(var i=0 ; i<options.columnNumber;i++){
  16. 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>';
  17. }
  18. }
  19. grid += '</div>';
  20. this.grid = $(grid);
  21. this.options.element.addClass('dashboard-container');
  22. this.options.element.css({width:'100%',height:'100%'});
  23. this.options.element.append(this.grid);
  24. this.widgetTemplate = $('#dashboard-widget-template').html();
  25. this.placeHolders = $('.dashboard-placeholder');
  26. this.placeHolders.initialWidth = this.placeHolders.eq(0).outerWidth();
  27. this.placeHolders.initialHeight = this.placeHolders.eq(0).outerHeight();
  28. this.placeHolders.render = function(placeholder, offsets, delay){
  29. var delay = delay!=null ? delay : 200;
  30. var offsets = $.extend({
  31. rowStart: 0,
  32. rowEnd: 0,
  33. columnStart: 0,
  34. columnEnd: 0
  35. }, offsets);
  36. var rowStart = parseInt(placeholder.attr('data-row'))+1+parseInt(offsets.rowStart);
  37. var rowEnd = parseInt(placeholder.attr('data-row'))+1+parseInt(offsets.rowEnd);
  38. var columnStart = parseInt(placeholder.attr('data-column'))+1+parseInt(offsets.columnStart);
  39. var columnEnd = parseInt(placeholder.attr('data-column'))+1+parseInt(offsets.columnEnd);
  40. setTimeout(function(){
  41. placeholder.css({
  42. 'grid-row': rowStart+'/'+rowEnd,
  43. 'grid-column': columnStart+'/'+columnEnd,
  44. });
  45. }, delay);
  46. }
  47. this.triggers = [];
  48. var object = this;
  49. this.placeHolders.droppable({
  50. tolerance: 'pointer',
  51. classes: { "ui-droppable-hover": 'drag-over' },
  52. over: function(event, ui){
  53. //@TODO:
  54. // - 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
  55. // - Ne pas pouvoir créer un widget dessous un widget existant
  56. // - Ne pas pouvoir superposer des widgets (ou en faire une option)
  57. var currentWidget = ui.draggable;
  58. var offsets = {
  59. rowEnd: currentWidget.attr('data-height'),
  60. columnEnd: currentWidget.attr('data-width')
  61. }
  62. //On custom la grid du placeholder avec la taille du widget
  63. object.placeHolders.render($(this), offsets, 0);
  64. },
  65. out: function(event, ui){
  66. //On reset la grid du placeholder
  67. object.placeHolders.render($(this));
  68. },
  69. drop: function(event, ui) {
  70. var placeholder = $(this);
  71. //On reset la grid du placeholder
  72. object.placeHolders.render(placeholder);
  73. var data = placeholder.data();
  74. var widgetId = ui.draggable.attr('data-id');
  75. object.widgets[widgetId].column = data.column;
  76. object.widgets[widgetId].row = data.row;
  77. object.renderPositions();
  78. object.trigger('move', object.widgetToArray(object.widgets[widgetId]));
  79. }
  80. });
  81. $(window).resize(function() {
  82. object.renderPositions();
  83. });
  84. $(window).mousemove(function(e){
  85. if(!object.resizing) return;
  86. $('.dashboard-widget').css('z-index', '');
  87. object.resizing.element.css('z-index', 1000);
  88. var objectPosition = object.resizing.element.position();
  89. object.resizing.element.css('width',(e.clientX - objectPosition.left)+'px');
  90. object.resizing.element.css('height',(e.clientY - objectPosition.top - 50)+'px');
  91. //On affiche la taille finale du widget avec de la couleur sur les placeholders pour guider le resize
  92. object.placeHolders.removeClass('drag-over');
  93. var width = Math.round(object.resizing.element.outerWidth() / object.placeHolders.initialWidth);
  94. var height = Math.round(object.resizing.element.outerHeight() / object.placeHolders.initialHeight);
  95. var placeholder = $('.dashboard-placeholder[data-row="'+parseInt(object.resizing.row)+'"][data-column="'+parseInt(object.resizing.column)+'"]');
  96. var offsets = {
  97. rowEnd: height,
  98. columnEnd: width
  99. }
  100. object.placeHolders.render(placeholder, offsets, 0);
  101. $(placeholder).addClass('drag-over');
  102. });
  103. $(window).mouseup(function(){
  104. if(!object.resizing) return;
  105. object.placeHolders.removeClass('drag-over');
  106. object.grid.removeClass('bordered');
  107. var width = Math.round(object.resizing.element.outerWidth() / object.placeHolders.initialWidth);
  108. var height = Math.round(object.resizing.element.outerHeight() / object.placeHolders.initialHeight);
  109. object.widgets[object.resizing.id].width = width;
  110. object.widgets[object.resizing.id].height = height;
  111. object.widgets[object.resizing.id].element.attr({
  112. 'data-width': width,
  113. 'data-height': height,
  114. });
  115. //On reset la grid du placeholder
  116. var placeholder = $('.dashboard-placeholder[data-row="'+parseInt(object.resizing.row)+'"][data-column="'+parseInt(object.resizing.column)+'"]');
  117. object.placeHolders.render(placeholder);
  118. object.renderPositions();
  119. object.trigger('resize', object.widgetToArray(object.widgets[object.resizing.id]));
  120. object.resizing = null;
  121. });
  122. $('.dashboard-placeholder').click(function(){
  123. var placeholder = $(this);
  124. object.trigger('placeholder-click',{
  125. element : placeholder,
  126. row: placeholder.attr('data-row'),
  127. column: placeholder.attr('data-column')
  128. });
  129. });
  130. }
  131. on(event,callback){
  132. if(!this.triggers[event]) this.triggers[event] = [];
  133. this.triggers[event].push(callback);
  134. return this;
  135. }
  136. trigger(event,data){
  137. if(!this.triggers[event]) return;
  138. if(!data) data = {};
  139. var abort = false;
  140. for(var k in this.triggers[event]){
  141. if(this.triggers[event][k](data) === true) abort = true;
  142. }
  143. return abort;
  144. }
  145. //Ajoute un ou plusieurs widgets
  146. addWidgets(widgets){
  147. var object = this;
  148. for(var k in widgets){
  149. var widgetOptions = $.extend({
  150. id: Math.floor(Math.random() * 100) + Date.now(),
  151. draggable: true,
  152. resizeable: true,
  153. removable: true,
  154. width: 1,
  155. height: 1,
  156. row: 0,
  157. column:0,
  158. options: [],
  159. label: 'Sans titre',
  160. content: "Aucun contenu"
  161. }, widgets[k]);
  162. widgetOptions.options.push({
  163. icon: 'fas fa-ellipsis-v',
  164. class: 'widget-option-configure',
  165. label: 'Configurer',
  166. click: function(element){
  167. object.configureWidget($(element).closest('.dashboard-widget').attr('data-id'));
  168. }
  169. });
  170. widgetOptions.options.push({
  171. icon: 'fa fa-times',
  172. class: 'widget-option-delete',
  173. label: 'Supprimer',
  174. click: function(element){
  175. object.deleteWidget($(element).closest('.dashboard-widget').attr('data-id'));
  176. }
  177. });
  178. object.addWidget(widgetOptions)
  179. }
  180. //Gestion de la disposition des widgets sur la dashboard
  181. this.renderPositions();
  182. }
  183. //Ajout d'un widget en fonction de paramètre
  184. addWidget(widgetOptions){
  185. var object = this;
  186. //Ajout du widget au tableau de widgets interne
  187. object.widgets[widgetOptions.id] = widgetOptions;
  188. //Render de la template pour les propriétés du widget courant
  189. object.renderContent(widgetOptions.id);
  190. var widgetElement = $(object.widgets[widgetOptions.id].element);
  191. object.grid.append(widgetElement);
  192. widgetElement.draggable({
  193. handle: '.widget-header-icon, .widget-header-title',
  194. containment: object.grid,
  195. "ui-draggable-dragging" : "widget-dragging",
  196. cursorAt: {
  197. left: 7,
  198. top: 10
  199. },
  200. start: function(event, ui){
  201. var element = $(event.currentTarget);
  202. if(!element.hasClass('widget-draggable')) return false;
  203. $('.dashboard-widget').css('z-index','');
  204. element.css('z-index',1000);
  205. },
  206. });
  207. widgetElement.find('.dashboard-widget-resize').mousedown(function(){
  208. var element = $(this).closest('.dashboard-widget');
  209. if(!element.hasClass('widget-resizeable')) return;
  210. object.resizing = object.widgets[element.attr('data-id')];
  211. //On affiche la taille finale du widget avec de la couleur sur les placeholders pour guider le resize
  212. object.placeHolders.removeClass('drag-over');
  213. var width = Math.round(object.resizing.element.outerWidth() / object.placeHolders.initialWidth);
  214. var height = Math.round(object.resizing.element.outerHeight() / object.placeHolders.initialHeight);
  215. var placeHolder = $('.dashboard-placeholder[data-row="'+parseInt(object.resizing.row)+'"][data-column="'+parseInt(object.resizing.column)+'"]');
  216. var offsets = {
  217. rowEnd: height,
  218. columnEnd: width
  219. }
  220. object.placeHolders.render(placeHolder, offsets, 0);
  221. $(placeHolder).addClass('drag-over');
  222. object.grid.addClass('bordered');
  223. });
  224. object.trigger('add', object.widgetToArray(widgetOptions));
  225. }
  226. //Supprime un widget en fonction de son id
  227. deleteWidget(id){
  228. var widget = this.widgets[id];
  229. if(this.trigger('delete',this.widgetToArray(widget))) return;
  230. this.widgets[id].element.remove();
  231. }
  232. configureWidget(id){
  233. var object = this;
  234. $('#dashboard-modal').off('shown.bs.modal').on('shown.bs.modal', function () {
  235. object.trigger('configure', id);
  236. }).modal('show');
  237. }
  238. widgetToArray(widget){
  239. if(!widget) return {};
  240. return {
  241. id: widget.id,
  242. draggable: widget.draggable,
  243. resizeable: widget.resizeable,
  244. removable: widget.removable,
  245. width: widget.width,
  246. height: widget.height,
  247. row: widget.row,
  248. column: widget.column,
  249. label: widget.label,
  250. content: widget.content,
  251. icon: widget.icon,
  252. headerBackground: widget.headerBackground
  253. };
  254. }
  255. //Render d'un widget en fonction de ses paramètres
  256. //Gère le refresh du widget si l'élément est déjà existant
  257. renderContent(id) {
  258. var widget = this.widgets[id];
  259. var widgetElement = widget.element!=null ? widget.element : $(Mustache.render(this.widgetTemplate, widget));
  260. if(widget.label !== null) widgetElement.find('.widget-header-title').html(widget.label);
  261. if(widget.icon !== null) widgetElement.find('.widget-header-icon i').attr('class', widget.icon);
  262. if(widget.headerBackground !== null) widgetElement.find('.dashboard-widget-header').css('backgroundColor',widget.headerBackground);
  263. if(widget.bodyBackground !== null) widgetElement.find('.dashboard-widget-content').css('backgroundColor',widget.bodyBackground);
  264. if(widget.titleColor !== null) widgetElement.find('.widget-header-title,.widget-header-options').css('color',widget.titleColor);
  265. if(widget.iconColor !== null) widgetElement.find('.widget-header-icon i').css('color',widget.iconColor);
  266. if(widget.options !== null){
  267. var tpl = '<li class="{{class}}" title="{{label}}"><i class="{{icon}}"></i></li>';
  268. widgetElement.find('.widget-header-options').html('');
  269. for(var u in widget.options){
  270. var option = $(Mustache.render(tpl, widget.options[u]));
  271. if(widget.options[u].click){
  272. option.data('click',widget.options[u].click);
  273. option.click(function(){
  274. $(this).data('click')(this);
  275. });
  276. }
  277. widgetElement.find('.widget-header-options').append(option);
  278. }
  279. }
  280. widgetElement
  281. .toggleClass('widget-draggable', widget.draggable)
  282. .toggleClass('widget-resizeable', widget.resizeable)
  283. .toggleClass('widget-removable', widget.removable);
  284. if(widget.content !== null) widgetElement.find('.dashboard-widget-content').html(widget.content);
  285. widget.element = widgetElement;
  286. }
  287. //Rafraichit le contenu des widgets en fonction du tableau de widgets interne
  288. renderContents() {
  289. for(var k in this.widgets){
  290. var widget = this.widgets[k];
  291. this.renderContent(widget.id);
  292. }
  293. }
  294. //Resize / Replace les widgets en fonction de la taille de la fenetre et des cellules de grilles
  295. renderPositions(){
  296. var placeholderWidth = this.placeHolders.initialWidth;
  297. var placeholderHeight = this.placeHolders.initialHeight;
  298. for(var k in this.widgets){
  299. var widget = this.widgets[k];
  300. var cell = $('[data-row="'+widget.row+'"][data-column="'+widget.column+'"]').position();
  301. if(!cell) continue;
  302. widget.element.css({
  303. width: ((widget.width * placeholderWidth)-(this.options.placeHolderPadding*2))+'px',
  304. height: ((widget.height * placeholderHeight)-(this.options.placeHolderPadding*2))+'px',
  305. top: (cell.top+this.options.placeHolderPadding)+'px',
  306. left: (cell.left+this.options.placeHolderPadding)+'px'
  307. });
  308. }
  309. }
  310. }