Advertisement
najjah

Task Board Rally

Feb 11th, 2014
168
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
HTML 76.20 KB | None | 0 0
  1. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
  2.  
  3. <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  4.     <head>
  5.         <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
  6.         <title>Task Board</title>
  7.  
  8.         <link type="text/css" rel="stylesheet" href="__SERVER_URL__/js-lib/yui/2.6.0/build/reset-fonts-grids/reset-fonts-grids.css" />
  9.         <link type="text/css" rel="stylesheet" href="__SERVER_URL__/css/toolkit/0.02/renderer.css" />
  10.         <link type="text/css" rel="stylesheet" href="__SERVER_URL__/css/toolkit/0.02/grid.css" />
  11.         <link type="text/css" rel="stylesheet" href="__SERVER_URL__/css/toolkit/0.02/editor.css" />
  12.         <link type="text/css" rel="stylesheet" href="__SERVER_URL__/css/toolkit/0.02/toolkit.css" />
  13.  
  14.         <script type="text/javascript" src="__SERVER_URL__/js/toolkit/0.02/yui.js"></script>
  15.         <script type="text/javascript" src="__SERVER_URL__/js/toolkit/0.02/toolkit.js"></script>
  16.         <script type="text/javascript" src="__SERVER_URL__/js/toolkit/0.02/date.js"></script>
  17.         <script type="text/javascript" src="__SERVER_URL__/js/toolkit/0.02/modal.js"></script>
  18.         <script type="text/javascript" src="__SERVER_URL__/js/toolkit/0.02/html.js"></script>
  19.         <script type="text/javascript" src="__SERVER_URL__/js/toolkit/0.02/connection.js"></script>
  20.         <script type="text/javascript" src="__SERVER_URL__/js/toolkit/0.02/grid.js"></script>
  21.         <script type="text/javascript" src="__SERVER_URL__/js/toolkit/0.02.1/renderer/renderer.js"></script>
  22.         <script type="text/javascript" src="__SERVER_URL__/js/toolkit/0.02/editor.js"></script>
  23.         <script type="text/javascript" src="__SERVER_URL__/js/toolkit/0.02/datasource.js"></script>
  24.         <script type="text/javascript" src="/apps/1.33/sdk.js?apiVersion=1.29"></script>
  25.         <!-- <script type="text/javascript" src="/apps/2.0rc1/sdk.js"></script> -->
  26.         <script type="text/javascript" src="/slm/js-lib/async/async.min.js"></script>
  27.         <script type="text/javascript">
  28.                
  29.             // aqui tentamos preservar o scroll do iframe, para não perder de vista o objeto que modificamos
  30.             // só funcionou com um delay bem grande... a tabela demora a carregar
  31.             window.onload = function() {
  32.                 if(window.localStorage && localStorage.panelScrollTop) {
  33.                    setTimeout("document.body.scrollTop = localStorage.panelScrollTop;", 3000);
  34.                 }
  35.             }
  36.  
  37.             window.onunload = function() {
  38.                 if(window.localStorage) {
  39.                     localStorage.panelScrollTop = document.body.scrollTop;
  40.                 }
  41.             }
  42.    
  43.             var defaultTasks = [
  44.                 "Adicionar link ao documento da review",
  45.                 "Aplicar mudanças na JSP",
  46.                 "Analisar o problema",
  47.                 "Corrigir o problema",
  48.                 "Aplicar correção em produção via FIX",
  49.                 "Adicionar correção ao código",
  50.                 "Criar branch",
  51.                 "Criar cenários de testes",
  52.                 "Criar layout",
  53.                 "Montar mockup HTML",
  54.                 "Revisão de código",
  55.                 "Validação de HTML",
  56.                 "Validação de QA"
  57.             ];
  58.    
  59.             function showDefaultTasks() {
  60.                 var w = parent.editorWindow;
  61.                 if(w.location === null) return false;
  62.                 _showDefaultTasksCounter = window._showDefaultTasksCounter || 0;    
  63.                 try {
  64.                     var elem = w.document.getElementById('edit_header') || false;
  65.                     if(!elem) {
  66.                         if(++_showDefaultTasksCounter <= 10) setTimeout(showDefaultTasks, 1000);
  67.                            return false;
  68.                        }
  69.                    } catch(e) {
  70.                        if(++_showDefaultTasksCounter <= 10) setTimeout(showDefaultTasks, 1000);
  71.                    }
  72.                    var sel = w.document.createElement('select');
  73.                    sel.innerHTML = '<select id="default-tasks"><option>Tarefas comuns...</option><option>' + defaultTasks.join('</option><option>') + '</option></select>';
  74.                     sel.style.cssText = 'position: absolute; top: 2px; right: 45px;';
  75.                     sel.onchange = function() {
  76.                     var ind = this.selectedIndex, input = w.document.querySelector('#formContent tr:nth-child(3) input');
  77.                     if(ind == 0) return false;
  78.                     input.value = this.options[ind].innerHTML;
  79.                     sel.selectedIndex = 0;
  80.                 };
  81.                 elem.parentNode.insertBefore(sel, elem);
  82.                 _showDefaultTasksCounter = 0;
  83.             }
  84.    
  85.    
  86.             function showText(a) {
  87.                 closeText();
  88.                 var txt = a.nextSibling.innerHTML;
  89.                 var parent = a.parentNode.parentNode;
  90.                 var box = document.createElement('div');
  91.                 box.className = 'show-info';
  92.                 box.innerHTML = '<a class="closetxt" href="javascript://" onclick="closeText()"></a>'+txt;
  93.                 parent.appendChild(box);
  94.                 var links = box.getElementsByTagName('a');
  95.                 for(var i = 0; i < links.length; i++) {
  96.                    links[i].target = '_blank';
  97.                }
  98.            }
  99.    
  100.            function closeText() {
  101.                var boxes = document.querySelectorAll('.show-info');
  102.                for(var i = 0; i < boxes.length; i++) {
  103.                    boxes[i].parentNode.removeChild(boxes[i]);
  104.                }
  105.            }
  106.  
  107.            function deleteStory(ID, name, formatID) {
  108.                if(!confirm('Tem certeza que quer excluir a User Story '+formatID+': '+name+'?\n\nNão será possível desfazer esta ação.')) return false;
  109.                top.deleteAR({
  110.                    itemOid: ID,
  111.                    name: name,
  112.                    formattedID: formatID,
  113.                    msg: '',
  114.                    directChildrenCount: '0'
  115.                });
  116.            };
  117.  
  118.            function deleteTask(ID, name, formatID) {
  119.                if(!confirm('Tem certeza que quer excluir a Task '+formatID+': '+name+'?\n\nNão será possível desfazer esta ação.')) return false;
  120.                top.deleteAR({
  121.                    itemOid: ID,
  122.                    name: name,
  123.                    formattedID: formatID,
  124.                    msg: '',
  125.                    directChildrenCount: '0'
  126.                });
  127.            };
  128.  
  129.            function createStory() {
  130.                top.Rally.nav.Manager.create('userstory',{Iteration:'/iteration/' + document.getElementById('change_iteration_control').value});
  131.            };
  132.  
  133.            function createTask(objectID) {
  134.                rally.sdk.util.Navigation.popupCreatePage('task', {workProduct: objectID});
  135.                showDefaultTasks();
  136.            };
  137.  
  138.            function editObject(objectType, objectID) {
  139.                rally.sdk.util.Navigation.popupEditPage("/" + objectType + "/" + objectID);
  140.            };
  141.            
  142.            function toggle(id) {
  143.                var elem = Dom.get(id);
  144.                elem.style.display = elem.style.display == 'none' ? 'block' : 'none';
  145.            };  
  146.    
  147.            function filterByType() {
  148.                var types = document.getElementsByName('filter_product_type'), prefixes = [];
  149.                for(var i = 0; i < types.length; i++) {
  150.                    if(types[i].checked) prefixes.push(types[i].value);
  151.                }
  152.                selectedTypes = '(' + prefixes.join('|') + ')';
  153.                RALLY.toolkit.Cookie.add('selectedProductTypes', selectedTypes);
  154.  
  155.                var ths = document.querySelectorAll('.rally-grid-row-header');
  156.                for(var i = 0; i < ths.length; i++) {
  157.                    var tr = Dom.getAncestorByTagName(ths[i], 'tr'),
  158.                    prefix = ths[i].querySelector('.id > a[target="_blank"]').innerHTML.substr(0,2);    
  159.                     if(prefixes.length > 0) {
  160.                         if(new RegExp(selectedTypes).test(prefix)) tr.className = '';
  161.                         else tr.className = 'hidden';
  162.                     } else {
  163.                         tr.className = '';
  164.                     }
  165.    
  166.                     if(tr.className == '' && Dom.get('hide_empty_lines').checked) {
  167.                        var tasks = tr.querySelectorAll('.rally-grid-cell .rally-task'), visible = 0;
  168.                         for(var it = 0; it < tasks.length; it++) {
  169.                            if(tasks[it].className.indexOf('hidden') < 0) visible++;
  170.                        }
  171.                        if(visible == 0) tr.className = 'hidden';
  172.                    }
  173.                }
  174.            }
  175.    
  176.            function filterTasksByOwner(owner) {
  177.                var users = document.getElementsByName('filter_usernames'), usernames = [];
  178.                for(var i = 0; i < users.length; i++) {
  179.                    if(users[i].checked) usernames.push(users[i].value);
  180.                }
  181.                selectedOwners = usernames.join('|');
  182.                RALLY.toolkit.Cookie.add('selectedOwners', selectedOwners);
  183.    
  184.                var tasks = document.querySelectorAll('.rally-grid-cell .rally-task');
  185.                for(var i = 0; i < tasks.length; i++) {
  186.                    var owner = tasks[i].querySelector('.owner').innerHTML;
  187.                    if(selectedOwners && selectedOwners.indexOf(owner) < 0) {
  188.                        Dom.addClass(tasks[i], 'hidden');
  189.                    } else {
  190.                        Dom.removeClass(tasks[i], 'hidden');
  191.                    }
  192.                }
  193.                filterByType();
  194.            }
  195.    
  196.            function hideEmptyStories(show) {
  197.                var ths = document.querySelectorAll('.rally-grid-row-header');
  198.                for(var i = 0; i < ths.length; i++) {
  199.                    var tr = Dom.getAncestorByTagName(ths[i], 'tr');
  200.                    if(show) { Dom.removeClass(tr, 'hidden'); continue; }
  201.                    var tasks = tr.querySelectorAll('.rally-grid-cell .rally-task'), visible = 0;
  202.                    for(var it = 0; it < tasks.length; it++) {
  203.                        var cn = tasks[it].className;
  204.                        if(cn.indexOf('hidden') < 0) visible++;
  205.                    }
  206.                    if(visible == 0) {
  207.                        Dom.addClass(tr, 'hidden');
  208.                    }
  209.                }
  210.                filterByType();
  211.            }
  212.  
  213.            // users selection
  214.            function getHoveredUserIndex() {
  215.                var labels = Dom.get('filter_user_list').querySelectorAll('li.visible label');
  216.                //console.log(labels);
  217.                for(var i = 0; i < labels.length; i++) {
  218.                    if(labels[i].className.indexOf('hover') >= 0) return Number(labels[i].parentNode.getAttribute('data-index'));
  219.                 }
  220.                 return -1;
  221.             }
  222.    
  223.             function getVisibleIndexes() {
  224.                 var labels = Dom.get('filter_user_list').querySelectorAll('li.visible label'), ret = [];
  225.                 for(var i = 0; i < labels.length; i++) {
  226.                    var ind = Number(labels[i].parentNode.getAttribute('data-index'));
  227.                    ret[ret.length] = ind;
  228.                }
  229.                return ret;
  230.            }
  231.    
  232.            function changeHoveredUser(dir) {
  233.                var visible = getVisibleIndexes(),
  234.                sel = getHoveredUserIndex(),
  235.                index = inArray(visible, sel),
  236.                nIndex;
  237.                //console.log('visible, sel, index -> ', visible, sel, index);
  238.                 if(dir == 'up') {
  239.                     if(index <= 0) nIndex = visible.length - 1;
  240.                    else nIndex = index - 1;
  241.                } else {
  242.                    if(1 + index >= visible.length) nIndex = 0;
  243.                     else nIndex = index + 1;
  244.                 }
  245.                 setHoveredUser(visible[nIndex]);
  246.             }
  247.    
  248.             function inArray(arr, val) {
  249.                 for(var i = 0; i < arr.length; i++) {
  250.                    if(arr[i] == val) return i;
  251.                }
  252.                return -1;
  253.            }
  254.    
  255.            function selectHoveredUser() {
  256.                //console.log('selectHoveredUser');
  257.                var sel = getHoveredUserIndex();
  258.                if(sel < 0) return false;
  259.                Dom.get('filter_user_list').getElementsByTagName('input')[sel].click();
  260.            }
  261.    
  262.            function setHoveredUser(index) {
  263.                //console.log('setHoveredUser: ', index);
  264.                var labels = Dom.get('filter_user_list').getElementsByTagName('label');
  265.                for(var i = 0; i < labels.length; i++) {
  266.                    Dom.removeClass(labels[i], 'hover');
  267.                }
  268.                if(index >= 0 && labels[index].parentNode.className.indexOf('hidden') >= 0) {
  269.                }
  270.                if(index >= 0 && labels[index].parentNode.className.indexOf('hidden') < 0) {
  271.                    Dom.addClass(labels[index], 'hover');
  272.                     labels[index].scrollIntoView();
  273.                     document.body.scrollTop = 0;
  274.                 }
  275.             }
  276.  
  277.             function translateInfo() {
  278.                 var info = Dom.get('info').innerHTML.split('<br>'), timearr = info[1].split(/,* /);
  279.                 var months = {
  280.                     January: 'janeiro',
  281.                     February: 'fevereiro',
  282.                     March: 'março',
  283.                     April: 'abril',
  284.                     May: 'maio',
  285.                     June: 'junho',
  286.                     July: 'julho',
  287.                     August: 'agosto',
  288.                     September: 'setembro',
  289.                     October: 'outubro',
  290.                     November: 'novembro',
  291.                     December: 'dezembro'
  292.                 };
  293.                 var days = {
  294.                     Sunday: 'Domingo',
  295.                     Monday: 'Segunda',
  296.                     Tuesday: 'Terça',
  297.                     Wednesday: 'Quarta',
  298.                     Thursday: 'Quinta',
  299.                     Friday: 'Sexta',
  300.                     Saturday: 'Sábado',
  301.                 };              
  302.                 Dom.get('info').innerHTML = info[0] + '<br>' + days[timearr[0]] + ', ' + timearr[2] + ' de ' + months[timearr[1]] + ' de ' + timearr[3];
  303.             }
  304.  
  305.             function initTaskboard() {
  306.  
  307.                 RALLY.toolkit.HTML.createMaskLayer();
  308.                 var statusDiv = null;
  309.                 var setStatus = function(msg) {
  310.                     if (!statusDiv) {
  311.                         statusDiv = Dom.get('status');
  312.                     }
  313.                     if (msg) {
  314.                         RALLY.toolkit.HTML.enableMask(null, 0);
  315.                         Dom.addClass(window.document.body, 'show-progress');
  316.                     } else {
  317.                         RALLY.toolkit.HTML.disableMask();
  318.                         Dom.removeClass(window.document.body, 'show-progress');
  319.                     }
  320.                     statusDiv.innerHTML = msg || '';
  321.                 }
  322.                 var insideRally         = RALLY.toolkit.insideRally();
  323.                 var showActuals         = false;
  324.                 var currentProjectOid   = '__PROJECT_OID__';
  325.                 var projectScopeUp      = '__PROJECT_SCOPING_UP__' == 'true';
  326.                 var projectScopeDown    = '__PROJECT_SCOPING_DOWN__' == 'true';
  327.  
  328.                 // use this to rebuild the full query, stitching in the
  329.                 // project scoping information from the top-level variables above
  330.                 function getQuery() {
  331.                     var scoping             = "&project=${currentProject}&projectScopeUp=" + projectScopeUp + "&projectScopeDown=" + projectScopeDown;
  332.                     var paging              = "&pagesize=100";
  333.                     return {
  334.                         "currentProject"   : "/iteration:current/project",
  335.                         "#storyType"       : "/typedefinition?query=(Name = \"Hierarchical Requirement\")",
  336.  
  337.                         "taskUnit"         : "${iteration/workspace/workspaceConfiguration/taskUnitName}",
  338.                         "storyStates"      : "${#storyType/attributes[name=schedule state]/allowedvalues/stringvalue}",
  339.  
  340.                         "iteration"        : "/iteration:current?fetch=name,objectid&order=StartDate",
  341.                        "iterations"       : "/iterations?fetch=name,objectid&order=StartDate,Name&project=${currentProject}&projectScopeUp=false&projectScopeDown=false" + paging,
  342.  
  343.                        "users"            : "/users?fetch=displayname,loginname,emailaddress,objectid" + paging,
  344.  
  345.                        "tasks"            : "/tasks?fetch=description,notes,taskindex,name,objectid,formattedid,owner,blocked,estimate,todo,actuals,state,workproduct&query=(Iteration = ${iteration})" + scoping + paging,
  346.  
  347.                        "stories"          : "/hierarchicalrequirement?fetch=description,notes,rank,blocked,formattedid,name,objectid,owner,project,schedulestate,taskestimatetotal,taskremainingtotal,taskactualtotal,PlanEstimate,tasks&order=Rank&query=(Iteration = ${iteration})" + scoping + paging,
  348.                        "defects"          : "/defect?fetch=description,notes,rank,blocked,formattedid,name,objectid,owner,project,schedulestate,taskestimatetotal,taskremainingtotal,taskactualtotal&order=Rank&query=(Iteration = ${iteration})" + scoping + paging,
  349.                        "defectsuite"      : "/defectsuite?fetch=rank,blocked,formattedid,name,objectid,owner,project,schedulestate,taskestimatetotal,taskremainingtotal,taskactualtotal&order=Rank&query=(Iteration = ${iteration})" + scoping + paging,
  350.                        "testsets"         : "/testset?fetch=rank,blocked,formattedid,name,objectid,owner,project,schedulestate,taskestimatetotal,taskremainingtotal,taskactualtotal&query=(Iteration = ${iteration})" + scoping + paging
  351.                    };
  352.                 }
  353.  
  354.                 var query = getQuery();
  355.                 var renderTimeCell = function(label, value, c) {
  356.                     var v = (Lang.isValue(value)) ? value : '-';
  357.                     return '<div class="' + c + '"><div>' + label + '</div><span>' + v + '</span></div>';
  358.                 };
  359.                 var projScopeUpControl = Dom.get('proj_scope_up_control');
  360.                 var projScopeDownControl = Dom.get('proj_scope_down_control');
  361.                 if (insideRally) {
  362.                     Dom.setStyle(Dom.get('project'), 'display', 'none');
  363.                 } else {
  364.                     projScopeUpControl.checked = projectScopeUp;
  365.                     projScopeDownControl.checked = projectScopeDown;
  366.                     var updateAfterScopeChange = function(e) {
  367.                         var src = Event.getTarget(e);
  368.                         if(src.id == 'proj_scope_up_control') {
  369.                             projectScopeUp = src.checked;
  370.                         }
  371.                         else if(src.id == 'proj_scope_down_control') {
  372.                             projectScopeDown = src.checked;
  373.                         }
  374.                         query = getQuery();
  375.  
  376.                         RALLY.toolkit.showMessage('Refreshing iteration with selected project scoping');
  377.                         gridController.display();
  378.                     };
  379.                     Event.addListener(projScopeUpControl, 'click', updateAfterScopeChange);
  380.                     Event.addListener(projScopeDownControl, 'click', updateAfterScopeChange);
  381.                 }
  382.  
  383.                 var viewConfig = {
  384.  
  385.                     // configure columns
  386.                     columnAttribute: "State",
  387.                     columnValuesAccessor: function(model, modelSchema) {
  388.                         var stateOptions = modelSchema.Task.State.options;
  389.                         var stateNames = [];
  390.                         for(var i = 0, length = stateOptions.length; i < length; i++) {
  391.                            stateNames.push(stateOptions[i].Value);
  392.                        }
  393.                        return stateNames;
  394.                    },
  395.                    columnHeaderRenderer: function(container, value) {
  396.                        container.innerHTML = value;
  397.                    },
  398.  
  399.                    // configure rows
  400.                    rowAttribute: "WorkProduct",
  401.                    rowKeyAccessor: function(workProduct) {return workProduct.ObjectID;},
  402.                    rowValuesAccessor: function(model, modelSchema) {
  403.                        return model.items[0].workProducts;
  404.                    },
  405.                    rowHeaderRenderer: function(container, value, modelSchema) {
  406.                        var state,
  407.                            html = [],
  408.                            schema = modelSchema.WorkProduct,
  409.                            owner,
  410.                            ownerClass = ['owner'],
  411.                            divId = 'rally-workprod-' + value.ObjectID,
  412.                            divClass = ['rally-workprod'],
  413.                            timeClass = ['rally-time'];
  414.  
  415.                        // owner could be an object literal, or a string (if the user has been deleted in the app)
  416.                        if (!value.Owner) {
  417.                            ownerClass.push('de-emphasis');
  418.                            owner = gridController.noOwnerLabel;
  419.                        } else if (Lang.isObject(value.Owner)) {
  420.                            owner = value.Owner._refObjectName;
  421.                        } else {
  422.                            ownerClass.push('de-emphasis deleted-owner');
  423.                            owner = value.Owner;
  424.                        }
  425.                        if (value.ScheduleState == 'Accepted') {
  426.                            divClass.push('rally-workprod-accepted');
  427.                            divClass.push('de-emphasis');
  428.                        } else if (value.TaskRemainingTotal=='' || value.TaskRemainingTotal==0) {
  429.                            timeClass.push('de-emphasis');
  430.                        }
  431.  
  432.                        html.push('<div class="' + divClass.join(' ') + '" id="' + divId + '">');
  433.                         state = new RALLY.toolkit.renderer.StateRenderer({
  434.                             schema: schema.State,
  435.                             state: value.ScheduleState,
  436.                             blocked: value.Blocked
  437.                         });
  438.                         html.push('<div class="state">' + state.display(false, value.ScheduleState=='Accepted') + '</div>');
  439.  
  440.                         var detail_type = 'ar';
  441.                         if( /^DE/.test( value.FormattedID ) ) {
  442.                             detail_type = 'df';
  443.                         }
  444.        
  445.                         var _type = value._type.toLowerCase();
  446.                         html.push('<div class="id">');
  447.                                         html.push('<a href="/slm/detail/' + detail_type + '/' + value.ObjectID + '" title="Visualizar história" target="_blank">' + value.FormattedID + '</a> | ');
  448.                         html.push('<a href="javascript://" title="Editar história" onclick="editObject(\'' + _type + '\',' + value.ObjectID + '); return false;" class="edit-story"></a> | ');
  449.                         html.push('<a class="add-task" href="javascript://" title="Adicionar tarefa" onclick="createTask(' + value.ObjectID + '); return false;"></a>');
  450.                         html.push('</div>');
  451.  
  452.                         html.push('<a class="delete-story" href="javascript://" title="Remover história" onclick="deleteStory(' +
  453.                  value.ObjectID + ', \'' + value.Name + '\', \'' + value.FormattedID + '\'); return false;"></a>');
  454.  
  455.                         if(value.PlanEstimate) html.push('<div class="planestimate">' + value.PlanEstimate + '</div>');
  456.  
  457.                         html.push('<div class="name">' + RALLY.toolkit.niceSubstring(value.Name, 100) + '</div>');
  458.                         html.push('<div class="' + ownerClass.join(' ') + '">' + owner + '</div>');
  459.  
  460.                         if(value.Description) html.push('<div class="desc"><a onclick="showText(this)" href="javascript://">Description</a><div style="display:none">' + value.Description + '</div></div>');
  461.                         if(value.Notes) html.push('<div class="notes"><a onclick="showText(this)" href="javascript://">Notes</a><div style="display:none">' + value.Notes + '</div></div>');
  462.  
  463.                         html.push( renderTimeCell((schema.TaskEstimateTotal) ? schema.TaskEstimateTotal.DisplayName : 'Est', value.TaskEstimateTotal, timeClass.join(' ')) );
  464.                         html.push( renderTimeCell((schema.TaskRemainingTotal) ? schema.TaskRemainingTotal.DisplayName : 'To Do', value.TaskRemainingTotal, timeClass.join(' ')) );
  465.                         if (showActuals) {
  466.                             html.push( renderTimeCell((schema.TaskActualTotal) ? schema.TaskActualTotal.DisplayName : 'Actuals', value.TaskActualTotal, timeClass.join(' ')) );
  467.                         }
  468.  
  469.                         html.push('<div class="clear"></div>');
  470.                         html.push('</div>');
  471.  
  472.                         container.innerHTML = html.join('');
  473.                     },
  474.  
  475.                     // configure cells
  476.                     cellSortFunction: function(a,b) { return a - b; },
  477.  
  478.                     // configure items
  479.                     itemRankAccessor: function(item) { return item.TaskIndex },
  480.                     itemKeyAccessor: function(item) { return item.ObjectID },
  481.                     itemsAccessor: function(model) {
  482.                         return model.items[0].tasks;
  483.                     },
  484.                     itemRenderer: function(container, item, modelSchema) {
  485.                         var html = [],
  486.                             owner,
  487.                             taskClass = ['rally-task'],
  488.                             ownerClass = ['owner'],
  489.                             estClass = ['rally-time'],
  490.                             todoClass = ['rally-time'],
  491.                             actClass = ['rally-time'],
  492.                             schema = modelSchema.Task,
  493.                             contentStyle = '',
  494.                             editIconId = 'edit-' + Dom.generateId(),
  495.                             deleteIconId = 'delete-' + Dom.generateId();
  496.  
  497.                         // owner could be an object literal, or a string (if the user has been deleted in the app)
  498.                         if (!item.Owner) {
  499.                             ownerClass.push('de-emphasis');
  500.                             owner = gridController.noOwnerLabel;
  501.                         } else if (Lang.isObject(item.Owner)) {
  502.                             owner = item.Owner._refObjectName;
  503.                         } else {
  504.                             ownerClass.push('de-emphasis deleted-owner');
  505.                             owner = item.Owner;
  506.                         }
  507.                         if (item.Blocked) {
  508.                             taskClass.push('rally-task-blocked');
  509.                         }
  510.                         if (item.State == 'Defined') {
  511.                             todoClass.push('de-emphasis');
  512.                             actClass.push('de-emphasis');
  513.                         } else if (item.State == 'Completed') {
  514.                             todoClass.push('de-emphasis');
  515.                         }
  516.                         if (item.WorkProduct && item.WorkProduct.ScheduleState == 'Accepted') {
  517.                            taskClass.push('rally-task-accepted');
  518.                             taskClass.push('de-emphasis');
  519.                         }
  520.                         html.push('<div class="' + taskClass.join(' ') + '" id="rally-task-' + item.ObjectID + '">');
  521.  
  522.                         if (item.Owner && Lang.isValue(item.Owner.ObjectID)) {
  523.                            // if there is an image, we need to pad the content to account for it
  524.                            contentStyle = 'margin-left: 70px';
  525.                             html.push('<div class="image">');
  526.                             html.push('<img id="edit-control" src="' + RALLY.toolkit.Connection.getServerURL() + '/profile/viewThumbnailImage.sp?tSize=60&uid=' + item.Owner.ObjectID + '" alt="" />');
  527.                             html.push('</div>');
  528.                         }
  529.                         html.push('<div style="' + contentStyle + '">');
  530.  
  531.                         var _type = item._type.toLowerCase();
  532.                         html.push('<div class="id">');
  533.                                         html.push('<a href="/slm/detail/tk/' + item.ObjectID + '" target="_blank" title="Visualizar tarefa">' + item.FormattedID + '</a>');
  534.                         html.push(' | <a href="javascript://" class="edit-task" title="Editar em nova janela" onclick="editObject(\'' + _type + '\',' + item.ObjectID + '); return false;"></a>');
  535.                         html.push(' | <a id="' + editIconId + '" href="javascript://" class="short-edit-task" title="Editar tarefa"></a>');
  536.                         html.push('</div>');                        
  537.                         html.push('<a href="javascript://" class="delete-task" title="Remover tarefa" onclick="deleteTask(' +
  538.                  item.ObjectID + ', \'' + item.Name + '\', \'' + item.FormattedID + '\'); return false;"></a>');
  539.                         html.push('<div class="name">' + RALLY.toolkit.niceSubstring(item.Name) + '</div>');
  540.                        
  541.                         if(item.Notes) {
  542.                             //html.push('<a href="javascript://" class="view-notes" title="Ver anotações" onclick="return false;">Notas</a>');
  543.                             html.push('<div class="notes"><a onclick="showText(this)" href="javascript://">Notes</a><div style="display:none">' + item.Notes + '</div></div>');
  544.                         }
  545.  
  546.                         html.push('<div class="' + ownerClass.join(' ') + '">' + owner + '</div>');
  547.  
  548.                         html.push('<div class="time">');
  549.                         html.push( renderTimeCell((schema.Estimate) ? schema.Estimate.ShortName : 'Est', item.Estimate, estClass.join(' ')) );
  550.                         html.push( renderTimeCell((schema.ToDo) ? schema.ToDo.ShortName : 'To Do', item.ToDo, todoClass.join(' ')) );
  551.                         if (showActuals) {
  552.                             html.push( renderTimeCell((schema.Actuals) ? schema.Actuals.ShortName : 'Actuals', item.Actuals, actClass.join(' ')) );
  553.                         }
  554.                         html.push('<div class="clear"></div>');
  555.                         html.push('</div>');
  556.                         html.push('</div>');
  557.  
  558.                         if (item.Blocked) {
  559.                             html.push('<img class="blocked-icon" src="' + RALLY.toolkit.Connection.getServerURL() + '/images/icon_blocked.gif" alt="Blocked" />');
  560.                             html.push('<div class="clear"></div>');
  561.                         }
  562.  
  563.                         html.push('</div>');
  564.                         container.innerHTML = html.join('');
  565.                         Event.purgeElement(editIconId);
  566.                         Event.addListener(editIconId, 'click', function(e) {
  567.                             gridController.showEditor(item, schema);
  568.                         });
  569.                     },
  570.  
  571.                     // configure drag event
  572.                     dragDropCallback: function(item, value) {
  573.                         setStatus('Saving changes...');
  574.                         var cell = this.getRenderedItem(item);
  575.                         if (cell) {
  576.                             var anim = new YAHOO.util.ColorAnim(cell, { backgroundColor: { from: '#F5F4CD', to: '#fff' } });
  577.                             anim.animate();
  578.                         }
  579.                         gridController.saveChanges(item, { 'State': value }, 'taskboard', function() {
  580.                             setStatus();
  581.                         });
  582.                     }
  583.                 };
  584.  
  585.                 RALLY.toolkit.Controller = function(query, viewConfig) {
  586.                     this.query          = query;
  587.                     this.schemaConfig   = {};
  588.                     this.viewConfig     = viewConfig;
  589.                     this.dataSource     = new RALLY.toolkit.TaskboardDataSource(query, '__SERVER_URL__');
  590.                     this.view           = new RALLY.toolkit.Grid('taskboard', this.viewConfig);
  591.                     this.editor         = new RALLY.toolkit.Editor();
  592.                 };
  593.  
  594.                 RALLY.toolkit.Controller.prototype = {
  595.                     acceptedCookieKey:  'taskboard-hide-accepted',
  596.                     ownerCookieKey:     'taskboard-filter-by-owner',
  597.                     iterationCookieKey: 'taskboard-filter-by-iteration',
  598.                     projectOidCookieKey:'taskboard-current-project',
  599.                     noOwnerLabel:       'No Owner',
  600.                     allOwnersLabel:     'All Team Members',
  601.  
  602.                     getIterationByOid: function(objectID) {
  603.                         for (var i = 0; i < this.iterations.length; i++) {
  604.                            if (this.iterations[i].ObjectID == objectID) {
  605.                                return this.iterations[i];
  606.                            }
  607.                        }
  608.                        return null;
  609.                    },
  610.  
  611.                    display: function(iterationOid) {
  612.                        var len, iterations, selectedIteration,
  613.                            html = [],
  614.                            that = this,
  615.                            itr = (iterationOid) ? iterationOid : RALLY.toolkit.Cookie.get(this.iterationCookieKey) || '',
  616.                            projectOid = RALLY.toolkit.Cookie.get(this.projectOidCookieKey),
  617.                            curProjectOid = (currentProjectOid!='') ? currentProjectOid : null;
  618.  
  619.                        // if our selcted project has changed, reset the iteration ObjectID in the cookie
  620.                        if (projectOid != curProjectOid) {
  621.                            itr = '';
  622.                            RALLY.toolkit.Cookie.remove(this.iterationCookieKey);
  623.                        }
  624.                        RALLY.toolkit.Cookie.add(this.projectOidCookieKey, curProjectOid);
  625.  
  626.                        // re-write the query to scope to the specified iteration
  627.                        if (itr) {
  628.                            query.iteration = "/iterations?fetch=name,objectid&order=StartDate&query=(ObjectID = " + itr + ")";
  629.                        } else {
  630.                            query.iteration = "/iteration:current?fetch=name,objectid&order=StartDate";
  631.                        }
  632.                        setStatus('Loading...');
  633.                        if (this.dataSource) {
  634.                            this.dataSource.get(function(model) {
  635.                                var i;
  636.                                that.hideAcceptedControl = Dom.get('hide_accepted_control');
  637.                                that.iterationSelect = Dom.get('change_iteration_control');
  638.  
  639.                                // clear out the grid container and any state-dependant vars
  640.                                YAHOO.util.Dom.get('taskboard').innerHTML = '';
  641.                                that.users = [];
  642.                                YAHOO.util.Event.removeListener(that.hideAcceptedControl, 'click');
  643.                                YAHOO.util.Event.removeListener(that.iterationSelect, 'change');
  644.  
  645.                                if (model.errors.length > 0) {
  646.                                     html.push('<ul>');
  647.                                     for (i = 0, len = model.errors.length; i < len; i++) {
  648.                                        html.push('<li>' + model.errors[i].message + '</li>');
  649.                                     }
  650.                                     html.push('</ul>');
  651.                                     RALLY.toolkit.showError(html.join(''));
  652.                                     setStatus();
  653.                                     return;
  654.                                 } else if (model.items.length == 0 || (model.items.length == 1 && model.items[0].iteration == null)) {
  655.                                    RALLY.toolkit.showError('There are no stories to display in the selected project');
  656.                                     setStatus();
  657.                                     return;
  658.                                 }
  659.  
  660.                                 // add in the header info
  661.                                 Dom.get('proj_name').innerHTML = model.items[0].project.Name;
  662.                                 Dom.get('info').innerHTML = (model.items[0].iteration.Name || '') + '<br/>' + (RALLY.Date.formatNow());
  663.  
  664.                                 // build the iteration select
  665.                                 selectedIteration = RALLY.toolkit.Cookie.get(that.iterationCookieKey);
  666.                                 iterations = model.items[0].iterations;
  667.                                 that.iterations = model.items[0].iterations;
  668.                                 RALLY.toolkit.HTML.clearSelect(that.iterationSelect);
  669.                                 for (i = 0, len = iterations.length; i < len; i++) {
  670.                                    that.iterationSelect.options[that.iterationSelect.options.length] = new Option(iterations[i].Name, iterations[i].ObjectID);
  671.                                    if (iterations[i].ObjectID == selectedIteration || iterations[i].Name == model.items[0].iteration.Name) {
  672.                                        RALLY.toolkit.Cookie.add(that.iterationCookieKey, iterations[i].ObjectID);
  673.                                        that.iterationSelect.selectedIndex = i;
  674.                                    }
  675.                                }
  676.                                Event.removeListener(that.iterationSelect, 'change');
  677.                                Event.addListener(that.iterationSelect, 'change', that.updateIteration, that, true);
  678.                                if (model.items[0].tasks.length == 0 && model.items[0].workProducts.length == 0) {
  679.                                    RALLY.toolkit.showError('There are no stories to display for the given iteration');
  680.                                    setStatus();
  681.                                    return;
  682.                                }
  683.        
  684.                                // filtrar por tipo
  685.                                var selectedTypes = RALLY.toolkit.Cookie.get('selectedProductTypes');
  686.                                var selectedOwners = RALLY.toolkit.Cookie.get('selectedOwners');
  687.                                var productTypes = {}, prods = model.items[0].workProducts;
  688.                                for(var curId in prods) {
  689.                                    var curType = prods[curId]._type, curPrefix = prods[curId].FormattedID.substr(0,2);
  690.                                    if(curType == 'HierarchicalRequirement') curType = 'UserStory'; // corrigindo o nome, que curiosamente é 'HierarchicalRequirement'
  691.                                    if(typeof productTypes[curType] == 'undefined') productTypes[curType] = curPrefix;
  692.                                }
  693.        
  694.                                var count = 0, cbwrap = Dom.get('filter_product');
  695.                                for(var ptName in productTypes) {
  696.                                    var cleanName = ptName.replace(/([a-z])([A-Z])/, '$1 $2');
  697.                                    var cbox = document.createElement('input');
  698.                                    cbox.type = 'checkbox';
  699.                                    cbox.name = 'filter_product_type';
  700.                                    cbox.id = 'filter_product_type'+count;
  701.                                    cbox.value = productTypes[ptName];
  702.                                    if(selectedTypes && new RegExp(selectedTypes).test(productTypes[ptName])) {
  703.                                        cbox.checked = true;
  704.                                    }
  705.                                    var label = document.createElement('label');
  706.                                    label.appendChild(cbox);
  707.                                    label.appendChild(document.createTextNode(' '+cleanName));
  708.                                    cbwrap.appendChild(label);
  709.                                    cbox.onclick = filterByType;//AndUser;
  710.                                    count++;
  711.                                }      
  712.  
  713.                                // build the grid
  714.                                that.view.display(model);
  715.  
  716.                                // should we hide accepted work?
  717.                                if (RALLY.toolkit.Cookie.get(that.acceptedCookieKey)=='true' || RALLY.toolkit.Cookie.get(that.acceptedCookieKey)==null) {
  718.                                    that.hideAcceptedControl.checked = true;
  719.                                    that.toggleAccepted();
  720.                                }
  721.                                Event.removeListener(that.hideAcceptedControl, 'click');
  722.                                Event.addListener(that.hideAcceptedControl, 'click', that.toggleAccepted, that, true);
  723.  
  724.                                // add users into the select that were found during the rendering
  725.                                //that.refreshUserSelect();
  726.  
  727.                                // colocar usuarios na lista
  728.                                that.refreshUserList();
  729.        
  730.                                // atualiza os checkboxes
  731.                                filterByType();
  732.                                filterTasksByOwner();
  733.                                
  734.                                translateInfo();
  735.        
  736.                                // select users
  737.                                Event.addListener(document.body, 'mousedown', function(ev) {
  738.                                    var wrap = Dom.get('filter_users'), box = Dom.get('filter_users_wrap');
  739.                                    if(wrap == ev.target || Dom.isAncestor(wrap, ev.target)) return true;
  740.                                    box.style.display = 'none';
  741.                                });
  742.        
  743.        
  744.                                // select users
  745.                                Event.addListener(Dom.get('search_users'), 'keyup', function(ev) {      
  746.                                    if(ev.keyCode == 27) { // Esc
  747.                                        toggle('filter_users_wrap');
  748.                                    }      
  749.                                    if(ev.keyCode == 39) { // seta direita
  750.                                        selectHoveredUser();
  751.                                        return true;
  752.                                    }      
  753.                                    if(ev.keyCode == 38 || ev.keyCode == 40) { // setas up / down
  754.                                        var dir = ev.keyCode == 38 ? 'up' : 'down';
  755.                                        changeHoveredUser(dir);
  756.                                        return false;
  757.                                    }      
  758.                                    var term = this.value.toLowerCase(), lis = Dom.get('filter_user_list').getElementsByTagName('li');
  759.                                    for(var i = 0; i < lis.length; i++) {
  760.                                        if(term == '') {
  761.                                            Dom.removeClass(lis[i], 'hidden');
  762.                                            Dom.addClass(lis[i], 'visible');
  763.                                            continue;
  764.                                        }                                  
  765.                                        var val = lis[i].getElementsByTagName('label')[0].innerHTML.replace(/<[^>]+>/g, '').replace(/^\s*|\s*$/g, '').toLowerCase();
  766.                                         if(val.substr(0, term.length) == term) {
  767.                                             Dom.removeClass(lis[i], 'hidden');
  768.                                             Dom.addClass(lis[i], 'visible');
  769.                                         } else {
  770.                                             Dom.addClass(lis[i], 'hidden');
  771.                                             Dom.removeClass(lis[i], 'visible');
  772.                                         }
  773.                                     }
  774.                                 });
  775.        
  776.                                 Event.addListener(Dom.get('hide_empty_lines'), 'click', function() { hideEmptyStories(!this.checked); });
  777.            
  778.                                 // marcar todos os usuarios
  779.                                 Event.addListener(Dom.get('checkall'), 'click', function() {
  780.                                     if(this.checked) {
  781.                                         var inputs = Dom.getAncestorByTagName(this, 'ul').getElementsByTagName('input');
  782.                                         for(var i = 1; i < inputs.length; i++) {
  783.                                            inputs[i].checked = false;
  784.                                            inputs[i].parentNode.className = '';
  785.                                        }
  786.                                        filterTasksByOwner();
  787.                                    }
  788.                                });
  789.                                setStatus();
  790.                            }, query);
  791.                        }
  792.                    },
  793.  
  794.                    showEditor: function(item, schema) {
  795.                        var editorConfig = {
  796.                            props: (showActuals) ? ['Estimate', 'ToDo', 'Actuals', 'Owner', 'State'] : ['Estimate', 'ToDo', 'Owner', 'State'],
  797.                            propertyKeyAccessors: {
  798.                                Owner: function(prop) { return prop != null ? prop.LoginName : null; }
  799.                            },
  800.                            propertyValueAccessors: {
  801.                                Owner: function(prop) { return prop != null ? prop._refObjectName : null; }
  802.                            },
  803.                            titleAccessor: function(obj) { return obj.FormattedID + '<br/>' + obj.Name; },
  804.                             onErrorCallback: function() { gridController.display(); },
  805.  
  806.                             onSaveCallback: function(changes) { gridController.saveChanges(item, changes, 'editor'); }
  807.                         };
  808.                         console.log(item, schema, editorConfig)
  809.                         this.editor.display(item, schema, editorConfig);
  810.                     },
  811.  
  812.                     getParentRow: function(el) {
  813.                         while (el && el.tagName && el.tagName.toUpperCase()!='TR') { el = el.parentNode; }
  814.                         return el;
  815.                     },
  816.  
  817.                     toggleAccepted: function() {
  818.                         var i, len, row,
  819.                             hideAcceptedControl = this.hideAcceptedControl || Dom.get('hide_accepted_control'),
  820.                             //userSelect = this.userSelect || Dom.get('filter_user_control'),
  821.                             hideAccepted = hideAcceptedControl.checked,
  822.                             workProds = Dom.getElementsByClassName('rally-workprod-accepted');
  823.  
  824.                         RALLY.toolkit.Cookie.add(this.acceptedCookieKey, hideAccepted);
  825.  
  826.                         for (i = 0, len = workProds.length; i < len; i++) {
  827.                            row = this.getParentRow(workProds[i]);
  828.  
  829.                            if (hideAccepted) {
  830.                                Dom.addClass(row, 'accepted');
  831.                            } else {
  832.                                Dom.removeClass(row, 'accepted');
  833.                            }
  834.                        }
  835.  
  836.                        /*if (hideAccepted && this.numItemsVisible() == 0) {
  837.                            if (userSelect.selectedIndex >= 0 && userSelect.options[userSelect.selectedIndex].value == this.allOwnersLabel) {
  838.                                RALLY.toolkit.showWarning('The selected iteration contains only accepted stories');
  839.                             } else {
  840.                                 RALLY.toolkit.showWarning('The selected owner has only accepted stories and/or tasks');
  841.                             }
  842.                         }*/
  843.                     },
  844.  
  845.                     refreshUserList: function() {
  846.                         var selectedOwners = (RALLY.toolkit.Cookie.get('selectedOwners') || ''),
  847.                         users = this.getOwnersOnTaskboard(),
  848.                         list = Dom.get('filter_user_list'),
  849.                         count = 0;                      
  850.                         list.innerHTML = '<li class="visible"><label><input id="checkall" type="checkbox" value="all" data-index="0"> Todos</label></li>';
  851.                         users.sort(function(a,b) { return (a.toLowerCase() < b.toLowerCase()) ? -1: 1; });
  852.        
  853.                        for(var i = 0; i < users.length; i++) {
  854.                            var cleanName = users[i];
  855.                            var label = document.createElement('label');
  856.                            var cbox = document.createElement('input');
  857.                            cbox.type = 'checkbox';
  858.                            cbox.name = 'filter_usernames';
  859.                            cbox.id = 'filter_usernames'+count;
  860.                            cbox.value = cleanName;
  861.                            if(selectedOwners && selectedOwners.indexOf(cleanName) >= 0) {
  862.                                 label.className = 'checked';
  863.                                 cbox.checked = true;
  864.                             }
  865.                             label.appendChild(cbox);
  866.                             label.appendChild(document.createTextNode(' '+ cleanName));
  867.                             var li = document.createElement('li');
  868.                             li.className = 'visible';
  869.                             li.setAttribute('data-index', String(i+1));
  870.                             li.appendChild(label);
  871.                             list.appendChild(li);
  872.                             cbox.onclick = function() {
  873.                                 Dom[this.checked ? 'addClass' : 'removeClass'](this.parentNode, 'checked'); Dom.get('checkall').checked = false;
  874.                                 filterTasksByOwner();
  875.                             };
  876.                             label.onmouseover = function() {
  877.                                 setHoveredUser(-1);
  878.                             };
  879.                             count++;
  880.                         }
  881.                     },
  882.  
  883.                     saveChanges: function(item, changes, source, successCallback) {
  884.                         var that = this, i, len, html = [], error, isConcurrencyError;
  885.                         if (!Lang.isValue(source)) {
  886.                             source = 'taskboard';
  887.                         }
  888.                         // set to-do to 0
  889.                         if(changes.State == "Completed") { changes.ToDo = "0"; }
  890.                         // always include _objectVersion
  891.                         if (!changes._objectVersion) {
  892.                             changes._objectVersion = item._objectVersion;
  893.                         }
  894.                         if (this.dataSource) {
  895.                             this.dataSource.update(item, changes, function(model) {
  896.                                 if (model.OperationResult.Errors.length == 0) {
  897.                                     that.refreshItem(item);
  898.                                     that.editor.close();
  899.                                     if (successCallback) {
  900.                                         successCallback();
  901.                                     }
  902.                                 } else {
  903.                                     html.push('<ul>');
  904.                                     for (i = 0, len = model.OperationResult.Errors.length; i < len; i++) {
  905.                                        error = model.OperationResult.Errors[i];
  906.                                        if (error.toLowerCase().indexOf('concurrency conflict') != -1 || error.toLowerCase().indexOf('could not read') != -1) {
  907.                                            isConcurrencyError = true;
  908.                                        }
  909.                                        error = that.parseSaveErrorsIntoReadableString(error);
  910.                                        if (error) {
  911.                                            html.push('<li>' + error + '</li>');
  912.                                         }
  913.                                     }
  914.                                     html.push('</ul>');
  915.  
  916.                                     if (source == 'editor') {
  917.                                         that.editor.displayError(html.join(''), isConcurrencyError);
  918.                                     } else {
  919.                                         RALLY.toolkit.showWarning('The task you have drag-n-dropped has been modified by another user.<br/>Refreshing task board state.');
  920.                                         that.display();
  921.                                     }
  922.                                 }
  923.                             });
  924.                         }
  925.                     },
  926.  
  927.                     parseSaveErrorsIntoReadableString: function(error) {
  928.                         var matches, tmp;
  929.  
  930.                         // what type of error did we get back?
  931.                         if (error.toLowerCase().indexOf('concurrency conflict') != -1) {
  932.                             error = 'Item was modified by another user';
  933.                         } else if (error.toLowerCase().indexOf('could not read') != -1) {
  934.                             error = 'Item was deleted by another user';
  935.                         } else if (error.toLowerCase().indexOf('could not convert') != -1) {
  936.                             // let's try and strip some of the nastyness out of this message
  937.                             matches = error.match(/could not convert: (.*)/i);
  938.                             if (matches && matches.length > 1) {
  939.                                error = matches[1];
  940.                             }
  941.                             error = error.replace('double', 'number');
  942.                         } else if (error.toLowerCase().indexOf('validation error') != -1) {
  943.                             matches = error.match(/validation error: .* ([a-z]+) >= 0 is an invalid numeric/i);
  944.                             if (matches && matches.length > 1) {
  945.                                tmp = matches[1].toLowerCase();
  946.                                 // ignore these rollup fields
  947.                                 if (tmp == 'taskestimatetotal' || tmp == 'taskremainingtotal') {
  948.                                     return null;
  949.                                 }
  950.                                 error = '"' + tmp.substr(0,1).toUpperCase() + tmp.substr(1) + '" must be non-negative';
  951.                             }
  952.                         }
  953.                         return error;
  954.                     },
  955.  
  956.                     deleteItem: function(task) {
  957.                         var that = this;
  958.  
  959.                         if (task) {
  960.                             this.dataSource.remove(task, function(model) {
  961.  
  962.                                 if (model.OperationResult.Errors.length == 0) {
  963.                                     that.view.removeItem(task);
  964.  
  965.                                     if(RALLY.toolkit.insideRally() && parent && parent.RALLY) {
  966.                                        parent.RALLY.util.showDeleteFlair({
  967.                                            oid: task.ObjectID,
  968.                                            record: task,
  969.                                            recordName : task.FormattedID + ': ' + task.Name,
  970.                                            restorable : true
  971.                                        });
  972.                                     }
  973.                                     that.dataSource.refreshWorkProduct(task.WorkProduct, function(newWorkProduct) {
  974.                                         that.view.refreshRowHeader(newWorkProduct);
  975.                                        // that.refreshUserSelect();
  976.                                         that.refreshUserList();
  977.                                     });
  978.                                 } else {
  979.                                     RALLY.toolkit.showError('There was an error deleting the task');
  980.                                     // rebuild the task board to get back to a consistent state
  981.                                     that.updateIteration();
  982.                                 }
  983.                             });
  984.                         }
  985.                     },
  986.  
  987.                     refreshItem: function(item) {
  988.                         var that = this;
  989.                         if (item) {
  990.                             this.dataSource.refreshTask(item, function(model) {
  991.                                 that.view.refreshItem(model.items[0].tasks[0]);
  992.                                 //that.refreshUserSelect();
  993.                                 that.refreshUserList();
  994.                                 that.view.refreshRowHeader(model.items[0].workProducts[0]);
  995.                             });
  996.                         }
  997.                     },
  998.  
  999.                     getOwnersOnTaskboard: function() {
  1000.                         var i, len, div, ownerMap = {}, owners = [], items = this.view.findItems();
  1001.                         for (i = 0, len = items.length; i < len; i++) {
  1002.                            div = Dom.getElementsByClassName('owner', 'div', items[i])[0];
  1003.                            if (div && div.innerHTML != this.noOwnerLabel && !Dom.hasClass(div, 'deleted-owner')) {
  1004.                                ownerMap[div.innerHTML] = 1;
  1005.                            }
  1006.                        }
  1007.                        for (i in ownerMap) {
  1008.                            owners.push(RALLY.toolkit.HTML.unescapeXMLEntities(i));
  1009.                        }
  1010.                        return owners;
  1011.                    },
  1012.  
  1013.                    numItemsVisible: function() {
  1014.                        var i, len, items, num = 0;
  1015.                        var comparator = function(el) {
  1016.                            try { return Dom.hasClass(el, 'rally-task') || Dom.hasClass(el, 'rally-workprod'); } catch (e) {}
  1017.                            return false;
  1018.                        };
  1019.                        items = this.view.findItems(comparator);
  1020.                        for (i = 0, len = items.length; i < len; i++) {
  1021.                            if ( !(Dom.hasClass(items[i], 'hidden') || Dom.hasClass(this.getParentRow(items[i]), 'accepted') || Dom.hasClass(this.getParentRow(items[i]), 'hidden')) ) {
  1022.                                num++;
  1023.                            }
  1024.                        }
  1025.                        return num;
  1026.                    },
  1027.  
  1028.                    filterByOwner: function() {
  1029.                        var i, len, that = this, shown = 0, shownAccepted = 0, itemMap = {}, rows = [], items, item,
  1030.                            sel = this.userSelect || Dom.get('filter_user_control'),
  1031.                            selected = sel.options[sel.selectedIndex];
  1032.  
  1033.                        RALLY.toolkit.Cookie.add(this.ownerCookieKey, selected.innerHTML);
  1034.  
  1035.                        var buildComparator = function(owner) {
  1036.                            return function(item) {
  1037.                                try {
  1038.                                    return owner.innerHTML == that.allOwnersLabel || YAHOO.util.Dom.getElementsByClassName('owner', 'div', item)[0].innerHTML == owner.innerHTML;
  1039.                                } catch (e) {}
  1040.                                return false;
  1041.                            };
  1042.                        };
  1043.                        var isTask = function(o) { return Dom.hasClass(o, 'rally-task'); };
  1044.                        var isWorkProd = function(o) { return Dom.hasClass(o, 'rally-workprod'); };
  1045.                        var isParentVisible = function(el) {
  1046.                            for (var i = 0, len = rows.length; i < len; i++) {
  1047.                                if (rows[i] == el) { return true; }
  1048.                            }
  1049.                        };
  1050.  
  1051.                        items = this.view.findItems(buildComparator(selected));
  1052.                        for (i = 0, len = items.length; i < len; i++) {
  1053.                            item = items[i];
  1054.                            if (isTask(item)) {
  1055.                                itemMap[item.id] = 1;
  1056.                                rows.push(this.getParentRow(item));
  1057.                            } else if (isWorkProd(item)) {
  1058.                                rows.push(this.getParentRow(item));
  1059.                            }
  1060.                        }
  1061.  
  1062.                        items = this.view.findItems();
  1063.                        for (i = 0, len = items.length; i < len; i++) {
  1064.                            item = items[i];
  1065.  
  1066.                            if (isTask(item)) {
  1067.                                if (typeof itemMap[item.id] == 'undefined') {
  1068.                                    Dom.addClass(item, 'hidden');
  1069.                                } else {
  1070.                                    if (this.hideAcceptedControl.checked && Dom.hasClass(item, 'rally-task-accepted')) {
  1071.                                        shownAccepted++;
  1072.                                    } else {
  1073.                                        shown++;
  1074.                                    }
  1075.                                    Dom.removeClass(item, 'hidden');
  1076.                                }
  1077.                            } else if (isWorkProd(item)) {
  1078.                                item = this.getParentRow(item);
  1079.                                if (isParentVisible(item)) {
  1080.                                    if (this.hideAcceptedControl.checked && Dom.hasClass(item, 'hidden')) {
  1081.                                        shownAccepted++;
  1082.                                    } else {
  1083.                                        shown++;
  1084.                                    }
  1085.                                    Dom.removeClass(item, 'hidden');
  1086.                                } else {
  1087.                                    Dom.addClass(item, 'hidden');
  1088.                                }
  1089.                            }
  1090.                        }
  1091.                        if (shown == 0 && shownAccepted > 0) {
  1092.                             RALLY.toolkit.showWarning('The selected owner has only accepted stories and/or tasks');
  1093.                         }
  1094.                         if ((shown+shownAccepted) == 0) {
  1095.                             RALLY.toolkit.showWarning('The selected owner does not own any stories or tasks');
  1096.                         }
  1097.                     },
  1098.  
  1099.                     updateIteration: function() {
  1100.                         var sel = this.iterationSelect,
  1101.                             selected = sel.options[sel.selectedIndex];
  1102.                         RALLY.toolkit.Cookie.add(this.iterationCookieKey, selected.value);
  1103.                         this.display(selected.value);
  1104.                     }
  1105.                 };
  1106.  
  1107.                 var config = (query.adHocQuery) ? query : { adHocQuery: query };
  1108.                 config.integrationInfo = {};
  1109.                 config.integrationInfo.Name = "Mashup: Taskboard";
  1110.                 config.integrationInfo.Version = "2009.5";
  1111.                 config.integrationInfo.Vendor = "Rally Software";
  1112.                 var gridController = new RALLY.toolkit.Controller(config, viewConfig);
  1113.                 gridController.display();
  1114.             }
  1115.  
  1116.             YAHOO.util.Event.addListener(window, 'load', initTaskboard);
  1117.         </script>
  1118.  
  1119.         <style type="text/css">
  1120.             /** Customize the default grid styles **/
  1121.             body {
  1122.                 font-family: tahoma,geneva,helvetica,arial,sans-serif;
  1123.             }
  1124.             .rally-grid-item {
  1125.                 border: 0;
  1126.             }
  1127.             .rally-grid-item-content {
  1128.                 margin: 0;
  1129.             }
  1130.             .rally-grid-column-header {
  1131.                 padding: .2em;
  1132.             }
  1133.  
  1134.             /** Custom styles for this implementation **/
  1135.             #wrapper {
  1136.                 margin: .8em;
  1137.             }
  1138.             #header {
  1139.                 border-bottom: 2px solid #666;
  1140.                 padding: .2em .3em;
  1141.                 margin-bottom: .4em;
  1142.             }
  1143.             #header #title {
  1144.                 float: left;
  1145.                 text-align: left;
  1146.             }
  1147.             #header #title h3 {
  1148.                 font-weight: bold;
  1149.                 font-size: 1.6em;
  1150.             }
  1151.             #header #info {
  1152.                 float: right;
  1153.                 text-align: right;
  1154.             }
  1155.             .clear {
  1156.                 clear: both;
  1157.                 height: 0;
  1158.             }
  1159.             .hidden, .accepted {
  1160.                 display: none;
  1161.                 visibility: hidden;
  1162.                 height: 0;
  1163.             }
  1164.             .de-emphasis {
  1165.                 color: #999;
  1166.             }
  1167.             .rally-time {
  1168.                 float: left;
  1169.                 width: 58px;
  1170.                 margin: .1em;
  1171.                 padding: .1em;
  1172.                 font-size: .8em;
  1173.                 font-weight: normal;
  1174.                 text-align: center;
  1175.                 border: 1px solid #bbb;
  1176.             }
  1177.             .rally-time div {
  1178.                 border-bottom: 1px solid #ccc;
  1179.             }
  1180.  
  1181.             /** work products **/
  1182.             .rally-workprod {
  1183.                 margin: .5em;
  1184.                 font-size: .9em;
  1185.                 font-weight: normal;
  1186.             }
  1187.             .rally-workprod .id {
  1188.                 clear: left;
  1189.             }
  1190.             .rally-workprod .name {
  1191.                 font-size: 1.3em;
  1192.                 padding-bottom: .5em;
  1193.             }
  1194.             .rally-workprod .owner {
  1195.                 margin-bottom: .5em;
  1196.             }
  1197.  
  1198.             /** tasks **/
  1199.             .rally-task {
  1200.                 padding: .8em;
  1201.                 font-size: .9em;
  1202.                 text-align: left;
  1203.                 border: 1px solid #999;
  1204.             }
  1205.             .rally-task .name {
  1206.                 font-size: 1.3em;
  1207.                 margin-bottom: .5em;
  1208.             }
  1209.             .rally-task .owner {
  1210.                 margin-bottom: .5em;
  1211.             }
  1212.             .rally-task .image {
  1213.                 float: left;
  1214.             }
  1215.             .rally-task .actions {
  1216.                 float: right;
  1217.                 position: relative;
  1218.                 top: -0.3em;
  1219.                 right: -0.3em;
  1220.             }
  1221.             .rally-task .actions img {
  1222.                 display: block;
  1223.                 margin-bottom: .4em;
  1224.                 cursor:pointer;
  1225.             }
  1226.             .rally-task .rally-time {
  1227.                 width: 45px;
  1228.             }
  1229.             .rally-task-blocked {
  1230.                 border: 1px solid #f00;
  1231.             }
  1232.             .rally-task-blocked img.blocked-icon {
  1233.                 float: right;
  1234.                 position: relative;
  1235.                 bottom: -0.3em;
  1236.                 right: -0.3em;
  1237.             }
  1238.             .rally-task-accepted {
  1239.                 background-color: #ececec;
  1240.                 border: 1px solid #cbcbcb;
  1241.             }
  1242.  
  1243.             /** filter bar **/
  1244.             #filter {
  1245.                 width: 100%;
  1246.                 background-color: #efefdf;
  1247.                 text-align: left;
  1248.                 font-size: .9em;
  1249.                 padding: .2em 0;
  1250.             }
  1251.             #filter span {
  1252.                 padding: 0 1em;
  1253.             }
  1254.             #filter #change_iteration, #filter #filter_user {
  1255.                 border-right: 1px solid #ccc;
  1256.             }
  1257.             #filter #proj_scope_up, #filter #proj_scope_down {
  1258.                 border-left: 1px solid #ccc;
  1259.             }
  1260.             #project {
  1261.                 float: right;
  1262.             }
  1263.  
  1264.             /** status box - displays messages while loading **/
  1265.             #status {
  1266.                 width: 50%;
  1267.                 text-align: right;
  1268.                 float: left;
  1269.                 position: absolute;
  1270.                 top: 0;
  1271.                 left: 0;
  1272.                 font-size: 11px;
  1273.                 font-style: italic;
  1274.                 font-weight: bold;
  1275.             }
  1276.  
  1277.             .show-progress{
  1278.                 cursor:progress;
  1279.             }
  1280.    
  1281.             .rally-workprod .id {
  1282.                 padding: 4px 0;
  1283.             }
  1284.            
  1285.             .rally-task img,
  1286.             .rally-workprod img {
  1287.                 vertical-align: middle;
  1288.             }
  1289.            
  1290.             a.closetxt, a.add-task, a.edit-task, a.short-edit-task, a.delete-story, a.edit-story, a.delete-task {
  1291.                 background: transparent url(https://rally1.rallydev.com/slm/images/sprites.gif) 0 0 no-repeat scroll;
  1292.                 display: inline-block;
  1293.                 height: 16px;
  1294.                 line-height: 16px;
  1295.                 width: 16px;
  1296.                 vertical-align: middle;
  1297.             }
  1298.    
  1299.             a.add-task {
  1300.                 background-position: 0 -1190px;
  1301.             }
  1302.    
  1303.             .short-edit-task {
  1304.                 background-image: url(https://rally1.rallydev.com/slm/images/icon_pencil.gif) !important;
  1305.                 background-position: 0 0;
  1306.             }
  1307.  
  1308.             .rally-workprod {
  1309.                 position: relative;
  1310.             }
  1311.    
  1312.             a.edit-story,
  1313.             a.edit-task {
  1314.                 background-position: 0 -595px;
  1315.             }
  1316.    
  1317.             .rally-workprod {
  1318.                 position: relative;
  1319.             }
  1320.    
  1321.             a.closetxt {
  1322.                 background-position: 0 -289px;
  1323.                 float: right;
  1324.                 margin: 0 0 5px 5px;
  1325.             }
  1326.            
  1327.             a.delete-task,
  1328.             a.delete-story {
  1329.                 background-position: 0 -425px;
  1330.                 position: absolute;
  1331.                 top: 0;
  1332.                 right: 0;
  1333.             }
  1334.            
  1335.             a.delete-task {
  1336.                 top: 4px;
  1337.                 right: 4px;
  1338.             }
  1339.            
  1340.             a.delete-task:hover,
  1341.             a.delete-story:hover {
  1342.                 background-position: 0 -441px;
  1343.             }
  1344.    
  1345.             .show-info {
  1346.                 position: absolute;
  1347.                 top: 0;
  1348.                 /*left: 101%;*/
  1349.                 left: 106px;
  1350.                 width: 450px;
  1351.                 height: auto;
  1352.                 border: 1px solid #CCC;
  1353.                 border-radius: 6px;
  1354.                 -moz-border-radius: 6px;
  1355.                 -webkit-border-radius: 6px;
  1356.                 background-color: #FFF;
  1357.                 padding: 10px;
  1358.                 z-index: 10;
  1359.             }
  1360.            
  1361.             .rally-task .show-info {
  1362.                 left: -5px;
  1363.                 top: -5px;
  1364.                 width: 275px;
  1365.             }
  1366.            
  1367.             .show-info img {
  1368.                 max-width: 98%;
  1369.                 height: auto;
  1370.             }
  1371.            
  1372.             .appHeaderContainer {
  1373.                 margin-top: 0px;
  1374.             }
  1375.            
  1376.             .rally-task {
  1377.                 position: relative;
  1378.             }
  1379.    
  1380.             .rally-task-blocked img.blocked-icon {
  1381.                 float: none;
  1382.                 position: absolute;
  1383.                 bottom: 6px;
  1384.                 right: 6px;
  1385.             }
  1386.            
  1387.             .planestimate {
  1388.                 position: absolute;
  1389.                 top: 0px;
  1390.                 right: 26px;
  1391.                 font-size: 22px;
  1392.                 font-weight: bold;
  1393.             }
  1394.            
  1395.             #filter label {
  1396.                 display: inline-block;
  1397.                 width: auto;
  1398.                 margin: 0 6px 0 4px;
  1399.             }
  1400.            
  1401.             #filter input[type="checkbox"] {
  1402.                 vertical-align: bottom;
  1403.             }  
  1404.    
  1405.             #filter_users {
  1406.                 position: relative;
  1407.             }
  1408.            
  1409.             #filter_users_wrap {
  1410.                 padding: 4px;
  1411.                 margin-top: 4px;
  1412.                 border: 1px solid #DDD;
  1413.                 border-top-width: 0px;
  1414.                 position: absolute;
  1415.                 top: 12px;
  1416.                 left: 0;
  1417.                 width: 200px;
  1418.                 height: auto;
  1419.                 max-height: 230px;
  1420.                 padding: 0px;
  1421.                 background-color: #efefdf;
  1422.                 z-index: 999;
  1423.             }
  1424.            
  1425.             #search {
  1426.             }
  1427.            
  1428.             #search_users {
  1429.                 width: 182px;
  1430.                 margin: 5px;
  1431.                 font-size: 12px;
  1432.                 padding: 2px;
  1433.             }
  1434.            
  1435.             #filter_user_list_wrap {
  1436.                 max-height: 190px;
  1437.                 overflow: auto;
  1438.             }
  1439.    
  1440.             #filter_user_list {
  1441.                 margin: 0;
  1442.                 padding: 0;
  1443.                 list-style: none;
  1444.                 list-style-type: none;
  1445.                 list-style-position: inside;
  1446.             }
  1447.            
  1448.             #filter_user_list li {
  1449.                 list-style: none;
  1450.                 list-style-type: none;
  1451.                 list-style-position: inside;
  1452.                 /*display: inline;*/
  1453.             }
  1454.            
  1455.             #filter_user_list li label {
  1456.                 display: block;
  1457.                 padding: 4px 6px;
  1458.             }
  1459.            
  1460.             #filter_user_list li label:hover,
  1461.             #filter_user_list li label.hover {
  1462.                 background-color: #06F;
  1463.                 color: #FFF;
  1464.             }
  1465.            
  1466.             #filter_user_list li label.checked {
  1467.                 background-color: #95D1EE;
  1468.                 color: #FFF;
  1469.             }
  1470.            
  1471.             #filter_user_list li label.checked:hover,
  1472.             #filter_user_list li label.checked.hover {
  1473.                 background-color: #09F;
  1474.                 color: #FFF;
  1475.             }
  1476.    
  1477.             #filter_user_list li input {
  1478.             }
  1479.  
  1480.         </style>
  1481.     </head>
  1482.     <body>
  1483.         <div id="wrapper">
  1484.             <div id="status"></div>
  1485.             <div id="header">
  1486.                 <div id="title"><h3>Extra Full Task Board</h3></div>
  1487.                 <div id="info"></div>
  1488.                 <div class="clear"></div>
  1489.             </div>
  1490.             <div id="filter">
  1491.                 <div id="project">
  1492.                     <span id="proj_name"></span>
  1493.                     <span id="proj_scope_up">
  1494.                         Scope Up:
  1495.                         <input type="checkbox" id="proj_scope_up_control" />
  1496.                     </span>
  1497.                     <span id="proj_scope_down">
  1498.                         Scope Down:
  1499.                         <input type="checkbox" id="proj_scope_down_control" />
  1500.                     </span>
  1501.                 </div>
  1502.                     <span id="change_iteration">
  1503.                         Mostrar sprint:
  1504.                         <select id="change_iteration_control" size="1"></select>
  1505.                     </span>
  1506.                     <span id="hide_accepted">
  1507.                         <label><input type="checkbox" id="hide_accepted_control" /> Esconder trabalhos finalizados</label>
  1508.                     </span>
  1509.                     <span id="hide_accepted">
  1510.                         <label><input type="checkbox" id="hide_empty_lines" /> Esconder linhas sem tarefas</label>
  1511.                     </span>
  1512.                     <span id="filter_product">
  1513.                         Filtrar por tipo:
  1514.                     </span>
  1515.                     <span id="filter_users">
  1516.                       <a href="javascript://" class="toggle-owner-filter" accesskey="a" onclick="
  1517.                        toggle('filter_users_wrap'); try { Dom.get('search_users').focus(); } catch(e) {}">Filtrar por autor</a>
  1518.                       <div id="filter_users_wrap" style="display: none;">
  1519.                         <div id="search">
  1520.                             <input type="text" id="search_users" placeholder="Filtrar..." />
  1521.                         </div>
  1522.                         <div id="filter_user_list_wrap">
  1523.                           <ul id="filter_user_list"></ul>
  1524.                         </div>
  1525.                       </div>
  1526.                     </span>
  1527.                     <span id="add_history">
  1528.                         <a href="javascript://" onclick="createStory()">Adicionar história</a>
  1529.                     </span>
  1530.             </div>
  1531.  
  1532.             <div id="taskboard"></div>
  1533.         </div>
  1534.     </body>
  1535. </html>
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement