<commit_before>/*
 * Copyright 2005-2014 The Kuali Foundation
 *
 * Licensed under the Educational Community License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.opensource.org/licenses/ecl2.php
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
var bodyHeight;
var profilingOn = false;

/**
 * Handle checkbox label clicks to get around issue with rich message content.
 *
 * <p>When the label text itself is clicked, the checkbox should toggle.  When the field associated with
 * the checkbox is clicked, the checkbox should be checked regardless of state.
 * Clicking links or buttons in rich content should do nothing to the state.</p>
 *
 * @param checkboxId id of the checkbox to check/uncheck
 * @param event event with the associated clicked target
 */
function handleCheckboxLabelClick(checkboxId, event) {
    var checkbox = jQuery("#" + checkboxId);
    if (!checkbox.prop("disabled")) {
        if (jQuery(event.target).is("input, select, textarea, option")) {
            if (!checkbox.prop("checked")) {
                checkbox.prop("checked", true);
                checkbox.change();
            }
        }
        else if (jQuery(event.target).is("a, button")) {
            //do nothing
        }
        else {
            if (checkbox.prop("checked")) {
                checkbox.prop("checked", false);
                checkbox.change();
            }
            else {
                checkbox.prop("checked", true);
                checkbox.change();
            }
        }
    }
}

/**
 * Handle radio label clicks to get around issue with rich message content.
 *
 * @param radioId id of the radio to check
 * @param event event with the associated clicked target
 */
function handleRadioLabelClick(radioId, event) {
    var radio = jQuery("#" + radioId);
    if (!radio.prop("disabled") && !radio.prop("checked")) {
        radio.prop("checked", true);
        radio.change();
    }
}

/**
 * Takes a name that may have characters incompatible with jQuery selection and escapes them so they can
 * be used in selectors.  This method MUST be called when selecting on a name that can be ANY name on the page
 * to avoid issues with collections(mainly)
 *
 * @returns a string that has been escaped for use in jQuery selectors
 */
function escapeName(name) {
    name = name.replace(/\\'/g, "'");
    name = name.replace(/'/g, "\\'");
    name = name.replace(/\\"/g, "\"");
    name = name.replace(/"/g, "\\\"");
    name = name.replace(/\./g, "\\.");
    name = name.replace(/\[/g, "\\[");
    name = name.replace(/\]/g, "\\]");

    return name;
}

/**
 * Convert the text passed in from escapedHtml to html text.  Remove all anchor tags if flag is set to true.
 *
 * @param text the text with gt; and lt; and other escaped symbols that need to be translated
 * @param removeAnchors if true, do not include the anchor tags in the converted text
 * (but, still include their textual content)
 */
function convertToHtml(text, removeAnchors) {
    if (removeAnchors) {
        text = text.replace(/&lt;a.+?&gt;/gi, "");
        text = text.replace(/&lt;\/a&gt;/gi, "");
    }

    return jQuery("<span />").html(text).text();
}

/**
 * Can be used when the view is within a iframe to publish its height to the surrounding window (for
 * resizing the iframe if necessary)
 */
function publishHeight() {
    var parentUrl = "";
    if (navigator.cookieEnabled) {
        parentUrl = jQuery.cookie('parentUrl');
        var passedUrl = decodeURIComponent(document.location.hash.replace(/^#/, ''));
        if (passedUrl && passedUrl.substring(0, 4) === "http") {
            jQuery.cookie('parentUrl', passedUrl, {path: '/'});
            parentUrl = passedUrl;
        }
    }

    if (parentUrl === "") {
        //make the assumption for not cross-domain, will have no effect if cross domain (message wont be
        //received)
        parentUrl = window.location;
        parentUrl = decodeURIComponent(parentUrl);
    }

    var height = jQuery("body").outerHeight();
    jQuery("body").attr("style", "overflow-x: auto; padding-right: 20px;");
    if (parentUrl && !isNaN(height) && height > 0) {
        jQuery.postMessage({ if_height: height}, parentUrl, parent);
        bodyHeight = height;
    }
}

/**
 * Get the current context
 *
 * @returns the jQuery context that can be used to perform actions that must be global to the entire page
 * ie, showing lightBoxes and growls etc
 */
function getContext() {
    if (usePortalForContext()) {
        return top.jQuery;
    }
    else {
        return jQuery.noConflict();
    }
}

/**
 * Check if portal should be used for context
 *
 * <p>
 * To avoid cross server script errors the local context is used in case the portal window is on a different host.
 * </p>
 *
 * @return true if portal is used for context, false otherwise
 */
function usePortalForContext() {
    var usePortal = false;

    // for iframe use the outer window's context unless the outer window is hosted on a different domain.
    try {
        // For security reasons the browsers will not allow cross server scripts and
        // throw an exception instead.
        // Note that bad browsers (e.g. google chrome) will not catch the exception
        usePortal = (top != self) && (top.location.host == location.host);
    }
    catch (e) {
        usePortal = false;
    }

    return usePortal;
}

/**
 * Indicates whether the given window is the portal container
 *
 * @param window - window to test
 */
function isPortalContainer(window) {
    return window.jQuery('#' + kradVariables.PORTAL_IFRAME_ID).length;
}

/**
 * Attempts to find an element based on the given selector taking into consideration the portal.
 *
 * @param selector jquery selector to find element
 * @returns jquery object for found element, or null if an element was not found
 */
function findElement(selector) {
    var jqElement;

    if (parent.jQuery('iframe[id*=easyXDM_]').length > 0) {
        // portal and content on same domain
        jqElement = top.jQuery('iframe[id*=easyXDM_]').contents().find('#' + kradVariables.PORTAL_IFRAME_ID).contents().find(selector);
    } else if (parent.parent.jQuery('#' + kradVariables.PORTAL_IFRAME_ID).length > 0) {
        // portal and content on different domain
        jqElement = parent.parent.jQuery('#' + kradVariables.PORTAL_IFRAME_ID).contents().find(selector);
    } else {
        jqElement = top.jq(selector);
    }

    return jqElement;
}

/**
 * Finds element(s) within the given context that have the data role attribute equal to the given role.
 *
 * @param role data attribute role value to find
 * @param $context (optional) content to find elements, if empty the entire document will be used
 * @returns {*} found elements, if any
 */
function findByDataRole(role, $context) {
    $context = $context || jQuery(document);

    return $context.find("[" + kradVariables.ATTRIBUTES.DATA_ROLE + "='" + role + "']");
}

/**
 * Sets a configuration parameter that will be accessible with script
 *
 * <p>
 * Configuration parameters are sent from the server and represent non-component
 * state, such as location of images
 * </p>
 *
 * @param paramName - name of the configuration parameter
 * @param paramValue - value for the configuration parameter
 */
function setConfigParam(paramName, paramValue) {
    var configParams = jQuery(document).data("ConfigParameters");
    if (!configParams) {
        configParams = new Object();
        jQuery(document).data("ConfigParameters", configParams);
    }
    configParams[paramName] = paramValue;
}

/**
 * Called when a view is rendered to initialize the state of components
 * that need to be accessed client side
 *
 * @param viewState - map (object) containing the view state
 */
function initializeViewState(viewState) {
    jQuery(document).data(kradVariables.VIEW_STATE, viewState);
}

/**
 * Updates the current view state with the given map of view state
 *
 * <p>
 * The given state will be merged with the current. Matching keys for simple properties will be overridden
 * if contained in the second map, in all cases except when the value is another map, in which case the map
 * value will be merged
 * </p>
 *
 * @param viewState - view state to merge in
 */
function updateViewState(viewState) {
    var currentViewState = jQuery(document).data(kradVariables.VIEW_STATE);
    if (currentViewState) {
        jQuery.extend(currentViewState, viewState);
    }
    else {
        jQuery(document).data(kradVariables.VIEW_STATE, viewState);
    }
}

/**
 * Sets a key/value pair in the view state
 *
 * @param key - name to reference state by
 * @param value - value for the state
 */
function setViewState(key, value) {
    var viewState = jQuery(document).data(kradVariables.VIEW_STATE);
    if (!viewState) {
        viewState = new Object();
        jQuery(document).data(kradVariables.VIEW_STATE, viewState);
    }
    viewState[key] = value;
}

/**
 * Retrieves the current value for a given key in the view state, if
 * not found empty string is returned
 *
 * @param key - name of the variable in view state to return
 */
function getViewState(key) {
    var viewState = jQuery(document).data(kradVariables.VIEW_STATE);
    if (viewState && viewState.hasOwnProperty(key)) {
        return viewState[key];
    }
    return "";
}

/**
 * Adds the given key/value pair to the state maintained for the give component
 *
 * @param componentId - id for the component the state should be associated with
 * @param key - name to reference the state by
 * @param value - value for the state
 */
function setComponentState(componentId, key, value) {
    var componentState = getViewState(componentId);
    if (!componentState) {
        componentState = new Object();
        setViewState(componentId, componentState);
    }
    componentState[key] = value;
}

/**
 * Retrieves the state value for the given key and given component
 *
 * @param componentId - id for the component the key is associated with
 * @param key - name of the state to retrieve
 */
function getComponentState(componentId, key) {
    var componentState = getViewState(componentId);
    if (componentState && componentState.hasOwnProperty(key)) {
        return componentState[key];
    }
    return "";
}

/**
 * Returns the current view state as a JSON string for posting
 */
function getSerializedViewState() {
    var jsonViewState = "";

    var viewState = jQuery(document).data(kradVariables.VIEW_STATE);
    if (!jQuery.isEmptyObject(viewState)) {
        var jsonViewState = jQuery.toJSON(viewState);

        // change double quotes to single because escaping causes problems on URL
        jsonViewState = jsonViewState.replace(/"/g, "'");
    }

    return jsonViewState;
}

// gets the the label for field with the corresponding id
function getLabel(id) {
    var label = jQuery("[data-label_for='" + id + "']");
    if (label) {
        return label.text();
    }
    else {
        return "";
    }
}
/**
 * runs hidden scripts. The hidden scripts are contained in hidden input elements
 *
 * @param id - the tag id or selector expression to use. If empty, run all hidden scripts
 * @param isSelector - when present and true, the value given for the id used as jquery selector expression
 * @param skipValidationBubbling - set to true to skip processing each field - ONLY is ever true for pages since
 * they handle this during the setupPage call
 */
function runHiddenScripts(id, isSelector, skipValidationBubbling) {
    profile(true, "run-scripts:" + id);
    if (id) {

        var selector = "#" + id;
        if (isSelector) {
            selector = id;
        }

        evaluateScripts(selector);

        if (!isSelector) {
            runScriptsForId(id);
        }

        //Interpret new server message state for refreshed InputFields and write them out
        if (!skipValidationBubbling) {
            pageValidationPhase = true;

            jQuery(selector).find("div[data-role='InputField']").andSelf().filter("div[data-role='InputField']").each(function () {
                var id = jQuery(this).attr('id');
                var field = jQuery("#" + id);
                var data = getValidationData(field);
                var parent = field.data("parent");
                writeMessagesAtField(id);
                handleMessagesAtGroup(parent, id, data, true);
            });

            writeMessagesForPage();
            pageValidationPhase = false;
        }
    }
    else {
        evaluateScripts();
    }

    profile(false, "run-scripts:" + id);
}

/**
 * Evaluate scripts for the selection, if defined.  If no selector is defined, evaluate hidden scripts
 * for the entire document
 *
 * @param selector optional jQuery selector string to select the object to run scripts for
 */
function evaluateScripts(selector) {
    if (selector) {
        //run dataScript first always
        jQuery(selector).find("input[data-role='dataScript']").each(function () {
            evalHiddenScript(jQuery(this));
        });

        jQuery(selector).find("input[name='script']").each(function () {
            evalHiddenScript(jQuery(this));
        });
    }
    else {
        //run scripts for entire document if no selector defined
        //run dataScript first always
        jQuery("input[data-role='dataScript']").each(function () {
            evalHiddenScript(jQuery(this));
        });

        jQuery("input[name='script']").each(function () {
            evalHiddenScript(jQuery(this));
        });
    }
}

/**
 * runs hidden scripts. The hidden scripts are contained in hidden input elements
 *
 * <p>Finds all hidden inputs with the attribute data-role having a value of 'dataScript' or 'script'
 * then runs the script in the value attribute if the input's data-for attribute value is equal to the id provided</p>
 *
 * @param id - the tag id to use
 */
function runScriptsForId(id) {
    if (id) {
        jQuery("input[data-for='" + id + "']").filter("[data-role='dataScript']").each(function () {
            evalHiddenScript(jQuery(this));
        });

        jQuery("input[data-for='" + id + "']").filter("[data-role='script']").each(function () {
            evalHiddenScript(jQuery(this));
        });
    }
}

/**
 * do the actual work of evaluating a script once it has been located
 *
 * @param jqueryObj - a jquery object representing a hidden input element with a script in its value attribute
 */
function evalHiddenScript(jqueryObj) {
    if (jqueryObj.attr("name") === undefined || (jqueryObj.closest("[data-open]").length &&
            jqueryObj.closest("[data-open]").attr("data-open") === "false")) {
        return;
    }

    jqueryObj.removeAttr("name");
    jqueryObj.attr("script", "first_run");

    var js = jqueryObj.val();
    try {
        eval(js);
    } catch (err) {
        if (err instanceof ReferenceError) {
            throw new ReferenceError(err.message + " -> offending code: " + js, err.fileName, err.lineNumber);
        } else {
            throw err;
        }
    }

    // cleanup script for non-dev modes
    if (scriptCleanup) {
        jqueryObj.remove();
    }
}

/**
 * Writes a hidden for property 'methodToCall' set to the given value. This is
 * useful for submitting forms with JavaScript where the methodToCall needs to
 * be set before the form is submitted.
 *
 * @param methodToCall -
 *          the value that should be set for the methodToCall parameter
 */
function setMethodToCall(methodToCall) {
    jQuery("<input type='hidden' name='methodToCall' value='" + methodToCall + "'/>").appendTo(jQuery("#" + kradVariables.FORM_COMPLETE_ID));
}

/**
 * Writes a property name/value pair as a hidden input field on the form.
 *
 * <p>Called to dynamically set request parameters based on a chosen action. Assumes
 * existence of a div named 'formComplete' where the hidden inputs will be
 * inserted</p>
 *
 * @param propertyName name for the input field to write
 * @param propertyValue value for the input field to write
 */
function writeHiddenToForm(propertyName, propertyValue) {
    jQuery('input[name="' + escapeName(propertyName) + '"]').remove();

    if (propertyValue && typeof propertyValue === 'string') {
        propertyValue = propertyValue.replace(/"/g, "\\\"");
    }

    jQuery("<input type='hidden' name='" + propertyName + "'" + ' value="' + propertyValue + '"/>').appendTo(jQuery("#" + kradVariables.FORM_COMPLETE_ID));
}

/**
 * In some cases when an action is invoked, data that should be passed with
 * the request is written to the form as hiddens using the #writeHiddenToForm method. If
 * there are errors in the script that prevent the action from completing, this method can
 * be called to clear the hiddens
 */
function clearHiddens() {
    jQuery("#" + kradVariables.FORM_COMPLETE_ID).html("");
}

/**
 * Retrieves the actual value from the input widget specified by name
 */
function coerceValue(name) {
    var value = "";
    var nameSelect = "[name='" + escapeName(name) + "']";

    var fancyBoxWrapper = jQuery(".fancybox-wrap");
    var control;
    // Attempt to get from fancybox first, if it exists
    if (fancyBoxWrapper.length) {
        control = jQuery(nameSelect, fancyBoxWrapper);
    }

    // If no fancybox or control not in fancybox, get from document
    if (control == null || control.length == 0) {
        control = jQuery(nameSelect, document);
    }

    if (control.is(":checkbox") && control.length == 1) {
        value = control.filter(":checked").val();
    }
    else if (control.is(":checkbox") && control.length > 1) {
        value = [];
        control.filter(":checked").each(function () {
            value.push(jQuery(this).val());
        });
    }
    else if (control.is(":radio")) {
        value = control.filter(":checked").val();
    }
    else if (control.length) {
        if (control.hasClass("watermark")) {
            jQuery.watermark.hide(control, parent);
            value = control.val();
            jQuery.watermark.show(control, parent);
        }
        else {
            value = control.val();
        }
    }

    if (value == null) {
        value = "";
        return value;
    }

    // boolean matching
    if (value && !jQuery.isArray(value)) {
        if (value.toUpperCase() == "TRUE") {
            value = true;
        } else if (value.toUpperCase() == "FALSE") {
            value = false;
        }
    }

    return value;
}

/**
 * Sets a value on the control with the given name attribute
 *
 * @param name - name on control to set value for
 * @param value - value to set
 */
function setValue(name, value) {
    var nameSelect = "[name='" + escapeName(name) + "']";
    var control = jQuery(nameSelect);

    if (value != undefined && !jQuery.isArray(value)
            && (control.is(":radio") || control.is("select") || control.is(":checkbox"))) {
        var valueArray = [value];
        control.val(valueArray);
    }
    else {
        control.val(value);
    }
}

//returns true if the field with name of name1 occurs before field with name2
function occursBefore(name1, name2) {
    var field1 = jQuery("[name='" + escapeName(name1) + "']");
    var field2 = jQuery("[name='" + escapeName(name2) + "']");

    field1.addClass("prereqcheck");
    field2.addClass("prereqcheck");

    var fields = jQuery(".prereqcheck");

    field1.removeClass("prereqcheck");
    field2.removeClass("prereqcheck");

    if (fields.index(field1) < fields.index(field2)) {
        return true;
    }
    else {
        return false;
    }
}

/**
 * Gets the actual attribute id to use element manipulation related to this attribute.
 *
 * @param elementId
 * @param elementType
 */
function getAttributeId(elementId) {
    var id = elementId;
    if (!id) {
        return '';
    }
    id = elementId.replace(/_control\S*/, "");
    return id;
}

// The following javascript is intended to resize the route log iframe
// to stay at an appropriate height based on the size of the documents
// contents contained in the iframe.
// NOTE: this will only work when the domain serving the content of kuali
// is the same as the domain serving the content of workflow.
var routeLogResizeTimer = "";
var currentHeight = 500;
var safari = navigator.userAgent.toLowerCase().indexOf('safari');

function setRouteLogIframeDimensions() {
    var routeLogFrame = document.getElementById("routeLogIFrame");
    var routeLogFrame = document.getElementById("routeLogIFrame");
    var routeLogFrameWin = window.frames["routeLogIFrame"];
    var frameDocHeight = 0;
    try {
        frameDocHeight = routeLogFrameWin.document.documentElement.scrollHeight;
    } catch (e) {
        // unable to set due to cross-domain scripting
        frameDocHeight = 0;
    }

    if (frameDocHeight > 0) {
        if (routeLogFrame && routeLogFrameWin) {

            if ((Math.abs(frameDocHeight - currentHeight)) > 30) {
                if (safari > -1) {
                    if ((Math.abs(frameDocHeight - currentHeight)) > 59) {
                        routeLogFrame.style.height = (frameDocHeight + 30) + "px";
                        currentHeight = frameDocHeight;
                    }
                } else {
                    routeLogFrame.style.height = (frameDocHeight + 30) + "px";
                    currentHeight = frameDocHeight;
                }
            }
        }
    }

    if (routeLogResizeTimer == "") {
        routeLogResizeTimer = setInterval("resizeTheRouteLogFrame()", 300);
    }
}

function resizeTheRouteLogFrame() {
    setRouteLogIframeDimensions();
}

/**
 * Open new browser window for the specified help url
 *
 * The help window is positioned in the center of the screen and resized to 1/4th of the screen.
 *
 * Browsers don't allow one to modify windows of other domains.  Thus to ensure that only one help window exist
 * and to guarantee it's placement, size and that the window is in the foreground the following process is performed:
 *   1) open the help window - this ensures that we get a window handle to any existing help window
 *   2) close the help window
 *   3) open a new help window with the correct placement, size and url
 *
 * @param url - url of the help window content
 */
function openHelpWindow(url) {
    var windowWidth = screen.availWidth / 2;
    var windowHeight = screen.availHeight / 2;
    var windowPositionY = parseInt((screen.availWidth / 2) - (windowWidth / 2));
    var windowPositionX = parseInt((screen.availHeight / 2) - (windowHeight / 2));

    var windowUrl = url;
    var windowName = 'HelpWindow';
    var windowOptions = 'width=' + windowWidth + ',height=' + windowHeight + ',top=' + windowPositionX + ',left=' + windowPositionY + ',scrollbars=yes,resizable=yes';
    var myWindow;

    /* chrome will not allow a open,close,open */
    var is_chrome = navigator.userAgent.toLowerCase().indexOf('chrome') > -1;
    if (!is_chrome) {
        myWindow = window.open('', windowName);
        myWindow.close();
    }

    myWindow = window.open(windowUrl, windowName, windowOptions);
}

/**
 * Uppercases the current value for the control with the given id
 *
 * @param controlId - id for the control whose value should be uppercased
 */
function uppercaseValue(controlId) {
    jQuery("#" + controlId).css('text-transform', 'uppercase');
    jQuery("#" + controlId).change(function () {
        this.value = this.value.toUpperCase();
    });
}

/**
 * Profiling helper method will print out profile info in firefox console
 *
 * @param start true to start profiling, false to stop profiling
 * @param testingText text to be printed with this profile
 */
function profile(start, testingText) {
    if (profilingOn && window.console && window.console.time && window.console.profile) {
        if (start) {
            console.time(testingText);
            console.profile(testingText);
        }
        else {
            console.profileEnd();
            console.timeEnd(testingText);
        }

    }
}

/**
 * Timing method for profiling use - will print out in console
 *
 * @param start true to start timing, false to stop timing
 * @param testingText text to be printed out with time results
 */
function time(start, testingText) {
    if (profilingOn && window.console && window.console.time) {
        if (start) {
            console.time(testingText);
        }
        else {
            console.timeEnd(testingText);
        }
    }
}

/**
 * Adds a class to the collection item related to the delete action
 *
 * @param deleteButton - the delete button that this event was triggered from
 * @param highlightItemClass - the class to add to the item that should be highlighted
 */
function deleteLineMouseOver(deleteButton, highlightItemClass) {
    var innerLayout = jQuery(deleteButton).parents('.' + kradVariables.TABLE_COLLECTION_LAYOUT_CLASS
            + ', .' + kradVariables.STACKED_COLLECTION_LAYOUT_CLASS).first().attr('class');
    if (innerLayout) {
        if (innerLayout.indexOf(kradVariables.TABLE_COLLECTION_LAYOUT_CLASS) >= 0) {
            jQuery(deleteButton).closest('tr').addClass(highlightItemClass);
        } else {
            jQuery(deleteButton).closest('.' + kradVariables.COLLECTION_ITEM_CLASS).addClass(highlightItemClass);
        }
    }
}

/**
 * Removes a class from the collection item related to the delete action
 *
 * @param deleteButton - the delete button that this event was triggered from
 * @param highlightItemClass - the class remove from the collection item
 */
function deleteLineMouseOut(deleteButton, highlightItemClass) {
    var innerLayout = jQuery(deleteButton).parents('.' + kradVariables.TABLE_COLLECTION_LAYOUT_CLASS
            + ', .' + kradVariables.STACKED_COLLECTION_LAYOUT_CLASS).first().attr('class');
    if (innerLayout) {
        if (innerLayout.indexOf(kradVariables.TABLE_COLLECTION_LAYOUT_CLASS) >= 0) {
            jQuery(deleteButton).closest('tr').removeClass(highlightItemClass);
        } else {
            jQuery(deleteButton).closest('.' + kradVariables.COLLECTION_ITEM_CLASS).removeClass(highlightItemClass);
        }
    }
}

/**
 * Adds a class to the collection group related to the add action
 *
 * @param addButton - the add button that this event was triggered from
 * @param highlightItemClass - the class to add to the group that should be highlighted
 */
function addLineMouseOver(addButton, highlightItemClass) {
    var innerLayout = jQuery(addButton).parents('.' + kradVariables.TABLE_COLLECTION_LAYOUT_CLASS
            + ', .' + kradVariables.STACKED_COLLECTION_LAYOUT_CLASS).first().attr('class');
    if (innerLayout) {
        if (innerLayout.indexOf(kradVariables.TABLE_COLLECTION_LAYOUT_CLASS) >= 0) {
            jQuery(addButton).parent().find('table').addClass(highlightItemClass);
        } else {
            jQuery(addButton).parent().find('.' + kradVariables.STACKED_COLLECTION_LAYOUT_CLASS).addClass(highlightItemClass).children().addClass(highlightItemClass);
        }
    }
}

/**
 * Removes a class from the collection group related to the add action
 *
 * @param addButton - the add button that this event was triggered from
 * @param highlightItemClass - the class remove from the collection group
 */
function addLineMouseOut(addButton, highlightItemClass) {
    var innerLayout = jQuery(addButton).parents('.' + kradVariables.TABLE_COLLECTION_LAYOUT_CLASS
            + ', .' + kradVariables.STACKED_COLLECTION_LAYOUT_CLASS).first().attr('class');
    if (innerLayout) {
        if (innerLayout.indexOf(kradVariables.TABLE_COLLECTION_LAYOUT_CLASS) >= 0) {
            jQuery(addButton).parent().find('table').removeClass(highlightItemClass);
        } else {
            jQuery(addButton).parent().find('.' + kradVariables.STACKED_COLLECTION_LAYOUT_CLASS).removeClass(highlightItemClass).children().removeClass(highlightItemClass);
        }
    }
}

/**
 * Enables and disables the save action
 *
 * @param inputField
 * @param highlightItemClass - the class to add to the collection item
 */
function collectionLineChanged(inputField, highlightItemClass) {
    // This is not very good for performance but because dirty_form gets binded after this event so we need to trigger
    // the dirty_form check before checking for the dirty fields
    jQuery(inputField).triggerHandler('change');

    // Get the innerlayout to see if we are dealing with table or stack group
    var innerLayout = jQuery(inputField).parents('.' + kradVariables.TABLE_COLLECTION_LAYOUT_CLASS
            + ', .' + kradVariables.STACKED_COLLECTION_LAYOUT_CLASS).first().attr('class');

    if (innerLayout.indexOf(kradVariables.TABLE_COLLECTION_LAYOUT_CLASS) >= 0) {
        var row = jQuery(inputField).closest('tr');
        var enabled = row.find('.dirty').length > 0;
        var saveButton = row.find('.' + kradVariables.SAVE_LINE_ACTION_CLASS);

        if (enabled) {
            saveButton.removeClass('disabled');
            saveButton.removeAttr('disabled');
        } else {
            saveButton.addClass('disabled');
            saveButton.attr('disabled', 'disabled');
        }

    } else {
        var itemGroup = jQuery(inputField).closest('.' + kradVariables.COLLECTION_ITEM_CLASS);
        var enabled = itemGroup.find('.dirty').length > 0;
        var saveButton = itemGroup.find('.' + kradVariables.SAVE_LINE_ACTION_CLASS);

        if (enabled) {
            saveButton.removeClass('disabled');
            saveButton.removeAttr('disabled');
        } else {
            saveButton.addClass('disabled');<commit_msg>KULRICE-13262: Check whether innerLayout exists<commit_after>/*
 * Copyright 2005-2014 The Kuali Foundation
 *
 * Licensed under the Educational Community License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.opensource.org/licenses/ecl2.php
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
var bodyHeight;
var profilingOn = false;

/**
 * Handle checkbox label clicks to get around issue with rich message content.
 *
 * <p>When the label text itself is clicked, the checkbox should toggle.  When the field associated with
 * the checkbox is clicked, the checkbox should be checked regardless of state.
 * Clicking links or buttons in rich content should do nothing to the state.</p>
 *
 * @param checkboxId id of the checkbox to check/uncheck
 * @param event event with the associated clicked target
 */
function handleCheckboxLabelClick(checkboxId, event) {
    var checkbox = jQuery("#" + checkboxId);
    if (!checkbox.prop("disabled")) {
        if (jQuery(event.target).is("input, select, textarea, option")) {
            if (!checkbox.prop("checked")) {
                checkbox.prop("checked", true);
                checkbox.change();
            }
        }
        else if (jQuery(event.target).is("a, button")) {
            //do nothing
        }
        else {
            if (checkbox.prop("checked")) {
                checkbox.prop("checked", false);
                checkbox.change();
            }
            else {
                checkbox.prop("checked", true);
                checkbox.change();
            }
        }
    }
}

/**
 * Handle radio label clicks to get around issue with rich message content.
 *
 * @param radioId id of the radio to check
 * @param event event with the associated clicked target
 */
function handleRadioLabelClick(radioId, event) {
    var radio = jQuery("#" + radioId);
    if (!radio.prop("disabled") && !radio.prop("checked")) {
        radio.prop("checked", true);
        radio.change();
    }
}

/**
 * Takes a name that may have characters incompatible with jQuery selection and escapes them so they can
 * be used in selectors.  This method MUST be called when selecting on a name that can be ANY name on the page
 * to avoid issues with collections(mainly)
 *
 * @returns a string that has been escaped for use in jQuery selectors
 */
function escapeName(name) {
    name = name.replace(/\\'/g, "'");
    name = name.replace(/'/g, "\\'");
    name = name.replace(/\\"/g, "\"");
    name = name.replace(/"/g, "\\\"");
    name = name.replace(/\./g, "\\.");
    name = name.replace(/\[/g, "\\[");
    name = name.replace(/\]/g, "\\]");

    return name;
}

/**
 * Convert the text passed in from escapedHtml to html text.  Remove all anchor tags if flag is set to true.
 *
 * @param text the text with gt; and lt; and other escaped symbols that need to be translated
 * @param removeAnchors if true, do not include the anchor tags in the converted text
 * (but, still include their textual content)
 */
function convertToHtml(text, removeAnchors) {
    if (removeAnchors) {
        text = text.replace(/&lt;a.+?&gt;/gi, "");
        text = text.replace(/&lt;\/a&gt;/gi, "");
    }

    return jQuery("<span />").html(text).text();
}

/**
 * Can be used when the view is within a iframe to publish its height to the surrounding window (for
 * resizing the iframe if necessary)
 */
function publishHeight() {
    var parentUrl = "";
    if (navigator.cookieEnabled) {
        parentUrl = jQuery.cookie('parentUrl');
        var passedUrl = decodeURIComponent(document.location.hash.replace(/^#/, ''));
        if (passedUrl && passedUrl.substring(0, 4) === "http") {
            jQuery.cookie('parentUrl', passedUrl, {path: '/'});
            parentUrl = passedUrl;
        }
    }

    if (parentUrl === "") {
        //make the assumption for not cross-domain, will have no effect if cross domain (message wont be
        //received)
        parentUrl = window.location;
        parentUrl = decodeURIComponent(parentUrl);
    }

    var height = jQuery("body").outerHeight();
    jQuery("body").attr("style", "overflow-x: auto; padding-right: 20px;");
    if (parentUrl && !isNaN(height) && height > 0) {
        jQuery.postMessage({ if_height: height}, parentUrl, parent);
        bodyHeight = height;
    }
}

/**
 * Get the current context
 *
 * @returns the jQuery context that can be used to perform actions that must be global to the entire page
 * ie, showing lightBoxes and growls etc
 */
function getContext() {
    if (usePortalForContext()) {
        return top.jQuery;
    }
    else {
        return jQuery.noConflict();
    }
}

/**
 * Check if portal should be used for context
 *
 * <p>
 * To avoid cross server script errors the local context is used in case the portal window is on a different host.
 * </p>
 *
 * @return true if portal is used for context, false otherwise
 */
function usePortalForContext() {
    var usePortal = false;

    // for iframe use the outer window's context unless the outer window is hosted on a different domain.
    try {
        // For security reasons the browsers will not allow cross server scripts and
        // throw an exception instead.
        // Note that bad browsers (e.g. google chrome) will not catch the exception
        usePortal = (top != self) && (top.location.host == location.host);
    }
    catch (e) {
        usePortal = false;
    }

    return usePortal;
}

/**
 * Indicates whether the given window is the portal container
 *
 * @param window - window to test
 */
function isPortalContainer(window) {
    return window.jQuery('#' + kradVariables.PORTAL_IFRAME_ID).length;
}

/**
 * Attempts to find an element based on the given selector taking into consideration the portal.
 *
 * @param selector jquery selector to find element
 * @returns jquery object for found element, or null if an element was not found
 */
function findElement(selector) {
    var jqElement;

    if (parent.jQuery('iframe[id*=easyXDM_]').length > 0) {
        // portal and content on same domain
        jqElement = top.jQuery('iframe[id*=easyXDM_]').contents().find('#' + kradVariables.PORTAL_IFRAME_ID).contents().find(selector);
    } else if (parent.parent.jQuery('#' + kradVariables.PORTAL_IFRAME_ID).length > 0) {
        // portal and content on different domain
        jqElement = parent.parent.jQuery('#' + kradVariables.PORTAL_IFRAME_ID).contents().find(selector);
    } else {
        jqElement = top.jq(selector);
    }

    return jqElement;
}

/**
 * Finds element(s) within the given context that have the data role attribute equal to the given role.
 *
 * @param role data attribute role value to find
 * @param $context (optional) content to find elements, if empty the entire document will be used
 * @returns {*} found elements, if any
 */
function findByDataRole(role, $context) {
    $context = $context || jQuery(document);

    return $context.find("[" + kradVariables.ATTRIBUTES.DATA_ROLE + "='" + role + "']");
}

/**
 * Sets a configuration parameter that will be accessible with script
 *
 * <p>
 * Configuration parameters are sent from the server and represent non-component
 * state, such as location of images
 * </p>
 *
 * @param paramName - name of the configuration parameter
 * @param paramValue - value for the configuration parameter
 */
function setConfigParam(paramName, paramValue) {
    var configParams = jQuery(document).data("ConfigParameters");
    if (!configParams) {
        configParams = new Object();
        jQuery(document).data("ConfigParameters", configParams);
    }
    configParams[paramName] = paramValue;
}

/**
 * Called when a view is rendered to initialize the state of components
 * that need to be accessed client side
 *
 * @param viewState - map (object) containing the view state
 */
function initializeViewState(viewState) {
    jQuery(document).data(kradVariables.VIEW_STATE, viewState);
}

/**
 * Updates the current view state with the given map of view state
 *
 * <p>
 * The given state will be merged with the current. Matching keys for simple properties will be overridden
 * if contained in the second map, in all cases except when the value is another map, in which case the map
 * value will be merged
 * </p>
 *
 * @param viewState - view state to merge in
 */
function updateViewState(viewState) {
    var currentViewState = jQuery(document).data(kradVariables.VIEW_STATE);
    if (currentViewState) {
        jQuery.extend(currentViewState, viewState);
    }
    else {
        jQuery(document).data(kradVariables.VIEW_STATE, viewState);
    }
}

/**
 * Sets a key/value pair in the view state
 *
 * @param key - name to reference state by
 * @param value - value for the state
 */
function setViewState(key, value) {
    var viewState = jQuery(document).data(kradVariables.VIEW_STATE);
    if (!viewState) {
        viewState = new Object();
        jQuery(document).data(kradVariables.VIEW_STATE, viewState);
    }
    viewState[key] = value;
}

/**
 * Retrieves the current value for a given key in the view state, if
 * not found empty string is returned
 *
 * @param key - name of the variable in view state to return
 */
function getViewState(key) {
    var viewState = jQuery(document).data(kradVariables.VIEW_STATE);
    if (viewState && viewState.hasOwnProperty(key)) {
        return viewState[key];
    }
    return "";
}

/**
 * Adds the given key/value pair to the state maintained for the give component
 *
 * @param componentId - id for the component the state should be associated with
 * @param key - name to reference the state by
 * @param value - value for the state
 */
function setComponentState(componentId, key, value) {
    var componentState = getViewState(componentId);
    if (!componentState) {
        componentState = new Object();
        setViewState(componentId, componentState);
    }
    componentState[key] = value;
}

/**
 * Retrieves the state value for the given key and given component
 *
 * @param componentId - id for the component the key is associated with
 * @param key - name of the state to retrieve
 */
function getComponentState(componentId, key) {
    var componentState = getViewState(componentId);
    if (componentState && componentState.hasOwnProperty(key)) {
        return componentState[key];
    }
    return "";
}

/**
 * Returns the current view state as a JSON string for posting
 */
function getSerializedViewState() {
    var jsonViewState = "";

    var viewState = jQuery(document).data(kradVariables.VIEW_STATE);
    if (!jQuery.isEmptyObject(viewState)) {
        var jsonViewState = jQuery.toJSON(viewState);

        // change double quotes to single because escaping causes problems on URL
        jsonViewState = jsonViewState.replace(/"/g, "'");
    }

    return jsonViewState;
}

// gets the the label for field with the corresponding id
function getLabel(id) {
    var label = jQuery("[data-label_for='" + id + "']");
    if (label) {
        return label.text();
    }
    else {
        return "";
    }
}
/**
 * runs hidden scripts. The hidden scripts are contained in hidden input elements
 *
 * @param id - the tag id or selector expression to use. If empty, run all hidden scripts
 * @param isSelector - when present and true, the value given for the id used as jquery selector expression
 * @param skipValidationBubbling - set to true to skip processing each field - ONLY is ever true for pages since
 * they handle this during the setupPage call
 */
function runHiddenScripts(id, isSelector, skipValidationBubbling) {
    profile(true, "run-scripts:" + id);
    if (id) {

        var selector = "#" + id;
        if (isSelector) {
            selector = id;
        }

        evaluateScripts(selector);

        if (!isSelector) {
            runScriptsForId(id);
        }

        //Interpret new server message state for refreshed InputFields and write them out
        if (!skipValidationBubbling) {
            pageValidationPhase = true;

            jQuery(selector).find("div[data-role='InputField']").andSelf().filter("div[data-role='InputField']").each(function () {
                var id = jQuery(this).attr('id');
                var field = jQuery("#" + id);
                var data = getValidationData(field);
                var parent = field.data("parent");
                writeMessagesAtField(id);
                handleMessagesAtGroup(parent, id, data, true);
            });

            writeMessagesForPage();
            pageValidationPhase = false;
        }
    }
    else {
        evaluateScripts();
    }

    profile(false, "run-scripts:" + id);
}

/**
 * Evaluate scripts for the selection, if defined.  If no selector is defined, evaluate hidden scripts
 * for the entire document
 *
 * @param selector optional jQuery selector string to select the object to run scripts for
 */
function evaluateScripts(selector) {
    if (selector) {
        //run dataScript first always
        jQuery(selector).find("input[data-role='dataScript']").each(function () {
            evalHiddenScript(jQuery(this));
        });

        jQuery(selector).find("input[name='script']").each(function () {
            evalHiddenScript(jQuery(this));
        });
    }
    else {
        //run scripts for entire document if no selector defined
        //run dataScript first always
        jQuery("input[data-role='dataScript']").each(function () {
            evalHiddenScript(jQuery(this));
        });

        jQuery("input[name='script']").each(function () {
            evalHiddenScript(jQuery(this));
        });
    }
}

/**
 * runs hidden scripts. The hidden scripts are contained in hidden input elements
 *
 * <p>Finds all hidden inputs with the attribute data-role having a value of 'dataScript' or 'script'
 * then runs the script in the value attribute if the input's data-for attribute value is equal to the id provided</p>
 *
 * @param id - the tag id to use
 */
function runScriptsForId(id) {
    if (id) {
        jQuery("input[data-for='" + id + "']").filter("[data-role='dataScript']").each(function () {
            evalHiddenScript(jQuery(this));
        });

        jQuery("input[data-for='" + id + "']").filter("[data-role='script']").each(function () {
            evalHiddenScript(jQuery(this));
        });
    }
}

/**
 * do the actual work of evaluating a script once it has been located
 *
 * @param jqueryObj - a jquery object representing a hidden input element with a script in its value attribute
 */
function evalHiddenScript(jqueryObj) {
    if (jqueryObj.attr("name") === undefined || (jqueryObj.closest("[data-open]").length &&
            jqueryObj.closest("[data-open]").attr("data-open") === "false")) {
        return;
    }

    jqueryObj.removeAttr("name");
    jqueryObj.attr("script", "first_run");

    var js = jqueryObj.val();
    try {
        eval(js);
    } catch (err) {
        if (err instanceof ReferenceError) {
            throw new ReferenceError(err.message + " -> offending code: " + js, err.fileName, err.lineNumber);
        } else {
            throw err;
        }
    }

    // cleanup script for non-dev modes
    if (scriptCleanup) {
        jqueryObj.remove();
    }
}

/**
 * Writes a hidden for property 'methodToCall' set to the given value. This is
 * useful for submitting forms with JavaScript where the methodToCall needs to
 * be set before the form is submitted.
 *
 * @param methodToCall -
 *          the value that should be set for the methodToCall parameter
 */
function setMethodToCall(methodToCall) {
    jQuery("<input type='hidden' name='methodToCall' value='" + methodToCall + "'/>").appendTo(jQuery("#" + kradVariables.FORM_COMPLETE_ID));
}

/**
 * Writes a property name/value pair as a hidden input field on the form.
 *
 * <p>Called to dynamically set request parameters based on a chosen action. Assumes
 * existence of a div named 'formComplete' where the hidden inputs will be
 * inserted</p>
 *
 * @param propertyName name for the input field to write
 * @param propertyValue value for the input field to write
 */
function writeHiddenToForm(propertyName, propertyValue) {
    jQuery('input[name="' + escapeName(propertyName) + '"]').remove();

    if (propertyValue && typeof propertyValue === 'string') {
        propertyValue = propertyValue.replace(/"/g, "\\\"");
    }

    jQuery("<input type='hidden' name='" + propertyName + "'" + ' value="' + propertyValue + '"/>').appendTo(jQuery("#" + kradVariables.FORM_COMPLETE_ID));
}

/**
 * In some cases when an action is invoked, data that should be passed with
 * the request is written to the form as hiddens using the #writeHiddenToForm method. If
 * there are errors in the script that prevent the action from completing, this method can
 * be called to clear the hiddens
 */
function clearHiddens() {
    jQuery("#" + kradVariables.FORM_COMPLETE_ID).html("");
}

/**
 * Retrieves the actual value from the input widget specified by name
 */
function coerceValue(name) {
    var value = "";
    var nameSelect = "[name='" + escapeName(name) + "']";

    var fancyBoxWrapper = jQuery(".fancybox-wrap");
    var control;
    // Attempt to get from fancybox first, if it exists
    if (fancyBoxWrapper.length) {
        control = jQuery(nameSelect, fancyBoxWrapper);
    }

    // If no fancybox or control not in fancybox, get from document
    if (control == null || control.length == 0) {
        control = jQuery(nameSelect, document);
    }

    if (control.is(":checkbox") && control.length == 1) {
        value = control.filter(":checked").val();
    }
    else if (control.is(":checkbox") && control.length > 1) {
        value = [];
        control.filter(":checked").each(function () {
            value.push(jQuery(this).val());
        });
    }
    else if (control.is(":radio")) {
        value = control.filter(":checked").val();
    }
    else if (control.length) {
        if (control.hasClass("watermark")) {
            jQuery.watermark.hide(control, parent);
            value = control.val();
            jQuery.watermark.show(control, parent);
        }
        else {
            value = control.val();
        }
    }

    if (value == null) {
        value = "";
        return value;
    }

    // boolean matching
    if (value && !jQuery.isArray(value)) {
        if (value.toUpperCase() == "TRUE") {
            value = true;
        } else if (value.toUpperCase() == "FALSE") {
            value = false;
        }
    }

    return value;
}

/**
 * Sets a value on the control with the given name attribute
 *
 * @param name - name on control to set value for
 * @param value - value to set
 */
function setValue(name, value) {
    var nameSelect = "[name='" + escapeName(name) + "']";
    var control = jQuery(nameSelect);

    if (value != undefined && !jQuery.isArray(value)
            && (control.is(":radio") || control.is("select") || control.is(":checkbox"))) {
        var valueArray = [value];
        control.val(valueArray);
    }
    else {
        control.val(value);
    }
}

//returns true if the field with name of name1 occurs before field with name2
function occursBefore(name1, name2) {
    var field1 = jQuery("[name='" + escapeName(name1) + "']");
    var field2 = jQuery("[name='" + escapeName(name2) + "']");

    field1.addClass("prereqcheck");
    field2.addClass("prereqcheck");

    var fields = jQuery(".prereqcheck");

    field1.removeClass("prereqcheck");
    field2.removeClass("prereqcheck");

    if (fields.index(field1) < fields.index(field2)) {
        return true;
    }
    else {
        return false;
    }
}

/**
 * Gets the actual attribute id to use element manipulation related to this attribute.
 *
 * @param elementId
 * @param elementType
 */
function getAttributeId(elementId) {
    var id = elementId;
    if (!id) {
        return '';
    }
    id = elementId.replace(/_control\S*/, "");
    return id;
}

// The following javascript is intended to resize the route log iframe
// to stay at an appropriate height based on the size of the documents
// contents contained in the iframe.
// NOTE: this will only work when the domain serving the content of kuali
// is the same as the domain serving the content of workflow.
var routeLogResizeTimer = "";
var currentHeight = 500;
var safari = navigator.userAgent.toLowerCase().indexOf('safari');

function setRouteLogIframeDimensions() {
    var routeLogFrame = document.getElementById("routeLogIFrame");
    var routeLogFrame = document.getElementById("routeLogIFrame");
    var routeLogFrameWin = window.frames["routeLogIFrame"];
    var frameDocHeight = 0;
    try {
        frameDocHeight = routeLogFrameWin.document.documentElement.scrollHeight;
    } catch (e) {
        // unable to set due to cross-domain scripting
        frameDocHeight = 0;
    }

    if (frameDocHeight > 0) {
        if (routeLogFrame && routeLogFrameWin) {

            if ((Math.abs(frameDocHeight - currentHeight)) > 30) {
                if (safari > -1) {
                    if ((Math.abs(frameDocHeight - currentHeight)) > 59) {
                        routeLogFrame.style.height = (frameDocHeight + 30) + "px";
                        currentHeight = frameDocHeight;
                    }
                } else {
                    routeLogFrame.style.height = (frameDocHeight + 30) + "px";
                    currentHeight = frameDocHeight;
                }
            }
        }
    }

    if (routeLogResizeTimer == "") {
        routeLogResizeTimer = setInterval("resizeTheRouteLogFrame()", 300);
    }
}

function resizeTheRouteLogFrame() {
    setRouteLogIframeDimensions();
}

/**
 * Open new browser window for the specified help url
 *
 * The help window is positioned in the center of the screen and resized to 1/4th of the screen.
 *
 * Browsers don't allow one to modify windows of other domains.  Thus to ensure that only one help window exist
 * and to guarantee it's placement, size and that the window is in the foreground the following process is performed:
 *   1) open the help window - this ensures that we get a window handle to any existing help window
 *   2) close the help window
 *   3) open a new help window with the correct placement, size and url
 *
 * @param url - url of the help window content
 */
function openHelpWindow(url) {
    var windowWidth = screen.availWidth / 2;
    var windowHeight = screen.availHeight / 2;
    var windowPositionY = parseInt((screen.availWidth / 2) - (windowWidth / 2));
    var windowPositionX = parseInt((screen.availHeight / 2) - (windowHeight / 2));

    var windowUrl = url;
    var windowName = 'HelpWindow';
    var windowOptions = 'width=' + windowWidth + ',height=' + windowHeight + ',top=' + windowPositionX + ',left=' + windowPositionY + ',scrollbars=yes,resizable=yes';
    var myWindow;

    /* chrome will not allow a open,close,open */
    var is_chrome = navigator.userAgent.toLowerCase().indexOf('chrome') > -1;
    if (!is_chrome) {
        myWindow = window.open('', windowName);
        myWindow.close();
    }

    myWindow = window.open(windowUrl, windowName, windowOptions);
}

/**
 * Uppercases the current value for the control with the given id
 *
 * @param controlId - id for the control whose value should be uppercased
 */
function uppercaseValue(controlId) {
    jQuery("#" + controlId).css('text-transform', 'uppercase');
    jQuery("#" + controlId).change(function () {
        this.value = this.value.toUpperCase();
    });
}

/**
 * Profiling helper method will print out profile info in firefox console
 *
 * @param start true to start profiling, false to stop profiling
 * @param testingText text to be printed with this profile
 */
function profile(start, testingText) {
    if (profilingOn && window.console && window.console.time && window.console.profile) {
        if (start) {
            console.time(testingText);
            console.profile(testingText);
        }
        else {
            console.profileEnd();
            console.timeEnd(testingText);
        }

    }
}

/**
 * Timing method for profiling use - will print out in console
 *
 * @param start true to start timing, false to stop timing
 * @param testingText text to be printed out with time results
 */
function time(start, testingText) {
    if (profilingOn && window.console && window.console.time) {
        if (start) {
            console.time(testingText);
        }
        else {
            console.timeEnd(testingText);
        }
    }
}

/**
 * Adds a class to the collection item related to the delete action
 *
 * @param deleteButton - the delete button that this event was triggered from
 * @param highlightItemClass - the class to add to the item that should be highlighted
 */
function deleteLineMouseOver(deleteButton, highlightItemClass) {
    var innerLayout = jQuery(deleteButton).parents('.' + kradVariables.TABLE_COLLECTION_LAYOUT_CLASS
            + ', .' + kradVariables.STACKED_COLLECTION_LAYOUT_CLASS).first().attr('class');
    if (innerLayout) {
        if (innerLayout.indexOf(kradVariables.TABLE_COLLECTION_LAYOUT_CLASS) >= 0) {
            jQuery(deleteButton).closest('tr').addClass(highlightItemClass);
        } else {
            jQuery(deleteButton).closest('.' + kradVariables.COLLECTION_ITEM_CLASS).addClass(highlightItemClass);
        }
    }
}

/**
 * Removes a class from the collection item related to the delete action
 *
 * @param deleteButton - the delete button that this event was triggered from
 * @param highlightItemClass - the class remove from the collection item
 */
function deleteLineMouseOut(deleteButton, highlightItemClass) {
    var innerLayout = jQuery(deleteButton).parents('.' + kradVariables.TABLE_COLLECTION_LAYOUT_CLASS
            + ', .' + kradVariables.STACKED_COLLECTION_LAYOUT_CLASS).first().attr('class');
    if (innerLayout) {
        if (innerLayout.indexOf(kradVariables.TABLE_COLLECTION_LAYOUT_CLASS) >= 0) {
            jQuery(deleteButton).closest('tr').removeClass(highlightItemClass);
        } else {
            jQuery(deleteButton).closest('.' + kradVariables.COLLECTION_ITEM_CLASS).removeClass(highlightItemClass);
        }
    }
}

/**
 * Adds a class to the collection group related to the add action
 *
 * @param addButton - the add button that this event was triggered from
 * @param highlightItemClass - the class to add to the group that should be highlighted
 */
function addLineMouseOver(addButton, highlightItemClass) {
    var innerLayout = jQuery(addButton).parents('.' + kradVariables.TABLE_COLLECTION_LAYOUT_CLASS
            + ', .' + kradVariables.STACKED_COLLECTION_LAYOUT_CLASS).first().attr('class');
    if (innerLayout) {
        if (innerLayout.indexOf(kradVariables.TABLE_COLLECTION_LAYOUT_CLASS) >= 0) {
            jQuery(addButton).parent().find('table').addClass(highlightItemClass);
        } else {
            jQuery(addButton).parent().find('.' + kradVariables.STACKED_COLLECTION_LAYOUT_CLASS).addClass(highlightItemClass).children().addClass(highlightItemClass);
        }
    }
}

/**
 * Removes a class from the collection group related to the add action
 *
 * @param addButton - the add button that this event was triggered from
 * @param highlightItemClass - the class remove from the collection group
 */
function addLineMouseOut(addButton, highlightItemClass) {
    var innerLayout = jQuery(addButton).parents('.' + kradVariables.TABLE_COLLECTION_LAYOUT_CLASS
            + ', .' + kradVariables.STACKED_COLLECTION_LAYOUT_CLASS).first().attr('class');
    if (innerLayout) {
        if (innerLayout.indexOf(kradVariables.TABLE_COLLECTION_LAYOUT_CLASS) >= 0) {
            jQuery(addButton).parent().find('table').removeClass(highlightItemClass);
        } else {
            jQuery(addButton).parent().find('.' + kradVariables.STACKED_COLLECTION_LAYOUT_CLASS).removeClass(highlightItemClass).children().removeClass(highlightItemClass);
        }
    }
}

/**
 * Enables and disables the save action
 *
 * @param inputField
 * @param highlightItemClass - the class to add to the collection item
 */
function collectionLineChanged(inputField, highlightItemClass) {
    // This is not very good for performance but because dirty_form gets binded after this event so we need to trigger
    // the dirty_form check before checking for the dirty fields
    jQuery(inputField).triggerHandler('change');

    // Get the innerlayout to see if we are dealing with table or stack group
    var innerLayout = jQuery(inputField).parents('.' + kradVariables.TABLE_COLLECTION_LAYOUT_CLASS
            + ', .' + kradVariables.STACKED_COLLECTION_LAYOUT_CLASS).first().attr('class');

    if (innerLayout && innerLayout.indexOf(kradVariables.TABLE_COLLECTION_LAYOUT_CLASS) >= 0) {
        var row = jQuery(inputField).closest('tr');
        var enabled = row.find('.dirty').length > 0;
        var saveButton = row.find('.' + kradVariables.SAVE_LINE_ACTION_CLASS);

        if (enabled) {
            saveButton.removeClass('disabled');
            saveButton.removeAttr('disabled');
        } else {
            saveButton.addClass('disabled');
            saveButton.attr('disabled', 'disabled');
        }

    } else {
        var itemGroup = jQuery(inputField).closest('.' + kradVariables.COLLECTION_ITEM_CLASS);
        var enabled = itemGroup.find('.dirty').length > 0;
        var saveButton = itemGroup.find('.' + kradVariables.SAVE_LINE_ACTION_CLASS);

        if (enabled) {
            saveButton.removeClass('disabled');
            saveButton.removeAttr('disabled');
        } else {
            saveButton.addClass('disabled');
{'fraction_non_alphanumeric': 0.11556552451415276, 'fraction_numerical': 0.0024940995296362633, 'mean_word_length': 2.939725666051174, 'pattern_counts': {'":': 16, '<': 35, '<?xml version=': 0, '>': 67, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2405193', 'n_tokens_mistral': 16599, 'n_tokens_neox': 16223, 'n_words': 5254}