├── src
├── img
│ ├── info.png
│ ├── icon-milestone.png
│ ├── icon-reportable.png
│ ├── icon-timesheets.png
│ ├── icon-acttimesheets.png
│ └── icon-deliverable.png
├── css
│ ├── favicon.ico
│ ├── openhand.cur
│ ├── closedhand.cur
│ ├── images
│ │ ├── bg.jpg
│ │ ├── cut.png
│ │ ├── top.png
│ │ ├── bottom.png
│ │ ├── door.png
│ │ ├── link.png
│ │ ├── user.png
│ │ ├── comment.png
│ │ ├── comments.png
│ │ ├── OneViewPPMg.png
│ │ ├── arrow_left.png
│ │ ├── arrow_right.png
│ │ ├── properties.png
│ │ ├── animated-overlay.gif
│ │ ├── page_white_add.png
│ │ ├── page_white_copy.png
│ │ ├── page_white_edit.png
│ │ ├── page_white_paste.png
│ │ ├── page_white_delete.png
│ │ ├── ui-icons_222222_256x240.png
│ │ ├── ui-icons_228ef1_256x240.png
│ │ ├── ui-icons_3572ac_256x240.png
│ │ ├── ui-icons_8c291d_256x240.png
│ │ ├── ui-icons_b83400_256x240.png
│ │ ├── ui-icons_ef8c08_256x240.png
│ │ ├── ui-icons_fbdb93_256x240.png
│ │ ├── ui-icons_ffd27a_256x240.png
│ │ ├── ui-icons_ffffff_256x240.png
│ │ ├── ui-bg_flat_10_000000_40x100.png
│ │ ├── ui-bg_glass_100_f6f6f6_1x400.png
│ │ ├── ui-bg_glass_100_fdf5ce_1x400.png
│ │ ├── ui-bg_glass_65_ffffff_1x400.png
│ │ ├── ui-bg_fine-grain_10_eceadf_60x60.png
│ │ ├── ui-bg_fine-grain_10_f8f7f6_60x60.png
│ │ ├── ui-bg_fine-grain_15_eceadf_60x60.png
│ │ ├── ui-bg_fine-grain_15_f7f3de_60x60.png
│ │ ├── ui-bg_fine-grain_15_ffffff_60x60.png
│ │ ├── ui-bg_fine-grain_65_654b24_60x60.png
│ │ ├── ui-bg_fine-grain_68_b83400_60x60.png
│ │ ├── ui-bg_diagonal-maze_20_6e4f1c_10x10.png
│ │ ├── ui-bg_diagonal-maze_40_000000_10x10.png
│ │ ├── ui-bg_gloss-wave_35_f6a828_500x100.png
│ │ ├── ui-bg_highlight-soft_75_ffe45c_1x100.png
│ │ ├── ui-bg_diagonals-thick_18_b81900_40x40.png
│ │ ├── ui-bg_diagonals-thick_20_666666_40x40.png
│ │ └── ui-bg_highlight-soft_100_eeeeee_1x100.png
│ ├── TopMenu.css
│ ├── reset.css
│ ├── sortable.css
│ ├── loader.css
│ ├── jquery.contextMenu.css
│ └── gantt.css
├── libs
│ ├── jquery.comment
│ │ ├── comment.gif
│ │ ├── ajax-loader.gif
│ │ ├── add_comment_ico.png
│ │ ├── jquery.comment.min.css
│ │ └── jquery.comment.js
│ ├── images
│ │ ├── ui-icons_222222_256x240.png
│ │ ├── ui-icons_228ef1_256x240.png
│ │ ├── ui-icons_ef8c08_256x240.png
│ │ ├── ui-icons_ffd27a_256x240.png
│ │ ├── ui-icons_ffffff_256x240.png
│ │ ├── ui-bg_flat_10_000000_40x100.png
│ │ ├── ui-bg_glass_65_ffffff_1x400.png
│ │ ├── ui-bg_glass_100_f6f6f6_1x400.png
│ │ ├── ui-bg_glass_100_fdf5ce_1x400.png
│ │ ├── ui-bg_gloss-wave_35_f6a828_500x100.png
│ │ ├── ui-bg_diagonals-thick_18_b81900_40x40.png
│ │ ├── ui-bg_diagonals-thick_20_666666_40x40.png
│ │ ├── ui-bg_highlight-soft_100_eeeeee_1x100.png
│ │ └── ui-bg_highlight-soft_75_ffe45c_1x100.png
│ ├── semantic
│ │ └── themes
│ │ │ ├── basic
│ │ │ └── assets
│ │ │ │ └── fonts
│ │ │ │ ├── icons.eot
│ │ │ │ ├── icons.ttf
│ │ │ │ └── icons.woff
│ │ │ └── default
│ │ │ └── assets
│ │ │ ├── fonts
│ │ │ ├── icons.eot
│ │ │ ├── icons.otf
│ │ │ ├── icons.ttf
│ │ │ └── icons.woff
│ │ │ └── images
│ │ │ └── flags.png
│ ├── backbone.KonvaView.js
│ ├── jquery.mousewheel.js
│ ├── FileSaver.js
│ └── xmlToJSON.js
└── js
│ ├── views
│ ├── Notifications.js
│ ├── TopMenuView
│ │ ├── TopMenuView.js
│ │ ├── GroupingMenuView.js
│ │ ├── MiscMenuView.js
│ │ ├── ZoomMenuView.js
│ │ ├── FilterMenuView.js
│ │ └── MSProjectMenuView.js
│ ├── sideBar
│ │ ├── DatePicker.js
│ │ ├── ContextMenuView.js
│ │ ├── NestedTask.js
│ │ └── SidePanel.js
│ ├── canvasChart
│ │ ├── NestedTaskView.js
│ │ ├── ConnectorView.js
│ │ └── AloneTaskView.js
│ ├── ResourcesEditor.js
│ ├── CommentsView.js
│ ├── GanttView.js
│ └── ModalTaskEditView.js
│ ├── clientConfig.js
│ ├── models
│ ├── ResourceReference.js
│ ├── ResourceReferenceCollection.js
│ ├── TaskModel.js
│ └── SettingModel.js
│ ├── utils
│ ├── util.js
│ └── xmlWorker.js
│ ├── collections
│ ├── ResourceReferenceCollection.js
│ └── TaskCollection.js
│ └── app.js
├── resources
├── ganttImage2.jpg
└── jQueryGantt.png
├── .gitignore
├── gulpfile.js
├── package.json
├── data
├── config.js
├── comments.js
├── resources.js
└── tasks.js
├── README.md
├── test_server.js
└── .eslintrc
/src/img/info.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/img/info.png
--------------------------------------------------------------------------------
/src/css/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/css/favicon.ico
--------------------------------------------------------------------------------
/src/css/openhand.cur:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/css/openhand.cur
--------------------------------------------------------------------------------
/src/css/closedhand.cur:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/css/closedhand.cur
--------------------------------------------------------------------------------
/src/css/images/bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/css/images/bg.jpg
--------------------------------------------------------------------------------
/src/css/images/cut.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/css/images/cut.png
--------------------------------------------------------------------------------
/src/css/images/top.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/css/images/top.png
--------------------------------------------------------------------------------
/resources/ganttImage2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/resources/ganttImage2.jpg
--------------------------------------------------------------------------------
/resources/jQueryGantt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/resources/jQueryGantt.png
--------------------------------------------------------------------------------
/src/css/images/bottom.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/css/images/bottom.png
--------------------------------------------------------------------------------
/src/css/images/door.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/css/images/door.png
--------------------------------------------------------------------------------
/src/css/images/link.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/css/images/link.png
--------------------------------------------------------------------------------
/src/css/images/user.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/css/images/user.png
--------------------------------------------------------------------------------
/src/css/images/comment.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/css/images/comment.png
--------------------------------------------------------------------------------
/src/css/images/comments.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/css/images/comments.png
--------------------------------------------------------------------------------
/src/img/icon-milestone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/img/icon-milestone.png
--------------------------------------------------------------------------------
/src/img/icon-reportable.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/img/icon-reportable.png
--------------------------------------------------------------------------------
/src/img/icon-timesheets.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/img/icon-timesheets.png
--------------------------------------------------------------------------------
/src/css/images/OneViewPPMg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/css/images/OneViewPPMg.png
--------------------------------------------------------------------------------
/src/css/images/arrow_left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/css/images/arrow_left.png
--------------------------------------------------------------------------------
/src/css/images/arrow_right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/css/images/arrow_right.png
--------------------------------------------------------------------------------
/src/css/images/properties.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/css/images/properties.png
--------------------------------------------------------------------------------
/src/img/icon-acttimesheets.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/img/icon-acttimesheets.png
--------------------------------------------------------------------------------
/src/img/icon-deliverable.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/img/icon-deliverable.png
--------------------------------------------------------------------------------
/src/css/images/animated-overlay.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/css/images/animated-overlay.gif
--------------------------------------------------------------------------------
/src/css/images/page_white_add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/css/images/page_white_add.png
--------------------------------------------------------------------------------
/src/css/images/page_white_copy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/css/images/page_white_copy.png
--------------------------------------------------------------------------------
/src/css/images/page_white_edit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/css/images/page_white_edit.png
--------------------------------------------------------------------------------
/src/css/images/page_white_paste.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/css/images/page_white_paste.png
--------------------------------------------------------------------------------
/src/libs/jquery.comment/comment.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/libs/jquery.comment/comment.gif
--------------------------------------------------------------------------------
/src/css/images/page_white_delete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/css/images/page_white_delete.png
--------------------------------------------------------------------------------
/src/libs/jquery.comment/ajax-loader.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/libs/jquery.comment/ajax-loader.gif
--------------------------------------------------------------------------------
/src/css/images/ui-icons_222222_256x240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/css/images/ui-icons_222222_256x240.png
--------------------------------------------------------------------------------
/src/css/images/ui-icons_228ef1_256x240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/css/images/ui-icons_228ef1_256x240.png
--------------------------------------------------------------------------------
/src/css/images/ui-icons_3572ac_256x240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/css/images/ui-icons_3572ac_256x240.png
--------------------------------------------------------------------------------
/src/css/images/ui-icons_8c291d_256x240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/css/images/ui-icons_8c291d_256x240.png
--------------------------------------------------------------------------------
/src/css/images/ui-icons_b83400_256x240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/css/images/ui-icons_b83400_256x240.png
--------------------------------------------------------------------------------
/src/css/images/ui-icons_ef8c08_256x240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/css/images/ui-icons_ef8c08_256x240.png
--------------------------------------------------------------------------------
/src/css/images/ui-icons_fbdb93_256x240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/css/images/ui-icons_fbdb93_256x240.png
--------------------------------------------------------------------------------
/src/css/images/ui-icons_ffd27a_256x240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/css/images/ui-icons_ffd27a_256x240.png
--------------------------------------------------------------------------------
/src/css/images/ui-icons_ffffff_256x240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/css/images/ui-icons_ffffff_256x240.png
--------------------------------------------------------------------------------
/src/libs/images/ui-icons_222222_256x240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/libs/images/ui-icons_222222_256x240.png
--------------------------------------------------------------------------------
/src/libs/images/ui-icons_228ef1_256x240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/libs/images/ui-icons_228ef1_256x240.png
--------------------------------------------------------------------------------
/src/libs/images/ui-icons_ef8c08_256x240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/libs/images/ui-icons_ef8c08_256x240.png
--------------------------------------------------------------------------------
/src/libs/images/ui-icons_ffd27a_256x240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/libs/images/ui-icons_ffd27a_256x240.png
--------------------------------------------------------------------------------
/src/libs/images/ui-icons_ffffff_256x240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/libs/images/ui-icons_ffffff_256x240.png
--------------------------------------------------------------------------------
/src/libs/jquery.comment/add_comment_ico.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/libs/jquery.comment/add_comment_ico.png
--------------------------------------------------------------------------------
/src/css/images/ui-bg_flat_10_000000_40x100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/css/images/ui-bg_flat_10_000000_40x100.png
--------------------------------------------------------------------------------
/src/css/images/ui-bg_glass_100_f6f6f6_1x400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/css/images/ui-bg_glass_100_f6f6f6_1x400.png
--------------------------------------------------------------------------------
/src/css/images/ui-bg_glass_100_fdf5ce_1x400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/css/images/ui-bg_glass_100_fdf5ce_1x400.png
--------------------------------------------------------------------------------
/src/css/images/ui-bg_glass_65_ffffff_1x400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/css/images/ui-bg_glass_65_ffffff_1x400.png
--------------------------------------------------------------------------------
/src/libs/images/ui-bg_flat_10_000000_40x100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/libs/images/ui-bg_flat_10_000000_40x100.png
--------------------------------------------------------------------------------
/src/libs/images/ui-bg_glass_65_ffffff_1x400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/libs/images/ui-bg_glass_65_ffffff_1x400.png
--------------------------------------------------------------------------------
/src/libs/images/ui-bg_glass_100_f6f6f6_1x400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/libs/images/ui-bg_glass_100_f6f6f6_1x400.png
--------------------------------------------------------------------------------
/src/libs/images/ui-bg_glass_100_fdf5ce_1x400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/libs/images/ui-bg_glass_100_fdf5ce_1x400.png
--------------------------------------------------------------------------------
/src/css/images/ui-bg_fine-grain_10_eceadf_60x60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/css/images/ui-bg_fine-grain_10_eceadf_60x60.png
--------------------------------------------------------------------------------
/src/css/images/ui-bg_fine-grain_10_f8f7f6_60x60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/css/images/ui-bg_fine-grain_10_f8f7f6_60x60.png
--------------------------------------------------------------------------------
/src/css/images/ui-bg_fine-grain_15_eceadf_60x60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/css/images/ui-bg_fine-grain_15_eceadf_60x60.png
--------------------------------------------------------------------------------
/src/css/images/ui-bg_fine-grain_15_f7f3de_60x60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/css/images/ui-bg_fine-grain_15_f7f3de_60x60.png
--------------------------------------------------------------------------------
/src/css/images/ui-bg_fine-grain_15_ffffff_60x60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/css/images/ui-bg_fine-grain_15_ffffff_60x60.png
--------------------------------------------------------------------------------
/src/css/images/ui-bg_fine-grain_65_654b24_60x60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/css/images/ui-bg_fine-grain_65_654b24_60x60.png
--------------------------------------------------------------------------------
/src/css/images/ui-bg_fine-grain_68_b83400_60x60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/css/images/ui-bg_fine-grain_68_b83400_60x60.png
--------------------------------------------------------------------------------
/src/css/images/ui-bg_diagonal-maze_20_6e4f1c_10x10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/css/images/ui-bg_diagonal-maze_20_6e4f1c_10x10.png
--------------------------------------------------------------------------------
/src/css/images/ui-bg_diagonal-maze_40_000000_10x10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/css/images/ui-bg_diagonal-maze_40_000000_10x10.png
--------------------------------------------------------------------------------
/src/css/images/ui-bg_gloss-wave_35_f6a828_500x100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/css/images/ui-bg_gloss-wave_35_f6a828_500x100.png
--------------------------------------------------------------------------------
/src/css/images/ui-bg_highlight-soft_75_ffe45c_1x100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/css/images/ui-bg_highlight-soft_75_ffe45c_1x100.png
--------------------------------------------------------------------------------
/src/libs/images/ui-bg_gloss-wave_35_f6a828_500x100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/libs/images/ui-bg_gloss-wave_35_f6a828_500x100.png
--------------------------------------------------------------------------------
/src/libs/semantic/themes/basic/assets/fonts/icons.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/libs/semantic/themes/basic/assets/fonts/icons.eot
--------------------------------------------------------------------------------
/src/libs/semantic/themes/basic/assets/fonts/icons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/libs/semantic/themes/basic/assets/fonts/icons.ttf
--------------------------------------------------------------------------------
/src/libs/semantic/themes/basic/assets/fonts/icons.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/libs/semantic/themes/basic/assets/fonts/icons.woff
--------------------------------------------------------------------------------
/src/libs/semantic/themes/default/assets/fonts/icons.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/libs/semantic/themes/default/assets/fonts/icons.eot
--------------------------------------------------------------------------------
/src/libs/semantic/themes/default/assets/fonts/icons.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/libs/semantic/themes/default/assets/fonts/icons.otf
--------------------------------------------------------------------------------
/src/libs/semantic/themes/default/assets/fonts/icons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/libs/semantic/themes/default/assets/fonts/icons.ttf
--------------------------------------------------------------------------------
/src/css/images/ui-bg_diagonals-thick_18_b81900_40x40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/css/images/ui-bg_diagonals-thick_18_b81900_40x40.png
--------------------------------------------------------------------------------
/src/css/images/ui-bg_diagonals-thick_20_666666_40x40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/css/images/ui-bg_diagonals-thick_20_666666_40x40.png
--------------------------------------------------------------------------------
/src/css/images/ui-bg_highlight-soft_100_eeeeee_1x100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/css/images/ui-bg_highlight-soft_100_eeeeee_1x100.png
--------------------------------------------------------------------------------
/src/libs/images/ui-bg_diagonals-thick_18_b81900_40x40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/libs/images/ui-bg_diagonals-thick_18_b81900_40x40.png
--------------------------------------------------------------------------------
/src/libs/images/ui-bg_diagonals-thick_20_666666_40x40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/libs/images/ui-bg_diagonals-thick_20_666666_40x40.png
--------------------------------------------------------------------------------
/src/libs/images/ui-bg_highlight-soft_100_eeeeee_1x100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/libs/images/ui-bg_highlight-soft_100_eeeeee_1x100.png
--------------------------------------------------------------------------------
/src/libs/images/ui-bg_highlight-soft_75_ffe45c_1x100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/libs/images/ui-bg_highlight-soft_75_ffe45c_1x100.png
--------------------------------------------------------------------------------
/src/libs/semantic/themes/default/assets/fonts/icons.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/libs/semantic/themes/default/assets/fonts/icons.woff
--------------------------------------------------------------------------------
/src/libs/semantic/themes/default/assets/images/flags.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2bitcoder/Jquery-Gantt/HEAD/src/libs/semantic/themes/default/assets/images/flags.png
--------------------------------------------------------------------------------
/src/css/TopMenu.css:
--------------------------------------------------------------------------------
1 | #top-menu {
2 | font-size: 1em;
3 | }
4 |
5 | #top-menu .header {
6 | font-weight: bold;
7 | }
8 |
9 | #top-menu .action {
10 | cursor : pointer;
11 | }
--------------------------------------------------------------------------------
/src/js/views/Notifications.js:
--------------------------------------------------------------------------------
1 | var Notifications = Backbone.View.extend({
2 | initialize : function() {
3 | this.listenTo(this.collection, 'error', _.debounce(this.onError, 10));
4 | },
5 | onError : function() {
6 | console.error(arguments);
7 | noty({
8 | text: 'Error while saving task, please refresh your browser, request support if this error continues.',
9 | layout : 'topRight',
10 | type : 'error'
11 | });
12 | }
13 | });
14 |
15 | module.exports = Notifications;
16 |
--------------------------------------------------------------------------------
/src/js/clientConfig.js:
--------------------------------------------------------------------------------
1 | let util = require('./utils/util');
2 | let params = util.getURLParams();
3 |
4 | let tasksSubURL = '';
5 | // detect API params from get, e.g. ?project=143&profile=17&sitekey=2b00da46b57c0395
6 | if (params.project && params.profile && params.sitekey) {
7 | tasksSubURL = '/' + params.project + '/' + params.profile + '/' + params.sitekey;
8 | }
9 | export let tasksURL = 'api/tasks/' + tasksSubURL;
10 |
11 |
12 |
13 | let configSubURL = '';
14 | if (window.location.hostname.indexOf('localhost') === -1) {
15 | configSubURL = '/wbs/' + params.project + '/' + params.sitekey;
16 | }
17 |
18 | export let configURL = '/api/GanttConfig' + configSubURL;
19 |
20 |
--------------------------------------------------------------------------------
/src/js/models/ResourceReference.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var util = require('../utils/util');
4 | var params = util.getURLParams();
5 |
6 | var ResourceReference = Backbone.Model.extend({
7 | defaults: {
8 | // main params
9 | WBSID : 1, // task id
10 | ResID: 1, // resource id
11 | TSActivate: true,
12 |
13 | // some server params
14 | WBSProfileID : params.profile,
15 | WBS_ID : params.profile,
16 | PartitNo : params.sitekey, // have no idea what is that
17 | ProjectRef : params.project,
18 | sitekey: params.sitekey
19 |
20 | },
21 | initialize : function() {
22 |
23 | }
24 | });
25 |
26 | module.exports = ResourceReference;
27 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Windows image file caches
3 | Thumbs.db
4 | ehthumbs.db
5 |
6 | # Folder config file
7 | Desktop.ini
8 |
9 | # Recycle Bin used on file shares
10 | $RECYCLE.BIN/
11 |
12 | # Windows Installer files
13 | *.cab
14 | *.msi
15 | *.msm
16 | *.msp
17 | node_modules
18 | # =========================
19 | # Operating System Files
20 | # =========================
21 |
22 | # OSX
23 | # =========================
24 |
25 | .DS_Store
26 | .AppleDouble
27 | .LSOverride
28 |
29 | # Icon must ends with two \r.
30 | Icon
31 |
32 |
33 | # Thumbnails
34 | ._*
35 |
36 | # Files that might appear on external disk
37 | .Spotlight-V100
38 | .Trashes
39 |
40 |
41 |
42 | *sublime*
43 | .idea
44 |
--------------------------------------------------------------------------------
/src/js/views/TopMenuView/TopMenuView.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var ZoomMenuView = require('./ZoomMenuView');
3 | var GroupingMenuView = require('./GroupingMenuView');
4 | var FilterMenuView = require('./FilterMenuView');
5 | var MSProjectMenuView = require('./MSProjectMenuView');
6 | var MiscMenuView = require('./MiscMenuView');
7 |
8 | var TopMenuView = Backbone.View.extend({
9 | initialize : function(params) {
10 | new ZoomMenuView(params).render();
11 | new GroupingMenuView(params).render();
12 | new FilterMenuView(params).render();
13 | new MSProjectMenuView(params).render();
14 | new MiscMenuView(params).render();
15 | }
16 | });
17 |
18 | module.exports = TopMenuView;
19 |
--------------------------------------------------------------------------------
/src/js/views/TopMenuView/GroupingMenuView.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var GroupingMenuView = Backbone.View.extend({
4 | el : '#grouping-menu',
5 | initialize : function(params) {
6 | this.settings = params.settings;
7 | },
8 | events : {
9 | 'click #top-expand-all' : function() {
10 | this.collection.each(function(task) {
11 | if (task.isNested()) {
12 | task.set('collapsed', false);
13 | }
14 | });
15 | },
16 | 'click #top-collapse-all' : function() {
17 | this.collection.each(function(task) {
18 | if (task.isNested()) {
19 | task.set('collapsed', true);
20 | }
21 | });
22 | }
23 | }
24 | });
25 |
26 | module.exports = GroupingMenuView;
27 |
--------------------------------------------------------------------------------
/src/js/views/TopMenuView/MiscMenuView.js:
--------------------------------------------------------------------------------
1 | var ReportsMenuView = Backbone.View.extend({
2 | el: '#reports-menu',
3 | initialize: function(params) {
4 | this.settings = params.settings;
5 | },
6 | events: {
7 | 'click #print': 'generatePDF',
8 | 'click #showVideo': 'showHelp'
9 | },
10 | generatePDF: function(evt) {
11 | window.print();
12 | evt.preventDefault();
13 | },
14 | showHelp: function() {
15 | $('#showVideoModal').modal({
16 | onHidden: function() {
17 | $(document.body).removeClass('dimmable');
18 | },
19 | onApprove: function() {
20 | $(document.body).removeClass('dimmable');
21 | }
22 | }).modal('show');
23 | }
24 | });
25 |
26 | module.exports = ReportsMenuView;
27 |
--------------------------------------------------------------------------------
/src/js/views/TopMenuView/ZoomMenuView.js:
--------------------------------------------------------------------------------
1 | var ZoomMenuView = Backbone.View.extend({
2 | el: '#zoom-menu',
3 | initialize: function(params) {
4 | this.settings = params.settings;
5 | this._hightlightSelected();
6 | },
7 | events: {
8 | 'click .action': 'onIntervalButtonClicked'
9 | },
10 | onIntervalButtonClicked: function(evt) {
11 | var button = $(evt.currentTarget);
12 | var interval = button.data('interval');
13 | this.settings.set('interval', interval);
14 | this._hightlightSelected();
15 | },
16 | _hightlightSelected: function() {
17 | this.$('.action').removeClass('selected');
18 |
19 | let interval = this.settings.get('interval');
20 | this.$('[data-interval="' + interval + '"]').addClass('selected');
21 | }
22 | });
23 |
24 | module.exports = ZoomMenuView;
25 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | var gulp = require('gulp');
2 | var gutil = require('gulp-util');
3 | var browserify = require('gulp-browserify');
4 | var livereload = require('gulp-livereload');
5 | var rename = require('gulp-rename');
6 |
7 | // Basic usage
8 | gulp.task('scripts', function() {
9 | // Single entry point to browserify
10 | gulp.src('src/js/app.js')
11 | .pipe(browserify({
12 | debug: true,
13 | transform: [['babelify'], 'brfs']
14 | }).on('error', gutil.log))
15 | .pipe(rename('bundle.js'))
16 | .pipe(gulp.dest('./src/'))
17 | .pipe(livereload());
18 | });
19 |
20 | gulp.task('watch', function() {
21 | gulp.watch(['src/**/*.js', 'src/**/*.html', 'src/**/*.xml', '!src/bundle.js'], ['scripts']);
22 | });
23 |
24 | gulp.task('server', function() {
25 | require('./test_server');
26 | });
27 |
28 | gulp.task('default', ['scripts', 'watch', 'server']);
29 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "GanttJS",
3 | "version": "0.1.0",
4 | "description": "",
5 | "main": "test_server.js",
6 | "scripts": {
7 | "start": "gulp",
8 | "test": "node node_modules/mocha/bin/mocha --compilers js:babel/register",
9 | "debug-test": "node node_modules/mocha/bin/mocha --debug-brk --compilers js:babel/register"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "git@github.com:2bitcoder/Jquery-Gantt.git"
14 | },
15 | "license": "MIT License",
16 | "dependencies": {
17 | "backbone": "^1.1.2",
18 | "body-parser": "1.9.2",
19 | "brfs": "^1.2.0",
20 | "chai": "^1.10.0",
21 | "express": "4.10.1",
22 | "gulp": "^3.8.10",
23 | "gulp-browserify": "^0.5.0",
24 | "gulp-livereload": "^2.1.1",
25 | "gulp-util": "^3.0.1",
26 | "lodash": "^2.4.1",
27 | "morgan": "1.5.0",
28 | "serve-static": "^1.7.1",
29 | "underscore": "^1.7.0"
30 | },
31 | "devDependencies": {
32 | "babelify": "^5.0.4",
33 | "gulp-rename": "^1.2.0"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/css/reset.css:
--------------------------------------------------------------------------------
1 | /* v1.0 | 20080212 */
2 |
3 | html, body, div, span, applet, object, iframe,
4 | h1, h2, h3, h4, h5, h6, p, blockquote, pre,
5 | a, abbr, acronym, address, big, cite, code,
6 | del, dfn, em, font, img, ins, kbd, q, s, samp,
7 | small, strike, strong, sub, sup, tt, var,
8 | b, u, i, center,
9 | dl, dt, dd, ol, ul, li,
10 | fieldset, form, label, legend,
11 | table, caption, tbody, tfoot, thead, tr, th, td {
12 | margin: 0;
13 | padding: 0;
14 | border: 0;
15 | outline: 0;
16 | font-size: 100%;
17 | vertical-align: baseline;
18 | background: transparent;
19 | }
20 | body {
21 | line-height: 1;
22 | }
23 | ol, ul {
24 | list-style: none;
25 | }
26 | blockquote, q {
27 | quotes: none;
28 | }
29 | blockquote:before, blockquote:after,
30 | q:before, q:after {
31 | content: '';
32 | content: none;
33 | }
34 |
35 | /* remember to define focus styles! */
36 | :focus {
37 | outline: 0;
38 | }
39 |
40 | /* remember to highlight inserts somehow! */
41 | ins {
42 | text-decoration: none;
43 | }
44 | del {
45 | text-decoration: line-through;
46 | }
47 |
48 | /* tables still need 'cellspacing="0"' in the markup */
49 | table {
50 | border-collapse: collapse;
51 | border-spacing: 0;
52 | }
--------------------------------------------------------------------------------
/data/config.js:
--------------------------------------------------------------------------------
1 | module.exports = {"cfgdata":[{"Category":"Task Health","data":[{"Alias":"","cClose":null,"cDefault":false,"cfg_item":"Green","ID":16199,"SortOrder":0,"txtValue":null},{"Alias":"","cClose":null,"cDefault":false,"cfg_item":"Amber","ID":16200,"SortOrder":0,"txtValue":null},{"Alias":"","cClose":null,"cDefault":false,"cfg_item":"Red","ID":16201,"SortOrder":0,"txtValue":null}]},{"Category":"Task Status","data":[{"Alias":"","cClose":null,"cDefault":false,"cfg_item":"In Progress","ID":23,"SortOrder":1,"txtValue":null},{"Alias":"","cClose":true,"cDefault":false,"cfg_item":"Closed","ID":24,"SortOrder":2,"txtValue":null},{"Alias":"","cClose":null,"cDefault":false,"cfg_item":"Ready","ID":218,"SortOrder":0,"txtValue":null},{"Alias":"","cClose":false,"cDefault":true,"cfg_item":"Backlog","ID":16461,"SortOrder":1,"txtValue":null}]}],"resourcedata":[{"JobTitle":null,"UserId":1,"Username":"Greg Vandeligt"},{"JobTitle":null,"UserId":58,"Username":"James Gumley"},{"JobTitle":null,"UserId":65,"Username":"Management"},{"JobTitle":null,"UserId":68,"Username":"pmo user"}],"wodata":[{"data":[{"ID":43,"SortOrder":43,"WONumber":"WO10018"},{"ID":301,"SortOrder":301,"WONumber":"WO10296"},{"ID":313,"SortOrder":313,"WONumber":"WO10315"}],"WONumber":"WorkOrders"}]};
--------------------------------------------------------------------------------
/src/js/views/sideBar/DatePicker.js:
--------------------------------------------------------------------------------
1 | var DatePicker = React.createClass({
2 | displayName: 'DatePicker',
3 | componentDidMount: function() {
4 | $(this.getDOMNode()).datepicker({
5 | dateFormat: this.props.dateFormat,
6 | onSelect: function() {
7 | console.log('select');
8 | var date = this.getDOMNode().value.split('/');
9 | var value = new Date(date[2] + '-' + date[1] + '-' + date[0]);
10 | this.props.onChange({
11 | target: {
12 | value: value
13 | }
14 | });
15 | }.bind(this)
16 | });
17 | $(this.getDOMNode()).datepicker('show');
18 | },
19 | componentWillUnmount: function() {
20 | $(this.getDOMNode()).datepicker('destroy');
21 | },
22 | shouldComponentUpdate: function() {
23 | var dateStr = $.datepicker.formatDate(this.props.dateFormat, this.props.value);
24 | this.getDOMNode().value = dateStr;
25 | $(this.getDOMNode()).datepicker( "refresh" );
26 | return false;
27 | },
28 | render: function() {
29 | return React.createElement('input', {
30 | defaultValue: $.datepicker.formatDate(this.props.dateFormat, this.props.value)
31 | });
32 | }
33 | });
34 |
35 | module.exports = DatePicker;
36 |
--------------------------------------------------------------------------------
/src/js/utils/util.js:
--------------------------------------------------------------------------------
1 | var monthsCode=['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
2 | var _ = require('underscore');
3 |
4 | module.exports.correctdate = function(str) {
5 | "use strict";
6 | if (_.last(str) !== 'Z') {
7 | return str + 'Z';
8 | }
9 | return str;
10 | };
11 |
12 | module.exports.formatdata = function(val, type) {
13 | "use strict";
14 | if (type === 'm') {
15 | return monthsCode[val];
16 | }
17 | return val;
18 | };
19 |
20 | module.exports.hfunc = function(pos) {
21 | "use strict";
22 | return {
23 | x: pos.x,
24 | y: this.getAbsolutePosition().y
25 | };
26 | };
27 |
28 | function transformToAssocArray(prmstr) {
29 | var params = {};
30 | var prmarr = prmstr.split('&');
31 | var i, tmparr;
32 | for (i = 0; i < prmarr.length; i++) {
33 | tmparr = prmarr[i].split('=');
34 | params[tmparr[0]] = tmparr[1];
35 | }
36 | return params;
37 | }
38 |
39 | module.exports.getURLParams = function() {
40 | if (typeof window === "undefined") {
41 | return {};
42 | }
43 | var prmstr = window.location.search.substr(1);
44 | return prmstr !== null && prmstr !== '' ? transformToAssocArray(prmstr) : {};
45 | };
46 |
47 | module.exports.convertDateToUTC = function(date) {
48 | return new Date(
49 | date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(),
50 | date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds()
51 | );
52 | };
53 |
--------------------------------------------------------------------------------
/data/comments.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | '1897' : [
3 | {
4 | "Id":1,
5 | "Author":"Greg Vandeligt",
6 | "Comment":"hjkhjkhjkhkj",
7 | "UserAvatar":null,
8 | "Date":"2015-02-15T17:14:00",
9 | "ParentId":null,
10 | "CanDelete":false,
11 | "CanReply":true,
12 | "PartitNo":null,
13 | "vKey":null,
14 | "dtype":null,
15 | "ProjectRef":null,
16 | "ActionID":null,
17 | "UserID":null
18 | },
19 | {
20 | "Id":2,
21 | "Author":"Greg Vandeligt",
22 | "Comment":"hjkhkjhkjhjkhjk",
23 | "UserAvatar":null,
24 | "Date":"2015-02-15T17:15:00",
25 | "ParentId":null,
26 | "CanDelete":false,
27 | "CanReply":true,
28 | "PartitNo":null,
29 | "vKey":null,
30 | "dtype":null,
31 | "ProjectRef":null,
32 | "ActionID":null,
33 | "UserID":null
34 | },
35 | {
36 | "Id":3,
37 | "Author":"Greg Vandeligt",
38 | "Comment":"testing",
39 | "UserAvatar":null,
40 | "Date":"2015-02-16T12:22:00",
41 | "ParentId":23,
42 | "CanDelete":false,
43 | "CanReply":true,
44 | "PartitNo":null,
45 | "vKey":null,
46 | "dtype":null,
47 | "ProjectRef":null,
48 | "ActionID":null,
49 | "UserID":null
50 | }
51 | ]};
--------------------------------------------------------------------------------
/src/css/sortable.css:
--------------------------------------------------------------------------------
1 | body.dragging, body.dragging * {
2 | cursor: move !important;
3 | }
4 |
5 | .dragged {
6 | position: absolute;
7 | opacity: 0.5;
8 | z-index: 2000;
9 | }
10 |
11 | ol.sortable li.placeholder {
12 | /*position: relative;*/
13 | /** More li styles **/
14 | }
15 | ol.sortable li.placeholder:before {
16 | /*position: absolute;*/
17 | /** Define arrowhead **/
18 | }
19 |
20 | /* line 1, /Users/jonasvonandrian/jquery-sortable/source/css/jquery-sortable.css.sass */
21 | body.dragging, body.dragging * {
22 | cursor: move !important; }
23 |
24 | /* line 4, /Users/jonasvonandrian/jquery-sortable/source/css/jquery-sortable.css.sass */
25 | .dragged {
26 | position: absolute;
27 | top: 0;
28 | opacity: 0.5;
29 | z-index: 2000; }
30 |
31 |
32 | ol.sortable li {
33 | /*display: block;*/
34 | /*margin: 2px;*/
35 | /*padding: 2px;*/
36 | /*border: 1px solid #cccccc;*/
37 | /*color: #0088cc;*/
38 | /*background: #eeeeee;*/
39 | }
40 |
41 | ol.sortable li.placeholder {
42 | /*position: relative;*/
43 | margin: 0;
44 | padding: 0;
45 | border: none;
46 | }
47 |
48 | ol.sortable li.placeholder:before {
49 | /*position: absolute;*/
50 | /*z-index: 20;*/
51 | content: "__________";
52 | width: 250px;
53 | height: 10px;
54 | /*margin-top: 5px;*/
55 | /*left: 10px;*/
56 | /*top: 4px;*/
57 | border: 1px dashed lightblue;
58 | /*border-left-color: red;*/
59 | /*border-right: none;*/
60 | }
--------------------------------------------------------------------------------
/data/resources.js:
--------------------------------------------------------------------------------
1 | module.exports = [
2 | {
3 | "WBSID":1832,
4 | "Resources":[
5 | {
6 | "ID":74,
7 | "TSActivate":null,
8 | "ResID":58
9 | },
10 | {
11 | "ID":75,
12 | "TSActivate":null,
13 | "ResID":55
14 | },
15 | {
16 | "ID":76,
17 | "TSActivate":null,
18 | "ResID":56
19 | },
20 | {
21 | "ID":77,
22 | "TSActivate":null,
23 | "ResID":59
24 | }
25 | ]
26 | },
27 | {
28 | "WBSID":1894,
29 | "Resources":[
30 | {
31 | "ID":78,
32 | "TSActivate":null,
33 | "ResID":58
34 | },
35 | {
36 | "ID":79,
37 | "TSActivate":null,
38 | "ResID":59
39 | },
40 | {
41 | "ID":111,
42 | "TSActivate":true,
43 | "ResID":1
44 | }
45 | ]
46 | },
47 | {
48 | "WBSID":1897,
49 | "Resources":[
50 | {
51 | "ID":72,
52 | "TSActivate":null,
53 | "ResID":58
54 | },
55 | {
56 | "ID":73,
57 | "TSActivate":null,
58 | "ResID":1
59 | }
60 | ]
61 | }
62 | ];
--------------------------------------------------------------------------------
/src/js/collections/ResourceReferenceCollection.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var ResourceReferenceModel = require('../models/ResourceReference');
4 |
5 | var util = require('../utils/util');
6 | var params = util.getURLParams();
7 |
8 | var Collection = Backbone.Collection.extend({
9 | url : 'api/resources/' + (params.project || 1) + '/' + (params.profile || 1),
10 | model: ResourceReferenceModel,
11 | idAttribute : 'ID',
12 | updateResourcesForTask : function(task) {
13 | // remove old references
14 | this.toArray().forEach(function(ref) {
15 | if (ref.get('WBSID').toString() !== task.id.toString()) {
16 | return;
17 | }
18 | var isOld = task.get('resources').indexOf(ref.get('ResID'));
19 | if (isOld) {
20 | ref.destroy();
21 | }
22 | }, this);
23 | // add new references
24 | task.get('resources').forEach(function(resId) {
25 | var isExist = this.findWhere({ResID : resId});
26 | if (!isExist) {
27 | this.add({
28 | ResID : resId,
29 | WBSID : task.id.toString()
30 | }).save();
31 | }
32 | }.bind(this));
33 | },
34 | parse : function(res) {
35 | var result = [];
36 | res.forEach(function(item) {
37 | item.Resources.forEach(function(resItem) {
38 | var obj = resItem;
39 | obj.WBSID = item.WBSID;
40 | result.push(obj);
41 | });
42 | });
43 | return result;
44 | }
45 | });
46 |
47 | module.exports = Collection;
48 |
49 |
--------------------------------------------------------------------------------
/src/js/models/ResourceReferenceCollection.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var ResourceReferenceModel = require('../models/ResourceReference');
4 |
5 | var util = require('../utils/util');
6 | var params = util.getURLParams();
7 |
8 | var Collection = Backbone.Collection.extend({
9 | url : 'api/resources/' + (params.project || 1) + '/' + (params.profile || 1),
10 | model: ResourceReferenceModel,
11 | idAttribute : 'ID',
12 | updateResourcesForTask : function(task) {
13 | // remove old references
14 | this.each(function(ref) {
15 | console.log(ref);
16 | if (ref.get('WBSID').toString() !== task.id.toString()) {
17 | return;
18 | }
19 | var isOld = task.get('resources').indexOf(ref.get('ResID'));
20 | if (isOld) {
21 | ref.destroy();
22 | }
23 | });
24 | // add new references
25 | task.get('resources').forEach(function(resId) {
26 | var isExist = this.findWhere({ResID : resId});
27 | if (!isExist) {
28 | this.add({
29 | ResID : resId,
30 | WBSID : task.id.toString()
31 | }).save();
32 | }
33 | }.bind(this));
34 | },
35 | parse : function(res) {
36 | var result = [];
37 | res.forEach(function(item) {
38 | item.Resources.forEach(function(resItem) {
39 | var obj = resItem;
40 | obj.WBSID = item.WBSID;
41 | result.push(obj);
42 | });
43 | });
44 | console.log(result);
45 |
46 | return result;
47 | }
48 | });
49 |
50 | module.exports = Collection;
51 |
52 |
--------------------------------------------------------------------------------
/src/css/loader.css:
--------------------------------------------------------------------------------
1 | #loader {
2 | position: absolute;
3 | width: 100%;
4 | height: 100%;
5 | background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iNiIgaGVpZ2h0PSI2Ij4KPHJlY3Qgd2lkdGg9IjYiIGhlaWdodD0iNiIgZmlsbD0iI2VlZWVlZSI+PC9yZWN0Pgo8ZyBpZD0iYyI+CjxyZWN0IHdpZHRoPSIzIiBoZWlnaHQ9IjMiIGZpbGw9IiNlNmU2ZTYiPjwvcmVjdD4KPHJlY3QgeT0iMSIgd2lkdGg9IjMiIGhlaWdodD0iMiIgZmlsbD0iI2Q4ZDhkOCI+PC9yZWN0Pgo8L2c+Cjx1c2UgeGxpbms6aHJlZj0iI2MiIHg9IjMiIHk9IjMiPjwvdXNlPgo8L3N2Zz4=");
6 | z-index: 20000;
7 | }
8 |
9 | .spinner {
10 | margin: 100px auto 0;
11 | width: 70px;
12 | text-align: center;
13 |
14 | position: relative;
15 | top: 50%;
16 | left: 50%;
17 | margin-top: -20px;
18 | margin-left: -20px;
19 | }
20 |
21 | .spinner > div {
22 | width: 18px;
23 | height: 18px;
24 | background-color: #333;
25 |
26 | border-radius: 100%;
27 | display: inline-block;
28 | -webkit-animation: sk-bouncedelay 1.4s infinite ease-in-out both;
29 | animation: sk-bouncedelay 1.4s infinite ease-in-out both;
30 | }
31 |
32 | .spinner .bounce1 {
33 | -webkit-animation-delay: -0.32s;
34 | animation-delay: -0.32s;
35 | }
36 |
37 | .spinner .bounce2 {
38 | -webkit-animation-delay: -0.16s;
39 | animation-delay: -0.16s;
40 | }
41 |
42 | @-webkit-keyframes sk-bouncedelay {
43 | 0%, 80%, 100% { -webkit-transform: scale(0) }
44 | 40% { -webkit-transform: scale(1.0) }
45 | }
46 |
47 | @keyframes sk-bouncedelay {
48 | 0%, 80%, 100% {
49 | -webkit-transform: scale(0);
50 | transform: scale(0);
51 | } 40% {
52 | -webkit-transform: scale(1.0);
53 | transform: scale(1.0);
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/js/app.js:
--------------------------------------------------------------------------------
1 | // require('babel/external-helpers');
2 |
3 | import TaskCollection from './collections/TaskCollection';
4 | import Settings from './models/SettingModel';
5 |
6 | import GanttView from './views/GanttView';
7 | import {tasksURL, configURL} from './clientConfig';
8 |
9 |
10 | function loadTasks(tasks) {
11 | var dfd = new $.Deferred();
12 | tasks.fetch({
13 | success: function() {
14 | dfd.resolve();
15 | },
16 | error: function(err) {
17 | dfd.reject(err);
18 | },
19 | parse: true,
20 | reset: true
21 | });
22 | return dfd.promise();
23 | }
24 |
25 | function loadSettings(settings) {
26 | return $.getJSON(configURL)
27 | .then((data) => {
28 | settings.statuses = data;
29 | });
30 | }
31 |
32 |
33 | $(() => {
34 | let tasks = new TaskCollection();
35 | tasks.url = tasksURL;
36 | let settings = new Settings({}, {tasks: tasks});
37 |
38 | window.tasks = tasks;
39 |
40 | $.when(loadTasks(tasks))
41 | .then(() => loadSettings(settings))
42 | .then(() => {
43 | console.log('Success loading tasks.');
44 | new GanttView({
45 | settings: settings,
46 | collection: tasks
47 | }).render();
48 | })
49 | .then(() => {
50 | tasks.setDefaultStatusId(settings.getDefaultStatusId());
51 | tasks.setClosedStatusId(settings.getClosedStatusId());
52 | })
53 | .then(() => {
54 | // hide loading
55 | $('#loader').fadeOut(function() {
56 |
57 | // display head always on top
58 | $('#head').css({
59 | position: 'fixed'
60 | });
61 |
62 | // enable scrolling
63 | $('body').removeClass('hold-scroll');
64 | });
65 | }).fail((error) => {
66 | console.error('Error while loading', error);
67 | });
68 | });
69 |
--------------------------------------------------------------------------------
/src/libs/jquery.comment/jquery.comment.min.css:
--------------------------------------------------------------------------------
1 | .commentsBlock{overflow-y:auto;padding-right:5px}.commentsBlock form{margin-top:10px;margin-bottom:5px}.commentsBlock ul{list-style:none;margin:0;padding:0}.commentsBlock .comment .commentText{word-wrap:break-word;word-break:break-all;white-space:normal;white-space:-normal;white-space:-moz-normal;white-space:-o-normal;margin:5px 0 10px 0}.commentsBlock .newComment{padding:2px}.commentsBlock .heading{border-bottom:1px solid #dcdcdc}.commentsBlock .heading h4{float:left;color:#888;margin:0 5px 5px 0;float:left}.commentsBlock .heading .loadingIndicator{background:transparent url('./ajax-loader.gif') no-repeat;height:15px;width:15px;float:left;display:none}.commentsBlock .sendComment{color:#508bc0;font-size:12px}.commentsBlock .sendComment span{padding:0 5px 0 20px;background:transparent url('./add_comment_ico.png') no-repeat 0 4px;display:block}.commentsBlock textarea{width:98%;height:30px;margin-bottom:10px;height:40px}.commentsBlock .sendComment{color:#508bc0;font-size:12px}.comments{overflow-x:hidden;overflow-y:auto;list-style:none}.comments .comment{padding:10px 0;margin:0 20px;border-bottom:#eee solid 1px}.comments .comment .commentContent .avatar{margin-right:10px;float:left}.comments .comment .commentContent .avatar img{height:64px}.comments .comment .commentContent .avatar .defaultAvatar{background-image:url("/img/userDefaultImage.png");background-repeat:no-repeat;height:64px;width:64px}.comments .comment .commentContent .content.avatarPadding{padding-left:64px}.comments .comment .info{font-size:13px;color:#505050;margin:0}.comments .comment .info .author{font-weight:bold;font-size:12px}.comments .comment .info time{font-size:10px;color:#666;line-height:20px;height:20px;margin-right:5px}.comments .comment .info a{margin-left:5px}.comments li:last-child{border:0}.comments .comment .contentBlockComment{padding:0;border:0;margin-top:5px}.comments .comment .reply_comments{margin-top:10px}.comments .comment .reply_comments .comment{padding:5px 20px 5px 20px;margin-bottom:0;border-top:1px solid #eee;border-bottom-width:0;background:transparent url('./comment.gif') no-repeat 0 10px}.commentsBlock .clearFix{clear:both}
--------------------------------------------------------------------------------
/src/js/views/canvasChart/NestedTaskView.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by lavrton on 17.12.2014.
3 | */
4 | "use strict";
5 | var BasicTaskView = require('./BasicTaskView');
6 |
7 | var NestedTaskView = BasicTaskView.extend({
8 | _color : '#b3d1fc',
9 | _borderSize : 6,
10 | _barHeight : 10,
11 | _completeColor : '#336699',
12 | el : function() {
13 | var group = BasicTaskView.prototype.el.call(this);
14 | var leftBorder = new Konva.Line({
15 | fill : this._color,
16 | y : this._topPadding + this._barHeight,
17 | points : [0, 0, this._borderSize * 1.5, 0, 0, this._borderSize],
18 | closed : true,
19 | name : 'leftBorder'
20 | });
21 | group.add(leftBorder);
22 | var rightBorder = new Konva.Line({
23 | fill : this._color,
24 | y : this._topPadding + this._barHeight,
25 | points : [-this._borderSize * 1.5, 0, 0, 0, 0, this._borderSize],
26 | closed : true,
27 | name : 'rightBorder'
28 | });
29 | group.add(rightBorder);
30 | return group;
31 | },
32 | _updateDates : function() {
33 | // group is moved
34 | // so we need to detect interval
35 | var attrs = this.settings.getSetting('attr'),
36 | boundaryMin=attrs.boundaryMin,
37 | daysWidth=attrs.daysWidth;
38 |
39 | var rect = this.el.find('.mainRect')[0];
40 | var x = this.el.x() + rect.x();
41 | var days1 = Math.floor(x / daysWidth);
42 | var newStart = boundaryMin.clone().addDays(days1);
43 | this.model.moveToStart(newStart);
44 | },
45 | render : function() {
46 | var x = this._calculateX();
47 | this.el.find('.leftBorder')[0].x(0);
48 | this.el.find('.rightBorder')[0].x(x.x2 - x.x1);
49 | var completeWidth = (x.x2 - x.x1) * this.model.get('complete') / 100;
50 | if (completeWidth > this._borderSize / 2) {
51 | this.el.find('.leftBorder')[0].fill(this._completeColor);
52 | } else {
53 | this.el.find('.leftBorder')[0].fill(this._color);
54 | }
55 | if ((x.x2 - x.x1) - completeWidth < this._borderSize / 2) {
56 | this.el.find('.rightBorder')[0].fill(this._completeColor);
57 | } else {
58 | this.el.find('.rightBorder')[0].fill(this._color);
59 | }
60 |
61 | BasicTaskView.prototype.render.call(this);
62 | return this;
63 | }
64 | });
65 |
66 | module.exports = NestedTaskView;
67 |
--------------------------------------------------------------------------------
/src/js/views/ResourcesEditor.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 |
4 | var ResourceEditorView = Backbone.View.extend({
5 | initialize : function(params) {
6 | this.settings = params.settings;
7 | },
8 | render : function(pos) {
9 | var stagePos = $('#gantt-container').offset();
10 | var fakeEl = $('
').appendTo('body');
11 | fakeEl.css({
12 | position : 'absolute',
13 | top : pos.y + stagePos.top + 'px',
14 | left : pos.x + stagePos.left + 'px'
15 | });
16 |
17 | this.popup = $('.custom.popup');
18 | fakeEl.popup({
19 | popup : this.popup,
20 | on : 'hover',
21 | position : 'bottom left',
22 | onHidden : function() {
23 | this._saveData();
24 | this.popup.off('.editor');
25 | }.bind(this)
26 | }).popup('show');
27 |
28 | this._addResources();
29 | this.popup.find('.button').on('click.editor', function() {
30 | this.popup.popup('hide');
31 | this._saveData();
32 | this.popup.off('.editor');
33 | }.bind(this));
34 |
35 | this._fullData();
36 | },
37 | _addResources : function() {
38 | this.popup.empty();
39 | var htmlString = '';
40 | (this.settings.statuses.resourcedata || []).forEach(function(resource) {
41 | htmlString += '
' +
42 | ' ' +
43 | '' + resource.Username + ' ' +
44 | '
';
45 | });
46 | htmlString +='
' +
47 | 'Close' +
48 | '
';
49 | this.popup.append(htmlString);
50 | this.popup.find('.ui.checkbox').checkbox();
51 | },
52 | _fullData : function() {
53 | var popup = this.popup;
54 | this.model.get('resources').forEach(function(resource) {
55 | popup.find('[name="' + resource + '"]').prop('checked', true);
56 | });
57 | },
58 | _saveData : function() {
59 | var resources = [];
60 | this.popup.find('input').each(function(i, input) {
61 | var $input = $(input);
62 | if ($input.prop('checked')) {
63 | resources.push($input.attr('name'));
64 | }
65 | }.bind(this));
66 | this.model.set('resources', resources);
67 | }
68 | });
69 |
70 | module.exports = ResourceEditorView;
71 |
--------------------------------------------------------------------------------
/src/js/views/CommentsView.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var util = require('../utils/util');
3 | var params = util.getURLParams();
4 |
5 | var CommentsView = Backbone.View.extend({
6 | el : '#taskCommentsModal',
7 | initialize : function(params) {
8 | this.settings = params.settings;
9 | },
10 | render : function() {
11 | this._fillData();
12 |
13 | // open modal
14 | this.$el.modal({
15 | onHidden : function() {
16 | $("#taskComments").empty();
17 | $(document.body).removeClass('dimmable');
18 | }.bind(this),
19 | onApprove : function() {
20 | console.log('onApprove');
21 | }.bind(this),
22 | onHide : function() {
23 | console.log('onHide');
24 | return false;
25 | },
26 | onDeny : function() {
27 | console.log('onDeny');
28 | return false;
29 | }
30 | }).modal('show');
31 |
32 | var updateCount = function() {
33 | var count = $("#taskComments").comments("count");
34 | this.model.set('Comments', count);
35 | }.bind(this);
36 | var callback = {
37 | afterDelete : updateCount,
38 | afterCommentAdd : updateCount
39 | };
40 | if (window.location.hostname.indexOf('localhost') === -1) {
41 | // init comments
42 | $("#taskComments").comments({
43 | getCommentsUrl: "/api/comment/" + this.model.id + "/" + params.sitekey + "/WBS/000",
44 | postCommentUrl: "/api/comment/" + this.model.id + "/" + params.sitekey + "/WBS/" + params.project,
45 | deleteCommentUrl: "/api/comment/" + this.model.id,
46 | displayAvatar: false,
47 | callback : callback
48 | });
49 | } else {
50 | $("#taskComments").comments({
51 | getCommentsUrl: "/api/comment/" + this.model.id,
52 | postCommentUrl: "/api/comment/" + this.model.id,
53 | deleteCommentUrl: "/api/comment/" + this.model.id,
54 | displayAvatar: false,
55 | callback : callback
56 | });
57 | }
58 | },
59 | _fillData : function() {
60 | _.each(this.model.attributes, function(val, key) {
61 | var input = this.$el.find('[name="' + key + '"]');
62 | if (!input.length) {
63 | return;
64 | }
65 | input.val(val);
66 | }, this);
67 | }
68 | });
69 |
70 | module.exports = CommentsView;
71 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Gantt.js Editor
2 | ============
3 |
4 | A Gantt Project using Backbone, React and Konva
5 |
6 | # Version 1.0 Beta
7 |
8 | The version 1.0 development is in Beta a stage, the Gantt Editor plugin allows for complex
9 | Project Gantt charts, dependency and resource assignments. Data is being read from structured
10 | json files that should allow integration to any backend technologies such as .net, php, java etc.
11 |
12 | ## Functions available:
13 | * Add, edit, delete tasks
14 | * dependencies created between items
15 | * gantt bars moved to change dates
16 | * gantt bars resized
17 | * drag and drop rows
18 | * additional task properties in a modal window
19 | * left mouse click context menu
20 | * Indent / Outdent tasks to great grouping relationships
21 | * Add tasks above and below line item
22 | * Changes are automatically saved to the server through an REST api
23 | * Edit task resources
24 |
25 | ### User Interface:
26 | * different view options, Day, Week, Month, Quarter
27 | * Expandable table section for additional line item details
28 | * Highlight rows with attributes
29 | * Filter rows with attributes
30 |
31 | ### Video Link of Gantt Editor in Action
32 | [](http://www.youtube.com/watch?v=2EzHS1TR2v0)
33 |
34 |
35 | ### Authors and Contributors
36 | This project is being developed to be used in the Cloud Based Project, Portfolio Management application
OneView PPM by
Level35 .
37 |
38 | We are currently looking for active contribition to complete this plugin. Please contact us if you have the skills and the time to contribute.
39 |
40 | ### LICENSE
41 |
42 | This software is offered under the MIT License
43 |
44 | Permission is hereby granted, free of charge, to any person obtaining a copy
45 | of this software and associated documentation files (the "Software"), to deal
46 | in the Software without restriction, including without limitation the rights
47 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
48 | copies of the Software, and to permit persons to whom the Software is
49 | furnished to do so, subject to the following conditions:
50 |
51 | The above copyright notice and this permission notice shall be included in
52 | all copies or substantial portions of the Software.
53 |
54 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
55 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
56 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
57 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
58 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
59 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
60 | THE SOFTWARE.
61 |
--------------------------------------------------------------------------------
/src/js/views/sideBar/ContextMenuView.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var ModalEdit = require('../ModalTaskEditView');
4 | var Comments = require('../CommentsView');
5 |
6 | function ContextMenuView(params) {
7 | this.collection = params.collection;
8 | this.settings = params.settings;
9 | }
10 |
11 | ContextMenuView.prototype.render = function() {
12 | var self = this;
13 | $('.task-container').contextMenu({
14 | selector: 'ul',
15 | callback: function(key) {
16 | var id = $(this).attr('id') || $(this).data('id');
17 | var model = self.collection.get(id);
18 | if(key === 'delete'){
19 | model.destroy();
20 | }
21 | if(key === 'properties'){
22 | var view = new ModalEdit({
23 | model: model,
24 | settings: self.settings
25 | });
26 | view.render();
27 | }
28 | if(key === 'comments'){
29 | new Comments({
30 | model: model,
31 | settings: self.settings
32 | }).render();
33 | }
34 | if (key === 'rowAbove'){
35 | var data = {
36 | reference_id: id
37 | };
38 | self.addTask(data, 'above');
39 | }
40 | if(key === 'rowBelow'){
41 | self.addTask({
42 | reference_id : id
43 | }, 'below');
44 | }
45 | if (key === 'indent') {
46 | self.collection.indent(model);
47 | }
48 | if (key === 'outdent'){
49 | self.collection.outdent(model);
50 | }
51 | },
52 | items: {
53 | "rowAbove": { name: " New Row Above", icon: "above" },
54 | "rowBelow": { name: " New Row Below", icon: "below" },
55 | "indent": { name: " Indent Row", icon: "indent" },
56 | "outdent": { name: " Outdent Row", icon: "outdent" },
57 | "sep1": "---------",
58 | "properties": { name: " Properties", icon: "properties" },
59 | "comments": { name: " Comments", icon: "comment" },
60 | "sep2": "---------",
61 | "delete": { name: " Delete Row", icon: "delete" }
62 | }
63 | });
64 | };
65 |
66 | ContextMenuView.prototype.addTask = function(data, insertPos) {
67 | var sortindex = 0;
68 | var ref_model = this.collection.get(data.reference_id);
69 | if (ref_model) {
70 | sortindex = ref_model.get('sortindex') + (insertPos === 'above' ? -0.5 : 0.5);
71 | } else {
72 | sortindex = (this.collection.last().get('sortindex') + 1);
73 | }
74 | data.sortindex = sortindex;
75 | data.parentid = ref_model.get('parentid');
76 | var task = this.collection.add(data, {parse : true});
77 | this.collection.checkSortedIndex();
78 | task.save();
79 | };
80 |
81 | module.exports = ContextMenuView;
--------------------------------------------------------------------------------
/src/js/views/canvasChart/ConnectorView.js:
--------------------------------------------------------------------------------
1 | var ConnectorView = Backbone.KonvaView.extend({
2 | _color: 'grey',
3 | _wrongColor: 'red',
4 | initialize: function (params) {
5 | this.settings = params.settings;
6 | this.beforeModel = params.beforeModel;
7 | this.afterModel = params.afterModel;
8 | this._y1 = 0;
9 | this._y2 = 0;
10 | this._initSettingsEvents();
11 | this._initModelEvents();
12 | },
13 | el: function() {
14 | var line = new Konva.Line({
15 | strokeWidth: 2,
16 | stroke: 'black',
17 | points: [0, 0, 0, 0]
18 | });
19 | return line;
20 | },
21 | setY1: function(y1) {
22 | this._y1 = y1;
23 | this.render();
24 | },
25 | setY2: function(y2) {
26 | this._y2 = y2;
27 | this.render();
28 | },
29 | render: function() {
30 | var x = this._calculateX();
31 | if (x.x2 >= x.x1) {
32 | this.el.stroke(this._color);
33 | this.el.points([x.x1, this._y1, x.x1 + 10, this._y1, x.x1 + 10, this._y2, x.x2, this._y2]);
34 | } else {
35 | this.el.stroke(this._wrongColor);
36 | this.el.points([
37 | x.x1, this._y1,
38 | x.x1 + 10, this._y1,
39 | x.x1 + 10, this._y1 + (this._y2 - this._y1) / 2,
40 | x.x2 - 10, this._y1 + (this._y2 - this._y1) / 2,
41 | x.x2 - 10, this._y2,
42 | x.x2, this._y2
43 | ]);
44 | }
45 | this.el.getLayer().batchDraw();
46 | return this;
47 | },
48 | _initSettingsEvents: function() {
49 | this.listenTo(this.settings, 'change:interval change:dpi', function() {
50 | this.render();
51 | });
52 | },
53 | _initModelEvents: function() {
54 | this.listenTo(this.beforeModel, 'change', function() {
55 | this.render();
56 | });
57 |
58 | this.listenTo(this.beforeModel, 'change:hidden', function() {
59 | if (this.beforeModel.get('hidden')) {
60 | this.el.hide();
61 | } else {
62 | this.el.show();
63 | }
64 | });
65 | this.listenTo(this.afterModel, 'change', function() {
66 | this.render();
67 | });
68 |
69 | this.listenTo(this.afterModel, 'change:hidden', function() {
70 | if (this.beforeModel.get('hidden')) {
71 | this.el.hide();
72 | } else {
73 | this.el.show();
74 | }
75 | });
76 | },
77 | _calculateX: function() {
78 | var attrs = this.settings.getSetting('attr'),
79 | boundaryMin = attrs.boundaryMin,
80 | daysWidth = attrs.daysWidth;
81 | return {
82 | x1: Date.daysdiff(boundaryMin, this.beforeModel.get('end')) * daysWidth,
83 | x2: Date.daysdiff(boundaryMin, this.afterModel.get('start')) * daysWidth
84 | };
85 | }
86 | });
87 |
88 | module.exports = ConnectorView;
89 |
--------------------------------------------------------------------------------
/src/js/views/sideBar/NestedTask.js:
--------------------------------------------------------------------------------
1 | var TaskItem = require('./TaskItem');
2 |
3 | var NestedTask = React.createClass({
4 | displayName: 'NestedTask',
5 | componentDidMount: function() {
6 | this.props.model.on('change:hidden change:collapsed', function() {
7 | this.forceUpdate();
8 | }, this);
9 | },
10 | componentWillUnmount: function() {
11 | this.props.model.off(null, null, this);
12 | },
13 | render: function() {
14 | var subtasks = this.props.model.children.map((task) => {
15 | if (task.get('hidden')) {
16 | return;
17 | }
18 | if (task.children.length) {
19 | return React.createElement(NestedTask, {
20 | model: task,
21 | isSubTask: true,
22 | key: task.cid,
23 | dateFormat: this.props.dateFormat,
24 | onSelectRow: this.props.onSelectRow,
25 | onEditRow: this.props.onEditRow,
26 | editedRow: this.props.editedRow,
27 | selectedRow: this.props.selectedRow,
28 | selectedModelCid: this.props.selectedModelCid,
29 | getAllStatuses: this.props.getAllStatuses,
30 | getStatusId: this.props.getStatusId
31 | });
32 | }
33 | return React.createElement('li', {
34 | id: task.cid,
35 | key: task.cid,
36 | className: 'drag-item',
37 | 'data-id': task.cid
38 | },
39 | React.createElement(TaskItem, {
40 | model: task,
41 | isSubTask: true,
42 | dateFormat: this.props.dateFormat,
43 | onSelectRow: this.props.onSelectRow,
44 | onEditRow: this.props.onEditRow,
45 | editedRow: (this.props.selectedModelCid === task.cid) && this.props.editedRow,
46 | selectedRow: (this.props.selectedModelCid === task.cid) && this.props.selectedRow,
47 | getAllStatuses: this.props.getAllStatuses,
48 | getStatusId: this.props.getStatusId
49 | })
50 | );
51 | });
52 | return React.createElement('li', {
53 | className: 'task-list-container drag-item' + (this.props.isSubTask ? ' sub-task' : ''),
54 | id: this.props.model.cid,
55 | 'data-id': this.props.model.cid
56 | },
57 | React.createElement('div', {
58 | id: this.props.model.cid,
59 | 'data-id': this.props.model.cid
60 | },
61 | React.createElement(TaskItem, {
62 | model: this.props.model,
63 | dateFormat: this.props.dateFormat,
64 | onSelectRow: this.props.onSelectRow,
65 | onEditRow: this.props.onEditRow,
66 | editedRow: (this.props.selectedModelCid === this.props.model.cid) && this.props.editedRow,
67 | selectedRow: (this.props.selectedModelCid === this.props.model.cid) && this.props.selectedRow,
68 | getAllStatuses: this.props.getAllStatuses,
69 | getStatusId: this.props.getStatusId
70 | })
71 | ),
72 | React.createElement('ol', {
73 | className: 'sub-task-list sortable'
74 | },
75 | subtasks
76 | )
77 | );
78 | }
79 | });
80 |
81 | module.exports = NestedTask;
82 |
--------------------------------------------------------------------------------
/src/js/views/TopMenuView/FilterMenuView.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var FilterView = Backbone.View.extend({
4 | el : '#filter-menu',
5 | initialize : function(params) {
6 | this.settings = params.settings;
7 | },
8 | events : {
9 | 'change #hightlights-select' : function(e) {
10 | var hightlightTasks = this._getModelsForCriteria(e.target.value);
11 | this.collection.each(function(task) {
12 | if (hightlightTasks.indexOf(task) > -1) {
13 | task.set('hightlight', this.colors[e.target.value]);
14 | } else {
15 | task.set('hightlight', undefined);
16 | }
17 | }, this);
18 | },
19 | 'change #filters-select' : function(e) {
20 | var criteria = e.target.value;
21 | if (criteria === 'reset') {
22 | this.collection.each(function(task) {
23 | task.show();
24 | });
25 | } else {
26 | var showTasks = this._getModelsForCriteria(e.target.value);
27 | this.collection.each(function(task) {
28 | if (showTasks.indexOf(task) > -1) {
29 | task.show();
30 | // show all parents
31 | var parent = task.parent;
32 | while(parent) {
33 | parent.show();
34 | parent = parent.parent;
35 | }
36 | } else {
37 | task.hide();
38 | }
39 | });
40 | }
41 | }
42 | },
43 | colors : {
44 | 'status-backlog' : '#D2D2D9',
45 | 'status-ready' : '#B2D1F0',
46 | 'status-in progress' : '#66A3E0',
47 | 'status-complete' : '#99C299',
48 | 'late' : '#FFB2B2',
49 | 'due' : ' #FFC299',
50 | 'milestone' : '#D6C2FF',
51 | 'deliverable' : '#E0D1C2',
52 | 'financial' : '#F0E0B2',
53 | 'timesheets' : '#C2C2B2',
54 | 'reportable' : ' #E0C2C2',
55 | 'health-red' : 'red',
56 | 'health-amber' : '#FFBF00',
57 | 'health-green' : 'green'
58 | },
59 | _getModelsForCriteria : function(creteria) {
60 | if (creteria === 'resets') {
61 | return [];
62 | }
63 | if (creteria.indexOf('status') !== -1) {
64 | var status = creteria.slice(creteria.indexOf('-') + 1);
65 | var id = (this.settings.findStatusId(status) || '').toString();
66 | return this.collection.filter(function(task) {
67 | return task.get('status').toString() === id;
68 | });
69 | }
70 | if (creteria === 'late') {
71 | return this.collection.filter(function(task) {
72 | return task.get('end') < new Date();
73 | });
74 | }
75 | if (creteria === 'due') {
76 | var lastDate = new Date();
77 | lastDate.addWeeks(2);
78 | return this.collection.filter(function(task) {
79 | return task.get('end') > new Date() && task.get('end') < lastDate;
80 | });
81 | }
82 | if (['milestone', 'deliverable', 'financial', 'timesheets', 'reportable'].indexOf(creteria) !== -1) {
83 | return this.collection.filter(function(task) {
84 | return task.get(creteria);
85 | });
86 | }
87 | if (creteria.indexOf('health') !== -1) {
88 | var health = creteria.slice(creteria.indexOf('-') + 1);
89 | var healthId = (this.settings.findHealthId(health) || '').toString();
90 | return this.collection.filter(function(task) {
91 | return task.get('health').toString() === healthId;
92 | });
93 | }
94 | return [];
95 | }
96 | });
97 |
98 | module.exports = FilterView;
99 |
--------------------------------------------------------------------------------
/src/js/views/canvasChart/AloneTaskView.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by lavrton on 17.12.2014.
3 | */
4 | "use strict";
5 | var BasicTaskView = require('./BasicTaskView');
6 |
7 | var AloneTaskView = BasicTaskView.extend({
8 | _borderWidth : 3,
9 | _color : '#E6F0FF',
10 | events : function() {
11 | return _.extend(BasicTaskView.prototype.events(), {
12 | 'dragmove .leftBorder' : '_changeSize',
13 | 'dragmove .rightBorder' : '_changeSize',
14 |
15 | 'dragend .leftBorder' : 'render',
16 | 'dragend .rightBorder' : 'render',
17 |
18 | 'mouseover .leftBorder' : '_resizePointer',
19 | 'mouseout .leftBorder' : '_defaultMouse',
20 |
21 | 'mouseover .rightBorder' : '_resizePointer',
22 | 'mouseout .rightBorder' : '_defaultMouse'
23 | });
24 | },
25 | el : function() {
26 | var group = BasicTaskView.prototype.el.call(this);
27 | var leftBorder = new Konva.Rect({
28 | dragBoundFunc : function(pos) {
29 | var offset = this.el.getStage().x() + this.el.x();
30 | var localX = pos.x - offset;
31 | return {
32 | x : Math.min(localX, this.el.find('.rightBorder')[0].x()) + offset,
33 | y : this._y + this._topPadding
34 | };
35 | }.bind(this),
36 | width : this._borderWidth,
37 | fill : 'black',
38 | y : this._topPadding,
39 | height : this._barHeight,
40 | draggable : true,
41 | name : 'leftBorder'
42 | });
43 | group.add(leftBorder);
44 | var rightBorder = new Konva.Rect({
45 | dragBoundFunc : function(pos) {
46 | var offset = this.el.getStage().x() + this.el.x();
47 | var localX = pos.x - offset;
48 | return {
49 | x : Math.max(localX, this.el.find('.leftBorder')[0].x()) + offset,
50 | y : this._y + this._topPadding
51 | };
52 | }.bind(this),
53 | width : this._borderWidth,
54 | fill : 'black',
55 | y : this._topPadding,
56 | height : this._barHeight,
57 | draggable : true,
58 | name : 'rightBorder'
59 | });
60 | group.add(rightBorder);
61 | return group;
62 | },
63 | _resizePointer : function() {
64 | document.body.style.cursor = 'ew-resize';
65 | },
66 | _changeSize : function() {
67 | var leftX = this.el.find('.leftBorder')[0].x();
68 | var rightX = this.el.find('.rightBorder')[0].x() + this._borderWidth;
69 |
70 | var rect = this.el.find('.mainRect')[0];
71 | rect.width(rightX - leftX);
72 | rect.x(leftX);
73 |
74 | // update complete params
75 | var completeRect = this.el.find('.completeRect')[0];
76 | completeRect.x(leftX);
77 | completeRect.width(this._calculateCompleteWidth());
78 |
79 | // move tool position
80 | var tool = this.el.find('.dependencyTool')[0];
81 | tool.x(rightX);
82 | var resources = this.el.find('.resources')[0];
83 | resources.x(rightX + this._toolbarOffset);
84 |
85 | this._updateDates();
86 | },
87 | render : function() {
88 | var x = this._calculateX();
89 | this.el.find('.leftBorder')[0].x(0);
90 | this.el.find('.rightBorder')[0].x(x.x2 - x.x1 - this._borderWidth);
91 | if (this.model.get('milestone')) {
92 | this.el.find('.diamond').show();
93 | this.el.find('.mainRect').hide();
94 | this.el.find('.completeRect').hide();
95 | this.el.find('.leftBorder').hide();
96 | this.el.find('.rightBorder').hide();
97 | } else {
98 | this.el.find('.diamond').hide();
99 | this.el.find('.mainRect').show();
100 | this.el.find('.completeRect').show();
101 | this.el.find('.leftBorder').show();
102 | this.el.find('.rightBorder').show();
103 | }
104 | BasicTaskView.prototype.render.call(this);
105 | return this;
106 | }
107 | });
108 |
109 | module.exports = AloneTaskView;
110 |
--------------------------------------------------------------------------------
/src/js/views/GanttView.js:
--------------------------------------------------------------------------------
1 | var ContextMenuView = require('./sideBar/ContextMenuView');
var SidePanel = require('./sideBar/SidePanel');
var GanttChartView = require('./canvasChart/GanttChartView');
var TopMenuView = require('./TopMenuView/TopMenuView');
var Notifications = require('./Notifications');
var GanttView = Backbone.View.extend({
el: '.Gantt',
initialize: function(params) {
this.settings = params.settings;
this.$el.find('input[name="end"],input[name="start"]').on('change', this.calculateDuration);
this.$menuContainer = this.$el.find('.menu-container');
new ContextMenuView({
collection: this.collection,
settings: this.settings
}).render();
// new task button
$('.new-task').click(function() {
var lastTask = params.collection.last();
var lastIndex = -1;
if (lastTask) {
lastIndex = lastTask.get('sortindex');
}
params.collection.add({
name: 'New task',
sortindex: lastIndex + 1
});
});
new Notifications({
collection: this.collection
});
new TopMenuView({
settings: this.settings,
collection: this.collection
}).render();
this.canvasView = new GanttChartView({
collection: this.collection,
settings: this.settings
});
this.canvasView.render();
this._moveCanvasView();
setTimeout(function() {
this.canvasView._updateStageAttrs();
// set side tasks panel height
var $sidePanel = $('.menu-container');
$sidePanel.css({
'min-height': window.innerHeight - $sidePanel.offset().top
});
}.bind(this), 500);
var tasksContainer = $('.tasks').get(0);
React.render(
React.createElement(SidePanel, {
collection: this.collection,
settings: this.settings,
dateFormat: this.settings.getDateFormat()
}),
tasksContainer
);
this.listenTo(this.collection, 'sort', _.debounce(function() {
React.unmountComponentAtNode(tasksContainer);
React.render(
React.createElement(SidePanel, {
collection: this.collection,
settings: this.settings,
dateFormat: this.settings.getDateFormat()
}),
tasksContainer
);
}.bind(this),5));
window.addEventListener('scroll', () => {
var y = Math.max(0, document.body.scrollTop || window.scrollY);
$('.menu-header').css({
marginTop: (y) + 'px'
});
$('.tasks').css({
marginTop: '-' + y + 'px'
});
});
},
events: {
'click #tHandle': 'expand',
'click #deleteAll': 'deleteAll'
},
calculateDuration: function(){
// Calculating the duration from start and end date
var startdate = new Date($(document).find('input[name="start"]').val());
var enddate = new Date($(document).find('input[name="end"]').val());
var _MS_PER_DAY = 1000 * 60 * 60 * 24;
if(startdate !== "" && enddate !== ""){
var utc1 = Date.UTC(startdate.getFullYear(), startdate.getMonth(), startdate.getDate());
var utc2 = Date.UTC(enddate.getFullYear(), enddate.getMonth(), enddate.getDate());
$(document).find('input[name="duration"]').val(Math.floor((utc2 - utc1) / _MS_PER_DAY));
}else{
$(document).find('input[name="duration"]').val(Math.floor(0));
}
},
expand: function(evt) {
var button = $(evt.target);
if (button.hasClass('contract')) {
this.$menuContainer.addClass('panel-collapsed');
this.$menuContainer.removeClass('panel-expanded');
}
else {
this.$menuContainer.addClass('panel-expanded');
this.$menuContainer.removeClass('panel-collapsed');
}
setTimeout(function() {
this._moveCanvasView();
}.bind(this), 600);
button.toggleClass('contract');
},
_moveCanvasView: function() {
var sideBarWidth = $('.menu-container').width();
this.canvasView.setLeftPadding(sideBarWidth);
},
deleteAll: function() {
$('#confirm').modal({
onHidden: function() {
$(document.body).removeClass('dimmable');
},
onApprove: function() {
while(this.collection.at(0)) {
this.collection.at(0).destroy();
}
}.bind(this)
}).modal('show');
}
});
module.exports = GanttView;
--------------------------------------------------------------------------------
/src/libs/backbone.KonvaView.js:
--------------------------------------------------------------------------------
1 | (function(root, factory) {
2 |
3 | if (typeof define === 'function' && define.amd) {
4 | define(['underscore', 'backbone', 'konva'], function(_, Backbone, Konva) {
5 | Backbone.KonvaView = factory(root, _, Backbone, Konva);
6 | return Backbone;
7 | });
8 | } else if (typeof exports !== 'undefined') {
9 | var Backbone_ = require('backbone');
10 | Backbone_.KonvaView = factory(root, require('underscore'), Backbone_, require('konva'));
11 | module.exports = Backbone_.KonvaView;
12 |
13 | } else {
14 | root.Backbone.KonvaView = factory(root, _, Backbone, Konva);
15 |
16 | }
17 |
18 | }(this, function(root, _, Backbone, Konva) {
19 |
20 | var KonvaView = function(options) {
21 | this.cid = _.uniqueId('view');
22 | options || (options = {});
23 | _.extend(this, _.pick(options, viewOptions));
24 | this._ensureElement();
25 | this.initialize.apply(this, arguments);
26 | };
27 | KonvaView.extend = Backbone.Model.extend;
28 | var delegateEventSplitter = /^(\S+)\s*(.*)$/;
29 | var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'events', 'nodeType'];
30 |
31 | _.extend(KonvaView.prototype, Backbone.Events, {
32 | nodeType : 'Group',
33 |
34 | initialize: function(){},
35 |
36 | render : function(){
37 | return this;
38 | },
39 |
40 | remove: function() {
41 | this.undelegateEvents();
42 | this.el.destroy();
43 | this.stopListening();
44 | return this;
45 | },
46 |
47 | setElement: function(element) {
48 | this.undelegateEvents();
49 | this.el = element;
50 | this.delegateEvents();
51 | return this;
52 | },
53 |
54 | _deleteEventFromNode : function(node) {
55 | var _this = this;
56 | _.each(_.keys(node.eventListeners),function(eventType){
57 | node.off(eventType+'.delegateEvents' + _this.cid);
58 | });
59 | },
60 | undelegateEvents: function() {
61 | var _this = this;
62 | if (!this.el) {
63 | return this;
64 | }
65 | this._deleteEventFromNode(this.el);
66 | _.each(this.el.children, function(child){
67 | _this._deleteEventFromNode(child);
68 | });
69 | return this;
70 | },
71 | undelegate: function(eventName, selector) {
72 | eventName += '.delegateEvents' + this.cid;
73 | if (selector === '' || !selector) {
74 | this.el.off(eventName);
75 | } else {
76 | this.el.find && this.el.find(selector).each(function(child){
77 | child.off(eventName);
78 | });
79 | }
80 | },
81 | delegateEvents: function(events) {
82 | if (!(events || (events = _.result(this, 'events')))) return this;
83 | this.undelegateEvents();
84 | for (var key in events) {
85 | var method = events[key];
86 | if (!_.isFunction(method)) method = this[events[key]];
87 | if (!method) continue;
88 | var match = key.match(delegateEventSplitter);
89 | this.delegate(match[1], match[2], _.bind(method, this));
90 | }
91 | return this;
92 | },
93 | delegate: function(eventName, selector, listener) {
94 | // listener = selector || listener;
95 | eventName += '.delegateEvents' + this.cid;
96 | if (selector === '') {
97 | this.el.on(eventName, listener);
98 | } else if (!listener) {
99 | this.el.on(eventName, selector);
100 | } else {
101 | this.el.find && this.el.find(selector).each(function(child){
102 | child.on(eventName, listener);
103 | });
104 | }
105 | },
106 | _configure: function(options) {
107 | if (this.options) options = _.extend({}, _.result(this, 'options'), options);
108 | _.extend(this, _.pick(options, viewOptions));
109 | this.options = options;
110 | },
111 | _createElement: function(nodeType) {
112 | return new Konva[nodeType];
113 | },
114 | _ensureElement: function() {
115 | if (!this.el) {
116 | var attrs = _.extend({}, _.result(this, 'attributes'));
117 | if (this.id) attrs.id = _.result(this, 'id');
118 | this.setElement(this._createElement(_.result(this, 'nodeType')));
119 | this._setAttributes(attrs);
120 | } else {
121 | this.setElement(_.result(this, 'el'));
122 | }
123 | },
124 | _setAttributes: function(attributes) {
125 | this.el.setAttrs(attributes);
126 | }
127 | });
128 | return KonvaView;
129 | }));
130 |
--------------------------------------------------------------------------------
/test_server.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var bodyParser = require('body-parser');
3 | var logger = require('morgan');
4 | var _ = require('lodash');
5 | var serveStatic = require('serve-static');
6 |
7 | var tasks = require('./data/tasks');
8 |
9 | // _.times(2, function() {
10 | // tasks = tasks.concat(JSON.parse(JSON.stringify(tasks)));
11 | // });
12 |
13 |
14 | var resources = require('./data/resources');
15 | var comments = require('./data/comments');
16 |
17 | var config = require('./data/config');
18 |
19 | var taskIdCounter = 1;
20 | var ids = [];
21 | _(tasks).each(function(task) {
22 | if (!task.id || ids.indexOf(task.id) >= 0) {
23 | task.id = Math.round(Math.random() * 100000);
24 | }
25 | ids.push(task.id);
26 | taskIdCounter = Math.max(parseInt(task.id), taskIdCounter);
27 | });
28 |
29 |
30 | var resourceIdCounter = 0;
31 | _(resources).each(function(resource) {
32 | if (!resource.id) {
33 | resource.id = resourceIdCounter++;
34 | }
35 | resourceIdCounter = Math.max(parseInt(resource.id), resourceIdCounter);
36 | });
37 | var commentsIdCounter = 3;
38 |
39 | var app = express();
40 |
41 | app.use(serveStatic('./src'));
42 | app.use(bodyParser.json());
43 | app.use(bodyParser.urlencoded({extended: true}));
44 | app.use(logger('dev'));
45 |
46 |
47 |
48 | app.get('/', function(req, res) {
49 | res.send('please select a collection, e.g., /collections/messages');
50 | });
51 |
52 | function generateAPI(items, baseURL) {
53 | app.get(baseURL, function(req, res) {
54 | console.log('return items ', baseURL);
55 | res.send(items);
56 | });
57 |
58 |
59 | app.post(baseURL, function(req, res) {
60 | req.body.id = ++taskIdCounter;
61 | console.log('add item', req.body);
62 | items.push(req.body);
63 | return res.send(req.body);
64 | });
65 |
66 | app.get(baseURL + '/:id', function(req, res) {
67 | var id = req.params.id.toString();
68 | console.log('return item with id ' + id);
69 |
70 | var task = _.find(items, function(item) {
71 | return item.id.toString() === id.toString();
72 | });
73 |
74 | res.send(task || null);
75 | });
76 |
77 | app.put(baseURL + '/:id', function(req, res) {
78 | var id = req.params.id.toString();
79 | console.log('update item with id ' + id);
80 | var item = _(items).find(function(i) {
81 | return i.id.toString() === id;
82 | });
83 |
84 | if (item) {
85 | _(req.body).each(function(val, key) {
86 | item[key] = val;
87 | });
88 | _(item).each(function(val, key) {
89 | if (!req.body[key]) {
90 | item[key] = undefined;
91 | }
92 | });
93 | } else {
94 | console.error('no such item');
95 | }
96 | res.send(item || {});
97 | });
98 |
99 | app.delete(baseURL + '/:id', function(req, res) {
100 | console.log(req.params);
101 | var id = req.params.id.toString();
102 |
103 | console.log('delete item with id ' + req.params.id);
104 |
105 | var item = _(items).find(function(i) {
106 | return i.id.toString() === id;
107 | });
108 |
109 | if (item) {
110 | items = _.without(items, item);
111 | res.send({
112 | msg: 'success'
113 | });
114 | } else {
115 | console.error('no such task');
116 | res.send({
117 | msg: 'error'
118 | });
119 | }
120 | });
121 | }
122 |
123 | generateAPI(tasks, '/api/tasks');
124 | generateAPI(resources, '/api/resources/1/1');
125 |
126 |
127 |
128 | app.get('/api/comment/:id/', function(req, res) {
129 | var id = req.params.id.toString();
130 | console.log('return comments with id ' + id);
131 | res.send(comments[id] || []);
132 | });
133 |
134 | app.get('/api/GanttConfig', function(req, res) {
135 | res.send(config);
136 | });
137 |
138 |
139 | app.post('/api/comment/:id/', function(req, res) {
140 | var id = req.params.id.toString();
141 | console.log('add comment', req.body, 'to task', id);
142 | var comment = {
143 | Comment: req.body.comment,
144 | Id: ++commentsIdCounter,
145 | Author: 'User',
146 | Date: new Date(),
147 | UserAvatar: null,
148 | ParentId: null,
149 | CanDelete: false,
150 | CanReply: true,
151 | PartitNo: null,
152 | vKey: null,
153 | dtype: null,
154 | ProjectRef: null,
155 | ActionID: null,
156 | UserID: null
157 | };
158 | comments[id] = comments[id] || [];
159 | comments[id].push(comment);
160 | return res.send(comment);
161 | });
162 |
163 |
164 | app.listen(3000, function(){
165 | console.log('Express server listening on port 3000');
166 | });
167 |
--------------------------------------------------------------------------------
/src/css/jquery.contextMenu.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * jQuery contextMenu - Plugin for simple contextMenu handling
3 | *
4 | * Version: git-master
5 | *
6 | * Authors: Rodney Rehm, Addy Osmani (patches for FF)
7 | * Web: http://medialize.github.com/jQuery-contextMenu/
8 | *
9 | * Licensed under
10 | * MIT License http://www.opensource.org/licenses/mit-license
11 | * GPL v3 http://opensource.org/licenses/GPL-3.0
12 | *
13 | */
14 |
15 | .context-menu-list {
16 | margin:0;
17 | padding:0;
18 |
19 | min-width: 120px;
20 | max-width: 250px;
21 | display: inline-block;
22 | position: absolute;
23 | list-style-type: none;
24 |
25 | border: 1px solid #DDD;
26 | background: #EEE;
27 |
28 | -webkit-box-shadow: 0 2px 5px rgba(0, 0, 0, 0.5);
29 | -moz-box-shadow: 0 2px 5px rgba(0, 0, 0, 0.5);
30 | -ms-box-shadow: 0 2px 5px rgba(0, 0, 0, 0.5);
31 | -o-box-shadow: 0 2px 5px rgba(0, 0, 0, 0.5);
32 | box-shadow: 0 2px 5px rgba(0, 0, 0, 0.5);
33 |
34 | font-family: Verdana, Arial, Helvetica, sans-serif;
35 | font-size: 11px;
36 | }
37 |
38 | .context-menu-item {
39 | padding: 5px 2px 5px 20px;
40 | background-color: #EEE;
41 | position: relative;
42 | -webkit-user-select: none;
43 | -moz-user-select: -moz-none;
44 | -ms-user-select: none;
45 | user-select: none;
46 | }
47 |
48 | .context-menu-separator {
49 | padding-bottom:0;
50 | border-bottom: 1px solid #DDD;
51 | }
52 |
53 | .context-menu-item > label > input,
54 | .context-menu-item > label > textarea {
55 | -webkit-user-select: text;
56 | -moz-user-select: text;
57 | -ms-user-select: text;
58 | user-select: text;
59 | }
60 |
61 | .context-menu-item.hover {
62 | cursor: pointer;
63 | background-color: #39F;
64 | }
65 |
66 | .context-menu-item.disabled {
67 | color: #666;
68 | }
69 |
70 | .context-menu-input.hover,
71 | .context-menu-item.disabled.hover {
72 | cursor: default;
73 | background-color: #EEE;
74 | }
75 |
76 | .context-menu-submenu:after {
77 | content: ">";
78 | color: #666;
79 | position: absolute;
80 | top: 0;
81 | right: 3px;
82 | z-index: 1;
83 | }
84 |
85 | /* icons
86 | #protip:
87 | In case you want to use sprites for icons (which I would suggest you do) have a look at
88 | http://css-tricks.com/13224-pseudo-spriting/ to get an idea of how to implement
89 | .context-menu-item.icon:before {}
90 | */
91 | .context-menu-item.icon { min-height: 18px; background-repeat: no-repeat; background-position: 4px 2px; }
92 | .context-menu-item.icon-edit { background-image: url(images/page_white_edit.png); }
93 | .context-menu-item.icon-cut { background-image: url(images/cut.png); }
94 | .context-menu-item.icon-copy { background-image: url(images/page_white_copy.png); }
95 | .context-menu-item.icon-paste { background-image: url(images/page_white_paste.png); }
96 | .context-menu-item.icon-delete { background-image: url(images/page_white_delete.png); }
97 | .context-menu-item.icon-add { background-image: url(images/page_white_add.png); }
98 | .context-menu-item.icon-quit { background-image: url(images/door.png); }
99 | .context-menu-item.icon-outdent { background-image: url(images/arrow_left.png); }
100 | .context-menu-item.icon-indent { background-image: url(images/arrow_right.png); }
101 | .context-menu-item.icon-above { background-image: url(images/top.png); }
102 | .context-menu-item.icon-below { background-image: url(images/bottom.png); }
103 | .context-menu-item.icon-properties { background-image: url(images/properties.png); }
104 | .context-menu-item.icon-comment { background-image: url(images/comment.png); }
105 |
106 | /* vertically align inside labels */
107 | .context-menu-input > label > * { vertical-align: top; }
108 |
109 | /* position checkboxes and radios as icons */
110 | .context-menu-input > label > input[type="checkbox"],
111 | .context-menu-input > label > input[type="radio"] {
112 | margin-left: -17px;
113 | }
114 | .context-menu-input > label > span {
115 | margin-left: 5px;
116 | }
117 |
118 | .context-menu-input > label,
119 | .context-menu-input > label > input[type="text"],
120 | .context-menu-input > label > textarea,
121 | .context-menu-input > label > select {
122 | display: block;
123 | width: 100%;
124 |
125 | -webkit-box-sizing: border-box;
126 | -moz-box-sizing: border-box;
127 | -ms-box-sizing: border-box;
128 | -o-box-sizing: border-box;
129 | box-sizing: border-box;
130 | }
131 |
132 | .context-menu-input > label > textarea {
133 | height: 100px;
134 | }
135 | .context-menu-item > .context-menu-list {
136 | display: none;
137 | /* re-positioned by js */
138 | right: -5px;
139 | top: 5px;
140 | }
141 |
142 | .context-menu-item.hover > .context-menu-list {
143 | display: block;
144 | }
145 |
146 | .context-menu-accesskey {
147 | text-decoration: underline;
148 | }
149 |
--------------------------------------------------------------------------------
/src/js/utils/xmlWorker.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs');
2 | var xml = fs.readFileSync(__dirname + '/xmlTemplate.xml', 'utf8');
3 | var compiled = _.template(xml);
4 | var xmlToJSON = window.xmlToJSON;
5 |
6 | function parseXMLObj(xmlString) {
7 | var obj = xmlToJSON.parseString(xmlString);
8 | var tasks = [];
9 | _.each(obj.Project[0].Tasks[0].Task, function(xmlItem) {
10 | if (!xmlItem.Name) {
11 | return;
12 | // xmlItem.Name = [{_text: 'no name ' + xmlItem.UID[0]._text}];
13 | }
14 | // skip root project task
15 | if (xmlItem.OutlineNumber[0]._text.toString() === '0') {
16 | return;
17 | }
18 | tasks.push({
19 | name: xmlItem.Name[0]._text,
20 | start: xmlItem.Start[0]._text,
21 | end: xmlItem.Finish[0]._text,
22 | complete: xmlItem.PercentComplete[0]._text,
23 | outline: xmlItem.OutlineNumber[0]._text.toString()
24 | });
25 | });
26 | return tasks;
27 | }
28 |
29 | module.exports.parseDepsFromXML = function(xmlString) {
30 | var obj = xmlToJSON.parseString(xmlString);
31 | var uids = {};
32 | var outlines = {};
33 | var deps = [];
34 | var parents = [];
35 | _.each(obj.Project[0].Tasks[0].Task, function(xmlItem) {
36 | if (!xmlItem.Name) {
37 | return;
38 | // xmlItem.Name = [{_text: 'no name ' + xmlItem.UID[0]._text}];
39 | }
40 | var item = {
41 | name: xmlItem.Name[0]._text.toString(),
42 | outline: xmlItem.OutlineNumber[0]._text.toString()
43 | };
44 | uids[xmlItem.UID[0]._text] = item;
45 | outlines[item.outline] = item;
46 | });
47 | _.each(obj.Project[0].Tasks[0].Task, function(xmlItem) {
48 | if (!xmlItem.Name) {
49 | return;
50 | }
51 | var task = uids[xmlItem.UID[0]._text];
52 | // var name = xmlItem.Name[0]._text;
53 | var outline = task.outline;
54 |
55 | if (xmlItem.PredecessorLink) {
56 | xmlItem.PredecessorLink.forEach((link) => {
57 | var beforeUID = link.PredecessorUID[0]._text;
58 | var before = uids[beforeUID];
59 |
60 | deps.push({
61 | before: before,
62 | after: task
63 | });
64 | });
65 |
66 | }
67 |
68 | if (outline.indexOf('.') !== -1) {
69 | var parentOutline = outline.slice(0, outline.lastIndexOf('.'));
70 | var parent = outlines[parentOutline];
71 | if (!parent) {
72 | console.error('can not find parent');
73 | return;
74 | }
75 |
76 | parents.push({
77 | parent: parent,
78 | child: task
79 | });
80 | }
81 | });
82 | return {
83 | deps: deps,
84 | parents: parents
85 | };
86 | };
87 |
88 | module.exports.parseXMLObj = parseXMLObj;
89 |
90 | function cut(date) {
91 | let formated = date.toISOString();
92 | return formated.slice(0, formated.indexOf('.'));
93 | }
94 |
95 | function getDuration(end, start) {
96 | var diff = end.getTime() - start.getTime();
97 | const days = Math.floor(diff / 1000 / 60 / 60 / 24) + 1;
98 | // if (days >= 1) {
99 |
100 | // }
101 | var hours = days * 8;
102 | // var mins = Math.floor((diff - hours * 1000 * 60 * 60) / 1000 /60);
103 | // var secs = Math.floor((diff - hours * 1000 * 60 * 60 - mins * 1000 * 60) / 1000);
104 | return `PT${hours}H0M0S`;
105 | }
106 |
107 | module.exports.tasksToXML = function(tasks) {
108 | var start = tasks.at(0).get('start');
109 | var end = tasks.at(0).get('end');
110 | var data = tasks.map(function(task) {
111 | if (start > task.get('start')) {
112 | start = task.get('start');
113 | }
114 | if (end < task.get('end')) {
115 | end = task.get('end');
116 | }
117 |
118 | const depend = _.map(task.get('depend'), (id) => {
119 | return tasks.get(id).get('sortindex') + 1;
120 | });
121 |
122 | return {
123 | id: task.get('sortindex') + 1,
124 | name: task.get('name'),
125 | outlineNumber: task.getOutlineNumber(),
126 | outlineLevel: task.getOutlineLevel(),
127 | start: cut(task.get('start')),
128 | finish: cut(task.get('end')),
129 | duration: getDuration(task.get('end'), task.get('start')),
130 | depend: depend[0]
131 | };
132 | });
133 | return compiled({
134 | tasks: data,
135 | currentDate: cut(new Date()),
136 | startDate: cut(start),
137 | finishDate: cut(end),
138 | duration: getDuration(end, start)
139 | });
140 | };
141 |
--------------------------------------------------------------------------------
/src/js/views/ModalTaskEditView.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 |
4 | var ModalTaskEditComponent = Backbone.View.extend({
5 | el : '#editTask',
6 | initialize : function(params) {
7 | this.settings = params.settings;
8 | },
9 | render : function() {
10 | this.$el.find('.ui.checkbox').checkbox();
11 | // setup values for selectors
12 | this._prepareSelects();
13 |
14 | this.$el.find('.tabular.menu .item').tab();
15 |
16 |
17 | this.$el.find('[name="start"], [name="end"]').datepicker({
18 | // dateFormat: "dd/mm/yy"
19 | dateFormat : this.settings.getDateFormat()
20 | });
21 |
22 | this._fillData();
23 |
24 | // open modal
25 | this.$el.modal({
26 | onHidden : function() {
27 | $(document.body).removeClass('dimmable');
28 | this.undelegateEvents();
29 | }.bind(this),
30 | onApprove : function() {
31 | this._saveData();
32 | }.bind(this)
33 | }).modal('show');
34 | this._listenInputs();
35 |
36 | },
37 | _listenInputs : function() {
38 | var $milestone = this.$el.find('[name="milestone"]');
39 | var $deliverable = this.$el.find('[name="deliverable"]');
40 | var $start = this.$el.find('[name="start"]');
41 | var $end = this.$el.find('[name="end"]');
42 | $milestone.on('change', function() {
43 | var val = $milestone.prop('checked');
44 | if (val) {
45 | $start.val($end.val());
46 | $deliverable.prop('checked', false);
47 | }
48 | });
49 | $deliverable.on('change', function() {
50 | if ($deliverable.prop('checked')) {
51 | $milestone.prop('checked', false);
52 | }
53 | });
54 | },
55 | _prepareSelects : function() {
56 | var statusSelect = this.$el.find('[name="status"]');
57 | statusSelect.children().each(function(i, child) {
58 | var id = this.settings.findStatusId(child.text);
59 | $(child).prop('value', id);
60 | }.bind(this));
61 |
62 | var healthSelect = this.$el.find('[name="health"]');
63 | healthSelect.children().each(function(i, child) {
64 | var id = this.settings.findHealthId(child.text);
65 | $(child).prop('value', id);
66 | }.bind(this));
67 |
68 | var workOrderSelect = this.$el.find('[name="wo"]');
69 | workOrderSelect.empty();
70 | this.settings.statuses.wodata[0].data.forEach(function(data) {
71 | $('
' + data.WONumber + ' ').appendTo(workOrderSelect);
72 | });
73 | },
74 | _fillData : function() {
75 | _.each(this.model.attributes, function(val, key) {
76 | if (key === 'status' && (!val || !this.settings.findStatusForId(val))) {
77 | val = this.settings.findDefaultStatusId();
78 | }
79 | if (key === 'health' && (!val || !this.settings.findHealthForId(val))) {
80 | val = this.settings.findDefaultHealthId();
81 | }
82 | if (key === 'wo' && (!val || !this.settings.findWOForId(val))) {
83 | val = this.settings.findDefaultWOId();
84 | }
85 | var input = this.$el.find('[name="' + key + '"]');
86 | if (!input.length) {
87 | return;
88 | }
89 | if (key === 'start' || key === 'end') {
90 | var dateStr = $.datepicker.formatDate(this.settings.getDateFormat(), val);
91 | input.get(0).value = dateStr;
92 | input.datepicker( "refresh" );
93 | } else if (input.prop('type') === 'checkbox') {
94 | input.prop('checked', val);
95 | } else {
96 | input.val(val);
97 | }
98 | }, this);
99 | if (this.model.children.length) {
100 | this.$el.find('[name="milestone"]').parent().addClass('disabled');
101 | }
102 | },
103 | _saveData : function() {
104 | _.each(this.model.attributes, function(val, key) {
105 | var input = this.$el.find('[name="' + key + '"]');
106 | if (!input.length) {
107 | return;
108 | }
109 | if (key === 'start' || key === 'end') {
110 | var date = input.val().split('/');
111 | var value = new Date(date[2] + '-' + date[1] + '-' + date[0]);
112 | this.model.set(key, new Date(value));
113 | } else if (input.prop('type') === 'checkbox') {
114 | this.model.set(key, input.prop('checked'));
115 | } else {
116 | this.model.set(key, input.val());
117 | }
118 | }, this);
119 | this.model.save();
120 | }
121 | });
122 |
123 | module.exports = ModalTaskEditComponent;
124 |
--------------------------------------------------------------------------------
/src/js/views/TopMenuView/MSProjectMenuView.js:
--------------------------------------------------------------------------------
1 | var parseXML = require('../../utils/xmlWorker').parseXMLObj;
2 | var tasksToXML = require('../../utils/xmlWorker').tasksToXML;
3 | var parseDepsFromXML = require('../../utils/xmlWorker').parseDepsFromXML;
4 |
5 | var MSProjectMenuView = Backbone.View.extend({
6 | el: '#project-menu',
7 |
8 | initialize: function(params) {
9 | this.settings = params.settings;
10 | this.importing = false;
11 | this._setupInput();
12 | },
13 | _setupInput: function() {
14 | var input = $('#importFile');
15 | var self = this;
16 | input.on('change', function(evt) {
17 | var files = evt.target.files;
18 | _.each(files, function(file) {
19 | var parts = file.name.split('.');
20 | var extention = parts[parts.length - 1].toLowerCase();
21 | if (extention !== 'xml') {
22 | alert('The file type "' + extention + '" is not supported. Only xml files are allowed. Please save your MS project as a xml file and try again.');
23 | return;
24 | }
25 | var reader = new FileReader();
26 | reader.onload = function(e) {
27 | try {
28 | self.xmlData = e.target.result;
29 | } catch (e) {
30 | alert('Error while paring file.');
31 | throw e;
32 | }
33 | };
34 | reader.readAsText(file);
35 | });
36 | });
37 | },
38 | events: {
39 | 'click #upload-project' : function() {
40 | $('#msimport').modal({
41 | onHidden : function() {
42 | $(document.body).removeClass('dimmable');
43 | },
44 | onApprove : function() {
45 | if (!this.xmlData || this.importing) {
46 | return false;
47 | }
48 | this.importing = true;
49 | $("#importProgress").show();
50 | $("#importFile").hide();
51 | $(document.body).removeClass('dimmable');
52 | $('#xmlinput-form').trigger('reset');
53 | setTimeout(this.importData.bind(this), 20);
54 | return false;
55 | }.bind(this)
56 | }).modal('show');
57 | $("#importProgress").hide();
58 | $("#importFile").show();
59 | },
60 | 'click #download-project' : function() {
61 | var data = tasksToXML(this.collection);
62 | var blob = new Blob([data], {type : 'application/json'});
63 | saveAs(blob, 'GanttTasks.xml');
64 | }
65 | },
66 | progress : function(percent) {
67 | $('#importProgress').progress({
68 | percent : percent
69 | });
70 | },
71 | _prepareData : function(data) {
72 | var defStatus = this.settings.findDefaultStatusId();
73 | var defHealth = this.settings.findDefaultHealthId();
74 | var defWO = this.settings.findDefaultWOId();
75 | data.forEach(function(item) {
76 | item.health = defHealth;
77 | item.status = defStatus;
78 | item.wo = defWO;
79 | });
80 | return data;
81 | },
82 | importData : function() {
83 | var delay = 100;
84 | this.progress(0);
85 | // this is some sort of callback hell!!
86 | // we need timeouts for better user experience
87 | // I think user want to see animated progress bar
88 | // but without timeouts it is not possible, right?
89 | setTimeout(function() {
90 | this.progress(10);
91 | var col = this.collection;
92 | var data = parseXML(this.xmlData);
93 | data = this._prepareData(data);
94 |
95 | setTimeout(function() {
96 | this.progress(26);
97 | col.importTasks(data, function() {
98 | this.progress(43);
99 | setTimeout(function() {
100 | this.progress(59);
101 | var deps = parseDepsFromXML(this.xmlData);
102 | setTimeout(function() {
103 | this.progress(78);
104 | col.createDeps(deps);
105 | setTimeout(function() {
106 | this.progress(100);
107 | this.importing = false;
108 | $('#msimport').modal('hide');
109 | }.bind(this), delay);
110 | }.bind(this), delay);
111 | }.bind(this), delay);
112 | }.bind(this), delay);
113 | }.bind(this), delay);
114 | }.bind(this), delay);
115 | }
116 | });
117 |
118 | module.exports = MSProjectMenuView;
119 |
--------------------------------------------------------------------------------
/src/libs/jquery.comment/jquery.comment.js:
--------------------------------------------------------------------------------
1 | !function (a) { var b = { init: function (c) { var d = this, e = { getCommentsUrl: null, postCommentUrl: null, deleteCommentUrl: null, localization: { headerText: "Comments", commentPlaceHolderText: "Add a comment...", sendButtonText: "Send", replyButtonText: "Reply", deleteButtonText: "Delete" }, callback: { beforeDelete: function () { return !0 }, afterDelete: function () { }, beforeCommentAdd: function () { return !0 }, afterCommentAdd: function () { }, beforeRefresh: function () { }, afterRefresh: function () { }, onGetError: function () { }, onPostError: function () { } }, displayHeader: !0, displayCount: !0, loadWhenVisible: !1, readOnly: !1, displayAvatar: !1 }; if (c = a.extend(!0, {}, e, c), this.data("data", { options: c }), this.addClass("commentsBlock"), c.displayHeader) { var f = "
" + c.localization.headerText; c.displayCount && (f += ' ( ) '), f += "
", this.append(f) } c.readOnly || this.append('"), this.append(''); var g = this.find(".newComment").find("textarea"), h = g.parent().find("[data-action='send']"); if (h.hide(), g.focusout(function () { 0 == a.trim(a(this).val()).length && (h.hide(), a(this).val("")) }), g.focus(function () { h.show() }), h.bind("click", function () { var c = a(this).parents("form"), e = g.val(); if (a.trim(e).length > 0) { var f = c.serialize(); b.postComment.call(d, f), g.val(""), h.hide() } }), c.loadWhenVisible) { var i = d.offset().top, j = i + d.height(), k = a(window).scrollTop(); j <= k + a(window).height() && i >= k ? b.refresh.call(d) : a(window).bind("scroll", function () { k = a(window).scrollTop(), j <= k + a(window).height() && i >= k && (a(window).unbind("scroll"), b.refresh.call(d)) }) } else b.refresh.call(d) }, count: function () { var a = this; return a.find(".comment").length }, refresh: function () { var c = this, d = c.find(".loadingIndicator"); d.show(); var e = this.data().data.options; e.callback.beforeRefresh(), a.getJSON(e.getCommentsUrl, function (f) { c.find(".comments").empty(), a.each(f, function (a, d) { b.bindComment.call(c, d) }), b.bindEvents.call(c), d.hide(), c.find(".heading h4 [data-action='count']").html(c.find(".comment").length), e.callback.afterRefresh() }).fail(function () { d.hide(), e.callback.onGetError() }) }, postComment: function (c) { var d = this, e = d.data().data.options; if (e.callback.beforeCommentAdd()) { var f = d.find(".loadingIndicator"); f.show(), a.post(e.postCommentUrl, c, function (a) { f.hide(), b.bindComment.call(d, a), b.bindEvents.call(d), d.find(".heading h4 [data-action='count']").html(d.find(".comment").length), e.callback.afterCommentAdd(a.Id) }).fail(function () { f.hide(), e.callback.onPostError() }) } }, bindComment: function (a) { var b, c = this.data().data.options; null != a.ParentId ? (b = this.find("[data-commentid=" + a.ParentId + "]"), 0 == b.find(".reply_comments").length && b.append(""), b = b.children(".reply_comments").children(".comments")) : b = this.children(".comments"), null != a.Comment && (a.Comment = a.Comment.replace("", "")); var d = "