<commit_before>/*
 * Licensed to the Sakai Foundation (SF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The SF licenses this file
 * to you under the Apache 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.apache.org/licenses/LICENSE-2.0
 *
 * 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 sakai = sakai || {};

/**
 * @name sakai.basiclti
 *
 * @class basiclti
 *
 * @description
 * Basiclti widget
 *
 * @version 0.0.1
 * @param {String} tuid Unique id of the widget
 * @param {Boolean} showSettings Show the settings of the widget or not
 */
sakai.sakai2tools = function(tuid, showSettings){

    /////////////////////////////
    // Configuration variables //
    /////////////////////////////

    var rootel = $("#" + tuid);
    var json = false;
    var isAdvancedSettingsVisible = false;

    // Default values
    var defaultWidth = 100;
    var defaultWidthUnit = "%";
    var defaultHeight = 200;

    // Links and labels
    var basiclti = "#basiclti";
    var basicltiSettings = basiclti + "_settings";
    var basicltiSettingsAdvanced = basicltiSettings + "_advanced";
    var basicltiSettingsAdvancedDown = basicltiSettingsAdvanced + "_down";
    var basicltiSettingsAdvancedToggleSettings = basicltiSettingsAdvanced + "_toggle_settings";
    var basicltiSettingsAdvancedUp = basicltiSettingsAdvanced + "_up";
    var basicltiSettingsBorders = basicltiSettings + "_borders";
    var basicltiSettingsCancel = basicltiSettings + "_cancel";
    var basicltiSettingsColorContainer = basicltiSettings + "_color_container";
    var basicltiSettingsHeight = basicltiSettings + "_frame_height";
    var basicltiSettingsInsert = basicltiSettings + "_insert";
    var basicltiSettingsPreview = basicltiSettings + "_preview";
    var basicltiSettingsPreviewId = tuid + "_frame";
    var basicltiSettingsPreviewFrame = "#" + basicltiSettingsPreviewId;
    var basicltiSettingsWidth = basicltiSettings + "_width";
    var basicltiSettingsVirtualToolId = basicltiSettings + "_lti_virtual_tool_id";

    // Containers
    var basicltiMainContainer = basiclti + "_main_container";

    // Classes
    var basicltiSettingsWidthUnitClass = ".basiclti_settings_width_unit";
    var basicltiSettingsWidthUnitSelectedClass = "basiclti_settings_width_unit_selected";

    // Templates
    var $basicltiSettingsColorContainerTemplate = $("#basiclti_settings_color_container_template", rootel);
    var $basicltiSettingsTemplate = $("#basiclti_settings_template", rootel);
    var $basicltiSettingsPreviewTemplate = $("#basiclti_settings_preview_template", rootel);

    // see: http://www.ietf.org/rfc/rfc2396.txt Appendix B
    var urlRegExp = new RegExp("^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?");

    ///////////////////////
    // Utility functions //
    ///////////////////////

    /**
     * Check if the value is a decimal or not
     * @param {Object} value Value that needs to be checked
     * @return {Boolean}
     *     true: is a decimal
     *     false: is not a decimal
     */
    var isDecimal = function(value){
        return (/^\d+$/).test(value);
    };

    /**
     * Check if the input url is in fact an url or not
     * @param {String} url Url that needs to be tested
     * @return {Boolean}
     *     true: is an url
     *     false: is not an url
     */
    var isUrl = function(url){
        var matches = urlRegExp.exec(url);
        // e.g. if("http:" && "localhost")
        if(matches[1] && matches[4]) {
            return true;
        } else {
            return false;
        }
    };

    /**
     * Check to see if both URLs are in the same origin. See: http://en.wikipedia.org/wiki/Same_origin_policy.
     * @param {String} url1
     * @param {String} url2
     * @return {Boolean}
     *     true: in the same origin policy
     *     false: NOT in the same origin policy
     */
    var isSameOriginPolicy = function(url1, url2){
        if(url1 == url2) {
            return true;
        }
        // console.log(isUrl(url1) + ": " + url1 + "=" + urlRegExp.exec(url1)[4]);
        // console.log(isUrl(url2) + ": " + url2 + "=" + urlRegExp.exec(url2)[4]);
        // i.e. protocol, domain (and optional port numbers) must match
        if((urlRegExp.exec(url1)[2] == urlRegExp.exec(url2)[2]) &&
           (urlRegExp.exec(url1)[4] == urlRegExp.exec(url2)[4])){
            return true;
        } else {
            return false;
        }
    }

    /**
     * Called when the data has been saved to the JCR.
     */
    var savedDataToJCR = function(data, textStatus, XMLHttpRequest){
        sakai.api.Widgets.Container.informFinish(tuid);
    };

    //////////////////////
    // Render functions //
    //////////////////////

    /**
     * Render the iframe for the widget in settings mode
     * @param {Boolean} complete Render the preview completely or only adjust values
     */
    var renderIframeSettings = function(complete){
        if (complete) {
            json.launchDataUrl = sakai.config.URL.SDATA_FETCH_URL.replace(/__PLACEMENT__/, sakai.site.currentsite.id + "/_widgets").replace(/__TUID__/, tuid).replace(/__NAME__/, "basiclti") + '.launch.html';
            $(basicltiSettingsPreview).html($.TemplateRenderer($basicltiSettingsPreviewTemplate, json));
        }
        else {
            $(basicltiSettingsPreviewFrame).attr("style", "border: " + json.border_size + "px #" + json.border_color + " solid");
        }
    };

    /**
     * Render the iframe for the widget
     */
    var renderIframe = function(){
        if (json) {
            json.launchDataUrl = sakai.config.URL.SDATA_FETCH_URL.replace(/__PLACEMENT__/, sakai.site.currentsite.id + "/_widgets").replace(/__TUID__/, tuid).replace(/__NAME__/, "basiclti") + '.launch.html';
            json.tuidFrame = basicltiSettingsPreviewId;
            $(basicltiMainContainer, rootel).html($.TemplateRenderer($basicltiSettingsPreviewTemplate, json));
            // SAKIII-542 Basic LTI no longer renders IFRAME content (workaround)
            $("#" + json.tuidFrame).attr("src", json.launchDataUrl);
            // resize the iframe to match inner body height if in the same origin (i.e. same protocol/domain/port)
            if(isSameOriginPolicy(window.location.href, json.ltiurl)) {
                $(basicltiSettingsPreviewFrame).load(function() {
                    $(this).height($(this).contents().find("body").height() + 15); // add 10px for IE and 5px more for Gradebook weirdness
                });
            }

            // SAKIII-314 We need to show the container, otherwise the second item won't be shown.
            $(basicltiMainContainer, rootel).show();
        }
    };

    /**
     * Render the html of the basicltisettings
     */
    var renderRemoteContentSettings = function(){
        if (json) {
            $(basicltiSettings).html($.TemplateRenderer($basicltiSettingsTemplate, json));
            $(basicltiSettingsVirtualToolId).val(json.lti_virtual_tool_id);
        }
    };

    /**
     * Render the color container
     */
    var renderColorContainer = function(){
        if (json) {
            $(basicltiSettingsColorContainer).html($.TemplateRenderer($basicltiSettingsColorContainerTemplate, json));
        }
    };

    //////////////////////
    // Global functions //
    //////////////////////

    /**
     * Display the iframe in normal mode
     * @param {Object} parameters JSON object that contains the necessary information for the iframe
     */
    var displayRemoteContent = function(parameters){
        // default to some reasonable vaules if the settings node does not have them (maybe a legacy node)
        if (parameters.border_size == null) {
            parameters.border_size = 0;
        }
        if (parameters.border_color == null) {
            parameters.border_color = "cccccc";
        }
        if (parameters.width == null) {
            parameters.width = defaultWidth;
        }
        if (parameters.width_unit == null) {
            parameters.width_unit = defaultWidthUnit;
        }
        if (parameters.frame_height == null) {
            parameters.frame_height = defaultHeight;
        }
        json = parameters;
        renderIframe();
    };

    /**
     * Save the basiclti to the jcr
     */
    var saveRemoteContent = function(){
        if (json.ltiurl !== "") {
            json["lti_virtual_tool_id"] = $(basicltiSettingsVirtualToolId).val() || "";
            json[":operation"] = "basiclti";
            json["sling:resourceType"] = "sakai/basiclti";
            json.launchDataUrl = ""; // does not need to be persisted
            json.tuidFrame = ""; // does not need to be persisted
            json.defined = ""; // what the heck is this? Where does it come from?
            json._MODIFIERS = null; // trimpath garbage - probably need a more selective way of saving data
            var saveUrl = sakai.config.URL.SDATA_FETCH_URL.replace(/__PLACEMENT__/, sakai.site.currentsite.id + "/_widgets").replace(/__TUID__/, tuid).replace(/__NAME__/, "basiclti");
            $.ajax({
                type: 'POST',
                url: saveUrl,
                data: json,
                success: savedDataToJCR
            });
        }
        else {
            alert("Please specify a URL");
        }
    };

    /**
     * Change the direction (up/down) of the arrow for the advanced settings
     */
    var changeAdvancedSettingsArrow = function(){
        if (isAdvancedSettingsVisible) {
            $(basicltiSettingsAdvancedDown, rootel).hide();
            $(basicltiSettingsAdvancedUp, rootel).show();
        }
        else {
            $(basicltiSettingsAdvancedUp, rootel).hide();
            $(basicltiSettingsAdvancedDown, rootel).show();
        }
    };

    //////////////
    // Bindings //
    //////////////

    /*
     * Add binding to the color boxes
     */
    var addColorBinding = function(){
        $(".basiclti_settings_color").click(function(){
            json.border_color = $(this).attr("id").split("_")[$(this).attr("id").split("_").length - 1];
            renderIframeSettings(false);
            renderColorContainer();
            addColorBinding();
        });
    };

    /*
     * Add binding to all the elements
     */
    var addBinding = function(){

        // Change the iframe width
        $(basicltiSettingsWidth).change(function(){
            var widthValue = $(basicltiSettingsWidth).val();

            if (isDecimal(widthValue)) {
                json.width = widthValue;
            }
            renderIframeSettings(false);
        });

        // Change the iframe height
        $(basicltiSettingsHeight).change(function(){
            var heightValue = $(basicltiSettingsHeight).val();

            if (isDecimal(heightValue)) {
                json.frame_height = heightValue;
            }
            renderIframeSettings(false);
        });

        // Change the border width
        $(basicltiSettingsBorders).change(function(){
            var borderValue = $(basicltiSettingsBorders).val();
            if (isDecimal(borderValue)) {
                json.border_size = borderValue;
                renderIframeSettings(false);
            }
        });

        // Toggle the advanced view
        $(basicltiSettingsAdvancedToggleSettings).click(function(){
            $("#basiclti_settings_advanced", rootel).toggle();
            isAdvancedSettingsVisible = !isAdvancedSettingsVisible;
            changeAdvancedSettingsArrow();
        });

        // When you click on one of the width units (px or percentage)
        $(basicltiSettingsWidthUnitClass).click(function(){
            var widthUnitValue = $(this).attr("id").split("_")[$(this).attr("id").split("_").length - 1];
            if (widthUnitValue === "px") {
                json.width_unit = widthUnitValue;
            }
            else {
                json.width_unit = "%";
            }
            $(basicltiSettingsWidthUnitClass).removeClass(basicltiSettingsWidthUnitSelectedClass);
            $(this).addClass(basicltiSettingsWidthUnitSelectedClass);
            renderIframeSettings(false);
        });

        // When you push the save button..
        $(basicltiSettingsInsert).click(function(){
            saveRemoteContent();
        });

        // Cancel it
        $(basicltiSettingsCancel).click(function(){
            sakai.api.Widgets.Container.informCancel(tuid);
        });

        addColorBinding();
    };

    ///////////////////////
    // Initial functions //
    ///////////////////////

    /**
     * Function that fills in the input fields in the settings tab.
     * @param {Object} parameters A JSON object that contains the necessary information.
     * @param {Boolean} exists Does there exist a previous basiclti
     */
    var displaySettings = function(parameters, exists){
        if (exists) {
            json = parameters;
        }
        else { // use default values
            json = {
                border_size: 0,
                border_color: "cccccc",
                frame_height: defaultHeight,
                width: defaultWidth,
                width_unit: defaultWidthUnit
            };
        }
        renderRemoteContentSettings();
        //renderIframeSettings(true); // LDS disabled preview
        renderColorContainer();
        addBinding(); // Add binding to the various elements
        changeAdvancedSettingsArrow();
        $(basicltiSettings).show(); // Show the basiclti settings
    };

    /*
     * Is the widget in settings mode or not
     */
    if (showSettings) {
        $(basicltiMainContainer).hide();
        $(basicltiSettings).show();
    }
    else {
        $(basicltiSettings).hide();
        $(basicltiMainContainer).show();
    }

    /**
     * Will fetch the URL and other parameters from the JCR and according to which
     * view we are in, fill in the settings or display an iframe.
     */
    var getRemoteContent = function(){
        var settingsUrl = sakai.config.URL.SDATA_FETCH_URL.replace(/__PLACEMENT__/, sakai.site.currentsite.id + "/_widgets").replace(/__TUID__/, tuid).replace(/__NAME__/, "basiclti");
        $.ajax({
            url: settingsUrl,
            cache: false,
            success: function(data){

                if (showSettings) {
                    displaySettings(data, true); // Fill in the settings page.
                }
                else {
                    displayRemoteContent(data); // Show the frame
                }
            },
            error: function(xhr, textStatus, thrownError){
                // When the request isn't successful, it means that  there was no existing basiclti
                // so we show the basic settings.
                displaySettings(null, false);
            }
        });
    };

    getRemoteContent();
};

