├── README.md
├── editor
├── css
│ └── ui-lightness
│ │ ├── images
│ │ ├── ui-bg_diagonals-thick_18_b81900_40x40.png
│ │ ├── ui-bg_diagonals-thick_20_666666_40x40.png
│ │ ├── ui-bg_flat_10_000000_40x100.png
│ │ ├── ui-bg_glass_100_f6f6f6_1x400.png
│ │ ├── ui-bg_glass_100_fdf5ce_1x400.png
│ │ ├── ui-bg_glass_65_ffffff_1x400.png
│ │ ├── ui-bg_gloss-wave_35_f6a828_500x100.png
│ │ ├── ui-bg_highlight-soft_100_eeeeee_1x100.png
│ │ ├── ui-bg_highlight-soft_75_ffe45c_1x100.png
│ │ ├── ui-icons_222222_256x240.png
│ │ ├── ui-icons_228ef1_256x240.png
│ │ ├── ui-icons_ef8c08_256x240.png
│ │ ├── ui-icons_ffd27a_256x240.png
│ │ └── ui-icons_ffffff_256x240.png
│ │ └── jquery-ui.css
├── index.html
├── js
│ └── jquery-ui.js
├── jsganttchart.editor.js
└── style.css
├── gantt.png
├── jsganttchart.js
├── lib
├── backbone.js
├── backbone.localStorage.js
├── jquery.js
└── underscore.js
├── project
├── gantt.js
└── index.html
└── themes
├── default.css
└── default.images
└── urgent.png
/README.md:
--------------------------------------------------------------------------------
1 | JS Gantt Chart
2 | ==============
3 |
4 | Simply put, this is a JavaScript Gantt Chart generator. It generates a gantt chart from JSON.
5 |
6 | 
7 |
8 | Editor
9 | ------
10 |
11 | There is also a limited UI for creating and editing Gantt Chart.
12 |
13 |
14 | Requirements
15 | ------------
16 | * jQuery
17 | * Backbone.js
18 | * Underscore.js
19 |
20 | Example
21 | -------
22 |
23 | To produce the gantt chart as shown in the image above:
24 |
25 | ```javascript
26 | var gantt = JSGanttChart.create({
27 | resources: {
28 | programmer: "Peter West",
29 | supervisor: "mc",
30 | cosupervisor: "max"
31 | },
32 | types: {
33 | analysis: {
34 | name: "Analysis",
35 | color: "#C79810" // yellow
36 | },
37 | critical: {
38 | name: "Critical",
39 | color: "#CC0000" // red
40 | },
41 | programming: {
42 | name: "Programming",
43 | color: "#356AA0" // blue
44 | },
45 | documentation: {
46 | name: "Documentation",
47 | color: "#FF7400" // orange
48 | }
49 | },
50 | elements: [
51 | {
52 | id: "meeting1",
53 | name: "Supervisor meeting",
54 | startDate: "1 October 2011 11:00",
55 | percentageDone: 100,
56 | resources: [ "programmer", "supervisor", "cosupervisor" ]
57 | },
58 | {
59 | id: "brief",
60 | name: "Project Brief",
61 | predecessors: ["meeting1"],
62 | startDate: "3 October 2011",
63 | endDate: "14 October 2011 16:00",
64 | type: "critical",
65 | slackEndDate: "21 October 2011 16:00",//slackDuration: 4, // or slackEndDate: date
66 | elements: [
67 | {
68 | id: "briefdraft1",
69 | name: "Draft 1",
70 | startDate: "3 October 2011",
71 | endDate: "14 October 2011 16:00",
72 | type: "analysis",
73 | percentageDone: 100,
74 | estimatedHours: 5
75 | },
76 | {
77 | id: "briefdraft2",
78 | predecessors: ["briefdraft1"],
79 | name: "Draft 2",
80 | startDate: "15 October 2011",
81 | endDate: "17 October 2011",
82 | percentageDone: 90,
83 | estimatedHours: 5
84 | },
85 | ],
86 | percentageDone: 95,
87 | estimatedHours: 10
88 | },
89 | {
90 | id: "planning",
91 | name: "Project Planning",
92 | predecessors: ["brief"],
93 | startDate: "17 October 2011",
94 | endDate: "30 October 2011 14:00",
95 | percentageDone: 50,
96 | elements: [
97 | {
98 | id: "gantt1",
99 | name: "Time planning",
100 | startDate: "17 October 2011",
101 | endDate: "21 October 2011 14:00",
102 | estimatedHours: 6,
103 | percentageDone: 60
104 | },
105 | {
106 | id: "design1",
107 | name: "Initial mockups",
108 | startDate: "20 October 2011",
109 | endDate: "21 October 2011 14:00",
110 | estimatedHours: 6,
111 | percentageDone: 10
112 | },
113 | {
114 | id: "prestudy",
115 | name: "Prestudy",
116 | startDate: "18 October 2011",
117 | endDate: "29 October 2011",
118 | type: "analysis",
119 | percentageDone: 2,
120 | }
121 | ]
122 | },
123 | {
124 | id: "dev",
125 | name: "Software development",
126 | startDate: "1 November 2011",
127 | endDate: "1 March 2012",
128 | predecessors: ["planning"],
129 | elements: [
130 | {
131 | id: "software1",
132 | name: "Software development P1",
133 | startDate: "1 November 2011",
134 | endDate: "17 December 2011"
135 | },
136 | {
137 | id: "software2",
138 | name: "Software development P2",
139 | startDate: "12 January 2012",
140 | endDate: "14 February 2012"
141 | }
142 | ]
143 | },
144 | {
145 | id: "study",
146 | name: "Study",
147 | startDate: "18 December 2011",
148 | endDate: "12 April 2012",
149 | type: "analysis",
150 | elements: [
151 | {
152 | id: "study1",
153 | name: "First study",
154 | startDate: "18 December 2011",
155 | endDate: "11 January 2012",
156 | type: "analysis",
157 | predecessors: ["software1"]
158 | },
159 | {
160 | id: "study2",
161 | name: "Second study",
162 | startDate: "15 February 2012",
163 | endDate: "12 April 2012",
164 | type: "analysis",
165 | predecessors: ["software2"]
166 | }
167 | ]
168 | },
169 | {
170 | id: "reviewmeeting1",
171 | name: "Review meeting",
172 | startDate: "13 November 2011"
173 | },
174 | {
175 | id: "progreport",
176 | name: "Progress Report",
177 | startDate: "7 December 2011",
178 | endDate: "14 December 2011 16:00",
179 | type: "documentation"
180 | },
181 | {
182 | id: "finalreport",
183 | name: "Final Project Report",
184 | startDate: "2 April 2012",
185 | endDate: "2 May 2012",
186 | type: "documentation"
187 | }
188 | ]
189 | });
190 |
191 | jQuery("#container").append(gantt.render().el);
192 |
193 | ```
194 |
--------------------------------------------------------------------------------
/editor/css/ui-lightness/images/ui-bg_diagonals-thick_18_b81900_40x40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlamingTempura/JSGanttChart/f8a1355c5ee9d40a8741cf7c2926d55a88a1ede7/editor/css/ui-lightness/images/ui-bg_diagonals-thick_18_b81900_40x40.png
--------------------------------------------------------------------------------
/editor/css/ui-lightness/images/ui-bg_diagonals-thick_20_666666_40x40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlamingTempura/JSGanttChart/f8a1355c5ee9d40a8741cf7c2926d55a88a1ede7/editor/css/ui-lightness/images/ui-bg_diagonals-thick_20_666666_40x40.png
--------------------------------------------------------------------------------
/editor/css/ui-lightness/images/ui-bg_flat_10_000000_40x100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlamingTempura/JSGanttChart/f8a1355c5ee9d40a8741cf7c2926d55a88a1ede7/editor/css/ui-lightness/images/ui-bg_flat_10_000000_40x100.png
--------------------------------------------------------------------------------
/editor/css/ui-lightness/images/ui-bg_glass_100_f6f6f6_1x400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlamingTempura/JSGanttChart/f8a1355c5ee9d40a8741cf7c2926d55a88a1ede7/editor/css/ui-lightness/images/ui-bg_glass_100_f6f6f6_1x400.png
--------------------------------------------------------------------------------
/editor/css/ui-lightness/images/ui-bg_glass_100_fdf5ce_1x400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlamingTempura/JSGanttChart/f8a1355c5ee9d40a8741cf7c2926d55a88a1ede7/editor/css/ui-lightness/images/ui-bg_glass_100_fdf5ce_1x400.png
--------------------------------------------------------------------------------
/editor/css/ui-lightness/images/ui-bg_glass_65_ffffff_1x400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlamingTempura/JSGanttChart/f8a1355c5ee9d40a8741cf7c2926d55a88a1ede7/editor/css/ui-lightness/images/ui-bg_glass_65_ffffff_1x400.png
--------------------------------------------------------------------------------
/editor/css/ui-lightness/images/ui-bg_gloss-wave_35_f6a828_500x100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlamingTempura/JSGanttChart/f8a1355c5ee9d40a8741cf7c2926d55a88a1ede7/editor/css/ui-lightness/images/ui-bg_gloss-wave_35_f6a828_500x100.png
--------------------------------------------------------------------------------
/editor/css/ui-lightness/images/ui-bg_highlight-soft_100_eeeeee_1x100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlamingTempura/JSGanttChart/f8a1355c5ee9d40a8741cf7c2926d55a88a1ede7/editor/css/ui-lightness/images/ui-bg_highlight-soft_100_eeeeee_1x100.png
--------------------------------------------------------------------------------
/editor/css/ui-lightness/images/ui-bg_highlight-soft_75_ffe45c_1x100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlamingTempura/JSGanttChart/f8a1355c5ee9d40a8741cf7c2926d55a88a1ede7/editor/css/ui-lightness/images/ui-bg_highlight-soft_75_ffe45c_1x100.png
--------------------------------------------------------------------------------
/editor/css/ui-lightness/images/ui-icons_222222_256x240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlamingTempura/JSGanttChart/f8a1355c5ee9d40a8741cf7c2926d55a88a1ede7/editor/css/ui-lightness/images/ui-icons_222222_256x240.png
--------------------------------------------------------------------------------
/editor/css/ui-lightness/images/ui-icons_228ef1_256x240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlamingTempura/JSGanttChart/f8a1355c5ee9d40a8741cf7c2926d55a88a1ede7/editor/css/ui-lightness/images/ui-icons_228ef1_256x240.png
--------------------------------------------------------------------------------
/editor/css/ui-lightness/images/ui-icons_ef8c08_256x240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlamingTempura/JSGanttChart/f8a1355c5ee9d40a8741cf7c2926d55a88a1ede7/editor/css/ui-lightness/images/ui-icons_ef8c08_256x240.png
--------------------------------------------------------------------------------
/editor/css/ui-lightness/images/ui-icons_ffd27a_256x240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlamingTempura/JSGanttChart/f8a1355c5ee9d40a8741cf7c2926d55a88a1ede7/editor/css/ui-lightness/images/ui-icons_ffd27a_256x240.png
--------------------------------------------------------------------------------
/editor/css/ui-lightness/images/ui-icons_ffffff_256x240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlamingTempura/JSGanttChart/f8a1355c5ee9d40a8741cf7c2926d55a88a1ede7/editor/css/ui-lightness/images/ui-icons_ffffff_256x240.png
--------------------------------------------------------------------------------
/editor/css/ui-lightness/jquery-ui.css:
--------------------------------------------------------------------------------
1 | /*
2 | * jQuery UI CSS Framework 1.8.16
3 | *
4 | * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
5 | * Dual licensed under the MIT or GPL Version 2 licenses.
6 | * http://jquery.org/license
7 | *
8 | * http://docs.jquery.com/UI/Theming/API
9 | */
10 |
11 | /* Layout helpers
12 | ----------------------------------*/
13 | .ui-helper-hidden { display: none; }
14 | .ui-helper-hidden-accessible { position: absolute !important; clip: rect(1px 1px 1px 1px); clip: rect(1px,1px,1px,1px); }
15 | .ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; }
16 | .ui-helper-clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; }
17 | .ui-helper-clearfix { display: inline-block; }
18 | /* required comment for clearfix to work in Opera \*/
19 | * html .ui-helper-clearfix { height:1%; }
20 | .ui-helper-clearfix { display:block; }
21 | /* end clearfix */
22 | .ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); }
23 |
24 |
25 | /* Interaction Cues
26 | ----------------------------------*/
27 | .ui-state-disabled { cursor: default !important; }
28 |
29 |
30 | /* Icons
31 | ----------------------------------*/
32 |
33 | /* states and images */
34 | .ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; }
35 |
36 |
37 | /* Misc visuals
38 | ----------------------------------*/
39 |
40 | /* Overlays */
41 | .ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
42 |
43 |
44 | /*
45 | * jQuery UI CSS Framework 1.8.16
46 | *
47 | * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
48 | * Dual licensed under the MIT or GPL Version 2 licenses.
49 | * http://jquery.org/license
50 | *
51 | * http://docs.jquery.com/UI/Theming/API
52 | *
53 | * To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Trebuchet%20MS,%20Tahoma,%20Verdana,%20Arial,%20sans-serif&fwDefault=bold&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=f6a828&bgTextureHeader=12_gloss_wave.png&bgImgOpacityHeader=35&borderColorHeader=e78f08&fcHeader=ffffff&iconColorHeader=ffffff&bgColorContent=eeeeee&bgTextureContent=03_highlight_soft.png&bgImgOpacityContent=100&borderColorContent=dddddd&fcContent=333333&iconColorContent=222222&bgColorDefault=f6f6f6&bgTextureDefault=02_glass.png&bgImgOpacityDefault=100&borderColorDefault=cccccc&fcDefault=1c94c4&iconColorDefault=ef8c08&bgColorHover=fdf5ce&bgTextureHover=02_glass.png&bgImgOpacityHover=100&borderColorHover=fbcb09&fcHover=c77405&iconColorHover=ef8c08&bgColorActive=ffffff&bgTextureActive=02_glass.png&bgImgOpacityActive=65&borderColorActive=fbd850&fcActive=eb8f00&iconColorActive=ef8c08&bgColorHighlight=ffe45c&bgTextureHighlight=03_highlight_soft.png&bgImgOpacityHighlight=75&borderColorHighlight=fed22f&fcHighlight=363636&iconColorHighlight=228ef1&bgColorError=b81900&bgTextureError=08_diagonals_thick.png&bgImgOpacityError=18&borderColorError=cd0a0a&fcError=ffffff&iconColorError=ffd27a&bgColorOverlay=666666&bgTextureOverlay=08_diagonals_thick.png&bgImgOpacityOverlay=20&opacityOverlay=50&bgColorShadow=000000&bgTextureShadow=01_flat.png&bgImgOpacityShadow=10&opacityShadow=20&thicknessShadow=5px&offsetTopShadow=-5px&offsetLeftShadow=-5px&cornerRadiusShadow=5px
54 | */
55 |
56 |
57 | /* Component containers
58 | ----------------------------------*/
59 | .ui-widget { font-family: Trebuchet MS, Tahoma, Verdana, Arial, sans-serif; font-size: 1.1em; }
60 | .ui-widget .ui-widget { font-size: 1em; }
61 | .ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Trebuchet MS, Tahoma, Verdana, Arial, sans-serif; font-size: 1em; }
62 | .ui-widget-content { border: 1px solid #dddddd; background: #eeeeee url(images/ui-bg_highlight-soft_100_eeeeee_1x100.png) 50% top repeat-x; color: #333333; }
63 | .ui-widget-content a { color: #333333; }
64 | .ui-widget-header { border: 1px solid #e78f08; background: #f6a828 url(images/ui-bg_gloss-wave_35_f6a828_500x100.png) 50% 50% repeat-x; color: #ffffff; font-weight: bold; }
65 | .ui-widget-header a { color: #ffffff; }
66 |
67 | /* Interaction states
68 | ----------------------------------*/
69 | .ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #cccccc; background: #f6f6f6 url(images/ui-bg_glass_100_f6f6f6_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #1c94c4; }
70 | .ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #1c94c4; text-decoration: none; }
71 | .ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #fbcb09; background: #fdf5ce url(images/ui-bg_glass_100_fdf5ce_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #c77405; }
72 | .ui-state-hover a, .ui-state-hover a:hover { color: #c77405; text-decoration: none; }
73 | .ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #fbd850; background: #ffffff url(images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #eb8f00; }
74 | .ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #eb8f00; text-decoration: none; }
75 | .ui-widget :active { outline: none; }
76 |
77 | /* Interaction Cues
78 | ----------------------------------*/
79 | .ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight {border: 1px solid #fed22f; background: #ffe45c url(images/ui-bg_highlight-soft_75_ffe45c_1x100.png) 50% top repeat-x; color: #363636; }
80 | .ui-state-highlight a, .ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a { color: #363636; }
81 | .ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error {border: 1px solid #cd0a0a; background: #b81900 url(images/ui-bg_diagonals-thick_18_b81900_40x40.png) 50% 50% repeat; color: #ffffff; }
82 | .ui-state-error a, .ui-widget-content .ui-state-error a, .ui-widget-header .ui-state-error a { color: #ffffff; }
83 | .ui-state-error-text, .ui-widget-content .ui-state-error-text, .ui-widget-header .ui-state-error-text { color: #ffffff; }
84 | .ui-priority-primary, .ui-widget-content .ui-priority-primary, .ui-widget-header .ui-priority-primary { font-weight: bold; }
85 | .ui-priority-secondary, .ui-widget-content .ui-priority-secondary, .ui-widget-header .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; }
86 | .ui-state-disabled, .ui-widget-content .ui-state-disabled, .ui-widget-header .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; }
87 |
88 | /* Icons
89 | ----------------------------------*/
90 |
91 | /* states and images */
92 | .ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_222222_256x240.png); }
93 | .ui-widget-content .ui-icon {background-image: url(images/ui-icons_222222_256x240.png); }
94 | .ui-widget-header .ui-icon {background-image: url(images/ui-icons_ffffff_256x240.png); }
95 | .ui-state-default .ui-icon { background-image: url(images/ui-icons_ef8c08_256x240.png); }
96 | .ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_ef8c08_256x240.png); }
97 | .ui-state-active .ui-icon {background-image: url(images/ui-icons_ef8c08_256x240.png); }
98 | .ui-state-highlight .ui-icon {background-image: url(images/ui-icons_228ef1_256x240.png); }
99 | .ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_ffd27a_256x240.png); }
100 |
101 | /* positioning */
102 | .ui-icon-carat-1-n { background-position: 0 0; }
103 | .ui-icon-carat-1-ne { background-position: -16px 0; }
104 | .ui-icon-carat-1-e { background-position: -32px 0; }
105 | .ui-icon-carat-1-se { background-position: -48px 0; }
106 | .ui-icon-carat-1-s { background-position: -64px 0; }
107 | .ui-icon-carat-1-sw { background-position: -80px 0; }
108 | .ui-icon-carat-1-w { background-position: -96px 0; }
109 | .ui-icon-carat-1-nw { background-position: -112px 0; }
110 | .ui-icon-carat-2-n-s { background-position: -128px 0; }
111 | .ui-icon-carat-2-e-w { background-position: -144px 0; }
112 | .ui-icon-triangle-1-n { background-position: 0 -16px; }
113 | .ui-icon-triangle-1-ne { background-position: -16px -16px; }
114 | .ui-icon-triangle-1-e { background-position: -32px -16px; }
115 | .ui-icon-triangle-1-se { background-position: -48px -16px; }
116 | .ui-icon-triangle-1-s { background-position: -64px -16px; }
117 | .ui-icon-triangle-1-sw { background-position: -80px -16px; }
118 | .ui-icon-triangle-1-w { background-position: -96px -16px; }
119 | .ui-icon-triangle-1-nw { background-position: -112px -16px; }
120 | .ui-icon-triangle-2-n-s { background-position: -128px -16px; }
121 | .ui-icon-triangle-2-e-w { background-position: -144px -16px; }
122 | .ui-icon-arrow-1-n { background-position: 0 -32px; }
123 | .ui-icon-arrow-1-ne { background-position: -16px -32px; }
124 | .ui-icon-arrow-1-e { background-position: -32px -32px; }
125 | .ui-icon-arrow-1-se { background-position: -48px -32px; }
126 | .ui-icon-arrow-1-s { background-position: -64px -32px; }
127 | .ui-icon-arrow-1-sw { background-position: -80px -32px; }
128 | .ui-icon-arrow-1-w { background-position: -96px -32px; }
129 | .ui-icon-arrow-1-nw { background-position: -112px -32px; }
130 | .ui-icon-arrow-2-n-s { background-position: -128px -32px; }
131 | .ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }
132 | .ui-icon-arrow-2-e-w { background-position: -160px -32px; }
133 | .ui-icon-arrow-2-se-nw { background-position: -176px -32px; }
134 | .ui-icon-arrowstop-1-n { background-position: -192px -32px; }
135 | .ui-icon-arrowstop-1-e { background-position: -208px -32px; }
136 | .ui-icon-arrowstop-1-s { background-position: -224px -32px; }
137 | .ui-icon-arrowstop-1-w { background-position: -240px -32px; }
138 | .ui-icon-arrowthick-1-n { background-position: 0 -48px; }
139 | .ui-icon-arrowthick-1-ne { background-position: -16px -48px; }
140 | .ui-icon-arrowthick-1-e { background-position: -32px -48px; }
141 | .ui-icon-arrowthick-1-se { background-position: -48px -48px; }
142 | .ui-icon-arrowthick-1-s { background-position: -64px -48px; }
143 | .ui-icon-arrowthick-1-sw { background-position: -80px -48px; }
144 | .ui-icon-arrowthick-1-w { background-position: -96px -48px; }
145 | .ui-icon-arrowthick-1-nw { background-position: -112px -48px; }
146 | .ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }
147 | .ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }
148 | .ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }
149 | .ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }
150 | .ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }
151 | .ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }
152 | .ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }
153 | .ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }
154 | .ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }
155 | .ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }
156 | .ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }
157 | .ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }
158 | .ui-icon-arrowreturn-1-w { background-position: -64px -64px; }
159 | .ui-icon-arrowreturn-1-n { background-position: -80px -64px; }
160 | .ui-icon-arrowreturn-1-e { background-position: -96px -64px; }
161 | .ui-icon-arrowreturn-1-s { background-position: -112px -64px; }
162 | .ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }
163 | .ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }
164 | .ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }
165 | .ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }
166 | .ui-icon-arrow-4 { background-position: 0 -80px; }
167 | .ui-icon-arrow-4-diag { background-position: -16px -80px; }
168 | .ui-icon-extlink { background-position: -32px -80px; }
169 | .ui-icon-newwin { background-position: -48px -80px; }
170 | .ui-icon-refresh { background-position: -64px -80px; }
171 | .ui-icon-shuffle { background-position: -80px -80px; }
172 | .ui-icon-transfer-e-w { background-position: -96px -80px; }
173 | .ui-icon-transferthick-e-w { background-position: -112px -80px; }
174 | .ui-icon-folder-collapsed { background-position: 0 -96px; }
175 | .ui-icon-folder-open { background-position: -16px -96px; }
176 | .ui-icon-document { background-position: -32px -96px; }
177 | .ui-icon-document-b { background-position: -48px -96px; }
178 | .ui-icon-note { background-position: -64px -96px; }
179 | .ui-icon-mail-closed { background-position: -80px -96px; }
180 | .ui-icon-mail-open { background-position: -96px -96px; }
181 | .ui-icon-suitcase { background-position: -112px -96px; }
182 | .ui-icon-comment { background-position: -128px -96px; }
183 | .ui-icon-person { background-position: -144px -96px; }
184 | .ui-icon-print { background-position: -160px -96px; }
185 | .ui-icon-trash { background-position: -176px -96px; }
186 | .ui-icon-locked { background-position: -192px -96px; }
187 | .ui-icon-unlocked { background-position: -208px -96px; }
188 | .ui-icon-bookmark { background-position: -224px -96px; }
189 | .ui-icon-tag { background-position: -240px -96px; }
190 | .ui-icon-home { background-position: 0 -112px; }
191 | .ui-icon-flag { background-position: -16px -112px; }
192 | .ui-icon-calendar { background-position: -32px -112px; }
193 | .ui-icon-cart { background-position: -48px -112px; }
194 | .ui-icon-pencil { background-position: -64px -112px; }
195 | .ui-icon-clock { background-position: -80px -112px; }
196 | .ui-icon-disk { background-position: -96px -112px; }
197 | .ui-icon-calculator { background-position: -112px -112px; }
198 | .ui-icon-zoomin { background-position: -128px -112px; }
199 | .ui-icon-zoomout { background-position: -144px -112px; }
200 | .ui-icon-search { background-position: -160px -112px; }
201 | .ui-icon-wrench { background-position: -176px -112px; }
202 | .ui-icon-gear { background-position: -192px -112px; }
203 | .ui-icon-heart { background-position: -208px -112px; }
204 | .ui-icon-star { background-position: -224px -112px; }
205 | .ui-icon-link { background-position: -240px -112px; }
206 | .ui-icon-cancel { background-position: 0 -128px; }
207 | .ui-icon-plus { background-position: -16px -128px; }
208 | .ui-icon-plusthick { background-position: -32px -128px; }
209 | .ui-icon-minus { background-position: -48px -128px; }
210 | .ui-icon-minusthick { background-position: -64px -128px; }
211 | .ui-icon-close { background-position: -80px -128px; }
212 | .ui-icon-closethick { background-position: -96px -128px; }
213 | .ui-icon-key { background-position: -112px -128px; }
214 | .ui-icon-lightbulb { background-position: -128px -128px; }
215 | .ui-icon-scissors { background-position: -144px -128px; }
216 | .ui-icon-clipboard { background-position: -160px -128px; }
217 | .ui-icon-copy { background-position: -176px -128px; }
218 | .ui-icon-contact { background-position: -192px -128px; }
219 | .ui-icon-image { background-position: -208px -128px; }
220 | .ui-icon-video { background-position: -224px -128px; }
221 | .ui-icon-script { background-position: -240px -128px; }
222 | .ui-icon-alert { background-position: 0 -144px; }
223 | .ui-icon-info { background-position: -16px -144px; }
224 | .ui-icon-notice { background-position: -32px -144px; }
225 | .ui-icon-help { background-position: -48px -144px; }
226 | .ui-icon-check { background-position: -64px -144px; }
227 | .ui-icon-bullet { background-position: -80px -144px; }
228 | .ui-icon-radio-off { background-position: -96px -144px; }
229 | .ui-icon-radio-on { background-position: -112px -144px; }
230 | .ui-icon-pin-w { background-position: -128px -144px; }
231 | .ui-icon-pin-s { background-position: -144px -144px; }
232 | .ui-icon-play { background-position: 0 -160px; }
233 | .ui-icon-pause { background-position: -16px -160px; }
234 | .ui-icon-seek-next { background-position: -32px -160px; }
235 | .ui-icon-seek-prev { background-position: -48px -160px; }
236 | .ui-icon-seek-end { background-position: -64px -160px; }
237 | .ui-icon-seek-start { background-position: -80px -160px; }
238 | /* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */
239 | .ui-icon-seek-first { background-position: -80px -160px; }
240 | .ui-icon-stop { background-position: -96px -160px; }
241 | .ui-icon-eject { background-position: -112px -160px; }
242 | .ui-icon-volume-off { background-position: -128px -160px; }
243 | .ui-icon-volume-on { background-position: -144px -160px; }
244 | .ui-icon-power { background-position: 0 -176px; }
245 | .ui-icon-signal-diag { background-position: -16px -176px; }
246 | .ui-icon-signal { background-position: -32px -176px; }
247 | .ui-icon-battery-0 { background-position: -48px -176px; }
248 | .ui-icon-battery-1 { background-position: -64px -176px; }
249 | .ui-icon-battery-2 { background-position: -80px -176px; }
250 | .ui-icon-battery-3 { background-position: -96px -176px; }
251 | .ui-icon-circle-plus { background-position: 0 -192px; }
252 | .ui-icon-circle-minus { background-position: -16px -192px; }
253 | .ui-icon-circle-close { background-position: -32px -192px; }
254 | .ui-icon-circle-triangle-e { background-position: -48px -192px; }
255 | .ui-icon-circle-triangle-s { background-position: -64px -192px; }
256 | .ui-icon-circle-triangle-w { background-position: -80px -192px; }
257 | .ui-icon-circle-triangle-n { background-position: -96px -192px; }
258 | .ui-icon-circle-arrow-e { background-position: -112px -192px; }
259 | .ui-icon-circle-arrow-s { background-position: -128px -192px; }
260 | .ui-icon-circle-arrow-w { background-position: -144px -192px; }
261 | .ui-icon-circle-arrow-n { background-position: -160px -192px; }
262 | .ui-icon-circle-zoomin { background-position: -176px -192px; }
263 | .ui-icon-circle-zoomout { background-position: -192px -192px; }
264 | .ui-icon-circle-check { background-position: -208px -192px; }
265 | .ui-icon-circlesmall-plus { background-position: 0 -208px; }
266 | .ui-icon-circlesmall-minus { background-position: -16px -208px; }
267 | .ui-icon-circlesmall-close { background-position: -32px -208px; }
268 | .ui-icon-squaresmall-plus { background-position: -48px -208px; }
269 | .ui-icon-squaresmall-minus { background-position: -64px -208px; }
270 | .ui-icon-squaresmall-close { background-position: -80px -208px; }
271 | .ui-icon-grip-dotted-vertical { background-position: 0 -224px; }
272 | .ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }
273 | .ui-icon-grip-solid-vertical { background-position: -32px -224px; }
274 | .ui-icon-grip-solid-horizontal { background-position: -48px -224px; }
275 | .ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
276 | .ui-icon-grip-diagonal-se { background-position: -80px -224px; }
277 |
278 |
279 | /* Misc visuals
280 | ----------------------------------*/
281 |
282 | /* Corner radius */
283 | .ui-corner-all, .ui-corner-top, .ui-corner-left, .ui-corner-tl { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; -khtml-border-top-left-radius: 4px; border-top-left-radius: 4px; }
284 | .ui-corner-all, .ui-corner-top, .ui-corner-right, .ui-corner-tr { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; -khtml-border-top-right-radius: 4px; border-top-right-radius: 4px; }
285 | .ui-corner-all, .ui-corner-bottom, .ui-corner-left, .ui-corner-bl { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; -khtml-border-bottom-left-radius: 4px; border-bottom-left-radius: 4px; }
286 | .ui-corner-all, .ui-corner-bottom, .ui-corner-right, .ui-corner-br { -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; -khtml-border-bottom-right-radius: 4px; border-bottom-right-radius: 4px; }
287 |
288 | /* Overlays */
289 | .ui-widget-overlay { background: #666666 url(images/ui-bg_diagonals-thick_20_666666_40x40.png) 50% 50% repeat; opacity: .50;filter:Alpha(Opacity=50); }
290 | .ui-widget-shadow { margin: -5px 0 0 -5px; padding: 5px; background: #000000 url(images/ui-bg_flat_10_000000_40x100.png) 50% 50% repeat-x; opacity: .20;filter:Alpha(Opacity=20); -moz-border-radius: 5px; -khtml-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; }/*
291 | * jQuery UI Resizable 1.8.16
292 | *
293 | * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
294 | * Dual licensed under the MIT or GPL Version 2 licenses.
295 | * http://jquery.org/license
296 | *
297 | * http://docs.jquery.com/UI/Resizable#theming
298 | */
299 | .ui-resizable { position: relative;}
300 | .ui-resizable-handle { position: absolute;font-size: 0.1px;z-index: 99999; display: block; }
301 | .ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { display: none; }
302 | .ui-resizable-n { cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0; }
303 | .ui-resizable-s { cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0; }
304 | .ui-resizable-e { cursor: e-resize; width: 7px; right: -5px; top: 0; height: 100%; }
305 | .ui-resizable-w { cursor: w-resize; width: 7px; left: -5px; top: 0; height: 100%; }
306 | .ui-resizable-se { cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px; }
307 | .ui-resizable-sw { cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px; }
308 | .ui-resizable-nw { cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px; }
309 | .ui-resizable-ne { cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;}/*
310 | * jQuery UI Selectable 1.8.16
311 | *
312 | * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
313 | * Dual licensed under the MIT or GPL Version 2 licenses.
314 | * http://jquery.org/license
315 | *
316 | * http://docs.jquery.com/UI/Selectable#theming
317 | */
318 | .ui-selectable-helper { position: absolute; z-index: 100; border:1px dotted black; }
319 | /*
320 | * jQuery UI Accordion 1.8.16
321 | *
322 | * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
323 | * Dual licensed under the MIT or GPL Version 2 licenses.
324 | * http://jquery.org/license
325 | *
326 | * http://docs.jquery.com/UI/Accordion#theming
327 | */
328 | /* IE/Win - Fix animation bug - #4615 */
329 | .ui-accordion { width: 100%; }
330 | .ui-accordion .ui-accordion-header { cursor: pointer; position: relative; margin-top: 1px; zoom: 1; }
331 | .ui-accordion .ui-accordion-li-fix { display: inline; }
332 | .ui-accordion .ui-accordion-header-active { border-bottom: 0 !important; }
333 | .ui-accordion .ui-accordion-header a { display: block; font-size: 1em; padding: .5em .5em .5em .7em; }
334 | .ui-accordion-icons .ui-accordion-header a { padding-left: 2.2em; }
335 | .ui-accordion .ui-accordion-header .ui-icon { position: absolute; left: .5em; top: 50%; margin-top: -8px; }
336 | .ui-accordion .ui-accordion-content { padding: 1em 2.2em; border-top: 0; margin-top: -2px; position: relative; top: 1px; margin-bottom: 2px; overflow: auto; display: none; zoom: 1; }
337 | .ui-accordion .ui-accordion-content-active { display: block; }
338 | /*
339 | * jQuery UI Autocomplete 1.8.16
340 | *
341 | * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
342 | * Dual licensed under the MIT or GPL Version 2 licenses.
343 | * http://jquery.org/license
344 | *
345 | * http://docs.jquery.com/UI/Autocomplete#theming
346 | */
347 | .ui-autocomplete { position: absolute; cursor: default; }
348 |
349 | /* workarounds */
350 | * html .ui-autocomplete { width:1px; } /* without this, the menu expands to 100% in IE6 */
351 |
352 | /*
353 | * jQuery UI Menu 1.8.16
354 | *
355 | * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
356 | * Dual licensed under the MIT or GPL Version 2 licenses.
357 | * http://jquery.org/license
358 | *
359 | * http://docs.jquery.com/UI/Menu#theming
360 | */
361 | .ui-menu {
362 | list-style:none;
363 | padding: 2px;
364 | margin: 0;
365 | display:block;
366 | float: left;
367 | }
368 | .ui-menu .ui-menu {
369 | margin-top: -3px;
370 | }
371 | .ui-menu .ui-menu-item {
372 | margin:0;
373 | padding: 0;
374 | zoom: 1;
375 | float: left;
376 | clear: left;
377 | width: 100%;
378 | }
379 | .ui-menu .ui-menu-item a {
380 | text-decoration:none;
381 | display:block;
382 | padding:.2em .4em;
383 | line-height:1.5;
384 | zoom:1;
385 | }
386 | .ui-menu .ui-menu-item a.ui-state-hover,
387 | .ui-menu .ui-menu-item a.ui-state-active {
388 | font-weight: normal;
389 | margin: -1px;
390 | }
391 | /*
392 | * jQuery UI Button 1.8.16
393 | *
394 | * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
395 | * Dual licensed under the MIT or GPL Version 2 licenses.
396 | * http://jquery.org/license
397 | *
398 | * http://docs.jquery.com/UI/Button#theming
399 | */
400 | .ui-button { display: inline-block; position: relative; padding: 0; margin-right: .1em; text-decoration: none !important; cursor: pointer; text-align: center; zoom: 1; overflow: visible; } /* the overflow property removes extra width in IE */
401 | .ui-button-icon-only { width: 2.2em; } /* to make room for the icon, a width needs to be set here */
402 | button.ui-button-icon-only { width: 2.4em; } /* button elements seem to need a little more width */
403 | .ui-button-icons-only { width: 3.4em; }
404 | button.ui-button-icons-only { width: 3.7em; }
405 |
406 | /*button text element */
407 | .ui-button .ui-button-text { display: block; line-height: 1.4; }
408 | .ui-button-text-only .ui-button-text { padding: .4em 1em; }
409 | .ui-button-icon-only .ui-button-text, .ui-button-icons-only .ui-button-text { padding: .4em; text-indent: -9999999px; }
410 | .ui-button-text-icon-primary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 1em .4em 2.1em; }
411 | .ui-button-text-icon-secondary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 2.1em .4em 1em; }
412 | .ui-button-text-icons .ui-button-text { padding-left: 2.1em; padding-right: 2.1em; }
413 | /* no icon support for input elements, provide padding by default */
414 | input.ui-button { padding: .4em 1em; }
415 |
416 | /*button icon element(s) */
417 | .ui-button-icon-only .ui-icon, .ui-button-text-icon-primary .ui-icon, .ui-button-text-icon-secondary .ui-icon, .ui-button-text-icons .ui-icon, .ui-button-icons-only .ui-icon { position: absolute; top: 50%; margin-top: -8px; }
418 | .ui-button-icon-only .ui-icon { left: 50%; margin-left: -8px; }
419 | .ui-button-text-icon-primary .ui-button-icon-primary, .ui-button-text-icons .ui-button-icon-primary, .ui-button-icons-only .ui-button-icon-primary { left: .5em; }
420 | .ui-button-text-icon-secondary .ui-button-icon-secondary, .ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; }
421 | .ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; }
422 |
423 | /*button sets*/
424 | .ui-buttonset { margin-right: 7px; }
425 | .ui-buttonset .ui-button { margin-left: 0; margin-right: -.3em; }
426 |
427 | /* workarounds */
428 | button.ui-button::-moz-focus-inner { border: 0; padding: 0; } /* reset extra padding in Firefox */
429 | /*
430 | * jQuery UI Dialog 1.8.16
431 | *
432 | * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
433 | * Dual licensed under the MIT or GPL Version 2 licenses.
434 | * http://jquery.org/license
435 | *
436 | * http://docs.jquery.com/UI/Dialog#theming
437 | */
438 | .ui-dialog { position: absolute; padding: .2em; width: 300px; overflow: hidden; }
439 | .ui-dialog .ui-dialog-titlebar { padding: .4em 1em; position: relative; }
440 | .ui-dialog .ui-dialog-title { float: left; margin: .1em 16px .1em 0; }
441 | .ui-dialog .ui-dialog-titlebar-close { position: absolute; right: .3em; top: 50%; width: 19px; margin: -10px 0 0 0; padding: 1px; height: 18px; }
442 | .ui-dialog .ui-dialog-titlebar-close span { display: block; margin: 1px; }
443 | .ui-dialog .ui-dialog-titlebar-close:hover, .ui-dialog .ui-dialog-titlebar-close:focus { padding: 0; }
444 | .ui-dialog .ui-dialog-content { position: relative; border: 0; padding: .5em 1em; background: none; overflow: auto; zoom: 1; }
445 | .ui-dialog .ui-dialog-buttonpane { text-align: left; border-width: 1px 0 0 0; background-image: none; margin: .5em 0 0 0; padding: .3em 1em .5em .4em; }
446 | .ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { float: right; }
447 | .ui-dialog .ui-dialog-buttonpane button { margin: .5em .4em .5em 0; cursor: pointer; }
448 | .ui-dialog .ui-resizable-se { width: 14px; height: 14px; right: 3px; bottom: 3px; }
449 | .ui-draggable .ui-dialog-titlebar { cursor: move; }
450 | /*
451 | * jQuery UI Slider 1.8.16
452 | *
453 | * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
454 | * Dual licensed under the MIT or GPL Version 2 licenses.
455 | * http://jquery.org/license
456 | *
457 | * http://docs.jquery.com/UI/Slider#theming
458 | */
459 | .ui-slider { position: relative; text-align: left; }
460 | .ui-slider .ui-slider-handle { position: absolute; z-index: 2; width: 1.2em; height: 1.2em; cursor: default; }
461 | .ui-slider .ui-slider-range { position: absolute; z-index: 1; font-size: .7em; display: block; border: 0; background-position: 0 0; }
462 |
463 | .ui-slider-horizontal { height: .8em; }
464 | .ui-slider-horizontal .ui-slider-handle { top: -.3em; margin-left: -.6em; }
465 | .ui-slider-horizontal .ui-slider-range { top: 0; height: 100%; }
466 | .ui-slider-horizontal .ui-slider-range-min { left: 0; }
467 | .ui-slider-horizontal .ui-slider-range-max { right: 0; }
468 |
469 | .ui-slider-vertical { width: .8em; height: 100px; }
470 | .ui-slider-vertical .ui-slider-handle { left: -.3em; margin-left: 0; margin-bottom: -.6em; }
471 | .ui-slider-vertical .ui-slider-range { left: 0; width: 100%; }
472 | .ui-slider-vertical .ui-slider-range-min { bottom: 0; }
473 | .ui-slider-vertical .ui-slider-range-max { top: 0; }/*
474 | * jQuery UI Tabs 1.8.16
475 | *
476 | * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
477 | * Dual licensed under the MIT or GPL Version 2 licenses.
478 | * http://jquery.org/license
479 | *
480 | * http://docs.jquery.com/UI/Tabs#theming
481 | */
482 | .ui-tabs { position: relative; padding: .2em; zoom: 1; } /* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */
483 | .ui-tabs .ui-tabs-nav { margin: 0; padding: .2em .2em 0; }
484 | .ui-tabs .ui-tabs-nav li { list-style: none; float: left; position: relative; top: 1px; margin: 0 .2em 1px 0; border-bottom: 0 !important; padding: 0; white-space: nowrap; }
485 | .ui-tabs .ui-tabs-nav li a { float: left; padding: .5em 1em; text-decoration: none; }
486 | .ui-tabs .ui-tabs-nav li.ui-tabs-selected { margin-bottom: 0; padding-bottom: 1px; }
487 | .ui-tabs .ui-tabs-nav li.ui-tabs-selected a, .ui-tabs .ui-tabs-nav li.ui-state-disabled a, .ui-tabs .ui-tabs-nav li.ui-state-processing a { cursor: text; }
488 | .ui-tabs .ui-tabs-nav li a, .ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a { cursor: pointer; } /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */
489 | .ui-tabs .ui-tabs-panel { display: block; border-width: 0; padding: 1em 1.4em; background: none; }
490 | .ui-tabs .ui-tabs-hide { display: none !important; }
491 | /*
492 | * jQuery UI Datepicker 1.8.16
493 | *
494 | * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
495 | * Dual licensed under the MIT or GPL Version 2 licenses.
496 | * http://jquery.org/license
497 | *
498 | * http://docs.jquery.com/UI/Datepicker#theming
499 | */
500 | .ui-datepicker { width: 17em; padding: .2em .2em 0; display: none; }
501 | .ui-datepicker .ui-datepicker-header { position:relative; padding:.2em 0; }
502 | .ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next { position:absolute; top: 2px; width: 1.8em; height: 1.8em; }
503 | .ui-datepicker .ui-datepicker-prev-hover, .ui-datepicker .ui-datepicker-next-hover { top: 1px; }
504 | .ui-datepicker .ui-datepicker-prev { left:2px; }
505 | .ui-datepicker .ui-datepicker-next { right:2px; }
506 | .ui-datepicker .ui-datepicker-prev-hover { left:1px; }
507 | .ui-datepicker .ui-datepicker-next-hover { right:1px; }
508 | .ui-datepicker .ui-datepicker-prev span, .ui-datepicker .ui-datepicker-next span { display: block; position: absolute; left: 50%; margin-left: -8px; top: 50%; margin-top: -8px; }
509 | .ui-datepicker .ui-datepicker-title { margin: 0 2.3em; line-height: 1.8em; text-align: center; }
510 | .ui-datepicker .ui-datepicker-title select { font-size:1em; margin:1px 0; }
511 | .ui-datepicker select.ui-datepicker-month-year {width: 100%;}
512 | .ui-datepicker select.ui-datepicker-month,
513 | .ui-datepicker select.ui-datepicker-year { width: 49%;}
514 | .ui-datepicker table {width: 100%; font-size: .9em; border-collapse: collapse; margin:0 0 .4em; }
515 | .ui-datepicker th { padding: .7em .3em; text-align: center; font-weight: bold; border: 0; }
516 | .ui-datepicker td { border: 0; padding: 1px; }
517 | .ui-datepicker td span, .ui-datepicker td a { display: block; padding: .2em; text-align: right; text-decoration: none; }
518 | .ui-datepicker .ui-datepicker-buttonpane { background-image: none; margin: .7em 0 0 0; padding:0 .2em; border-left: 0; border-right: 0; border-bottom: 0; }
519 | .ui-datepicker .ui-datepicker-buttonpane button { float: right; margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; }
520 | .ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { float:left; }
521 |
522 | /* with multiple calendars */
523 | .ui-datepicker.ui-datepicker-multi { width:auto; }
524 | .ui-datepicker-multi .ui-datepicker-group { float:left; }
525 | .ui-datepicker-multi .ui-datepicker-group table { width:95%; margin:0 auto .4em; }
526 | .ui-datepicker-multi-2 .ui-datepicker-group { width:50%; }
527 | .ui-datepicker-multi-3 .ui-datepicker-group { width:33.3%; }
528 | .ui-datepicker-multi-4 .ui-datepicker-group { width:25%; }
529 | .ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header { border-left-width:0; }
530 | .ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { border-left-width:0; }
531 | .ui-datepicker-multi .ui-datepicker-buttonpane { clear:left; }
532 | .ui-datepicker-row-break { clear:both; width:100%; font-size:0em; }
533 |
534 | /* RTL support */
535 | .ui-datepicker-rtl { direction: rtl; }
536 | .ui-datepicker-rtl .ui-datepicker-prev { right: 2px; left: auto; }
537 | .ui-datepicker-rtl .ui-datepicker-next { left: 2px; right: auto; }
538 | .ui-datepicker-rtl .ui-datepicker-prev:hover { right: 1px; left: auto; }
539 | .ui-datepicker-rtl .ui-datepicker-next:hover { left: 1px; right: auto; }
540 | .ui-datepicker-rtl .ui-datepicker-buttonpane { clear:right; }
541 | .ui-datepicker-rtl .ui-datepicker-buttonpane button { float: left; }
542 | .ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current { float:right; }
543 | .ui-datepicker-rtl .ui-datepicker-group { float:right; }
544 | .ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
545 | .ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
546 |
547 | /* IE6 IFRAME FIX (taken from datepicker 1.5.3 */
548 | .ui-datepicker-cover {
549 | display: none; /*sorry for IE5*/
550 | display/**/: block; /*sorry for IE5*/
551 | position: absolute; /*must have*/
552 | z-index: -1; /*must have*/
553 | filter: mask(); /*must have*/
554 | top: -4px; /*must have*/
555 | left: -4px; /*must have*/
556 | width: 200px; /*must have*/
557 | height: 200px; /*must have*/
558 | }/*
559 | * jQuery UI Progressbar 1.8.16
560 | *
561 | * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
562 | * Dual licensed under the MIT or GPL Version 2 licenses.
563 | * http://jquery.org/license
564 | *
565 | * http://docs.jquery.com/UI/Progressbar#theming
566 | */
567 | .ui-progressbar { height:2em; text-align: left; }
568 | .ui-progressbar .ui-progressbar-value {margin: -1px; height:100%; }
--------------------------------------------------------------------------------
/editor/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Gantt Chart editor
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/editor/jsganttchart.editor.js:
--------------------------------------------------------------------------------
1 | /*jslint nomen: true*/
2 | (function ($, _, Backbone, JSGanttChart) {
3 | 'use strict';
4 |
5 | var ToolbarView = Backbone.View.extend({
6 | className: "toolbar",
7 | $el: undefined,
8 |
9 | initialize: function () {
10 | this.$el = $(this.el);
11 | },
12 |
13 | render: function () {
14 | var this_ = this;
15 | $.fn.append.apply(this.$el.html(""), _(this.options.buttons).map(function (button) {
16 | return jQuery('')
17 | .click(function () {
18 | button.action.call(this_);
19 | });
20 | }));
21 | return this;
22 | }
23 | }),
24 |
25 | FieldSetView = Backbone.View.extend({
26 | className: "fieldset",
27 | tagName: "table",
28 | $el: undefined,
29 | fieldInputs: undefined,
30 | model: undefined,
31 | initialize: function () {
32 | this.$el = $(this.el);
33 | },
34 | render: function () {
35 | var this_ = this;
36 | this.fieldInputs = [];
37 | $.fn.append.apply(this.$el.html(""), _(this.options.fields).map(function (field) {
38 | var html = field.type === "textarea" ? '' : '',
39 | row = jQuery('' + field.name + ' | ' + html + ' |
'),
40 | input = row.find("input, textarea");
41 | this_.fieldInputs.push(input);
42 | switch (field.type) {
43 | case "date":
44 | input.datepicker({
45 | dateformar: "D d M yy"
46 | });
47 | break;
48 | }
49 | return row;
50 | }));
51 | return this;
52 | },
53 | load: function (model) {
54 | var this_ = this;
55 | this.model = model;
56 | _(this.options.fields).each(function (field, i) {
57 | this_.fieldInputs[i].val(field.load(model));
58 | });
59 | },
60 | save: function() {
61 | var this_ = this,
62 | newsettings = {};
63 | _(this.options.fields).each(function (field, i) {
64 | _(newsettings).extend(field.save(this_.model, this_.fieldInputs[i].val()));
65 | });
66 | this.model.save(newsettings);
67 | }
68 | }),
69 |
70 | DialogView = Backbone.View.extend({
71 | className: "dialog",
72 | $el: undefined,
73 | initialize: function () {
74 | this.dialogOptions = {
75 | autoOpen: false,
76 | show: "fade",
77 | hide: "fade"
78 | };
79 | this.$el = $(this.el);
80 | },
81 | show: function () {
82 | this.$el.dialog("open");
83 | },
84 | hide: function () {
85 | this.$el.dialog("close");
86 | },
87 | render: function () {
88 | this.$el.dialog(this.dialogOptions);
89 | return this;
90 | }
91 | }),
92 |
93 | EditDialogView = DialogView.extend({
94 | fieldset: undefined,
95 | model: undefined,
96 | render: function () {
97 | var this_ = this,
98 | apply = function () { this_.fieldset.save(); }
99 |
100 | _(this.dialogOptions).extend({
101 | buttons: {
102 | Apply: function () { apply.call(); },
103 | OK: function () {
104 | apply.call();
105 | this_.hide();
106 | },
107 | Cancel: function () { this_.hide(); },
108 | Delete: function () {
109 | this_.model.destroy();
110 | this_.hide();
111 | }
112 | },
113 | title: "Add / Edit stage",
114 | resizable: false,
115 | width: 600
116 | });
117 |
118 | this.fieldset = new FieldSetView({
119 | fields: [
120 | {
121 | name: "ID",
122 | load: function (model) { return model.get("id"); },
123 | save: function (model, value) {
124 | return { id: value };
125 | model.collection.sort();
126 | } },
127 | {
128 | name: "Order",
129 | load: function (model) { return model.get("order"); },
130 | save: function (model, value) { return { order: parseInt(value) }; } },
131 | {
132 | name: "Name",
133 | load: function (model) { return model.get("name"); },
134 | save: function (model, value) { return { name: value }; } },
135 | {
136 | name: "Description",
137 | type: "textarea",
138 | load: function (model) { return model.get("description"); },
139 | save: function (model, value) { return { description: value }; } },
140 | {
141 | name: "Start date",
142 | type: "date",
143 | load: function (model) { return model.get("startDate"); },
144 | save: function (model, value) { return { startDate: value ? new Date(value) : undefined }; } },
145 | {
146 | name: "End date",
147 | type: "date",
148 | load: function (model) { return model.get("endDate"); },
149 | save: function (model, value) { return { endDate: value ? new Date(value) : undefined }; } },
150 | {
151 | name: "Slack end date",
152 | type: "date",
153 | load: function (model) { return model.get("slackEndDate"); },
154 | save: function (model, value) { return { slackEndDate: value ? new Date(value) : undefined }; } },
155 | {
156 | name: "Type",
157 | load: function (model) { return model.get("type"); },
158 | save: function (model, value) { return { type: value }; } },
159 | {
160 | name: "Parent",
161 | load: function (model) { return model.get("parentElement"); },
162 | save: function (model, value) { console.log({ parentElement: value.trim() === "" ? undefined : value.trim() });return { parentElement: value.trim() === "" ? undefined : value.trim() }; } },
163 | {
164 | name: "Percentage done",
165 | load: function (model) { return model.get("percentageDone"); },
166 | save: function (model, value) { return { percentageDone: value === undefined ? undefined : parseInt(value) }; } },
167 | {
168 | name: "Hours expected",
169 | load: function (model) { return model.get("estimatedHours"); },
170 | save: function (model, value) { return { estimatedHours: value === undefined ? undefined : parseInt(value) }; } },
171 | {
172 | name: "Resource (comma separated)",
173 | load: function (model) { return (model.get("resources") || []).join(", "); },
174 | save: function (model, value) {
175 | return { resources: _(value.split(",")).chain()
176 | .map(function (r) { return r.trim(); })
177 | .reject(function (r) { return r === ""; }).value() };
178 | }
179 | },
180 | {
181 | name: "Predecessors (dependancies,
comma separated)",
182 | load: function (model) { return (model.get("predecessors") || []).join(", "); },
183 | save: function (model, value) {
184 | return { predecessors: _(value.split(",")).chain()
185 | .map(function (r) { return r.trim(); })
186 | .reject(function (r) { return r === ""; }).value() };
187 | }
188 | },
189 | {
190 | name: "Icons",
191 | type: "textarea",
192 | load: function (model) { return JSON.stringify(model.get("icons") || []); },
193 | save: function (model, value) {
194 | var icons = JSON.parse(value);
195 | _(icons).each(function (icon) {
196 | icon.date = new Date(icon.date);
197 | });
198 | return { icons: icons };
199 | }
200 | }
201 | ]
202 | });
203 |
204 | this.$el.html("").append(this.fieldset.render().el);
205 | return DialogView.prototype.render.apply(this);
206 | },
207 | load: function (model) {
208 | this.model = model;
209 | this.fieldset.load(model);
210 | }
211 | }),
212 |
213 | JSONDialogView = DialogView.extend({
214 | textarea: undefined,
215 |
216 | render: function () {
217 | var this_ = this,
218 | textarea = jQuery(""),
219 | apply = function () {
220 | this_.options.gantt.setJSON(JSON.parse(this_.val()));
221 | },
222 | toolbar = new ToolbarView({
223 | buttons: [
224 | {
225 | name: "Apply",
226 | action: function () {
227 | apply.call();
228 | }
229 | },
230 | {
231 | name: "OK",
232 | action: function () {
233 | apply.call();
234 | this_.hide();
235 | }
236 | },
237 | {
238 | name: "Cancel",
239 | action: function () {
240 | this_.hide();
241 | }
242 | },
243 | ]
244 | });
245 |
246 | this.textarea = textarea;
247 |
248 | this.$el.html("").append(textarea, toolbar.render().el).hide();
249 |
250 | return DialogView.prototype.render.apply(this);
251 | },
252 | val: function () { return $.fn.val.apply(this.textarea, arguments); }
253 | }),
254 |
255 | EditorView = Backbone.View.extend({
256 | className: "editor",
257 | $el: undefined,
258 | gantt: undefined,
259 | editDialog: undefined,
260 | jsonDialog: undefined,
261 | toolbar: undefined,
262 |
263 | initialize: function () {
264 | var this_ = this;
265 | this.$el = $(this.el);
266 | this.gantt = this.options.gantt;
267 | this.editDialog = new EditDialogView();
268 | this.jsonDialog = new JSONDialogView({ gantt: this.gantt });
269 | this.toolbar = new ToolbarView({
270 | buttons: [
271 | {
272 | name: "New Stage",
273 | action: function () {
274 | this_.editDialog.load(this_.gantt.newElementModel());
275 | this_.editDialog.show();
276 | }
277 | },
278 | {
279 | name: "View Resources",
280 | action: function () {
281 | this_.resourcesDialog.show();
282 | }
283 | },
284 | {
285 | name: "View Types",
286 | action: function () {
287 | this_.resourcesDialog.show();
288 | }
289 | },
290 | {
291 | name: "View/Edit JSON",
292 | action: function () {
293 | this_.jsonDialog.val(JSON.stringify(this_.gantt.getJSON(), undefined, " "));
294 | this_.jsonDialog.show();
295 | }
296 | }
297 | ]
298 | });
299 |
300 | this.gantt.bind("row_click", function (e, model) {
301 | e.preventDefault();
302 | e.stopPropagation();
303 | this_.editDialog.load(model);
304 | this_.editDialog.show();
305 | });
306 | },
307 | render: function () {
308 | this.$el.html("").append(this.gantt.render().el, this.toolbar.render().el);
309 | this.editDialog.render();
310 | this.jsonDialog.render();
311 | return this;
312 | }
313 | }),
314 |
315 | editor;
316 |
317 | JSGanttChart.Editor = function (options) {
318 | editor = new EditorView({ gantt: options.gantt });
319 | };
320 |
321 | _(JSGanttChart.Editor).extend({
322 | create: function () {
323 | var F = function () {}, // Dummy function
324 | o;
325 | F.prototype = JSGanttChart.Editor.prototype;
326 | o = new F();
327 | JSGanttChart.Editor.apply(o, arguments);
328 | o.constructor = JSGanttChart.Editor;
329 | return o;
330 | }
331 | });
332 |
333 | _(JSGanttChart.Editor.prototype).extend(Backbone.Events, {
334 | render: function () {
335 | return editor.render();
336 | }
337 | });
338 |
339 | }(jQuery, _, Backbone, JSGanttChart));
--------------------------------------------------------------------------------
/editor/style.css:
--------------------------------------------------------------------------------
1 | /* Editor elements */
2 |
3 | .editor {
4 | margin-top: 50px;
5 | }
6 |
7 | body, table, input {
8 | font-family: verdana, sans-serif;
9 | font-size: 11px;
10 | color: #333;
11 | }
12 |
13 | .editor > .toolbar {
14 | position: fixed;
15 | top: 0;
16 | left: 0;
17 | right: 0;
18 | background: #f7f7f6;
19 | }
20 |
21 | .toolbar {
22 | text-align: center;
23 | }
24 |
25 | .fieldset th {
26 | text-align: right;
27 | }
28 |
29 | .fieldset, .fieldset td, .fieldset th {
30 | margin: 0;
31 | padding: 1px;
32 | border: 0;
33 | }
34 |
35 | .toolbar {
36 | padding: 5px;
37 | }
38 |
39 | .toolbar input {
40 | border: 1px solid #ccc;
41 | background: #e7e7e7;
42 | font-size: 11px;
43 | padding: 4px;
44 | }
45 |
46 | #editform .form, #jsonform .form {
47 | margin: 0 auto;
48 | width: 100%;
49 | height: 100%;
50 | }
51 |
52 | #jsonform .form {
53 | width: 99%;
54 | height: 100%;
55 | }
56 |
57 | #editform .form > table {
58 | margin: 0 auto;
59 | }
60 |
61 | textarea {
62 | width: 100%;
63 | }
64 |
65 | .gantt-container tr {
66 | cursor: pointer;
67 | }
68 |
--------------------------------------------------------------------------------
/gantt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlamingTempura/JSGanttChart/f8a1355c5ee9d40a8741cf7c2926d55a88a1ede7/gantt.png
--------------------------------------------------------------------------------
/jsganttchart.js:
--------------------------------------------------------------------------------
1 | /*jslint nomen: true*/
2 | (function ($, _, Backbone) {
3 | 'use strict';
4 |
5 | var root = window,
6 |
7 | jsgtThis,
8 |
9 | ganttView,
10 | collection,
11 |
12 | monthNames = [ "January", "February", "March", "April", "May", "June",
13 | "July", "August", "September", "October", "November", "December" ],
14 |
15 | fieldNames = {
16 | name: 'Project stage',
17 | resources: "Resources",
18 | percentageDone: "Status",
19 | estimatedHours: 'Estim. Hours'
20 | },
21 |
22 | mandatoryFields = ['id', 'name', 'startDate'],
23 |
24 | GanttElementModel = Backbone.Model.extend({
25 | defaults: {
26 | order: 0,
27 | name: undefined,
28 | description: undefined,
29 | startDate: undefined,
30 | endDate: undefined,
31 | slackEndDate: undefined,
32 | type: undefined,
33 | percentageDone: undefined,
34 | hoursExpected: undefined,
35 | resources: undefined,
36 | predecessors: undefined,
37 | icons: undefined
38 | },
39 |
40 | collection: undefined,
41 |
42 | initialize: function (model, options) {
43 | this.collection = options.collection;
44 | this.normalize();
45 | if (this.collection.hasOwnProperty("localStorage")) {
46 | this.save();
47 | }
48 | },
49 |
50 | normalize: function () {
51 | var this_ = this;
52 | // Ensure the element has all mandatory fields
53 | _(mandatoryFields).each(function (field) {
54 | if (!this_.has(field)) {
55 | throw "element " + this_.get("id") + " is missing " + field + ".";
56 | }
57 | });
58 |
59 | if (!_(this.get("startDate")).isDate()) {
60 | this.set({ startDate: new Date(this.get("startDate")) });
61 | }
62 |
63 | if (this.has("endDate") && !_(this.get("endDate")).isDate()) {
64 | this.set({ endDate: new Date(this.get("endDate")) });
65 | }
66 |
67 | if (!this.has("endDate")) {
68 | this.set({ endDate: new Date(this.get("startDate").getTime()) });
69 | if (this.has("duration")) {
70 | this.get("endDate").setDate(this.get("startDate").getDate() + this.get("duration"));
71 | this.unset("duration");
72 | } else {
73 | this.get("endDate").setDate(this.get("startDate").getDate() + 1);
74 | }
75 | }
76 |
77 | if (this.has("slackEndDate")) {
78 | if (!_(this.get("slackEndDate")).isDate()) {
79 | this.set({ slackEndDate: new Date(this.get("slackEndDate")) });
80 | }
81 | } else if (this.has("slackDuration")) {
82 | var date = new Date(this.get("endDate"));
83 | date.setDate(date.getDate() + this.get("slackDuration"));
84 | this.set({ slackEndDate: date });
85 | }
86 |
87 | if (this.has("elements")) {
88 | this.collection.add(_(this.get("elements")).map(function (el) {
89 | var element = _(el).clone();
90 | element.parentElement = this_.get("id");
91 | return element;
92 | }));
93 | this.unset("elements");
94 | }
95 |
96 | if (this.has("icons")) {
97 | _(this.get("icons")).each(function (icon) {
98 | if (!_(icon.date).isDate()) {
99 | icon.date = new Date(icon.date);
100 | }
101 | });
102 | }
103 | }
104 | }),
105 |
106 | GanttElementCollection = Backbone.Collection.extend({
107 | model: GanttElementModel,
108 | initialize: function (models, options) {
109 | var this_ = this,
110 | triggerChange = function () { this_.trigger("change"); };
111 |
112 | this.bind("add", triggerChange);
113 | this.bind("remove", triggerChange);
114 | this.bind("reset", triggerChange);
115 | },
116 |
117 | add: function (models) {
118 | var i,
119 | l;
120 | if (_.isArray(models)) {
121 | for (i = 0, l = models.length; i < l; i = i + 1) {
122 | this.add(models[i]);
123 | }
124 | } else {
125 | Backbone.Collection.prototype.add.call(this, models, { at: this.length }); // Order properly
126 | }
127 | return this;
128 | },
129 |
130 | sort: function (model) {
131 | var this_ = this;
132 | this.reset(collection.sortBy(function (model) {
133 | var order;
134 | if (model.has("parentElement") && this_.get(model.get("parentElement"))) { /// HACK
135 | order = this_.get(model.get("parentElement")).get("order") + (0.00001 * model.get("order") + 0.00001);
136 | } else {
137 | order = model.get("order");
138 | }
139 | console.log(model.get("name"), "Order", order, "Parent", model.get("parentElement"))
140 | return order;
141 | }));
142 | }
143 | }),
144 |
145 | /* options:
146 | fields
147 | */
148 | DataTableView = Backbone.View.extend({
149 | className: "gantt-data-table",
150 | tagName: "table",
151 | $el: undefined,
152 |
153 | initialize: function () {
154 | _.bindAll(this, "render");
155 | this.options.collection.bind("change", this.render);
156 | this.$el = $(this.el);
157 | },
158 |
159 | highlight: function (model) {
160 | if (model) {
161 | this.$el.find('.jsgt_' + model.get("id"))
162 | .addClass("highlight")
163 | .siblings()
164 | .removeClass("highlight");
165 | } else {
166 | this.$el.find('tr').removeClass("highlight");
167 | }
168 | },
169 |
170 | render: function () {
171 | var this_ = this;
172 |
173 | this.$el.html('');
174 |
175 | // Populate headers
176 | this.$el.append($.fn.append.apply($('
'), _(this_.options.fields).map(function (field) {
177 | return $('' + fieldNames[field] + ' | ');
178 | })));
179 |
180 | // Populate data
181 | $.fn.append.apply(this.$el, this.options.collection.map(function (model) {
182 | var row = $('
');
183 | return $.fn.append.apply(row, _(this_.options.fields).map(function (field) {
184 | var str = (model.has(field) ? model.get(field) : '');
185 | if (field === "name" && model.get("parentElement") && model.get("parentElement").trim()) {
186 | str = " " + str;
187 | row.addClass("child");
188 | } else if (field === "resources") {
189 | str = _(model.get(field)).reduce(function (memo, resource) {
190 | return (memo ? memo + ", " : "") + this_.options.resources[resource];
191 | }, "");
192 | } else if (field === "percentageDone") {
193 | if (str === 100) {
194 | str = 'Done
';
195 | } else if (!str || str === 0) {
196 | str = 'Not started
';
197 | } else {
198 | str = 'In progress: ' + str + '%
';
199 | }
200 | }
201 | return $('' + (str || " ") + ' | ');
202 | }))
203 | .click(function (e) { this_.trigger("row_click", e, model); })
204 | .mouseenter(function (e) { this_.trigger("row_enter", e, model); })
205 | .mouseleave(function (e) { this_.trigger("row_leave", e, model); });
206 | }));
207 |
208 | return this;
209 | }
210 | }),
211 |
212 | GanttElementView = Backbone.View.extend({
213 | className: "gantt-element",
214 | $el: undefined,
215 |
216 | initialize: function () {
217 | _.bindAll(this, "render");
218 | this.options.model.bind("change", this.render);
219 | this.$el = $(this.el);
220 | },
221 |
222 | render: function () {
223 | var this_ = this,
224 | model = this.options.model,
225 | noOfDays = Math.round((model.get("endDate").getTime() - model.get("startDate").getTime()) / (24 * 60 * 60 * 1000)),
226 | dayFromStart = Math.round((model.get("startDate").getTime() - this.options.firstDate.getTime()) / (24 * 60 * 60 * 1000)),
227 | el;
228 |
229 | this.$el.css({ width: noOfDays * 23 - 3 });
230 |
231 | if (model.has("type") && this.options.types.hasOwnProperty(model.get("type"))) {
232 | this.$el.css({ borderBottomColor: this.options.types[model.get("type")].color });
233 | }
234 |
235 | if (model.has("percentageDone") && model.get("percentageDone") > 0) {
236 | el = $('');
237 | el.css({ width: model.get("percentageDone") + "%" });
238 | this.$el.append(el, $('' + (model.get("percentageDone") < 100 ? model.get("percentageDone") + "% done" : "Done") + '
'));
239 | }
240 |
241 | if (model.has("slackEndDate")) {
242 | el = $('');
243 | noOfDays = Math.round((model.get("slackEndDate").getTime() - model.get("endDate").getTime()) / (24 * 60 * 60 * 1000));
244 | el.css({ left: "100%", width: noOfDays * 23 });
245 | this.$el.append(el);
246 | }
247 |
248 | if (model.has("predecessors")) {
249 | $.fn.append.apply(this.$el, _(model.get("predecessors")).map(function (predecessor) {
250 | var predecessorModel = this_.options.collection.get(predecessor);
251 | if (predecessorModel) {
252 | el = $('');
253 | noOfDays = Math.round((model.get("startDate").getTime() - predecessorModel.get("endDate").getTime()) / (24 * 60 * 60 * 1000));
254 | var noOfRows = collection.indexOf(model) - collection.indexOf(predecessorModel);
255 | el.css({ right: "100%", bottom: "100%", width: noOfDays * 23, height: noOfRows * 17 - 5 });
256 | return el;
257 | }
258 | }));
259 | }
260 |
261 | return this;
262 | }
263 | }),
264 |
265 | GanttTableView = Backbone.View.extend({
266 | className: "gantt-table",
267 | tagName: "table",
268 | $el: undefined,
269 |
270 | initialize: function () {
271 | _.bindAll(this, "render");
272 | this.options.collection.bind("change", this.render);
273 | this.$el = $(this.el);
274 | },
275 |
276 | highlight: function (model) {
277 | if (model) {
278 | this.$el.find('.jsgt_' + model.get("id"))
279 | .addClass("highlight")
280 | .siblings()
281 | .removeClass("highlight");
282 | } else {
283 | this.$el.find('tr').removeClass("highlight");
284 | }
285 | },
286 |
287 | render: function () {
288 | this.$el.html('');
289 | var this_ = this,
290 | firstDate,
291 | lastDate,
292 | dateIterator,
293 | today = new Date();
294 |
295 | // Determine when the gantt chart starts and finishes
296 | this.options.collection.each(function (model) {
297 | var startDate = model.get("startDate").getTime(),
298 | endDate = model.get("endDate").getTime();
299 | firstDate = (!firstDate || startDate < firstDate) ? startDate : firstDate;
300 | lastDate = (!lastDate || endDate > lastDate) ? endDate : lastDate;
301 | });
302 |
303 | firstDate = new Date(firstDate);
304 | lastDate = new Date(lastDate);
305 |
306 | var monthRow = $('
'),
307 | dayRow = $('
'),
308 | currMonth,
309 | currMonthSize,
310 | currMonthEl;
311 |
312 | dateIterator = new Date(firstDate.getTime());
313 | // Populate days
314 | while (dateIterator <= lastDate) {
315 | if (dateIterator.getMonth() !== currMonth) {
316 | if (currMonthEl) {
317 | currMonthEl.attr({ colspan: currMonthSize });
318 | }
319 | currMonth = dateIterator.getMonth();
320 | currMonthSize = 0;
321 | currMonthEl = $('' + monthNames[dateIterator.getMonth()] + ' ' + dateIterator.getFullYear() + ' | ');
322 | monthRow.append(currMonthEl);
323 | }
324 | var el = $('' + dateIterator.getDate() + ' | '),
325 | dateString = dateIterator.toDateString();
326 |
327 | if (today.toDateString() === dateString) {
328 | el.addClass("important");
329 | }
330 | if (dateIterator.getDay() === 6) {
331 | el.addClass("markend");
332 | }
333 | this.options.collection.map(function (model) {
334 | if (model.has("icons")) {
335 | model.get("icons").map(function (icon) {
336 | if (icon.date.toDateString() === dateString) {
337 | el.append('');
338 | }
339 | });
340 | }
341 | });
342 | dayRow.append(el);
343 | dateIterator.setDate(dateIterator.getDate() + 1);
344 | currMonthSize = currMonthSize + 1;
345 | }
346 | if (currMonthEl) {
347 | currMonthEl.attr({ colspan: currMonthSize });
348 | }
349 | this.$el.append(monthRow, dayRow);
350 |
351 | $.fn.append.apply(this.$el, this.options.collection.map(function (model) {
352 | var row = $('
'),
353 | elementView = new GanttElementView({
354 | model: model,
355 | firstDate: firstDate,
356 | types: this_.options.types,
357 | collection: this_.options.collection
358 | }),
359 | dateIterator = new Date(firstDate.getTime()),
360 | elementHolder = $('
'),
361 | modelDate = model.get("startDate");
362 |
363 | if (model.has("parentElement")) {
364 | row.addClass("child");
365 | }
366 |
367 | var html = "",
368 | classes = [];
369 |
370 | while (dateIterator <= lastDate) {
371 | classes = "";
372 |
373 | if (dateIterator.getDay() === 6) {
374 | classes += " markend";
375 | }
376 | if (dateIterator.getDay() === 6 || dateIterator.getDay() === 0) {
377 | classes += " weekend";
378 | }
379 | html += '';
380 |
381 | _(model.get("icons")).each(function (icon) {
382 | if (icon.date.toDateString() === dateIterator.toDateString()) {
383 | html += ' ' + icon.description + ' ';
384 | }
385 | });
386 |
387 | html += ' | ';
388 |
389 | dateIterator.setDate(dateIterator.getDate() + 1);
390 | }
391 |
392 | row.append(html);
393 |
394 | row.click(function (e) { this_.trigger("row_click", e, model); })
395 | .mouseenter(function (e) { this_.trigger("row_enter", e, model); })
396 | .mouseleave(function (e) { this_.trigger("row_leave", e, model); })
397 | .find("." + modelDate.getDate() + "-" + modelDate.getMonth() + "-" + modelDate.getFullYear())
398 | .append(elementHolder.append(elementView.render().el));
399 |
400 | return row;
401 | }));
402 |
403 | return this;
404 | }
405 | }),
406 |
407 | KeyView = Backbone.View.extend({
408 | className: "gantt-key",
409 | $el: undefined,
410 |
411 | initialize: function () {
412 | _.bindAll(this, "render");
413 | this.$el = $(this.el);
414 | },
415 |
416 | render: function () {
417 | this.$el.append("Key");
418 |
419 | $.fn.append.apply(this.$el, _(this.options.types).map(function (type) {
420 | return '';
421 | }));
422 |
423 | return this;
424 | }
425 | }),
426 |
427 | // Options:
428 | // collection: collection of type GanttElementCollection
429 | // displayKey
430 | // fields: array of fields
431 | // types: mapping of types to name+colour
432 | GanttContainerView = Backbone.View.extend({
433 | className: "gantt-container",
434 | dataView: undefined,
435 | ganttView: undefined,
436 | keyView: undefined,
437 | $el: undefined,
438 |
439 | initialize: function () {
440 | var this_ = this;
441 |
442 | this.dataView = new DataTableView({
443 | collection: this.options.collection,
444 | fields: this.options.fields,
445 | resources: this.options.resources
446 | });
447 | this.ganttView = new GanttTableView({
448 | collection: this.options.collection,
449 | types: this.options.types
450 | });
451 | this.keyView = new KeyView({ types: this.options.types });
452 | this.$el = $(this.el);
453 |
454 | var rowClick = function (e, model) {
455 | this_.trigger("row_click", e, model);
456 | },
457 | rowEnter = function (e, model) {
458 | this_.dataView.highlight(model);
459 | this_.ganttView.highlight(model);
460 | this_.trigger("row_enter", e, model);
461 | },
462 | rowLeave = function (e, model) {
463 | this_.dataView.highlight();
464 | this_.ganttView.highlight();
465 | this_.trigger("row_leave", e, model);
466 | };
467 |
468 | this.dataView.bind("row_click", rowClick);
469 | this.ganttView.bind("row_click", rowClick);
470 |
471 | this.dataView.bind("row_enter", rowEnter);
472 | this.ganttView.bind("row_enter", rowEnter);
473 | this.dataView.bind("row_leave", rowLeave);
474 | this.ganttView.bind("row_leave", rowLeave);
475 | },
476 |
477 | render: function () {
478 | var this_ = this;
479 | this.$el.html('')
480 | .append(this.dataView.render().el, this.ganttView.render().el);
481 | if (this.options.displayKey) {
482 | this.$el.append(this.keyView.render().el);
483 | }
484 | setTimeout(function () {
485 | console.log(this_.dataView.$el.outerWidth())
486 | this_.ganttView.$el.css({ marginLeft: this_.dataView.$el.outerWidth() });
487 | });
488 | return this;
489 | }
490 | }),
491 |
492 | JSGanttChart = root.JSGanttChart = function (options) {
493 | jsgtThis = this;
494 |
495 | if (!options) {
496 | options = {};
497 | }
498 |
499 | _(options).defaults({
500 | displayKey: true,
501 | fields: [ "name", "resources", "percentageDone", "estimatedHours" ],
502 | types: {},
503 | resources: {}
504 | });
505 |
506 | collection = new GanttElementCollection(options.elements);
507 |
508 | if (options.hasOwnProperty("localStorage")) {
509 | collection.localStorage = options.localStorage;
510 | collection.fetch();
511 | collection.sort();
512 | }
513 |
514 | ganttView = new GanttContainerView({
515 | collection: collection,
516 | displayKey: options.displayKey,
517 | fields: options.fields,
518 | types: options.types,
519 | resources: options.resources
520 | });
521 |
522 | ganttView.bind("row_click", function (e, model) {
523 | jsgtThis.trigger("row_click", e, model);
524 | });
525 | };
526 |
527 | _(JSGanttChart).extend({
528 | create: function () {
529 | var F = function () {}, // Dummy function
530 | o;
531 | F.prototype = JSGanttChart.prototype;
532 | o = new F();
533 | JSGanttChart.apply(o, arguments);
534 | o.constructor = JSGanttChart;
535 | return o;
536 | }
537 | });
538 |
539 | _(JSGanttChart.prototype).extend(Backbone.Events, {
540 | setElements: function (newelements) {},
541 |
542 | setTypes: function (newtypes) {},
543 |
544 | render: function () {
545 | return ganttView.render();
546 | },
547 |
548 | newElementModel: function () {
549 | var model = new GanttElementModel({
550 | id: Math.round(Math.random() * 1000000),
551 | name: "New stage",
552 | startDate: new Date()
553 | }, { collection: collection });
554 | collection.add(model);
555 | return model;
556 | },
557 |
558 | getJSON: function () {
559 | return collection.toJSON();
560 | },
561 |
562 | setJSON: function (json) {
563 | collection.reset(json);
564 | }
565 | });
566 |
567 | }(jQuery, _, Backbone));
--------------------------------------------------------------------------------
/lib/backbone.js:
--------------------------------------------------------------------------------
1 | // Backbone.js 0.5.3
2 | // (c) 2010 Jeremy Ashkenas, DocumentCloud Inc.
3 | // Backbone may be freely distributed under the MIT license.
4 | // For all details and documentation:
5 | // http://documentcloud.github.com/backbone
6 |
7 | (function(){
8 |
9 | // Initial Setup
10 | // -------------
11 |
12 | // Save a reference to the global object.
13 | var root = this;
14 |
15 | // Save the previous value of the `Backbone` variable.
16 | var previousBackbone = root.Backbone;
17 |
18 | // The top-level namespace. All public Backbone classes and modules will
19 | // be attached to this. Exported for both CommonJS and the browser.
20 | var Backbone;
21 | if (typeof exports !== 'undefined') {
22 | Backbone = exports;
23 | } else {
24 | Backbone = root.Backbone = {};
25 | }
26 |
27 | // Current version of the library. Keep in sync with `package.json`.
28 | Backbone.VERSION = '0.5.3';
29 |
30 | // Require Underscore, if we're on the server, and it's not already present.
31 | var _ = root._;
32 | if (!_ && (typeof require !== 'undefined')) _ = require('underscore')._;
33 |
34 | // For Backbone's purposes, jQuery or Zepto owns the `$` variable.
35 | var $ = root.jQuery || root.Zepto;
36 |
37 | // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable
38 | // to its previous owner. Returns a reference to this Backbone object.
39 | Backbone.noConflict = function() {
40 | root.Backbone = previousBackbone;
41 | return this;
42 | };
43 |
44 | // Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option will
45 | // fake `"PUT"` and `"DELETE"` requests via the `_method` parameter and set a
46 | // `X-Http-Method-Override` header.
47 | Backbone.emulateHTTP = false;
48 |
49 | // Turn on `emulateJSON` to support legacy servers that can't deal with direct
50 | // `application/json` requests ... will encode the body as
51 | // `application/x-www-form-urlencoded` instead and will send the model in a
52 | // form param named `model`.
53 | Backbone.emulateJSON = false;
54 |
55 | // Backbone.Events
56 | // -----------------
57 |
58 | // A module that can be mixed in to *any object* in order to provide it with
59 | // custom events. You may `bind` or `unbind` a callback function to an event;
60 | // `trigger`-ing an event fires all callbacks in succession.
61 | //
62 | // var object = {};
63 | // _.extend(object, Backbone.Events);
64 | // object.bind('expand', function(){ alert('expanded'); });
65 | // object.trigger('expand');
66 | //
67 | Backbone.Events = {
68 |
69 | // Bind an event, specified by a string name, `ev`, to a `callback` function.
70 | // Passing `"all"` will bind the callback to all events fired.
71 | bind : function(ev, callback, context) {
72 | var calls = this._callbacks || (this._callbacks = {});
73 | var list = calls[ev] || (calls[ev] = []);
74 | list.push([callback, context]);
75 | return this;
76 | },
77 |
78 | // Remove one or many callbacks. If `callback` is null, removes all
79 | // callbacks for the event. If `ev` is null, removes all bound callbacks
80 | // for all events.
81 | unbind : function(ev, callback) {
82 | var calls;
83 | if (!ev) {
84 | this._callbacks = {};
85 | } else if (calls = this._callbacks) {
86 | if (!callback) {
87 | calls[ev] = [];
88 | } else {
89 | var list = calls[ev];
90 | if (!list) return this;
91 | for (var i = 0, l = list.length; i < l; i++) {
92 | if (list[i] && callback === list[i][0]) {
93 | list[i] = null;
94 | break;
95 | }
96 | }
97 | }
98 | }
99 | return this;
100 | },
101 |
102 | // Trigger an event, firing all bound callbacks. Callbacks are passed the
103 | // same arguments as `trigger` is, apart from the event name.
104 | // Listening for `"all"` passes the true event name as the first argument.
105 | trigger : function(eventName) {
106 | var list, calls, ev, callback, args;
107 | var both = 2;
108 | if (!(calls = this._callbacks)) return this;
109 | while (both--) {
110 | ev = both ? eventName : 'all';
111 | if (list = calls[ev]) {
112 | for (var i = 0, l = list.length; i < l; i++) {
113 | if (!(callback = list[i])) {
114 | list.splice(i, 1); i--; l--;
115 | } else {
116 | args = both ? Array.prototype.slice.call(arguments, 1) : arguments;
117 | callback[0].apply(callback[1] || this, args);
118 | }
119 | }
120 | }
121 | }
122 | return this;
123 | }
124 |
125 | };
126 |
127 | // Backbone.Model
128 | // --------------
129 |
130 | // Create a new model, with defined attributes. A client id (`cid`)
131 | // is automatically generated and assigned for you.
132 | Backbone.Model = function(attributes, options) {
133 | var defaults;
134 | attributes || (attributes = {});
135 | if (defaults = this.defaults) {
136 | if (_.isFunction(defaults)) defaults = defaults.call(this);
137 | attributes = _.extend({}, defaults, attributes);
138 | }
139 | this.attributes = {};
140 | this._escapedAttributes = {};
141 | this.cid = _.uniqueId('c');
142 | this.set(attributes, {silent : true});
143 | this._changed = false;
144 | this._previousAttributes = _.clone(this.attributes);
145 | if (options && options.collection) this.collection = options.collection;
146 | this.initialize(attributes, options);
147 | };
148 |
149 | // Attach all inheritable methods to the Model prototype.
150 | _.extend(Backbone.Model.prototype, Backbone.Events, {
151 |
152 | // A snapshot of the model's previous attributes, taken immediately
153 | // after the last `"change"` event was fired.
154 | _previousAttributes : null,
155 |
156 | // Has the item been changed since the last `"change"` event?
157 | _changed : false,
158 |
159 | // The default name for the JSON `id` attribute is `"id"`. MongoDB and
160 | // CouchDB users may want to set this to `"_id"`.
161 | idAttribute : 'id',
162 |
163 | // Initialize is an empty function by default. Override it with your own
164 | // initialization logic.
165 | initialize : function(){},
166 |
167 | // Return a copy of the model's `attributes` object.
168 | toJSON : function() {
169 | return _.clone(this.attributes);
170 | },
171 |
172 | // Get the value of an attribute.
173 | get : function(attr) {
174 | return this.attributes[attr];
175 | },
176 |
177 | // Get the HTML-escaped value of an attribute.
178 | escape : function(attr) {
179 | var html;
180 | if (html = this._escapedAttributes[attr]) return html;
181 | var val = this.attributes[attr];
182 | return this._escapedAttributes[attr] = escapeHTML(val == null ? '' : '' + val);
183 | },
184 |
185 | // Returns `true` if the attribute contains a value that is not null
186 | // or undefined.
187 | has : function(attr) {
188 | return this.attributes[attr] != null;
189 | },
190 |
191 | // Set a hash of model attributes on the object, firing `"change"` unless you
192 | // choose to silence it.
193 | set : function(attrs, options) {
194 |
195 | // Extract attributes and options.
196 | options || (options = {});
197 | if (!attrs) return this;
198 | if (attrs.attributes) attrs = attrs.attributes;
199 | var now = this.attributes, escaped = this._escapedAttributes;
200 |
201 | // Run validation.
202 | if (!options.silent && this.validate && !this._performValidation(attrs, options)) return false;
203 |
204 | // Check for changes of `id`.
205 | if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
206 |
207 | // We're about to start triggering change events.
208 | var alreadyChanging = this._changing;
209 | this._changing = true;
210 |
211 | // Update attributes.
212 | for (var attr in attrs) {
213 | var val = attrs[attr];
214 | if (!_.isEqual(now[attr], val)) {
215 | now[attr] = val;
216 | delete escaped[attr];
217 | this._changed = true;
218 | if (!options.silent) this.trigger('change:' + attr, this, val, options);
219 | }
220 | }
221 |
222 | // Fire the `"change"` event, if the model has been changed.
223 | if (!alreadyChanging && !options.silent && this._changed) this.change(options);
224 | this._changing = false;
225 | return this;
226 | },
227 |
228 | // Remove an attribute from the model, firing `"change"` unless you choose
229 | // to silence it. `unset` is a noop if the attribute doesn't exist.
230 | unset : function(attr, options) {
231 | if (!(attr in this.attributes)) return this;
232 | options || (options = {});
233 | var value = this.attributes[attr];
234 |
235 | // Run validation.
236 | var validObj = {};
237 | validObj[attr] = void 0;
238 | if (!options.silent && this.validate && !this._performValidation(validObj, options)) return false;
239 |
240 | // Remove the attribute.
241 | delete this.attributes[attr];
242 | delete this._escapedAttributes[attr];
243 | if (attr == this.idAttribute) delete this.id;
244 | this._changed = true;
245 | if (!options.silent) {
246 | this.trigger('change:' + attr, this, void 0, options);
247 | this.change(options);
248 | }
249 | return this;
250 | },
251 |
252 | // Clear all attributes on the model, firing `"change"` unless you choose
253 | // to silence it.
254 | clear : function(options) {
255 | options || (options = {});
256 | var attr;
257 | var old = this.attributes;
258 |
259 | // Run validation.
260 | var validObj = {};
261 | for (attr in old) validObj[attr] = void 0;
262 | if (!options.silent && this.validate && !this._performValidation(validObj, options)) return false;
263 |
264 | this.attributes = {};
265 | this._escapedAttributes = {};
266 | this._changed = true;
267 | if (!options.silent) {
268 | for (attr in old) {
269 | this.trigger('change:' + attr, this, void 0, options);
270 | }
271 | this.change(options);
272 | }
273 | return this;
274 | },
275 |
276 | // Fetch the model from the server. If the server's representation of the
277 | // model differs from its current attributes, they will be overriden,
278 | // triggering a `"change"` event.
279 | fetch : function(options) {
280 | options || (options = {});
281 | var model = this;
282 | var success = options.success;
283 | options.success = function(resp, status, xhr) {
284 | if (!model.set(model.parse(resp, xhr), options)) return false;
285 | if (success) success(model, resp);
286 | };
287 | options.error = wrapError(options.error, model, options);
288 | return (this.sync || Backbone.sync).call(this, 'read', this, options);
289 | },
290 |
291 | // Set a hash of model attributes, and sync the model to the server.
292 | // If the server returns an attributes hash that differs, the model's
293 | // state will be `set` again.
294 | save : function(attrs, options) {
295 | options || (options = {});
296 | if (attrs && !this.set(attrs, options)) return false;
297 | var model = this;
298 | var success = options.success;
299 | options.success = function(resp, status, xhr) {
300 | if (!model.set(model.parse(resp, xhr), options)) return false;
301 | if (success) success(model, resp, xhr);
302 | };
303 | options.error = wrapError(options.error, model, options);
304 | var method = this.isNew() ? 'create' : 'update';
305 | return (this.sync || Backbone.sync).call(this, method, this, options);
306 | },
307 |
308 | // Destroy this model on the server if it was already persisted. Upon success, the model is removed
309 | // from its collection, if it has one.
310 | destroy : function(options) {
311 | options || (options = {});
312 | if (this.isNew()) return this.trigger('destroy', this, this.collection, options);
313 | var model = this;
314 | var success = options.success;
315 | options.success = function(resp) {
316 | model.trigger('destroy', model, model.collection, options);
317 | if (success) success(model, resp);
318 | };
319 | options.error = wrapError(options.error, model, options);
320 | return (this.sync || Backbone.sync).call(this, 'delete', this, options);
321 | },
322 |
323 | // Default URL for the model's representation on the server -- if you're
324 | // using Backbone's restful methods, override this to change the endpoint
325 | // that will be called.
326 | url : function() {
327 | var base = getUrl(this.collection) || this.urlRoot || urlError();
328 | if (this.isNew()) return base;
329 | return base + (base.charAt(base.length - 1) == '/' ? '' : '/') + encodeURIComponent(this.id);
330 | },
331 |
332 | // **parse** converts a response into the hash of attributes to be `set` on
333 | // the model. The default implementation is just to pass the response along.
334 | parse : function(resp, xhr) {
335 | return resp;
336 | },
337 |
338 | // Create a new model with identical attributes to this one.
339 | clone : function() {
340 | return new this.constructor(this);
341 | },
342 |
343 | // A model is new if it has never been saved to the server, and lacks an id.
344 | isNew : function() {
345 | return this.id == null;
346 | },
347 |
348 | // Call this method to manually fire a `change` event for this model.
349 | // Calling this will cause all objects observing the model to update.
350 | change : function(options) {
351 | this.trigger('change', this, options);
352 | this._previousAttributes = _.clone(this.attributes);
353 | this._changed = false;
354 | },
355 |
356 | // Determine if the model has changed since the last `"change"` event.
357 | // If you specify an attribute name, determine if that attribute has changed.
358 | hasChanged : function(attr) {
359 | if (attr) return this._previousAttributes[attr] != this.attributes[attr];
360 | return this._changed;
361 | },
362 |
363 | // Return an object containing all the attributes that have changed, or false
364 | // if there are no changed attributes. Useful for determining what parts of a
365 | // view need to be updated and/or what attributes need to be persisted to
366 | // the server.
367 | changedAttributes : function(now) {
368 | now || (now = this.attributes);
369 | var old = this._previousAttributes;
370 | var changed = false;
371 | for (var attr in now) {
372 | if (!_.isEqual(old[attr], now[attr])) {
373 | changed = changed || {};
374 | changed[attr] = now[attr];
375 | }
376 | }
377 | return changed;
378 | },
379 |
380 | // Get the previous value of an attribute, recorded at the time the last
381 | // `"change"` event was fired.
382 | previous : function(attr) {
383 | if (!attr || !this._previousAttributes) return null;
384 | return this._previousAttributes[attr];
385 | },
386 |
387 | // Get all of the attributes of the model at the time of the previous
388 | // `"change"` event.
389 | previousAttributes : function() {
390 | return _.clone(this._previousAttributes);
391 | },
392 |
393 | // Run validation against a set of incoming attributes, returning `true`
394 | // if all is well. If a specific `error` callback has been passed,
395 | // call that instead of firing the general `"error"` event.
396 | _performValidation : function(attrs, options) {
397 | var error = this.validate(attrs);
398 | if (error) {
399 | if (options.error) {
400 | options.error(this, error, options);
401 | } else {
402 | this.trigger('error', this, error, options);
403 | }
404 | return false;
405 | }
406 | return true;
407 | }
408 |
409 | });
410 |
411 | // Backbone.Collection
412 | // -------------------
413 |
414 | // Provides a standard collection class for our sets of models, ordered
415 | // or unordered. If a `comparator` is specified, the Collection will maintain
416 | // its models in sort order, as they're added and removed.
417 | Backbone.Collection = function(models, options) {
418 | options || (options = {});
419 | if (options.comparator) this.comparator = options.comparator;
420 | _.bindAll(this, '_onModelEvent', '_removeReference');
421 | this._reset();
422 | if (models) this.reset(models, {silent: true});
423 | this.initialize.apply(this, arguments);
424 | };
425 |
426 | // Define the Collection's inheritable methods.
427 | _.extend(Backbone.Collection.prototype, Backbone.Events, {
428 |
429 | // The default model for a collection is just a **Backbone.Model**.
430 | // This should be overridden in most cases.
431 | model : Backbone.Model,
432 |
433 | // Initialize is an empty function by default. Override it with your own
434 | // initialization logic.
435 | initialize : function(){},
436 |
437 | // The JSON representation of a Collection is an array of the
438 | // models' attributes.
439 | toJSON : function() {
440 | return this.map(function(model){ return model.toJSON(); });
441 | },
442 |
443 | // Add a model, or list of models to the set. Pass **silent** to avoid
444 | // firing the `added` event for every new model.
445 | add : function(models, options) {
446 | if (_.isArray(models)) {
447 | for (var i = 0, l = models.length; i < l; i++) {
448 | this._add(models[i], options);
449 | }
450 | } else {
451 | this._add(models, options);
452 | }
453 | return this;
454 | },
455 |
456 | // Remove a model, or a list of models from the set. Pass silent to avoid
457 | // firing the `removed` event for every model removed.
458 | remove : function(models, options) {
459 | if (_.isArray(models)) {
460 | for (var i = 0, l = models.length; i < l; i++) {
461 | this._remove(models[i], options);
462 | }
463 | } else {
464 | this._remove(models, options);
465 | }
466 | return this;
467 | },
468 |
469 | // Get a model from the set by id.
470 | get : function(id) {
471 | if (id == null) return null;
472 | return this._byId[id.id != null ? id.id : id];
473 | },
474 |
475 | // Get a model from the set by client id.
476 | getByCid : function(cid) {
477 | return cid && this._byCid[cid.cid || cid];
478 | },
479 |
480 | // Get the model at the given index.
481 | at: function(index) {
482 | return this.models[index];
483 | },
484 |
485 | // Force the collection to re-sort itself. You don't need to call this under normal
486 | // circumstances, as the set will maintain sort order as each item is added.
487 | sort : function(options) {
488 | options || (options = {});
489 | if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
490 | this.models = this.sortBy(this.comparator);
491 | if (!options.silent) this.trigger('reset', this, options);
492 | return this;
493 | },
494 |
495 | // Pluck an attribute from each model in the collection.
496 | pluck : function(attr) {
497 | return _.map(this.models, function(model){ return model.get(attr); });
498 | },
499 |
500 | // When you have more items than you want to add or remove individually,
501 | // you can reset the entire set with a new list of models, without firing
502 | // any `added` or `removed` events. Fires `reset` when finished.
503 | reset : function(models, options) {
504 | models || (models = []);
505 | options || (options = {});
506 | this.each(this._removeReference);
507 | this._reset();
508 | this.add(models, {silent: true});
509 | if (!options.silent) this.trigger('reset', this, options);
510 | return this;
511 | },
512 |
513 | // Fetch the default set of models for this collection, resetting the
514 | // collection when they arrive. If `add: true` is passed, appends the
515 | // models to the collection instead of resetting.
516 | fetch : function(options) {
517 | options || (options = {});
518 | var collection = this;
519 | var success = options.success;
520 | options.success = function(resp, status, xhr) {
521 | collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options);
522 | if (success) success(collection, resp);
523 | };
524 | options.error = wrapError(options.error, collection, options);
525 | return (this.sync || Backbone.sync).call(this, 'read', this, options);
526 | },
527 |
528 | // Create a new instance of a model in this collection. After the model
529 | // has been created on the server, it will be added to the collection.
530 | // Returns the model, or 'false' if validation on a new model fails.
531 | create : function(model, options) {
532 | var coll = this;
533 | options || (options = {});
534 | model = this._prepareModel(model, options);
535 | if (!model) return false;
536 | var success = options.success;
537 | options.success = function(nextModel, resp, xhr) {
538 | coll.add(nextModel, options);
539 | if (success) success(nextModel, resp, xhr);
540 | };
541 | model.save(null, options);
542 | return model;
543 | },
544 |
545 | // **parse** converts a response into a list of models to be added to the
546 | // collection. The default implementation is just to pass it through.
547 | parse : function(resp, xhr) {
548 | return resp;
549 | },
550 |
551 | // Proxy to _'s chain. Can't be proxied the same way the rest of the
552 | // underscore methods are proxied because it relies on the underscore
553 | // constructor.
554 | chain: function () {
555 | return _(this.models).chain();
556 | },
557 |
558 | // Reset all internal state. Called when the collection is reset.
559 | _reset : function(options) {
560 | this.length = 0;
561 | this.models = [];
562 | this._byId = {};
563 | this._byCid = {};
564 | },
565 |
566 | // Prepare a model to be added to this collection
567 | _prepareModel: function(model, options) {
568 | if (!(model instanceof Backbone.Model)) {
569 | var attrs = model;
570 | model = new this.model(attrs, {collection: this});
571 | if (model.validate && !model._performValidation(attrs, options)) model = false;
572 | } else if (!model.collection) {
573 | model.collection = this;
574 | }
575 | return model;
576 | },
577 |
578 | // Internal implementation of adding a single model to the set, updating
579 | // hash indexes for `id` and `cid` lookups.
580 | // Returns the model, or 'false' if validation on a new model fails.
581 | _add : function(model, options) {
582 | options || (options = {});
583 | model = this._prepareModel(model, options);
584 | if (!model) return false;
585 | var already = this.getByCid(model);
586 | if (already) throw new Error(["Can't add the same model to a set twice", already.id]);
587 | this._byId[model.id] = model;
588 | this._byCid[model.cid] = model;
589 | var index = options.at != null ? options.at :
590 | this.comparator ? this.sortedIndex(model, this.comparator) :
591 | this.length;
592 | this.models.splice(index, 0, model);
593 | model.bind('all', this._onModelEvent);
594 | this.length++;
595 | if (!options.silent) model.trigger('add', model, this, options);
596 | return model;
597 | },
598 |
599 | // Internal implementation of removing a single model from the set, updating
600 | // hash indexes for `id` and `cid` lookups.
601 | _remove : function(model, options) {
602 | options || (options = {});
603 | model = this.getByCid(model) || this.get(model);
604 | if (!model) return null;
605 | delete this._byId[model.id];
606 | delete this._byCid[model.cid];
607 | this.models.splice(this.indexOf(model), 1);
608 | this.length--;
609 | if (!options.silent) model.trigger('remove', model, this, options);
610 | this._removeReference(model);
611 | return model;
612 | },
613 |
614 | // Internal method to remove a model's ties to a collection.
615 | _removeReference : function(model) {
616 | if (this == model.collection) {
617 | delete model.collection;
618 | }
619 | model.unbind('all', this._onModelEvent);
620 | },
621 |
622 | // Internal method called every time a model in the set fires an event.
623 | // Sets need to update their indexes when models change ids. All other
624 | // events simply proxy through. "add" and "remove" events that originate
625 | // in other collections are ignored.
626 | _onModelEvent : function(ev, model, collection, options) {
627 | if ((ev == 'add' || ev == 'remove') && collection != this) return;
628 | if (ev == 'destroy') {
629 | this._remove(model, options);
630 | }
631 | if (model && ev === 'change:' + model.idAttribute) {
632 | delete this._byId[model.previous(model.idAttribute)];
633 | this._byId[model.id] = model;
634 | }
635 | this.trigger.apply(this, arguments);
636 | }
637 |
638 | });
639 |
640 | // Underscore methods that we want to implement on the Collection.
641 | var methods = ['forEach', 'each', 'map', 'reduce', 'reduceRight', 'find', 'detect',
642 | 'filter', 'select', 'reject', 'every', 'all', 'some', 'any', 'include',
643 | 'contains', 'invoke', 'max', 'min', 'sortBy', 'sortedIndex', 'toArray', 'size',
644 | 'first', 'rest', 'last', 'without', 'indexOf', 'lastIndexOf', 'isEmpty', 'groupBy'];
645 |
646 | // Mix in each Underscore method as a proxy to `Collection#models`.
647 | _.each(methods, function(method) {
648 | Backbone.Collection.prototype[method] = function() {
649 | return _[method].apply(_, [this.models].concat(_.toArray(arguments)));
650 | };
651 | });
652 |
653 | // Backbone.Router
654 | // -------------------
655 |
656 | // Routers map faux-URLs to actions, and fire events when routes are
657 | // matched. Creating a new one sets its `routes` hash, if not set statically.
658 | Backbone.Router = function(options) {
659 | options || (options = {});
660 | if (options.routes) this.routes = options.routes;
661 | this._bindRoutes();
662 | this.initialize.apply(this, arguments);
663 | };
664 |
665 | // Cached regular expressions for matching named param parts and splatted
666 | // parts of route strings.
667 | var namedParam = /:([\w\d]+)/g;
668 | var splatParam = /\*([\w\d]+)/g;
669 | var escapeRegExp = /[-[\]{}()+?.,\\^$|#\s]/g;
670 |
671 | // Set up all inheritable **Backbone.Router** properties and methods.
672 | _.extend(Backbone.Router.prototype, Backbone.Events, {
673 |
674 | // Initialize is an empty function by default. Override it with your own
675 | // initialization logic.
676 | initialize : function(){},
677 |
678 | // Manually bind a single named route to a callback. For example:
679 | //
680 | // this.route('search/:query/p:num', 'search', function(query, num) {
681 | // ...
682 | // });
683 | //
684 | route : function(route, name, callback) {
685 | Backbone.history || (Backbone.history = new Backbone.History);
686 | if (!_.isRegExp(route)) route = this._routeToRegExp(route);
687 | Backbone.history.route(route, _.bind(function(fragment) {
688 | var args = this._extractParameters(route, fragment);
689 | callback.apply(this, args);
690 | this.trigger.apply(this, ['route:' + name].concat(args));
691 | }, this));
692 | },
693 |
694 | // Simple proxy to `Backbone.history` to save a fragment into the history.
695 | navigate : function(fragment, triggerRoute) {
696 | Backbone.history.navigate(fragment, triggerRoute);
697 | },
698 |
699 | // Bind all defined routes to `Backbone.history`. We have to reverse the
700 | // order of the routes here to support behavior where the most general
701 | // routes can be defined at the bottom of the route map.
702 | _bindRoutes : function() {
703 | if (!this.routes) return;
704 | var routes = [];
705 | for (var route in this.routes) {
706 | routes.unshift([route, this.routes[route]]);
707 | }
708 | for (var i = 0, l = routes.length; i < l; i++) {
709 | this.route(routes[i][0], routes[i][1], this[routes[i][1]]);
710 | }
711 | },
712 |
713 | // Convert a route string into a regular expression, suitable for matching
714 | // against the current location hash.
715 | _routeToRegExp : function(route) {
716 | route = route.replace(escapeRegExp, "\\$&")
717 | .replace(namedParam, "([^\/]*)")
718 | .replace(splatParam, "(.*?)");
719 | return new RegExp('^' + route + '$');
720 | },
721 |
722 | // Given a route, and a URL fragment that it matches, return the array of
723 | // extracted parameters.
724 | _extractParameters : function(route, fragment) {
725 | return route.exec(fragment).slice(1);
726 | }
727 |
728 | });
729 |
730 | // Backbone.History
731 | // ----------------
732 |
733 | // Handles cross-browser history management, based on URL fragments. If the
734 | // browser does not support `onhashchange`, falls back to polling.
735 | Backbone.History = function() {
736 | this.handlers = [];
737 | _.bindAll(this, 'checkUrl');
738 | };
739 |
740 | // Cached regex for cleaning hashes.
741 | var hashStrip = /^#*/;
742 |
743 | // Cached regex for detecting MSIE.
744 | var isExplorer = /msie [\w.]+/;
745 |
746 | // Has the history handling already been started?
747 | var historyStarted = false;
748 |
749 | // Set up all inheritable **Backbone.History** properties and methods.
750 | _.extend(Backbone.History.prototype, {
751 |
752 | // The default interval to poll for hash changes, if necessary, is
753 | // twenty times a second.
754 | interval: 50,
755 |
756 | // Get the cross-browser normalized URL fragment, either from the URL,
757 | // the hash, or the override.
758 | getFragment : function(fragment, forcePushState) {
759 | if (fragment == null) {
760 | if (this._hasPushState || forcePushState) {
761 | fragment = window.location.pathname;
762 | var search = window.location.search;
763 | if (search) fragment += search;
764 | if (fragment.indexOf(this.options.root) == 0) fragment = fragment.substr(this.options.root.length);
765 | } else {
766 | fragment = window.location.hash;
767 | }
768 | }
769 | return decodeURIComponent(fragment.replace(hashStrip, ''));
770 | },
771 |
772 | // Start the hash change handling, returning `true` if the current URL matches
773 | // an existing route, and `false` otherwise.
774 | start : function(options) {
775 |
776 | // Figure out the initial configuration. Do we need an iframe?
777 | // Is pushState desired ... is it available?
778 | if (historyStarted) throw new Error("Backbone.history has already been started");
779 | this.options = _.extend({}, {root: '/'}, this.options, options);
780 | this._wantsPushState = !!this.options.pushState;
781 | this._hasPushState = !!(this.options.pushState && window.history && window.history.pushState);
782 | var fragment = this.getFragment();
783 | var docMode = document.documentMode;
784 | var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
785 | if (oldIE) {
786 | this.iframe = $('').hide().appendTo('body')[0].contentWindow;
787 | this.navigate(fragment);
788 | }
789 |
790 | // Depending on whether we're using pushState or hashes, and whether
791 | // 'onhashchange' is supported, determine how we check the URL state.
792 | if (this._hasPushState) {
793 | $(window).bind('popstate', this.checkUrl);
794 | } else if ('onhashchange' in window && !oldIE) {
795 | $(window).bind('hashchange', this.checkUrl);
796 | } else {
797 | setInterval(this.checkUrl, this.interval);
798 | }
799 |
800 | // Determine if we need to change the base url, for a pushState link
801 | // opened by a non-pushState browser.
802 | this.fragment = fragment;
803 | historyStarted = true;
804 | var loc = window.location;
805 | var atRoot = loc.pathname == this.options.root;
806 | if (this._wantsPushState && !this._hasPushState && !atRoot) {
807 | this.fragment = this.getFragment(null, true);
808 | window.location.replace(this.options.root + '#' + this.fragment);
809 | // Return immediately as browser will do redirect to new url
810 | return true;
811 | } else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) {
812 | this.fragment = loc.hash.replace(hashStrip, '');
813 | window.history.replaceState({}, document.title, loc.protocol + '//' + loc.host + this.options.root + this.fragment);
814 | }
815 |
816 | if (!this.options.silent) {
817 | return this.loadUrl();
818 | }
819 | },
820 |
821 | // Add a route to be tested when the fragment changes. Routes added later may
822 | // override previous routes.
823 | route : function(route, callback) {
824 | this.handlers.unshift({route : route, callback : callback});
825 | },
826 |
827 | // Checks the current URL to see if it has changed, and if it has,
828 | // calls `loadUrl`, normalizing across the hidden iframe.
829 | checkUrl : function(e) {
830 | var current = this.getFragment();
831 | if (current == this.fragment && this.iframe) current = this.getFragment(this.iframe.location.hash);
832 | if (current == this.fragment || current == decodeURIComponent(this.fragment)) return false;
833 | if (this.iframe) this.navigate(current);
834 | this.loadUrl() || this.loadUrl(window.location.hash);
835 | },
836 |
837 | // Attempt to load the current URL fragment. If a route succeeds with a
838 | // match, returns `true`. If no defined routes matches the fragment,
839 | // returns `false`.
840 | loadUrl : function(fragmentOverride) {
841 | var fragment = this.fragment = this.getFragment(fragmentOverride);
842 | var matched = _.any(this.handlers, function(handler) {
843 | if (handler.route.test(fragment)) {
844 | handler.callback(fragment);
845 | return true;
846 | }
847 | });
848 | return matched;
849 | },
850 |
851 | // Save a fragment into the hash history. You are responsible for properly
852 | // URL-encoding the fragment in advance. This does not trigger
853 | // a `hashchange` event.
854 | navigate : function(fragment, triggerRoute) {
855 | var frag = (fragment || '').replace(hashStrip, '');
856 | if (this.fragment == frag || this.fragment == decodeURIComponent(frag)) return;
857 | if (this._hasPushState) {
858 | var loc = window.location;
859 | if (frag.indexOf(this.options.root) != 0) frag = this.options.root + frag;
860 | this.fragment = frag;
861 | window.history.pushState({}, document.title, loc.protocol + '//' + loc.host + frag);
862 | } else {
863 | window.location.hash = this.fragment = frag;
864 | if (this.iframe && (frag != this.getFragment(this.iframe.location.hash))) {
865 | this.iframe.document.open().close();
866 | this.iframe.location.hash = frag;
867 | }
868 | }
869 | if (triggerRoute) this.loadUrl(fragment);
870 | }
871 |
872 | });
873 |
874 | // Backbone.View
875 | // -------------
876 |
877 | // Creating a Backbone.View creates its initial element outside of the DOM,
878 | // if an existing element is not provided...
879 | Backbone.View = function(options) {
880 | this.cid = _.uniqueId('view');
881 | this._configure(options || {});
882 | this._ensureElement();
883 | this.delegateEvents();
884 | this.initialize.apply(this, arguments);
885 | };
886 |
887 | // Element lookup, scoped to DOM elements within the current view.
888 | // This should be prefered to global lookups, if you're dealing with
889 | // a specific view.
890 | var selectorDelegate = function(selector) {
891 | return $(selector, this.el);
892 | };
893 |
894 | // Cached regex to split keys for `delegate`.
895 | var eventSplitter = /^(\S+)\s*(.*)$/;
896 |
897 | // List of view options to be merged as properties.
898 | var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName'];
899 |
900 | // Set up all inheritable **Backbone.View** properties and methods.
901 | _.extend(Backbone.View.prototype, Backbone.Events, {
902 |
903 | // The default `tagName` of a View's element is `"div"`.
904 | tagName : 'div',
905 |
906 | // Attach the `selectorDelegate` function as the `$` property.
907 | $ : selectorDelegate,
908 |
909 | // Initialize is an empty function by default. Override it with your own
910 | // initialization logic.
911 | initialize : function(){},
912 |
913 | // **render** is the core function that your view should override, in order
914 | // to populate its element (`this.el`), with the appropriate HTML. The
915 | // convention is for **render** to always return `this`.
916 | render : function() {
917 | return this;
918 | },
919 |
920 | // Remove this view from the DOM. Note that the view isn't present in the
921 | // DOM by default, so calling this method may be a no-op.
922 | remove : function() {
923 | $(this.el).remove();
924 | return this;
925 | },
926 |
927 | // For small amounts of DOM Elements, where a full-blown template isn't
928 | // needed, use **make** to manufacture elements, one at a time.
929 | //
930 | // var el = this.make('li', {'class': 'row'}, this.model.escape('title'));
931 | //
932 | make : function(tagName, attributes, content) {
933 | var el = document.createElement(tagName);
934 | if (attributes) $(el).attr(attributes);
935 | if (content) $(el).html(content);
936 | return el;
937 | },
938 |
939 | // Set callbacks, where `this.callbacks` is a hash of
940 | //
941 | // *{"event selector": "callback"}*
942 | //
943 | // {
944 | // 'mousedown .title': 'edit',
945 | // 'click .button': 'save'
946 | // }
947 | //
948 | // pairs. Callbacks will be bound to the view, with `this` set properly.
949 | // Uses event delegation for efficiency.
950 | // Omitting the selector binds the event to `this.el`.
951 | // This only works for delegate-able events: not `focus`, `blur`, and
952 | // not `change`, `submit`, and `reset` in Internet Explorer.
953 | delegateEvents : function(events) {
954 | if (!(events || (events = this.events))) return;
955 | if (_.isFunction(events)) events = events.call(this);
956 | $(this.el).unbind('.delegateEvents' + this.cid);
957 | for (var key in events) {
958 | var method = this[events[key]];
959 | if (!method) throw new Error('Event "' + events[key] + '" does not exist');
960 | var match = key.match(eventSplitter);
961 | var eventName = match[1], selector = match[2];
962 | method = _.bind(method, this);
963 | eventName += '.delegateEvents' + this.cid;
964 | if (selector === '') {
965 | $(this.el).bind(eventName, method);
966 | } else {
967 | $(this.el).delegate(selector, eventName, method);
968 | }
969 | }
970 | },
971 |
972 | // Performs the initial configuration of a View with a set of options.
973 | // Keys with special meaning *(model, collection, id, className)*, are
974 | // attached directly to the view.
975 | _configure : function(options) {
976 | if (this.options) options = _.extend({}, this.options, options);
977 | for (var i = 0, l = viewOptions.length; i < l; i++) {
978 | var attr = viewOptions[i];
979 | if (options[attr]) this[attr] = options[attr];
980 | }
981 | this.options = options;
982 | },
983 |
984 | // Ensure that the View has a DOM element to render into.
985 | // If `this.el` is a string, pass it through `$()`, take the first
986 | // matching element, and re-assign it to `el`. Otherwise, create
987 | // an element from the `id`, `className` and `tagName` proeprties.
988 | _ensureElement : function() {
989 | if (!this.el) {
990 | var attrs = this.attributes || {};
991 | if (this.id) attrs.id = this.id;
992 | if (this.className) attrs['class'] = this.className;
993 | this.el = this.make(this.tagName, attrs);
994 | } else if (_.isString(this.el)) {
995 | this.el = $(this.el).get(0);
996 | }
997 | }
998 |
999 | });
1000 |
1001 | // The self-propagating extend function that Backbone classes use.
1002 | var extend = function (protoProps, classProps) {
1003 | var child = inherits(this, protoProps, classProps);
1004 | child.extend = this.extend;
1005 | return child;
1006 | };
1007 |
1008 | // Set up inheritance for the model, collection, and view.
1009 | Backbone.Model.extend = Backbone.Collection.extend =
1010 | Backbone.Router.extend = Backbone.View.extend = extend;
1011 |
1012 | // Map from CRUD to HTTP for our default `Backbone.sync` implementation.
1013 | var methodMap = {
1014 | 'create': 'POST',
1015 | 'update': 'PUT',
1016 | 'delete': 'DELETE',
1017 | 'read' : 'GET'
1018 | };
1019 |
1020 | // Backbone.sync
1021 | // -------------
1022 |
1023 | // Override this function to change the manner in which Backbone persists
1024 | // models to the server. You will be passed the type of request, and the
1025 | // model in question. By default, uses makes a RESTful Ajax request
1026 | // to the model's `url()`. Some possible customizations could be:
1027 | //
1028 | // * Use `setTimeout` to batch rapid-fire updates into a single request.
1029 | // * Send up the models as XML instead of JSON.
1030 | // * Persist models via WebSockets instead of Ajax.
1031 | //
1032 | // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
1033 | // as `POST`, with a `_method` parameter containing the true HTTP method,
1034 | // as well as all requests with the body as `application/x-www-form-urlencoded` instead of
1035 | // `application/json` with the model in a param named `model`.
1036 | // Useful when interfacing with server-side languages like **PHP** that make
1037 | // it difficult to read the body of `PUT` requests.
1038 | Backbone.sync = function(method, model, options) {
1039 | var type = methodMap[method];
1040 |
1041 | // Default JSON-request options.
1042 | var params = _.extend({
1043 | type: type,
1044 | dataType: 'json'
1045 | }, options);
1046 |
1047 | // Ensure that we have a URL.
1048 | if (!params.url) {
1049 | params.url = getUrl(model) || urlError();
1050 | }
1051 |
1052 | // Ensure that we have the appropriate request data.
1053 | if (!params.data && model && (method == 'create' || method == 'update')) {
1054 | params.contentType = 'application/json';
1055 | params.data = JSON.stringify(model.toJSON());
1056 | }
1057 |
1058 | // For older servers, emulate JSON by encoding the request into an HTML-form.
1059 | if (Backbone.emulateJSON) {
1060 | params.contentType = 'application/x-www-form-urlencoded';
1061 | params.data = params.data ? {model : params.data} : {};
1062 | }
1063 |
1064 | // For older servers, emulate HTTP by mimicking the HTTP method with `_method`
1065 | // And an `X-HTTP-Method-Override` header.
1066 | if (Backbone.emulateHTTP) {
1067 | if (type === 'PUT' || type === 'DELETE') {
1068 | if (Backbone.emulateJSON) params.data._method = type;
1069 | params.type = 'POST';
1070 | params.beforeSend = function(xhr) {
1071 | xhr.setRequestHeader('X-HTTP-Method-Override', type);
1072 | };
1073 | }
1074 | }
1075 |
1076 | // Don't process data on a non-GET request.
1077 | if (params.type !== 'GET' && !Backbone.emulateJSON) {
1078 | params.processData = false;
1079 | }
1080 |
1081 | // Make the request.
1082 | return $.ajax(params);
1083 | };
1084 |
1085 | // Helpers
1086 | // -------
1087 |
1088 | // Shared empty constructor function to aid in prototype-chain creation.
1089 | var ctor = function(){};
1090 |
1091 | // Helper function to correctly set up the prototype chain, for subclasses.
1092 | // Similar to `goog.inherits`, but uses a hash of prototype properties and
1093 | // class properties to be extended.
1094 | var inherits = function(parent, protoProps, staticProps) {
1095 | var child;
1096 |
1097 | // The constructor function for the new subclass is either defined by you
1098 | // (the "constructor" property in your `extend` definition), or defaulted
1099 | // by us to simply call `super()`.
1100 | if (protoProps && protoProps.hasOwnProperty('constructor')) {
1101 | child = protoProps.constructor;
1102 | } else {
1103 | child = function(){ return parent.apply(this, arguments); };
1104 | }
1105 |
1106 | // Inherit class (static) properties from parent.
1107 | _.extend(child, parent);
1108 |
1109 | // Set the prototype chain to inherit from `parent`, without calling
1110 | // `parent`'s constructor function.
1111 | ctor.prototype = parent.prototype;
1112 | child.prototype = new ctor();
1113 |
1114 | // Add prototype properties (instance properties) to the subclass,
1115 | // if supplied.
1116 | if (protoProps) _.extend(child.prototype, protoProps);
1117 |
1118 | // Add static properties to the constructor function, if supplied.
1119 | if (staticProps) _.extend(child, staticProps);
1120 |
1121 | // Correctly set child's `prototype.constructor`.
1122 | child.prototype.constructor = child;
1123 |
1124 | // Set a convenience property in case the parent's prototype is needed later.
1125 | child.__super__ = parent.prototype;
1126 |
1127 | return child;
1128 | };
1129 |
1130 | // Helper function to get a URL from a Model or Collection as a property
1131 | // or as a function.
1132 | var getUrl = function(object) {
1133 | if (!(object && object.url)) return null;
1134 | return _.isFunction(object.url) ? object.url() : object.url;
1135 | };
1136 |
1137 | // Throw an error when a URL is needed, and none is supplied.
1138 | var urlError = function() {
1139 | throw new Error('A "url" property or function must be specified');
1140 | };
1141 |
1142 | // Wrap an optional error callback with a fallback error event.
1143 | var wrapError = function(onError, model, options) {
1144 | return function(resp) {
1145 | if (onError) {
1146 | onError(model, resp, options);
1147 | } else {
1148 | model.trigger('error', model, resp, options);
1149 | }
1150 | };
1151 | };
1152 |
1153 | // Helper function to escape a string for HTML rendering.
1154 | var escapeHTML = function(string) {
1155 | return string.replace(/&(?!\w+;|#\d+;|#x[\da-f]+;)/gi, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, ''').replace(/\//g,'/');
1156 | };
1157 |
1158 | }).call(this);
1159 |
--------------------------------------------------------------------------------
/lib/backbone.localStorage.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Backbone localStorage Adapter v1.0
3 | * https://github.com/jeromegn/Backbone.localStorage
4 | *
5 | * Date: Sun Aug 14 2011 09:53:55 -0400
6 | */
7 |
8 | // A simple module to replace `Backbone.sync` with *localStorage*-based
9 | // persistence. Models are given GUIDS, and saved into a JSON object. Simple
10 | // as that.
11 |
12 | // Generate four random hex digits.
13 | function S4() {
14 | return (((1+Math.random())*0x10000)|0).toString(16).substring(1);
15 | };
16 |
17 | // Generate a pseudo-GUID by concatenating random hexadecimal.
18 | function guid() {
19 | return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4());
20 | };
21 |
22 | // Our Store is represented by a single JS object in *localStorage*. Create it
23 | // with a meaningful name, like the name you'd give a table.
24 | window.Store = function(name) {
25 | this.name = name;
26 | var store = localStorage.getItem(this.name);
27 | this.records = (store && store.split(",")) || [];
28 | };
29 |
30 | _.extend(Store.prototype, {
31 |
32 | // Save the current state of the **Store** to *localStorage*.
33 | save: function() {
34 | localStorage.setItem(this.name, this.records.join(","));
35 | },
36 |
37 | // Add a model, giving it a (hopefully)-unique GUID, if it doesn't already
38 | // have an id of it's own.
39 | create: function(model) {
40 | if (!model.id) model.id = model.attributes.id = guid();
41 | localStorage.setItem(this.name+"-"+model.id, JSON.stringify(model));
42 | this.records.push(model.id.toString());
43 | this.save();
44 | return model;
45 | },
46 |
47 | // Update a model by replacing its copy in `this.data`.
48 | update: function(model) {
49 | localStorage.setItem(this.name+"-"+model.id, JSON.stringify(model));
50 | if (!_.include(this.records, model.id.toString())) this.records.push(model.id.toString()); this.save();
51 | return model;
52 | },
53 |
54 | // Retrieve a model from `this.data` by id.
55 | find: function(model) {
56 | return JSON.parse(localStorage.getItem(this.name+"-"+model.id));
57 | },
58 |
59 | // Return the array of all models currently in storage.
60 | findAll: function() {
61 | return _.map(this.records, function(id){return JSON.parse(localStorage.getItem(this.name+"-"+id));}, this);
62 | },
63 |
64 | // Delete a model from `this.data`, returning it.
65 | destroy: function(model) {
66 | localStorage.removeItem(this.name+"-"+model.id);
67 | this.records = _.reject(this.records, function(record_id){return record_id == model.id.toString();});
68 | this.save();
69 | return model;
70 | }
71 |
72 | });
73 |
74 | // Override `Backbone.sync` to use delegate to the model or collection's
75 | // *localStorage* property, which should be an instance of `Store`.
76 | Backbone.sync = function(method, model, options, error) {
77 |
78 | // Backwards compatibility with Backbone <= 0.3.3
79 | if (typeof options == 'function') {
80 | options = {
81 | success: options,
82 | error: error
83 | };
84 | }
85 |
86 | var resp;
87 | var store = model.localStorage || model.collection.localStorage;
88 |
89 | switch (method) {
90 | case "read": resp = model.id ? store.find(model) : store.findAll(); break;
91 | case "create": resp = store.create(model); break;
92 | case "update": resp = store.update(model); break;
93 | case "delete": resp = store.destroy(model); break;
94 | }
95 |
96 | if (resp) {
97 | options.success(resp);
98 | } else {
99 | options.error("Record not found");
100 | }
101 | };
--------------------------------------------------------------------------------
/lib/underscore.js:
--------------------------------------------------------------------------------
1 | // Underscore.js 1.2.0
2 | // (c) 2011 Jeremy Ashkenas, DocumentCloud Inc.
3 | // Underscore is freely distributable under the MIT license.
4 | // Portions of Underscore are inspired or borrowed from Prototype,
5 | // Oliver Steele's Functional, and John Resig's Micro-Templating.
6 | // For all details and documentation:
7 | // http://documentcloud.github.com/underscore
8 |
9 | (function() {
10 |
11 | // Baseline setup
12 | // --------------
13 |
14 | // Establish the root object, `window` in the browser, or `global` on the server.
15 | var root = this;
16 |
17 | // Save the previous value of the `_` variable.
18 | var previousUnderscore = root._;
19 |
20 | // Establish the object that gets returned to break out of a loop iteration.
21 | var breaker = {};
22 |
23 | // Save bytes in the minified (but not gzipped) version:
24 | var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;
25 |
26 | // Create quick reference variables for speed access to core prototypes.
27 | var slice = ArrayProto.slice,
28 | unshift = ArrayProto.unshift,
29 | toString = ObjProto.toString,
30 | hasOwnProperty = ObjProto.hasOwnProperty;
31 |
32 | // All **ECMAScript 5** native function implementations that we hope to use
33 | // are declared here.
34 | var
35 | nativeForEach = ArrayProto.forEach,
36 | nativeMap = ArrayProto.map,
37 | nativeReduce = ArrayProto.reduce,
38 | nativeReduceRight = ArrayProto.reduceRight,
39 | nativeFilter = ArrayProto.filter,
40 | nativeEvery = ArrayProto.every,
41 | nativeSome = ArrayProto.some,
42 | nativeIndexOf = ArrayProto.indexOf,
43 | nativeLastIndexOf = ArrayProto.lastIndexOf,
44 | nativeIsArray = Array.isArray,
45 | nativeKeys = Object.keys,
46 | nativeBind = FuncProto.bind;
47 |
48 | // Create a safe reference to the Underscore object for use below.
49 | var _ = function(obj) { return new wrapper(obj); };
50 |
51 | // Export the Underscore object for **CommonJS**, with backwards-compatibility
52 | // for the old `require()` API. If we're not in CommonJS, add `_` to the
53 | // global object.
54 | if (typeof module !== 'undefined' && module.exports) {
55 | module.exports = _;
56 | _._ = _;
57 | } else {
58 | // Exported as a string, for Closure Compiler "advanced" mode.
59 | root['_'] = _;
60 | }
61 |
62 | // Current version.
63 | _.VERSION = '1.2.0';
64 |
65 | // Collection Functions
66 | // --------------------
67 |
68 | // The cornerstone, an `each` implementation, aka `forEach`.
69 | // Handles objects with the built-in `forEach`, arrays, and raw objects.
70 | // Delegates to **ECMAScript 5**'s native `forEach` if available.
71 | var each = _.each = _.forEach = function(obj, iterator, context) {
72 | if (obj == null) return;
73 | if (nativeForEach && obj.forEach === nativeForEach) {
74 | obj.forEach(iterator, context);
75 | } else if (obj.length === +obj.length) {
76 | for (var i = 0, l = obj.length; i < l; i++) {
77 | if (i in obj && iterator.call(context, obj[i], i, obj) === breaker) return;
78 | }
79 | } else {
80 | for (var key in obj) {
81 | if (hasOwnProperty.call(obj, key)) {
82 | if (iterator.call(context, obj[key], key, obj) === breaker) return;
83 | }
84 | }
85 | }
86 | };
87 |
88 | // Return the results of applying the iterator to each element.
89 | // Delegates to **ECMAScript 5**'s native `map` if available.
90 | _.map = function(obj, iterator, context) {
91 | var results = [];
92 | if (obj == null) return results;
93 | if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
94 | each(obj, function(value, index, list) {
95 | results[results.length] = iterator.call(context, value, index, list);
96 | });
97 | return results;
98 | };
99 |
100 | // **Reduce** builds up a single result from a list of values, aka `inject`,
101 | // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available.
102 | _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
103 | var initial = memo !== void 0;
104 | if (obj == null) obj = [];
105 | if (nativeReduce && obj.reduce === nativeReduce) {
106 | if (context) iterator = _.bind(iterator, context);
107 | return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);
108 | }
109 | each(obj, function(value, index, list) {
110 | if (!initial) {
111 | memo = value;
112 | initial = true;
113 | } else {
114 | memo = iterator.call(context, memo, value, index, list);
115 | }
116 | });
117 | if (!initial) throw new TypeError("Reduce of empty array with no initial value");
118 | return memo;
119 | };
120 |
121 | // The right-associative version of reduce, also known as `foldr`.
122 | // Delegates to **ECMAScript 5**'s native `reduceRight` if available.
123 | _.reduceRight = _.foldr = function(obj, iterator, memo, context) {
124 | if (obj == null) obj = [];
125 | if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {
126 | if (context) iterator = _.bind(iterator, context);
127 | return memo !== void 0 ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
128 | }
129 | var reversed = (_.isArray(obj) ? obj.slice() : _.toArray(obj)).reverse();
130 | return _.reduce(reversed, iterator, memo, context);
131 | };
132 |
133 | // Return the first value which passes a truth test. Aliased as `detect`.
134 | _.find = _.detect = function(obj, iterator, context) {
135 | var result;
136 | any(obj, function(value, index, list) {
137 | if (iterator.call(context, value, index, list)) {
138 | result = value;
139 | return true;
140 | }
141 | });
142 | return result;
143 | };
144 |
145 | // Return all the elements that pass a truth test.
146 | // Delegates to **ECMAScript 5**'s native `filter` if available.
147 | // Aliased as `select`.
148 | _.filter = _.select = function(obj, iterator, context) {
149 | var results = [];
150 | if (obj == null) return results;
151 | if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context);
152 | each(obj, function(value, index, list) {
153 | if (iterator.call(context, value, index, list)) results[results.length] = value;
154 | });
155 | return results;
156 | };
157 |
158 | // Return all the elements for which a truth test fails.
159 | _.reject = function(obj, iterator, context) {
160 | var results = [];
161 | if (obj == null) return results;
162 | each(obj, function(value, index, list) {
163 | if (!iterator.call(context, value, index, list)) results[results.length] = value;
164 | });
165 | return results;
166 | };
167 |
168 | // Determine whether all of the elements match a truth test.
169 | // Delegates to **ECMAScript 5**'s native `every` if available.
170 | // Aliased as `all`.
171 | _.every = _.all = function(obj, iterator, context) {
172 | var result = true;
173 | if (obj == null) return result;
174 | if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
175 | each(obj, function(value, index, list) {
176 | if (!(result = result && iterator.call(context, value, index, list))) return breaker;
177 | });
178 | return result;
179 | };
180 |
181 | // Determine if at least one element in the object matches a truth test.
182 | // Delegates to **ECMAScript 5**'s native `some` if available.
183 | // Aliased as `any`.
184 | var any = _.some = _.any = function(obj, iterator, context) {
185 | iterator = iterator || _.identity;
186 | var result = false;
187 | if (obj == null) return result;
188 | if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context);
189 | each(obj, function(value, index, list) {
190 | if (result |= iterator.call(context, value, index, list)) return breaker;
191 | });
192 | return !!result;
193 | };
194 |
195 | // Determine if a given value is included in the array or object using `===`.
196 | // Aliased as `contains`.
197 | _.include = _.contains = function(obj, target) {
198 | var found = false;
199 | if (obj == null) return found;
200 | if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
201 | any(obj, function(value) {
202 | if (found = value === target) return true;
203 | });
204 | return found;
205 | };
206 |
207 | // Invoke a method (with arguments) on every item in a collection.
208 | _.invoke = function(obj, method) {
209 | var args = slice.call(arguments, 2);
210 | return _.map(obj, function(value) {
211 | return (method.call ? method || value : value[method]).apply(value, args);
212 | });
213 | };
214 |
215 | // Convenience version of a common use case of `map`: fetching a property.
216 | _.pluck = function(obj, key) {
217 | return _.map(obj, function(value){ return value[key]; });
218 | };
219 |
220 | // Return the maximum element or (element-based computation).
221 | _.max = function(obj, iterator, context) {
222 | if (!iterator && _.isArray(obj)) return Math.max.apply(Math, obj);
223 | if (!iterator && _.isEmpty(obj)) return -Infinity;
224 | var result = {computed : -Infinity};
225 | each(obj, function(value, index, list) {
226 | var computed = iterator ? iterator.call(context, value, index, list) : value;
227 | computed >= result.computed && (result = {value : value, computed : computed});
228 | });
229 | return result.value;
230 | };
231 |
232 | // Return the minimum element (or element-based computation).
233 | _.min = function(obj, iterator, context) {
234 | if (!iterator && _.isArray(obj)) return Math.min.apply(Math, obj);
235 | if (!iterator && _.isEmpty(obj)) return Infinity;
236 | var result = {computed : Infinity};
237 | each(obj, function(value, index, list) {
238 | var computed = iterator ? iterator.call(context, value, index, list) : value;
239 | computed < result.computed && (result = {value : value, computed : computed});
240 | });
241 | return result.value;
242 | };
243 |
244 | // Shuffle an array.
245 | _.shuffle = function(obj) {
246 | var shuffled = [], rand;
247 | each(obj, function(value, index, list) {
248 | if (index == 0) {
249 | shuffled[0] = value;
250 | } else {
251 | rand = Math.floor(Math.random() * (index + 1));
252 | shuffled[index] = shuffled[rand];
253 | shuffled[rand] = value;
254 | }
255 | });
256 | return shuffled;
257 | };
258 |
259 | // Sort the object's values by a criterion produced by an iterator.
260 | _.sortBy = function(obj, iterator, context) {
261 | return _.pluck(_.map(obj, function(value, index, list) {
262 | return {
263 | value : value,
264 | criteria : iterator.call(context, value, index, list)
265 | };
266 | }).sort(function(left, right) {
267 | var a = left.criteria, b = right.criteria;
268 | return a < b ? -1 : a > b ? 1 : 0;
269 | }), 'value');
270 | };
271 |
272 | // Groups the object's values by a criterion produced by an iterator
273 | _.groupBy = function(obj, iterator) {
274 | var result = {};
275 | each(obj, function(value, index) {
276 | var key = iterator(value, index);
277 | (result[key] || (result[key] = [])).push(value);
278 | });
279 | return result;
280 | };
281 |
282 | // Use a comparator function to figure out at what index an object should
283 | // be inserted so as to maintain order. Uses binary search.
284 | _.sortedIndex = function(array, obj, iterator) {
285 | iterator || (iterator = _.identity);
286 | var low = 0, high = array.length;
287 | while (low < high) {
288 | var mid = (low + high) >> 1;
289 | iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid;
290 | }
291 | return low;
292 | };
293 |
294 | // Safely convert anything iterable into a real, live array.
295 | _.toArray = function(iterable) {
296 | if (!iterable) return [];
297 | if (iterable.toArray) return iterable.toArray();
298 | if (_.isArray(iterable)) return slice.call(iterable);
299 | if (_.isArguments(iterable)) return slice.call(iterable);
300 | return _.values(iterable);
301 | };
302 |
303 | // Return the number of elements in an object.
304 | _.size = function(obj) {
305 | return _.toArray(obj).length;
306 | };
307 |
308 | // Array Functions
309 | // ---------------
310 |
311 | // Get the first element of an array. Passing **n** will return the first N
312 | // values in the array. Aliased as `head`. The **guard** check allows it to work
313 | // with `_.map`.
314 | _.first = _.head = function(array, n, guard) {
315 | return (n != null) && !guard ? slice.call(array, 0, n) : array[0];
316 | };
317 |
318 | // Returns everything but the last entry of the array. Especcialy useful on
319 | // the arguments object. Passing **n** will return all the values in
320 | // the array, excluding the last N. The **guard** check allows it to work with
321 | // `_.map`.
322 | _.initial = function(array, n, guard) {
323 | return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n));
324 | };
325 |
326 | // Get the last element of an array. Passing **n** will return the last N
327 | // values in the array. The **guard** check allows it to work with `_.map`.
328 | _.last = function(array, n, guard) {
329 | return (n != null) && !guard ? slice.call(array, array.length - n) : array[array.length - 1];
330 | };
331 |
332 | // Returns everything but the first entry of the array. Aliased as `tail`.
333 | // Especially useful on the arguments object. Passing an **index** will return
334 | // the rest of the values in the array from that index onward. The **guard**
335 | // check allows it to work with `_.map`.
336 | _.rest = _.tail = function(array, index, guard) {
337 | return slice.call(array, (index == null) || guard ? 1 : index);
338 | };
339 |
340 | // Trim out all falsy values from an array.
341 | _.compact = function(array) {
342 | return _.filter(array, function(value){ return !!value; });
343 | };
344 |
345 | // Return a completely flattened version of an array.
346 | _.flatten = function(array) {
347 | return _.reduce(array, function(memo, value) {
348 | if (_.isArray(value)) return memo.concat(_.flatten(value));
349 | memo[memo.length] = value;
350 | return memo;
351 | }, []);
352 | };
353 |
354 | // Return a version of the array that does not contain the specified value(s).
355 | _.without = function(array) {
356 | return _.difference(array, slice.call(arguments, 1));
357 | };
358 |
359 | // Produce a duplicate-free version of the array. If the array has already
360 | // been sorted, you have the option of using a faster algorithm.
361 | // Aliased as `unique`.
362 | _.uniq = _.unique = function(array, isSorted, iterator) {
363 | var initial = iterator ? _.map(array, iterator) : array;
364 | var result = [];
365 | _.reduce(initial, function(memo, el, i) {
366 | if (0 == i || (isSorted === true ? _.last(memo) != el : !_.include(memo, el))) {
367 | memo[memo.length] = el;
368 | result[result.length] = array[i];
369 | }
370 | return memo;
371 | }, []);
372 | return result;
373 | };
374 |
375 | // Produce an array that contains the union: each distinct element from all of
376 | // the passed-in arrays.
377 | _.union = function() {
378 | return _.uniq(_.flatten(arguments));
379 | };
380 |
381 | // Produce an array that contains every item shared between all the
382 | // passed-in arrays. (Aliased as "intersect" for back-compat.)
383 | _.intersection = _.intersect = function(array) {
384 | var rest = slice.call(arguments, 1);
385 | return _.filter(_.uniq(array), function(item) {
386 | return _.every(rest, function(other) {
387 | return _.indexOf(other, item) >= 0;
388 | });
389 | });
390 | };
391 |
392 | // Take the difference between one array and another.
393 | // Only the elements present in just the first array will remain.
394 | _.difference = function(array, other) {
395 | return _.filter(array, function(value){ return !_.include(other, value); });
396 | };
397 |
398 | // Zip together multiple lists into a single array -- elements that share
399 | // an index go together.
400 | _.zip = function() {
401 | var args = slice.call(arguments);
402 | var length = _.max(_.pluck(args, 'length'));
403 | var results = new Array(length);
404 | for (var i = 0; i < length; i++) results[i] = _.pluck(args, "" + i);
405 | return results;
406 | };
407 |
408 | // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**),
409 | // we need this function. Return the position of the first occurrence of an
410 | // item in an array, or -1 if the item is not included in the array.
411 | // Delegates to **ECMAScript 5**'s native `indexOf` if available.
412 | // If the array is large and already in sort order, pass `true`
413 | // for **isSorted** to use binary search.
414 | _.indexOf = function(array, item, isSorted) {
415 | if (array == null) return -1;
416 | var i, l;
417 | if (isSorted) {
418 | i = _.sortedIndex(array, item);
419 | return array[i] === item ? i : -1;
420 | }
421 | if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item);
422 | for (i = 0, l = array.length; i < l; i++) if (array[i] === item) return i;
423 | return -1;
424 | };
425 |
426 | // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.
427 | _.lastIndexOf = function(array, item) {
428 | if (array == null) return -1;
429 | if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item);
430 | var i = array.length;
431 | while (i--) if (array[i] === item) return i;
432 | return -1;
433 | };
434 |
435 | // Generate an integer Array containing an arithmetic progression. A port of
436 | // the native Python `range()` function. See
437 | // [the Python documentation](http://docs.python.org/library/functions.html#range).
438 | _.range = function(start, stop, step) {
439 | if (arguments.length <= 1) {
440 | stop = start || 0;
441 | start = 0;
442 | }
443 | step = arguments[2] || 1;
444 |
445 | var len = Math.max(Math.ceil((stop - start) / step), 0);
446 | var idx = 0;
447 | var range = new Array(len);
448 |
449 | while(idx < len) {
450 | range[idx++] = start;
451 | start += step;
452 | }
453 |
454 | return range;
455 | };
456 |
457 | // Function (ahem) Functions
458 | // ------------------
459 |
460 | // Create a function bound to a given object (assigning `this`, and arguments,
461 | // optionally). Binding with arguments is also known as `curry`.
462 | // Delegates to **ECMAScript 5**'s native `Function.bind` if available.
463 | // We check for `func.bind` first, to fail fast when `func` is undefined.
464 | _.bind = function(func, obj) {
465 | if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
466 | var args = slice.call(arguments, 2);
467 | return function() {
468 | return func.apply(obj, args.concat(slice.call(arguments)));
469 | };
470 | };
471 |
472 | // Bind all of an object's methods to that object. Useful for ensuring that
473 | // all callbacks defined on an object belong to it.
474 | _.bindAll = function(obj) {
475 | var funcs = slice.call(arguments, 1);
476 | if (funcs.length == 0) funcs = _.functions(obj);
477 | each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); });
478 | return obj;
479 | };
480 |
481 | // Memoize an expensive function by storing its results.
482 | _.memoize = function(func, hasher) {
483 | var memo = {};
484 | hasher || (hasher = _.identity);
485 | return function() {
486 | var key = hasher.apply(this, arguments);
487 | return hasOwnProperty.call(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
488 | };
489 | };
490 |
491 | // Delays a function for the given number of milliseconds, and then calls
492 | // it with the arguments supplied.
493 | _.delay = function(func, wait) {
494 | var args = slice.call(arguments, 2);
495 | return setTimeout(function(){ return func.apply(func, args); }, wait);
496 | };
497 |
498 | // Defers a function, scheduling it to run after the current call stack has
499 | // cleared.
500 | _.defer = function(func) {
501 | return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1)));
502 | };
503 |
504 | // Internal function used to implement `_.throttle` and `_.debounce`.
505 | var limit = function(func, wait, debounce) {
506 | var timeout;
507 | return function() {
508 | var context = this, args = arguments;
509 | var throttler = function() {
510 | timeout = null;
511 | func.apply(context, args);
512 | };
513 | if (debounce) clearTimeout(timeout);
514 | if (debounce || !timeout) timeout = setTimeout(throttler, wait);
515 | };
516 | };
517 |
518 | // Returns a function, that, when invoked, will only be triggered at most once
519 | // during a given window of time.
520 | _.throttle = function(func, wait) {
521 | return limit(func, wait, false);
522 | };
523 |
524 | // Returns a function, that, as long as it continues to be invoked, will not
525 | // be triggered. The function will be called after it stops being called for
526 | // N milliseconds.
527 | _.debounce = function(func, wait) {
528 | return limit(func, wait, true);
529 | };
530 |
531 | // Returns a function that will be executed at most one time, no matter how
532 | // often you call it. Useful for lazy initialization.
533 | _.once = function(func) {
534 | var ran = false, memo;
535 | return function() {
536 | if (ran) return memo;
537 | ran = true;
538 | return memo = func.apply(this, arguments);
539 | };
540 | };
541 |
542 | // Returns the first function passed as an argument to the second,
543 | // allowing you to adjust arguments, run code before and after, and
544 | // conditionally execute the original function.
545 | _.wrap = function(func, wrapper) {
546 | return function() {
547 | var args = [func].concat(slice.call(arguments));
548 | return wrapper.apply(this, args);
549 | };
550 | };
551 |
552 | // Returns a function that is the composition of a list of functions, each
553 | // consuming the return value of the function that follows.
554 | _.compose = function() {
555 | var funcs = slice.call(arguments);
556 | return function() {
557 | var args = slice.call(arguments);
558 | for (var i = funcs.length - 1; i >= 0; i--) {
559 | args = [funcs[i].apply(this, args)];
560 | }
561 | return args[0];
562 | };
563 | };
564 |
565 | // Returns a function that will only be executed after being called N times.
566 | _.after = function(times, func) {
567 | return function() {
568 | if (--times < 1) { return func.apply(this, arguments); }
569 | };
570 | };
571 |
572 | // Object Functions
573 | // ----------------
574 |
575 | // Retrieve the names of an object's properties.
576 | // Delegates to **ECMAScript 5**'s native `Object.keys`
577 | _.keys = nativeKeys || function(obj) {
578 | if (obj !== Object(obj)) throw new TypeError('Invalid object');
579 | var keys = [];
580 | for (var key in obj) if (hasOwnProperty.call(obj, key)) keys[keys.length] = key;
581 | return keys;
582 | };
583 |
584 | // Retrieve the values of an object's properties.
585 | _.values = function(obj) {
586 | return _.map(obj, _.identity);
587 | };
588 |
589 | // Return a sorted list of the function names available on the object.
590 | // Aliased as `methods`
591 | _.functions = _.methods = function(obj) {
592 | var names = [];
593 | for (var key in obj) {
594 | if (_.isFunction(obj[key])) names.push(key);
595 | }
596 | return names.sort();
597 | };
598 |
599 | // Extend a given object with all the properties in passed-in object(s).
600 | _.extend = function(obj) {
601 | each(slice.call(arguments, 1), function(source) {
602 | for (var prop in source) {
603 | if (source[prop] !== void 0) obj[prop] = source[prop];
604 | }
605 | });
606 | return obj;
607 | };
608 |
609 | // Fill in a given object with default properties.
610 | _.defaults = function(obj) {
611 | each(slice.call(arguments, 1), function(source) {
612 | for (var prop in source) {
613 | if (obj[prop] == null) obj[prop] = source[prop];
614 | }
615 | });
616 | return obj;
617 | };
618 |
619 | // Create a (shallow-cloned) duplicate of an object.
620 | _.clone = function(obj) {
621 | return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
622 | };
623 |
624 | // Invokes interceptor with the obj, and then returns obj.
625 | // The primary purpose of this method is to "tap into" a method chain, in
626 | // order to perform operations on intermediate results within the chain.
627 | _.tap = function(obj, interceptor) {
628 | interceptor(obj);
629 | return obj;
630 | };
631 |
632 | // Internal recursive comparison function.
633 | function eq(a, b, stack) {
634 | // Identical objects are equal. `0 === -0`, but they aren't identical.
635 | // See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal.
636 | if (a === b) return a !== 0 || 1 / a == 1 / b;
637 | // A strict comparison is necessary because `null == undefined`.
638 | if (a == null) return a === b;
639 | // Compare object types.
640 | var typeA = typeof a;
641 | if (typeA != typeof b) return false;
642 | // Optimization; ensure that both values are truthy or falsy.
643 | if (!a != !b) return false;
644 | // `NaN` values are equal.
645 | if (_.isNaN(a)) return _.isNaN(b);
646 | // Compare string objects by value.
647 | var isStringA = _.isString(a), isStringB = _.isString(b);
648 | if (isStringA || isStringB) return isStringA && isStringB && String(a) == String(b);
649 | // Compare number objects by value.
650 | var isNumberA = _.isNumber(a), isNumberB = _.isNumber(b);
651 | if (isNumberA || isNumberB) return isNumberA && isNumberB && +a == +b;
652 | // Compare boolean objects by value. The value of `true` is 1; the value of `false` is 0.
653 | var isBooleanA = _.isBoolean(a), isBooleanB = _.isBoolean(b);
654 | if (isBooleanA || isBooleanB) return isBooleanA && isBooleanB && +a == +b;
655 | // Compare dates by their millisecond values.
656 | var isDateA = _.isDate(a), isDateB = _.isDate(b);
657 | if (isDateA || isDateB) return isDateA && isDateB && a.getTime() == b.getTime();
658 | // Compare RegExps by their source patterns and flags.
659 | var isRegExpA = _.isRegExp(a), isRegExpB = _.isRegExp(b);
660 | if (isRegExpA || isRegExpB) {
661 | // Ensure commutative equality for RegExps.
662 | return isRegExpA && isRegExpB &&
663 | a.source == b.source &&
664 | a.global == b.global &&
665 | a.multiline == b.multiline &&
666 | a.ignoreCase == b.ignoreCase;
667 | }
668 | // Ensure that both values are objects.
669 | if (typeA != 'object') return false;
670 | // Unwrap any wrapped objects.
671 | if (a._chain) a = a._wrapped;
672 | if (b._chain) b = b._wrapped;
673 | // Invoke a custom `isEqual` method if one is provided.
674 | if (_.isFunction(a.isEqual)) return a.isEqual(b);
675 | // Assume equality for cyclic structures. The algorithm for detecting cyclic structures is
676 | // adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
677 | var length = stack.length;
678 | while (length--) {
679 | // Linear search. Performance is inversely proportional to the number of unique nested
680 | // structures.
681 | if (stack[length] == a) return true;
682 | }
683 | // Add the first object to the stack of traversed objects.
684 | stack.push(a);
685 | var size = 0, result = true;
686 | if (a.length === +a.length || b.length === +b.length) {
687 | // Compare object lengths to determine if a deep comparison is necessary.
688 | size = a.length;
689 | result = size == b.length;
690 | if (result) {
691 | // Deep compare array-like object contents, ignoring non-numeric properties.
692 | while (size--) {
693 | // Ensure commutative equality for sparse arrays.
694 | if (!(result = size in a == size in b && eq(a[size], b[size], stack))) break;
695 | }
696 | }
697 | } else {
698 | // Deep compare objects.
699 | for (var key in a) {
700 | if (hasOwnProperty.call(a, key)) {
701 | // Count the expected number of properties.
702 | size++;
703 | // Deep compare each member.
704 | if (!(result = hasOwnProperty.call(b, key) && eq(a[key], b[key], stack))) break;
705 | }
706 | }
707 | // Ensure that both objects contain the same number of properties.
708 | if (result) {
709 | for (key in b) {
710 | if (hasOwnProperty.call(b, key) && !size--) break;
711 | }
712 | result = !size;
713 | }
714 | }
715 | // Remove the first object from the stack of traversed objects.
716 | stack.pop();
717 | return result;
718 | }
719 |
720 | // Perform a deep comparison to check if two objects are equal.
721 | _.isEqual = function(a, b) {
722 | return eq(a, b, []);
723 | };
724 |
725 | // Is a given array or object empty?
726 | _.isEmpty = function(obj) {
727 | if (_.isArray(obj) || _.isString(obj)) return obj.length === 0;
728 | for (var key in obj) if (hasOwnProperty.call(obj, key)) return false;
729 | return true;
730 | };
731 |
732 | // Is a given value a DOM element?
733 | _.isElement = function(obj) {
734 | return !!(obj && obj.nodeType == 1);
735 | };
736 |
737 | // Is a given value an array?
738 | // Delegates to ECMA5's native Array.isArray
739 | _.isArray = nativeIsArray || function(obj) {
740 | return toString.call(obj) === '[object Array]';
741 | };
742 |
743 | // Is a given variable an object?
744 | _.isObject = function(obj) {
745 | return obj === Object(obj);
746 | };
747 |
748 | // Is a given variable an arguments object?
749 | _.isArguments = function(obj) {
750 | return !!(obj && hasOwnProperty.call(obj, 'callee'));
751 | };
752 |
753 | // Is a given value a function?
754 | _.isFunction = function(obj) {
755 | return !!(obj && obj.constructor && obj.call && obj.apply);
756 | };
757 |
758 | // Is a given value a string?
759 | _.isString = function(obj) {
760 | return !!(obj === '' || (obj && obj.charCodeAt && obj.substr));
761 | };
762 |
763 | // Is a given value a number?
764 | _.isNumber = function(obj) {
765 | return !!(obj === 0 || (obj && obj.toExponential && obj.toFixed));
766 | };
767 |
768 | // Is the given value `NaN`? `NaN` happens to be the only value in JavaScript
769 | // that does not equal itself.
770 | _.isNaN = function(obj) {
771 | return obj !== obj;
772 | };
773 |
774 | // Is a given value a boolean?
775 | _.isBoolean = function(obj) {
776 | return obj === true || obj === false || toString.call(obj) == '[object Boolean]';
777 | };
778 |
779 | // Is a given value a date?
780 | _.isDate = function(obj) {
781 | return !!(obj && obj.getTimezoneOffset && obj.setUTCFullYear);
782 | };
783 |
784 | // Is the given value a regular expression?
785 | _.isRegExp = function(obj) {
786 | return !!(obj && obj.test && obj.exec && (obj.ignoreCase || obj.ignoreCase === false));
787 | };
788 |
789 | // Is a given value equal to null?
790 | _.isNull = function(obj) {
791 | return obj === null;
792 | };
793 |
794 | // Is a given variable undefined?
795 | _.isUndefined = function(obj) {
796 | return obj === void 0;
797 | };
798 |
799 | // Utility Functions
800 | // -----------------
801 |
802 | // Run Underscore.js in *noConflict* mode, returning the `_` variable to its
803 | // previous owner. Returns a reference to the Underscore object.
804 | _.noConflict = function() {
805 | root._ = previousUnderscore;
806 | return this;
807 | };
808 |
809 | // Keep the identity function around for default iterators.
810 | _.identity = function(value) {
811 | return value;
812 | };
813 |
814 | // Run a function **n** times.
815 | _.times = function (n, iterator, context) {
816 | for (var i = 0; i < n; i++) iterator.call(context, i);
817 | };
818 |
819 | // Escape a string for HTML interpolation.
820 | _.escape = function(string) {
821 | return (''+string).replace(/&(?!\w+;|#\d+;|#x[\da-f]+;)/gi, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, ''').replace(/\//g,'/');
822 | };
823 |
824 | // Add your own custom functions to the Underscore object, ensuring that
825 | // they're correctly added to the OOP wrapper as well.
826 | _.mixin = function(obj) {
827 | each(_.functions(obj), function(name){
828 | addToWrapper(name, _[name] = obj[name]);
829 | });
830 | };
831 |
832 | // Generate a unique integer id (unique within the entire client session).
833 | // Useful for temporary DOM ids.
834 | var idCounter = 0;
835 | _.uniqueId = function(prefix) {
836 | var id = idCounter++;
837 | return prefix ? prefix + id : id;
838 | };
839 |
840 | // By default, Underscore uses ERB-style template delimiters, change the
841 | // following template settings to use alternative delimiters.
842 | _.templateSettings = {
843 | evaluate : /<%([\s\S]+?)%>/g,
844 | interpolate : /<%=([\s\S]+?)%>/g,
845 | escape : /<%-([\s\S]+?)%>/g
846 | };
847 |
848 | // JavaScript micro-templating, similar to John Resig's implementation.
849 | // Underscore templating handles arbitrary delimiters, preserves whitespace,
850 | // and correctly escapes quotes within interpolated code.
851 | _.template = function(str, data) {
852 | var c = _.templateSettings;
853 | var tmpl = 'var __p=[],print=function(){__p.push.apply(__p,arguments);};' +
854 | 'with(obj||{}){__p.push(\'' +
855 | str.replace(/\\/g, '\\\\')
856 | .replace(/'/g, "\\'")
857 | .replace(c.escape, function(match, code) {
858 | return "',_.escape(" + code.replace(/\\'/g, "'") + "),'";
859 | })
860 | .replace(c.interpolate, function(match, code) {
861 | return "'," + code.replace(/\\'/g, "'") + ",'";
862 | })
863 | .replace(c.evaluate || null, function(match, code) {
864 | return "');" + code.replace(/\\'/g, "'")
865 | .replace(/[\r\n\t]/g, ' ') + "__p.push('";
866 | })
867 | .replace(/\r/g, '\\r')
868 | .replace(/\n/g, '\\n')
869 | .replace(/\t/g, '\\t')
870 | + "');}return __p.join('');";
871 | var func = new Function('obj', tmpl);
872 | return data ? func(data) : func;
873 | };
874 |
875 | // The OOP Wrapper
876 | // ---------------
877 |
878 | // If Underscore is called as a function, it returns a wrapped object that
879 | // can be used OO-style. This wrapper holds altered versions of all the
880 | // underscore functions. Wrapped objects may be chained.
881 | var wrapper = function(obj) { this._wrapped = obj; };
882 |
883 | // Expose `wrapper.prototype` as `_.prototype`
884 | _.prototype = wrapper.prototype;
885 |
886 | // Helper function to continue chaining intermediate results.
887 | var result = function(obj, chain) {
888 | return chain ? _(obj).chain() : obj;
889 | };
890 |
891 | // A method to easily add functions to the OOP wrapper.
892 | var addToWrapper = function(name, func) {
893 | wrapper.prototype[name] = function() {
894 | var args = slice.call(arguments);
895 | unshift.call(args, this._wrapped);
896 | return result(func.apply(_, args), this._chain);
897 | };
898 | };
899 |
900 | // Add all of the Underscore functions to the wrapper object.
901 | _.mixin(_);
902 |
903 | // Add all mutator Array functions to the wrapper.
904 | each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
905 | var method = ArrayProto[name];
906 | wrapper.prototype[name] = function() {
907 | method.apply(this._wrapped, arguments);
908 | return result(this._wrapped, this._chain);
909 | };
910 | });
911 |
912 | // Add all accessor Array functions to the wrapper.
913 | each(['concat', 'join', 'slice'], function(name) {
914 | var method = ArrayProto[name];
915 | wrapper.prototype[name] = function() {
916 | return result(method.apply(this._wrapped, arguments), this._chain);
917 | };
918 | });
919 |
920 | // Start chaining a wrapped Underscore object.
921 | wrapper.prototype.chain = function() {
922 | this._chain = true;
923 | return this;
924 | };
925 |
926 | // Extracts the result from a wrapped and chained object.
927 | wrapper.prototype.value = function() {
928 | return this._wrapped;
929 | };
930 |
931 | })();
932 |
--------------------------------------------------------------------------------
/project/gantt.js:
--------------------------------------------------------------------------------
1 | jQuery(window).ready(function () {
2 |
3 | var gantt = JSGanttChart.create({
4 | resources: {
5 | programmer: "Peter West",
6 | supervisor: "mc",
7 | cosupervisor: "max"
8 | },
9 | types: {
10 | analysis: {
11 | name: "Analysis",
12 | color: "#C79810" // yellow
13 | },
14 | critical: {
15 | name: "Critical",
16 | color: "#CC0000" // red
17 | },
18 | programming: {
19 | name: "Programming",
20 | color: "#356AA0" // blue
21 | },
22 | documentation: {
23 | name: "Documentation",
24 | color: "#FF7400" // orange
25 | }
26 | },
27 | elements: [
28 | {
29 | id: "meeting1",
30 | name: "Supervisor meeting",
31 | startDate: "1 October 2011 11:00",
32 | percentageDone: 100,
33 | resources: [ "programmer", "supervisor", "cosupervisor" ]
34 | },
35 | {
36 | id: "brief",
37 | name: "Project Brief",
38 | predecessors: ["meeting1"],
39 | startDate: "3 October 2011",
40 | endDate: "14 October 2011 16:00",
41 | type: "critical",
42 | slackEndDate: "21 October 2011 16:00",//slackDuration: 4, // or slackEndDate: date
43 | elements: [
44 | {
45 | id: "briefdraft1",
46 | name: "Draft 1",
47 | startDate: "3 October 2011",
48 | endDate: "14 October 2011 16:00",
49 | type: "analysis",
50 | percentageDone: 100,
51 | estimatedHours: 5
52 | },
53 | {
54 | id: "briefdraft2",
55 | predecessors: ["briefdraft1"],
56 | name: "Draft 2",
57 | startDate: "15 October 2011",
58 | endDate: "17 October 2011",
59 | percentageDone: 90,
60 | estimatedHours: 5
61 | },
62 | ],
63 | percentageDone: 95,
64 | estimatedHours: 10
65 | },
66 | {
67 | id: "planning",
68 | name: "Project Planning",
69 | predecessors: ["brief"],
70 | startDate: "17 October 2011",
71 | endDate: "30 October 2011 14:00",
72 | percentageDone: 50,
73 | elements: [
74 | {
75 | id: "gantt1",
76 | name: "Time planning",
77 | startDate: "17 October 2011",
78 | endDate: "21 October 2011 14:00",
79 | estimatedHours: 6,
80 | percentageDone: 60
81 | },
82 | {
83 | id: "design1",
84 | name: "Initial mockups",
85 | startDate: "20 October 2011",
86 | endDate: "21 October 2011 14:00",
87 | estimatedHours: 6,
88 | percentageDone: 10
89 | },
90 | {
91 | id: "prestudy",
92 | name: "Prestudy",
93 | startDate: "18 October 2011",
94 | endDate: "29 October 2011",
95 | type: "analysis",
96 | percentageDone: 2,
97 | }
98 | ]
99 | },
100 | {
101 | id: "dev",
102 | name: "Software development",
103 | startDate: "1 November 2011",
104 | endDate: "1 March 2012",
105 | predecessors: ["planning"],
106 | elements: [
107 | {
108 | id: "software1",
109 | name: "Software development P1",
110 | startDate: "1 November 2011",
111 | endDate: "17 December 2011"
112 | },
113 | {
114 | id: "software2",
115 | name: "Software development P2",
116 | startDate: "12 January 2012",
117 | endDate: "14 February 2012"
118 | }
119 | ]
120 | },
121 | {
122 | id: "study",
123 | name: "Study",
124 | startDate: "18 December 2011",
125 | endDate: "12 April 2012",
126 | type: "analysis",
127 | elements: [
128 | {
129 | id: "study1",
130 | name: "First study",
131 | startDate: "18 December 2011",
132 | endDate: "11 January 2012",
133 | type: "analysis",
134 | predecessors: ["software1"]
135 | },
136 | {
137 | id: "study2",
138 | name: "Second study",
139 | startDate: "15 February 2012",
140 | endDate: "12 April 2012",
141 | type: "analysis",
142 | predecessors: ["software2"]
143 | }
144 | ]
145 | },
146 | {
147 | id: "reviewmeeting1",
148 | name: "Review meeting",
149 | startDate: "13 November 2011"
150 | },
151 | {
152 | id: "progreport",
153 | name: "Progress Report",
154 | startDate: "7 December 2011",
155 | endDate: "14 December 2011 16:00",
156 | type: "documentation"
157 | },
158 | {
159 | id: "finalreport",
160 | name: "Final Project Report",
161 | startDate: "2 April 2012",
162 | endDate: "2 May 2012",
163 | type: "documentation"
164 | }
165 | ]
166 | });
167 |
168 | jQuery("#container").append(gantt.render().el);
169 |
170 | });
--------------------------------------------------------------------------------
/project/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/themes/default.css:
--------------------------------------------------------------------------------
1 | .gantt-container, .gantt-container table {
2 | font-family: verdana, sans-serif;
3 | font-size: 11px;
4 | color: #333;
5 | white-space: nowrap;
6 | }
7 |
8 | .gantt-container {
9 | overflow: auto;
10 | }
11 |
12 | .gantt-container table {
13 | border-spacing: 0;
14 | table-layout: fixed;
15 | background: white;
16 | }
17 |
18 | .gantt-container tr {
19 | height: 18px;
20 | }
21 |
22 | .gantt-container td,
23 | .gantt-container th {
24 | border-right: 1px solid #ddd;
25 | border-bottom: 1px solid #ddd;
26 | padding: 2px;
27 | }
28 |
29 | .gantt-container .markend {
30 | border-right-color: #bbb;
31 | }
32 |
33 | .gantt-container .weekend {
34 | background: #f7f7f7;
35 | }
36 |
37 | .gantt-container th {
38 | font-weight: bold;
39 | }
40 | .gantt-container .important {
41 | color: red;
42 | }
43 |
44 | .gantt-key {
45 | position: fixed;
46 | margin-top: 30px;
47 | }
48 |
49 | .gantt-data-table {
50 | display: inline-block;
51 | border-top: 1px solid #ddd;
52 | border-left: 1px solid #ddd;
53 | position: fixed;
54 | z-index: 40;
55 | }
56 |
57 | .gantt-data-table th {
58 | padding-top: 20px;
59 | }
60 |
61 | @-moz-document url-prefix() {
62 | .gantt-data-table th {
63 | padding-top: 22px;
64 | }
65 | }
66 |
67 | .gantt-table {
68 | display: inline-block;
69 | border-top: 1px solid #ddd;
70 | vertical-align: top;
71 | white-space: nowrap;
72 | margin-left: 480px;
73 | }
74 |
75 | .gantt-table td .cell {
76 | width: 18px;
77 | }
78 |
79 | .gantt-element-holder {
80 | position: relative;
81 | }
82 | tr.child, .row.child td {
83 | background: #efe;
84 | }
85 |
86 | .arrowline {
87 | position: absolute;
88 | border-right: 1px solid red;
89 | border-top: 1px solid red;
90 | z-index: 15;
91 | margin-right: -3px;
92 | padding-left: 5px;
93 | }
94 |
95 | .arrowhead {
96 | position: absolute;
97 | right: -6px;
98 | bottom: 0;
99 | border-left: 6px solid transparent;
100 | border-right: 6px solid transparent;
101 | border-top: 6px solid red;
102 | width: 0;
103 | height: 0;
104 | }
105 |
106 | .gantt-element {
107 | position: absolute;
108 | top: -1px;
109 | left: -1px;
110 | border-bottom: 15px solid blue;
111 | z-index: 10;
112 | opacity: 0.8;
113 | }
114 |
115 | tr.highlight td {
116 | background: yellow !important;
117 | }
118 |
119 | tr.highlight .gantt-element {
120 | opacity: 1;
121 | }
122 |
123 | .done {
124 | position: absolute;
125 | top: 2px;
126 | border-bottom: 11px solid rgba(0, 0, 0, 0.5);
127 | }
128 |
129 | .donetext {
130 | font-size: 9px;
131 | font-weight: bold;
132 | text-shadow: black 1px 1px 4px;
133 | color: white;
134 | position: absolute;
135 | z-index: 20;
136 | top: 0;
137 | right: 0;
138 | left: 0;
139 | bottom: 0;
140 | text-align: center;
141 | line-height: 140%;
142 | }
143 |
144 | .slack {
145 | position: absolute;
146 | top: 7px;
147 | height: 3px;
148 | background: black;
149 | }
150 |
151 | .slack-end {
152 | position: absolute;
153 | right: 0;
154 | top: -4px;
155 | height: 10px;
156 | width: 3px;
157 | background: black;
158 | }
159 |
160 | .gantt-element.critical {
161 | background: red;
162 | }
163 |
164 | /*.gantt-container .row, .gantt-container tr {
165 | MozUserSelect: none;
166 | cursor: pointer;
167 | }*/
168 |
169 | .finished { color: green; }
170 | .in-progress { color: orange; }
171 | .not-started { color: #999; }
172 |
173 | .gantt-key > div {
174 | margin-bottom: 2px;
175 | }
176 |
177 | .gantt-key .color {
178 | width: 20px;
179 | height: 14px;
180 | display: inline-block;
181 | margin: 0 2px 0 0;
182 | vertical-align: top;
183 | }
184 |
185 |
186 | .icon {
187 | position: absolute;
188 | background: rgba(0, 0, 0, 0.2) url('default.images/urgent.png') no-repeat left top;
189 | border-radius: 3px;
190 | z-index: 15;
191 | top: 1px;
192 | font-weight: bold;
193 | font-size: 9px;
194 | padding: 2px 3px 2px 20px;
195 | margin-left: 1px;
196 | color: white;
197 | text-shadow: black 1px 1px 4px;
198 | }
199 |
200 | td, th {
201 | position: relative;
202 | }
203 |
204 | th .deadline {
205 | border-top: 3px solid red;
206 | border-left: 3px solid transparent;
207 | border-right: 3px solid transparent;
208 | height: 0;
209 | width: 0;
210 | position: absolute;
211 | bottom: 0;
212 | left: 40%;
213 | }
--------------------------------------------------------------------------------
/themes/default.images/urgent.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlamingTempura/JSGanttChart/f8a1355c5ee9d40a8741cf7c2926d55a88a1ede7/themes/default.images/urgent.png
--------------------------------------------------------------------------------