├── .gitignore
├── dist
└── msp-viewer.zip
├── src
├── images
│ ├── table.png
│ ├── toggle-icon.png
│ ├── folder_open_16.png
│ ├── location_http.png
│ ├── open_folder_yellow.png
│ ├── toggle-expand-icon.png
│ ├── toggle-small-icon.png
│ ├── 1407718307_kdb_table.png
│ ├── 1407718490_kdb_table.png
│ ├── application_side_tree.png
│ ├── text-list-numbers-icon.png
│ ├── toggle-small-expand-icon.png
│ ├── 1408645657_text_list_numbers.png
│ ├── 1408645695_text_list_bullets.png
│ ├── 1408645937_519617-116_List2.png
│ ├── Actions-view-calendar-day-icon.png
│ ├── Actions-view-calendar-list-icon.png
│ ├── Actions-view-calendar-week-icon.png
│ ├── 1407718371_application_side_tree.png
│ ├── Actions-view-calendar-month-icon.png
│ └── Actions-view-calendar-timeline-icon.png
├── css
│ ├── localized-date-formats.css
│ ├── app.css
│ └── ganttchart.css
├── html
│ └── index.html
└── js
│ ├── app.js
│ ├── mspdocument.js
│ └── ganttchart.js
├── doc
└── open-msp-viewer.png
├── LICENSE
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | dist/*
2 | !dist/msp-viewer.zip
--------------------------------------------------------------------------------
/dist/msp-viewer.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rpbouman/open-msp-viewer/HEAD/dist/msp-viewer.zip
--------------------------------------------------------------------------------
/src/images/table.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rpbouman/open-msp-viewer/HEAD/src/images/table.png
--------------------------------------------------------------------------------
/doc/open-msp-viewer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rpbouman/open-msp-viewer/HEAD/doc/open-msp-viewer.png
--------------------------------------------------------------------------------
/src/images/toggle-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rpbouman/open-msp-viewer/HEAD/src/images/toggle-icon.png
--------------------------------------------------------------------------------
/src/images/folder_open_16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rpbouman/open-msp-viewer/HEAD/src/images/folder_open_16.png
--------------------------------------------------------------------------------
/src/images/location_http.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rpbouman/open-msp-viewer/HEAD/src/images/location_http.png
--------------------------------------------------------------------------------
/src/images/open_folder_yellow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rpbouman/open-msp-viewer/HEAD/src/images/open_folder_yellow.png
--------------------------------------------------------------------------------
/src/images/toggle-expand-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rpbouman/open-msp-viewer/HEAD/src/images/toggle-expand-icon.png
--------------------------------------------------------------------------------
/src/images/toggle-small-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rpbouman/open-msp-viewer/HEAD/src/images/toggle-small-icon.png
--------------------------------------------------------------------------------
/src/images/1407718307_kdb_table.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rpbouman/open-msp-viewer/HEAD/src/images/1407718307_kdb_table.png
--------------------------------------------------------------------------------
/src/images/1407718490_kdb_table.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rpbouman/open-msp-viewer/HEAD/src/images/1407718490_kdb_table.png
--------------------------------------------------------------------------------
/src/images/application_side_tree.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rpbouman/open-msp-viewer/HEAD/src/images/application_side_tree.png
--------------------------------------------------------------------------------
/src/images/text-list-numbers-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rpbouman/open-msp-viewer/HEAD/src/images/text-list-numbers-icon.png
--------------------------------------------------------------------------------
/src/images/toggle-small-expand-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rpbouman/open-msp-viewer/HEAD/src/images/toggle-small-expand-icon.png
--------------------------------------------------------------------------------
/src/images/1408645657_text_list_numbers.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rpbouman/open-msp-viewer/HEAD/src/images/1408645657_text_list_numbers.png
--------------------------------------------------------------------------------
/src/images/1408645695_text_list_bullets.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rpbouman/open-msp-viewer/HEAD/src/images/1408645695_text_list_bullets.png
--------------------------------------------------------------------------------
/src/images/1408645937_519617-116_List2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rpbouman/open-msp-viewer/HEAD/src/images/1408645937_519617-116_List2.png
--------------------------------------------------------------------------------
/src/images/Actions-view-calendar-day-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rpbouman/open-msp-viewer/HEAD/src/images/Actions-view-calendar-day-icon.png
--------------------------------------------------------------------------------
/src/images/Actions-view-calendar-list-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rpbouman/open-msp-viewer/HEAD/src/images/Actions-view-calendar-list-icon.png
--------------------------------------------------------------------------------
/src/images/Actions-view-calendar-week-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rpbouman/open-msp-viewer/HEAD/src/images/Actions-view-calendar-week-icon.png
--------------------------------------------------------------------------------
/src/images/1407718371_application_side_tree.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rpbouman/open-msp-viewer/HEAD/src/images/1407718371_application_side_tree.png
--------------------------------------------------------------------------------
/src/images/Actions-view-calendar-month-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rpbouman/open-msp-viewer/HEAD/src/images/Actions-view-calendar-month-icon.png
--------------------------------------------------------------------------------
/src/images/Actions-view-calendar-timeline-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rpbouman/open-msp-viewer/HEAD/src/images/Actions-view-calendar-timeline-icon.png
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2014 Roland Bouman (roland.bouman@gmail.com)
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
--------------------------------------------------------------------------------
/src/css/localized-date-formats.css:
--------------------------------------------------------------------------------
1 | div.formatted-date {
2 | width: 5.2em;
3 | }
4 |
5 | .date-part-separator-dot div.formatted-date > span:after {
6 | content: ".";
7 | }
8 |
9 | .date-part-separator-slash div.formatted-date > span:after {
10 | content: "/";
11 | }
12 |
13 | .date-part-separator-dash div.formatted-date > span:after {
14 | content: "-";
15 | }
16 |
17 | /* Year */
18 | .date-part-order-DMY div.formatted-date > span.formatted-date-year,
19 | .date-part-order-MDY div.formatted-date > span.formatted-date-year {
20 | float: right;
21 | }
22 | .date-part-order-DMY div.formatted-date > span.formatted-date-year:after,
23 | .date-part-order-MDY div.formatted-date > span.formatted-date-year:after {
24 | content: "" !important;
25 | }
26 |
27 | .date-part-order-YMD div.formatted-date > span.formatted-date-year {
28 | float: left;
29 | }
30 |
31 | /* Day */
32 | .date-part-order-YMD div.formatted-date > span.formatted-date-day {
33 | float: right;
34 | }
35 | .date-part-order-YMD div.formatted-date > span.formatted-date-day:after {
36 | content: "" !important;
37 | }
38 |
39 | .date-part-order-DMY div.formatted-date > span.formatted-date-day {
40 | float: left;
41 | }
42 |
43 | /* Month */
44 | .date-part-order-MDY div.formatted-date > span.formatted-date-month {
45 | float: left;
46 | }
47 |
48 |
--------------------------------------------------------------------------------
/src/css/app.css:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Copyright 2014 Roland Bouman (roland.bouman@gmail.com)
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 |
17 | */
18 |
19 | html, head, body {
20 | margin: 0px;
21 | padding: 0px;
22 | font-family: sans-serif;
23 | font-size: 10pt;
24 | }
25 |
26 | div.toolbar > div.open {
27 | background-image: url("../images/folder_open_16.png");
28 | }
29 |
30 | div.toolbar > div.ganttchart {
31 | background-image: url("../images/1407718371_application_side_tree.png");
32 | }
33 |
34 | div.toolbar > div.resources {
35 | background-image: url("../images/1407718490_kdb_table.png");
36 | }
37 |
38 | div.toolbar > div.project {
39 | background-image: url("../images/Actions-view-calendar-timeline-icon.png");
40 | }
41 |
42 | div.toolbar > div.year {
43 | background-image: url("../images/Actions-view-calendar-list-icon.png");
44 | }
45 |
46 | div.toolbar > div.month {
47 | background-image: url("../images/Actions-view-calendar-month-icon.png");
48 | }
49 |
50 | div.toolbar > div.week {
51 | background-image: url("../images/Actions-view-calendar-week-icon.png");
52 | }
53 |
54 | div.toolbar > div.day {
55 | background-image: url("../images/Actions-view-calendar-day-icon.png");
56 | }
57 |
58 | div.toolbar > div.show-none {
59 | background-image: url("../images/1408645937_519617-116_List2.png");
60 | }
61 |
62 | div.toolbar > div.show-wbs {
63 | background-image: url("../images/1408645695_text_list_bullets.png");
64 | }
65 |
66 | div.toolbar > div.show-outlinenumber {
67 | background-image: url("../images/1408645657_text_list_numbers.png");
68 | }
--------------------------------------------------------------------------------
/src/html/index.html:
--------------------------------------------------------------------------------
1 |
2 |
19 |
20 |
21 | MS Project Viewer
22 |
23 |
24 |
25 |
26 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | open-msp-viewer
2 | ===============
3 |
4 | Open MS Project viewer - a pure HTML5 viewer for MS Project files.
5 |
6 | 
7 |
8 | Using
9 | -----
10 | To use the open msp viewer, download the [msp-viewer.zip](https://github.com/rpbouman/open-msp-viewer/blob/master/dist/msp-viewer.zip?raw=true "Open MS Project viewer distribution") archive.
11 | Unpack the archive, and use your web browser to open resources/html/index.html.
12 | This is a sample application that lets you load MS Project XML files via the browser file API, which is supported on all modern web browsers, and on Internet Explorer starting from version 10.
13 |
14 | The sample application also has an option to open a MS Project file using HTTP (use the folder icon on the toolbar). However, this option is subject to the standard same-origin policy of the XMLHttpRequest.
15 |
16 | The sample application also shows how you can embed the open msp viewer into your own webapplications. See the "Developers" section for more details.
17 |
18 | Supported MS Project File formats
19 | ---------------------------------
20 | The open-msp-viewer can handle only the XML format as exported by MS Project.
21 | This XML format is also known as mspdi (MS Project Data Interchange).
22 | This is [latest documentation](http://msdn.microsoft.com/en-us/library/bb428843(v=office.12).aspx) for the format pertains to MS Project 2007.
23 | XML Schemas for this format can be found [here](http://schemas.microsoft.com/project/2007/).
24 |
25 | The open-msp-viewr ships with a few sample files in the sample directory which you can use to test.
26 |
27 | If you run into trouble loading a particular XML file created by MS Project, please file an issue and please attach a minimal version of the file that reproduces the problem.
28 | Please clearly state with wich version of MS project you created the file.
29 |
30 | If you manipulate MS Project XML Files outside of MS Project, and you experience problems loading the file in the open-msp-viewer, but not in MS Project itself, then we're interested to learn about that too.
31 | In those cases, please clearly state the nature of the modification. Attach the file that won't load. Please also load the file in MS Project, re-export, and attach that as well so that we can discover any differences.
32 |
33 | If you have a need to load .msp files, or other file formats, please add a comment to [issue 10](https://github.com/rpbouman/open-msp-viewer/issues/10 "Ability to open .mpp files (binary) #10") and explain your requirements.
34 | As this is a not-for profit open source project, we cannot make any guarantees. However, we can always negotiate some conditions that do give you guarantees.
35 |
36 | Supported MS Project versions
37 | -----------------------------
38 | Technically, the open-msp-viewer does not support any version of MS Project.
39 | Currently open-msp-viewer reads and renders mspdi files.
40 | MS Project 2002 througj 2013 support reading and writing mspdi files.
41 | The format has been fairly constant.
42 | The open-msp-viewer should be able to read any mspdi file created by MS Project 2002 through MS Projcet 2012.
43 |
44 | Supported Browsers
45 | ------------------
46 | Open msp viewer is tested against the following browsers:
47 | - Chrome (current version) on Ubuntu Linux 13.10 and Windows 7
48 | - Firefox (current version) on Ubuntu Linux 13.10 and Windows 7
49 | - IE8, IE9, IE10, IE11 on Windows7
50 | - Opera 12.16 on Ubuntu Linux 13.10 and Opera 23.0 on Windows 7
51 |
52 | This simply reflects the platforms I have at my disposal.
53 | If you're interested in support for other browser please post an issue.
54 | If there is enough demand for a particular browser we can try and figure out a way to start supporting it.
55 |
56 | Developers
57 | ----------
58 | Please refer the sample application in the resources/html/index.html folder to see how to use the open msp viewer in your webpages.
--------------------------------------------------------------------------------
/src/js/app.js:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Copyright 2014 Roland Bouman (roland.bouman@gmail.com)
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 |
17 | */
18 |
19 | (function(){
20 |
21 | linkCss(cssDir + "app.css");
22 |
23 | var spinner = new Spinner();
24 | spinner.show();
25 |
26 | var dnd = new DDHandler({
27 | node: body
28 | });
29 | SplitPane.listenToDnd(dnd);
30 |
31 | var mainToolbar = new Toolbar({
32 | container: body,
33 | });
34 | mainToolbar.addButton([
35 | {"class": "open"},
36 | typeof(FileReader) === "undefined" ? {"class": "separator"} : cEl("input", {
37 | id: "file",
38 | type: "file"
39 | }),
40 | /*
41 | {"class": "separator"},
42 | {"class": "ganttchart", tooltip: "Gant T Chart", toggleGroup: "view", depressed: true},
43 | {"class": "resources", tooltip: "Resources", toggleGroup: "view"},
44 | */
45 | {"class": "separator"},
46 | {"class": "project", tooltip: "Project", toggleGroup: "calendar", depressed: true},
47 | {"class": "year", tooltip: "Year", toggleGroup: "calendar"},
48 | {"class": "month", tooltip: "Month", toggleGroup: "calendar"},
49 | {"class": "week", tooltip: "Week", toggleGroup: "calendar"},
50 | {"class": "day", tooltip: "Day", toggleGroup: "calendar"},
51 | {"class": "separator"},
52 | {"class": "show-none", tooltip: "Only Task name", toggleGroup: "show-task-id", depressed: true},
53 | {"class": "show-wbs", tooltip: "Task name + WBS", toggleGroup: "show-task-id", depressed: false},
54 | {"class": "show-outlinenumber", tooltip: "Task name + Outline Number", toggleGroup: "show-task-id", depressed: false}
55 | ]);
56 |
57 | mainToolbar.listen({
58 | buttonPressed: function(toolbar, event, data){
59 | switch (data.conf["class"]) {
60 | case "open":
61 | handleHttpOpen();
62 | break;
63 | }
64 | },
65 | afterToggleGroupStateChanged: function(toolbar, event, data){
66 | var newButton = data.newButton;
67 | var newButtonClass = newButton.conf["class"];
68 | switch (data.group) {
69 | /*
70 | case "view":
71 | //todo: show the current view, hide the others.
72 | break;
73 | */
74 | case "calendar":
75 | gantTChart.setCalendarResolution(newButtonClass);
76 | break;
77 | case "show-task-id":
78 | var displayOutlineNumber, displayWBS;
79 | switch (newButton.conf["class"]) {
80 | case "show-none":
81 | displayOutlineNumber = false;
82 | displayWBS = false;
83 | break;
84 | case "show-wbs":
85 | displayOutlineNumber = false;
86 | displayWBS = true;
87 | break;
88 | case "show-outlinenumber":
89 | displayOutlineNumber = true;
90 | displayWBS = false;
91 | break;
92 | }
93 | gantTChart.displayOutlineNumber(displayOutlineNumber);
94 | gantTChart.displayWBS(displayWBS);
95 | break;
96 | }
97 | }
98 | });
99 |
100 | function gotDocument(doc){
101 | gantEl.style.display = "";
102 | gantTChart.clear();
103 | gantTChart.setSplitterPosition("350px");
104 | gantTChart.setDocument(doc);
105 | }
106 |
107 | function handleHttpOpen(){
108 | var sampleUrl = document.location.href.split("/");
109 | sampleUrl.length = sampleUrl.length - 3;
110 | sampleUrl = sampleUrl.join("/") + "/sample/3PointPlan-example.xml";
111 | var url = prompt(
112 | "Please enter a url to download a MS Project XML file:",
113 | sampleUrl
114 | );
115 | if (url === null) return;
116 | loadFromHttp(url);
117 | }
118 |
119 | function loadFromHttp(url){
120 | spinner.show();
121 | ajax({
122 | url: url,
123 | success: function(options, xhr, data){
124 | try {
125 | var doc = new MspDocument();
126 | doc.loadFromDom(data);
127 | gotDocument(doc);
128 | }
129 | catch (e) {
130 | alert("Error occurred loading the document: " + e);
131 | }
132 | spinner.hide();
133 | },
134 | failure: function(options, xhr, exception){
135 | debugger;
136 | alert("Error occurred retrieving the document: " + exception.toString());
137 | spinner.hide();
138 | }
139 | });
140 | }
141 |
142 | if (typeof(FileReader) !== "undefined") {
143 | listen(gEl("file"), "change", function(e){
144 | spinner.show();
145 | var target = e.getTarget();
146 | var file = target.files[0];
147 | var reader = new FileReader();
148 | reader.onload = function(e){
149 | try {
150 | var doc = new MspDocument();
151 | doc.loadFromXml(e.target.result);
152 | gotDocument(doc);
153 | }
154 | catch (e) {
155 | alert("Error occurred loading the document: " + e);
156 | }
157 | spinner.hide();
158 | }
159 | reader.readAsText(file);
160 | });
161 | }
162 | //var top = mainToolbar.getDom().offsetHeight;
163 | cEl("div", {
164 | id: "workarea",
165 | style: {
166 | top: 34 + "px",
167 | left: "0px", right: "0px", bottom: "0px",
168 | position: "absolute"
169 | }
170 | }, null, body);
171 |
172 | var calendarResolution = mainToolbar.getDepressedButtonInToggleGroup("calendar").conf["class"];
173 | var dateFormatter = function(value, task, doc){
174 | var date = gantTChart.getDocument().parseDate(value);
175 | return (new Date(date)).toUTCString();
176 | }
177 | var durationFormatter = function(value, task, doc) {
178 | var duration = doc.parseISO8601Duration(value);
179 | var start = new Date(doc.parseDate(task.Start));
180 | //var end = dateAdd(start, duration);
181 | var finish = new Date(doc.parseDate(task.Finish));
182 | var durationFormat = doc.getDurationFormat(task);
183 | return doc.formatDuration(start, finish, durationFormat);
184 | }
185 | var gantTChart = new GantTChart({
186 | container: gEl("workarea"),
187 | calendarResolution: calendarResolution,
188 | configuredAttributes: {
189 | Start: {
190 | formatter: dateFormatter
191 | },
192 | Finish: {
193 | formatter: dateFormatter
194 | },
195 | Duration: {
196 | formatter: durationFormatter
197 | }
198 | }
199 | });
200 | var gantEl = gantTChart.createDom();
201 | gantEl.style.display = "none";
202 |
203 | //handle window resize.
204 | var windowResizedTimer = new Timer({delay: 100});
205 | var splitterRatio;
206 | windowResizedTimer.listen("expired", function(){
207 | gantTChart.setSplitterPosition(gantTChart.getSize() * splitterRatio);
208 | });
209 | listen(window, "resize", function(){
210 | splitterRatio = gantTChart.getSplitterRatio();
211 | windowResizedTimer.start();
212 | });
213 |
214 | //
215 | spinner.hide();
216 |
217 | loadFromHttp("../../sample/3PointPlan-example.xml");
218 | })()
--------------------------------------------------------------------------------
/src/js/mspdocument.js:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Copyright 2014 Roland Bouman (roland.bouman@gmail.com)
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 |
17 | */
18 |
19 | var MspDocument;
20 | (function(){
21 |
22 | function elementToObject(node) {
23 | if (typeof(node.childNodes) === "undefined") return null;
24 | var object = {};
25 | var childNodes = node.childNodes, n = childNodes.length,
26 | i, childNode, tagName, typeOfEl
27 | ;
28 | for (i = 0; i < n; i++){
29 | childNode = childNodes[i];
30 | switch (childNode.nodeType) {
31 | case 1:
32 | tagName = childNode.tagName;
33 | typeOfEl = typeof(object[tagName]);
34 | switch (typeOfEl) {
35 | case "undefined":
36 | object[tagName] = elementToObject(childNode);
37 | break;
38 | case "object":
39 | if (object[tagName].constructor !== Array) {
40 | object[tagName] = [object[tagName]];
41 | }
42 | object[tagName].push(elementToObject(childNode));
43 | break;
44 | }
45 | break;
46 | case 3:
47 | if (n === 1) return childNode.data;
48 | break;
49 | }
50 | }
51 | return object;
52 | }
53 |
54 | /*
55 | http://msdn.microsoft.com/en-us/library/bb968637(v=office.12).aspx
56 | */
57 | var durationFormats = {
58 | 3: {
59 | abbr: "m",
60 | name: "minutes",
61 | formatter: function(from, to) {
62 | return (to - from) / 60 / 1000;
63 | }
64 | },
65 | 4: {
66 | abbr: "em",
67 | name: "elapsed minutes",
68 | },
69 | 5: {
70 | abbr: "h",
71 | name: "hours",
72 | formatter: function(from, to) {
73 | return (to - from) / 60 / 60 / 1000;
74 | }
75 | },
76 | 6: {
77 | abbr: "eh",
78 | name: "elapsed hours",
79 | },
80 | 7: {
81 | abbr: "d",
82 | name: "days",
83 | formatter: function(from, to) {
84 | return (to - from) / 24 / 60 / 60 / 1000;
85 | }
86 | },
87 | 8: {
88 | abbr: "ed",
89 | name: "elapsed days",
90 | },
91 | 9: {
92 | abbr: "w",
93 | name: "weeks",
94 | formatter: function(from, to) {
95 | return (to - from) / 7 / 24 / 60 / 60 / 1000;
96 | }
97 | },
98 | 10: {
99 | abbr: "ew",
100 | name: "elapsed weeks",
101 | },
102 | 11: {
103 | abbr: "mo",
104 | name: "months",
105 | },
106 | 12: {
107 | abbr: "emo",
108 | name: "elapsed months",
109 | },
110 | 19: {
111 | abbr: "%",
112 | name: "percent",
113 | },
114 | 20: {
115 | abbr: "e%",
116 | name: "elapsed percent",
117 | },
118 | 21: null,
119 | 35: {
120 | abbr: "m?",
121 | name: "estimated minutes",
122 | },
123 | 36: {
124 | abbr: "em?",
125 | name: "estimated elapsed minutes",
126 | },
127 | 37: {
128 | abbr: "h?",
129 | name: "estimated hours",
130 | },
131 | 38: {
132 | abbr: "h?",
133 | name: "estimated elapsed hours",
134 | },
135 | 39: {
136 | abbr: "d?",
137 | name: "estimated days",
138 | },
139 | 40: {
140 | abbr: "ed?",
141 | name: "estimated elapsed days",
142 | },
143 | 41: {
144 | abbr: "w?",
145 | name: "estimated weeks",
146 | },
147 | 42: {
148 | abbr: "ew?",
149 | name: "estimated elapsed weeks",
150 | },
151 | 43: {
152 | abbr: "mo?",
153 | name: "estimated months",
154 | },
155 | 44: {
156 | abbr: "emo?",
157 | name: "estimated elapsed months",
158 | },
159 | 51: {
160 | abbr: "%?",
161 | name: "estimated percent",
162 | },
163 | 52: {
164 | abbr: "e%?",
165 | name: "estimated elapsed percent",
166 | },
167 | 53: null
168 | };
169 |
170 | (MspDocument = function(){
171 | this.data = null;
172 | }).prototype = {
173 | loadFromXml: function(xml){
174 | var doc = parseXmlText(xml);
175 | this.loadFromDom(doc);
176 | },
177 | loadFromDom: function(dom){
178 | var object = elementToObject(dom);
179 | this.data = object;
180 | this.indexData();
181 | },
182 | formatDuration: function(startDate, endDate, format){
183 | var format = durationFormats[format];
184 | var formatter = format.formatter;
185 | var value;
186 | if (formatter) {
187 | value = formatter(startDate.getTime(), endDate.getTime());
188 | value += " " + format.name;
189 | }
190 | else {
191 | value = "duration format \"" + format.name + "\" not implemented :(";
192 | }
193 | return value;
194 | },
195 | parseISO8601Duration: function(duration){
196 | // 12 34 56 78 91 12
197 | var match = /^P((\d+)Y)?((\d+)M)?((\d+)D)?T?((\d+)H)?((\d+)M)?((\d+)S)?$/.exec(duration);
198 | if (!match) throw "Illegal duration value.";
199 | var o = {};
200 | if (match[2]) {
201 | o.years = parseInt(match[2], 10);
202 | }
203 | if (match[4]){
204 | o.months = parseInt(match[4], 10);
205 | }
206 | if (match[6]){
207 | o.days = parseInt(match[6], 10);
208 | }
209 | if (match[8]){
210 | o.hours = parseInt(match[8], 10);
211 | }
212 | if (match[10]){
213 | o.minutes = parseInt(match[10], 10);
214 | }
215 | if (match[12]){
216 | o.seconds = parseInt(match[12], 10);
217 | }
218 | return o;
219 | },
220 | index: function(path, keys, multiple){
221 | var array = this.getValueAsArray.apply(this, path);
222 | var i, n, object, property, key, value, map, bucket;
223 | for (property in keys) {
224 | map = {};
225 | this[property] = map;
226 | }
227 | for (i = 0, n = array.length; i < n; i++){
228 | object = array[i];
229 | for (property in keys) {
230 | map = this[property];
231 | key = keys[property];
232 | value = object[key];
233 | if (multiple) {
234 | bucket = map[value];
235 | if (!bucket) {
236 | bucket = [];
237 | map[value] = bucket;
238 | }
239 | bucket.push(object);
240 | }
241 | else {
242 | map[value] = object;
243 | }
244 | }
245 | }
246 | },
247 | indexTasks: function(){
248 | this.index(["Project", "Tasks", "Task"], {
249 | tasksByUID: "UID"
250 | }, false);
251 | },
252 | getTaskByUID: function(uid){
253 | return this.tasksByUID[uid];
254 | },
255 | indexResources: function(){
256 | this.index(["Project", "Resources", "Resource"], {
257 | resourcesByUID: "UID"
258 | }, false);
259 | },
260 | getResourceByUID: function(uid){
261 | return this.resourcesByUID[uid];
262 | },
263 | indexAssignments: function(){
264 | this.index(["Project", "Assignments", "Assignment"], {
265 | assignmentsByTaskUID: "TaskUID",
266 | assignmentsByResourceUID: "ResourceUID",
267 | }, true);
268 | },
269 | getAssignmentsByResourceUID: function(resourceUID){
270 | return this.assignmentsByResourceUID[resourceUID];
271 | },
272 | getAssignmentsByTaskUID: function(taskUID){
273 | return this.assignmentsByTaskUID[taskUID];
274 | },
275 | indexData: function(){
276 | this.indexTasks();
277 | this.indexResources();
278 | this.indexAssignments();
279 | },
280 | getValue: function(){
281 | var value = this.data, argument;
282 | for (var i = 0; i < arguments.length; i++) {
283 | argument = arguments[i];
284 | value = value[argument];
285 | }
286 | return value;
287 | },
288 | getValueAsDate: function(){
289 | var value = this.getValue.apply(this, arguments);
290 | //Normally, new Date(Date.UTC(value)); should do the trick, but not in IE8
291 | return new Date(this.parseDate(value));
292 | },
293 | getValueAsBoolean: function(){
294 | var value = this.getValue.apply(this, arguments);
295 | switch (value) {
296 | case "0": return false;
297 | case "1": return true;
298 | default: throw "Invalid boolean value " + value;
299 | }
300 | },
301 | getValueAsInteger: function(){
302 | var value = this.getValue.apply(this, arguments);
303 | var iValue = parseInt(value, 10);
304 | if (isNaN(iValue)) {
305 | throw "Invalid int value " + value;
306 | }
307 | return iValue;
308 | },
309 | getValueAsFloat: function(){
310 | var value = this.getValue.apply(this, arguments);
311 | var fValue = parseFloat(value);
312 | if (isNaN(fValue)) {
313 | throw "Invalid float value " + value;
314 | }
315 | return fValue;
316 | },
317 | getValueAsArray: function(){
318 | var value = this.getValue.apply(this, arguments);
319 | if (iArr(value)) return value;
320 | return [value];
321 | },
322 | getBoundaryDates: function(){
323 | var minDate = this.getStartDate().getTime(),
324 | maxDate = this.getFinishDate().getTime()
325 | ;
326 | var me = this;
327 | this.forEachTask(function(task, i){
328 | var date;
329 | date = me.parseDate(task.Start);
330 | if (date < minDate) {
331 | minDate = date;
332 | }
333 | date = me.parseDate(task.Finish);
334 | if (date > maxDate) {
335 | maxDate = date;
336 | }
337 | });
338 | return {
339 | minDate: new Date(minDate),
340 | maxDate: new Date(maxDate)
341 | };
342 | },
343 | getDurationFormat: function(item){
344 | var durationFormat;
345 | if (item) {
346 | durationFormat = item.DurationFormat;
347 | durationFormat = parseInt(durationFormat, 10);
348 | if (durationFormat < 3 || durationFormat > 51) {
349 | throw "Invalid duration format " + durationFormat;
350 | }
351 | if (durationFormat !== 21 && durationFormat !== 51) {
352 | return durationFormat;
353 | }
354 | }
355 | if (!this.durationFormat){
356 | this.durationFormat = this.getValueAsInteger("Project", "DurationFormat");
357 | }
358 | return this.durationFormat;
359 | },
360 | getStartDate: function(){
361 | return this.getValueAsDate("Project", "StartDate");
362 | },
363 | getFinishDate: function(){
364 | return this.getValueAsDate("Project", "FinishDate");
365 | },
366 | getCalendars: function(){
367 | return this.getValueAsArray("Project", "Calendars", "Calendar");
368 | },
369 | getBaseCalendar: function(){
370 | if (!this.baseCalendar) {
371 | var calendars = this.getCalendars();
372 | var i, n = calendars.length, calendar;
373 | for (i = 0; i < n; i++) {
374 | calendar = calendars[i];
375 | if (calendar.IsBaseCalendar === "1") {
376 | this.baseCalendar = calendar;
377 | break;
378 | }
379 | }
380 | }
381 | return this.baseCalendar;
382 | },
383 | getWeekStartDay: function(){
384 | return this.getValueAsInteger("Project", "WeekStartDay");
385 | },
386 | getTasks: function(){
387 | return this.getValueAsArray("Project", "Tasks", "Task");
388 | },
389 | getAssignments: function(){
390 | return this.getValueAsArray("Project", "Assignments", "Assignment");
391 | },
392 | getResources: function(){
393 | return this.getValueAsArray("Project", "Resources", "Resource");
394 | },
395 | getTaskLinks: function(task) {
396 | var links = task.PredecessorLink;
397 | if (!links) {
398 | links = [];
399 | }
400 | else
401 | if (!iArr(links)){
402 | links = [links];
403 | }
404 | return links;
405 | },
406 | forEachTaskLink: function(task, callback, scope){
407 | if (!scope) scope = this;
408 | var i, n, link, links = this.getTaskLinks(task);
409 | for (i = 0, n = links.length; i < n; i++) {
410 | link = links[i];
411 | if (callback.call(scope, link, i) === false) {
412 | return false;
413 | }
414 | }
415 | return true;
416 | },
417 | forEach: function(){
418 | var args = arguments, n = args.length, i, arg, path = [];
419 | for (i = 0; i < n; i++){
420 | arg = args[i];
421 | if (iStr(arg)) {
422 | path.push(arg);
423 | }
424 | else {
425 | break;
426 | }
427 | }
428 | var array = this.getValueAsArray.apply(this, path);
429 | var callback = args[i++];
430 | var scope = args[i++];
431 | if (!scope) {
432 | scope = this;
433 | }
434 | for (i = 0, n = array.length; i < n; i++){
435 | arg = array[i];
436 | if (callback.call(scope, arg, i) === false) {
437 | return false;
438 | }
439 | }
440 | return true;
441 | },
442 | forEachTask: function(callback, scope){
443 | return this.forEach.apply(this, ["Project", "Tasks", "Task", callback, scope]);
444 | },
445 | forEachResource: function(callback, scope){
446 | return this.forEach.apply(this, ["Project", "Resources", "Resource", callback, scope]);
447 | },
448 | forEachAssignment: function(callback, scope){
449 | return this.forEach.apply(this, ["Project", "Assignements", "Assignment", callback, scope]);
450 | }
451 | };
452 |
453 | MspDocument.prototype.parseDate = (isNaN(Date.parse("1970-01-01T00:00:00")) ? function(value){
454 | var re = /^(\d\d\d\d)-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)$/;
455 | var match = re.exec(value);
456 | if (match === null) {
457 | return NaN;
458 | }
459 | return Date.UTC(
460 | parseInt(match[1], 10),
461 | parseInt(match[2], 10) - 1,
462 | parseInt(match[3], 10),
463 | parseInt(match[4], 10),
464 | parseInt(match[5], 10),
465 | parseInt(match[6], 10)
466 | );
467 | } : function(value) {
468 | return Date.parse(value);
469 | }
470 | );
471 |
472 | })();
--------------------------------------------------------------------------------
/src/css/ganttchart.css:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Copyright 2014 Roland Bouman (roland.bouman@gmail.com)
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 |
17 | */
18 |
19 | div.gant-t-chart-splitpane {
20 | }
21 |
22 | div.gant-t-chart-contentpane {
23 | overflow: hidden;
24 | }
25 |
26 | div.gant-t-chart-contentpane table {
27 | border-collapse: collapse;
28 | }
29 |
30 | tr.task > td, tr.task > th {
31 | white-space: nowrap;
32 | padding-top: 4px;
33 | padding-bottom: 4px;
34 | padding-right: 2px;
35 | }
36 |
37 | tr.task > td {
38 | border-color: whitesmoke;
39 | border-bottom-width: 1px;
40 | border-bottom-style: solid;
41 | }
42 |
43 | /**
44 | * TASK PANE
45 | **/
46 | div.gant-t-chart-taskpane {
47 |
48 | }
49 |
50 | div.gant-t-chart-taskpane-tasks-header {
51 | position: absolute;
52 | top: 0px;
53 | height: 41px;
54 | padding: 2px;
55 | vertical-align: middle;
56 | border-style: solid;
57 | border-width: 1px;
58 | border-color: whitesmoke;
59 | background-color: #dceef9;
60 | z-index: 10;
61 | }
62 |
63 | div.gant-t-chart-taskpane-tasks {
64 | position: absolute;
65 | top: 47px;
66 | }
67 |
68 | table.gant-t-chart-taskpane-tasks-table {
69 | }
70 |
71 | table.gant-t-chart-taskpane-tasks-table > tbody > tr.task > td,
72 | table.gant-t-chart-taskpane-attributes-table > tbody > tr.task > td {
73 | padding-left: .5em;
74 | padding-right: .5em;
75 | }
76 |
77 | table.gant-t-chart-taskpane-tasks-table > tbody > tr.task-Summary1,
78 | table.gant-t-chart-taskpane-attributes-table > tbody > tr.task-Summary1 {
79 | font-weight: bold;
80 | }
81 |
82 | tr.task > td.task-identity {
83 | text-align: right;
84 | border-style: none solid solid solid;
85 | border-width: 1px;
86 | }
87 |
88 | /* suppress bold for identity cell (MSP does, we do too) */
89 | table.gant-t-chart-taskpane-tasks-table > tbody > tr.task-Summary1 > td.task-identity {
90 | font-weight: normal;
91 | }
92 |
93 | tr.task > td.task-label > span.task-toggle {
94 | position: absolute;
95 | width: 16px;
96 | height: 16px;
97 | margin-left: -14px;
98 | }
99 |
100 | tr.task-Summary0 > td.task-label > span.task-toggle {
101 | cursor: default;
102 | }
103 |
104 | tr.task-Summary1 > td.task-label > span.task-toggle {
105 | cursor: pointer;
106 | }
107 |
108 | tr.task-expanded > td.task-label > span.task-toggle {
109 | background-image: url("../images/toggle-small-icon.png");
110 | }
111 |
112 | tr.task-collapsed > td.task-label > span.task-toggle {
113 | background-image: url("../images/toggle-small-expand-icon.png");
114 | }
115 |
116 | tr.task > td.task-label {
117 | }
118 |
119 | tr.task > td.task-label > span.task-wbs,
120 | tr.task > td.task-label > span.task-outlinenumber {
121 | padding-left: .5em;
122 | padding-right: .5em;
123 | }
124 |
125 | div.gant-t-chart-taskpane-attributes-header {
126 | position: absolute;
127 | height: 45px;
128 | background-color: #dceef9;
129 | z-index: 5;
130 | border-color: whitesmoke;
131 | border-bottom-style: solid;
132 | border-bottom-width: 1px;
133 | border-top-style: solid;
134 | border-top-width: 1px;
135 | padding: -1px;
136 | }
137 |
138 | div.gant-t-chart-taskpane-attributes-header > div.gant-t-chart-taskpane-attributes-header-item {
139 | position: absolute;
140 | top: 0px;
141 | bottom: 0px;
142 | padding: 2px;
143 | border-right-style: solid;
144 | border-right-width: 1px;
145 | border-right-color: whitesmoke;
146 | background-color: #dceef9;
147 | }
148 |
149 | div.gant-t-chart-taskpane-attributes {
150 | position: absolute;
151 | overflow-x: auto;
152 | overflow-y: hidden;
153 | top: 47px;
154 | bottom: 0px;
155 | border-style: none none none solid;
156 | border-width: 1px;
157 | border-color: whitesmoke;
158 | }
159 |
160 | table.gant-t-chart-taskpane-attributes-table > tbody > tr > td {
161 | border-right-style: solid;
162 | border-right-width: 1px;
163 | border-right-color: whitesmoke;
164 | white-space: nowrap;
165 | padding-left: 2px;
166 | padding-right: 2px;
167 | }
168 |
169 | /**
170 | * CALENDAR PANE
171 | **/
172 | div.gant-t-chart-calendarpane {
173 | }
174 |
175 | .gant-t-chart-calendarpane-header {
176 | position: absolute;
177 | top: 0px;
178 | left: 0px;
179 | right: 0px;
180 | bottom: 0px;
181 | }
182 |
183 | div.gant-t-chart-calendarpane-body {
184 | position: absolute;
185 | top: 47px;
186 | bottom: 0px;
187 | z-index: 20;
188 | overflow-x: auto;
189 | overflow-y: auto;
190 | border-bottom-color: #cbdde8;
191 | border-bottom-style: solid;
192 | }
193 |
194 | table.gant-t-chart-calendarpane-body-table {
195 | border-style: none;
196 | border-width: 0px;
197 | }
198 |
199 | /* calender body */
200 | table.gant-t-chart-calendarpane-body-table > tbody > tr > td > div.task-bar {
201 | position: absolute;
202 | z-index: 2;
203 | }
204 |
205 | table.gant-t-chart-calendarpane-body-table > tbody > tr.task-HideBar1 > td > div.task-bar {
206 | visibility: hidden;
207 | }
208 |
209 | table.gant-t-chart-calendarpane-body-table > tbody > tr > td > div.task-bar > div.task-bar-left {
210 | position:absolute;
211 | top: 0px;
212 | left: -1px;
213 | }
214 |
215 | table.gant-t-chart-calendarpane-body-table > tbody > tr > td > div.task-bar > div.task-bar-right {
216 | position:absolute;
217 | top: 0px;
218 | right: -1px;
219 | }
220 |
221 | table.gant-t-chart-calendarpane-body-table > tbody > tr > td > div.task-bar > div.task-bar-pct-complete,
222 | table.gant-t-chart-calendarpane-body-table > tbody > tr > td > div.task-bar > div.task-bar-pct-work-complete,
223 | table.gant-t-chart-calendarpane-body-table > tbody > tr > td > div.task-bar > div.task-bar-physical-pct-complete {
224 | position:absolute;
225 | left: 0px;
226 | height: 3px;
227 | }
228 |
229 | table.gant-t-chart-calendarpane-body-table > tbody > tr > td > div.task-bar > div.task-bar-pct-complete {
230 | top: 0px;
231 | background-color: red;
232 | }
233 | table.gant-t-chart-calendarpane-body-table > tbody > tr > td > div.task-bar > div.task-bar-pct-work-complete {
234 | top: 3px;
235 | background-color: green;
236 | }
237 | table.gant-t-chart-calendarpane-body-table > tbody > tr > td > div.task-bar > div.task-bar-physical-pct-complete {
238 | top: 6px;
239 | background-color: blue;
240 | }
241 |
242 | /* not a summary task */
243 | table.gant-t-chart-calendarpane-body-table > tbody > tr.task-Summary0 > td > div.task-bar {
244 | margin-top: 2px;
245 | border-width: 1px;
246 | border-style: solid;
247 | height: 9px;
248 | }
249 |
250 | /* not a critical task */
251 | table.gant-t-chart-calendarpane-body-table > tbody > tr.task-Critical0 > td > div.task-bar {
252 | border-color: blue;
253 | background-color: skyblue;
254 | }
255 |
256 | /* critical task */
257 | table.gant-t-chart-calendarpane-body-table > tbody > tr.task-Critical1 > td > div.task-bar {
258 | border-color: red;
259 | background-color: pink;
260 | }
261 |
262 | /* summary task */
263 | table.gant-t-chart-calendarpane-body-table > tbody > tr.task-Summary1 > td > div.task-bar {
264 | margin-top: 2px;
265 | border-width: 1px;
266 | border-style: solid;
267 | height: 4px;
268 | border-color: maroon;
269 | background-color: maroon;
270 | }
271 |
272 | table.gant-t-chart-calendarpane-body-table > tbody > tr.task-Summary1 > td > div.task-bar > div.task-bar-left {
273 | width: 0px;
274 | height: 0px;
275 | border-top: 5px solid transparent;
276 | border-bottom: 5px solid transparent;
277 | border-left: 5px solid maroon;
278 | }
279 |
280 | table.gant-t-chart-calendarpane-body-table > tbody > tr.task-Summary1 > td > div.task-bar > div.task-bar-right {
281 | width: 0px;
282 | height: 0px;
283 | border-top: 5px solid transparent;
284 | border-bottom: 5px solid transparent;
285 | border-right: 5px solid maroon;
286 | }
287 |
288 | /* milestone task */
289 | table.gant-t-chart-calendarpane-body-table > tbody > tr.task-Milestone1 > td > div.task-bar {
290 | border-color: transparent;
291 | }
292 |
293 | table.gant-t-chart-calendarpane-body-table > tbody > tr.task-Milestone1 > td > div.task-bar > div.task-bar-left {
294 | top: -2px;
295 | width: 0px;
296 | height: 0px;
297 | left: 0px;
298 | border-top: 7px solid transparent;
299 | border-bottom: 7px solid transparent;
300 | border-left: 7px solid blue;
301 | }
302 |
303 | table.gant-t-chart-calendarpane-body-table > tbody > tr.task-Milestone1 > td > div.task-bar > div.task-bar-right {
304 | top: -2px;
305 | width: 0px;
306 | height: 0px;
307 | right: 0px;
308 | border-top: 7px solid transparent;
309 | border-bottom: 7px solid transparent;
310 | border-right: 7px solid blue;
311 | }
312 |
313 | /* Null task */
314 | table.gant-t-chart-calendarpane-body-table > tbody > tr.task-IsNull1 > td > div.task-bar {
315 | }
316 |
317 | /* calendar headers */
318 | div.gant-t-calendar-header-item {
319 | position: absolute;
320 | bottom: 0px;
321 | }
322 |
323 | div.gant-t-calendar-header-item > div.label {
324 | position: absolute;
325 | height: 15px;
326 | top: 0px;
327 | left: 0px;
328 | right: 0px;
329 | border-style: solid;
330 | border-width: 1px;
331 | border-color: whitesmoke;
332 | }
333 |
334 | div.gant-t-calendar-header-year {
335 | top: 0px;
336 | border-right-width: 1px;
337 | border-right-style: solid;
338 | border-right-color: black;
339 | z-index: 2; /* ensures months don't block year boundary from showing */
340 | }
341 |
342 | div.gant-t-calendar-header-year > div.label {
343 | padding-left: 2px;
344 | }
345 |
346 | div.gant-t-calendar-header-year-odd > div.label{
347 | background-color: #dceef9;
348 | }
349 | div.gant-t-calendar-header-year-even > div.label {
350 | background-color: #baccd7;
351 | }
352 |
353 | div.gant-t-calendar-header-month {
354 | top: 15px;
355 | border-right-width: 1px;
356 | border-right-style: solid;
357 | border-right-color: darkgrey;
358 | z-index: 1; /* ensures days don't block month boundary from showing */
359 | }
360 |
361 | div.gant-t-calendar-header-month > div.label {
362 | padding-left: 2px;
363 | }
364 |
365 | div.gant-t-calendar-header-month-odd > div.label {
366 | background-color: #dceef9;
367 | }
368 | div.gant-t-calendar-header-month-even > div.label {
369 | background-color: #baccd7;
370 | }
371 |
372 | div.gant-t-calendar-header-day {
373 | top: 30px;
374 | }
375 |
376 | div.gant-t-calendar-header-day-weekstart {
377 | border-left-width: 1px;
378 | border-left-style: solid;
379 | border-left-color: lightgrey;
380 | }
381 |
382 | div.gant-t-calendar-header-day-workday {
383 | }
384 |
385 | div.gant-t-calendar-header-day-noworkday {
386 | border-width: 1px;
387 | border-style: none dotted none dotted;
388 | border-color: lightgrey;
389 | background-color: ghostwhite;
390 | }
391 |
392 | div.gant-t-calendar-header-day > div.label {
393 | border-style: solid none solid solid;
394 | background-color: white;
395 | }
396 |
397 | div.gant-t-calendar-gridline {
398 | position: absolute;
399 | top:0px;
400 | bottom:0px;
401 | width: 1px;
402 | height: 100%;
403 | background-color: lightgrey;
404 | }
405 |
406 | /* links */
407 | div.task-predecessor-link {
408 | position:absolute;
409 | overflow: visible;
410 | }
411 |
412 | /* FF (finish-to-finish) */
413 | div.task-predecessor-link-type0 {
414 | }
415 |
416 | /* FS (finish-to-start) */
417 | div.task-predecessor-link-type1 {
418 | }
419 |
420 | /* SF (start-to-finish) */
421 | div.task-predecessor-link-type2 {
422 | }
423 |
424 | /* SS (start-to-start) */
425 | div.task-predecessor-link-type3 {
426 | }
427 |
428 | div.task-predecessor-link > div.task-predecessor-link-segment {
429 | position:absolute;
430 | }
431 |
432 | /* div.task-predecessor-link-type0-below-left */
433 | div.task-predecessor-link-type0-below-left > div.task-predecessor-link-segment1 {
434 | background-color:black;
435 | height: 1px;
436 | width: 10px;
437 | right: -10px;
438 | }
439 | div.task-predecessor-link-type0-below-left > div.task-predecessor-link-segment2 {
440 | background-color:black;
441 | width: 1px;
442 | right: -11px;
443 | top: 0px;
444 | height: 11px;
445 | }
446 | div.task-predecessor-link-type0-below-left > div.task-predecessor-link-segment3 {
447 | background-color:black;
448 | height:1px;
449 | right: -10px;
450 | top: 10px;
451 | left: 0px;
452 | right: -10px;
453 | }
454 | div.task-predecessor-link-type0-below-left > div.task-predecessor-link-segment4 {
455 | background-color:black;
456 | top: 10px;
457 | width: 1px;
458 | left: 0px;
459 | bottom: 5px;
460 | }
461 | div.task-predecessor-link-type0-below-left > div.task-predecessor-link-segment5 {
462 | width: 0;
463 | height: 0;
464 | border-left: 5px solid transparent;
465 | border-right: 5px solid transparent;
466 | border-top: 5px solid black;
467 | bottom: 5px;
468 | left: -4px;
469 | }
470 |
471 | /* div.task-predecessor-link-type0-below-right */
472 | div.task-predecessor-link-type0-below-right > div.task-predecessor-link-segment1 {
473 | background-color:black;
474 | height: 1px;
475 | width: 8px;
476 | left: 0px;
477 | }
478 | div.task-predecessor-link-type0-below-right > div.task-predecessor-link-segment2 {
479 | background-color:black;
480 | width: 1px;
481 | left: 8px;
482 | top: 0px;
483 | height: 10px;
484 | }
485 | div.task-predecessor-link-type0-below-right > div.task-predecessor-link-segment3 {
486 | background-color:black;
487 | height: 1px;
488 | top: 10px;
489 | left: 8px;
490 | right: 0px;
491 | }
492 | div.task-predecessor-link-type0-below-right > div.task-predecessor-link-segment4 {
493 | background-color:black;
494 | top: 10px;
495 | width: 1px;
496 | right: 0px;
497 | bottom: 5px;
498 | }
499 | div.task-predecessor-link-type0-below-right > div.task-predecessor-link-segment5 {
500 | width: 0px;
501 | height: 0px;
502 | border-left: 5px solid transparent;
503 | border-right: 5px solid transparent;
504 | border-top: 5px solid black;
505 | bottom: 5px;
506 | right: -4px;
507 | }
508 |
509 | /* div.task-predecessor-link-type1-below-left */
510 | div.task-predecessor-link-type1-below-left > div.task-predecessor-link-segment1 {
511 | background-color:black;
512 | height: 1px;
513 | width: 10px;
514 | right: -10px;
515 | }
516 | div.task-predecessor-link-type1-below-left > div.task-predecessor-link-segment2 {
517 | background-color:black;
518 | width: 1px;
519 | right: -11px;
520 | top: 0px;
521 | height: 10px;
522 | }
523 | div.task-predecessor-link-type1-below-left > div.task-predecessor-link-segment3 {
524 | background-color:black;
525 | height: 1px;
526 | top: 10px;
527 | left: 0px;
528 | right: -10px;
529 | }
530 | div.task-predecessor-link-type1-below-left > div.task-predecessor-link-segment4 {
531 | background-color:black;
532 | top: 10px;
533 | width: 1px;
534 | left: 0px;
535 | bottom: 5px;
536 | }
537 | div.task-predecessor-link-type1-below-left > div.task-predecessor-link-segment5 {
538 | width: 0px;
539 | height: 0px;
540 | border-left: 5px solid transparent;
541 | border-right: 5px solid transparent;
542 | border-top: 5px solid black;
543 | bottom: 5px;
544 | left: -4px;
545 | }
546 |
547 | /* div.task-predecessor-link-type1-below-right */
548 | div.task-predecessor-link-type1-below-right > div.task-predecessor-link-segment1 {
549 | background-color:black;
550 | height: 1px;
551 | width: 8px;
552 | left: 0px;
553 | }
554 | div.task-predecessor-link-type1-below-right > div.task-predecessor-link-segment2 {
555 | background-color:black;
556 | width: 1px;
557 | left: 8px;
558 | top: 0px;
559 | height: 10px;
560 | }
561 | div.task-predecessor-link-type1-below-right > div.task-predecessor-link-segment3 {
562 | background-color:black;
563 | height: 1px;
564 | top: 10px;
565 | left: 8px;
566 | right: 0px;
567 | }
568 | div.task-predecessor-link-type1-below-right > div.task-predecessor-link-segment4 {
569 | background-color:black;
570 | top: 10px;
571 | width: 1px;
572 | right: 0px;
573 | bottom: 5px;
574 | }
575 | div.task-predecessor-link-type1-below-right > div.task-predecessor-link-segment5 {
576 | width: 0px;
577 | height: 0px;
578 | border-left: 5px solid transparent;
579 | border-right: 5px solid transparent;
580 | border-top: 5px solid black;
581 |
582 | bottom: 5px;
583 | right: -4px;
584 | }
585 |
586 | /* div.task-predecessor-link-type2-below-left */
587 | div.task-predecessor-link-type2-below-left > div.task-predecessor-link-segment1 {
588 | background-color:black;
589 | height: 1px;
590 | width: 10px;
591 | right: 0px;
592 | }
593 | div.task-predecessor-link-type2-below-left > div.task-predecessor-link-segment2 {
594 | background-color:black;
595 | width: 1px;
596 | right: 10px;
597 | top: 0px;
598 | height: 10px;
599 | }
600 | div.task-predecessor-link-type2-below-left > div.task-predecessor-link-segment3 {
601 | background-color:black;
602 | height: 1px;
603 | top: 10px;
604 | left: 0px;
605 | right: 10px;
606 | }
607 | div.task-predecessor-link-type2-below-left > div.task-predecessor-link-segment4 {
608 | background-color:black;
609 | top: 10px;
610 | width: 1px;
611 | left: 0px;
612 | bottom: 5px;
613 | }
614 | div.task-predecessor-link-type2-below-left > div.task-predecessor-link-segment5 {
615 | width: 0;
616 | height: 0;
617 | border-left: 5px solid transparent;
618 | border-right: 5px solid transparent;
619 | border-top: 5px solid black;
620 | bottom: 5px;
621 | left: -4px;
622 | }
623 |
624 | /* div.task-predecessor-link-type2-below-right */
625 | div.task-predecessor-link-type2-below-right > div.task-predecessor-link-segment1 {
626 | background-color:black;
627 | height: 1px;
628 | width: 11px;
629 | left: -13px;
630 | }
631 | div.task-predecessor-link-type2-below-right > div.task-predecessor-link-segment2 {
632 | background-color:black;
633 | width: 1px;
634 | left: -13px;
635 | top: 0px;
636 | height: 10px;
637 | }
638 | div.task-predecessor-link-type2-below-right > div.task-predecessor-link-segment3 {
639 | background-color:black;
640 | height: 1px;
641 | top: 10px;
642 | left: -13px;
643 | right: 0px;
644 | }
645 | div.task-predecessor-link-type2-below-right > div.task-predecessor-link-segment4 {
646 | background-color:black;
647 | top: 10px;
648 | width: 1px;
649 | right: 0px;
650 | bottom: 5px;
651 | }
652 | div.task-predecessor-link-type2-below-right > div.task-predecessor-link-segment5 {
653 | width: 0px;
654 | height: 0px;
655 | border-left: 5px solid transparent;
656 | border-right: 5px solid transparent;
657 | border-top: 5px solid black;
658 | bottom: 5px;
659 | right: -4px;
660 | }
661 |
662 | /* div.task-predecessor-link-type3-below-left */
663 | div.task-predecessor-link-type3-below-left > div.task-predecessor-link-segment1 {
664 | background-color:black;
665 | height: 1px;
666 | width: 10px;
667 | right: 0px;
668 | }
669 | div.task-predecessor-link-type3-below-left > div.task-predecessor-link-segment2 {
670 | background-color:black;
671 | width: 1px;
672 | right: 10px;
673 | top: 0px;
674 | height: 10px;
675 | }
676 | div.task-predecessor-link-type3-below-left > div.task-predecessor-link-segment3 {
677 | background-color:black;
678 | height: 1px;
679 | top: 10px;
680 | left: 0px;
681 | right: 10px;
682 | }
683 | div.task-predecessor-link-type3-below-left > div.task-predecessor-link-segment4 {
684 | background-color:black;
685 | top: 10px;
686 | width: 1px;
687 | left: 0px;
688 | bottom: 5px;
689 | }
690 | div.task-predecessor-link-type3-below-left > div.task-predecessor-link-segment5 {
691 | width: 0;
692 | height: 0;
693 | border-left: 5px solid transparent;
694 | border-right: 5px solid transparent;
695 | border-top: 5px solid black;
696 | bottom: 5px;
697 | left: -4px;
698 | }
699 |
700 | /* div.task-predecessor-link-type3-below-right */
701 | div.task-predecessor-link-type3-below-right > div.task-predecessor-link-segment1 {
702 | background-color:black;
703 | height: 1px;
704 | width: 10px;
705 | left: -13px;
706 | }
707 | div.task-predecessor-link-type3-below-right > div.task-predecessor-link-segment2 {
708 | background-color:black;
709 | width: 1px;
710 | left: -13px;
711 | top: 0px;
712 | height: 10px;
713 | }
714 | div.task-predecessor-link-type3-below-right > div.task-predecessor-link-segment3 {
715 | background-color:black;
716 | height: 1px;
717 | top: 10px;
718 | left: -12px;
719 | right: 0px;
720 | }
721 | div.task-predecessor-link-type3-below-right > div.task-predecessor-link-segment4 {
722 | background-color:black;
723 | top: 10px;
724 | width: 1px;
725 | right: 0px;
726 | bottom: 5px;
727 | }
728 | div.task-predecessor-link-type3-below-right > div.task-predecessor-link-segment5 {
729 | width: 0;
730 | height: 0;
731 | border-left: 5px solid transparent;
732 | border-right: 5px solid transparent;
733 | border-top: 5px solid black;
734 |
735 | bottom: 5px;
736 | right: -4px;
737 | }
738 | /*
739 | * Gant t resources
740 | **/
741 | table.gant-t-chart-calendarpane-body-table > tbody > tr > td > ul.task-bar-resources {
742 | display: inline;
743 | position: absolute;
744 | list-style: none;
745 | margin: 0px;
746 | padding-left: 13px;
747 | }
748 |
749 | table.gant-t-chart-calendarpane-body-table > tbody > tr > td > ul.task-bar-resources > li.task-bar-resource {
750 | display: inline;
751 | }
752 |
753 | /*
754 | * Separate resources by semi-colon.
755 | * MSP uses a comma as separator, but since IE8 will show a trailing separator, we use a semi-colon.
756 | */
757 | table.gant-t-chart-calendarpane-body-table > tbody > tr > td > ul.task-bar-resources > li.task-bar-resource:after {
758 | content: "; ";
759 | }
760 |
761 | table.gant-t-chart-calendarpane-body-table > tbody > tr > td > ul.task-bar-resources > li.task-bar-resource:last-child::after {
762 | content: "";
763 | }
764 |
765 | table.gant-t-chart-calendarpane-body-table > tbody > tr > td > ul.task-bar-resources > li.task-bar-resource-fulltime > span.task-bar-resource-units-pct {
766 | display: none;
767 | }
768 |
769 | table.gant-t-chart-calendarpane-body-table > tbody > tr > td > ul.task-bar-resources > li.task-bar-resource > span.task-bar-resource-units-pct:before {
770 | content: " ";
771 | }
772 |
773 | table.gant-t-chart-calendarpane-body-table > tbody > tr > td > ul.task-bar-resources > li.task-bar-resource > span.task-bar-resource-units-pct:after {
774 | content: "%";
775 | }
776 |
777 | /*
778 | * task bar finish
779 | */
780 | table.gant-t-chart-calendarpane-body-table > tbody > tr.task-Milestone0 > td > div.task-bar-finish {
781 | display: none;
782 | }
783 | table.gant-t-chart-calendarpane-body-table > tbody > tr.task-Milestone1 > td > div.task-bar-finish {
784 | position: absolute;
785 | padding-left: 13px;
786 | }
--------------------------------------------------------------------------------
/src/js/ganttchart.js:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Copyright 2014 Roland Bouman (roland.bouman@gmail.com)
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 |
17 | */
18 |
19 | var GantTChart;
20 |
21 | (function(){
22 |
23 | var resolutionMillis = {
24 | "year": 366 * 24 * 60 * 60 * 1000,
25 | "month": 31 * 24 * 60 * 60 * 1000,
26 | "week": 7 * 24 * 60 * 60 * 1000,
27 | "day": 1 * 24 * 60 * 60 * 1000
28 | };
29 |
30 | cEl("div", {
31 | id: "_scrollbars1",
32 | style: {
33 | "background-color": "blue",
34 | position: "absolute",
35 | overflow: "auto",
36 | width: "50px",
37 | height: "50px",
38 | left: "-50px",
39 | top: "-50px"
40 | }
41 | }, cEl("div", {
42 | id: "_scrollbars2",
43 | style: {
44 | "background-color": "red",
45 | position: "absolute",
46 | left: "0px",
47 | top: "0px",
48 | width: "100px",
49 | height: "100px"
50 | }
51 | }), body);
52 | var _scrollbars1 = gEl("_scrollbars1");
53 | var scrollbarWidth = (_scrollbars1.offsetWidth - _scrollbars1.clientWidth) + 2;
54 | var scrollbarHeight = (_scrollbars1.offsetHeight - _scrollbars1.clientHeight) + 2;
55 |
56 | (GantTChartComponent = function(conf){
57 | ContentPane.call(this, conf);
58 | }).prototype = {
59 | setGantTChart: function(gantTChart){
60 | this.gantTChart = gantTChart;
61 | },
62 | getGantTChart: function(){
63 | return this.gantTChart;
64 | },
65 | getTaskClassName: function(task) {
66 | var className = "task";
67 | className += " task-Type" + task.Type;
68 | className += " task-IsNull" + task.IsNull;
69 | className += " task-ResumeValid" + task.ResumeValid;
70 | className += " task-EffortDriven" + task.EffortDriven;
71 | className += " task-Recurring" + task.Recurring;
72 | className += " task-OverAllocated" + task.OverAllocated;
73 | className += " task-Estimated" + task.Estimated;
74 | className += " task-Milestone" + task.Milestone;
75 | className += " task-Summary" + task.Summary;
76 | className += " task-Critical" + task.Critical;
77 | className += " task-IsSubproject" + task.IsSubproject;
78 | className += " task-IsSubprojectReadOnly" + task.IsSubprojectReadOnly;
79 | className += " task-ExternalTask" + task.ExternalTask;
80 | className += " task-LevelAssignments" + task.LevelAssignments;
81 | className += " task-LevelingCanSplit" + task.LevelingCanSplit;
82 | className += " task-IgnoreResourceCalendar" + task.IgnoreResourceCalendar;
83 | className += " task-HideBar" + task.HideBar;
84 | className += " task-Rollup" + task.Rollup;
85 | className += " task-IsPublished" + (task.IsPublished || "");
86 | className += " task-PercentComplete" + task.PercentComplete;
87 | return className;
88 | }
89 | };
90 | adopt(GantTChartComponent, ContentPane);
91 |
92 | (GantTCalendarPane = function(conf){
93 | if (!conf) conf = {};
94 | if (!conf.classes) conf.classes = [];
95 | conf.classes = conf.classes.concat([
96 | "contentpane-noselect",
97 | GantTChart.prefix + "-contentpane",
98 | GantTChart.prefix + "-calendarpane"
99 | ]);
100 | GantTChartComponent.call(this, conf);
101 | }).prototype = {
102 | createHeaderDom: function(el) {
103 | this.header = cEl("div", {
104 | "class": GantTChart.prefix + "-calendarpane-header"
105 | }, null, el);
106 | },
107 | createBodyDom: function(el) {
108 | this.bodyTable = cEl("table", {
109 | "class": GantTChart.prefix + "-calendarpane-body-table",
110 | cellpadding: 0,
111 | cellspacing: 0
112 | });
113 | this.body = cEl("div", {
114 | "class": GantTChart.prefix + "-calendarpane-body",
115 | style: {
116 | "border-bottom-width": scrollbarHeight + "px",
117 | "overflow-x": this.getCalendarResolution() === "project" ? "hidden" : "auto"
118 | }
119 | }, this.bodyTable, el);
120 | listen(this.body, "scroll", this.scrollHandler, this)
121 | },
122 | createDom: function(){
123 | var el = ContentPane.prototype.createDom.call(this);
124 | this.createHeaderDom(el);
125 | this.createBodyDom(el);
126 | return el;
127 | },
128 | scrollHandler: function(event){
129 | var target = event.getTarget();
130 | var gantTChart = this.getGantTChart();
131 | gantTChart.scrolled(target.scrollLeft, target.scrollTop);
132 | },
133 | scroll: function(scrollLeft, scrollTop){
134 | var header = this.getHeader();
135 | header.style.left = (-scrollLeft) + "px";
136 | },
137 | createTaskLink: function(body, linkId){
138 | return cEl("div", {
139 | id: linkId
140 | }, [
141 | cEl("div", {
142 | id: linkId + "-segment1",
143 | "class": "task-predecessor-link-segment" +
144 | " task-predecessor-link-segment1"
145 | }),
146 | cEl("div", {
147 | id: linkId + "-segment2",
148 | "class": "task-predecessor-link-segment" +
149 | " task-predecessor-link-segment2"
150 | }),
151 | cEl("div", {
152 | id: linkId + "-segment3",
153 | "class": "task-predecessor-link-segment" +
154 | " task-predecessor-link-segment3"
155 | }),
156 | cEl("div", {
157 | id: linkId + "-segment4",
158 | "class": "task-predecessor-link-segment" +
159 | " task-predecessor-link-segment4"
160 | }),
161 | cEl("div", {
162 | id: linkId + "-segment5",
163 | "class": "task-predecessor-link-segment" +
164 | " task-predecessor-link-segment5"
165 | })
166 | ], body);
167 | },
168 | renderTaskLinks: function(mspDocument, projectDatesAndDimensions, task, taskStart, taskFinish, container, row, rowDisplayed, rowTop){
169 | mspDocument.forEachTaskLink(task, function(link, index){
170 | var isNew;
171 | var linkId = "link-task-" + task.UID + "-predecessor-task-" + link.PredecessorUID;
172 | var linkEl = gEl(linkId);
173 | if (linkEl) {
174 | isNew = false;
175 | }
176 | else {
177 | isNew = true;
178 | linkEl = this.createTaskLink(container, linkId);
179 | }
180 | if (!rowDisplayed) {
181 | linkEl.style.display = "none";
182 | return;
183 | }
184 | var predecessorRow = gEl("calendarpane-taskUID-" + link.PredecessorUID);
185 | if (!predecessorRow) {
186 | console.log("Whoops, predecessor not found.");
187 | return;
188 | }
189 | if (predecessorRow.style.display === "none") {
190 | linkEl.style.display = "none";
191 | return;
192 | }
193 | else {
194 | linkEl.style.display = "";
195 | }
196 |
197 | var linkType = parseInt(link.Type, 10);
198 |
199 | //is task below or above its predecessor?
200 | var aboveBelow = row.rowIndex > predecessorRow.rowIndex ? "below" : "above";
201 |
202 | var from;
203 | switch (linkType) {
204 | case 0: //FF (finish-to-finish)
205 | case 1: //FS (finish-to-start)
206 | from = parseInt(gAtt(predecessorRow, "data-finish"), 10);
207 | break;
208 | case 2: //SF (start-to-finish)
209 | case 3: //SS (start-to-start)
210 | from = parseInt(gAtt(predecessorRow, "data-start"), 10);
211 | break;
212 | default:
213 | }
214 |
215 | var to;
216 | switch (linkType) {
217 | case 0: //FF (finish-to-finish)
218 | case 2: //SF (start-to-finish)
219 | to = taskFinish;
220 | break;
221 | case 1: //FS (finish-to-start)
222 | case 3: //SS (start-to-start)
223 | to = taskStart;
224 | break;
225 | default:
226 | }
227 |
228 | var predecessorRowTop = predecessorRow.offsetTop;
229 | var height = Math.abs(rowTop - predecessorRowTop);
230 | linkEl.style.height = height + "px";
231 | var top = Math.min(rowTop, predecessorRowTop);
232 | linkEl.style.top = parseInt(row.offsetHeight/2 + top, 10) + "px";
233 | var left, width;
234 |
235 | var xFrom, xTo, diff;
236 | diff = from - projectDatesAndDimensions.startTime;
237 | xFrom = parseInt(projectDatesAndDimensions.width / projectDatesAndDimensions.screenMillis * diff, 10);
238 |
239 | diff = to - projectDatesAndDimensions.startTime;
240 | xTo = parseInt(projectDatesAndDimensions.width / projectDatesAndDimensions.screenMillis * diff, 10);
241 |
242 | var width = Math.abs(xTo - xFrom);
243 | if (from < to) {
244 | //task is right from its predecessor
245 | leftRight = "right";
246 | linkEl.style.left = (xFrom + 2) + "px";
247 | linkEl.style.width = width + "px";
248 | if (width < 9) {
249 | // linkEl.childNodes[2].style.left = width + "px";
250 | // linkEl.childNodes[2].style.right = (width - 9) + "px";
251 | }
252 | }
253 | else {
254 | //task is left from its predecessor
255 | leftRight = "left";
256 | linkEl.style.left = xTo + "px";
257 | linkEl.style.width = width + "px";
258 | if (width < 9) {
259 | // linkEl.childNodes[2].style.left = (width - 9) + "px";
260 | // linkEl.childNodes[2].style.left = width + "px";
261 | }
262 | }
263 | if (isNew) {
264 | linkEl.className = "task-predecessor-link" +
265 | " task-predecessor-link-type" + linkType +
266 | " task-predecessor-link-" + aboveBelow +
267 | " task-predecessor-link-" + leftRight +
268 | " task-predecessor-link-type" +
269 | linkType + "-" + aboveBelow + "-" + leftRight
270 | ;
271 | }
272 | }, this);
273 | },
274 | renderTasks: function(){
275 | var gantTChart = this.getGantTChart();
276 | var mspDocument = gantTChart.getDocument();
277 | if (!mspDocument) return;
278 | var clientWidth = this.getDom().offsetWidth - scrollbarWidth;
279 | var dontSuppressProjectSummaryTask = gantTChart.dontSuppressProjectSummaryTask();
280 | var projectDatesAndDimensions = this.getProjectDatesAndDimensions();
281 |
282 | var body = this.getBody();
283 | var bodyTable = this.getBodyTable();
284 | if (!bodyTable) return;
285 | var rows = bodyTable.rows;
286 | var rowIndex = 0;
287 | var rowTops = {};
288 | function getRowTop(row){
289 | var rowTop = rowTops[row.rowIndex];
290 | if (typeof(rowTop) === "undefined") {
291 | rowTop = row.offsetTop;
292 | rowTops[row.rowIndex] = rowTop;
293 | }
294 | return rowTop;
295 | }
296 |
297 | mspDocument.forEachTask(function(task, index){
298 | if (index === 0 && !dontSuppressProjectSummaryTask) return;
299 | var row = rows[rowIndex++];
300 | var rowDisplayed;
301 | if (row.style.display === "none") {
302 | rowDisplayed = false;
303 | }
304 | else {
305 | rowDisplayed = true;
306 | var cell = row.cells[0];
307 | var taskDiv = cell.firstChild;
308 |
309 | if (task.IsNull === "1") {
310 | taskDiv.style.width = 0 + "px";
311 | return;
312 | }
313 |
314 | var taskStart = mspDocument.parseDate(task.Start);
315 | var taskFinish = mspDocument.parseDate(task.Finish);
316 | var diff, w, l, style = taskDiv.style;
317 | diff = taskStart - projectDatesAndDimensions.startTime;
318 | l = parseInt(projectDatesAndDimensions.width / projectDatesAndDimensions.screenMillis * diff, 10);
319 | diff = taskFinish - projectDatesAndDimensions.startTime;
320 | w = parseInt(projectDatesAndDimensions.width / projectDatesAndDimensions.screenMillis * diff, 10);
321 | style.left = l + "px";
322 | style.width = (task.IsNull === "1" ? 0 : (w-l)) + "px";
323 |
324 | var resourcesDiv = taskDiv.nextSibling;
325 | resourcesDiv.style.left = (l + (w - l)) + "px";
326 |
327 | //var posRow = pos(row, body);
328 | var rowTop = row.offsetTop;
329 | }
330 | this.renderTaskLinks(
331 | mspDocument, projectDatesAndDimensions,
332 | task, taskStart, taskFinish,
333 | body,
334 | row, rowDisplayed, rowTop
335 | );
336 | }, this);
337 | },
338 | getProjectDatesAndDimensions: function(){
339 | var gantTChart = this.getGantTChart();
340 | var mspDocument = gantTChart.getDocument();
341 | if (!mspDocument) {
342 | return null;
343 | }
344 | var boundaryDates = mspDocument.getBoundaryDates();
345 | var startDate = truncateDate(boundaryDates.minDate, "day");
346 | var finishDate = truncateDate(dateAdd(boundaryDates.maxDate, {days: 1}), "day");
347 |
348 | var ret = {
349 | width: this.getDom().offsetWidth - scrollbarWidth,
350 | startDate: startDate,
351 | startTime: startDate.getTime(),
352 | finishDate: finishDate,
353 | finishTime: finishDate.getTime(),
354 | };
355 | ret.projectMillis = ret.finishTime - ret.startTime;
356 | var screenMillis, resolution = this.getCalendarResolution();
357 | switch (resolution) {
358 | case "project":
359 | screenMillis = ret.projectMillis;
360 | //TODO: calculate extra time to account for space required by links.
361 | var extra = parseInt(30 * (screenMillis/ret.width), 10); //10 is extra width
362 | ret.startTime -= extra;
363 | ret.startDate = truncateDate(new Date(ret.startTime), "day");
364 | ret.startTime = ret.startDate.getTime();
365 |
366 | ret.finishTime += extra;
367 | ret.finishDate = truncateDate(dateAdd(new Date(ret.finishTime), {days: 1}), "day");
368 | ret.finishTime = ret.finishDate.getTime();
369 |
370 | screenMillis = ret.finishTime - ret.startTime;
371 |
372 | if (screenMillis > resolutionMillis.month) {
373 | resolution = "year";
374 | }
375 | else
376 | if (screenMillis > resolutionMillis.week) {
377 | resolution = "month";
378 | }
379 | else
380 | if (screenMillis > resolutionMillis.day) {
381 | resolution = "week";
382 | }
383 | else {
384 | resolution = "day";
385 | }
386 | break;
387 | case "year":
388 | case "month":
389 | case "week":
390 | case "day":
391 | screenMillis = resolutionMillis[resolution];
392 | break;
393 | }
394 | ret.resolution = resolution;
395 | ret.screenMillis = screenMillis;
396 | return ret;
397 | },
398 | renderHeader: function(){
399 | var gantTChart = this.getGantTChart();
400 | var mspDocument = gantTChart.getDocument();
401 | if (!mspDocument) return;
402 | var baseCalendar = mspDocument.getBaseCalendar(),
403 | days = baseCalendar.WeekDays.WeekDay, day,
404 | weekStartDay = mspDocument.getWeekStartDay(),
405 | nonWorkingDays = {}
406 | ;
407 | if (!iArr(days)) days = [days];
408 | for (var i = 0; i < days.length; i++) {
409 | day = days[i];
410 | if (day.DayWorking === "0") {
411 | nonWorkingDays[parseInt(day.DayType, 10) - 1] = true;
412 | }
413 | }
414 | var projectDatesAndDimensions = this.getProjectDatesAndDimensions();
415 | var header = this.getHeader();
416 |
417 | var res, exitLoop = false, date, dateAddition, nextDate, num, label, l, w, diff, headerEl, baseClassName, className, isNonWorkingDay, id, year, month, day, color;
418 | for (res in resolutionMillis) {
419 | if (res === "week") continue;
420 | date = projectDatesAndDimensions.startDate;
421 | dateAddition = {};
422 | dateAddition[res + "s"] = 1;
423 | baseClassName = "gant-t-calendar-header-" + res;
424 | while (date.getTime() < projectDatesAndDimensions.finishTime) {
425 | nextDate = dateAdd(date, dateAddition);
426 | nextDate = truncateDate(nextDate, res);
427 | if (nextDate.getTime() === date.getTime()){
428 | alert("Whoops, our date truncation messed up. Bailing");
429 | return;
430 | }
431 | if (nextDate.getTime() > projectDatesAndDimensions.finishTime) {
432 | nextDate = projectDatesAndDimensions.finishDate;
433 | }
434 | diff = date.getTime() - projectDatesAndDimensions.startTime;
435 | l = parseInt(projectDatesAndDimensions.width / projectDatesAndDimensions.screenMillis * diff, 10);
436 | diff = nextDate.getTime() - projectDatesAndDimensions.startTime;
437 | w = parseInt(projectDatesAndDimensions.width / projectDatesAndDimensions.screenMillis * diff, 10);
438 | year = date.getUTCFullYear();
439 | month = date.getUTCMonth();
440 | day = date.getUTCDate();
441 | switch (res) {
442 | case "year":
443 | id = year;
444 | num = year;
445 | label = String(num);
446 | break;
447 | case "month":
448 | id = year + "-" + month;
449 | num = month;
450 | label = monthNames[num].substr(0,3);
451 | break;
452 | case "day":
453 | id = year + "-" + month + "-" + day;
454 | num = day;
455 | label = String(num);
456 | break;
457 | }
458 | id = "gant-t-calendar-header-" + id;
459 | headerEl = gEl(id);
460 | if (!headerEl) {
461 | switch (res) {
462 | case "year":
463 | case "month":
464 | className = "gant-t-calendar-header-" + res + "-" + (num % 2 === 0 ? "even" : "odd");
465 | break;
466 | case "day":
467 | var utcDay = date.getUTCDay();
468 | isNonWorkingDay = nonWorkingDays[utcDay] === true;
469 | className = "gant-t-calendar-header-day-" + (isNonWorkingDay ? "noworkday" : "workday") +
470 | " gant-t-calendar-header-day-" + (utcDay === weekStartDay ? "weekstart" : "noweekstart")
471 | ;
472 | break;
473 | }
474 | headerEl = cEl("div", {
475 | "class": "gant-t-calendar-header-item " + baseClassName + " " + className,
476 | id: id
477 | }, cEl("div", {
478 | "class": "label"
479 | }, label), header);
480 |
481 | if (res === "day") {
482 | headerEl.title = date.toDateString()
483 | }
484 | }
485 | headerEl.style.left = l + "px";
486 | headerEl.style.width = (w-l) + "px";
487 | if ((w - l) < 15) {
488 | color = "white";
489 | }
490 | else {
491 | color = "";
492 | }
493 | headerEl.firstChild.style.color = color;
494 | date = nextDate;
495 | }
496 | }
497 | var width = w+1;
498 | header.style.width = width + "px";
499 | this.getBodyTable().style.width = width + "px";
500 | },
501 | getHeader: function(){
502 | return this.header;
503 | },
504 | clearHeader: function(){
505 | var header = this.getHeader();
506 | if (!header) return;
507 | header.innerHTML = "";
508 | },
509 | getBodyTable: function(){
510 | return this.bodyTable;
511 | },
512 | getBody: function(){
513 | return this.body;
514 | },
515 | clearBodyTable: function(){
516 | var table = this.getBodyTable();
517 | var rows = table.rows;
518 | while (rows.length) {
519 | table.deleteRow(rows.length-1);
520 | }
521 | },
522 | clearLinks: function(){
523 | var table = this.getBodyTable();
524 | var body = this.getBody();
525 | while (body.childNodes.length > 1) {
526 | if (body.lastChild === table) return;
527 | body.removeChild(body.lastChild);
528 | }
529 | },
530 | clear: function(){
531 | this.clearHeader();
532 | this.clearBodyTable();
533 | this.clearLinks();
534 | },
535 | setCalendarResolution: function(calendarResolution){
536 | if (this.conf.calendarResolution === calendarResolution) return;
537 | var body = this.getBody();
538 | if (body) {
539 | if (calendarResolution === "project") {
540 | body.style.overflowX = "hidden";
541 | }
542 | else {
543 | body.style.overflowX = "auto";
544 | }
545 | }
546 | this.conf.calendarResolution = calendarResolution;
547 | this.renderHeader();
548 | this.renderTasks();
549 | this.updateCalendarHeight();
550 | },
551 | getCalendarResolution: function(){
552 | return this.conf.calendarResolution || "project";
553 | },
554 | widthChanged: function(){
555 | //var dom = this.getDom();
556 | //dom.style.overflow = "hidden";
557 | this.renderHeader();
558 | this.getBody().style.width = this.getDom().offsetWidth + "px";
559 | //dom.style.overflow = "auto";
560 | this.renderTasks();
561 | this.updateCalendarHeight();
562 | },
563 | hasHorizontalScrollBar: function(){
564 | var bodyTable = this.getBodyTable();
565 | var body = this.getBody();
566 | return bodyTable.offsetWidth > body.offsetWidth;
567 | },
568 | updateCalendarHeight: function(){
569 | var body = this.getBody();
570 | if (!body) return;
571 | var gantTChart = this.getGantTChart();
572 | var taskPane = gantTChart.getTaskPane();
573 | var borderBottomWidth;
574 | if (taskPane.attributesHasScrollbar()) {
575 | if (this.hasHorizontalScrollBar()) {
576 | borderBottomWidth = 0;
577 | }
578 | else {
579 | borderBottomWidth = scrollbarHeight;
580 | }
581 | }
582 | else {
583 | borderBottomWidth = 0;
584 | }
585 | body.style.borderBottomWidth = borderBottomWidth + "px";
586 | },
587 | createTaskBarHandles: function(task, children){
588 | if (task.Summary !== "1" && task.Milestone !== "1") {
589 | return;
590 | }
591 | children.push(
592 | cEl("div", {
593 | "class": "task-bar-left"
594 | })
595 | );
596 | children.push(
597 | cEl("div", {
598 | "class": "task-bar-right"
599 | })
600 | );
601 | },
602 | createTaskBarProgressIndicators: function(task, children){
603 | if (task.Summary === "1" || task.Milestone === "1") {
604 | return;
605 | }
606 | children.push(
607 | cEl("div", {
608 | "class": "task-bar-pct-complete",
609 | title: "Complete: " + task.PercentComplete + "%",
610 | style: {
611 | width: task.PercentComplete + "%"
612 | }
613 | })
614 | );
615 | children.push(
616 | cEl("div", {
617 | "class": "task-bar-pct-work-complete",
618 | title: "Work complete: " + task.PercentWorkComplete + "%",
619 | style: {
620 | width: task.PercentWorkComplete + "%"
621 | }
622 | })
623 | );
624 | children.push(
625 | cEl("div", {
626 | "class": "task-bar-physical-pct-complete",
627 | title: "Physical complete: " + task.PhysicalPercentComplete + "%",
628 | style: {
629 | width: task.PhysicalPercentComplete + "%"
630 | }
631 | })
632 | );
633 | },
634 | createTaskBarResources: function(cell, document, task){
635 | var assignments = document.getAssignmentsByTaskUID(task.UID);
636 | if (!assignments || !assignments.length) {
637 | return;
638 | }
639 | var i, n, assignment, resource, label, resourceElements = [];
640 | for (i = 0, n = assignments.length; i < n; i++){
641 | assignment = assignments[i];
642 | resource = document.getResourceByUID(assignment.ResourceUID);
643 | if (!resource) {
644 | continue;
645 | }
646 | var units = assignment.Units || "1";
647 | units = parseFloat(units);
648 | var partTimeFullTime;
649 | if (units < 1) {
650 | partTimeFullTime = "parttime";
651 | }
652 | else
653 | if (units > 1) {
654 | partTimeFullTime = "overtime";
655 | }
656 | else {
657 | partTimeFullTime = "fulltime";
658 | }
659 | var resourceChildElements = [
660 | cEl("span", {
661 | "class": "task-bar-resource-name"
662 | }, resource.Name),
663 | cEl("span", {
664 | "class": "task-bar-resource-units-pct"
665 | }, String(Math.round(units * 100)))
666 | ];
667 | resourceElements.push(
668 | cEl("li", {
669 | "data-resource-uid": resource.UID,
670 | "data-units": assignment.Units,
671 | "class": "task-bar-resource " +
672 | "task-bar-resource-" + partTimeFullTime
673 | }, resourceChildElements, resources)
674 | );
675 | }
676 | if (!resourceElements.length) {
677 | return;
678 | }
679 | var resources = cEl("ul", {
680 | "class": "task-bar-resources"
681 | }, resourceElements, cell);
682 | },
683 | createTaskBarFinishDateLabel: function(cell, document, task) {
684 | var finishDate = new Date(document.parseDate(task.Finish));
685 | var taskBarFinishDateLabel = cEl("div", {
686 | "class": "task-bar-finish formatted-date"
687 | }, [
688 | cEl("span", {
689 | "class": "formatted-date-year"
690 | }, finishDate.getUTCFullYear()),
691 | cEl("span", {
692 | "class": "formatted-date-month"
693 | }, 1+finishDate.getUTCMonth()),
694 | cEl("span", {
695 | "class": "formatted-date-day"
696 | }, finishDate.getUTCDate())
697 | ], cell);
698 | },
699 | addTask: function(task, index, suppressProjectSummaryTask, doc){
700 | var gantTChart = this.getGantTChart();
701 | var document = gantTChart.getDocument();
702 | if (suppressProjectSummaryTask && index === 0) return;
703 | var taskUID = task.UID;
704 | var bodyTable = this.getBodyTable();
705 | var rows = bodyTable.rows;
706 | var row = bodyTable.insertRow(rows.length);
707 | sAtts(row, {
708 | id: "calendarpane-taskUID-" + taskUID,
709 | "data-WBS": task.WBS,
710 | "data-start": document.parseDate(task.Start),
711 | "data-finish": document.parseDate(task.Finish),
712 | "class": this.getTaskClassName(task)
713 | });
714 |
715 | var cell = row.insertCell(0);
716 | var children = [];
717 | this.createTaskBarHandles(task,children);
718 | this.createTaskBarProgressIndicators(task,children);
719 |
720 | cEl("div", {
721 | id: "task-bar-" + task.UID,
722 | "class": "task-bar",
723 | title: task.Name,
724 | }, children, cell);
725 |
726 | this.createTaskBarResources(cell, document, task);
727 | this.createTaskBarFinishDateLabel(cell, document, task);
728 | cEl("br", {}, null, cell);
729 | },
730 | tasksAdded: function(){
731 | this.updateCalendarHeight();
732 | this.renderTasks();
733 | },
734 | taskToggled: function(){
735 | this.renderTasks();
736 | this.updateCalendarHeight();
737 | }
738 | };
739 | adopt(GantTCalendarPane, GantTChartComponent);
740 |
741 | (GantTTaskPane = function(conf){
742 | if (!conf) conf = {};
743 | if (!conf.classes) conf.classes = [];
744 | conf.classes = conf.classes.concat([
745 | "contentpane-noselect",
746 | GantTChart.prefix + "-contentpane",
747 | GantTChart.prefix + "-taskpane"
748 | ]);
749 | ContentPane.call(this, conf);
750 | }).prototype = {
751 | getTaskTableHeader: function(){
752 | return this.taskTableHeader;
753 | },
754 | getTasks: function(){
755 | return this.tasks;
756 | },
757 | getTaskTable: function(){
758 | return this.taskTable;
759 | },
760 | createWBSDom: function(el){
761 | this.taskTableHeader = cEl("div", {
762 | "class": GantTChart.prefix + "-taskpane-tasks-header",
763 | }, "Tasks", el);
764 | this.taskTable = cEl("table", {
765 | "class": GantTChart.prefix + "-taskpane-tasks-table",
766 | cellpadding: 0,
767 | cellspacing: 0
768 | });
769 | listen(this.taskTable, "click", this.taskTableClickHandler, this);
770 | this.tasks = cEl("div", {
771 | "class": GantTChart.prefix + "-taskpane-tasks"
772 | }, this.taskTable, el);
773 | },
774 | taskTableClickHandler: function(ev){
775 | var target = ev.getTarget();
776 | if (!hCls(target, "task-toggle")) {
777 | return;
778 | }
779 | var cell = target.parentNode;
780 | var row = cell.parentNode;
781 | //this.getGantTChart().toggleTask(row.id.substr("taskpane-taskUID-".length));
782 | this.getGantTChart().toggleTask(row.rowIndex);
783 | },
784 | getAttributesHeader: function(){
785 | return this.attributesHeader;
786 | },
787 | getAttributesTable: function(){
788 | return this.attributesTable;
789 | },
790 | getAttributes: function(){
791 | return this.attributes;
792 | },
793 | createAttributesDom: function(el){
794 | this.attributesHeader = cEl("div", {
795 | "class": GantTChart.prefix + "-taskpane-attributes-header",
796 | }, null, el);
797 |
798 | this.attributesTable = cEl("table", {
799 | "class": GantTChart.prefix + "-taskpane-attributes-table",
800 | cellpadding: 0,
801 | cellspacing: 0
802 | });
803 | this.attributes = cEl("div", {
804 | "class": GantTChart.prefix + "-taskpane-attributes"
805 | }, this.attributesTable, el);
806 | this.renderAttributeHeaders();
807 | listen(this.attributes, "scroll", this.attributesScrollHandler, this);
808 | },
809 | attributesScrollHandler: function(event){
810 | var target = event.getTarget();
811 | var header = this.getAttributesHeader();
812 | var taskPane = this.getTasks();
813 | header.style.left = (taskPane.offsetWidth - target.scrollLeft) + "px";
814 | },
815 | createDom: function(){
816 | var el = ContentPane.prototype.createDom.call(this);
817 | this.createWBSDom(el);
818 | this.createAttributesDom(el);
819 | return el;
820 | },
821 | setGantTChart: function(gantTChart){
822 | this.gantTChart = gantTChart;
823 | },
824 | clearAttributesTable: function(){
825 | var table = this.getAttributesTable();
826 | var rows = table.rows;
827 | while (rows.length) {
828 | table.deleteRow(rows.length-1);
829 | }
830 | },
831 | clearTaskTable: function(){
832 | var table = this.getTaskTable();
833 | var rows = table.rows;
834 | while (rows.length) {
835 | table.deleteRow(rows.length-1);
836 | }
837 | },
838 | clear: function(){
839 | this.clearTaskTable();
840 | this.clearAttributesTable();
841 | },
842 | getConfiguredAttributes: function(){
843 | var gantTChart = this.getGantTChart();
844 | var conf = gantTChart.conf;
845 | return conf.configuredAttributes || {};
846 | },
847 | renderAttributeHeaders: function(){
848 | var header = this.getAttributesHeader();
849 | header.innerHTML = "";
850 | var configuredAttributes = this.getConfiguredAttributes();
851 | var configuredAttribute, configuredAttributeDef;
852 | var id;
853 | for (configuredAttribute in configuredAttributes) {
854 | configuredAttributeDef = configuredAttributes[configuredAttribute];
855 | id = "gant-t-chart-taskpane-attributes-header-item-" + configuredAttribute;
856 | cEl("div", {
857 | id: id,
858 | "class": id + " gant-t-chart-taskpane-attributes-header-item"
859 | }, configuredAttributeDef.label || configuredAttribute, header);
860 | }
861 | this.positionAttributeHeaders();
862 | },
863 | positionAttributeHeaders: function(){
864 | var attributesTable = this.getAttributesTable();
865 | var attributesRow = attributesTable.rows[0];
866 | var attributesCells = attributesRow ? attributesRow.cells : null;
867 | var header = this.getAttributesHeader();
868 | var configuredAttributes = this.getConfiguredAttributes();
869 | var configuredAttribute, configuredAttributeDef;
870 | var id, headerItem, attributeCell, attributeCellPos, columnIndex = 0;
871 | for (configuredAttribute in configuredAttributes) {
872 | configuredAttributeDef = configuredAttributes[configuredAttribute];
873 | id = "gant-t-chart-taskpane-attributes-header-item-" + configuredAttribute;
874 | headerItem = gEl(id);
875 | if (headerItem === null) continue;
876 |
877 | if (configuredAttributeDef.displayed === false) {
878 | headerItem.style.display = "none";
879 | continue;
880 | }
881 |
882 | if (attributesCells && columnIndex < attributesCells.length) {
883 | attributeCell = attributesCells[columnIndex];
884 | attributesCellPos = pos(attributeCell, attributesTable);
885 | headerItem.style.left = (attributesCellPos.left + 1 + columnIndex) + "px";
886 | headerItem.style.width = (attributeCell.clientWidth - 4) + "px";
887 | }
888 | else {
889 | headerItem.style.width = "";
890 | }
891 | columnIndex++;
892 | }
893 | },
894 | addTask: function(task, index, suppressProjectSummaryTask, doc){
895 | if (suppressProjectSummaryTask && index === 0) return;
896 | var isNull = task.IsNull === "1";
897 | var outlineLevel = isNull ? NaN : parseInt(task.OutlineLevel, 10);
898 |
899 | var taskTable = this.getTaskTable();
900 | var rows = taskTable.rows;
901 | var row = taskTable.insertRow(rows.length);
902 |
903 | var toggleState;
904 | if (task.Summary === "1") {
905 | toggleState = " task-expanded";
906 | }
907 | var className = this.getTaskClassName(task) + toggleState;
908 | sAtts(row, {
909 | id: "taskpane-taskUID-" + task.UID,
910 | "data-WBS": task.WBS,
911 | "data-OutlineLevel": outlineLevel,
912 | "class": className
913 | });
914 |
915 | var cell;
916 | cell = row.insertCell(row.cells.length);
917 | cell.className = "task-identity"
918 | cell.innerHTML = task.ID;
919 | cell = row.insertCell(row.cells.length);
920 | cell.className = "task-label";
921 |
922 | cEl("span", {
923 | "class": "task-toggle"
924 | }, String.fromCharCode(160), cell);
925 | cEl("span", {
926 | "class": "task-wbs"
927 | }, task.WBS, cell);
928 | cEl("span", {
929 | "class": "task-outlinenumber"
930 | }, task.OutlineNumber, cell);
931 | cEl("label", {
932 | "class": "task-name"
933 | }, isNull ? String.fromCharCode(160) : task.Name, cell);
934 |
935 | if (suppressProjectSummaryTask) {
936 | //outlineLevel = outlineLevel - 1;
937 | }
938 | if (!isNaN(outlineLevel)) {
939 | cell.style.paddingLeft = outlineLevel + "em";
940 | }
941 |
942 | var attributesTable = this.getAttributesTable();
943 | var rows = attributesTable.rows;
944 | row = attributesTable.insertRow(rows.length);
945 | sAtts(row, {
946 | id: "taskpane-taskUID-" + task.UID,
947 | "class": className
948 | });
949 | var configuredAttributes = this.getConfiguredAttributes();
950 | var configuredAttribute, configuredAttributeDef, value;
951 | for (configuredAttribute in configuredAttributes) {
952 | if (isNull) {
953 | value = "
";
954 | }
955 | else {
956 | configuredAttributeDef = configuredAttributes[configuredAttribute];
957 | if (configuredAttributeDef.displayed === false) continue;
958 | cell = row.insertCell(row.cells.length);
959 | value = task[configuredAttribute];
960 | //todo: format value
961 | if (configuredAttributeDef.formatter) {
962 | value = configuredAttributeDef.formatter(value, task, doc);
963 | }
964 | }
965 | cell.innerHTML = value;
966 | }
967 | },
968 | updateAttributesWidth: function(){
969 | var dom = this.getDom();
970 | var tasks = this.getTasks();
971 | var atts = this.getAttributes();
972 | var header = this.getAttributesHeader();
973 |
974 | atts.style.left = tasks.offsetWidth + "px";
975 | header.style.left = tasks.offsetWidth + "px";
976 |
977 | var width = dom.clientWidth - tasks.offsetWidth;
978 | atts.style.width = width + "px";
979 | header.style.width = width + "px";
980 | },
981 | attributesHasScrollbar: function() {
982 | var atts = this.getAttributes();
983 | if (!atts) return false;
984 | var attsTable = this.getAttributesTable();
985 | if (!attsTable) return false;
986 | return attsTable.offsetWidth > atts.clientWidth;
987 | },
988 | widthChanged: function(){
989 | this.updateAttributesWidth();
990 | },
991 | taskToggled: function(){
992 | this.updateTaskTableHeaderWidth();
993 | this.updateAttributesWidth();
994 | this.positionAttributeHeaders();
995 | },
996 | updateTaskTableHeaderWidth: function(){
997 | var taskTable = this.getTaskTable();
998 | var taskTableHeader = this.getTaskTableHeader();
999 | taskTableHeader.style.width = (taskTable.offsetWidth - 5) + "px";
1000 | },
1001 | taskTableWidthChanged: function(){
1002 | this.updateTaskTableHeaderWidth();
1003 | this.updateAttributesWidth();
1004 | },
1005 | tasksAdded: function(){
1006 | this.updateTaskTableHeaderWidth();
1007 | this.updateAttributesWidth();
1008 | this.positionAttributeHeaders();
1009 | },
1010 | scroll: function(scrollLeft, scrollTop) {
1011 | this.getTasks().style.top = (47 -scrollTop) + "px";
1012 | this.getAttributes().style.top = (47 -scrollTop) + "px";
1013 | }
1014 | };
1015 | adopt(GantTTaskPane, GantTChartComponent);
1016 |
1017 | (GantTChart = function(conf){
1018 | if (!conf) conf = {};
1019 | if (!conf.classes) conf.classes = [];
1020 | conf.classes.push(GantTChart.prefix + "-splitpane");
1021 | conf.firstComponent = this.getTaskPane();
1022 | conf.secondComponent = this.getCalendarPane();
1023 | conf.orientation = SplitPane.orientations.vertical;
1024 | if (!conf.calendarResolution) {
1025 | conf.calendarResolution = "project";
1026 | }
1027 | SplitPane.call(this, conf);
1028 | this.setCalendarResolution(conf.calendarResolution);
1029 |
1030 | this.splitterPositionChangedTimer = new Timer({
1031 | delay: conf.splitterPositionChangedTimeout || GantTChart.splitterPositionChangedDefaultTimeout
1032 | });
1033 | this.splitterPositionChangedTimer.listen("expired", this.syncSize, this);
1034 | this.listen("splitterPositionChanged", this.splitterPositionChanged, this);
1035 | this.initStylesheet();
1036 | }).prototype = {
1037 | initStylesheet: function(){
1038 | var conf = this.conf, rules = {};
1039 | var idPrefix = "#" + this.getId();
1040 |
1041 | rules[idPrefix + " span.task-wbs"] = {
1042 | display: Boolean(conf.displayWBS) ? "inline" : "none"
1043 | };
1044 | rules[idPrefix + " span.task-outlinenumber"] = {
1045 | display: Boolean(conf.displayOutlineNumber) ? "inline" : "none"
1046 | };
1047 |
1048 | this.stylesheet = new CascadingStyleSheet({
1049 | id: this.getId() + "-styles",
1050 | rules: rules
1051 | });
1052 | },
1053 | createDom: function(){
1054 | var el = SplitPane.prototype.createDom.call(this);
1055 | this.stylesheet.createDom();
1056 | return el;
1057 | },
1058 | displayWBS: function(displayWBS){
1059 | this.conf.displayWBS = Boolean(displayWBS);
1060 | this.stylesheet.applyStyle("#" + this.getId() + " span.task-wbs", {
1061 | display: this.conf.displayWBS ? "inline" : "none"
1062 | });
1063 | this.getTaskPane().taskTableWidthChanged();
1064 | },
1065 | displayOutlineNumber: function(displayOutlineNumber){
1066 | this.conf.displayOutlineNumber = Boolean(displayOutlineNumber);
1067 | this.stylesheet.applyStyle("#" + this.getId() + " span.task-outlinenumber", {
1068 | display: this.conf.displayOutlineNumber ? "inline" : "none"
1069 | });
1070 | this.getTaskPane().taskTableWidthChanged();
1071 | },
1072 | setCalendarResolution: function(resolution){
1073 | this.getCalendarPane().setCalendarResolution(resolution);
1074 | },
1075 | toggleTask: function(rowIndex){
1076 | var taskPane = this.getTaskPane(),
1077 | taskTableRows = taskPane.getTaskTable().rows,
1078 | attsRows = taskPane.getAttributesTable().rows,
1079 | calendarPane = this.getCalendarPane(),
1080 | calendarRows = calendarPane.getBodyTable().rows,
1081 | row = taskTableRows[rowIndex],
1082 | oL = parseInt(gAtt(row, "data-outlinelevel"), 10), rOl, prevRol = oL,
1083 | r, i, n = taskTableRows.length, s, wbsStack = [],
1084 | cls1, cls2
1085 | ;
1086 | //determine what toggle means (exapand or collapse);
1087 | if (hCls(row, "task-expanded")) {
1088 | cls1 = "task-expanded";
1089 | cls2 = "task-collapsed";
1090 | }
1091 | else {
1092 | cls1 = "task-collapsed";
1093 | cls2 = "task-expanded";
1094 | }
1095 | rCls(row, cls1, cls2);
1096 | //put the new state on the stack.
1097 | wbsStack.push(cls2);
1098 |
1099 | for (i = rowIndex + 1; i < n; i++) {
1100 | r = taskTableRows[i];
1101 | rOl = parseInt(gAtt(r, "data-outlinelevel"), 10);
1102 |
1103 | //check of this task's outline level is less or same as toggled task.
1104 | //If it is, we visited all descendants of the toggled task, and we can leave.
1105 | if (rOl <= oL) {
1106 | break;
1107 | }
1108 |
1109 | //if the current outline level is less or same as previous,
1110 | //then we can clear the stack for the descendant levels.
1111 | //This ensures the scope of state is maintained.
1112 | if (rOl <= prevRol) {
1113 | wbsStack.length -= (prevRol-rOl) + 1;
1114 | }
1115 |
1116 | //determine the style that fits the current
1117 | s = (wbsStack[wbsStack.length - 1] === "task-collapsed") ? "none" : "";
1118 |
1119 | //set the style so as to hide/show rows.
1120 | r.style.display = s;
1121 | attsRows[i].style.display = s;
1122 | calendarRows[i].style.display = s;
1123 |
1124 | if (wbsStack[wbsStack.length - 1] === "task-collapsed") {
1125 | //if we are descendant of a task that is collapsed, then collapse.
1126 | wbsStack.push("task-collapsed");
1127 | }
1128 | else {
1129 | //if we are descendant of a task that is not collapsed, then...
1130 | if (hCls(r, "task-collapsed")) {
1131 | //if the current task is collapsed, push to stack to collapse its descendants
1132 | wbsStack.push("task-collapsed");
1133 | }
1134 | else
1135 | if (hCls(r, "task-expanded")) {
1136 | //if the current task is expanded, pusth to stack so we can expand its descendants,
1137 | //if applicable.
1138 | wbsStack.push("task-expanded");
1139 | }
1140 | else {
1141 | //if the current task is a leaf, push a state that is neither collapsed or expanded.
1142 | wbsStack.push("");
1143 | }
1144 | }
1145 | prevRol = rOl;
1146 | }
1147 | taskPane.taskToggled();
1148 | calendarPane.taskToggled();
1149 | },
1150 | splitterPositionChanged: function(){
1151 | this.splitterPositionChangedTimer.start();
1152 | },
1153 | syncSize: function(){
1154 | this.getTaskPane().widthChanged();
1155 | var calendarPane = this.getCalendarPane();
1156 | calendarPane.widthChanged();
1157 | calendarPane.updateCalendarHeight();
1158 | },
1159 | getTaskPane: function(){
1160 | var taskPane = this.taskPane;
1161 | if (!taskPane) {
1162 | taskPane = new GantTTaskPane();
1163 | taskPane.setGantTChart(this);
1164 | this.taskPane = taskPane;
1165 | }
1166 | return taskPane;
1167 | },
1168 | getCalendarPane: function(){
1169 | var calendarPane = this.calendarPane;
1170 | if (!calendarPane) {
1171 | calendarPane = new GantTCalendarPane();
1172 | calendarPane.setGantTChart(this);
1173 | this.calendarPane = calendarPane;
1174 | }
1175 | return calendarPane;
1176 | },
1177 | clear: function(){
1178 | this.getTaskPane().clear();
1179 | this.getCalendarPane().clear();
1180 | },
1181 | dontSuppressProjectSummaryTask: function(){
1182 | return this.conf.suppressProjectSummaryTask === false;
1183 | },
1184 | refresh: function(){
1185 | this.clear();
1186 | var dontSuppressProjectSummaryTask = this.dontSuppressProjectSummaryTask();
1187 | var taskPane = this.getTaskPane();
1188 | var calendarPane = this.getCalendarPane();
1189 | calendarPane.renderHeader();
1190 | var doc = this.getDocument();
1191 | doc.forEachTask(function(task, index) {
1192 | taskPane.addTask(task, index, !dontSuppressProjectSummaryTask, doc);
1193 | calendarPane.addTask(task, index, !dontSuppressProjectSummaryTask, doc);
1194 | });
1195 | taskPane.tasksAdded();
1196 | calendarPane.tasksAdded();
1197 | },
1198 | setDocument: function(doc){
1199 | this.mspDocument = doc;
1200 | this.refresh();
1201 | },
1202 | getDocument: function(){
1203 | return this.mspDocument;
1204 | },
1205 | scrolled: function(scrollLeft, scrollTop) {
1206 | this.getCalendarPane().scroll(scrollLeft, scrollTop);
1207 | this.getTaskPane().scroll(scrollLeft, scrollTop);
1208 | }
1209 | };
1210 |
1211 | GantTChart.splitterPositionChangedDefaultTimeout = 100;
1212 |
1213 | adopt(GantTChart, SplitPane);
1214 |
1215 | GantTChart.prefix = "gant-t-chart";
1216 | linkCss(cssDir + "ganttchart.css");
1217 | })();
--------------------------------------------------------------------------------