sakai.api.Widgets.widgetLoader.informOnLoad("sakai2tools");<commit_msg>SAKIII-546 Now that admin settings are returned in a GET, do not try to save them (it is futile).<commit_after>/*
 * Licensed to the Sakai Foundation (SF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The SF licenses this file
 * to you under the Apache 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.apache.org/licenses/LICENSE-2.0
 *
 * 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 sakai = sakai || {};

/**
 * @name sakai.basiclti
 *
 * @class basiclti
 *
 * @description
 * Basiclti widget
 *
 * @version 0.0.1
 * @param {String} tuid Unique id of the widget
 * @param {Boolean} showSettings Show the settings of the widget or not
 */
sakai.sakai2tools = function(tuid, showSettings){

    /////////////////////////////
    // Configuration variables //
    /////////////////////////////

    var rootel = $("#" + tuid);
    var json = false;
    var isAdvancedSettingsVisible = false;

    // Default values
    var defaultWidth = 100;
    var defaultWidthUnit = "%";
    var defaultHeight = 200;

    // Links and labels
    var basiclti = "#basiclti";
    var basicltiSettings = basiclti + "_settings";
    var basicltiSettingsAdvanced = basicltiSettings + "_advanced";
    var basicltiSettingsAdvancedDown = basicltiSettingsAdvanced + "_down";
    var basicltiSettingsAdvancedToggleSettings = basicltiSettingsAdvanced + "_toggle_settings";
    var basicltiSettingsAdvancedUp = basicltiSettingsAdvanced + "_up";
    var basicltiSettingsBorders = basicltiSettings + "_borders";
    var basicltiSettingsCancel = basicltiSettings + "_cancel";
    var basicltiSettingsColorContainer = basicltiSettings + "_color_container";
    var basicltiSettingsHeight = basicltiSettings + "_frame_height";
    var basicltiSettingsInsert = basicltiSettings + "_insert";
    var basicltiSettingsPreview = basicltiSettings + "_preview";
    var basicltiSettingsPreviewId = tuid + "_frame";
    var basicltiSettingsPreviewFrame = "#" + basicltiSettingsPreviewId;
    var basicltiSettingsWidth = basicltiSettings + "_width";
    var basicltiSettingsVirtualToolId = basicltiSettings + "_lti_virtual_tool_id";

    // Containers
    var basicltiMainContainer = basiclti + "_main_container";

    // Classes
    var basicltiSettingsWidthUnitClass = ".basiclti_settings_width_unit";
    var basicltiSettingsWidthUnitSelectedClass = "basiclti_settings_width_unit_selected";

    // Templates
    var $basicltiSettingsColorContainerTemplate = $("#basiclti_settings_color_container_template", rootel);
    var $basicltiSettingsTemplate = $("#basiclti_settings_template", rootel);
    var $basicltiSettingsPreviewTemplate = $("#basiclti_settings_preview_template", rootel);

    // see: http://www.ietf.org/rfc/rfc2396.txt Appendix B
    var urlRegExp = new RegExp("^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?");

    ///////////////////////
    // Utility functions //
    ///////////////////////

    /**
     * Check if the value is a decimal or not
     * @param {Object} value Value that needs to be checked
     * @return {Boolean}
     *     true: is a decimal
     *     false: is not a decimal
     */
    var isDecimal = function(value){
        return (/^\d+$/).test(value);
    };

    /**
     * Check if the input url is in fact an url or not
     * @param {String} url Url that needs to be tested
     * @return {Boolean}
     *     true: is an url
     *     false: is not an url
     */
    var isUrl = function(url){
        var matches = urlRegExp.exec(url);
        // e.g. if("http:" && "localhost")
        if(matches[1] && matches[4]) {
            return true;
        } else {
            return false;
        }
    };

    /**
     * Check to see if both URLs are in the same origin. See: http://en.wikipedia.org/wiki/Same_origin_policy.
     * @param {String} url1
     * @param {String} url2
     * @return {Boolean}
     *     true: in the same origin policy
     *     false: NOT in the same origin policy
     */
    var isSameOriginPolicy = function(url1, url2){
        if(url1 == url2) {
            return true;
        }
        // console.log(isUrl(url1) + ": " + url1 + "=" + urlRegExp.exec(url1)[4]);
        // console.log(isUrl(url2) + ": " + url2 + "=" + urlRegExp.exec(url2)[4]);
        // i.e. protocol, domain (and optional port numbers) must match
        if((urlRegExp.exec(url1)[2] == urlRegExp.exec(url2)[2]) &&
           (urlRegExp.exec(url1)[4] == urlRegExp.exec(url2)[4])){
            return true;
        } else {
            return false;
        }
    }

    /**
     * Called when the data has been saved to the JCR.
     */
    var savedDataToJCR = function(data, textStatus, XMLHttpRequest){
        sakai.api.Widgets.Container.informFinish(tuid);
    };

    //////////////////////
    // Render functions //
    //////////////////////

    /**
     * Render the iframe for the widget in settings mode
     * @param {Boolean} complete Render the preview completely or only adjust values
     */
    var renderIframeSettings = function(complete){
        if (complete) {
            json.launchDataUrl = sakai.config.URL.SDATA_FETCH_URL.replace(/__PLACEMENT__/, sakai.site.currentsite.id + "/_widgets").replace(/__TUID__/, tuid).replace(/__NAME__/, "basiclti") + '.launch.html';
            $(basicltiSettingsPreview).html($.TemplateRenderer($basicltiSettingsPreviewTemplate, json));
        }
        else {
            $(basicltiSettingsPreviewFrame).attr("style", "border: " + json.border_size + "px #" + json.border_color + " solid");
        }
    };

    /**
     * Render the iframe for the widget
     */
    var renderIframe = function(){
        if (json) {
            json.launchDataUrl = sakai.config.URL.SDATA_FETCH_URL.replace(/__PLACEMENT__/, sakai.site.currentsite.id + "/_widgets").replace(/__TUID__/, tuid).replace(/__NAME__/, "basiclti") + '.launch.html';
            json.tuidFrame = basicltiSettingsPreviewId;
            $(basicltiMainContainer, rootel).html($.TemplateRenderer($basicltiSettingsPreviewTemplate, json));
            // SAKIII-542 Basic LTI no longer renders IFRAME content (workaround)
            $("#" + json.tuidFrame).attr("src", json.launchDataUrl);
            // resize the iframe to match inner body height if in the same origin (i.e. same protocol/domain/port)
            if(isSameOriginPolicy(window.location.href, json.ltiurl)) {
                $(basicltiSettingsPreviewFrame).load(function() {
                    $(this).height($(this).contents().find("body").height() + 15); // add 10px for IE and 5px more for Gradebook weirdness
                });
            }

            // SAKIII-314 We need to show the container, otherwise the second item won't be shown.
            $(basicltiMainContainer, rootel).show();
        }
    };

    /**
     * Render the html of the basicltisettings
     */
    var renderRemoteContentSettings = function(){
        if (json) {
            $(basicltiSettings).html($.TemplateRenderer($basicltiSettingsTemplate, json));
            $(basicltiSettingsVirtualToolId).val(json.lti_virtual_tool_id);
        }
    };

    /**
     * Render the color container
     */
    var renderColorContainer = function(){
        if (json) {
            $(basicltiSettingsColorContainer).html($.TemplateRenderer($basicltiSettingsColorContainerTemplate, json));
        }
    };

    //////////////////////
    // Global functions //
    //////////////////////

    /**
     * Display the iframe in normal mode
     * @param {Object} parameters JSON object that contains the necessary information for the iframe
     */
    var displayRemoteContent = function(parameters){
        // default to some reasonable vaules if the settings node does not have them (maybe a legacy node)
        if (parameters.border_size == null) {
            parameters.border_size = 0;
        }
        if (parameters.border_color == null) {
            parameters.border_color = "cccccc";
        }
        if (parameters.width == null) {
            parameters.width = defaultWidth;
        }
        if (parameters.width_unit == null) {
            parameters.width_unit = defaultWidthUnit;
        }
        if (parameters.frame_height == null) {
            parameters.frame_height = defaultHeight;
        }
        json = parameters;
        renderIframe();
    };

    /**
     * Save the basiclti to the jcr
     */
    var saveRemoteContent = function(){
        if (json.ltiurl !== "") {
            json["lti_virtual_tool_id"] = $(basicltiSettingsVirtualToolId).val() || "";
            json[":operation"] = "basiclti";
            json["sling:resourceType"] = "sakai/basiclti";
            json.launchDataUrl = ""; // does not need to be persisted
            json.tuidFrame = ""; // does not need to be persisted
            json.ltiurl = ""; // does not need to be persisted
            json["ltiurl_lock@TypeHint"] = "Boolean";
            json.ltiurl_lock = null; // does not need to be persisted
            json.ltisecret = ""; // does not need to be persisted
            json["ltisecret_lock@TypeHint"] = "Boolean";
            json.ltisecret_lock = null; // does not need to be persisted
            json.ltikey = "" // does not need to be persisted
            json["ltikey_lock@TypeHint"] = "Boolean";
            json.ltikey_lock = null; // does not need to be persisted
            json["debug@TypeHint"] = "Boolean";
            json.debug = null; // does not need to be persisted
            json["debug_lock@TypeHint"] = "Boolean";
            json.debug_lock = null; // does not need to be persisted
            json["release_names@TypeHint"] = "Boolean";
            json.release_names = null; // does not need to be persisted
            json["release_names_lock@TypeHint"] = "Boolean";
            json.release_names_lock = null; // does not need to be persisted
            json["release_email@TypeHint"] = "Boolean";
            json.release_email = null; // does not need to be persisted
            json["release_email_lock@TypeHint"] = "Boolean";
            json.release_email_lock = null; // does not need to be persisted
            json["release_principal_name@TypeHint"] = "Boolean";
            json.release_principal_name = null; // does not need to be persisted
            json["release_principal_name_lock@TypeHint"] = "Boolean";
            json.release_principal_name_lock = null; // does not need to be persisted
            json.defined = ""; // what the heck is this? Where does it come from?
            json._MODIFIERS = null; // trimpath garbage - probably need a more selective way of saving data
            var saveUrl = sakai.config.URL.SDATA_FETCH_URL.replace(/__PLACEMENT__/, sakai.site.currentsite.id + "/_widgets").replace(/__TUID__/, tuid).replace(/__NAME__/, "basiclti");
            $.ajax({
                type: 'POST',
                url: saveUrl,
                data: json,
                success: savedDataToJCR
            });
        }
        else {
            alert("Please specify a URL");
        }
    };

    /**
     * Change the direction (up/down) of the arrow for the advanced settings
     */
    var changeAdvancedSettingsArrow = function(){
        if (isAdvancedSettingsVisible) {
            $(basicltiSettingsAdvancedDown, rootel).hide();
            $(basicltiSettingsAdvancedUp, rootel).show();
        }
        else {
            $(basicltiSettingsAdvancedUp, rootel).hide();
            $(basicltiSettingsAdvancedDown, rootel).show();
        }
    };

    //////////////
    // Bindings //
    //////////////

    /*
     * Add binding to the color boxes
     */
    var addColorBinding = function(){
        $(".basiclti_settings_color").click(function(){
            json.border_color = $(this).attr("id").split("_")[$(this).attr("id").split("_").length - 1];
            renderIframeSettings(false);
            renderColorContainer();
            addColorBinding();
        });
    };

    /*
     * Add binding to all the elements
     */
    var addBinding = function(){

        // Change the iframe width
        $(basicltiSettingsWidth).change(function(){
            var widthValue = $(basicltiSettingsWidth).val();

            if (isDecimal(widthValue)) {
                json.width = widthValue;
            }
            renderIframeSettings(false);
        });

        // Change the iframe height
        $(basicltiSettingsHeight).change(function(){
            var heightValue = $(basicltiSettingsHeight).val();

            if (isDecimal(heightValue)) {
                json.frame_height = heightValue;
            }
            renderIframeSettings(false);
        });

        // Change the border width
        $(basicltiSettingsBorders).change(function(){
            var borderValue = $(basicltiSettingsBorders).val();
            if (isDecimal(borderValue)) {
                json.border_size = borderValue;
                renderIframeSettings(false);
            }
        });

        // Toggle the advanced view
        $(basicltiSettingsAdvancedToggleSettings).click(function(){
            $("#basiclti_settings_advanced", rootel).toggle();
            isAdvancedSettingsVisible = !isAdvancedSettingsVisible;
            changeAdvancedSettingsArrow();
        });

        // When you click on one of the width units (px or percentage)
        $(basicltiSettingsWidthUnitClass).click(function(){
            var widthUnitValue = $(this).attr("id").split("_")[$(this).attr("id").split("_").length - 1];
            if (widthUnitValue === "px") {
                json.width_unit = widthUnitValue;
            }
            else {
                json.width_unit = "%";
            }
            $(basicltiSettingsWidthUnitClass).removeClass(basicltiSettingsWidthUnitSelectedClass);
            $(this).addClass(basicltiSettingsWidthUnitSelectedClass);
            renderIframeSettings(false);
        });

        // When you push the save button..
        $(basicltiSettingsInsert).click(function(){
            saveRemoteContent();
        });

        // Cancel it
        $(basicltiSettingsCancel).click(function(){
            sakai.api.Widgets.Container.informCancel(tuid);
        });

        addColorBinding();
    };

    ///////////////////////
    // Initial functions //
    ///////////////////////

    /**
     * Function that fills in the input fields in the settings tab.
     * @param {Object} parameters A JSON object that contains the necessary information.
     * @param {Boolean} exists Does there exist a previous basiclti
     */
    var displaySettings = function(parameters, exists){
        if (exists) {
            json = parameters;
        }
        else { // use default values
            json = {
                border_size: 0,
                border_color: "cccccc",
                frame_height: defaultHeight,
                width: defaultWidth,
                width_unit: defaultWidthUnit
            };
        }
        renderRemoteContentSettings();
        //renderIframeSettings(true); // LDS disabled preview
        renderColorContainer();
        addBinding(); // Add binding to the various elements
        changeAdvancedSettingsArrow();
        $(basicltiSettings).show(); // Show the basiclti settings
    };

    /*
     * Is the widget in settings mode or not
     */
    if (showSettings) {
        $(basicltiMainContainer).hide();
        $(basicltiSettings).show();
    }
    else {
        $(basicltiSettings).hide();
        $(basicltiMainContainer).show();
    }

    /**
     * Will fetch the URL and other parameters from the JCR and according to which
     * view we are in, fill in the settings or display an iframe.
     */
    var getRemoteContent = function(){
        var settingsUrl = sakai.config.URL.SDATA_FETCH_URL.replace(/__PLACEMENT__/, sakai.site.currentsite.id + "/_widgets").replace(/__TUID__/, tuid).replace(/__NAME__/, "basiclti");
        $.ajax({
            url: settingsUrl,
            cache: false,
            success: function(data){

                if (showSettings) {
                    displaySettings(data, true); // Fill in the settings page.
                }
                else {
                    displayRemoteContent(data); // Show the frame
                }
            },
            error: function(xhr, textStatus, thrownError){
                // When the request isn't successful, it means that  there was no existing basiclti
                // so we show the basic settings.
                displaySettings(null, false);
            }
        });
    };

    getRemoteContent();
};

sakai.api.Widgets.widgetLoader.informOnLoad("sakai2tools");<|endoftext|>


<commit_before>define( [ "ember", "./metadata" ], function( Ember, metadata ) {

	var	nwGui       = window.nwDispatcher.requireNwGui(),
		nwWindow    = nwGui.Window.get(),

		trayIcon    = null,
		isHidden    = false,
		isMaximized = false,
		isMinimized = false,

		trayTooltip = metadata.package.config[ "display-name" ],
		trayIconImg = (function getTrayIconRes() {
			var	dpr	= window.devicePixelRatio,
				res	= process.platform !== "darwin" || dpr > 2
					? 48
					: dpr > 1
						? 32
						: 16;
			return metadata.package.config[ "tray-icon" ].replace( "{res}", res );
		})(),
		trayMenu    = (function() {
			var menu = new nwGui.Menu();

			menu.append( new nwGui.MenuItem({
				label: "Exit %@".fmt( metadata.package.config[ "display-name" ] ),
				click: function() {
					nwWindow.close();
				}
			}));

			return menu;
		})();

	function removeTrayIcon() {
		if ( trayIcon ) {
			trayIcon.remove();
			trayIcon = null;
		}
	}

	window.addEventListener( "beforeunload", function() {
		// remove all listeners
		nwWindow.removeAllListeners();
		// prevent tray icons from stacking up when refreshing the page or devtools
		removeTrayIcon();
	}, false );

	nwWindow.on( "maximize",   function onMaximize()   { isMaximized = true;  } );
	nwWindow.on( "unmaximize", function onUnmaximize() { isMaximized = false; } );
	nwWindow.on( "minimize",   function onMinimize()   { isMinimized = true;  } );
	nwWindow.on( "restore",    function onRestore()    { isMinimized = false; } );

	nwWindow.toggleMaximize = function toggleMaximize( bool ) {
		if ( bool === undefined ) { bool = isMaximized; }
		nwWindow[ bool ? "unmaximize" : "maximize" ]();
	};

	nwWindow.toggleMinimize = function toggleMinimize( bool ) {
		if ( bool === undefined ) { bool = isMinimized; }
		nwWindow[ bool ? "restore" : "minimize" ]();
	};

	nwWindow.toggleVisibility = function toggleVisibility( bool ) {
		if ( bool === undefined ) { bool = isHidden; }
		nwWindow[ bool ? "show" : "hide" ]();
		isHidden = !bool;
	};

	nwWindow.setShowInTray = function setShowInTray( bool, taskbar ) {
		// always remove the tray icon...
		// we need a new click event listener in case the taskbar param has changed
		removeTrayIcon();
		if ( bool ) {
			trayIcon = new nwGui.Tray({ icon: trayIconImg });
			trayIcon.tooltip = trayTooltip;
			trayIcon.menu = trayMenu;
			trayIcon.on( "click", function() {
				nwWindow.toggleVisibility();
				// also toggle taskbar visiblity on click (gui_integration === both)
				if ( taskbar ) {
					nwWindow.setShowInTaskbar( !isHidden );
				}
			});
		}
	};

	nwWindow.changeIntegrations = function changeIntegrations( taskbar, tray ) {
		nwWindow.setShowInTaskbar( taskbar );
		nwWindow.setShowInTray( tray, taskbar );
	};

	nwWindow.cookiesRemoveAll = function cookiesRemoveAll() {
		var Cookies = nwWindow.cookies;
		Cookies.getAll( {}, function( cookies ) {
			[].forEach.call( cookies, function( c ) {
				Cookies.remove({
					url: "http" + ( c.secure ? "s" : "" ) + "://" + c.domain + c.path,
					name: c.name
				});
			});
		});
	};

	Ember.Application.initializer({
		name: "node-webkit",

		initialize: function( container ) {
			// inject nwGui and nwWindow into all routes and controllers
			container.register( "nw:nwGui",    nwGui,    { instantiate: false } );
			container.register( "nw:nwWindow", nwWindow, { instantiate: false } );
			container.injection( "route",      "nwGui",    "nw:nwGui" );
			container.injection( "controller", "nwGui",    "nw:nwGui" );
			container.injection( "route",      "nwWindow", "nw:nwWindow" );
			container.injection( "controller", "nwWindow", "nw:nwWindow" );

			// listen for the close event and show the dialog instead of strictly shutting down
			nwWindow.on( "close", function() {
				if ( location.pathname !== "/index.html" ) {
					return this.close( true );
				}

				try {
					nwWindow.toggleVisibility( true );
					nwWindow.focus();
					container.lookup( "controller:application" ).send( "winClose" );
				} catch ( e ) {
					this.close( true );
				}
			});
		}
	});

});
<commit_msg>Also remove all event listeners of process<commit_after>define( [ "ember", "./metadata" ], function( Ember, metadata ) {

	var	nwGui       = window.nwDispatcher.requireNwGui(),
		nwWindow    = nwGui.Window.get(),

		trayIcon    = null,
		isHidden    = false,
		isMaximized = false,
		isMinimized = false,

		trayTooltip = metadata.package.config[ "display-name" ],
		trayIconImg = (function getTrayIconRes() {
			var	dpr	= window.devicePixelRatio,
				res	= process.platform !== "darwin" || dpr > 2
					? 48
					: dpr > 1
						? 32
						: 16;
			return metadata.package.config[ "tray-icon" ].replace( "{res}", res );
		})(),
		trayMenu    = (function() {
			var menu = new nwGui.Menu();

			menu.append( new nwGui.MenuItem({
				label: "Exit %@".fmt( metadata.package.config[ "display-name" ] ),
				click: function() {
					nwWindow.close();
				}
			}));

			return menu;
		})();

	function removeTrayIcon() {
		if ( trayIcon ) {
			trayIcon.remove();
			trayIcon = null;
		}
	}

	window.addEventListener( "beforeunload", function() {
		// remove all listeners
		nwWindow.removeAllListeners();
		process.removeAllListeners();
		// prevent tray icons from stacking up when refreshing the page or devtools
		removeTrayIcon();
	}, false );

	nwWindow.on( "maximize",   function onMaximize()   { isMaximized = true;  } );
	nwWindow.on( "unmaximize", function onUnmaximize() { isMaximized = false; } );
	nwWindow.on( "minimize",   function onMinimize()   { isMinimized = true;  } );
	nwWindow.on( "restore",    function onRestore()    { isMinimized = false; } );

	nwWindow.toggleMaximize = function toggleMaximize( bool ) {
		if ( bool === undefined ) { bool = isMaximized; }
		nwWindow[ bool ? "unmaximize" : "maximize" ]();
	};

	nwWindow.toggleMinimize = function toggleMinimize( bool ) {
		if ( bool === undefined ) { bool = isMinimized; }
		nwWindow[ bool ? "restore" : "minimize" ]();
	};

	nwWindow.toggleVisibility = function toggleVisibility( bool ) {
		if ( bool === undefined ) { bool = isHidden; }
		nwWindow[ bool ? "show" : "hide" ]();
		isHidden = !bool;
	};

	nwWindow.setShowInTray = function setShowInTray( bool, taskbar ) {
		// always remove the tray icon...
		// we need a new click event listener in case the taskbar param has changed
		removeTrayIcon();
		if ( bool ) {
			trayIcon = new nwGui.Tray({ icon: trayIconImg });
			trayIcon.tooltip = trayTooltip;
			trayIcon.menu = trayMenu;
			trayIcon.on( "click", function() {
				nwWindow.toggleVisibility();
				// also toggle taskbar visiblity on click (gui_integration === both)
				if ( taskbar ) {
					nwWindow.setShowInTaskbar( !isHidden );
				}
			});
		}
	};

	nwWindow.changeIntegrations = function changeIntegrations( taskbar, tray ) {
		nwWindow.setShowInTaskbar( taskbar );
		nwWindow.setShowInTray( tray, taskbar );
	};

	nwWindow.cookiesRemoveAll = function cookiesRemoveAll() {
		var Cookies = nwWindow.cookies;
		Cookies.getAll( {}, function( cookies ) {
			[].forEach.call( cookies, function( c ) {
				Cookies.remove({
					url: "http" + ( c.secure ? "s" : "" ) + "://" + c.domain + c.path,
					name: c.name
				});
			});
		});
	};

	Ember.Application.initializer({
		name: "node-webkit",

		initialize: function( container ) {
			// inject nwGui and nwWindow into all routes and controllers
			container.register( "nw:nwGui",    nwGui,    { instantiate: false } );
			container.register( "nw:nwWindow", nwWindow, { instantiate: false } );
			container.injection( "route",      "nwGui",    "nw:nwGui" );
			container.injection( "controller", "nwGui",    "nw:nwGui" );
			container.injection( "route",      "nwWindow", "nw:nwWindow" );
			container.injection( "controller", "nwWindow", "nw:nwWindow" );

			// listen for the close event and show the dialog instead of strictly shutting down
			nwWindow.on( "close", function() {
				if ( location.pathname !== "/index.html" ) {
					return this.close( true );
				}

				try {
					nwWindow.toggleVisibility( true );
					nwWindow.focus();
					container.lookup( "controller:application" ).send( "winClose" );
				} catch ( e ) {
					this.close( true );
				}
			});
		}
	});

});
<|endoftext|>


<commit_before>define( [ "ember", "./metadata" ], function( Ember, metadata ) {

	var	nwGui       = window.nwDispatcher.requireNwGui(),
		nwWindow    = nwGui.Window.get(),

		trayIcon    = null,
		isHidden    = false,
		isMaximized = false,
		isMinimized = false,

		trayTooltip = metadata.package.config[ "display-name" ],
		trayIconImg = (function getTrayIconRes() {
			var	dpr	= window.devicePixelRatio,
				res	= process.platform !== "darwin" || dpr > 2
					? 48
					: dpr > 1
						? 32
						: 16;
			return metadata.package.config[ "tray-icon" ].replace( "{res}", res );
		})(),
		trayMenu    = (function() {
			var menu = new nwGui.Menu();

			menu.append( new nwGui.MenuItem({
				label: "Exit %@".fmt( metadata.package.config[ "display-name" ] ),
				click: function() {
					nwWindow.close();
				}
			}));

			return menu;
		})();

	if ( /^win/.test( process.platform ) ) {
		// register AppUserModelID
		// this is required for toast notifications on windows
		// https://github.com/rogerwang/node-webkit/wiki/Notification#windows
		var shortcut = "%@\\Microsoft\\Windows\\Start Menu\\Programs\\%@.lnk".fmt(
			process.env.APPDATA,
			Ember.get( metadata, "package.config.display-name" )
		);
		nwGui.App.createShortcut( shortcut );
	}

	function removeTrayIcon() {
		if ( trayIcon ) {
			trayIcon.remove();
			trayIcon = null;
		}
	}

	window.addEventListener( "beforeunload", function() {
		// remove all listeners
		nwWindow.removeAllListeners();
		process.removeAllListeners();
		// prevent tray icons from stacking up when refreshing the page or devtools
		removeTrayIcon();
	}, false );

	nwWindow.on( "maximize",   function onMaximize()   { isMaximized = true;  } );
	nwWindow.on( "unmaximize", function onUnmaximize() { isMaximized = false; } );
	nwWindow.on( "minimize",   function onMinimize()   { isMinimized = true;  } );
	nwWindow.on( "restore",    function onRestore()    { isMinimized = false; } );

	nwWindow.toggleMaximize = function toggleMaximize( bool ) {
		if ( bool === undefined ) { bool = isMaximized; }
		nwWindow[ bool ? "unmaximize" : "maximize" ]();
	};

	nwWindow.toggleMinimize = function toggleMinimize( bool ) {
		if ( bool === undefined ) { bool = isMinimized; }
		nwWindow[ bool ? "restore" : "minimize" ]();
	};

	nwWindow.toggleVisibility = function toggleVisibility( bool ) {
		if ( bool === undefined ) { bool = isHidden; }
		nwWindow[ bool ? "show" : "hide" ]();
		isHidden = !bool;
	};

	nwWindow.setShowInTray = function setShowInTray( bool, taskbar ) {
		// always remove the tray icon...
		// we need a new click event listener in case the taskbar param has changed
		removeTrayIcon();
		if ( bool ) {
			trayIcon = new nwGui.Tray({ icon: trayIconImg });
			trayIcon.tooltip = trayTooltip;
			trayIcon.menu = trayMenu;
			trayIcon.on( "click", function() {
				nwWindow.toggleVisibility();
				// also toggle taskbar visiblity on click (gui_integration === both)
				if ( taskbar ) {
					nwWindow.setShowInTaskbar( !isHidden );
				}
			});
		}
	};

	nwWindow.changeIntegrations = function changeIntegrations( taskbar, tray ) {
		nwWindow.setShowInTaskbar( taskbar );
		nwWindow.setShowInTray( tray, taskbar );
	};

	nwWindow.cookiesRemoveAll = function cookiesRemoveAll() {
		var Cookies = nwWindow.cookies;
		Cookies.getAll( {}, function( cookies ) {
			[].forEach.call( cookies, function( c ) {
				Cookies.remove({
					url: "http" + ( c.secure ? "s" : "" ) + "://" + c.domain + c.path,
					name: c.name
				});
			});
		});
	};

	Ember.Application.initializer({
		name: "node-webkit",

		initialize: function( container ) {
			// inject nwGui and nwWindow into all routes and controllers
			container.register( "nw:nwGui",    nwGui,    { instantiate: false } );
			container.register( "nw:nwWindow", nwWindow, { instantiate: false } );
			container.injection( "route",      "nwGui",    "nw:nwGui" );
			container.injection( "controller", "nwGui",    "nw:nwGui" );
			container.injection( "route",      "nwWindow", "nw:nwWindow" );
			container.injection( "controller", "nwWindow", "nw:nwWindow" );

			// listen for the close event and show the dialog instead of strictly shutting down
			nwWindow.on( "close", function() {
				if ( location.pathname !== "/index.html" ) {
					return this.close( true );
				}

				try {
					nwWindow.toggleVisibility( true );
					nwWindow.focus();
					container.lookup( "controller:application" ).send( "winClose" );
				} catch ( e ) {
					this.close( true );
				}
			});
		}
	});

});
<commit_msg>Fix OSX tray icon<commit_after>define( [ "ember", "./metadata" ], function( Ember, metadata ) {

	var	nwGui       = window.nwDispatcher.requireNwGui(),
		nwWindow    = nwGui.Window.get(),

		trayIcon    = null,
		isHidden    = false,
		isMaximized = false,
		isMinimized = false,

		trayTooltip = metadata.package.config[ "display-name" ],
		trayIconImg = (function getTrayIconRes() {
			var	dpr	= window.devicePixelRatio,
				res	= process.platform !== "darwin" || dpr > 2
					? 48
					: dpr > 1
						? 32
						: 16;
			return metadata.package.config[ "tray-icon" ].replace( "{res}", res );
		})(),
		trayMenu    = (function() {
			var menu = new nwGui.Menu();

			menu.append( new nwGui.MenuItem({
				label: "Exit %@".fmt( metadata.package.config[ "display-name" ] ),
				click: function() {
					nwWindow.close();
				}
			}));

			return menu;
		})();

	if ( /^win/.test( process.platform ) ) {
		// register AppUserModelID
		// this is required for toast notifications on windows
		// https://github.com/rogerwang/node-webkit/wiki/Notification#windows
		var shortcut = "%@\\Microsoft\\Windows\\Start Menu\\Programs\\%@.lnk".fmt(
			process.env.APPDATA,
			Ember.get( metadata, "package.config.display-name" )
		);
		nwGui.App.createShortcut( shortcut );
	}

	function removeTrayIcon() {
		if ( trayIcon ) {
			trayIcon.remove();
			trayIcon = null;
		}
	}

	window.addEventListener( "beforeunload", function() {
		// remove all listeners
		nwWindow.removeAllListeners();
		process.removeAllListeners();
		// prevent tray icons from stacking up when refreshing the page or devtools
		removeTrayIcon();
	}, false );

	nwWindow.on( "maximize",   function onMaximize()   { isMaximized = true;  } );
	nwWindow.on( "unmaximize", function onUnmaximize() { isMaximized = false; } );
	nwWindow.on( "minimize",   function onMinimize()   { isMinimized = true;  } );
	nwWindow.on( "restore",    function onRestore()    { isMinimized = false; } );

	nwWindow.toggleMaximize = function toggleMaximize( bool ) {
		if ( bool === undefined ) { bool = isMaximized; }
		nwWindow[ bool ? "unmaximize" : "maximize" ]();
	};

	nwWindow.toggleMinimize = function toggleMinimize( bool ) {
		if ( bool === undefined ) { bool = isMinimized; }
		nwWindow[ bool ? "restore" : "minimize" ]();
	};

	nwWindow.toggleVisibility = function toggleVisibility( bool ) {
		if ( bool === undefined ) { bool = isHidden; }
		nwWindow[ bool ? "show" : "hide" ]();
		isHidden = !bool;
	};

	nwWindow.setShowInTray = function setShowInTray( bool, taskbar ) {
		// always remove the tray icon...
		// we need a new click event listener in case the taskbar param has changed
		removeTrayIcon();
		if ( bool ) {
			trayIcon = new nwGui.Tray({ icon: trayIconImg });
			trayIcon.tooltip = trayTooltip;
			trayIcon.menu = trayMenu;
			trayIcon.iconsAreTemplates = false;
			trayIcon.on( "click", function() {
				nwWindow.toggleVisibility();
				// also toggle taskbar visiblity on click (gui_integration === both)
				if ( taskbar ) {
					nwWindow.setShowInTaskbar( !isHidden );
				}
			});
		}
	};

	nwWindow.changeIntegrations = function changeIntegrations( taskbar, tray ) {
		nwWindow.setShowInTaskbar( taskbar );
		nwWindow.setShowInTray( tray, taskbar );
	};

	nwWindow.cookiesRemoveAll = function cookiesRemoveAll() {
		var Cookies = nwWindow.cookies;
		Cookies.getAll( {}, function( cookies ) {
			[].forEach.call( cookies, function( c ) {
				Cookies.remove({
					url: "http" + ( c.secure ? "s" : "" ) + "://" + c.domain + c.path,
					name: c.name
				});
			});
		});
	};

	Ember.Application.initializer({
		name: "node-webkit",

		initialize: function( container ) {
			// inject nwGui and nwWindow into all routes and controllers
			container.register( "nw:nwGui",    nwGui,    { instantiate: false } );
			container.register( "nw:nwWindow", nwWindow, { instantiate: false } );
			container.injection( "route",      "nwGui",    "nw:nwGui" );
			container.injection( "controller", "nwGui",    "nw:nwGui" );
			container.injection( "route",      "nwWindow", "nw:nwWindow" );
			container.injection( "controller", "nwWindow", "nw:nwWindow" );

			// listen for the close event and show the dialog instead of strictly shutting down
			nwWindow.on( "close", function() {
				if ( location.pathname !== "/index.html" ) {
					return this.close( true );
				}

				try {
					nwWindow.toggleVisibility( true );
					nwWindow.focus();
					container.lookup( "controller:application" ).send( "winClose" );
				} catch ( e ) {
					this.close( true );
				}
			});
		}
	});

});
<|endoftext|>


<commit_before>class GatewayStorage {

	/**
	 * @typedef {Object} GatewayGetPathOptions
	 * @property {boolean} [avoidUnconfigurable=false] Whether the getPath should avoid unconfigurable keys
	 * @property {boolean} [piece=true] Whether the getPath should return pieces or folders
	 */

	/**
	 * @typedef {Object} GatewayGetPathResult
	 * @property {SchemaPiece} piece The piece resolved from the path
	 * @property {string[]} route The resolved path split by dots
	 */

	/**
	 * @typedef {Object} GatewayJSON
	 * @property {string} type The name of this gateway
	 * @property {GatewayDriverRegisterOptions} options The options for this gateway
	 * @property {Object} schema The current schema
	 */

	/**
	 * <warning>You should never create an instance of this class as it's abstract.</warning>
	 * @since 0.5.0
	 * @param {KlasaClient} client The client this GatewayStorage was created with
	 * @param {string} type The name of this GatewayStorage
	 * @param {Schema} schema The schema for this gateway
	 * @param {string} [provider] The provider's name
	 * @private
	 */
	constructor(client, type, schema, provider) {
		/**
		 * The client this GatewayStorage was created with.
		 * @since 0.5.0
		 * @name GatewayStorage#client
		 * @type {KlasaClient}
		 * @readonly
		 */
		Object.defineProperty(this, 'client', { value: client });

		/**
		 * The type of this GatewayStorage.
		 * @since 0.5.0
		 * @name GatewayStorage#type
		 * @type {string}
		 * @readonly
		 */
		Object.defineProperty(this, 'type', { value: type });

		/**
		 * The name of this instance's provider.
		 * @since 0.5.0
		 * @name GatewayStorage#providerName
		 * @type {string}
		 * @readonly
		 */
		Object.defineProperty(this, 'providerName', { value: provider || this.client.options.providers.default });

		/**
		 * @since 0.5.0
		 * @type {Schema}
		 */
		this.schema = schema;

		/**
		 * @since 0.5.0
		 * @type {boolean}
		 */
		this.ready = false;
	}

	/**
	 * Get the provider that manages the persistent data.
	 * @since 0.5.0
	 * @type {?Provider}
	 * @readonly
	 */
	get provider() {
		return this.client.providers.get(this.providerName) || null;
	}

	/**
	 * Get this gateway's defaults.
	 * @since 0.5.0
	 * @type {Object}
	 * @readonly
	 */
	get defaults() {
		return { ...this.schema.defaults, default: true };
	}

	/**
	 * Resolve a path from a string.
	 * @since 0.5.0
	 * @param {string} [key=null] A string to resolve
	 * @param {GatewayGetPathOptions} [options={}] Whether the Gateway should avoid configuring the selected key
	 * @returns {?GatewayGetPathResult}
	 */
	getPath(key = '', { avoidUnconfigurable = false, piece: requestPiece = true, errors = true } = {}) {
		if (key === '' || key === '.') return { piece: this.schema, route: [] };
		const route = key.split('.');
		const piece = this.schema.get(route);

		// The piece does not exist (invalid or non-existant path)
		if (!piece) {
			if (!errors) return null;
			throw `The key ${key} does not exist in the schema.`;
		}

		if (requestPiece === null) requestPiece = piece.type !== 'Folder';

		// GetPath expects a piece
		if (requestPiece) {
			// The piece is a key
			if (piece.type !== 'Folder') {
				// If the Piece is unconfigurable and avoidUnconfigurable is requested, throw
				if (avoidUnconfigurable && !piece.configurable) {
					if (!errors) return null;
					throw `The key ${piece.path} is not configurable.`;
				}
				return { piece, route };
			}

			// The piece is a folder
			if (!errors) return null;
			const keys = avoidUnconfigurable ? piece.configurableKeys : [...piece.keys()];
			throw keys.length ? `Please, choose one of the following keys: '${keys.join('\', \'')}'` : 'This group is not configurable.';
		}

		// GetPath does not expect a piece
		if (piece.type !== 'Folder') {
			// Remove leading key from the path
			route.pop();
			return { piece: piece.parent, route };
		}

		return { piece, route };
	}

	/**
	 * Inits the current Gateway.
	 * @since 0.5.0
	 */
	async init() {
		// A gateway must not init twice
		if (this.ready) throw new Error(`[INIT] ${this} has already initialized.`);

		// Check the provider's existence
		const { provider } = this;
		if (!provider) throw new Error(`This provider (${this.providerName}) does not exist in your system.`);
		this.ready = true;

		// Check for errors in the schema
		const debug = this.schema.debug();
		if (debug.length) throw new Error(`[SCHEMA] There is an error with your schema.\n${debug.join('\n')}`);

		// Init the table
		const hasTable = await provider.hasTable(this.type);
		if (!hasTable) await provider.createTable(this.type);

		// Add any missing columns (NoSQL providers return empty array)
		const columns = await provider.getColumns(this.type);
		if (columns.length) {
			const promises = [];
			for (const [key, piece] of this.schema.paths) if (!columns.includes(key)) promises.push(provider.addColumn(key, piece));
			await Promise.all(promises);
		}
	}

	/**
	 * Get a JSON object containing the schema and options.
	 * @since 0.5.0
	 * @returns {GatewayJSON}
	 */
	toJSON() {
		return {
			type: this.type,
			options: { provider: this.providerName },
			schema: this.schema.toJSON()
		};
	}

	/**
	 * Stringify a value or the instance itself.
	 * @since 0.5.0
	 * @returns {string}
	 */
	toString() {
		return `Gateway(${this.type})`;
	}

}

module.exports = GatewayStorage;
<commit_msg>table name instead of key to Provider#addColumn<commit_after>class GatewayStorage {

	/**
	 * @typedef {Object} GatewayGetPathOptions
	 * @property {boolean} [avoidUnconfigurable=false] Whether the getPath should avoid unconfigurable keys
	 * @property {boolean} [piece=true] Whether the getPath should return pieces or folders
	 */

	/**
	 * @typedef {Object} GatewayGetPathResult
	 * @property {SchemaPiece} piece The piece resolved from the path
	 * @property {string[]} route The resolved path split by dots
	 */

	/**
	 * @typedef {Object} GatewayJSON
	 * @property {string} type The name of this gateway
	 * @property {GatewayDriverRegisterOptions} options The options for this gateway
	 * @property {Object} schema The current schema
	 */

	/**
	 * <warning>You should never create an instance of this class as it's abstract.</warning>
	 * @since 0.5.0
	 * @param {KlasaClient} client The client this GatewayStorage was created with
	 * @param {string} type The name of this GatewayStorage
	 * @param {Schema} schema The schema for this gateway
	 * @param {string} [provider] The provider's name
	 * @private
	 */
	constructor(client, type, schema, provider) {
		/**
		 * The client this GatewayStorage was created with.
		 * @since 0.5.0
		 * @name GatewayStorage#client
		 * @type {KlasaClient}
		 * @readonly
		 */
		Object.defineProperty(this, 'client', { value: client });

		/**
		 * The type of this GatewayStorage.
		 * @since 0.5.0
		 * @name GatewayStorage#type
		 * @type {string}
		 * @readonly
		 */
		Object.defineProperty(this, 'type', { value: type });

		/**
		 * The name of this instance's provider.
		 * @since 0.5.0
		 * @name GatewayStorage#providerName
		 * @type {string}
		 * @readonly
		 */
		Object.defineProperty(this, 'providerName', { value: provider || this.client.options.providers.default });

		/**
		 * @since 0.5.0
		 * @type {Schema}
		 */
		this.schema = schema;

		/**
		 * @since 0.5.0
		 * @type {boolean}
		 */
		this.ready = false;
	}

	/**
	 * Get the provider that manages the persistent data.
	 * @since 0.5.0
	 * @type {?Provider}
	 * @readonly
	 */
	get provider() {
		return this.client.providers.get(this.providerName) || null;
	}

	/**
	 * Get this gateway's defaults.
	 * @since 0.5.0
	 * @type {Object}
	 * @readonly
	 */
	get defaults() {
		return { ...this.schema.defaults, default: true };
	}

	/**
	 * Resolve a path from a string.
	 * @since 0.5.0
	 * @param {string} [key=null] A string to resolve
	 * @param {GatewayGetPathOptions} [options={}] Whether the Gateway should avoid configuring the selected key
	 * @returns {?GatewayGetPathResult}
	 */
	getPath(key = '', { avoidUnconfigurable = false, piece: requestPiece = true, errors = true } = {}) {
		if (key === '' || key === '.') return { piece: this.schema, route: [] };
		const route = key.split('.');
		const piece = this.schema.get(route);

		// The piece does not exist (invalid or non-existant path)
		if (!piece) {
			if (!errors) return null;
			throw `The key ${key} does not exist in the schema.`;
		}

		if (requestPiece === null) requestPiece = piece.type !== 'Folder';

		// GetPath expects a piece
		if (requestPiece) {
			// The piece is a key
			if (piece.type !== 'Folder') {
				// If the Piece is unconfigurable and avoidUnconfigurable is requested, throw
				if (avoidUnconfigurable && !piece.configurable) {
					if (!errors) return null;
					throw `The key ${piece.path} is not configurable.`;
				}
				return { piece, route };
			}

			// The piece is a folder
			if (!errors) return null;
			const keys = avoidUnconfigurable ? piece.configurableKeys : [...piece.keys()];
			throw keys.length ? `Please, choose one of the following keys: '${keys.join('\', \'')}'` : 'This group is not configurable.';
		}

		// GetPath does not expect a piece
		if (piece.type !== 'Folder') {
			// Remove leading key from the path
			route.pop();
			return { piece: piece.parent, route };
		}

		return { piece, route };
	}

	/**
	 * Inits the current Gateway.
	 * @since 0.5.0
	 */
	async init() {
		// A gateway must not init twice
		if (this.ready) throw new Error(`[INIT] ${this} has already initialized.`);

		// Check the provider's existence
		const { provider } = this;
		if (!provider) throw new Error(`This provider (${this.providerName}) does not exist in your system.`);
		this.ready = true;

		// Check for errors in the schema
		const debug = this.schema.debug();
		if (debug.length) throw new Error(`[SCHEMA] There is an error with your schema.\n${debug.join('\n')}`);

		// Init the table
		const hasTable = await provider.hasTable(this.type);
		if (!hasTable) await provider.createTable(this.type);

		// Add any missing columns (NoSQL providers return empty array)
		const columns = await provider.getColumns(this.type);
		if (columns.length) {
			const promises = [];
			for (const [key, piece] of this.schema.paths) if (!columns.includes(key)) promises.push(provider.addColumn(this.type, piece));
			await Promise.all(promises);
		}
	}

	/**
	 * Get a JSON object containing the schema and options.
	 * @since 0.5.0
	 * @returns {GatewayJSON}
	 */
	toJSON() {
		return {
			type: this.type,
			options: { provider: this.providerName },
			schema: this.schema.toJSON()
		};
	}

	/**
	 * Stringify a value or the instance itself.
	 * @since 0.5.0
	 * @returns {string}
	 */
	toString() {
		return `Gateway(${this.type})`;
	}

}

