dashboard.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  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. loadJs(files,iteration,callback){
  184. var object = this;
  185. var js = files[iteration];
  186. if($('script[src="'+js+'"]').length!=0 || js==null) {
  187. if(files.length > iteration) object.loadJs(files,iteration+1,callback);
  188. if((files.length-1) == iteration) if(callback) callback();
  189. return;
  190. }
  191. //on supprime tout autre js ayant la même base mais des versions plus vielles
  192. var baseJs = js.replace(/\?v=.*/gm,'');
  193. $('script[src^="'+baseJs+'?"]').remove();
  194. var jsFile = document.createElement('script');
  195. jsFile.setAttribute("type","text/javascript");
  196. document.getElementsByTagName("body")[0].appendChild(jsFile);
  197. jsFile.onload = function() {
  198. if(files.length > iteration) object.loadJs(files,iteration+1,callback);
  199. if((files.length-1) == iteration) if(callback) callback();
  200. };
  201. jsFile.src = js;
  202. }
  203. //Ajout d'un widget en fonction de paramètre
  204. addWidget(widgetOptions,callback){
  205. var object = this;
  206. //Ajout du widget au tableau de widgets interne
  207. object.widgets[widgetOptions.id] = widgetOptions;
  208. //Ajout de css tiers si définits
  209. if(widgetOptions.cssUrl && widgetOptions.cssUrl.length>0){
  210. for (var k in widgetOptions.cssUrl) {
  211. var url = widgetOptions.cssUrl[k];
  212. if($('link[href="'+url+'"]').length!=0) continue;
  213. //on supprime tout autre css ayant la même base mais des versions plus vielles
  214. var baseCss = url.replace(/\?v=.*/gm,'');
  215. $('link[href^="'+baseCss+'"]').remove();
  216. var cssFile = document.createElement('link');
  217. cssFile.setAttribute("rel","stylesheet");
  218. cssFile.setAttribute("type","text/css");
  219. cssFile.setAttribute("href", url);
  220. document.getElementsByTagName("body")[0].appendChild(cssFile);
  221. }
  222. }
  223. //Ajout de js tiers si définits
  224. if(widgetOptions.jsUrl && widgetOptions.jsUrl.length>0){
  225. object.loadJs(widgetOptions.jsUrl,0,function(){
  226. if(callback) callback(data);
  227. });
  228. }
  229. //Render de la template pour les propriétés du widget courant
  230. object.renderContent(widgetOptions.id);
  231. var widgetElement = $(object.widgets[widgetOptions.id].element);
  232. object.grid.append(widgetElement);
  233. widgetElement.draggable({
  234. handle: '.widget-header-icon, .widget-header-title',
  235. containment: object.grid,
  236. "ui-draggable-dragging" : "widget-dragging",
  237. cursorAt: {
  238. left: 7,
  239. top: 10
  240. },
  241. start: function(event, ui){
  242. var element = $(event.currentTarget);
  243. if(!element.hasClass('widget-draggable')) return false;
  244. $('.dashboard-widget').css('z-index','');
  245. element.css('z-index',1000);
  246. },
  247. });
  248. widgetElement.find('.dashboard-widget-resize').mousedown(function(){
  249. var element = $(this).closest('.dashboard-widget');
  250. if(!element.hasClass('widget-resizeable')) return;
  251. object.resizing = object.widgets[element.attr('data-id')];
  252. //On affiche la taille finale du widget avec de la couleur sur les placeholders pour guider le resize
  253. object.placeHolders.removeClass('drag-over');
  254. var width = Math.round(object.resizing.element.outerWidth() / object.placeHolders.initialWidth);
  255. var height = Math.round(object.resizing.element.outerHeight() / object.placeHolders.initialHeight);
  256. var placeHolder = $('.dashboard-placeholder[data-row="'+parseInt(object.resizing.row)+'"][data-column="'+parseInt(object.resizing.column)+'"]');
  257. var offsets = {
  258. rowEnd: height,
  259. columnEnd: width
  260. }
  261. object.placeHolders.render(placeHolder, offsets, 0);
  262. $(placeHolder).addClass('drag-over');
  263. object.grid.addClass('bordered');
  264. });
  265. object.trigger('add', object.widgetToArray(widgetOptions));
  266. }
  267. //Supprime un widget en fonction de son id
  268. deleteWidget(id){
  269. var widget = this.widgets[id];
  270. if(this.trigger('delete',this.widgetToArray(widget))) return;
  271. this.widgets[id].element.remove();
  272. }
  273. configureWidget(id){
  274. var object = this;
  275. $('#dashboard-modal').off('shown.bs.modal').on('shown.bs.modal', function () {
  276. object.trigger('configure', id);
  277. }).modal('show');
  278. }
  279. widgetToArray(widget){
  280. if(!widget) return {};
  281. return {
  282. id: widget.id,
  283. draggable: widget.draggable,
  284. resizeable: widget.resizeable,
  285. removable: widget.removable,
  286. width: widget.width,
  287. height: widget.height,
  288. row: widget.row,
  289. column: widget.column,
  290. label: widget.label,
  291. content: widget.content,
  292. icon: widget.icon,
  293. headerBackground: widget.headerBackground
  294. };
  295. }
  296. //Render d'un widget en fonction de ses paramètres
  297. //Gère le refresh du widget si l'élément est déjà existant
  298. renderContent(id) {
  299. var widget = this.widgets[id];
  300. var widgetElement = widget.element!=null ? widget.element : $(Mustache.render(this.widgetTemplate, widget));
  301. if(widget.label !== null) widgetElement.find('.widget-header-title').html(widget.label);
  302. if(widget.icon !== null) widgetElement.find('.widget-header-icon i').attr('class', widget.icon);
  303. if(widget.headerBackground !== null) widgetElement.find('.dashboard-widget-header').css('backgroundColor',widget.headerBackground);
  304. if(widget.bodyBackground !== null) widgetElement.find('.dashboard-widget-content').css('backgroundColor',widget.bodyBackground);
  305. if(widget.titleColor !== null) widgetElement.find('.widget-header-title,.widget-header-options').css('color',widget.titleColor);
  306. if(widget.iconColor !== null) widgetElement.find('.widget-header-icon i').css('color',widget.iconColor);
  307. if(widget.options !== null){
  308. var tpl = '<li class="{{class}}" title="{{label}}"><i class="{{icon}}"></i></li>';
  309. widgetElement.find('.widget-header-options').html('');
  310. for(var u in widget.options){
  311. var option = $(Mustache.render(tpl, widget.options[u]));
  312. if(widget.options[u].click){
  313. option.data('click',widget.options[u].click);
  314. option.click(function(){
  315. $(this).data('click')(this);
  316. });
  317. }
  318. widgetElement.find('.widget-header-options').append(option);
  319. }
  320. }
  321. widgetElement
  322. .toggleClass('widget-draggable', widget.draggable)
  323. .toggleClass('widget-resizeable', widget.resizeable)
  324. .toggleClass('widget-removable', widget.removable);
  325. if(widget.content !== null) widgetElement.find('.dashboard-widget-content').html(widget.content);
  326. widget.element = widgetElement;
  327. }
  328. //Rafraichit le contenu des widgets en fonction du tableau de widgets interne
  329. renderContents() {
  330. for(var k in this.widgets){
  331. var widget = this.widgets[k];
  332. this.renderContent(widget.id);
  333. }
  334. }
  335. //Resize / Replace les widgets en fonction de la taille de la fenetre et des cellules de grilles
  336. renderPositions(){
  337. var firstPlaceHolder = $('.dashboard-placeholder:eq(0)');
  338. var placeholderWidth = firstPlaceHolder.width();
  339. var placeholderHeight = firstPlaceHolder.height();
  340. for(var k in this.widgets){
  341. var widget = this.widgets[k];
  342. var cell = $('[data-row="'+widget.row+'"][data-column="'+widget.column+'"]').position();
  343. if(!cell) continue;
  344. widget.element.css({
  345. width: ((widget.width * placeholderWidth)-(this.options.placeHolderPadding*2))+'px',
  346. height: ((widget.height * placeholderHeight)-(this.options.placeHolderPadding*2))+'px',
  347. top: (cell.top+this.options.placeHolderPadding)+'px',
  348. left: (cell.left+this.options.placeHolderPadding)+'px'
  349. });
  350. }
  351. }
  352. }