APIs

Show:
  1. /**
  2. * @namespace flexygo.ui.wc
  3. */
  4. var flexygo;
  5. (function (flexygo) {
  6. var ui;
  7. (function (ui) {
  8. var wc;
  9. (function (wc) {
  10. /**
  11. * Library for the FlxTimelineElement
  12. *
  13. * @class FlxTimelineElement
  14. * @constructor
  15. * @return {FlxTimelineElement} .
  16. */
  17. class FlxTimelineElement extends HTMLElement {
  18. constructor() {
  19. //If a constructor is defined, is REQUIRED call the super constructor
  20. super();
  21. /**
  22. * Vis Timeline
  23. * @property timelineranges {flexygo.api.timeline.timelineRanges}
  24. */
  25. this.timelineRanges = { Hour: 18E5, Day: 432E5, Week: 3024E5, Month: 1296E6, Year: 158112E5 };
  26. }
  27. /**
  28. * Array of observed attributes. REQUIRED
  29. * @property observedAttributes {Array}
  30. */
  31. static get observedAttributes() {
  32. return ['ModuleName', 'ObjectName', 'ObjectWhere'];
  33. }
  34. /**
  35. * Init the webcomponent. REQUIRED.
  36. * @method init
  37. */
  38. init() {
  39. try {
  40. let me = $(this);
  41. me.removeAttr('manualInit');
  42. this.filterValues = null;
  43. this.activeFilter = null;
  44. this.wcParentModule = me.closest('flx-module')[0];
  45. if (!this.wcParentModule) {
  46. flexygo.msg.error('flx-timeline has to be inside a flx-module');
  47. throw 'flx-timeline has to be inside a flx-module';
  48. }
  49. let history = flexygo.history.get(me);
  50. if (history && history.filtersValues && history.filtersValues[this.wcParentModule.moduleName]) {
  51. let filterHistoryValue = history.filtersValues[this.wcParentModule.moduleName];
  52. if (filterHistoryValue.activeFilter) {
  53. this.activeFilter = filterHistoryValue.activeFilter;
  54. }
  55. if (filterHistoryValue.properties) {
  56. this.filterValues = filterHistoryValue.properties;
  57. }
  58. }
  59. this.getTimeline();
  60. }
  61. catch (ex) {
  62. console.error('FlexyGo Timeline', ex);
  63. }
  64. }
  65. /**
  66. * Refresh of webcomponent. REQUIRED.
  67. * @method refresh
  68. */
  69. refresh() {
  70. if ($(this).attr('manualInit') != 'true') {
  71. this.init();
  72. }
  73. }
  74. /**
  75. * Set filter of webcomponent. REQUIRED.
  76. * @method refresh
  77. */
  78. setFilter() {
  79. if ($(this).attr('manualInit') != 'true') {
  80. //TODO: Modificar pedir solo los items a el controller
  81. this.getTimeline();
  82. }
  83. }
  84. /**
  85. * Render HTML data.
  86. * @method render
  87. */
  88. render() {
  89. try {
  90. let itemsWithoutGroup = `<aside id="items_without_group" class="folded">
  91. <div>
  92. <h4>${this.timelineSetting.TitleItemsWithoutGroup}</h4>
  93. </div>
  94. <div id="items-container"></div>
  95. <div class="fold">
  96. <i class="flx-icon icon-arrow-head-5"/>
  97. </div>
  98. </aside>`;
  99. let controls = `<div id="controls">
  100. <div>
  101. <button type="button" method="navigation" value="0.9" accesskey="l"><i class="fa fa-angle-left"/></button>
  102. <button type="button" method="today" accesskey="t">${flexygo.localization.translate('flxtimeline.today')}</button>
  103. <button type="button" method="navigation" value="-0.9" value="0.9" accesskey="r"><i class="fa fa-angle-right"/></button>
  104. </div>
  105. <div>
  106. <button type="button" method="changeRange" range="Hour" value="${this.timelineRanges.Hour}" accesskey="1">${flexygo.localization.translate('flxtimeline.hour')}</button>
  107. <button type="button" method="changeRange" range="Day" value="${this.timelineRanges.Day}" accesskey="2">${flexygo.localization.translate('flxtimeline.day')}</button>
  108. <button type="button" method="changeRange" range="Week" value="${this.timelineRanges.Week}" accesskey="3">${flexygo.localization.translate('flxtimeline.week')}</button>
  109. <button type="button" method="changeRange" range="Month" value="${this.timelineRanges.Month}" accesskey="4">${flexygo.localization.translate('flxtimeline.month')}</button>
  110. <button type="button" method="changeRange" range="Year" value="${this.timelineRanges.Year}" accesskey="5">${flexygo.localization.translate('flxtimeline.year')}</button>
  111. </div>
  112. </div>`;
  113. let title = `<div id="title">
  114. <h3>${this.timelineSetting.TimelineSettingDescrip}</h3>
  115. </div>`;
  116. $(this).html(`<section id="timeline_container" ShowControls="${this.timelineSetting.ShowControls}" layout="${(this.timelineSetting.WithGroups && this.timelineSetting.Editable && this.timelineSetting.ShowItemsWithoutGroup && this.timelineSetting.LayoutName) ? this.timelineSetting.LayoutName : ''}">
  117. ${(this.timelineSetting.WithGroups && this.timelineSetting.Editable && this.timelineSetting.ShowItemsWithoutGroup) ? itemsWithoutGroup : ''}
  118. <div id="vis">
  119. <div class="help">
  120. <i class="fa fa-question-circle"/>
  121. </div>
  122. ${(this.timelineSetting.ShowControls) ? controls + title : ''}
  123. </div>
  124. <div id="not_supported">
  125. <h1>${flexygo.localization.translate('flxtimeline.notsupported')}</h1>
  126. </div>
  127. </section>`);
  128. if (!flexygo.utils.isBlank(this.timelineSetting.DefaultRangeName)) {
  129. $(this).find(`#controls > div > button[range="${this.timelineSetting.DefaultRangeName}"]`).addClass('active');
  130. }
  131. this.setStructureEvents();
  132. }
  133. catch (ex) {
  134. console.error('FlexyGo Timeline', ex);
  135. }
  136. }
  137. /**
  138. * Set Structure Events.
  139. * @method setStructureEvents
  140. */
  141. setStructureEvents() {
  142. let removeActiveRangeEvent = (e) => {
  143. $(this).find('#controls > div > button.active').removeClass('active');
  144. };
  145. let flagEventMousewheel, flagAccessKeyVisible = false;
  146. $(this).find('#vis > div.help').off('click.timeline').on('click.timeline', (e) => {
  147. $.sweetModal({
  148. title: `${flexygo.localization.translate('flxtimeline.title')} 🙊`,
  149. content: `<div id="timeline-help">
  150. <table>
  151. <thead>
  152. <tr>
  153. <th>
  154. <h2>${flexygo.localization.translate('flxtimeline.description')}</h2>
  155. </th>
  156. <th>
  157. <h2>${flexygo.localization.translate('flxtimeline.action')}</h2>
  158. </th>
  159. </tr>
  160. </thead>
  161. <tbody>
  162. <tr class="bgseparator">
  163. <th>
  164. <h4>SHORTCUTS</h4>
  165. </th>
  166. </tr>
  167. <tr>
  168. <td>${flexygo.localization.translate('flxtimeline.zoom')}</td>
  169. <td>
  170. <kbd>
  171. Alt
  172. </kbd>
  173. <kbd>
  174. L
  175. </kbd>
  176. ${flexygo.localization.translate('flxtimeline.or')}
  177. <kbd>
  178. T
  179. </kbd>
  180. ${flexygo.localization.translate('flxtimeline.or')}
  181. <kbd>
  182. R
  183. </kbd>
  184. </td>
  185. </tr>
  186. <tr>
  187. <td>${flexygo.localization.translate('flxtimeline.navigationtime')}</td>
  188. <td>
  189. <kbd>
  190. Alt
  191. </kbd>
  192. <kbd>
  193. 1
  194. </kbd>
  195. ${flexygo.localization.translate('flxtimeline.to')}
  196. <kbd>
  197. 5
  198. </kbd>
  199. </td>
  200. </tr>
  201. <tr class="bgseparator">
  202. <th>
  203. <h4>HELP</h4>
  204. </th>
  205. </tr>
  206. <tr>
  207. <td>${flexygo.localization.translate('flxtimeline.selectitem')}</td>
  208. <td>
  209. <kbd>
  210. <i class="fa fa-hand-pointer-o"/>
  211. </kbd>
  212. </td>
  213. </tr>
  214. <tr>
  215. <td>${flexygo.localization.translate('flxtimeline.multiselectitems')}</td>
  216. <td>
  217. <kbd>
  218. Ctrl
  219. </kbd>
  220. <kbd>
  221. <i class="fa fa-hand-pointer-o"/>
  222. </kbd>
  223. ${flexygo.localization.translate('flxtimeline.or')}
  224. <kbd>
  225. <i class="fa fa-hand-pointer-o"/>
  226. <span>1s</span>
  227. </kbd>
  228. </td>
  229. </tr>
  230. <tr>
  231. <td>${flexygo.localization.translate('flxtimeline.multiselectbyrange')}</td>
  232. <td>
  233. <kbd>
  234. Shift
  235. </kbd>
  236. <kbd>
  237. <i class="fa fa-hand-pointer-o"/>
  238. </kbd>
  239. </td>
  240. </tr>
  241. <tr class="separator">
  242. <td>${flexygo.localization.translate('flxtimeline.multiselectbyrangegroup')}</td>
  243. <td>
  244. <kbd>
  245. Shift
  246. </kbd>
  247. <kbd>
  248. G
  249. </kbd>
  250. <kbd>
  251. <i class="fa fa-hand-pointer-o"/>
  252. </kbd>
  253. </td>
  254. </tr>
  255. <tr>
  256. <td>${flexygo.localization.translate('flxtimeline.createitem')}</td>
  257. <td>
  258. <kbd>
  259. <i class="fa fa-hand-pointer-o"/>
  260. <span>2x</span>
  261. </kbd>
  262. </td>
  263. </tr>
  264. <tr>
  265. <td>${flexygo.localization.translate('flxtimeline.createitemwithrange')}</td>
  266. <td>
  267. <kbd>
  268. Ctrl
  269. </kbd>
  270. <kbd>
  271. <i class="fa fa-hand-pointer-o"/>
  272. </kbd>
  273. <kbd>
  274. <i class="fa fa-arrows-h"/>
  275. </kbd>
  276. </td>
  277. </tr>
  278. <tr>
  279. <td>${flexygo.localization.translate('flxtimeline.edititem')}</td>
  280. <td>
  281. <kbd>
  282. <i class="fa fa-hand-pointer-o"/>
  283. </kbd>
  284. <span class="vertical-separator"/>
  285. <kbd>
  286. <i class="flx-icon icon-drag"/>
  287. </kbd>
  288. ${flexygo.localization.translate('flxtimeline.or')}
  289. <kbd>
  290. <i class="fa fa-arrows-h"/>
  291. </kbd>
  292. ${flexygo.localization.translate('flxtimeline.or')}
  293. <kbd>
  294. <i class="flx-icon icon-remove"/>
  295. </kbd>
  296.  
  297. </td>
  298. </tr>
  299. <tr class="separator">
  300. <td>${flexygo.localization.translate('flxtimeline.openeditview')}</td>
  301. <td>
  302. <kbd>
  303. <i class="fa fa-hand-pointer-o"/>
  304. <span>2x</span>
  305. </kbd>
  306. </td>
  307. </tr>
  308. <tr>
  309. <td>${flexygo.localization.translate('flxtimeline.zoom')} (Zoom)</td>
  310. <td>
  311. <kbd>
  312. Ctrl
  313. </kbd>
  314. <kbd>
  315. <svg id="mouseAnimation" width="18px" height="100%" viewBox="0 0 247 390" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;">
  316. <style>
  317. @keyframes scroll {
  318. 0% {
  319. transform: translateY(0);
  320. }
  321. 30% {
  322. transform: translateY(60px);
  323. }
  324. }
  325.  
  326. svg#mouseAnimation #wheel {
  327. animation: scroll ease 2s infinite;
  328. }
  329. </style>
  330. <path id="wheel" d="M123.359,79.775l0,32.843" style="fill:none;stroke:var(--timeline-light-color);stroke-width:15px;"/>
  331. <path id="mouse" d="M236.717,123.359c0,-62.565 -50.794,-113.359 -113.358,-113.359c-62.565,0 -113.359,50.794 -113.359,113.359l0,143.237c0,62.565 50.794,113.359 113.359,113.359c62.564,0 113.358,-50.794 113.358,-113.359l0,-143.237Z" style="fill:none;stroke:var(--timeline-light-color);stroke-width:15px;"/>
  332. </svg>
  333. </kbd>
  334. </td>
  335. </tr>
  336. <tr>
  337. <td>${flexygo.localization.translate('flxtimeline.navigationtime')}</td>
  338. <td>
  339. <kbd>
  340. <i class="fa fa-hand-pointer-o"/>
  341. </kbd>
  342. <kbd>
  343. <i class="flx-icon icon-drag"/>
  344. </kbd>
  345. </td>
  346. </tr>
  347. </tbody>
  348. </table>
  349. </div>`,
  350. width: '60%',
  351. theme: $.sweetModal.THEME_LIGHT
  352. });
  353. });
  354. if (this.timelineSetting.WithGroups && this.timelineSetting.Editable && this.timelineSetting.ShowItemsWithoutGroup) {
  355. $(this).find('#items_without_group > div.fold').off('click.timeline').on('click.timeline', () => {
  356. let itemsWithoutGroup = $(this).find('#items_without_group');
  357. (itemsWithoutGroup.hasClass('folded')) ? itemsWithoutGroup.removeClass('folded') : itemsWithoutGroup.addClass('folded');
  358. });
  359. }
  360. $(document).off('keydown.timeline keyup.timeline').on({
  361. 'keydown.timeline': (e) => {
  362. if (e.shiftKey && e.keyCode === 71 && !this.visTimeline.itemSet.options.multiselectPerGroup) {
  363. this.visTimeline.setOptions({ multiselectPerGroup: true });
  364. }
  365. if (e.ctrlKey && !flagEventMousewheel) {
  366. flagEventMousewheel = true;
  367. this.visTimeline.on('mousewheel', removeActiveRangeEvent);
  368. }
  369. /*Show access key*/
  370. if ((e.altKey || e.key === 'Alt') && !e.ctrlKey && !flagAccessKeyVisible) {
  371. e.preventDefault();
  372. e.stopPropagation();
  373. flagAccessKeyVisible = true;
  374. $(this).find('[accesskey]').filter(':visible').each((index, elem) => {
  375. let jElem = $(elem);
  376. let overlay = $(`<div class="accesskey_overlay">${jElem.attr('accesskey')}</div>`);
  377. let overlayParent;
  378. if (elem.tagName.toLowerCase() === "input") {
  379. /* special case for the input that has an access key defined.
  380. We cannot set the overlay on the input itself, only on its parent.*/
  381. overlayParent = jElem.parent();
  382. }
  383. else {
  384. overlayParent = jElem;
  385. }
  386. if (overlayParent.css('position') !== 'absolute') {
  387. overlayParent.css('position', 'relative');
  388. }
  389. overlay.appendTo(overlayParent);
  390. });
  391. }
  392. },
  393. 'keyup.timeline': (e) => {
  394. if ((e.keyCode === 16 || e.keyCode === 71) && this.visTimeline.itemSet.options.multiselectPerGroup) {
  395. this.visTimeline.setOptions({ multiselectPerGroup: false });
  396. }
  397. if (e.keyCode === 17 && flagEventMousewheel) {
  398. flagEventMousewheel = false;
  399. this.visTimeline.off('mousewheel', removeActiveRangeEvent);
  400. }
  401. /*Hide access Key*/
  402. if ((e.altKey || e.key === 'Alt') && !e.ctrlKey && flagAccessKeyVisible) {
  403. e.preventDefault();
  404. e.stopPropagation();
  405. flagAccessKeyVisible = false;
  406. let overlays = $(this).find('.accesskey_overlay');
  407. if (overlays.length)
  408. overlays.remove();
  409. }
  410. }
  411. });
  412. if (this.timelineSetting.ShowControls) {
  413. $(this).find('#controls > div > button').off('click.timeline').on('click.timeline', (e) => {
  414. let element = $(e.currentTarget);
  415. let method = element.attr('method');
  416. let value = parseFloat(element.attr('value'));
  417. let range = this.visTimeline.getWindow();
  418. switch (method) {
  419. case 'navigation':
  420. let intervalArrows = range.end.getTime() - range.start.getTime();
  421. this.visTimeline.setWindow(range.start.valueOf() - intervalArrows * value, range.end.valueOf() - intervalArrows * value);
  422. break;
  423. case 'today':
  424. this.visTimeline.moveTo(moment().format());
  425. break;
  426. case 'changeRange':
  427. let interval = moment((range.start.getTime() + range.end.getTime()) / 2).format();
  428. this.visTimeline.setWindow(moment(interval).subtract(value, 'millisecond'), moment(interval).add(value, 'millisecond'));
  429. $(this).find('#controls > div > button.active').removeClass('active');
  430. element.addClass('active');
  431. break;
  432. }
  433. });
  434. $(this).find('#controls > div:nth-child(1)').tooltip({ title: flexygo.localization.translate('flxtimeline.navigation'), placement: 'right', trigger: 'hover' });
  435. $(this).find('#controls > div:nth-child(2)').tooltip({ title: flexygo.localization.translate('flxtimeline.range'), placement: 'right', trigger: 'hover' });
  436. }
  437. }
  438. /**
  439. * Init Vis Timeline.
  440. * @method initVisTimeline
  441. * @param {vis.DataItemCollectionType} visItems Items Data.
  442. * @param {vis.DataGroupCollectionType} visGroups Groups Data.
  443. * @param {vis.TimelineOptions} visOptions Options Data.
  444. */
  445. initVisTimeline(visItems, visGroups, visOptions = {}) {
  446. try {
  447. this.visTimeline = new vis.Timeline($(this).find('#timeline_container > #vis')[0], visItems, visGroups, visOptions);
  448. }
  449. catch (ex) {
  450. console.error('FlexyGo Timeline', ex);
  451. flexygo.msg.error(ex.message, null, 'Error initializing the timeline');
  452. }
  453. }
  454. /**
  455. * Init Vis Timeline.
  456. * @method setItemsWithoutGroups
  457. * @param {vis.DataItemCollectionType} visItems Items Data.
  458. * @param {vis.DataGroupCollectionType} visGroups Groups Data.
  459. * @param {vis.TimelineOptions} visOptions Options Data.
  460. */
  461. setItemsWithoutGroups(visItems) {
  462. if (visItems.length === 0) {
  463. $(this).find('#timeline_container > #items_without_group > #items-container').append(`<span>${flexygo.localization.translate('flxtimeline.withoutRegisters')}</span>`);
  464. }
  465. else {
  466. for (const item of visItems) {
  467. let visItemData = this.buildVisItem(item, true);
  468. $((`<div class="item ${!flexygo.utils.isBlank(visItemData.className) ? visItemData.className : ''}" ${!flexygo.utils.isBlank(visItemData.style) ? `style="${visItemData.style}"` : ''} draggable= "true">
  469. ${(this.timelineSetting.ItemContentTemplate) ? flexygo.utils.parser.recursiveCompile(item, this.timelineSetting.ItemContentTemplate) : item[(this.timelineSetting.Advanced) ? this.timelineSetting.ItemDescripField : this.timelineSetting.PropertyDescrip]}
  470. </div>`)).appendTo($(this).find('#timeline_container > #items_without_group > #items-container'))[0].visItemData = visItemData;
  471. }
  472. $(this).find('#timeline_container > #items_without_group > #items-container > .item').off('dragstart.timeline').on('dragstart.timeline', (event) => {
  473. event.originalEvent.dataTransfer.effectAllowed = 'move';
  474. event.originalEvent.dataTransfer.setData("text", JSON.stringify(event.currentTarget.visItemData));
  475. });
  476. }
  477. }
  478. /**
  479. * Get Timeline.
  480. * @method getTimeline
  481. */
  482. getTimeline() {
  483. let params = {
  484. PageName: flexygo.history.getPageName($(this)),
  485. ModuleName: this.moduleName,
  486. ObjectName: $(this).attr('ObjectName'),
  487. ObjectWhere: $(this).attr('ObjectWhere'),
  488. SearchId: this.activeFilter,
  489. FilterValues: this.filterValues
  490. };
  491. flexygo.ajax.post('~/api/Timeline', 'GetTimeline', params, (response) => {
  492. if (response) {
  493. let visItems = new vis.DataSet(), visGroups;
  494. this.timelineSetting = response.TimelineSetting;
  495. this.defaults = (response.Defaults) ? response.Defaults : {};
  496. this.toolbar = response.Toolbar;
  497. this.searchSettings = response.SearchSettings;
  498. this.savedSearches = response.SavedSearches;
  499. this.objectName = this.timelineSetting.EntityConfiguration.ObjectName;
  500. this.objectWhere = response.ObjectWhere;
  501. this.filterWhere = response.FilterWhere;
  502. this.render();
  503. this.wcParentModule.setButtons(this.toolbar, this.timelineSetting.EntityConfiguration.CollectionName, this.objectWhere);
  504. this.loadFilters();
  505. for (const item of response.Items.filter(item => !(this.timelineSetting.WithGroups && flexygo.utils.isBlank(item[(this.timelineSetting.Advanced) ? this.timelineSetting.ItemGroupField : this.timelineSetting.PropertyGroup])))) {
  506. visItems.add(this.buildVisItem(item));
  507. }
  508. if (this.timelineSetting.WithGroups) {
  509. let groups = [];
  510. for (let i = 0; i < response.Groups.length; i++) {
  511. groups.push(this.buildVisGroup(response.Groups[i], i));
  512. }
  513. visGroups = new vis.DataSet(groups);
  514. }
  515. else {
  516. visGroups = null;
  517. }
  518. if (this.timelineSetting.WithGroups && this.timelineSetting.Editable && this.timelineSetting.ShowItemsWithoutGroup) {
  519. this.setItemsWithoutGroups(response.Items.filter(item => flexygo.utils.isBlank(item[(this.timelineSetting.Advanced) ? this.timelineSetting.ItemGroupField : this.timelineSetting.PropertyGroup])));
  520. }
  521. this.initVisTimeline(visItems, visGroups, this.buildVisOptions());
  522. this.wcParentModule.moduleLoaded(this);
  523. }
  524. });
  525. }
  526. /**
  527. * Build Vis Options.
  528. * @method buildVisOptions
  529. * @param {object} visTimelineOptions.
  530. */
  531. buildVisOptions() {
  532. let visOptions = {
  533. /*align: 'auto',*/
  534. /*autoResize: true,*/
  535. /*clickToUse: false,*/
  536. /*configure: false,*/
  537. /*dataAttributes: false,*/
  538. editable: (this.timelineSetting.Editable) ? { add: this.timelineSetting.EntityConfiguration.CanInsert, remove: this.timelineSetting.EntityConfiguration.CanDelete, updateGroup: this.timelineSetting.EntityConfiguration.CanUpdate && this.timelineSetting.CanEditPropertyGroup, updateTime: this.timelineSetting.EntityConfiguration.CanUpdate && (this.timelineSetting.CanEditPropertyStartDate || this.timelineSetting.CanEditPropertyEndDate), overrideItems: false } : this.timelineSetting.Editable,
  539. end: moment().add(this.timelineRanges[this.timelineSetting.DefaultRangeName], 'millisecond').format(),
  540. /*format: none,*/
  541. /*groupEditable: { add: false, remove: false, order: false },*/
  542. groupOrder: function (a, b) {
  543. return a.value - b.value;
  544. },
  545. /*groupOrderSwap: none,*/
  546. /*groupTemplate: none,*/
  547. height: (this.timelineSetting.ShowControls) ? 'calc(100% - 42px)' : '100%',
  548. /*hiddenDates: none,*/
  549. /*horizontalScroll: false,*/
  550. /*itemsAlwaysDraggable: {item: false, range: false},*/
  551. locale: flexygo.context.currentUserLang,
  552. /*locales: none*/
  553. /*moment: vis.moment,*/
  554. /* moment: function (date) {
  555. return (<any>vis).moment(date).utc();
  556. },*/
  557. /*margin: {axis: 20, item: { horizontal: 10, vertical: 10}},*/
  558. margin: { item: { horizontal: -1 } },
  559. max: moment().add('years', 25).format(),
  560. /*maxHeight: none*/
  561. /*maxMinorChars: 7,*/
  562. min: moment().add('years', -25).format(),
  563. /*minHeight: none,*/
  564. /*moveable: true,*/
  565. multiselect: true,
  566. /*multiselectPerGroup: false, (Modified On The Fly)*/
  567. onAdd: (item, callback) => {
  568. this.objectActions(item, (item.withOutGroup) ? 'update' : 'insert').then((data) => {
  569. callback(data);
  570. if (data) {
  571. $(this).find(`#timeline_container > #items_without_group > #items-container > .item`).filter((index, element) => element.visItemData.id === data.id).remove();
  572. this.visTimeline.focus(data.id);
  573. }
  574. }, function (error) {
  575. callback(null);
  576. console.error('FlexyGo Timeline', error);
  577. });
  578. },
  579. /*onAddGroup: none,*/
  580. /*onDropObjectOnItem: none, (Used Bellow)*/
  581. /*onInitialDrawComplete: none,*/
  582. onMove: (item, callback) => {
  583. this.objectActions(item, 'update').then((data) => {
  584. callback(data);
  585. if (data)
  586. this.visTimeline.focus(data.id);
  587. }, function (error) {
  588. callback(null);
  589. console.error('FlexyGo Timeline', error);
  590. });
  591. },
  592. /*onMoveGroup: none,*/
  593. /*onMoving: none, (Used Bellow)*/
  594. onRemove: (item, callback) => {
  595. this.objectActions(item, 'delete').then((data) => {
  596. callback(data);
  597. }, function (error) {
  598. callback(null);
  599. console.error('FlexyGo Timeline', error);
  600. });
  601. },
  602. /*onRemoveGroup: 'none',*/
  603. onUpdate: (item, callback) => {
  604. if (item.editable === undefined || (item.editable && typeof (item.editable) === 'boolean') || (item.editable && typeof (item.editable) === 'object' && (item.editable.updateGroup || item.editable.updateTime || item.editable.remove))) {
  605. this.objectActions(item, 'edit').then((data) => {
  606. callback(data);
  607. if (data)
  608. this.visTimeline.focus(data.id);
  609. }, function (error) {
  610. callback(null);
  611. console.error('FlexyGo Timeline', error);
  612. });
  613. }
  614. },
  615. /*order: none*/
  616. orientation: { axis: 'top', item: 'top' },
  617. /*rollingMode: { follow: false, offset: 0.5 },*/
  618. /*rtl: false,*/
  619. /*selectable: true,*/
  620. /*showCurrentTime: true,*/
  621. /*showMajorLabels: true,*/
  622. /*showMinorLabels: true,*/
  623. /*showTooltips: true,*/
  624. /*stack: true,*/
  625. /*stackSubgroups: true,*/
  626. /*snap: function,*/
  627. start: moment().subtract(this.timelineRanges[this.timelineSetting.DefaultRangeName], 'millisecond').format(),
  628. /*template: none,*/
  629. /*visibleFrameTemplate: none, (Used Bellow)*/
  630. /*timeAxis: { scale: none, step: 1 },*/
  631. /*type: none,*/
  632. /*tooltip: { followMouse: false, overflowMethod: 'flip' },*/
  633. tooltipOnItemUpdateTime: {
  634. template: (item) => {
  635. return `<div>
  636. <i class="fa fa-hourglass-1"/></i> ${moment(item.start).utc().format('l')} ${moment(item.start).utc().format('LT')}
  637. </div>
  638. ${(item.end) ? `<div>
  639. ${moment(item.end).utc().format('l')} ${moment(item.end).utc().format('LT')} <i class="fa fa-hourglass-end"></i>
  640. </div>` : ''}`;
  641. }
  642. },
  643. verticalScroll: true,
  644. /*width: '100%',*/
  645. /*zoomable: true,*/
  646. zoomKey: 'ctrlKey',
  647. zoomMax: 157680000000,
  648. zoomMin: 60000,
  649. };
  650. if (this.timelineSetting.Advanced && !flexygo.utils.isBlank(this.timelineSetting.OnDropObjectOnItemFunction)) {
  651. visOptions.onDropObjectOnItem = (objectData, item, callback) => {
  652. new Function('objectData', 'item', `return new Promise((resolve, reject) => { try {${this.timelineSetting.OnDropObjectOnItemFunction}} catch (ex) { reject(ex); } });`).call(this, objectData, item).then(function (data) { callback(data); }, function (error) { callback(item); console.error('FlexyGo Timeline', 'OnDropObjectOnItemFunction', error); });
  653. };
  654. }
  655. if (this.timelineSetting.Advanced && !flexygo.utils.isBlank(this.timelineSetting.OnMovingFunction)) {
  656. visOptions.onMoving = (item, callback) => {
  657. new Function('item', `return new Promise((resolve, reject) => { try {${this.timelineSetting.OnMovingFunction}} catch (ex) { reject(ex); } });`).call(this, item).then(function (data) { callback(data); }, function (error) { callback(item); console.error('FlexyGo Timeline', 'onMovingFunction', error); });
  658. };
  659. }
  660. if (this.timelineSetting.Advanced && !flexygo.utils.isBlank(this.timelineSetting.ItemVisibleFrameTemplate)) {
  661. visOptions.visibleFrameTemplate = (item, element) => {
  662. return (item && item.data) ? flexygo.utils.parser.recursiveCompile(item.data, this.timelineSetting.ItemVisibleFrameTemplate) : '';
  663. };
  664. }
  665. return $.extend(true, visOptions, this.isJson(this.timelineSetting.CustomOptions) ? JSON.parse(this.timelineSetting.CustomOptions, (key, value) => (value && (typeof value === 'string') && value.indexOf('function') === 0) ? new Function('return ' + value)() : value) : null);
  666. }
  667. /**
  668. * Build Vis Item.
  669. * @method buildVisItem
  670. * @param {object} data Data item.
  671. */
  672. buildVisItem(data, isNew = false) {
  673. if (data) {
  674. let id = new Array;
  675. this.timelineSetting.EntityConfiguration.ObjectKeys.forEach((key) => id.push({ [key]: data[key] }));
  676. let item = {
  677. id: JSON.stringify(id),
  678. group: data[(this.timelineSetting.Advanced) ? this.timelineSetting.ItemGroupField : this.timelineSetting.PropertyGroup],
  679. content: (this.timelineSetting.ItemContentTemplate) ? flexygo.utils.parser.recursiveCompile(data, this.timelineSetting.ItemContentTemplate) : (data[(this.timelineSetting.Advanced) ? this.timelineSetting.ItemDescripField : this.timelineSetting.PropertyDescrip]) ? data[(this.timelineSetting.Advanced) ? this.timelineSetting.ItemDescripField : this.timelineSetting.PropertyDescrip] : flexygo.localization.translate('flxtimeline.withoutDescription'),
  680. start: (data[(this.timelineSetting.Advanced) ? this.timelineSetting.ItemStartDateField : this.timelineSetting.PropertyStartDate]) ? moment(data[(this.timelineSetting.Advanced) ? this.timelineSetting.ItemStartDateField : this.timelineSetting.PropertyStartDate]).utc().format("YYYY-MM-DD HH:mm") : null,
  681. end: (data[(this.timelineSetting.Advanced) ? this.timelineSetting.ItemEndDateField : this.timelineSetting.PropertyEndDate]) ? moment(data[(this.timelineSetting.Advanced) ? this.timelineSetting.ItemEndDateField : this.timelineSetting.PropertyEndDate]).utc().format("YYYY-MM-DD HH:mm") : null,
  682. title: data[(this.timelineSetting.Advanced) ? this.timelineSetting.ItemDescripField : this.timelineSetting.PropertyDescrip],
  683. withOutGroup: (data[(this.timelineSetting.Advanced) ? this.timelineSetting.ItemGroupField : this.timelineSetting.PropertyGroup]) ? false : true,
  684. editable: (this.timelineSetting.Advanced && this.timelineSetting.Editable && this.checkEditableProperty(data[this.timelineSetting.ItemEditableField])) ? JSON.parse(data[this.timelineSetting.ItemEditableField]) : undefined,
  685. className: (this.timelineSetting.Advanced) ? data[this.timelineSetting.ItemClassNameField] : null,
  686. style: (this.timelineSetting.Advanced) ? data[this.timelineSetting.ItemStyleField] : null,
  687. type: (this.timelineSetting.Advanced && data[this.timelineSetting.ItemTypeField]) ? data[this.timelineSetting.ItemTypeField].trim().toLowerCase() : null,
  688. data: data
  689. };
  690. if (isNew) {
  691. item.start = null;
  692. item.end = null;
  693. }
  694. return item;
  695. }
  696. else {
  697. return null;
  698. }
  699. }
  700. /**
  701. * Build Vis Group.
  702. * @method buildVisGroup
  703. * @param {object} data Data group.
  704. */
  705. buildVisGroup(data, order) {
  706. return (data) ? {
  707. value: order,
  708. id: data[this.timelineSetting.GroupIdField] || data[this.timelineSetting.PropertyGroup],
  709. content: (this.timelineSetting.GroupContentTemplate) ? flexygo.utils.parser.recursiveCompile(data, this.timelineSetting.GroupContentTemplate) : data[this.timelineSetting.GroupDescripField] || data[this.timelineSetting.PropertyGroup],
  710. //title: data[this.timelineSetting.GroupDescripField] || data[this.timelineSetting.PropertyGroup],
  711. className: (this.timelineSetting.Advanced) ? data[this.timelineSetting.GroupClassNameField] : null,
  712. style: (this.timelineSetting.Advanced) ? data[this.timelineSetting.GroupStyleField] : null,
  713. data: data
  714. /* Not Implemented
  715. subgroupStack
  716. visible
  717. nestedGroups
  718. showNested
  719. */
  720. } : null;
  721. }
  722. /**
  723. * Object Actions.
  724. * @method objectActions
  725. * @param {flexygo.api.timeline.visTimelineItem} object item object.
  726. * @param {string} action action.
  727. */
  728. objectActions(object, action) {
  729. return new Promise((resolve, reject) => {
  730. try {
  731. let objectEntity = new flexygo.obj.Entity(this.timelineSetting.EntityConfiguration.ObjectName, (this.hasIdStructure(object.id.toString())) ? this.getObjectWhere(JSON.parse(object.id.toString())) : null);
  732. switch (action) {
  733. case 'insert':
  734. case 'edit':
  735. case 'update':
  736. if (!(object.end)) {
  737. object.end = moment(object.start).add(this.timelineSetting.DefaultTime, 'minutes').format("YYYY-MM-DD HH:mm");
  738. }
  739. ;
  740. if ((action === 'insert' && this.timelineSetting.OnInsertOpenNewWithDefaults) || action === 'edit') {
  741. this.openObjectEdit(object, objectEntity).then(function (data) { resolve(data); }, function (error) { throw error; });
  742. }
  743. else {
  744. if (objectEntity.read()) {
  745. $.extend(true, objectEntity.data, { [this.timelineSetting.PropertyGroup]: this.timelineSetting.WithGroups && this.timelineSetting.PropertyGroup ? { Value: object.group } : undefined, [this.timelineSetting.PropertyStartDate]: { Value: moment(object.start).format("YYYY-MM-DD HH:mm") }, [this.timelineSetting.PropertyEndDate]: this.timelineSetting.PropertyEndDate ? { Value: object.end ? moment(object.end).format("YYYY-MM-DD HH:mm") : object.end } : undefined });
  746. if ((action === 'insert') ? objectEntity.insert() : objectEntity.update()) {
  747. objectEntity.objectName = this.timelineSetting.EntityConfiguration.CollectionName;
  748. resolve(this.buildVisItem(objectEntity.getView((this.timelineSetting.Advanced) ? this.timelineSetting.ItemViewName : null, null, null, null, null, null, true)[0]));
  749. }
  750. else {
  751. resolve(null);
  752. }
  753. }
  754. else {
  755. resolve(null);
  756. }
  757. }
  758. break;
  759. case 'delete':
  760. flexygo.msg.confirm(flexygo.localization.translate('flxeditgrid.deleteconfirm'), (result) => {
  761. if (result) {
  762. if (this.timelineSetting.OnDeleteFunction) {
  763. let delFun = new Function("entity", "timeline", this.timelineSetting.OnDeleteFunction);
  764. resolve((delFun.call(objectEntity, objectEntity, this)) ? object : null);
  765. }
  766. else {
  767. resolve((objectEntity.delete()) ? object : null);
  768. }
  769. }
  770. });
  771. break;
  772. default:
  773. resolve(null);
  774. break;
  775. }
  776. }
  777. catch (ex) {
  778. reject(ex);
  779. }
  780. });
  781. }
  782. /**
  783. * Open Object Edit.
  784. * @method openObjectEdit
  785. * @param {flexygo.api.timeline.visTimelineItem} object item object.
  786. * @param {flexygo.obj.Entity} objectEntity object Entity.
  787. */
  788. openObjectEdit(object, objectEntity) {
  789. return new Promise((resolve, reject) => {
  790. try {
  791. flexygo.nav.openPage(this.timelineSetting.EventPageTypeId, objectEntity.objectName, objectEntity.objectWhere, JSON.stringify(Object.assign(this.defaults, { [this.timelineSetting.PropertyStartDate]: moment(object.start).format("YYYY-MM-DD HH:mm"), [this.timelineSetting.PropertyEndDate]: this.timelineSetting.PropertyEndDate ? object.end ? moment(object.end).format("YYYY-MM-DD HH:mm") : object.end : undefined, [this.timelineSetting.PropertyGroup]: this.timelineSetting.WithGroups && this.timelineSetting.PropertyGroup ? object.group : undefined })), 'modal');
  792. flexygo.events.on(this, 'entity', 'all', (e) => {
  793. if (this === e.context && e.sender.objectName === objectEntity.objectName) {
  794. flexygo.events.off(this, 'entity', 'all');
  795. flexygo.events.off(this, 'dialog', 'closed');
  796. if (e.type === 'deleted') {
  797. resolve(null);
  798. this.visTimeline.itemsData.remove(object.id);
  799. }
  800. else {
  801. e.sender.objectName = this.timelineSetting.EntityConfiguration.CollectionName;
  802. resolve(this.buildVisItem(e.sender.getView((this.timelineSetting.Advanced) ? this.timelineSetting.ItemViewName : null, null, null, null, null, null, true)[0]));
  803. }
  804. flexygo.nav.closePage($(`flx-edit[objectname="${objectEntity.objectName}"]`));
  805. }
  806. });
  807. flexygo.events.on(this, 'dialog', 'closed', (e) => {
  808. if (this === e.context && e.sender.objectname === objectEntity.objectName) {
  809. flexygo.events.off(this, 'entity', 'all');
  810. flexygo.events.off(this, 'dialog', 'closed');
  811. resolve(null);
  812. }
  813. });
  814. }
  815. catch (ex) {
  816. reject(ex);
  817. }
  818. });
  819. }
  820. /**
  821. * Get Object Where.
  822. * @method getObjectWhere
  823. * @param {Array} id id object.
  824. */
  825. getObjectWhere(id) {
  826. let where = ``;
  827. id.forEach((value, index) => {
  828. where += `[${this.timelineSetting.EntityConfiguration.TableName}].[${Object.keys(value)[0]}] = '${value[Object.keys(value)[0]]}'${(index < (id.length - 1)) ? ` AND ` : ``}`;
  829. });
  830. return where;
  831. }
  832. /**
  833. * Has id structure.
  834. * @method hasIdStructure
  835. * @param {string} id id object.
  836. */
  837. hasIdStructure(id) {
  838. try {
  839. if (isNaN(JSON.parse(id))) {
  840. return true;
  841. }
  842. else {
  843. return false;
  844. }
  845. }
  846. catch (e) {
  847. return false;
  848. }
  849. }
  850. /**
  851. * Has id structure.
  852. * @method hasIdStructure
  853. * @param {string} json JSON String
  854. */
  855. isJson(json) {
  856. try {
  857. JSON.parse(json);
  858. }
  859. catch (e) {
  860. return false;
  861. }
  862. return true;
  863. }
  864. /**
  865. * Check Editable Property.
  866. * @method checkEditableProperty
  867. * @param {string} valueJson JSON String
  868. */
  869. checkEditableProperty(valueJson) {
  870. let value;
  871. if (this.isJson(valueJson) && valueJson) {
  872. value = JSON.parse(valueJson);
  873. if (typeof (value) === 'boolean') {
  874. return true;
  875. }
  876. else if (typeof (value) === 'object') {
  877. for (const key of ['updateGroup', 'updateTime', 'remove']) {
  878. if (key in value) {
  879. return true;
  880. }
  881. }
  882. return false;
  883. }
  884. else {
  885. return false;
  886. }
  887. }
  888. return false;
  889. }
  890. /**
  891. * Load filters
  892. * @method loadFilters
  893. */
  894. loadFilters() {
  895. if (this.searchSettings) {
  896. let pane = $(this.wcParentModule).find('.cntBodyHeader .filterPanel');
  897. let filter = $('<flx-filter></flx-filter>');
  898. let wcFilter = filter[0];
  899. if (pane.length == 0 && this.toolbar && Object.keys(this.toolbar).length > 0) {
  900. pane = $('<div class="filterPanel"/>');
  901. $(this.wcParentModule).find('.cntBodyHeader').append(pane);
  902. }
  903. pane.html(filter);
  904. if (wcFilter) {
  905. wcFilter.settings = this.searchSettings;
  906. wcFilter.key = this.timelineSetting.EntityConfiguration.ObjectName + '-' + this.moduleName;
  907. wcFilter.grid = this;
  908. wcFilter.init();
  909. }
  910. }
  911. }
  912. /**
  913. * Establish webcomponent settings
  914. * @method configure
  915. */
  916. configure() {
  917. flexygo.nav.openPage('edit', 'sysTimeline_Setting', `[Timelines_Settings].[TimelineSettingName]='${this.timelineSetting.TimelineSettingName}'`, null, 'popup', true);
  918. }
  919. /**
  920. * Fires when element is attached to DOM
  921. * @method connectedCallback
  922. */
  923. connectedCallback() {
  924. let element = $(this);
  925. this.connected = true;
  926. this.moduleName = element.attr('ModuleName');
  927. this.objectName = element.attr('ObjectName');
  928. this.objectWhere = element.attr('ObjectWhere');
  929. if (element.attr('manualInit') != 'true') {
  930. this.init();
  931. }
  932. }
  933. /**
  934. * Fires when element is detached to DOM
  935. * @method disconnectedCallback
  936. */
  937. disconnectedCallback() {
  938. flexygo.events.off(this, 'entity', 'all');
  939. flexygo.events.off(this, 'dialog', 'closed');
  940. $(document).off('keydown.timeline keyup.timeline');
  941. }
  942. /**
  943. * Fires when the attribute value of the element is changed.
  944. * @method attributeChangedCallback
  945. */
  946. attributeChangedCallback(attrName, oldVal, newVal) {
  947. let needInit = false;
  948. if (attrName.toLowerCase() == 'modulename' && newVal) {
  949. this.moduleName = newVal;
  950. needInit = true;
  951. }
  952. else if (attrName.toLowerCase() == 'objectname' && newVal) {
  953. this.objectName = newVal;
  954. needInit = true;
  955. }
  956. else if (attrName.toLowerCase() == 'objectwhere' && newVal) {
  957. this.objectWhere = newVal;
  958. needInit = true;
  959. }
  960. if (this.connected && needInit) {
  961. this.init();
  962. }
  963. }
  964. }
  965. wc.FlxTimelineElement = FlxTimelineElement;
  966. /**
  967. * Library for the FlxTimelineProgressBar
  968. *
  969. * @class FlxTimelineProgressBar
  970. * @constructor
  971. * @return {FlxTimelineProgressBar} .
  972. */
  973. class FlxTimelineProgressBar extends HTMLElement {
  974. constructor() {
  975. //If a constructor is defined, is REQUIRED call the super constructor
  976. super();
  977. /**
  978. * Set if element has been connected to DOM
  979. * @property connected {boolean}
  980. */
  981. this.connected = false;
  982. }
  983. /**
  984. * Array of observed attributes. REQUIRED
  985. * @property observedAttributes {Array}
  986. */
  987. static get observedAttributes() {
  988. return ['color', 'percentage'];
  989. }
  990. /**
  991. * Init the webcomponent. REQUIRED.
  992. * @method init
  993. */
  994. init() {
  995. this.render();
  996. }
  997. /**
  998. * Refresh de webcomponent. REQUIRED.
  999. * @method refresh
  1000. */
  1001. refresh() {
  1002. this.init();
  1003. }
  1004. /**
  1005. * Render HTML data.
  1006. * @method render
  1007. */
  1008. render() {
  1009. let element = $(this);
  1010. element.html(`<div class="progressbar-container">
  1011. <div class="bar" style="width: ${this.percentage}%${(this.color) ? ';background-color:' + this.color : ''}; "> </div>
  1012. <div class="template" style="${(!this.percentage) ? 'color:#b31512' : ''}">${(this.percentage) ? (this.template.trim()) ? this.template : this.percentage + '%' : flexygo.localization.translate('flxtimeline.withoutpercentage')}</div>
  1013. </div>`);
  1014. }
  1015. /**
  1016. * Fires when element is attached to DOM
  1017. * @method connectedCallback
  1018. */
  1019. connectedCallback() {
  1020. let element = $(this);
  1021. if (!this.connected) {
  1022. this.template = element.html();
  1023. }
  1024. this.connected = true;
  1025. this.color = element.attr("color");
  1026. this.percentage = element.attr("percentage");
  1027. this.init();
  1028. }
  1029. /**
  1030. * Fires when the attribute value of the element is changed.
  1031. * @method attributeChangedCallback
  1032. */
  1033. attributeChangedCallback(attrName, oldVal, newVal) {
  1034. let needInit = false;
  1035. if (attrName.toLowerCase() == 'color' && newVal) {
  1036. this.color = newVal;
  1037. needInit = true;
  1038. }
  1039. else if (attrName.toLowerCase() == 'percentage' && newVal) {
  1040. this.percentage = newVal;
  1041. needInit = true;
  1042. }
  1043. if (this.connected && needInit) {
  1044. this.init();
  1045. }
  1046. }
  1047. }
  1048. wc.FlxTimelineProgressBar = FlxTimelineProgressBar;
  1049. })(wc = ui.wc || (ui.wc = {}));
  1050. })(ui = flexygo.ui || (flexygo.ui = {}));
  1051. })(flexygo || (flexygo = {}));
  1052. window.customElements.define('flx-timeline', flexygo.ui.wc.FlxTimelineElement);
  1053. window.customElements.define('flx-timeline-progressbar', flexygo.ui.wc.FlxTimelineProgressBar);
  1054. //# sourceMappingURL=flx-timeline.js.map