module.exports = GatewayStorage;
<|endoftext|>


<commit_before>function VCFValidator(options) {
    if (!(this instanceof VCFValidator)) {
        return new VCFValidator(options);
    }
    Validator.call(this, options);

    this._header = true;
    this._fileFormat = false;
    this._contigs = {};
    this._info = {};
    this._format = {};
    this._filter = {};
    this._headerElements = [];
    this._samples = [];
    this._columnsSize = 0;
    this._refTag = false;

    this._duplicates = {};
    this._sorted = true;

    this._prevChrPos = null;

    this._regExp = {
        "headerId": /ID=(\w+)/,
        "headerNumber": /Number=(\w+|\.)/,
        "headerType": /Type=(\w+)/,
        "headerDesc": /Description=\"(.+)\"/,
        "actg": /^[ACGTN]+$/,
        "gt": /^(\.|\d+)([|/](\.|\d+))?$/,
        "alpha": /^(\w+)$/,
        "idSemiColon": /^(\w+(;\w+)?)$/
    }
}

VCFValidator.prototype = Object.create(Validator.prototype);

VCFValidator.prototype.validateLine = function (line) {

    if (this._header) { // parse Header
        this.parseHeader(line);
    } else {
        if (!this._refTag) {
            this._checkReferenceTag();
            this._refTag = true;
        }
        if (!this._fileFormat) {
            this.addLog("error", "The file format declaration must be present");
            this._fileFormat = true;
        }
        this.parseData(line);
    }

}

VCFValidator.prototype.isHeaderLine = function (line) {
    return line.startsWith("#");
}

VCFValidator.prototype.parseHeader = function (line) {
    if (this.line == 0) { // parse fileformat
        if (!line.startsWith("##fileformat=")) {
            this.addLog("error", "The file format declaration must be the first line in the file");
        }
    }

    if (line.startsWith("##")) {
        var lineData = line.substring(2);
        var key = lineData.substring(0, lineData.indexOf("="))
        var value = lineData.substring(lineData.indexOf("=") + 1)

        if (key == "" || value == "") {
            this.addLog("error", "All lines must be key=value pairs.")
        }

        if (key.toLowerCase() == "fileformat") {
            this._fileFormat = true;

        } else if (key.toLowerCase() == "contig") {
            var contigId = this._getDataFromRegExp(value, "headerId");
            this._contigs[contigId] = contigId;
        } else if (key.toLowerCase() == "info") {
            var id = this._getDataFromRegExp(value, "headerId");
            var number = this._getDataFromRegExp(value, "headerNumber");
            var type = this._getDataFromRegExp(value, "headerType");
            var description = this._getDataFromRegExp(value, "headerDesc");

            if (id == null || number == null || type == null || description == null) {
                this.addLog("error", "INFO fields must be described as ##INFO=<ID=ID,Number=number,Type=type,Description='description'> ")
            }

            this._info[id] = {
                id: id,
                number: number,
                type: type,
                description: description
            }
        } else if (key.toLowerCase() == "format") {
            var id = this._getDataFromRegExp(value, "headerId");
            var number = this._getDataFromRegExp(value, "headerNumber");
            var type = this._getDataFromRegExp(value, "headerType");
            var description = this._getDataFromRegExp(value, "headerDesc");

            if (id == null || number == null || type == null || description == null) {
                this.addLog("error", "FORMAT fields must be described as ##FORMAT=<ID=ID,Number=number,Type=type,Description='description'> ")
            }

            this._format[id] = {
                id: id,
                number: number,
                type: type,
                description: description
            }
        } else if (key.toLowerCase() == "filter") {
            var id = this._getDataFromRegExp(value, "headerId");
            var description = this._getDataFromRegExp(value, "headerDesc");

            if (id == null || description == null) {
                this.addLog("error", "FILTER fields must be described as ##FILTER=<ID=ID,Description='description'>")
            }

            this._filter[id] = {
                id: id,
                description: description
            }

        } else {

            this._headerElements.push({
                id: key,
                value: value
            });
        }

    } else if (line.startsWith("#CHROM")) {
        this._header = false;
        splits = line.split("\t");
        this._columnsSize = splits.length;
        if (splits.length < 8) {
            this.addLog("error", "Must contain at least 8 columns(CHROM to INFO)");
        } else if (splits.length > 9) {
            for (var i = 9; i < splits.length; i++) {
                this._samples.push(splits[i]);
            }
        }
    } else {
        this.addLog("error", "All header lines must be prefixed by '##");
    }
}

VCFValidator.prototype._getDataFromRegExp = function (data, regExpId) {
    var regExp = this._regExp[regExpId];
    if (regExp == null) {
        return null;
    }
    if (regExp.test(data)) {
        var groups = regExp.exec(data);
        if (groups != null && groups.length > 1) {
            return groups[1];
        }
    }
    return null;

}

VCFValidator.prototype.parseData = function (line) {
    if (this.isHeaderLine(line)) {
        this.addLog("error", "Must not start with a header prefix (#)");
        return;
    }

    var columns = line.split("\t");

    if (columns.length < 9) {
        this.addLog("error", "Must contain at least 8 columns(CHROM to INFO)");
    } else if (columns.length - 9 != this._samples.length) {
        this.addLog("error", "The number of samples must match those listed in the header");
    }

    if (this._columnsSize != columns.length) {
        this.addLog("error", "All ines must have the same number of columns");
    }

    // Chromosome
    var chr = columns[0];
    if (chr == "") {
        this.addLog("error", "Chromosome must not be empty");
    }
    if (chr.search(":") >= 0) {
        this.addLog("error", "Chromosome must not contain colons");
    }
    if (chr.search(" ") >= 0) {
        this.addLog("error", "Chromosome must not contain whitespaces");
    }

    // Position
    var pos = columns[1]
    if (pos == "") {
        this.addLog("error", "Position must not be empty");
    }
    pos = parseInt(pos)
    if (isNaN(pos)) {
        this.addLog("error", "Position must be numeric");
    }

    // Check duplicates
    var chr_pos = chr + "_" + pos;
    if (chr_pos in this._duplicates) {
        this.addLog("warning", "There must be no chromosome+position duplicates");
    }

    this._duplicates[chr_pos] = null;

    // Id

    var id = columns[2];

    if (id != ".") {
        if (!this._regExp["alpha"].test(id)) {
            this.addLog("error", "ID must be alphanumeric");
        }

        if (!this._regExp["idSemiColon"].test(id)) {
            this.addLog("error", "If more than one ID is specified, the must be semo-colon separated");
        }
    }

    // ref

    var ref = columns[3];
    if (ref == "") {
        this.addLog("error", "Reference allele must not be empty");
    }

    if (!this._regExp["actg"].test(ref)) {
        this.addLog("error", "Reference allele must match the regular expression /^[ACTGN]+$/");
    }

    // alt
    var alt = columns[4];

    if (alt == undefined) {
        debugger
    }

    var altSplits = alt.split(",");
    var altSplitsUnique = altSplits.filter(function (item, pos) {
        return altSplits.indexOf(item) == pos;
    });

    if (altSplits.length != altSplitsUnique.length) {
        this.addLog("error", "Alternate must contain each allele only once")
    }

    if (altSplitsUnique.indexOf(ref) >= 0) {
        this.addLog("error", "The reference allele must not be in the list of alternate alleles")
    }

    for (var i = 0; i < altSplits.length; i++) {
        var altElem = altSplits[i];

        if (!this._regExp["actg"].test(altElem)) {
            this.addLog("error", "Alternate allele must match the regular expression /^[ACTGN]+$/")
        }

        if (altElem.length != ref.length) {
            if (altElem.charAt(0) != ref.charAt(0)) {
                this.addLog("warning", "The first base of each allele must match the reference if their lengths are different");
            }
        }
    }

    // Qual
    var qual = columns[5];
    if (qual != ".") {
        var qualFloat = parseFloat(qual);
        if (isNaN(qualFloat) || qualFloat < 0) {
            this.addLog("error", "Quality must be a non-negative float");
        }

    }

    // Filter

    var filter = columns[6];

    // Info
    var info = columns[7];

    if (!columns.length > 8) {
        return;
    }

    // Format
    var format = columns[8];

    if (this._samples.length > 0 && format == "") {
        this.addLog("eror", "Must not be empty if the file contains any samples");
    }

    if (format != "" && !format.startsWith("GT")) {
        this.addLog("error", "GT must be the first field");
    }

    var formatSplits = format.split(":");

    // Samples
    var samplesData = [];

    for (var i = 9; i < columns.length; i++) {
        samplesData.push(columns[i]);
    }

    for (var i = 0; i < samplesData.length; i++) {
        var sampleData = samplesData[i];

        if (sampleData == "") {
            this.addLog("error", "Sample fields must be not empty");
        } else {
            var sampleDataSplit = sampleData.split(":");
            var gt = sampleDataSplit[0];
            if (!this._regExp["gt"].test(gt)) {
                this.addLog("error", "GT must match the regular expression ^(\.|\d+)([|/]?)");
            } else {
                var gtGroups = this._regExp["gt"].exec(gt);
                if (gtGroups.length == 2) { // GT = 0,1
                    var gtAllele = parseInt(gtGroups[1]);
                    if (gtAllele > altSplits.length) {
                        this.addLog("error", "An allele index must not be greater than the number of alleles in that variant");
                    }
                } else if (gtGroups.length == 4) { // GT = 0/0,0/1,....
                    var gtAllele0 = parseInt(gtGroups[1]);
                    var gtAllele1 = parseInt(gtGroups[3]);

                    if (gtAllele0 > altSplits.length || gtAllele1 > altSplits.length) {
                        this.addLog("error", "An allele index must not be greater than the number of alleles in that variant");
                    }

                }
            }

            if (sampleDataSplit.length != formatSplits.length) {
                this.addLog("error", "The number of sub-fields can not be greater than the number in the FORMAT column. Expected : " + formatSplits.length + ", found: " + sampleDataSplit.length);
            }
        }
    }
}

VCFValidator.prototype._checkReferenceTag = function () {

    for (var i = 0; i < this._headerElements.length; i++) {
        var headerElement = this._headerElements[i];
        if (headerElement.id === "reference") {
            return;
        }
    }
    this.addLog("warning", "The tag 'reference' must be present");

}
<commit_msg>Added FILTER and INFO specifications to vcf-validator.js<commit_after>function VCFValidator(options) {
    if (!(this instanceof VCFValidator)) {
        return new VCFValidator(options);
    }
    Validator.call(this, options);

    this._header = true;
    this._fileFormat = false;
    this._contigs = {};
    this._info = {};
    this._format = {};
    this._filter = {};
    this._headerElements = [];
    this._samples = [];
    this._columnsSize = 0;
    this._refTag = false;

    this._duplicates = {};
    this._sorted = true;

    this._prevChrPos = null;

    this._regExp = {
        "headerId": /ID=(\w+)/,
        "headerNumber": /Number=(\w+|\.)/,
        "headerType": /Type=(\w+)/,
        "headerDesc": /Description=\"(.+)\"/,
        "actg": /^[ACGTN]+$/,
        "gt": /^(\.|\d+)([|/](\.|\d+))?$/,
        "alpha": /^(\w+)$/,
        "idSemiColon": /^(\w+(;\w+)?)$/
    }
}

VCFValidator.prototype = Object.create(Validator.prototype);

VCFValidator.prototype.validateLine = function (line) {

    if (this._header) { // parse Header
        this.parseHeader(line);
    } else {
        if (!this._refTag) {
            this._checkReferenceTag();
            this._refTag = true;
        }
        if (!this._fileFormat) {
            this.addLog("error", "The file format declaration must be present");
            this._fileFormat = true;
        }
        this.parseData(line);
    }

}

VCFValidator.prototype.isHeaderLine = function (line) {
    return line.startsWith("#");
}

