├── README.md ├── Template.php ├── estimator-print.css ├── estimator.css ├── estimator.js ├── estimator.php └── index.php /README.md: -------------------------------------------------------------------------------- 1 | # Web Development Project Estimator 2 | Web Development Project Estimator 3 | 4 | The Web Development Project Estimator is a simple tool that allows web designers and site developers to quickly and thoroughly estimate the time and materials required for a proposed web project. 5 | 6 | To use, simply enter the title of the project and your default hourly rate. Then adjust your anticipated hours accordingly to generate your total project estimate. When finished, you can view your finalized estimate in a print-ready format in case you’d like to save a PDF or print a copy for your records. 7 | -------------------------------------------------------------------------------- /Template.php: -------------------------------------------------------------------------------- 1 | ($params = array()){foreach($params as $key => $val){$$key = $val;}$output = << with a valid PHP 5 function name. 14 | * 15 | * The foot of the block is as follows: 16 | * 17 | 18 | EOT; 19 | return $output;} 20 | 21 | * 22 | * EOT must be alone on its own line. 23 | * 24 | * If you follow this format, you can place any text you want between the head and foot and it will be interpreted literally, with one important exception. 25 | * This exception is that any key/value pairs that are passed to the function in an array will be available as regular PHP substitution variables. 26 | * 27 | * Say you have the following function included in this Template class: 28 | * 29 | * 30 | 31 | public function my_template_block($params = array()){foreach($params as $key => $val){$$key = $val;}$output = <<really love the $football_team. 34 | EOT; 35 | return $output;} 36 | 37 | * 38 | * From another class, you can then do the following: 39 | * 40 | * 1) include this Template: 41 | 42 | require_once('Template.php'); 43 | 44 | * 2) Start your class and instantiate the Template class: 45 | * 46 | 47 | class Controller { 48 | function callTemplate(){ 49 | $tpl = new Template(); 50 | 51 | * 3) Call the template block and pass it an array: 52 | * 53 | 54 | $output = $tpl->my_template_block(array('football_team' => 'Packers')); 55 | 56 | * 4) Print it to the screen: 57 | 58 | print $output; 59 | 60 | * 61 | */ 62 | 63 | class Template{ 64 | 65 | public function header($params = array()){foreach($params as $key => $val){$$key = $val;}$output = << 68 | 69 | 70 | 71 | Web Development Project Estimator 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | EOT; 81 | return $output;} 82 | 83 | public function form_header($params = array()){foreach($params as $key => $val){$$key = $val;}$output = << 86 | 87 |
88 | 89 | EOT; 90 | return $output;} 91 | 92 | public function form_table_header($params = array()){foreach($params as $key => $val){$$key = $val;}$output = << 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 111 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | EOT; 150 | return $output;} 151 | 152 | public function form_table_row($params = array()){foreach($params as $key => $val){$$key = $val;}$output = << 155 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | EOT; 172 | return $output;} 173 | 174 | public function form_table_footer($params = array()){foreach($params as $key => $val){$$key = $val;}$output = << 177 | 178 |
Project Title:Default Rate:
105 | 106 | 107 | 108 | 109 | 110 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 |
Description of TaskHours Rate Estimated Fee
156 | 157 | 158 | 159 | 160 | 161 | 162 | ×=$fee
179 | 180 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 |
202 | 203 | 204 |
205 | EOT; 206 | return $output;} 207 | 208 | public function form_footer($params = array()){foreach($params as $key => $val){$$key = $val;}$output = << 211 | 212 | 213 | EOT; 214 | return $output;} 215 | 216 | public function estimate_header($params = array()){foreach($params as $key => $val){$$key = $val;}$output = << 218 |
219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | EOT; 243 | return $output;} 244 | 245 | public function estimate_row($params = array()){foreach($params as $key => $val){$$key = $val;}$output = << 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | EOT; 259 | return $output;} 260 | 261 | public function estimate_footer($params = array()){foreach($params as $key => $val){$$key = $val;}$output = << 264 | 265 |
Project Title:Default Rate:
$title_field$currency_field 228 | $default_rate_field
Description of TaskHours Rate Estimated Fee
$task_field$qty_itemx$price_item=$fee
266 | 267 | 274 | 275 | EOT; 276 | return $output;} 277 | 278 | public function footer($params = array()){foreach($params as $key => $val){$$key = $val;}$output = << 281 | 282 | 283 | 284 | 285 | EOT; 286 | return $output;} 287 | } 288 | -------------------------------------------------------------------------------- /estimator-print.css: -------------------------------------------------------------------------------- 1 | /* ------------------------------------------------------------ 2 | Estimator-Print 3 | ------------------------------------------------------------ */ 4 | body.ep-body { 5 | color: black; 6 | background: white; 7 | font-family: Arial, Helvetica, sans-serif; 8 | text-align: center; 9 | padding-top: 5%; 10 | } 11 | .ep-body #estimate-form { 12 | margin: 0 auto; 13 | background: white; 14 | float: none; 15 | display: block; 16 | width: 80%; 17 | } 18 | #estimate-form table { 19 | width: 100%; 20 | line-height: 1.23em; 21 | } 22 | #estimate-form thead th { 23 | font-size: 10px; 24 | font-weight: normal; 25 | text-align: center; 26 | vertical-align: middle; 27 | text-transform: uppercase; 28 | } 29 | #estimate-form thead th.project-title, 30 | #estimate-form thead th.rate { 31 | padding-left: 15px; 32 | text-align: left; 33 | font-weight: normal; 34 | } 35 | #estimate-form thead .col-heads th { 36 | padding-top: 10px; 37 | padding-bottom: 10px; 38 | color: gray; 39 | } 40 | #estimate-form thead td { 41 | border-bottom: 1px solid gray; 42 | padding: 8px 0 8px 15px; 43 | } 44 | #estimate-form thead td.project-title { 45 | vertical-align: top; 46 | } 47 | #estimate-form thead td .title { 48 | display: inline; 49 | line-height: 1.25em; 50 | float: left; 51 | padding-right: 10px; 52 | } 53 | #estimate-form thead #title_field, 54 | #estimate-form thead #default_rate_field { 55 | margin-right: 10px; 56 | } 57 | #estimate-form thead td .currency { 58 | display: inline; 59 | line-height: 1.25em; 60 | float: left; 61 | } 62 | #estimate-form thead td .default_rate { 63 | display: inline; 64 | line-height: 1.25em; 65 | float: left; 66 | padding-right: 10px; 67 | } 68 | .ep-body #estimate-form thead .project-title { 69 | text-align: left; 70 | width: 50%; 71 | border-right: 1px solid gray; 72 | padding-right: 5px; 73 | } 74 | #estimate-form thead .big { 75 | font-size: 24px; 76 | font-weight: bold; 77 | text-transform: none; 78 | line-height: 1.25em; 79 | } 80 | #estimate-form tbody td { 81 | text-align: center; 82 | vertical-align: middle; 83 | padding: 3px 0; 84 | border-bottom: 1px solid gray; 85 | } 86 | #estimate-form thead .left { 87 | text-align: left; 88 | padding-left: 15px; 89 | width: 50%; 90 | } 91 | #estimate-form tbody .left { 92 | text-align: left; 93 | padding-left: 15px; 94 | width: 50%; 95 | font-family: Times, serif; 96 | } 97 | #estimate-form thead .right, 98 | #estimate-form tbody .right { 99 | text-align: right; 100 | padding-right: 15px; 101 | width: 105px; 102 | } 103 | #estimate-form tbody .right { 104 | font-weight: bold; 105 | font-size: 16px; 106 | } 107 | #estimate-form table td.operator, 108 | #estimate-form table th.operator { 109 | width: 20px; 110 | color: gray; 111 | } 112 | #estimate-form .table_footer { 113 | padding: 15px 15px 0 0; 114 | text-align: right; 115 | font-size: 24px; 116 | font-weight: bold; 117 | line-height: 1.25em; 118 | } 119 | 120 | /* ------------------------------------------------------------ 121 | Estimate Form Table - Editable 122 | ------------------------------------------------------------ */ 123 | 124 | #title_field, 125 | #currency_field, 126 | select, 127 | #default_rate_field, 128 | #task_field 129 | { 130 | float: left; 131 | display: inline; 132 | margin: 3px 0; 133 | } 134 | #default_rate_field { 135 | width: 25px; 136 | margin-left: 5px; 137 | } 138 | 139 | 140 | 141 | -------------------------------------------------------------------------------- /estimator.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-size: 1em; 3 | line-height: 1.5; 4 | font-family: helvetica, arial, sans-serif; 5 | } 6 | 7 | /* ------------------------------------------------------------ 8 | Estimate Form Table 9 | ------------------------------------------------------------ */ 10 | 11 | .estimator { 12 | width: 100%; 13 | max-width: 50em; 14 | margin: 2em auto; 15 | } 16 | .estimator table { 17 | width: 100%; 18 | } 19 | .estimator thead th { 20 | font-size: .6875em; 21 | text-align: center; 22 | color: rgba(0,0,0,.5); 23 | text-transform: uppercase; 24 | } 25 | .estimator thead th.project-title, 26 | .estimator thead th.rate { 27 | padding-left: 1em; 28 | text-align: left; 29 | } 30 | .estimator thead .col-heads th { 31 | padding-top: .75em; 32 | } 33 | .estimator thead td { 34 | border-bottom: 1px solid rgba(0,0,0,.15); 35 | padding: .5em 0 .5em 1em; 36 | } 37 | .estimator thead td.project-title { 38 | vertical-align: top; 39 | } 40 | .estimator thead td .title { 41 | padding-right: .75em; 42 | } 43 | .estimator thead #title_field, 44 | .estimator thead #default_rate_field { 45 | margin-right: .75em; 46 | } 47 | .estimator thead td .currency { 48 | 49 | } 50 | .estimator thead td .default_rate { 51 | padding-right: .75em; 52 | } 53 | .estimator thead td.rate:hover { 54 | background-color: #FFEFC8; 55 | } 56 | .estimator thead td a { 57 | font-size: .75em; 58 | font-weight: 300; 59 | display: none; 60 | } 61 | .estimator thead td a.edit { 62 | font-size: .75em; 63 | display: none; 64 | } 65 | .estimator thead td a.save { 66 | display: none; 67 | font-size: .75em; 68 | } 69 | .estimator tbody td a.save { 70 | display: none; 71 | font-size: .75em; 72 | } 73 | .estimator thead td.project-title:hover, 74 | .estimator thead td.hover { 75 | background-color: #FFEFC8; 76 | } 77 | .estimator thead td:hover a { 78 | display: inline-block; /* Make the edit/save links visible on hover */ 79 | } 80 | .estimator thead .project-title { 81 | text-align: left; 82 | /*width: 260px;*/ 83 | border-right: 1px solid rgba(0,0,0,.15); 84 | padding-right: .3125em; 85 | } 86 | .estimator thead .big { 87 | font-size: 1.5em; 88 | font-weight: bold; 89 | color: #000000; 90 | line-height: 1.25em; 91 | } 92 | .estimator tbody #clone_row { 93 | display: none; 94 | } 95 | .estimator tbody tr.alt { 96 | background: rgba(0,0,0,.05); 97 | } 98 | .estimator tbody td { 99 | text-align: center; 100 | vertical-align: middle; 101 | padding: 3px 0; 102 | } 103 | .estimator tbody td .task { 104 | 105 | } 106 | .estimator tbody td .task_field, 107 | #title_field { 108 | 109 | } 110 | .estimator tbody td a { 111 | display: none; 112 | } 113 | .estimator tbody td a.edit { 114 | 115 | } 116 | .estimator tbody td a.delete { 117 | 118 | } 119 | .estimator tbody td.left:hover, 120 | .estimator tbody td.hover { 121 | background-color: #FFEFC8; 122 | } 123 | .estimator tbody td:hover a, 124 | .estimator tbody td.hover a { 125 | display: inline; /* Make the edit/save links visible on hover */ 126 | } 127 | .estimator thead .left, 128 | .estimator tbody .left { 129 | text-align: left; 130 | padding-left: 1em; 131 | width: 50%; 132 | } 133 | .estimator thead .right, 134 | .estimator tbody .right { 135 | text-align: right; 136 | padding-right: 1em; 137 | width: 20%; 138 | } 139 | .estimator tbody .right { 140 | font-weight: bold; 141 | font-size: 1.5em; 142 | } 143 | .estimator table td.operator, 144 | .estimator table th.operator { 145 | color: rgba(0,0,0,.5); 146 | font-size: .75em; 147 | text-align: center; 148 | } 149 | .estimator .estimator-footer { 150 | padding: 1em 0 0 0; 151 | width: 100%; 152 | } 153 | .estimator .estimator-footer #tf_buttons{ 154 | padding: 0 0 0 1em; 155 | } 156 | .estimator .estimator-footer #tf_buttons a { 157 | 158 | } 159 | .estimator .estimator-footer a.add-task { 160 | 161 | } 162 | .estimator .estimator-footer a.add-task:hover { 163 | 164 | } 165 | .estimator .estimator-footer #reset_link { 166 | 167 | } 168 | .estimator .estimator-footer #grandTotal { 169 | font-size: 1.5em; 170 | font-weight: bold; 171 | text-align: right; 172 | width: 20%; 173 | padding: 0 1em 0 0; 174 | float: right; 175 | } 176 | 177 | /* ------------------------------------------------------------ 178 | Estimate Form Table - Editable 179 | ------------------------------------------------------------ */ 180 | 181 | #title_field, 182 | #default_rate_field, 183 | .task_field, 184 | .qty_field, 185 | .price_item_field { 186 | font-size: 1rem; 187 | padding: .25em; 188 | border: 1px solid rgba(0,0,0,.2); 189 | border-radius: 3px; 190 | } 191 | 192 | #default_rate_field { 193 | margin-left: .3125em; 194 | width: 30%; 195 | } 196 | 197 | /* ------------------------------------------------------------ 198 | Print Ready Estimate 199 | ------------------------------------------------------------ */ 200 | 201 | body.ep-body { 202 | background: white; 203 | } 204 | .ep-body .estimator { 205 | padding: 40px; 206 | margin: 0 auto; 207 | background: white; 208 | float: none; 209 | display: block; 210 | } 211 | .ep-body .estimator table tbody td { 212 | border-bottom: 1px solid #CCCCCC; 213 | } 214 | .ep-body .estimator table td:hover { 215 | background: none; 216 | } 217 | .ep-body .estimator thead .col-heads th { 218 | padding-bottom: .75em; 219 | } 220 | .ep-body .estimator .estimator-footer #grandTotal { 221 | font-size: 1.5em; 222 | line-height: 1.25em; 223 | color: black; 224 | } -------------------------------------------------------------------------------- /estimator.js: -------------------------------------------------------------------------------- 1 | /** 2 | *The following code fixes one of Microsoft's little presents to the developer community: 3 | */ 4 | var isOpera, isIE = false; 5 | if(typeof(window.opera) != 'undefined'){isOpera = true;} 6 | if(!isOpera && navigator.userAgent.indexOf('Internet Explorer')){isIE = true;} 7 | 8 | //fix both IE and Opera (adjust when they implement this method properly) 9 | if(isOpera || isIE){ 10 | document.nativeGetElementById = document.getElementById; 11 | //redefine it! 12 | document.getElementById = function(id){ 13 | var elem = document.nativeGetElementById(id); 14 | if(elem){ 15 | //verify it is a valid match! 16 | if(elem.attributes['id'] && elem.attributes['id'].value == id){ 17 | //valid match! 18 | return elem; 19 | } else { 20 | //not a valid match! 21 | //the non-standard, document.all array has keys for all name'd, and id'd elements 22 | //start at one, because we know the first match, is wrong! 23 | for(var i=1;i -1){ 108 | i = parseFloat(amount); 109 | if(isNaN(i)) { 110 | i = 0.00; 111 | } 112 | minus = ''; 113 | if(i < 0) { 114 | minus = '-'; 115 | } 116 | i = Math.abs(i); 117 | i = parseInt(((i + 0.005) * 100),0); 118 | i = i / 100; 119 | s = i.toString(); 120 | if(s.indexOf('.') < 0) { 121 | s += '.00'; 122 | } 123 | if(s.indexOf('.') == (s.length - 2)) { 124 | s += '0'; 125 | } 126 | s = minus + s; 127 | amount = s; 128 | } 129 | 130 | delimiter = ","; // replace comma if desired 131 | a = amount.split('.',2); 132 | d = a[1]; 133 | i = parseInt(a[0],0); 134 | 135 | if(isNaN(i)) { 136 | return ''; 137 | } 138 | minus = ''; 139 | if(i < 0) { 140 | minus = '-'; 141 | } 142 | i = Math.abs(i); 143 | n = i.toString(); 144 | a = []; 145 | 146 | while(n.length > 3) 147 | { 148 | nn = n.substr(n.length-3); 149 | a.unshift(nn); 150 | n = n.substr(0,n.length-3); 151 | } 152 | 153 | if(n.length > 0) { 154 | a.unshift(n); 155 | } 156 | 157 | n = a.join(delimiter); 158 | 159 | if(!d ){ 160 | if(n){ 161 | 162 | amount = n; 163 | } 164 | } else if(d.length < 1) { 165 | amount = n; 166 | } else { 167 | amount = n + '.' + d; 168 | } 169 | 170 | amount = minus + amount; 171 | 172 | return amount; 173 | } 174 | 175 | /** 176 | * Iterates through the current Task Rows, reorganizing them and calculating values. 177 | */ 178 | function estimator_calc(old_rate, skip_save){ 179 | 180 | var element, children, default_rate, total, count, alt, child, id, index, element_qty, element_price, price, qty, subtotal, element_task_field, element_edit, element_delete, element_save, i, estimator_next_row; 181 | 182 | element = document.getElementById('estimator_tbody'); 183 | 184 | children = element.getElementsByTagName('*'); 185 | 186 | default_rate = document.getElementById('default_rate_field').value; 187 | 188 | total = 0; 189 | 190 | count = 1; 191 | 192 | alt = 'alt'; 193 | 194 | for (i = 0; i < children.length; i++) { 195 | 196 | child = children[i]; 197 | 198 | id = child.getAttribute("id"); 199 | 200 | if(id){ 201 | 202 | if(id.indexOf('task_row_') === 0){ 203 | 204 | if(alt === ''){ 205 | alt = 'alt'; 206 | } else { 207 | alt = ''; 208 | } 209 | child.className = alt; 210 | 211 | child.id = 'task_row_' + count; 212 | 213 | index=id.charAt(id.length-1); 214 | 215 | if(id.charAt(id.length-2) != '_'){ 216 | index = id.charAt(id.length-2) + index; 217 | } 218 | 219 | element_qty = document.getElementById('qty_item_' + index); 220 | 221 | if(!element_qty.value){ 222 | element_qty.value = '0'; 223 | } 224 | 225 | element_price = document.getElementById('price_item_' + index); 226 | 227 | if(!element_price.value){ 228 | 229 | element_price.value = '0'; 230 | } 231 | 232 | if(element_price.value == old_rate){ 233 | 234 | element_price.value = default_rate; 235 | } 236 | 237 | price=element_price.value.replace(/,/g,''); 238 | 239 | qty=element_qty.value.replace(/,/g,''); 240 | 241 | subtotal = parseFloat(price) * parseFloat(qty); 242 | 243 | total += subtotal; 244 | 245 | subtotal = estimator_format_currency(subtotal); 246 | 247 | document.getElementById('total_item_' + index).innerHTML = subtotal; 248 | 249 | element_qty.id = 'qty_item_' + count; 250 | element_qty.name = 'qty_item_' + count; 251 | element_price.id = 'price_item_' + count; 252 | element_price.name = 'price_item_' + count; 253 | 254 | element_task_field = document.getElementById('task_field_' + index); 255 | element_task_field.id = 'task_field_' + count; 256 | element_task_field.name = 'task_field_' + count; 257 | 258 | document.getElementById('total_item_' + index).id = 'total_item_' + count; 259 | document.getElementById('task_label_' + index).id = 'task_label_' + count; 260 | 261 | element_edit = document.getElementById('task_edit_' + index); 262 | element_edit.id = 'task_edit_' + count; 263 | element_edit.index = count; 264 | element_edit.onclick = function(){ 265 | estimator_edit_task(this.index); 266 | return false; 267 | }; 268 | 269 | element_delete = document.getElementById('task_delete_' + index); 270 | element_delete.id = 'task_delete_' + count; 271 | element_delete.index = count; 272 | element_delete.onclick = function(){ 273 | estimator_delete_task(this.index); 274 | return false; 275 | }; 276 | 277 | element_save = document.getElementById('task_save_' + index); 278 | element_save.id = 'task_save_' + count; 279 | element_save.index = count; 280 | element_save.onclick = function(){ 281 | estimator_save_task(this.index); 282 | return false; 283 | }; 284 | 285 | if(!skip_save){ 286 | estimator_save_task(count); 287 | } 288 | count++; 289 | 290 | 291 | } 292 | } 293 | 294 | estimator_next_row = count; 295 | 296 | } 297 | 298 | total += ''; 299 | 300 | total = estimator_format_currency(total); 301 | 302 | document.getElementById('total').innerHTML = total; 303 | 304 | document.getElementById('currency_total').innerHTML = document.getElementById('currency_label').innerHTML; 305 | 306 | } 307 | 308 | /** 309 | * Called when the page is loaded to override the values that are in place for non-javascript clients. 310 | */ 311 | function estimator_init() { 312 | 313 | document.getElementById('estimator_form').target = 'form_target'; 314 | 315 | document.getElementById('add_task_button').style.display = 'inline'; 316 | 317 | document.getElementById('title_field').style.display = 'none'; 318 | document.getElementById('title_label').style.display = 'inline'; 319 | document.getElementById('title_edit').style.display = ''; 320 | 321 | document.getElementById('default_rate_field').style.display = 'none'; 322 | document.getElementById('currency_field').style.display = 'none'; 323 | document.getElementById('default_rate_label').style.display = 'inline'; 324 | document.getElementById('default_rate_edit').style.display = ''; 325 | document.getElementById('currency_label').style.display = 'inline'; 326 | 327 | document.getElementById('form_submit').style.display = 'none'; 328 | document.getElementById('form_link').style.display = ''; 329 | 330 | document.getElementById('reset_submit').style.display = 'none'; 331 | document.getElementById('reset_link').style.display = ''; 332 | 333 | estimator_next_row_index = parseInt(document.getElementById('estimator_row_count').value,0); 334 | 335 | estimator_calc(); 336 | 337 | 338 | } 339 | 340 | /** 341 | * Switches the Title label to an input box. 342 | */ 343 | function estimator_edit_title(){ 344 | 345 | document.getElementById('title_field').style.display = 'inline'; 346 | document.getElementById('title_label').style.display = 'none'; 347 | document.getElementById('title_edit').style.display = 'none'; 348 | document.getElementById('title_save').style.display = ''; 349 | 350 | } 351 | 352 | /** 353 | * Switches the Title input box to a label. 354 | */ 355 | function estimator_save_title(){ 356 | 357 | var element_label, element_field; 358 | element_label = document.getElementById('title_label'); 359 | 360 | element_field = document.getElementById('title_field'); 361 | 362 | element_field.style.display = 'none'; 363 | 364 | element_label.innerHTML = element_field.value; 365 | 366 | element_label.style.display = 'inline'; 367 | 368 | document.getElementById('title_edit').style.display = ''; 369 | document.getElementById('title_save').style.display = 'none'; 370 | } 371 | 372 | /** 373 | * Switches the Default Rate label to an input box. 374 | */ 375 | function estimator_edit_default_rate(){ 376 | 377 | document.getElementById('default_rate_field').style.display = 'inline'; 378 | document.getElementById('currency_field').style.display = 'inline'; 379 | document.getElementById('default_rate_label').style.display = 'none'; 380 | document.getElementById('default_rate_edit').style.display = 'none'; 381 | document.getElementById('currency_label').style.display = 'none'; 382 | document.getElementById('default_rate_save').style.display = ''; 383 | 384 | } 385 | 386 | /** 387 | * Switches the Default Rate input box to a label. 388 | */ 389 | function estimator_save_default_rate(){ 390 | 391 | var element_label, element_field, index, old_rate, val; 392 | 393 | element_label = document.getElementById('default_rate_label'); 394 | 395 | element_field = document.getElementById('default_rate_field'); 396 | 397 | old_rate = element_label.innerHTML; 398 | 399 | element_label.innerHTML = element_field.value; 400 | 401 | element_field.style.display = 'none'; 402 | 403 | element_label.style.display = 'inline'; 404 | 405 | element_label = document.getElementById('currency_label'); 406 | 407 | element_field = document.getElementById('currency_field'); 408 | 409 | index = element_field.selectedIndex; 410 | 411 | if(index > -1){ 412 | val = element_field.options[index].value; 413 | } 414 | 415 | element_label.innerHTML = val; 416 | 417 | element_label.style.display = 'inline'; 418 | 419 | element_field.style.display = 'none'; 420 | 421 | document.getElementById('default_rate_edit').style.display = ''; 422 | document.getElementById('currency_label').style.display = 'inline'; 423 | document.getElementById('default_rate_save').style.display = 'none'; 424 | 425 | //update blank prices, subtotals, total, including currency 426 | document.getElementById('currency_total').innerHTML = document.getElementById('currency_label').innerHTML; 427 | 428 | estimator_calc(old_rate); 429 | } 430 | 431 | /** 432 | * Adds a Task Row. 433 | */ 434 | function estimator_add_task(){ 435 | 436 | var next_row_index, clone_row, clone, children, i, element, id, element_tbody; 437 | 438 | /*global estimator_next_row_index */ 439 | next_row_index = estimator_next_row_index; 440 | 441 | clone_row = document.getElementById('clone_row'); 442 | 443 | clone = clone_row.cloneNode(true); 444 | 445 | clone.id = 'task_row_' + next_row_index; 446 | clone.style.display = ''; 447 | 448 | children = clone.getElementsByTagName('*'); 449 | for (i = 0; i < children.length; i++) { 450 | 451 | element = children[i]; 452 | 453 | id = element.getAttribute("id"); 454 | 455 | if(id == 'clone_field'){ 456 | 457 | element.id = 'task_field_' + next_row_index; 458 | 459 | element.setAttribute("name", 'task_field_' + next_row_index); 460 | 461 | element.value = 'New Task'; 462 | 463 | element.style.display = 'inline'; 464 | 465 | } else if(id == 'clone_label'){ 466 | 467 | element.id = 'task_label_' + next_row_index; 468 | 469 | element.innerHTML = 'New Task'; 470 | 471 | element.style.display = 'none'; 472 | 473 | } else if(id == 'clone_edit'){ 474 | 475 | element.id = 'task_edit_' + next_row_index; 476 | 477 | element.setAttribute("id", 'task_edit_' + next_row_index); 478 | 479 | element.style.display = 'none'; 480 | 481 | } else if(id == 'clone_delete'){ 482 | 483 | element.id = 'task_delete_' + next_row_index; 484 | 485 | element.style.display = 'none'; 486 | 487 | } else if(id == 'clone_save'){ 488 | 489 | element.id = 'task_save_' + next_row_index; 490 | 491 | element.style.display = ''; 492 | 493 | } else if(id == 'clone_qty'){ 494 | 495 | element.id = 'qty_item_' + next_row_index; 496 | 497 | element.name = 'qty_item_' + next_row_index; 498 | 499 | } else if(id == 'clone_price'){ 500 | 501 | element.id = 'price_item_' + next_row_index; 502 | 503 | element.name = 'price_item_' + next_row_index; 504 | 505 | } else if(id == 'clone_total'){ 506 | 507 | element.id = 'total_item_' + next_row_index; 508 | 509 | } 510 | 511 | } 512 | 513 | element_tbody = document.getElementById('estimator_tbody'); 514 | 515 | element_tbody.appendChild(clone); 516 | 517 | estimator_calc(); 518 | 519 | /*global estimator_next_row_index */ 520 | estimator_next_row_index++; 521 | 522 | } 523 | 524 | /** 525 | * Submits the form. 526 | */ 527 | function estimator_submit(){ 528 | 529 | document.getElementById('estimator_form').target = '_blank'; 530 | document.getElementById('estimator_form').submit(); 531 | document.getElementById('estimator_form').target = 'form_target'; 532 | 533 | } 534 | 535 | /** 536 | * Resets the form. 537 | */ 538 | function estimator_reset(){ 539 | 540 | var x = confirm('Are you sure you want to reset this form?'); 541 | if(x){ 542 | document.getElementById('estimator_reset').submit(); 543 | } 544 | } 545 | 546 | /** 547 | * Provides a handy alternative to the "alert" function for ad hoc debugging. 548 | */ 549 | function estimator_debug(message){ 550 | var x = confirm(message); 551 | if(!x){ 552 | 553 | throw "exit"; 554 | 555 | } 556 | 557 | } -------------------------------------------------------------------------------- /estimator.php: -------------------------------------------------------------------------------- 1 | _tpl = new Template(); 37 | 38 | $action = $this->_getArg('action'); 39 | 40 | switch ($action) { 41 | case 'estimate': 42 | $this->_estimate(); 43 | break; 44 | case 'reset': 45 | $this->_reset(); 46 | break; 47 | default: 48 | $this->_form(); 49 | } 50 | 51 | } 52 | 53 | /** 54 | * _reset 55 | * 56 | * Resets the state of the form to the default. 57 | * 58 | * @return void 59 | */ 60 | protected function _reset(){ 61 | setcookie('Astuteo_Estimator', '', time(), '/'); 62 | 63 | //$this->_form(); 64 | header('Location: .'); 65 | } 66 | 67 | /** 68 | * _estimate 69 | * 70 | * Generates the estimate report. 71 | * 72 | * 1) Formats the POST variables. 73 | * 2) Iterates through the values, calculting totals and building the report. 74 | * 3) If selected, saves the data to a cookie. 75 | * 76 | * @return void 77 | */ 78 | protected function _estimate(){ 79 | 80 | $params = $_POST; 81 | foreach($params as $key => $val){ 82 | $params[$key] = stripslashes($val); 83 | } 84 | $params['page_title'] = 'Project Estimate: '.$params['title_field']; 85 | 86 | $this->_setOutput($this->_tpl->header($params)); 87 | 88 | $count = 15; 89 | 90 | $total = 0; 91 | 92 | $this->_setOutput($this->_tpl->estimate_header($params)); 93 | 94 | foreach($params as $key => $val){ 95 | 96 | if(substr($key, 0, 11) == 'task_field_'){ 97 | 98 | $index = substr($key, -2); 99 | 100 | if(substr($index, 0, 1) == '_'){ 101 | 102 | $index = substr($key, -1); 103 | } 104 | 105 | $params['qty_item_'.$index] = $this->_checkKey('qty_item_'.$index, $params); 106 | $default_rate = $this->_checkKey('default_rate_field', $params); 107 | $params['price_item_'.$index] = $this->_checkKey('price_item_'.$index, $params, $default_rate); 108 | 109 | $qty = str_replace(',', '', $params['qty_item_'.$index]); 110 | $price = str_replace(',', '', $params['price_item_'.$index]); 111 | 112 | $params['fee_'.$index] = $qty * $price; 113 | 114 | $total += $params['fee_'.$index]; 115 | 116 | if(strpos($params['fee_'.$index], '.' !== FALSE)){ 117 | $params['fee_'.$index] = number_format($params['fee_'.$index], 2, '.', ','); 118 | } 119 | 120 | $fee = number_format($params['fee_'.$index], 2, '.', ','); 121 | if(strpos($fee, '.') !== FALSE){ 122 | if(substr($fee, -2) == '00'){ 123 | $fee = substr($fee, 0, -3); 124 | } 125 | } 126 | 127 | $params_row = array( 128 | 129 | 'task_field' => $this->_checkKey('task_field_'.$index, $params), 130 | 'qty_item' => $this->_checkKey('qty_item_'.$index, $params, '0'), 131 | 'price_item' => $this->_checkKey('price_item_'.$index, $params, '0'), 132 | 'fee' => $fee, 133 | 'count' => $index 134 | ); 135 | 136 | $this->_setOutput($this->_tpl->estimate_row($params_row)); 137 | 138 | } 139 | } 140 | 141 | $total = number_format($total, 2, '.', ','); 142 | if(strpos($total, '.') !== FALSE){ 143 | if(substr($total, -2) == '00'){ 144 | $total = substr($total, 0, -3); 145 | } 146 | } 147 | $params['total'] = $total; 148 | 149 | $this->_setOutput($this->_tpl->estimate_footer($params)); 150 | 151 | $save = $this->_checkKey('save', $params); 152 | 153 | if($save){ 154 | //expires in 1 year 155 | $cookie = $this->_implodeAssoc('|',$params); 156 | 157 | //$cookie = urlencode($cookie); 158 | setcookie('Astuteo_Estimator', $cookie, time()+60*60*24*30*12, '/'); 159 | 160 | } else { 161 | setcookie('Astuteo_Estimator', '', time()+60*60*24*30*12, '/'); 162 | 163 | } 164 | 165 | $this->_setOutput($this->_tpl->footer()); 166 | 167 | 168 | } 169 | 170 | /** 171 | * _form 172 | * 173 | * Generates the form. 174 | * 175 | * 1) Tries to get values back from the cookie. 176 | * 2) Iterates through the values, building the form. 177 | * 178 | * @return void 179 | */ 180 | protected function _form(){ 181 | 182 | $this->_setOutput($this->_tpl->header(array('page_title' => 'Web Development Project Estimator'))); 183 | 184 | $this->_setOutput($this->_tpl->form_header()); 185 | 186 | $params = $this->_explodeAssoc('|',$this->_checkKey('Astuteo_Estimator', $_COOKIE)); 187 | 188 | $params['title_field'] = $this->_checkKey('title_field', $params, 'Untitled Project'); 189 | 190 | $params['default_rate_field'] = $this->_checkKey('default_rate_field', $params, '0'); 191 | 192 | $params['currency_field'] = $this->_checkKey('currency_field', $params, '$'); 193 | 194 | $params['currency_field_select'] = $this->_getCurrency($this->_checkKey('currency_field', $params)); 195 | 196 | $this->_setOutput($this->_tpl->form_table_header($params)); 197 | 198 | $tasks = $this->_getDefaultTaskNames(); 199 | 200 | $class = 'alt'; 201 | 202 | $count = 0; 203 | 204 | $row_count = 1; 205 | 206 | foreach($params as $key => $val){ 207 | 208 | if(substr($key, 0, 11) == 'task_field_'){ 209 | 210 | $index = substr($key, -2); 211 | 212 | if(substr($index, 0, 1) == '_'){ 213 | 214 | $index = substr($key, -1); 215 | } 216 | 217 | if(!$class) { 218 | $class = 'alt'; 219 | } else { 220 | $class = ''; 221 | } 222 | 223 | $params_row = array( 224 | 'class' => $class, 225 | 'task_field' => $val, 226 | 'qty_item' => $this->_checkKey('qty_item_'.$index, $params, ''), 227 | 'price_item' => $this->_checkKey('price_item_'.$index, $params, ''), 228 | 'fee' => $this->_checkKey('fee_'.$index, $params, '0'), 229 | 'count' => $row_count 230 | ); 231 | 232 | $this->_setOutput($this->_tpl->form_table_row($params_row)); 233 | $row_count++; 234 | 235 | } 236 | 237 | } 238 | 239 | if($row_count == 1){ 240 | for($i = 1; $i < 11; $i++){ 241 | 242 | if(!$class) { 243 | $class = 'alt'; 244 | } else { 245 | $class = ''; 246 | } 247 | 248 | $task_field = $tasks[$i]; 249 | 250 | $params_row = array( 251 | 'class' => $class, 252 | 'task_field' => $task_field, 253 | 'qty_item' => '0', 254 | 'price_item' => $params['default_rate_field'], 255 | 'fee' => '0', 256 | 'count' => $i 257 | ); 258 | 259 | $this->_setOutput($this->_tpl->form_table_row($params_row)); 260 | $row_count++; 261 | } 262 | } 263 | 264 | $params['row_count'] = $row_count; 265 | 266 | if($this->_checkKey('save', $params)){ $params['save'] = 'checked="checked"'; } else { $params['save'] = ''; } 267 | 268 | $this->_setOutput($this->_tpl->form_table_footer($params)); 269 | 270 | $this->_setOutput($this->_tpl->form_footer()); 271 | 272 | $this->_setOutput($this->_tpl->footer()); 273 | 274 | 275 | } 276 | 277 | /** 278 | * _getDefaultTaskNames 279 | * 280 | * Provides the default form task names, in the event that there is no cookie. 281 | * 282 | * 1) Tries to get values back from the cookie. 283 | * 2) Iterates through the values, building the form. 284 | * 285 | * @return array 286 | */ 287 | protected function _getDefaultTaskNames(){ 288 | 289 | return array( 290 | '', 291 | 'Information Architecture', 292 | 'Design Research', 293 | 'Initial Drafts & Sketches', 294 | 'Design Revisions', 295 | 'HTML+CSS Development', 296 | 'Server-Side Development', 297 | 'Testing & Debugging', 298 | 'Copywriting', 299 | 'Photography', 300 | 'Client Meetings' 301 | ); 302 | } 303 | 304 | /** 305 | * _getCurrency 306 | * 307 | * Provides an option list for the current drop-down 308 | * 309 | * 1) Iterates through currency types. 310 | * 2) Sets the currently selected type as "selected". 311 | * 312 | * @param string $selected - the currently selected currency type 313 | * @return string 314 | */ 315 | protected function _getCurrency($selected){ 316 | 317 | 318 | $arr = array('$', '£', '€', '¥' ); 319 | $output = ''; 320 | foreach($arr as $val){ 321 | 322 | 323 | $output .= "