<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<!-- Copyright (c) 2011  Rally Software Development Corp.  All rights reserved -->
<html>
<head>
    <title>Kanban Board</title>
    <meta name="Name" content="App: Kanban Board"/>
    <meta name="Version" content="2013.04.09"/>
    <meta name="Vendor" content="Rally Software"/>
  <script type ="text/javascript" src="/apps/1.32/sdk.js?apiVersion=1.41"></script>
  <script type ="text/javascript">
    /** Copyright (c) 2013 Rally Software Development Corp. All rights reserved **/
    KanbanBoard = function(rallyDataSource, isManualRankWorkspace) {
        var FILTER_FIELD = 'Tags.Name';
    
        var cardboard;
        var that = this;
        var checkBoxes = [];
        var filterByTagsDropdown, showAgeAfter,
                kanbanField, reportKanbanField,
                columns, scheduleStatesMap,
                hideLastColumnIfReleased, showTaskCompletion, showDefectStatus,
                lastState, artifactTypes,
                colorByArtifactType, policyCheckBox, releaseTypeAvailable;
    
        that.shouldShowConfig = function() {
            var query = window.location.href.split('?'), params, configShow;
            if (query.length > 1) {
                params = dojo.queryToObject(query[1]);
            }
    
            return (params && params.isEditable === 'true') ? true : false;
        };
    
        that.showConfig = function(disableCancel) {
            if (!that.shouldShowConfig()) {
                return;
            }
    
            if (!that.configPanel) {
                that.configPanel = new KanbanConfigPanel(rallyDataSource, that._redisplayBoard, releaseTypeAvailable);
                that.configPanel.disableCancel = disableCancel;
                that.configPanel.display();
            }
            else {
                that.configPanel.disableCancel = disableCancel;
                that.configPanel.show();
            }
        };
    
        that._showThroughputReport = function() {
            var config = {reportNumber: 180, reportName: "Throughput Report",
                reportKanbanField: reportKanbanField, artifactTypes: artifactTypes};
            if (filterByTagsDropdown && filterByTagsDropdown.getValue()) {
                config.tags = [filterByTagsDropdown.getValue()];
                config.reportName += " filtered by " + filterByTagsDropdown.getDisplayedValue() + " tag";
            }
            var report = new KanbanReport(config, rallyDataSource);
            report.display("cycleTimeReportDiv");
        };
    
        that._showCycleTimeReport = function() {
            var config = {reportNumber: 170, reportName: "Cycle Time Report",
                reportKanbanField: reportKanbanField, artifactTypes: artifactTypes};
            if (filterByTagsDropdown && filterByTagsDropdown.getValue()) {
                config.tags = [filterByTagsDropdown.getValue()];
                config.reportName += " filtered by " + filterByTagsDropdown.getDisplayedValue() + " tag";
            }
            var report = new KanbanReport(config, rallyDataSource);
            report.display("throughputReportDiv");
        };
    
        that._showNewStoryEditor = function() {
            rally.sdk.util.Navigation.popupCreatePage("hierarchicalrequirement", {iteration: 'u', release: 'u'});
        };
    
        that._showHidePolicies = function(checkbox, value) {
            if (value.checked) {
                that._showPolicies();
            } else {
                that._hidePolicies();
            }
        };
    
        that._showPolicies = function(button, args) {
            function resizeIt(textarea) {
                var str = textarea.value;
                var cols = textarea.cols;
    
                var linecount = 0;
                var newLines = str.split("\n");
    
                rally.forEach(newLines, function(line) {
                    if (Math.ceil(line.length / cols) > 2) {
                        linecount += Math.ceil(line.length / cols); // take into account long lines
                    } else {
                        linecount += 1;
                    }
                });
    
                textarea.rows = linecount;
            }
    
            var columnPolicies = dojo.query('.columnPolicy');
            rally.forEach(columnPolicies, function(div) {
                dojo.removeClass(div, 'hidePolicy');
                dojo.style(div, 'display', 'none');
                var wipeArgs = {
                    node: div,
                    duration: 500
                };
                rally.forEach(dojo.query('textarea', div), function(textarea) {
                    resizeIt(textarea);
                });
                dojo.fx.wipeIn(wipeArgs).play();
            });
        };
    
        that._hidePolicies = function(button, args) {
            var columnPolicies = dojo.query('.columnPolicy');
            rally.forEach(columnPolicies, function(div) {
                dojo.style(div, 'height', '');
                dojo.style(div, 'display', 'block');
                var wipeArgs = {
                    node: div,
                    duration: 500
                };
                dojo.fx.wipeOut(wipeArgs).play();
            });
        };
    
        that._createLayout = function(element) {
            rally.sdk.ui.AppHeader.setHelpTopic('238');
            rally.sdk.ui.AppHeader.showPageTools(true);
    
            var headerDiv = document.createElement("div");
            element.appendChild(headerDiv);
    
            var controlDiv = document.createElement("div");
            dojo.addClass(controlDiv, "controlContainer");
            headerDiv.appendChild(controlDiv);
    
            var dropdownContainerDiv = document.createElement("div");
            dojo.addClass(dropdownContainerDiv, "dropdownContainer");
            controlDiv.appendChild(dropdownContainerDiv);
    
            var checkBoxContainerDiv = document.createElement("div");
            dojo.addClass(checkBoxContainerDiv, "typeFilterContainer");
            controlDiv.appendChild(checkBoxContainerDiv);
    
            var clearDiv = document.createElement("div");
            dojo.addClass(clearDiv, "clearFloats");
            headerDiv.appendChild(clearDiv);
    
            var filterByTagsSpan = document.createElement("span");
            dojo.addClass(filterByTagsSpan, "filterByTagsDropdown");
            dropdownContainerDiv.appendChild(filterByTagsSpan);
    
            function populateFilterByDropdown(results) {
                rally.forEach(results.tags, function(tag) {
                    data.push({label: tag.Name, value: tag.ObjectID});
                });
                filterByTagsDropdown = new rally.sdk.ui.basic.Dropdown({label: "Filter by: ",
                    items : data,
                    rememberSelection:false,
                    defaultValue: ""});
                filterByTagsDropdown.display(filterByTagsSpan);
                filterByTagsDropdown.addEventListener("onChange", that._updateBoardWithFilter);
            }
    
            var data = [
                { label: "", value: ""}
            ];
            rallyDataSource.findAll(
                    {key    : 'tags',
                        order:   'Name',
                        type   : 'Tags',
                        fetch  : 'Name,ObjectID',
                        query  : '(Archived = False)' }, populateFilterByDropdown);
    
            var showSpan = document.createElement("span");
            showSpan.appendChild(document.createTextNode("Show:"));
            checkBoxContainerDiv.appendChild(showSpan);
    
            var userStoriesSpan = document.createElement("span");
            userStoriesSpan.id = "userStories";
            checkBoxContainerDiv.appendChild(userStoriesSpan);
    
            var userStoriesCheckBox = new rally.sdk.ui.basic.CheckBox({
                showLabel: true,
                label: "User Stories",
                labelPosition: "after",
                value: "HierarchicalRequirement",
                checked: true
            });
            checkBoxes.push(userStoriesCheckBox);
            userStoriesCheckBox.display(userStoriesSpan);
    
            var defectsSpan = document.createElement("span");
            defectsSpan.id = "defects";
            checkBoxContainerDiv.appendChild(defectsSpan);
    
            var defectsCheckBox = new rally.sdk.ui.basic.CheckBox({
                showLabel: true,
                label: "Defects",
                labelPosition: "after",
                value: "Defect",
                checked: true
            });
            checkBoxes.push(defectsCheckBox);
            defectsCheckBox.display(defectsSpan);
    
            var policySpan = document.createElement("span");
            policySpan.id = "policy-";
            dojo.addClass(policySpan, "policySpan");
            checkBoxContainerDiv.appendChild(policySpan);
    
            policyCheckBox = new rally.sdk.ui.basic.CheckBox({
                showLabel: true,
                label: "Policies",
                labelPosition: "after",
                value: "policy",
                checked: false,
                rememberChecked: true
            });
            policyCheckBox.display(policySpan);
            policyCheckBox.addEventListener("onChange", that._showHidePolicies);
    
            var kanbanBoard = document.createElement("div");
            kanbanBoard.id = "kanbanBoard";
            dojo.addClass(kanbanBoard, "kanbanBoard");
            element.appendChild(kanbanBoard);
    
            //Wire up events
            dojo.forEach(checkBoxes, function(checkBox) {
                checkBox.addEventListener("onChange", that._redisplayBoard);
            });
        };
    
        that._getColumn = function(field) {
            var showPolicy = policyCheckBox.getChecked();
            var policyDesc = field.policy || "";
            return {wipLimit:field.wip, policy:policyDesc, showPolicy:showPolicy};
        };
    
        that._getAndStorePrefData = function(callback) {
            function populateConfigForm(results) {
                var parsedResults = rally.sdk.data.Preferences.parse(results);
                var kanbanInfo;
                if (parsedResults.project.Kanban) {
                    kanbanInfo = parsedResults.project.Kanban.WipSettings;
                } else if (parsedResults.workspace.Kanban) {
                    kanbanInfo = parsedResults.workspace.Kanban.WipSettings;
                }
    
                if (kanbanInfo) {
                    kanbanField = kanbanInfo.kanbanField;
                    reportKanbanField = kanbanInfo.reportKanbanField;
                    hideLastColumnIfReleased = kanbanInfo.hideLastColumnIfReleased;
                    showTaskCompletion = kanbanInfo.showTaskCompletion;
                    showDefectStatus = kanbanInfo.showDefectStatus;
                    colorByArtifactType = kanbanInfo.colorByArtifactType;
                    if (kanbanInfo.showAge) {
                        showAgeAfter = kanbanInfo.showAgeAfter;
                    } else {
                        showAgeAfter = null;
                    }
                    columns = {};
                    scheduleStatesMap = {};
                    rally.forEach(kanbanInfo.fieldInfos, function(fieldInfo, key) {
                        if (fieldInfo.displayField) {
                            columns[key] = that._getColumn(fieldInfo);
                            scheduleStatesMap[key] = fieldInfo.state;
                            lastState = key;
                        }
                    });
    
                    callback();
                }
                else {
                    //no prefs found fire up dialog
                    that.showConfig(true);
                }
            }
            function determineIfReleaseTypeAvailable(results) {
                var rallyDataSource = new rally.sdk.data.RallyDataSource('__WORKSPACE_OID__',
                        '__PROJECT_OID__',
                        '__PROJECT_SCOPING_UP__',
                        '__PROJECT_SCOPING_DOWN__');
    
                rallyDataSource.find({
                    key: 'enabledTypeDefs',
                    type: 'TypeDefinition',
                    fetch  : 'Name,ElementName',
                    query  : '(Name = "Release")'
                }, function(typeDefs) {
                    releaseTypeAvailable = typeDefs.enabledTypeDefs.length > 0;
                    populateConfigForm(results);
                });
            }
    
            rallyDataSource.preferences.getAppPreferences(determineIfReleaseTypeAvailable);
        };
    
        that._onBeforeItemUpdated = function(c, args) {
            if (args.type === "move") {
    
                args.fieldsToUpdate.Ready = false;
    
                if (scheduleStatesMap[args.fieldsToUpdate[args.attribute]]) {
                    args.fieldsToUpdate.ScheduleState = scheduleStatesMap[args.fieldsToUpdate[args.attribute]];
                }
            }
        };
    
        that._updateBoardWithFilter = function(dropdown) {
            if (cardboard) {
                cardboard.filterBoard({field: FILTER_FIELD, value: dropdown.getDisplayedValue()});
            } else {
                that._redisplayBoard();
            }
        };
    
        that._redisplayBoard = function() {
            function displayBoard() {
                artifactTypes = [];
                var cardboardConfig = {
                    types           : [],
                    attribute       : kanbanField,
                    sortAscending   : true,
                    order           : "Rank",
                    isManualRankWorkspace  : isManualRankWorkspace,
                    cardRenderer    : KanbanCardRenderer,
                    cardOptions     : {
                        showTaskCompletion: showTaskCompletion,
                        showDefectStatus: showDefectStatus,
                        showAgeAfter: showAgeAfter,
                        colorByArtifactType: colorByArtifactType
                    },
                    columnRenderer  : KanbanColumnRenderer,
                    columns         : columns,
                    fetch           : "Name,FormattedID,Owner,ObjectID,Rank,Ready,Blocked,LastUpdateDate,Tags,State"
                };
    
    
                if (showTaskCompletion) {
                    cardboardConfig.fetch += ",Tasks";
                }
    
                if (showDefectStatus) {
                    cardboardConfig.fetch += ",Defects";
                }
    
                if (hideLastColumnIfReleased && releaseTypeAvailable) {
                    cardboardConfig.query = new rally.sdk.util.Query("Release = null").or(kanbanField + ' != "' + lastState + '"');
                }
    
                if (filterByTagsDropdown && filterByTagsDropdown.getDisplayedValue()) {
                    cardboardConfig.cardOptions.filterBy = {field: FILTER_FIELD, value: filterByTagsDropdown.getDisplayedValue()};
                }
                //Build types based on checkbox selections
    
                dojo.forEach(checkBoxes, function(checkBox) {
                    if (checkBox.getChecked()) {
                        cardboardConfig.types.push(checkBox.getValue());
                    }
                });
    
                if (cardboard) {
                    cardboard.destroy();
                }
                artifactTypes = cardboardConfig.types;
    
                cardboard = new rally.sdk.ui.CardBoard(cardboardConfig, rallyDataSource);
    
                cardboard.addEventListener("preUpdate", that._onBeforeItemUpdated);
                cardboard.display("kanbanBoard");
            }
            that._getAndStorePrefData(displayBoard);
        };
    
        that._addAppHeaderMenuItems = function() {
            if (that.shouldShowConfig()) {
                rally.sdk.ui.AppHeader.addPageTool({
                    key:"showConfig",
                    label: "Settings",
                    onClick: function() {that.showConfig();}
                });
            }
    
            rally.sdk.ui.AppHeader.addPageTool({
                key: "showCycleTimeReport",
                label: "Show Cycle Time Report",
                onClick: that._showCycleTimeReport
            });
    
            rally.sdk.ui.AppHeader.addPageTool({
                key: "showThroughputReport",
                label: "Show Throughput Report",
                onClick: that._showThroughputReport
            });
    
            if (that._inIframeInsideRally()) {
                rally.sdk.util.Login.isNotViewerOfProject(rallyDataSource.getProject(), function() {
                    rally.sdk.ui.AppHeader.addPageTool({
                        key: "newStory",
                        label: "Add New Story",
                        onClick: that._showNewStoryEditor
                    });
                    rally.sdk.ui.AppHeader.addPageTool(rally.sdk.ui.PageTools.BuiltIn.OpenInNewWindow);
                }, function(){}, function(){});
            }
        };
    
        that.display = function(element) {
            this._addAppHeaderMenuItems();
    
            //Build app layout
            this._createLayout(element);
    
            //Redisplay the board
            this._redisplayBoard();
        };
    
        that._inIframeInsideRally = function() {
            return rally.sdk.util.Context.isInsideRally() && window.top !== window.self;
        };
    };
    /** Copyright (c) 2013 Rally Software Development Corp. All rights reserved **/
    KanbanCardRenderer = function(column, item, options) {
        rally.sdk.ui.cardboard.BasicCardRenderer.call(this, column, item, options);
        var that = this;
        var cardContent,card,cardMenu;
        this.getItem = function() {
            return dojo.clone(item);
        };
        this.selectCard = function() {
            var expand = dojo.animateProperty({
                node : cardMenu,
                properties : {
                    height : {end : dojo.style(cardMenu, "lineHeight")},
                    rate: 7
                }
            });
            expand.play();
            dojo.addClass(cardMenu, "expanded");
            KanbanCardRenderer.SelectedCard = that;
        };
    
        this.unselectCard = function() {
            var shrink = dojo.animateProperty({
                node : cardMenu,
                properties : {
                    height : {end : 0},
                    rate: 7
                }
            });
            shrink.play();
            dojo.removeClass(cardMenu, "expanded");
            KanbanCardRenderer.SelectedCard = null;
        };
    
        this._contentClicked = function() {
            that.fireEvent(that.getValidEvents().onContentClick, {
                item: item
            });
        };
    
    
        this._headerMouseOver = function(event) {
            var headerElement = event.srcElement;
            var timer;
            var delay = 3000;
            var tooltip;
            var link;
    
            function removeTooltip() {
                if (tooltip && tooltip.hide) {
                    dojo.disconnect(link);
                    tooltip.hide();
                }
                clearTimeout(timer);
            }
    
    
            function showTooltip() {
                var tooltipDiv = document.createElement("div");
                headerElement.appendChild(tooltipDiv);
                // attach to the right of the formatted id
                tooltip = rally.sdk.ui.basic.Tooltip.show(headerElement.parentNode.firstChild, "Drag to move story, click to show toolbar", "before", 3000);
    
            }
    
            link = dojo.connect(headerElement, "onmouseout", removeTooltip);
            timer = setTimeout(showTooltip, delay);
        };
    
        this._headerClicked = function() {
            var previousSelection = KanbanCardRenderer.SelectedCard;
            if (KanbanCardRenderer.SelectedCard) {
                KanbanCardRenderer.SelectedCard.unselectCard();
            }
    
            if (previousSelection !== that) {
                that.selectCard();
            }
    
            that.fireEvent(that.getValidEvents().onHeaderClick, {
                item: item
            });
        };
    
        this.getValidEvents = function() {
            return {
                onContentClick:"onContentClick",
                onHeaderClick: "onHeaderClick",
                onItemUpdate: "onItemUpdate"
            };
        };
    
        this.applyFilter = function() {
    
            function applyFilterClass() {
                dojo.removeClass(card, 'filterByFade');
                dojo.addClass(card, 'filterByShow');
            }
    
            if (options.filterBy.value && options.filterBy.value.length > 0) { //if the filter value is not empty string
                dojo.removeClass(card, 'filterByShow');
                dojo.addClass(card, 'filterByFade');
    
                var filterFieldSplit = options.filterBy.field.toString().split('.');
                var filterField = filterFieldSplit[0];
                var filterAttr = filterFieldSplit[filterFieldSplit.length - 1];
    
                if (item[filterField] === options.filterBy.value) { // field is string or number
                    applyFilterClass();
                } else if (filterField !== filterAttr) {
                    if (item[filterField] && item[filterField][filterAttr] === options.filterBy.value) { // field is an object
                        applyFilterClass();
                    } else { // field is a collection
                        rally.forEach(item[filterField], function(field) {
                            if (field[filterAttr] === options.filterBy.value) {
                                applyFilterClass();
                            }
                        });
                    }
                }
            } else { //if the filter value is empty string, show card
                applyFilterClass();
            }
        };
    
        this.applyArtifactTypeStyle = function() {
            dojo.addClass(card, rally.sdk.util.Ref.getTypeFromRef(item._ref));
        };
    
        this._getColumnAgeDays = function() {
            var daysOld = 0;
    
            function getLastStateChange() {
                var revisions = item.RevisionHistory.Revisions;
                var lastStateChangeDate = "";
    
                rally.forEach(revisions, function(revision) {
                    if (lastStateChangeDate.length === 0) {
                        var attr = options.attribute.toUpperCase();
    
                        if (revision.Description.indexOf(attr + " changed from") !== -1) {
                            lastStateChangeDate = revision.CreationDate;
                        }
                        if (revision.Description.indexOf(attr + " added") !== -1) {
                            lastStateChangeDate = revision.CreationDate;
                        }
                    }
                });
                return lastStateChangeDate || item.CreationDate;
            }
    
            var lastStateDate = getLastStateChange();
    
            var lastUpdateDate = rally.sdk.util.DateTime.fromIsoString(lastStateDate);
            return rally.sdk.util.DateTime.getDifference(new Date(), lastUpdateDate, "day");
        };
    
        this.updateCard = function(node) {
            card = node.firstChild;
            that._populateCard();
        };
    
        this._populateCard = function() {
            var idDiv = dojo.query('.leftCardHeader', card)[0];
            var link = new rally.sdk.ui.basic.Link({item: item});
            dojo.empty(idDiv);
            link.display(idDiv);
    
            var ownerImg = dojo.query('.cardOwner', card)[0];
            var ownerName = dojo.query('.cardOwnerName', card)[0];
            dojo.empty(ownerName);
    
            if (item.Owner) {
                ownerImg.setAttribute("src", rally.sdk.util.Ref.getUserImage(item.Owner._ref));
                ownerName.appendChild(document.createTextNode(item.Owner._refObjectName));
            }
            else {
                ownerImg.setAttribute("src", rally.sdk.loader.launcherPath + "/../images/profile-mark-18.png");
                ownerName.appendChild(document.createTextNode("No Owner"));
            }
    
            var cardName = dojo.query('.cardName', card)[0];
            cardName.innerHTML = item.Name;
    
            var tasksDiv = dojo.query('.tasks', card);
    
            if (tasksDiv.length > 0) {
                tasksDiv = tasksDiv[0];
                dojo.empty(tasksDiv);
                var completedTasks = 0;
                rally.forEach(item.Tasks, function(task) {
                    if (task.State === "Completed") {
                        completedTasks++;
                    }
                });
    
                if (completedTasks === 0) {
                    dojo.addClass(tasksDiv, "none");
                } else if (completedTasks < item.Tasks.length) {
                    dojo.addClass(tasksDiv, "some");
                } else {
                    dojo.addClass(tasksDiv, "all");
                }
    
                var tasksLink = new rally.sdk.ui.basic.Link({
                    item: item,
                    text: completedTasks + " of " +
                            item.Tasks.length + " tasks completed",
                    subPage: "tasks"
                });
                tasksLink.display(tasksDiv);
            }
    
            var defectsDiv = dojo.query('.defects', card);
    
            if (defectsDiv.length > 0) {
                defectsDiv = defectsDiv[0];
                dojo.empty(defectsDiv);
                var closedDefects = 0;
                rally.forEach(item.Defects, function(defect) {
                    if (defect.State === "Closed") {
                        closedDefects++;
                    }
                });
    
                if (closedDefects === 0) {
                    dojo.addClass(defectsDiv, "none");
                } else if (closedDefects < item.Defects.length) {
                    dojo.addClass(defectsDiv, "some");
                } else {
                    dojo.addClass(defectsDiv, "all");
                }
    
                var defectsLink = new rally.sdk.ui.basic.Link({
                    item: item,
                    text: closedDefects + " of " +
                            item.Defects.length + " defects closed",
                    subPage: "defects"
                });
                defectsLink.display(defectsDiv);
            }
    
            if (options && options.filterBy) {
                that.applyFilter();
            }
    
            if (options && options.colorByArtifactType) {
                that.applyArtifactTypeStyle();
            }
    
            if (options && options.showAgeAfter && options.attribute && item.RevisionHistory) {
                var ageDiv = dojo.query('.age', card);
    
                var daysOld = that._getColumnAgeDays();
    
                if (ageDiv.length > 0 && daysOld > options.showAgeAfter) {
                    ageDiv = ageDiv[0];
                    dojo.empty(ageDiv);
                    dojo.addClass(ageDiv, 'agedCard');
    
                    var ageDisplay = (daysOld === 1) ? daysOld + " day" : daysOld + " days";
                    var ageTextNode = document.createTextNode(ageDisplay + " in this column");
                    ageDiv.appendChild(ageTextNode);
                }
            }
        };
    
        this.renderCard = function() {
            card = document.createElement("div");
            dojo.addClass(card, "card");
    
            var header = document.createElement("div");
            dojo.addClass(header, "cardHeader");
            dojo.addClass(header, "dojoDndHandle");
            card.appendChild(header);
            dojo.connect(header, "onclick", that._headerClicked);
            dojo.connect(header, "onmouseover", that._headerMouseOver);
    
            var idDiv = document.createElement("div");
            dojo.addClass(idDiv, "leftCardHeader");
            header.appendChild(idDiv);
            var ownerImg = document.createElement("img");
            dojo.addClass(ownerImg, "cardOwner");
    
            var ownerName = document.createElement("div");
            dojo.addClass(ownerName, "cardOwnerName");
    
            header.appendChild(ownerImg);
            header.appendChild(ownerName);
    
            cardContent = document.createElement("div");
            dojo.addClass(cardContent, "cardContent");
            card.appendChild(cardContent);
    
            var cardName = document.createElement("div");
            dojo.addClass(cardName, "cardName");
            cardContent.appendChild(cardName);
    
            var statusDiv = document.createElement("div");
            dojo.addClass(statusDiv, 'status');
            if (options && options.showTaskCompletion && item.Tasks && item.Tasks.length) {
                var tasksDiv = document.createElement("div");
                dojo.addClass(tasksDiv, "tasks");
                statusDiv.appendChild(tasksDiv);
            }
    
            if (options && options.showDefectStatus && item.Defects && item.Defects.length) {
                var defectsDiv = document.createElement("div");
                dojo.addClass(defectsDiv, "defects");
                statusDiv.appendChild(defectsDiv);
            }
    
            if (statusDiv.childNodes.length) {
                cardContent.appendChild(statusDiv);
            }
    
            if (options && options.showAgeAfter) {
                var ageDiv = document.createElement('div');
                dojo.addClass(ageDiv, "age");
                cardContent.appendChild(ageDiv);
            }
    
            that._populateCard(card);
    
            cardMenu = document.createElement("div");
            dojo.addClass(cardMenu, "cardMenu");
            card.appendChild(cardMenu);
            that._addReadyDivToMenu();
            that._addBlockedDivToMenu();
            that._addEditLinkToMenu();
            dojo.connect(cardContent, "onclick", that._contentClicked);
    
            return card;
        };
    
        this.renderDndCard = function() {
            var avatarCard = that.renderCard();
            dojo.addClass(avatarCard, "avatar");
            return avatarCard;
        };
    
        this.updateDisplay = function() {
            if (item.Ready) {
                dojo.addClass(card, "ready");
            }
            else {
                dojo.removeClass(card, "ready");
            }
            if (item.Blocked) {
                dojo.addClass(card, "blocked");
            }
            else {
                dojo.removeClass(card, "blocked");
            }
        };
    
        this._addReadyDivToMenu = function() {
            if (item.hasOwnProperty("Ready")) {
                var readyIndicator = document.createElement("div");
                dojo.addClass(readyIndicator, "readyIndicator");
                readyIndicator.onclick = function(e) {
                    item.Ready = !item.Ready;
                    that.updateDisplay();
                    that.updateItem({_ref:item._ref,Ready:item.Ready}, function() {
                    });
                };
                cardMenu.appendChild(readyIndicator);
            }
            that.updateDisplay();
        };
        this._addBlockedDivToMenu = function() {
            var blockedIndicator = document.createElement("div");
            dojo.addClass(blockedIndicator, "blockedIndicator");
            blockedIndicator.onclick = function(e) {
                item.Blocked = !item.Blocked;
                that.updateDisplay();
                that.updateItem({_ref:item._ref,Blocked:item.Blocked}, function() {
                });
            };
            cardMenu.appendChild(blockedIndicator);
            that.updateDisplay();
        };
    
        this._addEditLinkToMenu = function() {
            var editLinkContainer = document.createElement("div");
            dojo.addClass(editLinkContainer, "editLinkContainer");
            var editLink = new rally.sdk.ui.basic.EditLink({item: item});
            editLink.display(editLinkContainer);
            cardMenu.appendChild(editLinkContainer);
        };
    };
    /** Copyright (c) 2013 Rally Software Development Corp. All rights reserved **/
    var KanbanColumnRenderer = function(board, value, options) {
        rally.sdk.ui.cardboard.BasicColumnRenderer.call(this, board, value, options);
        var that = this;
        var dndContainer;
        var cards = 0;
        var columnDiv;
        var capacity = Infinity;
        var resourcesDisplay;
        var saveLinkHandler, cancelLinkHandler;
        var columnHeader, columnPolicy, policyHeaderDiv, policySubheaderDiv, policyTextboxDiv, policyTextArea, policyTextAreaExtension, editPolicyDiv;
        var editPolicyElements;
        var editPolicyHandlers = [];
    
        var kanbanPolicy = new KanbanPolicy(board.getRallyDataSource(), function(updatedVal) {
            if (updatedVal !== null) {
                options.policy = updatedVal;
            }
            that.updateColumn();
        });
    
        this.updateColumn = function() {
            if (saveLinkHandler) {
                dojo.disconnect(saveLinkHandler);
            }
            if (cancelLinkHandler) {
                dojo.disconnect(cancelLinkHandler);
            }
            that._populateColumn();
        };
    
        this.render = function() {
    
            columnDiv = document.createElement('div');
            dojo.addClass(columnDiv, 'column');
    
            columnHeader = document.createElement('div');
            dojo.addClass(columnHeader, 'columnHeader');
    
            if (options && options.wipLimit) {
                capacity = options.wipLimit;
                resourcesDisplay = document.createElement('div');
                dojo.addClass(resourcesDisplay, 'capacityDisplay');
                columnHeader.appendChild(resourcesDisplay);
            }
            columnDiv.appendChild(columnHeader);
    
            columnPolicy = document.createElement('div');
            dojo.addClass(columnPolicy, 'columnPolicy hidePolicy');
            columnDiv.appendChild(columnPolicy);
    
            policyHeaderDiv = document.createElement('div');
            dojo.addClass(policyHeaderDiv, 'policyHeaderDiv');
            columnPolicy.appendChild(policyHeaderDiv);
    
            policySubheaderDiv = document.createElement('div');
            dojo.addClass(policySubheaderDiv, 'policySubheaderDiv');
            columnPolicy.appendChild(policySubheaderDiv);
    
            policyTextboxDiv = document.createElement('div');
            dojo.addClass(policyTextboxDiv, 'policyTextboxDiv');
            policyTextArea = dojo.create('textarea');
            policyTextboxDiv.appendChild(policyTextArea);
            policyTextAreaExtension = new dijit.form.Textarea({ maxLength: 256 }, policyTextArea);
            columnPolicy.appendChild(policyTextboxDiv);
    
            editPolicyDiv = document.createElement('div');
            dojo.addClass(editPolicyDiv, 'editPolicyDiv');
            columnPolicy.appendChild(editPolicyDiv);
    
            dndContainer = document.createElement('div');
            dojo.addClass(dndContainer, 'columnContent');
            columnDiv.appendChild(dndContainer);
            this._populateColumn();
    
            return columnDiv;
        };
    
        this._populateColumn = function() {
            columnHeader.innerHTML = options.displayValue || value || "-- No Entry --";
    
            if (options && options.wipLimit) {
                setCapacityText();
                columnHeader.appendChild(resourcesDisplay);
            }
    
            policyHeaderDiv.innerHTML = 'Exit Policy';
            policySubheaderDiv.innerHTML = 'What needs to be done before an item is ready to leave this column?';
    
            var textArea = dojo.query('.policyTextboxDiv textarea', columnDiv)[0];
            dojo.style(textArea, 'border', '1px solid transparent');
            dojo.attr(textArea, 'readOnly', true);
            if (options && options.hasOwnProperty('policy')) {
                if (options.policy === "") {
                    policyTextAreaExtension.setValue(options.policy + '\n');
                } else {
                    policyTextAreaExtension.setValue(options.policy);
                }
            }
    
            if (options && options.showPolicy) {
                dojo.removeClass(columnPolicy, 'hidePolicy');
            }
    
            editPolicyElements = [columnPolicy, policyHeaderDiv, policySubheaderDiv, textArea];
            rally.forEach(editPolicyElements, dojo.hitch(this, function(element) {
                editPolicyHandlers.push(dojo.connect(element, 'onmouseenter', this, this._editCursor));
            }));
        };
    
        this._editCursor = function(evt) {
            var srcElement = evt.srcElement || evt.target;
    
            if (dojo.isIE <= 8) {
                dojo.style(srcElement, 'cursor', 'pointer');
            } else {
                dojo.style(srcElement, 'cursor', 'url("/apps/images/editIcon.cur"), url("/apps/images/editIcon.png"), pointer');
            }
            editPolicyHandlers.push(dojo.connect(srcElement, 'onclick', this, this._editPolicy));
        };
    
        this._savePolicy = function() {
            dojo.empty(editPolicyDiv);
            dojo.style(policySubheaderDiv, 'display', 'none');
            dojo.removeClass(editPolicyDiv, 'editPolicyDivExtraPadding');
            kanbanPolicy.savePolicy(value, policyTextAreaExtension.getValue());
        };
    
        this._cancelPolicy = function(evt) {
            evt.preventDefault();
            dojo.empty(editPolicyDiv);
            dojo.style(policySubheaderDiv, 'display', 'none');
            dojo.removeClass(editPolicyDiv, 'editPolicyDivExtraPadding');
            that.updateColumn();
        };
    
        this._formatTextArea = function() {
            var textarea = dojo.query('.policyTextboxDiv textarea', columnDiv)[0];
    
            rally.forEach(editPolicyHandlers, function(handler) {
                dojo.disconnect(handler);
            });
            editPolicyHandlers = [];
    
            rally.forEach(editPolicyElements, function(element) {
                element.style.cursor = 'default';
            });
            editPolicyElements = null;
    
            dojo.style(textarea, 'border', '1px solid #c6c6c6');
            dojo.attr(textarea, 'readOnly', false);
    
            dojo.addClass(editPolicyDiv, 'editPolicyDivExtraPadding');
            policyTextAreaExtension.focus();
        };
    
        this._showPolicyInstructions = function() {
            dojo.style(policySubheaderDiv, 'display', 'block');
        };
    
        this._createSaveButton = function(element) {
            var saveButtonSpan = document.createElement('span');
            dojo.addClass(saveButtonSpan, 'saveButtonSpan');
            element.appendChild(saveButtonSpan);
    
            var saveButton = new rally.sdk.ui.basic.Button({text: 'Save'});
            saveButton.addEventListener('onClick', dojo.hitch(this, this._savePolicy));
            saveButton.display(saveButtonSpan);
        };
    
        this._createCancelLink = function(element) {
            var cancelLink = dojo.create('a', {href: '#', innerHTML: 'Cancel'}, element);
            cancelLinkHandler = dojo.connect(cancelLink, 'onclick', this, this._cancelPolicy);
        };
    
        this._editPolicy = function() {
            this._formatTextArea();
            this._showPolicyInstructions();
    
            dojo.empty(editPolicyDiv);
            this._createSaveButton(editPolicyDiv);
            this._createCancelLink(editPolicyDiv);
        };
    
        function setCapacityText() {
            if (options && options.wipLimit) {
                dojo.empty(resourcesDisplay);
                resourcesDisplay.innerHTML = getCapacityText(cards, capacity);
                if (capacity >= cards) {
                    dojo.removeClass(columnDiv, 'overCapacity');
                } else {
                    dojo.addClass(columnDiv, 'overCapacity');
                }
            }
        }
    
        function getCapacityText(cards, wip) {
            if (wip === Infinity || wip === "Infinity") {
                return " (" + cards + '/<span class="infinitySymbol">&#8734;</span>)';
            }
            else {
                return " (" + cards + "/" + wip + ")";
            }
        }
    
        this.addNoDropClass = function(nodes) {
            if (capacity === Infinity) {
                return false;
            }
            return capacity <= cards;
        };
    
        this.cardRemoved = function(card) {
            cards--;
            setCapacityText();
        };
    
        this.cardAdded = function(card) {
            cards++;
            setCapacityText();
        };
    
        this.getDndContainer = function() {
            return dndContainer;
        };
    
        this.getColumnNode = function() {
            return columnDiv;
        };
    
        this.clear = function() {
            dojo.empty(that.getDndContainer());
            cards = 0;
            setCapacityText();
        };
    };
    /** Copyright (c) 2013 Rally Software Development Corp. All rights reserved **/
    KanbanConfigPanel = function(rallyDataSource, onConfigHide, hasReleaseType) {
        rally.sdk.ComponentBase.call(this);
    
        this.getValidEvents = function() {
            return {onHide:"onHide"};
        };
    
        var that = this;
        var projectPrefRef, workspacePrefRef, currentPrefs = {};
        var stateDropdown, dialog;
        var allAttributes = {}, scheduleStateValues = {};
        var controls = [], rows = [], accessors = [];
        var releaseTypeAvailable = hasReleaseType;
        var hideCardsCheckBox, showTasksCheckBox, showDefectsCheckBox, showAgeCheckBox, showAgeTextBox, colorByArtifactTypeCheckBox;
        var dialogId = new Date().toString();
    
        var notMappedKey = "NotMapped";
        var notMappedVal = "-- Not Mapped --";
    
        //show list of kanbanable states
        that.display = function() {
            that._displayKanbanFieldDropdown(this._displayColumnPreferences);
        };
    
        that.show = function() {
            if (dialog) {
                that._disableCancelButton();
                dialog.show();
                that._alignSettingsDialog();
            }
        };
    
        that.hide = function() {
            if (dialog) {
                dialog.hide();
            }
        };
    
        that.destroy = function() {
            if (dialog) {
                dialog.destroyRecursive();
            }
        };
    
        that.displayDialog = function() {
            if (dialog) {
                return;
            }
            function createDialog(names) {
                var content = dojo.byId("configPanel");
                var title;
                dojo.byId("configPanel").style.visibility = "visible";
                if (names.projectName) {
                    title = "Configure Settings for " + names.projectName + " Project";
                }
                else if (names.workspaceName) {
                    title = "Configure Settings for Current Project";
                }
                else {
                    title = "Configure Default Settings";
                }
                dialog = new rally.sdk.ui.basic.Dialog({
                    id : dialogId,
                    title: title,
                    width: 400,
                    height: 350,
                    draggable:false,
                    closable:false,
                    content: content
                });
    
                dialog.addEventListener("onHide", function() {
                    that.fireEvent(that.getValidEvents().onHide, {});
                });
                dialog.display();
                that._alignSettingsDialog();
                that.displaySaveCancelFeatures();
                that._displayHelpDialog();
                that._disableCancelButton();
            }
    
            that._retrievePreferences(createDialog);
        };
    
        that.displaySaveCancelFeatures = function() {
            var buttonContainer = dojo.query(".buttonContainer")[0];
            dojo.addClass(buttonContainer, "saveCancelButtons");
    
            var saveButton = new rally.sdk.ui.basic.Button({text:"Save", value:"Save"});
            saveButton.display(buttonContainer, function() {
                dialog.hide();
                that._storeValues(that._saveComplete);
            });
    
            var cancelLink = "<a href=''>Cancel</a>";
            that.cancelButton = dojo.place(cancelLink, buttonContainer);
            dojo.connect(that.cancelButton, "onclick", function(event) {
                dialog.hide();
                that._setValues();
                dojo.stopEvent(event);
            });
        };
    
        that._alignSettingsDialog = function() {
            var dialogContainer = dojo.query(".dijitDialog");
            if (dialogContainer.length > 0) {
                dojo.style(dialogContainer[0], 'top', '10px');
            }
            //Hack to force dojo widget to maintain position relative to container
            var dialog = dijit.byId(dialogId);
            dialog._relativePosition = dojo.position(dialog.domNode);
        };
    
        that._alterWipTextBox = function(textbox, args) {
            if (!args.value && args.value !== 0) {
                textbox.setValue("");
            }
            else if (!isNaN(args.value)) {
                textbox.setValue(Math.max(args.value, 0).toString());
            }
            else {
                textbox.setValue("");
            }
        };
    
        that._alterAgeTextBox = function(textbox, args) {
            if (!args.value || isNaN(args.value) || args.value < 1) {
                textbox.setValue(3);
            }
        };
    
        that._addControlToRow = function(row, divId, control, containerCss) {
            var td = document.createElement("td");
            var div = document.createElement("div");
            if (containerCss) {
                dojo.addClass(div, containerCss);
            }
            td.appendChild(div);
            div.id = divId;
            control.display(div);
            controls.push(control);
            row.appendChild(td);
        };
    
        that._createAllowedValueTableRow = function(allowedValue, fieldName) {
            var stringValue = allowedValue.StringValue;
            var row = document.createElement("tr");
    
    
            var columnNameContainer = document.createElement("td");
            columnNameContainer.innerHTML = stringValue || "-- No Entry --";
            row.appendChild(columnNameContainer);
    
    
            var checkbox = new rally.sdk.ui.basic.Checkbox({
                rememberChecked:false,
                checked:true
            });
    
            that._addControlToRow(row, stringValue + "-checkBox-" + rows.length, checkbox, "configCheckBox");
    
            var textBox = new rally.sdk.ui.basic.TextBox(
                    {   value:'',
                        rememberValue:false
                    });
    
            textBox.addEventListener("onChange", that._alterWipTextBox);
            that._addControlToRow(row, stringValue + "-textBox-" + rows.length, textBox, "wipTextBoxContainer");
    
            var stateDropdownConfig = {
                defaultValue: notMappedKey,
                rememberSelection:false,
                width:115
            };
            var mappingDropdown = new rally.sdk.ui.basic.Dropdown(stateDropdownConfig);
    
            if (fieldName === "Schedule State") {
                var notMappedScheduleStateVals = {};
                notMappedScheduleStateVals[notMappedKey] = notMappedVal;
                mappingDropdown.setItems(notMappedScheduleStateVals);
            }
            else {
                mappingDropdown.setItems(scheduleStateValues);
            }
    
            that._addControlToRow(row, stringValue + "-dropdown-" + rows.length, mappingDropdown);
    
            var accessor = {
                field:stringValue,
                get: function() {
                    var result = {};
                    var wipText = textBox.getValue();
                    result.wip = wipText.length === 0 ? Infinity : wipText;
                    //this turns undefined into false.
                    result.displayField = !!checkbox.getChecked();
                    var scheduleStateValue = mappingDropdown.getValue();
                    if (scheduleStateValue !== notMappedKey) {
                        result.state = scheduleStateValue;
                    }
                    return result;
                },
                set:function(object) {
                    if (object.state) {
                        mappingDropdown.setValue(object.state);
                    } else {
                        mappingDropdown.setValue(notMappedVal);
                    }
    
                    var wipText = (object.wip === Infinity || object.wip === "Infinity"  ) ? "" : object.wip;
                    textBox.setValue(wipText);
                    checkbox.setChecked(object.displayField);
                }
            };
            accessors.push(accessor);
            return row;
        };
    
        that._destroyPreviousControls = function() {
            dojo.forEach(controls, function(control) {
                if (control && control.destroy) {
                    control.destroy();
                }
            });
            controls = [];
            dojo.forEach(rows, function(row) {
                if (row) {
                    dojo.destroy(row);
                }
            });
            rows = [];
        };
    
        that._checkReadyField = function(readyFieldExist) {
            if (!readyFieldExist) {
                var msgText = "To enable the 'ready to pull' feature, create a custom field named 'Ready' on the User Story and Defect work products.";
                var messageElement = document.createElement("div");
                messageElement.id = "appSettingsDialogMsg";
    
                var messageTextElement = document.createElement("span");
                messageElement.appendChild(messageTextElement);
    
                var textNode = document.createTextNode(msgText);
                if (messageTextElement.firstChild) {
                    messageTextElement.replaceChild(textNode, messageTextElement.firstChild);
                } else {
                    messageTextElement.appendChild(textNode);
                }
    
                dojo.addClass(messageElement, "configMessage");
                dojo.byId("msgContainer").appendChild(messageElement);
            }
        };
    
        that._displayTogglePreferences = function() {
            if (releaseTypeAvailable) {
                hideCardsCheckBox = new rally.sdk.ui.basic.CheckBox({
                    label:"Hide cards in last visible column if assigned to a release",
                    showLabel:true,
                    labelPosition:"after",
                    checked: true,
                    rememberChecked: true
                });
                hideCardsCheckBox.display("hideCardsCheckBox");
            }
    
            showTasksCheckBox = new rally.sdk.ui.basic.CheckBox({
                label:"Show task status for cards with tasks",
                showLabel:true,
                labelPosition:"after",
                checked: true,
                rememberChecked: true
            });
            showTasksCheckBox.display("showTasksCheckBox");
    
            showDefectsCheckBox = new rally.sdk.ui.basic.CheckBox({
                label:"Show defect status for cards with defects",
                showLabel:true,
                labelPosition:"after",
                checked: true,
                rememberChecked: true
            });
            showDefectsCheckBox.display("showDefectsCheckBox");
    
            showAgeCheckBox = new rally.sdk.ui.basic.CheckBox({
                label:"Show age for card after",
                showLabel:true,
                labelPosition:"after",
                checked: false,
                rememberChecked: true
            });
            showAgeCheckBox.display("showAgeCheckBox");
    
            showAgeTextBox = new rally.sdk.ui.basic.TextBox({
                label: " day(s) in column",
                showLabel:true,
                labelPosition:"after",
                width: 30,
                value: 3,
                rememberValue: true
            });
            showAgeTextBox.addEventListener("onChange", that._alterAgeTextBox);
            showAgeTextBox.display("showAgeTextBox");
    
            colorByArtifactTypeCheckBox = new rally.sdk.ui.basic.CheckBox({
                label:"Color card headers by work item type",
                showLabel:true,
                labelPosition:"after",
                checked: false,
                rememberChecked: true
            });
            colorByArtifactTypeCheckBox.display("colorByArtifactTypeCheckBox");
        };
    
        that._displayColumnPreferences = function(dropdown, args) {
            that._destroyPreviousControls();
            accessors = [];
            var attributeObject = allAttributes[args.value];
            var tableBody = dojo.byId("configTableBody");
    
            dojo.forEach(attributeObject.AllowedValues, function(allowed) {
                var row = that._createAllowedValueTableRow(allowed, attributeObject.Name);
                rows.push(row);
                tableBody.appendChild(row);
            });
            that.displayDialog();
        };
    
        that._translateAllowedValuesToDropdownItems = function(values) {
            var items = {};
            items[notMappedKey] = notMappedVal;
            dojo.forEach(values, function(value) {
                items[value.StringValue] = value.StringValue;
            });
            return items;
        };
    
        that._displayHelpDialog = function() {
            var configHelpElement = dojo.byId("configHelp");
            dojo.addClass(configHelpElement, "configHelp");
            var help = new rally.sdk.ui.Help();
            help.setHelpTopic("238");
            help.display(configHelpElement);
        };
    
        that._displayKanbanFieldDropdown = function(callback) {
            allAttributes = {};
    
            function gatherAttributes(results) {
    
                try {
                    var usableFields = {};
                    var firstAttr,scheduleStateAttr,attributes = [];
                    var readyFieldExist = false;
    
                    dojo.forEach(results.types, function(type) {
                        attributes = attributes.concat(type.Attributes);
                    });
                    attributes.sort(function(a, b) {
                        var nameA = a.Name.toLowerCase();
                        var nameB = b.Name.toLowerCase();
                        if (nameA < nameB) {
                            return -1;
                        }
                        if (nameA > nameB) {
                            return 1;
                        }
                        return 0;
                    });
    
                    dojo.forEach(attributes, function(attr) {
                        if (attr.Constrained && attr.AttributeType !== "OBJECT" && attr.AttributeType !== "COLLECTION") {
                            firstAttr = firstAttr || attr;
                            usableFields[attr.ElementName] = attr.Name;
                            allAttributes[attr.ElementName] = attr;
                        }
                        if (attr.Name == "Ready") {
                            readyFieldExist = true;
                        }
                        if (attr.Name == "Schedule State") {
                            scheduleStateAttr = attr;
                            scheduleStateValues = that._translateAllowedValuesToDropdownItems(attr.AllowedValues);
                        }
                    });
                    stateDropdown = new rally.sdk.ui.basic.Dropdown({
                        defaultValue : scheduleStateAttr.ElementName,
                        label:"Group By",
                        showLabel:true,
                        items:usableFields
                    });
    
                    stateDropdown.addEventListener("onLoad", callback);
                    stateDropdown.addEventListener("onChange", callback);
                    stateDropdown.display("stateDropdown");
    
                    that._displayTogglePreferences();
    
                    that._checkReadyField(readyFieldExist);
    
                }
                catch(ex) {
                    if (ex.stack) {
                        rally.Logger.warn(ex.stack);
                    }
                    rally.Logger.warn(ex);
                }
            }
    
            rallyDataSource.find({
                type:"TypeDefinition",
                key: "types",
                query:'( Name = "Hierarchical Requirement" )',
                fetch:"Name,Attributes",
                project:null
            }, gatherAttributes);
        };
    
        //This wraps our controls to get their values
        that._getValues = function() {
            var values = {
                kanbanField:stateDropdown.getValue(),
                reportKanbanField:stateDropdown.getDisplayedValue(),
                fieldInfos:{},
                showTaskCompletion: showTasksCheckBox.getChecked(),
                showDefectStatus: showDefectsCheckBox.getChecked(),
                showAge: showAgeCheckBox.getChecked(),
                showAgeAfter: showAgeTextBox.getValue(),
                colorByArtifactType: colorByArtifactTypeCheckBox.getChecked()
            };
            if (hideCardsCheckBox) {
                values.hideLastColumnIfReleased = hideCardsCheckBox.getChecked();
            }
            rally.forEach(accessors, function(value) {
                values.fieldInfos[value.field] = value.get();
    
                //ensure we don't lose policy descriptions that already stored for that field
                if (currentPrefs && currentPrefs.fieldInfos &&
                        currentPrefs.fieldInfos[value.field] && currentPrefs.fieldInfos[value.field].policy) {
                    values.fieldInfos[value.field].policy = currentPrefs.fieldInfos[value.field].policy;
                }
            });
            return values;
        };
    
        //this wraps the controls and sets their values.
        that._setValues = function() {
            if (hideCardsCheckBox) {
                hideCardsCheckBox.setChecked(currentPrefs.hideLastColumnIfReleased);
            }
            showTasksCheckBox.setChecked(currentPrefs.showTaskCompletion);
            showDefectsCheckBox.setChecked(currentPrefs.showDefectStatus);
            showAgeCheckBox.setChecked(currentPrefs.showAge);
            showAgeTextBox.setValue(currentPrefs.showAgeAfter);
            colorByArtifactTypeCheckBox.setChecked(currentPrefs.colorByArtifactType);
    
            function setValues() {
                var fieldInfos = currentPrefs.fieldInfos;
                // remove the Event Handler from the dropdown
                if (deleter) {
                    deleter.remove();
                }
                rally.forEach(accessors, function(accessor) {
                    if (fieldInfos[accessor.field]) {
                        accessor.set(fieldInfos[accessor.field]);
                    }
                });
            }
    
            if (stateDropdown.getValue() !== currentPrefs.kanbanField) {
                stateDropdown.setValue(currentPrefs.kanbanField);
                var deleter = stateDropdown.addEventListener("onChange", setValues);
            }
            else {
                setValues();
            }
        };
    
        that._retrievePreferences = function(/*function*/callback) {
            function populateConfigForm(results) {
                var workspacePref;
                var projectPref;
                if (results.length) {
                    dojo.forEach(results, function(p) {
                        if (p.Project) {
                            //projectOid is a string need both strings to compare.
                            var projectRef = rally.sdk.util.Ref.getOidFromRef(p.Project) + "";
                            if (projectRef === rallyDataSource.projectOid) {
                                projectPref = p;
                                projectPrefRef = projectPref._ref;
                            }
                        }
                        if (p.Workspace) {
                            workspacePrefRef = p._ref;
                            workspacePref = p;
                        }
                    });
                    if (projectPref) {
                        currentPrefs = projectPref.Value;
                        that._setValues();
                        callback({projectName:projectPref.Project._refObjectName});
                    }
                    else if (workspacePref) {
                        currentPrefs = workspacePref.Value;
                        that._setValues();
                        callback({workspaceName:workspacePref.Workspace._refObjectName});
                    }
                } else {
                    callback({});
                }
            }
    
            rallyDataSource.preferences.getAppPreferences(populateConfigForm);
        };
    
        that._saveComplete = function() {
            that._retrievePreferences(function() {});
            onConfigHide();
        };
    
        that._storeValues = function(callback) {
    
            function workspaceCallback(results) {
    
            }
    
            function errorCallback(results) {
                rally.Logger.warn(results);
            }
    
            function errorProjectCallback(results) {
                dialog.hide();
                rally.Logger.warn(results);
                if (results.Errors.length > 0) {
                    var error = results.Errors[0];
                    if (error.match(/user does not have preference write permission/i)) {
                        error = "You must have Editor permissions to setup the Kanban Board for the current project.";
                    }
                    rally.sdk.ui.AppHeader.showMessage("error", error, 10000);
                }
            }
    
            function updateAppPreference(pref) {
    
                currentPrefs = dojo.fromJson(pref.Value);
                var updatedPref = {_ref : projectPrefRef,
                    Value:that._getValues()
                };
                rallyDataSource.preferences.update(updatedPref, callback, errorProjectCallback);
            }
    
            if (!projectPrefRef) {
                if (!workspacePrefRef) {
                    rallyDataSource.preferences.createAppPreference(
                            {
                                Name: "Kanban/WipSettings",
                                Value: that._getValues(),
                                Workspace:"/Workspace/" + rallyDataSource.workspaceOid
                            },
                            workspaceCallback,
                            errorCallback);
                }
                rallyDataSource.preferences.createAppPreference(
                        {
                            Name: "Kanban/WipSettings",
                            Value: that._getValues(),
                            Project:"/Project/" + rallyDataSource.projectOid
                        },
                        callback,
                        errorProjectCallback);
            }
            else {
    
                //Re-read pref before saving so we don't clobber anything
                rallyDataSource.getRallyObject(projectPrefRef, updateAppPreference, errorCallback);
    
            }
    
        };
    
        that._disableCancelButton = function() {
            if (!that.cancelButton) {
                return;
            }
    
            if (that.disableCancel) {
                dojo.style(that.cancelButton, 'display', 'none');
            } else {
                dojo.style(that.cancelButton, 'display', 'inline');
            }
        };
    
    };
    /** Copyright (c) 2013 Rally Software Development Corp. All rights reserved **/
    KanbanPolicy = function(rallyDataSource, callback) {
        var that = this;
        var workspacePrefRef, projectPrefRef, fieldName, policyDesc;
    
        that._updatePolicyPref = function(currentPrefs) {
            currentPrefs.fieldInfos[fieldName].policy = policyDesc;
            that._storeValues(currentPrefs);
        };
    
        that.savePolicy = function(field, policy) {
            fieldName = field;
            policyDesc = dojo.trim(policy);
            that._retrievePreferences(that._updatePolicyPref);
        };
    
        that._retrievePreferences = function(prefCallback) {
            function getProjectOrWorkspacePref(results) {
                var workspacePref, projectPref, currentPrefs;
    
                if (results.length) {
                    dojo.forEach(results, function(p) {
                        if (p.Project) {
                            //projectOid is a string need both strings to compare.
                            var projectRef = rally.sdk.util.Ref.getOidFromRef(p.Project) + "";
                            if (projectRef === rallyDataSource.projectOid) {
                                projectPref = p;
                                projectPrefRef = projectPref._ref;
                            }
                        }
                        if (p.Workspace) {
                            workspacePref = p;
                            workspacePrefRef = p._ref;
                        }
                    });
                    if (projectPref) {
                        currentPrefs = projectPref.Value;
                    }
                    else if (workspacePref) {
                        currentPrefs = workspacePref.Value;
                    }
                }
                prefCallback(currentPrefs);
            }
            rallyDataSource.preferences.getAppPreferences(getProjectOrWorkspacePref);
        };
    
        that._storeValues = function(values) {
            function successCallback(results) {
                callback(policyDesc);
            }
    
            function errorProjectCallback(results) {
                rally.Logger.warn(results);
                if (results.Errors.length > 0) {
                    var error = results.Errors[0];
                    if (error.match(/user does not have preference write permission/i)) {
                        error = "You must have Editor permissions to set up the Kanban Board for the current project.";
                    }
                    rally.sdk.ui.AppHeader.showMessage("error", error, 10000);
                    callback(null);
                }
            }
    
            if (!projectPrefRef) {
                rally.sdk.ui.AppHeader.showMessage("error", "You must set up a Kanban Board before creating column policies.", 10000);
            }
            else {
    
                var pref = {_ref : projectPrefRef,
                    Value: values
                };
                rallyDataSource.preferences.update(pref, successCallback, errorProjectCallback);
            }
    
        };
    
    };
    /** Copyright (c) 2013 Rally Software Development Corp. All rights reserved **/
    KanbanReport = function(config, rallyDataSource) {
        var that = this;
    
        var dialog, report, divId, msgDiv;
        var artifactTypes = config.artifactTypes;
        var reportName = config.reportName;
    
        var reportWidth = 500;
        var reportHeight = 350;
    
        that._onDialogClose = function(d, args) {
            d.destroy();
        };
    
        that._onLoad = function() {
            msgDiv.style.display = 'none';
        };
    
        that._displayReport = function() {
            dialog.display();
            report.display(divId, that._onLoad); //need to render dialog before we can populate the iframe with report
    
            //workaround for the sub-chart that adds 125px to height
            dojo.query("iframe").forEach(function(node, index, arr) {
                dojo.attr(node, "style", {
                    height: reportHeight + "px",
                    width: reportWidth + "px"
                });
            });
        };
    
        that._createReport = function() {
            var workItems,reportConfig;
            var dialogDiv = document.createElement("div");
    
            msgDiv = document.createElement("div");
            msgDiv.innerHTML = "Some reports may take up to 4 minutes to load.";
            dialogDiv.appendChild(msgDiv);
    
            var map = {Defect: 'D', HierarchicalRequirement: 'G'};
    
            if (artifactTypes.length === 2) {
                workItems = 'N';
            } else {
                workItems = map[artifactTypes];
            }
    
            reportConfig = {
                report: config.reportNumber,
                width: reportWidth - 25, //make chart a bit smaller than dialog
                height: reportHeight - 150, //EB account for sub-chart that takes up 125px
                work_items: workItems
            };
    
            //workaround issue with reports where applying filter on ScheduleState causes no data to be returned
            if (config.reportKanbanField !== "Schedule State") {
                reportConfig.filter_field = config.reportKanbanField;
            }
    
            if (config.tags) {
                reportConfig.tags = config.tags;
            }
    
            //window.top check ensures we are not running in the pop out
            if (!rally.sdk.util.Context.isInsideRally() && window.top === window.self) {
                reportConfig.project = rallyDataSource.projectOid;
                reportConfig.projectScopeDown = rallyDataSource.projectScopeDown;
                reportConfig.projectScopeUp = rallyDataSource.projectScopeUp;
            }
    
            var reportDiv = document.createElement("div");
            reportDiv.id = divId;
            dojo.addClass(reportDiv, "kanbanReport");
            dialogDiv.appendChild(reportDiv);
    
            report = new rally.sdk.ui.StandardReport(reportConfig);
            return dialogDiv;
        };
    
        that._createReportDialog = function() {
            if (!artifactTypes || artifactTypes.length <= 0) {
                rally.sdk.ui.AppHeader.showMessage("error", "No " + reportName + " data to show", 5000);
                return;
            }
    
            dialog = new rally.sdk.ui.basic.Dialog({title: reportName, draggable: true,
                closable: false, width: reportWidth + 25, height: reportHeight + 25, buttons:["Close"],
                content: that._createReport()});
    
            dialog.addEventListener("onButtonClick", that._onDialogClose);
            that._displayReport(divId);
        };
    
        this.display = function(element) {
            divId = element;
            that._createReportDialog();
        };
    };

  </script>

  <style type="text/css">
        .appHeaderContainer {
        margin-top: 0;
        margin-bottom: 0;
    }
    
    .appHeader {
        position: relative;
    }
    
    .togglePoliciesContainer {
        display: inline;
    }
    
    .controlContainer {
        margin-top: 10px;
        margin-right: 20px;
        float: right;
    }
    
    .typeFilterContainer {
        float: left;
        margin-left: 3px;
    }
    
    .dropdownContainer {
        float: left;
        margin-left: 3px;
        margin-right: 10px;
    }
    
    .dropdownContainer .filterByTagsDropdown .dijitTextBox {
        vertical-align: middle;
    }
    
    .dropdownContainer .filterByTagsDropdown .dropdownLabel {
        vertical-align: middle;
    }
    
    .kanbanBoard {
        clear: both;
    }
    
    .overCapacity .columnHeader {
        color: #FC5350;
    }
    
    .overCapacity .columnContent {
        background-color: #F9E2E1;
    }
    
    .cardboard .columnHeader {
        height: 35px;
    }
    
    .columnPolicy {
        margin: 5px;
        border-top: 1px dotted #c6c6c6;
        padding-top: 5px;
    }
    
    .hidePolicy {
        display: none;
    }
    
    .columnPolicy .policyHeaderDiv {
        color: #808080;
        font-weight: bold;
        padding-bottom: 3px;
    }
    
    .columnPolicy .policySubheaderDiv {
        color: #808080;
        display: none;
        padding-bottom: 4px;
    }
    
    .columnPolicy textarea {
        color: #808080;
        font: 11px arial, tahoma, helvetica, sans-serif;
        resize: none;
    }
    
    .columnPolicy .dijitTextBoxDisabled {
        background-color: transparent;
    }
    
    .columnPolicy .dijitTextAreaDisabled {
        color: #c6c6c6 !important;
    }
    
    .columnPolicy .dijitTextBox {
        background: none;
    }
    
    .editPolicyDiv {
        padding-top: 3px;
        text-align: center;
    }
    
    .editPolicyDiv a {
        vertical-align: middle;
    }
    
    .editPolicyDivExtraPadding {
        padding-top: 8px;
    }
    
    .cardboard .capacityDisplay {
        font-size: 12px;
        line-height: 20px;
    }
    
    .cardboard .cardMenu {
        height: 0;
        background-color: #F4F7F8;
        line-height: 18px;
        padding-top: 1px;
        overflow: hidden;
    }
    
    .cardboard .cardHeader {
        background-color: #e0e0e0;
    }
    
    .cardboard .ready .cardContent, .cardboard .blocked .cardContent {
        overflow: hidden; /*KRM - For Chrome*/
    }
    
    .cardboard .cardMenu.expanded {
        border-top: 1px;
        border-top-style: dotted;
        border-top-color: #CCC;
        padding: 0;
    }
    
    .cardboard .infinitySymbol {
        font-size: 16px;
        position: relative;
        top: 2px;
    }
    
    .cardboard .readyIndicator, .cardboard .blockedIndicator {
        width: 16px;
        height: 16px;
        float: right;
        margin: 1px;
        cursor: pointer;
    }
    
    .cardboard .readyIndicator {
        background-image: url(/apps/images/unready.png);
    }
    
    .cardboard .ready .readyIndicator {
        overflow: hidden; /*KRM - For Chrome*/
        background-image: url(/apps/images/ready.png);
    }
    
    .cardboard .blockedIndicator {
        background-image: url(/apps/images/not-blocked.png);
    }
    
    .cardboard .blocked .blockedIndicator {
        overflow: hidden; /*KRM - For Chrome*/
        background-image: url(/apps/images/blocked.png);
    }
    
    .cardboard .ready {
        border: 3px solid #21CD18;
    }
    
    .cardboard .blocked {
        border: 3px solid #EF3F36;
    }
    
    .cardboard .cardContent .status {
        border-top: 1px dotted #ccc;
        margin-top: 5px;
    }
    
    .cardboard .cardContent .status a {
        color: #666;
    }
    
    .cardboard .cardContent .tasks, .defects {
        background-repeat: no-repeat;
        color: #666;
        padding-top: 5px;
        padding-left: 16px;
        line-height: 12px;
    }
    
    .cardboard .cardContent .tasks.none {
        background-image: url(/apps/resources/Kanban/status-defined-12.png);
        background-position: left bottom;
    }
    
    .cardboard .cardContent .tasks.some {
        background-image: url(/apps/resources/Kanban/status-inprogress-good-12.png);
        background-position: left bottom;
    }
    
    .cardboard .cardContent .tasks.all, .cardboard .cardContent .defects.all {
        background-image: url(/apps/resources/Kanban/status-good-12.png);
        background-position: left bottom;
    }
    
    .cardboard .cardContent .defects.none {
        background-image: url(/apps/resources/Kanban/status-bad-12.png);
        background-position: left bottom;
    }
    
    .cardboard .cardContent .defects.some {
        background-image: url(/apps/resources/Kanban/status-inprogress-bad-12.png);
        background-position: left bottom;
    }
    
    .cardboard .filterByFade {
        -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=30)";
        filter: alpha(opacity = 30);
        opacity: .3;
        zoom: 1; /*ANS - necessary for IE7 opacity */
    }
    
    .cardboard .filterByFade.blocked,
    .cardboard .filterByFade.ready {
        -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=30)";
        filter: alpha(opacity = 30);
        opacity: .3;
        zoom: 1; /*ANS - necessary for IE7 opacity */
    }
    
    .cardboard .editLinkContainer {
        float: left;
        padding-left: 5px;
        vertical-align: middle;
    }
    
    .wipTextBoxContainer .dijitTextBox {
        width: 40px;
    }
    
    #configPanel {
        visibility: hidden;
    }
    
    .tableHeaderRow {
        font-weight: bold;
        text-align: left;
    }
    
    .configCheckBox {
        text-align: center;
    }
    
    .configMessage {
        margin-top: 5px;
        word-wrap: break-word;
        width: 300px;
        color: #EF3F36;
    }
    
    .configHelp {
        float: right;
    }
    
    .kanbanReport {
        float: left;
    }
    
    #configTable {
        margin-top: 10px;
        margin-bottom: 10px;
    }
    
    .dijitTooltipContents {
        font-size: 9.5px;
    }
    
    .buttonContainer.saveCancelButtons span,
    .buttonContainer.saveCancelButtons a {
        vertical-align: middle;
    }
    
    #showAgeTextBoxInnerDivLabel {
        vertical-align: baseline;
    }
    
    #widget_showAgeTextBoxInnerDiv {
        vertical-align: top;
    }
    
    .cardboard .columnContent .agedCard {
        border-top: 1px dotted #ccc;
        color: #666666;
        margin-top: 5px;
        padding-top: 5px;
    }

  </style>
    <script type="text/javascript">
        //this function will help us by getting our default project and workspace to help with external kanban development.
        var WORKSPACE_OID,PROJECT_OID,PROJECT_SCOPE_UP,PROJECT_SCOPE_DOWN;
        function projectParser(response) {
            var meta = response.QueryResult.Meta;
            var numberYankingRegex = /[^\d]/g;
            WORKSPACE_OID = meta.workspace.replace(numberYankingRegex, '');
            PROJECT_OID = meta.project.replace(numberYankingRegex, '');
        }
    </script>

    <script type="text/javascript">
        /*configure following if you plan to run the Kanban app externally
         rally.sdk.info.server = "https://community.rallydev.com/slm";*/
        // var WORKSPACE_OID = "1234";
        // var PROJECT_OID = "54678";
        // var PROJECT_SCOPE_UP = false;
        // var PROJECT_SCOPE_DOWN = true;
        /********************************/

        var projectOid = '__PROJECT_OID__';
        projectOid = projectOid.indexOf("__") !== -1 ? PROJECT_OID : projectOid;
        var workspaceOid = '__WORKSPACE_OID__';
        workspaceOid = workspaceOid.indexOf("__") !== -1 ? WORKSPACE_OID : workspaceOid;
        var projectScopeUp = '__PROJECT_SCOPING_UP__';
        projectScopeUp = projectScopeUp.indexOf("__") !== -1 ? PROJECT_SCOPE_UP : projectScopeUp;
        var projectScopeDown = '__PROJECT_SCOPING_DOWN__';
        projectScopeDown = projectScopeDown.indexOf("__") !== -1 ? PROJECT_SCOPE_DOWN : projectScopeDown;

        function onLoad() {
            var rallyDataSource = new rally.sdk.data.RallyDataSource(workspaceOid,
                    projectOid, projectScopeUp, projectScopeDown);

            rallyDataSource.projectOid = projectOid;
            rallyDataSource.workspaceOid = workspaceOid;
            rallyDataSource.projectScopeUp = projectScopeUp;
            rallyDataSource.projectScopeDown = projectScopeDown;

            var kanbanBoard;
            rallyDataSource.find({
                     key: 'WorkspaceConfiguration',
                     type: 'WorkspaceConfiguration',
                     fetch: 'DragDropRankingEnabled'
                 }, function(results) {
                     var isManualRankWorkspace = !results.WorkspaceConfiguration[0].DragDropRankingEnabled;
                     kanbanBoard = new KanbanBoard(rallyDataSource, isManualRankWorkspace);
                     kanbanBoard.display(dojo.body());
                 }
            );
        }
        rally.addOnLoad(onLoad);
    </script>

</head>
<body>
<div style="display:none">
    <div id="configPanel">
        <div id="configHelp"></div>
        <div id="stateDropdown"></div>
        <table id="configTable" cellspacing="15">
            <tbody id="configTableBody">
            <tr class="tableHeaderRow">
                <td>Column</td>
                <td>Display</td>
                <td>WIP Limit</td>
                <td>Schedule State Mapping</td>
            </tr>
            </tbody>
        </table>
        <div id="hideCardsCheckBox"></div>
        <div id="showTasksCheckBox"></div>
        <div id="showDefectsCheckBox"></div>
        <div>
            <span id="showAgeCheckBox"></span>
            <span id="showAgeTextBox"></span>
        </div>
        <div id="colorByArtifactTypeCheckBox"></div>
        <div id="msgContainer"></div>
    </div>
</div>
</body>
</html>