VCFValidator.prototype.parseHeader = function (line) {
    if (this.line == 0) { // parse fileformat
        if (!line.startsWith("##fileformat=")) {
            this.addLog("error", "The file format declaration must be the first line in the file");
        }
    }

    if (line.startsWith("##")) {
        var lineData = line.substring(2);
        var key = lineData.substring(0, lineData.indexOf("="))
        var value = lineData.substring(lineData.indexOf("=") + 1)

        if (key == "" || value == "") {
            this.addLog("error", "All lines must be key=value pairs.")
        }

        if (key.toLowerCase() == "fileformat") {
            this._fileFormat = true;

        } else if (key.toLowerCase() == "contig") {
            var contigId = this._getDataFromRegExp(value, "headerId");
            this._contigs[contigId] = contigId;
        } else if (key.toLowerCase() == "info") {
            var id = this._getDataFromRegExp(value, "headerId");
            var number = this._getDataFromRegExp(value, "headerNumber");
            var type = this._getDataFromRegExp(value, "headerType");
            var description = this._getDataFromRegExp(value, "headerDesc");

            if (id == null || number == null || type == null || description == null) {
                this.addLog("error", "INFO fields must be described as ##INFO=<ID=ID,Number=number,Type=type,Description='description'> ")
            }

            this._info[id] = {
                id: id,
                number: number,
                type: type,
                description: description
            }
        } else if (key.toLowerCase() == "format") {
            var id = this._getDataFromRegExp(value, "headerId");
            var number = this._getDataFromRegExp(value, "headerNumber");
            var type = this._getDataFromRegExp(value, "headerType");
            var description = this._getDataFromRegExp(value, "headerDesc");

            if (id == null || number == null || type == null || description == null) {
                this.addLog("error", "FORMAT fields must be described as ##FORMAT=<ID=ID,Number=number,Type=type,Description='description'> ")
            }

            this._format[id] = {
                id: id,
                number: number,
                type: type,
                description: description
            }
        } else if (key.toLowerCase() == "filter") {
            var id = this._getDataFromRegExp(value, "headerId");
            var description = this._getDataFromRegExp(value, "headerDesc");

            if (id == null || description == null) {
                this.addLog("error", "FILTER fields must be described as ##FILTER=<ID=ID,Description='description'>")
            }

            this._filter[id] = {
                id: id,
                description: description
            }

        } else {

            this._headerElements.push({
                id: key,
                value: value
            });
        }

    } else if (line.startsWith("#CHROM")) {
        this._header = false;
        splits = line.split("\t");
        this._columnsSize = splits.length;
        if (splits.length < 8) {
            this.addLog("error", "Must contain at least 8 columns(CHROM to INFO)");
        } else if (splits.length > 9) {
            for (var i = 9; i < splits.length; i++) {
                this._samples.push(splits[i]);
            }
        }
    } else {
        this.addLog("error", "All header lines must be prefixed by '##");
    }
}

VCFValidator.prototype._getDataFromRegExp = function (data, regExpId) {
    var regExp = this._regExp[regExpId];
    if (regExp == null) {
        return null;
    }
    if (regExp.test(data)) {
        var groups = regExp.exec(data);
        if (groups != null && groups.length > 1) {
            return groups[1];
        }
    }
    return null;

}

VCFValidator.prototype.parseData = function (line) {
    if (this.isHeaderLine(line)) {
        this.addLog("error", "Must not start with a header prefix (#)");
        return;
    }

    var columns = line.split("\t");

    if (columns.length < 9) {
        this.addLog("error", "Must contain at least 8 columns(CHROM to INFO)");
    } else if (columns.length - 9 != this._samples.length) {
        this.addLog("error", "The number of samples must match those listed in the header");
    }

    if (this._columnsSize != columns.length) {
        this.addLog("error", "All ines must have the same number of columns");
    }

    // Chromosome
    var chr = columns[0];
    if (chr == "") {
        this.addLog("error", "Chromosome must not be empty");
    }
    if (chr.search(":") >= 0) {
        this.addLog("error", "Chromosome must not contain colons");
    }
    if (chr.search(" ") >= 0) {
        this.addLog("error", "Chromosome must not contain whitespaces");
    }

    // Position
    var pos = columns[1]
    if (pos == "") {
        this.addLog("error", "Position must not be empty");
    }
    pos = parseInt(pos)
    if (isNaN(pos)) {
        this.addLog("error", "Position must be numeric");
    }

    // Check duplicates
    var chr_pos = chr + "_" + pos;
    if (chr_pos in this._duplicates) {
        this.addLog("warning", "There must be no chromosome+position duplicates");
    }

    this._duplicates[chr_pos] = null;

    // Id

    var id = columns[2];

    if (id != ".") {
        if (!this._regExp["alpha"].test(id)) {
            this.addLog("error", "ID must be alphanumeric");
        }

        if (!this._regExp["idSemiColon"].test(id)) {
            this.addLog("error", "If more than one ID is specified, the must be semo-colon separated");
        }
    }

    // ref

    var ref = columns[3];
    if (ref == "") {
        this.addLog("error", "Reference allele must not be empty");
    }

    if (!this._regExp["actg"].test(ref)) {
        this.addLog("error", "Reference allele must match the regular expression /^[ACTGN]+$/");
    }

    // alt
    var alt = columns[4];

    if (alt == undefined) {
        debugger
    }

    var altSplits = alt.split(",");
    var altSplitsUnique = altSplits.filter(function (item, pos) {
        return altSplits.indexOf(item) == pos;
    });

    if (altSplits.length != altSplitsUnique.length) {
        this.addLog("error", "Alternate must contain each allele only once")
    }

    if (altSplitsUnique.indexOf(ref) >= 0) {
        this.addLog("error", "The reference allele must not be in the list of alternate alleles")
    }

    for (var i = 0; i < altSplits.length; i++) {
        var altElem = altSplits[i];

        if (!this._regExp["actg"].test(altElem)) {
            this.addLog("error", "Alternate allele must match the regular expression /^[ACTGN]+$/")
        }

        if (altElem.length != ref.length) {
            if (altElem.charAt(0) != ref.charAt(0)) {
                this.addLog("warning", "The first base of each allele must match the reference if their lengths are different");
            }
        }
    }

    // Qual
    var qual = columns[5];
    if (qual != ".") {
        var qualFloat = parseFloat(qual);
        if (isNaN(qualFloat) || qualFloat < 0) {
            this.addLog("error", "Quality must be a non-negative float");
        }

    }

    // Filter
    var filter = columns[6];
    if (filter == "") {
        this.addLog("error", "Filter status field must not be empty");
    }
    if (filter.toLowerCase() != 'pass') {
        var filterIds = filter.split(";");
        for (var i = 0; i < filterIds.length; i++) {
            var id = filterIds[i];
            if (this._filter[id.toLowerCase()] == null) {
                this.addLog("error", "Filter status must be specified in header, be PASS or be set to the missing value '.'");
            }
        }
    }

    // Info
    var info = columns[7];
    if (info == "") {
        this.addLog("error", "Info field must not be empty");
    }
    var infoFields = info.split(";");
    // debugger
    // if (infoFields.length != Object.keys(this._info).length) {
    //   this.addLog("eror", "Info must have the same number of fields specified in header");
    // }
    if (infoFields.length != 1 || infoFields[0] != '.') {
        for (var i = 0; i < infoFields.length; i++) {
            var field = infoFields[i];
            if (field.indexOf("=") > 0) {
                var key = field.substring(0, field.indexOf("="));
                var value = field.substring(field.indexOf("=") + 1);
                if (this._info[key] == null) {
                    this.addLog("error", "Info field must be specified in header");
                } else {
                    var v = value.split(",");
                    if (v.length > 1) {
                        if (this._info[key].number == 'A') {
                          //'A': one value per alternate
                          var n=alt.split(",").length;
                          if(n != v.length){
                            this.addLog("error", "If Info number is 'A', value must have one value per alternate");
                          }
                            //TODO: 'R': one value for each posible allele
                            //TODO: 'G': one value for each posible genotype

                        } else if (this._info[key].number < v.length) {
                            this.addLog("error", "Number of values in info must be less or equal than Number in Info field");
                        }
                        if (this._info[key].type == 'Flag') {
                            this.addLog("error", "Flag type must not have value");
                        } else {
                          for (var i = 0; i < v.length; i++) {
                            var auxV=v[i];
                          }
                            if (this._info[key].type == 'Integer' && Number.isInteger(auxV) == false) {
                                this.addLog("error", "Info type and value type must be the same");
                            } else if (this._info[key].type == 'String' && isNaN(auxV) == false) {
                                this.addLog("error", "Info type and value type must be the same");
                            // } else if (this._info[key].type == 'Float' && Number.isFloat(auxV) == false) {
                            //     this.addLog("error", "Info type and value type must be the same");
                            }
                        }
                    }
                }
            } else {
                if (this._info[key] == null) {
                    this.addLog("error", "Info field must be specified in header");
                } else if (this._info[key].type != "Flag") {
                    this.addLog("error", "Info field must be a Flag type or have a data value");
                } else {
                    if (this._info[key].number != 0) {
                        this.addLog("error", "In Info, Number must be 0 for a Flag type");
                    }
                }
            }
        }
    }

    if (!columns.length > 8) {
        return;
    }

    // Format
    var format = columns[8];

    if (this._samples.length > 0 && format == "") {
        this.addLog("eror", "Must not be empty if the file contains any samples");
    }

    if (format != "" && !format.startsWith("GT")) {
        this.addLog("error", "GT must be the first field");
    }

    var formatSplits = format.split(":");

    // Samples
    var samplesData = [];

    for (var i = 9; i < columns.length; i++) {
        samplesData.push(columns[i]);
    }

    for (var i = 0; i < samplesData.length; i++) {
        var sampleData = samplesData[i];

        if (sampleData == "") {
            this.addLog("error", "Sample fields must be not empty");
        } else {
            var sampleDataSplit = sampleData.split(":");
            var gt = sampleDataSplit[0];
            if (!this._regExp["gt"].test(gt)) {
                this.addLog("error", "GT must match the regular expression ^(\.|\d+)([|/]?)");
            } else {
                var gtGroups = this._regExp["gt"].exec(gt);
                if (gtGroups.length == 2) { // GT = 0,1
                    var gtAllele = parseInt(gtGroups[1]);
                    if (gtAllele > altSplits.length) {
                        this.addLog("error", "An allele index must not be greater than the number of alleles in that variant");
                    }
                } else if (gtGroups.length == 4) { // GT = 0/0,0/1,....
                    var gtAllele0 = parseInt(gtGroups[1]);
                    var gtAllele1 = parseInt(gtGroups[3]);

                    if (gtAllele0 > altSplits.length || gtAllele1 > altSplits.length) {
                        this.addLog("error", "An allele index must not be greater than the number of alleles in that variant");
                    }

                }
            }

            if (sampleDataSplit.length != formatSplits.length) {
                this.addLog("error", "The number of sub-fields can not be greater than the number in the FORMAT column. Expected : " + formatSplits.length + ", found: " + sampleDataSplit.length);
            }
        }
    }
}

VCFValidator.prototype._checkReferenceTag = function () {

    for (var i = 0; i < this._headerElements.length; i++) {
        var headerElement = this._headerElements[i];
        if (headerElement.id === "reference") {
            return;
        }
    }
    this.addLog("warning", "The tag 'reference' must be present");

}
<|endoftext|>


<commit_before>import 'bootstrap/js/dropdown'
import 'bootstrap/js/tooltip'
import 'bootstrap/js/popover'

import {queryByHook} from '../util'

export default class {
  constructor (opts) {
    const elements = {
      loginLink: queryByHook('login-link', opts.el),
      logoutLink: queryByHook('logout-link', opts.el),
      adminLinkListItem: queryByHook('admin-link-list-item', opts.el),
      userName: queryByHook('user-name', opts.el),
      userDropdown: queryByHook('user-dropdown', opts.el),
      userDropdownLink: queryByHook('user-dropdown-link', opts.el),
      userIssue: queryByHook('user-issue', opts.el)
    }

    elements.loginLink.on('click', function (e) {
      opts.user.initiateLogin()
      e.preventDefault()
    })

    elements.logoutLink.on('click', function (e) {
      opts.user.logout()
      window.location.reload(true)
      e.preventDefault()
    })

    if (opts.user.username) this._setUserInfo(opts.user, elements)
    opts.user.on('change', (user) => {
      if (user.username) this._setUserInfo(user, elements)
    })
  }

