Module | TasksHelper |
In: |
app/helpers/tasks_helper.rb
|
File: | tasks_helper.rb |
(C): | Hipposoft 2008, 2009 |
Purpose: | Support functions for views related to Task objects. See controllers/tasks_controller.rb for more. |
04-Jan-2008 (ADH): Created.
Return list actions appropriate for the given task.
# File app/helpers/tasks_helper.rb, line 73 73: def taskhelp_actions( task ) 74: if ( @current_user.admin? or ( task.active and @current_user.manager? ) ) 75: actions = [ 'edit' ] 76: actions.push( 'delete' ) if ( @current_user.admin? ) 77: else 78: actions = [] 79: end 80: 81: actions.push( 'show' ) 82: return actions 83: end
As taskhelp_billable_class, but for active/inactive tasks.
# File app/helpers/tasks_helper.rb, line 107 107: def taskhelp_active_class( task_or_boolean ) 108: task_or_boolean = task_or_boolean.active if ( task_or_boolean.is_a?( Task ) ) 109: return ( task_or_boolean ? "task_active" : "task_inactive" ) 110: end
As taskhelp_billable_help, but for active/inactive tasks.
# File app/helpers/tasks_helper.rb, line 114 114: def taskhelp_active_help 115: "Names of active or inactive tasks are shown as follows: " << 116: "'<span class=\"#{ taskhelp_active_class( true ) }\">active</span>', " << 117: "'<span class=\"#{ taskhelp_active_class( false ) }\">inactive</span>'." 118: end
Return an HTML class name appropriate for a billable or not billable task according to the given task or, if given a boolean, the value of that boolean where ‘true’ means ‘billable‘
Note that an additional class name may be included for active/inactive tasks (but not when a boolean is given, obviously!).
# File app/helpers/tasks_helper.rb, line 92 92: def taskhelp_billable_class( task_or_boolean ) 93: task_or_boolean = task_or_boolean.billable if ( task_or_boolean.is_a?( Task ) ) 94: return ( task_or_boolean ? "task_billable" : "task_not_billable" ) 95: end
Return an HTML fragment giving help on billable versus non-billable tasks.
# File app/helpers/tasks_helper.rb, line 99 99: def taskhelp_billable_help 100: "Names of billable or non-billable tasks are shown as follows: " << 101: "'<span class=\"#{ taskhelp_billable_class( true ) }\">billable</span>', " << 102: "'<span class=\"#{ taskhelp_billable_class( false ) }\">not billable</span>'." 103: end
Return a name of a given task‘s associated customer, for use in list views.
# File app/helpers/tasks_helper.rb, line 60 60: def taskhelp_customer( task ) 61: return '-' if ( task.project.nil? or task.project.customer.nil? ) 62: return link_to( task.project.customer.title, customer_path( task.project.customer ) ) 63: end
Create a degrading task selector using either a YUI tree or a SELECT list, but not both. The latter has high database load. The former has greater client requirements.
The use cases for task selectors in Track Record are so varied that very specific cases are handled with special-case code and HTML output may include extra text to help the user for certain edge conditions, such as a lack of any available tasks (in a timesheet editor, this may be because all tasks are already added to the timesheet; for a user‘s choice of the default list of tasks to show in timesheets, this may be because the user has no permission to view any tasks; when configuring the tasks which a restricted user is able to see, this may be because no active tasks exist).
As a result, pass the reason for calling in the first parameter and an options list in the second.
Supported reasons are symbols and listsed in the ‘case’ statement in the code below. Each is preceeded by comprehensive comments describing the mandatory and (if any) optional key/value pairs which should go into the options hash. Please consult these comments for more information.
The following global options are also supported (none are mandatory):
Key Value ===================================================================== :line_prefix A string to insert at the start of each line of output - usually spaces, used if worried about the indentation of the overall view HTML.
# File app/helpers/tasks_helper.rb, line 338 338: def taskhelp_degrading_selector( reason, options ) 339: output = '' 340: form = options.delete( :form ) 341: user = options.delete( :user ) 342: 343: case reason 344: 345: # Generate a selector used to add tasks to a given timesheet. Includes a 346: # task selector and "add" button which submits to the given form with 347: # name "add_row". The timesheet and form are specified in the options: 348: # 349: # Key Value 350: # ===================================================================== 351: # :form Prevailing outer form, e.g. the value of "f" in a view 352: # which has enclosed the call in "form_for :foo do | f |". 353: # 354: # Leads to "task_ids" being invoked on the model associated 355: # with the form to determine which items, if any, must be 356: # initially selected for lists in the non-JS page version. 357: # 358: # :timesheet Instance of the timesheet being edited - used to find out 359: # which tasks are already included in the timesheet and 360: # thus which, if any, should be offered in the selector. 361: # 362: # NOTE: An empty string is returned if all tasks are already included in 363: # the timesheet. 364: # 365: when :timesheet_editor 366: tasks = timesheethelp_tasks_for_addition( options[ :timesheet ] ) 367: 368: unless ( tasks.empty? ) 369: 370: if ( session[ :javascript ].nil? ) 371: Task.sort_by_augmented_title( tasks ) 372: output << apphelp_collection_select( form, :task_ids, tasks, :id, :augmented_title ) 373: output << '<br />' 374: output << form.submit( 'Add', { :name => 'add_row', :id => nil } ) 375: else 376: output << taskhelp_tree_selector( 377: form, 378: { 379: :included_tasks => tasks, 380: :change_text => 'Choose tasks...', 381: :suffix_html => " then #{ form.submit( 'add them', { :name => 'add_row', :id => nil } ) }\n" 382: } 383: ) 384: end 385: 386: end 387: 388: # Generate a selector used to add tasks to a given report. The report and 389: # form are specified in the following options: 390: # 391: # Key Value 392: # ===================================================================== 393: # :form Prevailing outer form, e.g. the value of "f" in a view 394: # which has enclosed the call in "form_for :foo do | f |". 395: # 396: # Leads to "task_ids" being invoked on the model associated 397: # with the form to determine which items, if any, must be 398: # initially selected for lists in the non-JS page version. 399: # 400: # :report Instance of the report being created - used to find out 401: # which tasks are already included in the report (for form 402: # resubmissions, e.g. from validation failure). 403: # 404: # :inactive If 'true', the selector is generated for inactive tasks 405: # only. If omitted or 'false', only active tasks are shown. 406: # 407: # :name A name to use instead of "task_ids" in the form submission 408: # - optional, required if you want multiple selectors in the 409: # same form. 410: # 411: # NOTE: All edge case conditions (no tasks, etc.) are handled internally 412: # with relevant messages included in the HTML output for individual 413: # selectors, but the caller ought to check that at least *some* tasks can 414: # be chosen before presenting the user with a report generator form. 415: # 416: when :report_generator 417: report = options[ :report ] 418: active = ( options[ :inactive ] != true ) 419: field = active ? :active_task_ids : :inactive_task_ids 420: 421: if ( session[ :javascript ].nil? ) 422: tasks = active ? Task.active() : Task.inactive() 423: count = tasks.length 424: else 425: tasks = active ? report.active_tasks : report.inactive_tasks 426: count = @current_user.all_permitted_tasks.count 427: end 428: 429: if ( count.zero? ) 430: 431: hint = active ? :active : :inactive 432: output << "No #{ hint } tasks are available." 433: 434: else 435: 436: if ( session[ :javascript ].nil? ) 437: Task.sort_by_augmented_title( tasks ) 438: output << apphelp_collection_select( 439: form, 440: field, 441: tasks, 442: :id, 443: :augmented_title 444: ) 445: else 446: output << taskhelp_tree_selector( 447: form, 448: { 449: :selected_tasks => tasks, 450: :params_name => field, 451: :inactive => ! active 452: } 453: ) 454: end 455: end 456: 457: # Generate a selector which controls the default task list shown in 458: # new timesheets. 459: # 460: # Key Value 461: # ===================================================================== 462: # :user Instance of User model for the user for whom default timesheet 463: # options are being changed. 464: # 465: # :form Prevailing outer form, e.g. the value of "f" in a view 466: # which has enclosed the call in "form_for :foo do | f |". 467: # Typically this is used by a User configuration view, though, 468: # where a nested set of fields are being built via something 469: # like "fields_for :control_panel do | cp |". In such a case, 470: # use "cp" for the ":form" option's value. 471: # 472: # This leads to "task_ids" being invoked on the model associated 473: # with the form to determine which items in the selection list, 474: # if any, must be initially selected in the non-JS page version. 475: # 476: # NOTE: All edge case conditions (no tasks, etc.) are handled internally 477: # with relevant messages included in the HTML output. 478: # 479: when :user_default_task_list 480: 481: if ( user.active_permitted_tasks.count.zero? ) 482: 483: # Warn that the user has no permission to see any tasks at all. 484: 485: output << "This account does not have permission to view\n" 486: output << "any active tasks.\n" 487: output << "\n\n" 488: output << "<p>\n" 489: 490: # If the currently logged in user is unrestricted, tell them how to 491: # rectify the above problem. Otherwise, tell them to talk to their 492: # system administrator. 493: 494: if ( @current_user.restricted? ) 495: output << " Please contact your system administrator for help.\n" 496: else 497: output << " To enable this section, please assign tasks to\n" 498: output << " the user account with the security settings above\n" 499: output << " and save your changes. Then edit the user account\n" 500: output << " again to see the new permitted task list.\n" 501: end 502: 503: output << "</p>" 504: 505: else 506: 507: if ( session[ :javascript ].nil? ) 508: tasks = user.active_permitted_tasks 509: Task.sort_by_augmented_title( tasks ) 510: output << apphelp_collection_select( form, :task_ids, tasks, :id, :augmented_title ) 511: else 512: output << taskhelp_tree_selector( 513: form, 514: { 515: :restricted_by => ( user.restricted? ) ? user : nil, 516: :selected_tasks => user.control_panel.tasks 517: } 518: ) 519: end 520: 521: end 522: 523: # Generate a selector which controls the list of tasks the user is 524: # permitted to see. Mandatory options: 525: # 526: # Key Value 527: # ===================================================================== 528: # :user Instance of User model for the user to whom task viewing 529: # permission is being granted or revoked. 530: # 531: # :form Prevailing outer form, e.g. the value of "f" in a view 532: # which has enclosed the call in "form_for :foo do | f |". 533: # 534: # This leads to "task_ids" being invoked on the model associated 535: # with the form to determine which items in the selection list, 536: # if any, must be initially selected in the non-JS page version. 537: # 538: # NOTE: All edge case conditions (no tasks, etc.) are handled internally 539: # with relevant messages included in the HTML output. 540: # 541: when :user_permitted_task_list 542: return '' if ( @current_user.restricted? ) # Privileged users only! 543: 544: if ( Task.active.count.zero? ) 545: 546: output << "There are no tasks currently defined. Please\n" 547: output << "#{ link_to( 'create at least one', new_task_path() ) }." 548: 549: else 550: 551: if ( session[ :javascript ].nil? ) 552: tasks = Task.active() 553: Task.sort_by_augmented_title( tasks ) 554: output << apphelp_collection_select( form, :task_ids, tasks, :id, :augmented_title ) 555: else 556: 557: # Don't use "user.[foo_]permitted_tasks" here as we *want* an empty 558: # list for privileged accounts where no tasks have been set up. 559: 560: output << taskhelp_tree_selector( 561: form, 562: { :selected_tasks => user.tasks } 563: ) 564: end 565: 566: unless ( user.restricted? ) 567: output << "\n\n" 568: output << "<p>\n" 569: output << " This list is only enforced for users with a\n" 570: output << " 'Normal' account type. It is included here\n" 571: output << " in case you are intending to change the account\n" 572: output << " type and want to assign tasks at the same time.\n" 573: output << "</p>" 574: end 575: 576: end 577: end 578: 579: # All done. Indent or otherwise add a prefix to each line of output if 580: # so required by the options and return the overall result. 581: 582: line_prefix = options.delete( :line_prefix ) 583: output.gsub!( /^/, line_prefix ) unless ( output.empty? || line_prefix.nil? ) 584: 585: return output 586: end
Return a formatted duration for the given task, for use in list views.
# File app/helpers/tasks_helper.rb, line 67 67: def taskhelp_duration( task ) 68: return apphelp_string_hours( task.duration.to_s, '0', '?' ) 69: end
Return HTML showing the number of hours overrun on a task, or a label indicating no overrun. Pass the number of hours and the expected task duration.
# File app/helpers/tasks_helper.rb, line 43 43: def taskhelp_overrun( hours, duration ) 44: if ( hours > duration and duration != 0 ) 45: return apphelp_hours( hours - duration ) 46: else 47: return '<span class="no_overrun">None</span>' 48: end 49: end
Return a name of a given task‘s associated project, for use in list views.
# File app/helpers/tasks_helper.rb, line 53 53: def taskhelp_project( task ) 54: reutnr '-' if ( task.project.nil? ) 55: return link_to( task.project.title, project_path( task.project ) ) 56: end
Return HTML suitable for an edit form, providing a grouped list of projects that can be assigned to the task. The list is grouped by customer in default customer sort order, along with a ‘None’ entry for unassigned projects. Pass the task object being edited.
# File app/helpers/tasks_helper.rb, line 18 18: def taskhelp_project_selector( task ) 19: return apphelp_project_selector( 20: 'task_project_id', 21: 'task[project_id]', 22: task.project_id 23: ) 24: end
Return HTML showing the remaining hours on a task, or a label indicating overrun. Pass the number of hours and the expected task duration.
# File app/helpers/tasks_helper.rb, line 30 30: def taskhelp_remaining( hours, duration ) 31: if ( hours > duration and duration != 0 ) 32: return '<span class="overrun">Overrunning</span>' 33: else 34: return apphelp_hours( 0 ) if ( duration == 0 ) 35: return apphelp_hours( duration - hours ) 36: end 37: end
Generate a YUI tree task selector. Pass a form builder object in the first parameter (e.g. "bar" in "form_for @foo do | bar |"). The "object_name" field is used to generate a unique ID and name for a hidden element which carries IDs of selected YUI tree nodes, in the form "name[task_ids][]" - as used by non-JS SELECT lists elsewhere. In the form submission processing code, you must handle the use of special IDs in the YUI tree ("P" prefix for Projects, "C" prefix for Customers, no prefix for tasks).
In the next parameter optionally pass an options hash with keys and values as shown below; any omitted key/value pair results in the described default value being used instead.
Key Meaning ========================================================================= :inactive If 'true', only inactive tasks, customers and projects are shown in the selector. By default, only active items will be shown. :restricted_by The task/project/customer list for currently logged in users who are restricted is always restricted by that user's permitted task list no matter what you set here. If the current user is privileged, though, then passing in a User object results in restriction by that user's permitted task list. By default there is no restriction so for privileged currently logged in users, all tasks would be shown. :included_tasks If you know up-front a full list of tasks to show, then pass them in here. Only items in the included list will be shown. IDs get passed to the XHR handler, among other things. This is NOT a security feature - see ":restricted_by" for that. :selected_tasks An array of tasks to be initially selected in the tree. May be empty. By default no tasks are selected. Ideally the list should include no tasks that the current user or 'restricted_by' key would hide, but if it does, they simply won't be shown or selected and only the task IDs will appear in HTML output. :suffix_html Beneath the text area gadget listing selected tasks is a "Change..." link which pops up the Leightbox overlay containing the YUI tree. If you want any extra HTML inserted directly after the "</a>" of this link but before the (hidden) DIV enclosing the tree, use this option to include it. To keep things tidy, ensure that the string is terminated by a newline ("\n") character. :change_text Speaking of the "Change..." link - alter its text with this option, or omit for the default "Change..." string. :params_name Name to use in params instead of "task_ids", so that instead of reading "params[form.object_name][:task_ids]" in the controller handling the form submission, you read the params entry corresponding to the given name.
See also "taskhelp_degrading_selector" for JS/non-JS degrading code.
# File app/helpers/tasks_helper.rb, line 178 178: def taskhelp_tree_selector( form, options = {} ) 179: 180: inactive = options.delete( :inactive ) 181: restricted_by = options.delete( :restricted_by ) 182: included_tasks = options.delete( :included_tasks ) 183: selected_tasks = options.delete( :selected_tasks ) || [] 184: suffix_html = options.delete( :suffix_html ) || '' 185: change_text = options.delete( :change_text ) || 'Change...' 186: params_name = options.delete( :params_name ) || :task_ids 187: 188: # Callers may generate trees restricted by the current user, but if that 189: # user is themselves privileged their restricted task list will be empty 190: # (because they can see anything). The simplest way to deal with this is 191: # to clear the restricted user field in such cases. 192: 193: restricted_by = nil unless ( restricted_by.nil? || restricted_by.restricted? ) 194: 195: # Based on the restricted task list - or otherwise - try to get at the root 196: # customer array as easily as possible, trying to avoid pulling all tasks 197: # out of the database. This is a bit painful either way, but usually a 198: # restricted user will have a relatively small set of tasks assigned to 199: # them so doing the array processing in Ruby isn't too big a deal. 200: 201: if ( @current_user.restricted? ) 202: permitted_tasks = @current_user.active_permitted_tasks() 203: else 204: permitted_tasks = restricted_by.active_permitted_tasks() unless ( restricted_by.nil? ) 205: end 206: 207: unless ( included_tasks.nil? ) 208: if ( permitted_tasks.nil? ) 209: permitted_tasks = included_tasks 210: else 211: permitted_tasks &= included_tasks 212: end 213: end 214: 215: if ( permitted_tasks.nil? ) 216: root_customers = Customer.all 217: else 218: root_projects = permitted_tasks.map { | task | task.project }.uniq 219: root_customers = root_projects.map { | project | project.customer }.uniq 220: end 221: 222: # Reject items with no active / inactive tasks. 223: 224: method = inactive ? :inactive : :active 225: 226: root_customers.reject! do | customer | 227: customer.tasks.send( method ).count.zero? 228: end 229: 230: # Sort the root list by default order. Related associations are sorted 231: # according to declarations made in the model code. 232: 233: Customer.apply_default_sort_order( root_customers ) 234: 235: # Now take the selected task list and do something similar to get at the 236: # selected project and customer IDs so we can build a complete list of the 237: # node IDs to be initially expanded and checked in the YUI tree. The 238: # customer list is sorted so that when the YUI tree starts expanding nodes, 239: # it does it in display order from top to bottom - this looks better than 240: # an arbitrary expansion order. 241: 242: selected_projects = selected_tasks.map { | task | task.project }.uniq 243: selected_customers = selected_projects.map { | project | project.customer }.uniq 244: 245: Customer.apply_default_sort_order( selected_customers ) 246: 247: selected_task_ids = selected_tasks.map { | item | item.id } 248: selected_project_ids = selected_projects.map { | item | "P#{ item.id }" } 249: selected_customer_ids = selected_customers.map { | item | "C#{ item.id }" } 250: 251: selected_ids = selected_customer_ids + selected_project_ids + selected_task_ids 252: 253: # Turn an included task list into IDs too, if present 254: 255: included_ids = ( included_tasks || [] ).map { | item | item.id } 256: 257: # Generate the root node data and extra XHR parameters to pass to the tree 258: # controller in 'tree_controller.rb'. 259: 260: roots = root_customers.map do | customer | 261: { 262: :label => customer.title, 263: :isLeaf => false, 264: :id => "C#{ customer.id }" 265: } 266: end 267: 268: data_for_xhr_call = [] 269: data_for_xhr_call << 'inactive' if ( inactive ) 270: data_for_xhr_call << "restrict,#{ restricted_by.id }" unless ( restricted_by.nil? ) 271: data_for_xhr_call << "include,#{ included_ids.join( '_' ) }" unless ( included_ids.empty? ) 272: 273: # Create and (implicitly) return the HTML. 274: 275: id = "#{ form.object_name }_#{ params_name }" 276: name = "#{ form.object_name }[#{ params_name }][]" 277: tree = yui_tree( 278: :multiple => true, 279: :target_form_field_id => id, 280: :target_name_field_id => "#{ id }_text", 281: :name_field_separator => "\n", # Yes, a literal newline character 282: :name_include_parents => ' » ', 283: :name_leaf_nodes_only => true, 284: :form_leaf_nodes_only => true, 285: :expand => selected_ids, 286: :highlight => selected_ids, 287: :propagate_up => true, 288: :propagate_down => true, 289: :root_collection => roots, 290: :data_for_xhr_call => data_for_xhr_call.join( ',' ), 291: :div_id => 'yui_tree_container_' << id 292: ).gsub( /^/, ' ' ) 293: html = "<textarea disabled=\"disabled\" rows=\"5\" cols=\"60\" class=\"tree_selector_text\" id=\"\#{ id }_text\">Task data loading...</textarea>\n<br />\n<a href=\"#leightbox_tree_\#{ id }\" rel=\"leightbox_tree_\#{ id }\" class=\"lbOn\">\#{ change_text }</a>\n\#{ suffix_html }<div id=\"leightbox_tree_\#{ id }\" class=\"leightbox\">\n <a href=\"#\" class=\"lbAction\" rel=\"deactivate\">Close</a>\n \#{ taskhelp_billable_help }\n <p />\n \#{ hidden_field_tag( id, selected_task_ids.join( ',' ), { :name => name } ) }\n\#{ tree }\n <a href=\"#\" class=\"lbAction\" rel=\"deactivate\">Close</a>\n</div>\n" 294: end