  _setUserInfo (user, elements) {
    elements.loginLink.hide()
    elements.userName.text(user.username)
    elements.userDropdown.removeClass('hidden')
    if (user.isCollaborator) {
      elements.adminLinkListItem.show()
    } else if (user.isCollaborator === false) { // don't catch undefined
      elements.userIssue.show()
      elements.userDropdownLink.popover({
        content: 'You do not have collaborator access to this repository, so you will not be able to make any changes.',
        placement: 'bottom',
        trigger: 'hover',
        container: 'body'
      })
    }
  }
}
<commit_msg>include bootstrap/collapse plugin. fixes #113<commit_after>import 'bootstrap/js/dropdown'
import 'bootstrap/js/tooltip'
import 'bootstrap/js/popover'
import 'bootstrap/js/collapse'

import {queryByHook} from '../util'

export default class {
  constructor (opts) {
    const elements = {
      loginLink: queryByHook('login-link', opts.el),
      logoutLink: queryByHook('logout-link', opts.el),
      adminLinkListItem: queryByHook('admin-link-list-item', opts.el),
      userName: queryByHook('user-name', opts.el),
      userDropdown: queryByHook('user-dropdown', opts.el),
      userDropdownLink: queryByHook('user-dropdown-link', opts.el),
      userIssue: queryByHook('user-issue', opts.el)
    }

    elements.loginLink.on('click', function (e) {
      opts.user.initiateLogin()
      e.preventDefault()
    })

    elements.logoutLink.on('click', function (e) {
      opts.user.logout()
      window.location.reload(true)
      e.preventDefault()
    })

    if (opts.user.username) this._setUserInfo(opts.user, elements)
    opts.user.on('change', (user) => {
      if (user.username) this._setUserInfo(user, elements)
    })
  }

  _setUserInfo (user, elements) {
    elements.loginLink.hide()
    elements.userName.text(user.username)
    elements.userDropdown.removeClass('hidden')
    if (user.isCollaborator) {
      elements.adminLinkListItem.show()
    } else if (user.isCollaborator === false) { // don't catch undefined
      elements.userIssue.show()
      elements.userDropdownLink.popover({
        content: 'You do not have collaborator access to this repository, so you will not be able to make any changes.',
        placement: 'bottom',
        trigger: 'hover',
        container: 'body'
      })
    }
  }
}
<|endoftext|>


<commit_before>// Copyright 2014 Google Inc.
//
// Licensed under the Apache 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.apache.org/licenses/LICENSE-2.0
//
// 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.

goog.require('axs.AuditRule');
goog.require('axs.AuditRules');
goog.require('axs.constants.Severity');
goog.require('axs.properties');

/**
 * Based on recommendations in document: http://www.w3.org/TR/aria-in-html/
 * @type {axs.AuditRule.Spec}
 */
axs.AuditRule.specs.ariaOnReservedElement = {
    name: 'ariaOnReservedElement',
    heading: 'This element does not support ARIA roles, states and properties',
    url: '',
    severity: axs.constants.Severity.WARNING,
    relevantElementMatcher: function(element) {
        return !axs.properties.canTakeAriaAttributes(element);
    },
    test: function(element) {
        return !!axs.properties.getAriaProperties(element);
    },
    code: 'AX_ARIA_12'
};
<commit_msg>Actioning pull request feedback.<commit_after>// Copyright 2014 Google Inc.
//
// Licensed under the Apache 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.apache.org/licenses/LICENSE-2.0
//
// 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.

goog.require('axs.AuditRule');
goog.require('axs.AuditRules');
goog.require('axs.constants.Severity');
goog.require('axs.properties');

/**
 * Based on recommendations in document: http://www.w3.org/TR/aria-in-html/
 * @type {axs.AuditRule.Spec}
 */
axs.AuditRule.specs.ariaOnReservedElement = {
    name: 'ariaOnReservedElement',
    heading: 'This element does not support ARIA roles, states and properties',
    url: '',
    severity: axs.constants.Severity.WARNING,
    relevantElementMatcher: function(element) {
        return !axs.properties.canTakeAriaAttributes(element);
    },
    test: function(element) {
        return axs.properties.getAriaProperties(element) !== null;
    },
    code: 'AX_ARIA_12'
};
<|endoftext|>


<commit_before>module.exports = function (dates) {
  var badges = {
    daysInRow: {
      name: 'Consistency',
      id: 14,
      tiers: {1: 5, 2: 20, 3: 50}
    }
  };

  function sequentializeDates (dates) {
    var k = 0;
    var sorted = [];
    sorted[k] = [];
    dates.sort(function (a, b) {
      return +a > +b ? 1 : +a === +b ? 0 : -1;
    })
    .forEach(function (v, i) {
      var a = v;
      var b = dates[i + 1] || 0;
      sorted[k].push(+a);
      if ((+b - +a) > 86400000) {
        sorted[++k] = [];
      }
      return 1;
    });
    sorted.sort(function (a, b) {
      return a.length > b.length ? -1 : 1;
    });
    return sorted;
  }

  function checkBadge (sequentialDates, badge) {
    for (var i = 0; i < sequentialDates.length; ++i) {
      var dayStreakLength = sequentialDates[i].length;
      if (dayStreakLength >= badge.tiers[1] && dayStreakLength < badge.tiers[2]) {
        return 1;
      } else if (dayStreakLength >= badge.tiers[2] && dayStreakLength < badge.tiers[3]) {
        return 2;
      } else if (dayStreakLength >= badge.tiers[3]) {
        return 3;
      } else {
        return null;
      }
    }
  }

  var sequentialDates = sequentializeDates(dates);
  var badgeLevel = checkBadge(sequentialDates, badges.daysInRow);

  var earnedBadges = {};
  if (badgeLevel !== null) {
    earnedBadges['consistency'] = badgeLevel;
  }

  return earnedBadges;
};
<commit_msg>Changed function name for consistency<commit_after>module.exports = function (dates) {
  var badges = {
    daysInRow: {
      name: 'Consistency',
      id: 14,
      tiers: {1: 5, 2: 20, 3: 50}
    }
  };

  function sequentializeDates (dates) {
    var k = 0;
    var sorted = [];
    sorted[k] = [];
    dates.sort(function (a, b) {
      return +a > +b ? 1 : +a === +b ? 0 : -1;
    })
    .forEach(function (v, i) {
      var a = v;
      var b = dates[i + 1] || 0;
      sorted[k].push(+a);
      if ((+b - +a) > 86400000) {
        sorted[++k] = [];
      }
      return 1;
    });
    sorted.sort(function (a, b) {
      return a.length > b.length ? -1 : 1;
    });
    return sorted;
  }

  function checkBadgeLevel (sequentialDates, badge) {
    for (var i = 0; i < sequentialDates.length; ++i) {
      var dayStreakLength = sequentialDates[i].length;
      if (dayStreakLength >= badge.tiers[1] && dayStreakLength < badge.tiers[2]) {
        return 1;
      } else if (dayStreakLength >= badge.tiers[2] && dayStreakLength < badge.tiers[3]) {
        return 2;
      } else if (dayStreakLength >= badge.tiers[3]) {
        return 3;
      } else {
        return null;
      }
    }
  }

  var sequentialDates = sequentializeDates(dates);
  var badgeLevel = checkBadgeLevel(sequentialDates, badges.daysInRow);

  var earnedBadges = {};
  if (badgeLevel !== null) {
    earnedBadges['consistency'] = badgeLevel;
  }

  return earnedBadges;
};
<|endoftext|>


<commit_before>define(['itemObjectModel'], function(itemObjectModel) {

  'use strict';

  function open(item) {
    return item.getBytes().then(function(bytes) {
      if (String.fromCharCode.apply(null, bytes.subarray(0, 4)) !== 'RIFX'
        || String.fromCharCode.apply(null, bytes.subarray(8, 12)) !== 'MV93') {
        return Promise.reject('not a MV93 RIFX file');
      }
      var dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
      if (bytes.length < dv.getUint32(4, false) + 8) {
        return Promise.reject('bad length');
      }
      item.notifyPopulating(new Promise(function(resolve, reject) {
        for (var pos = 12; pos < bytes.length; ) {
          var chunkItem = itemObjectModel.createItem(String.fromCharCode.apply(null, bytes.subarray(pos, pos+4)));
          var chunkLen = dv.getUint32(pos + 4);
          if (chunkLen !== 0) {
            chunkItem.byteSource = item.byteSource.slice(pos + 8, pos + 8 + dv.getUint32(pos + 4));
          }
          item.addItem(chunkItem);
          pos += 8 + chunkLen + chunkLen % 2;
        }
      }));

    });
  }

  return open;

});
<commit_msg>Remember to actually resolve the Promise<commit_after>define(['itemObjectModel'], function(itemObjectModel) {

  'use strict';

  function open(item) {
    return item.getBytes().then(function(bytes) {
      if (String.fromCharCode.apply(null, bytes.subarray(0, 4)) !== 'RIFX'
        || String.fromCharCode.apply(null, bytes.subarray(8, 12)) !== 'MV93') {
        return Promise.reject('not a MV93 RIFX file');
      }
      var dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
      if (bytes.length < dv.getUint32(4, false) + 8) {
        return Promise.reject('bad length');
      }
      item.notifyPopulating(new Promise(function(resolve, reject) {
        for (var pos = 12; pos < bytes.length; ) {
          var chunkItem = itemObjectModel.createItem(String.fromCharCode.apply(null, bytes.subarray(pos, pos+4)));
          var chunkLen = dv.getUint32(pos + 4);
          if (chunkLen !== 0) {
            chunkItem.byteSource = item.byteSource.slice(pos + 8, pos + 8 + dv.getUint32(pos + 4));
          }
          item.addItem(chunkItem);
          pos += 8 + chunkLen + chunkLen % 2;
        }
        resolve(item);
      }));

    });
  }

  return open;

});
<|endoftext|>


<commit_before>define(function() {

  'use strict';

  function open(item) {
    return item.getBytes().then(function(bytes) {
      var deltas = new Int8Array(bytes.buffer, bytes.byteOffset + 4, 16);
      bytes = bytes.subarray(20);
      var samples = new Uint8Array(2 * bytes.length);
      var value = 0x80;
      for (var i = 0; i < bytes.length; i++) {
        value += deltas[bytes[i] & 0xf];
        value &= 0xff;
        samples[i*2] = value;
        value += deltas[(bytes[i] >> 4) & 0xf];
        value &= 0xff;
        samples[i*2 + 1] = value;
      }
      item.setRawAudio({
        channels: 1,
        samplingRate: 11000,
        bytesPerSample: 1,
        samples: samples,
      });
    });
  }

  return open;

});
<commit_msg>Try not forcing rolling value to a byte<commit_after>define(function() {

  'use strict';

  function open(item) {
    return item.getBytes().then(function(bytes) {
      var deltas = new Int8Array(bytes.buffer, bytes.byteOffset + 4, 16);
      bytes = bytes.subarray(20);
      var samples = new Uint8Array(2 * bytes.length);
      var value = 0x80;
      for (var i = 0; i < bytes.length; i++) {
        value += deltas[bytes[i] & 0xf];
        samples[i*2] = value & 0xff;
        value += deltas[(bytes[i] >> 4) & 0xf];
        samples[i*2 + 1] = value & 0xff;
      }
      item.setRawAudio({
        channels: 1,
        samplingRate: 11000,
        bytesPerSample: 1,
        samples: samples,
      });
    });
  }

  return open;

});
<|endoftext|>