<commit_before>/**
 * JavaScript for the user selectors.
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 * @package userselector
 */

// Define the core_user namespace if it has not already been defined
M.core_user = M.core_user || {};
// Define a user selectors array for against the cure_user namespace
M.core_user.user_selectors = [];
/**
 * Retrieves an instantiated user selector or null if there isn't one by the requested name
 * @param {string} name The name of the selector to retrieve
 * @return bool
 */
M.core_user.get_user_selector = function (name) {
    return this.user_selectors[name] || null;
};

/**
 * Initialise a new user selector.
 *
 * @param {YUI} Y The YUI3 instance
 * @param {string} name the control name/id.
 * @param {string} hash the hash that identifies this selector in the user's session.
 * @param {array} extrafields extra fields we are displaying for each user in addition to fullname.
 * @param {string} lastsearch The last search that took place
 */
M.core_user.init_user_selector = function (Y, name, hash, extrafields, lastsearch) {
    // Creates a new user_selector object
    var user_selector = {
        /** This id/name used for this control in the HTML. */
        name : name,
        /** Array of fields to display for each user, in addition to fullname. */
        extrafields: extrafields,
        /** Number of seconds to delay before submitting a query request */
        querydelay : 0.5,
        /** The input element that contains the search term. */
        searchfield : Y.one('#'+name + '_searchtext'),
        /** The clear button. */
        clearbutton : null,
        /** The select element that contains the list of users. */
        listbox : Y.one('#'+name),
        /** Used to hold the timeout id of the timeout that waits before doing a search. */
        timeoutid : null,
        /** The last string that we searched for, so we can avoid unnecessary repeat searches. */
        lastsearch : lastsearch,
        /** Whether any options where selected last time we checked. Used by
         *  handle_selection_change to track when this status changes. */
        selectionempty : true,
        /**
         * Initialises the user selector object
         * @constructor
         */
        init : function() {
            // Hide the search button and replace it with a label.
            var searchbutton = Y.one('#'+this.name + '_searchbutton');
            this.searchfield.insert(Y.Node.create(''+searchbutton.get('value')+''), this.searchfield);
            searchbutton.remove();

            // Hook up the event handler for when the search text changes.
            this.searchfield.on('keyup', this.handle_keyup, this);

            // Hook up the event handler for when the selection changes.
            this.listbox.on('keyup', this.handle_selection_change, this);
            this.listbox.on('click', this.handle_selection_change, this);
            this.listbox.on('change', this.handle_selection_change, this);

            // And when the search any substring preference changes. Do an immediate re-search.
            Y.one('#userselector_searchanywhereid').on('click', this.handle_searchanywhere_change, this);

            // Define our custom event.
            //this.createEvent('selectionchanged');
            this.selectionempty = this.is_selection_empty();

            // Replace the Clear submit button with a clone that is not a submit button.
            var clearbtn = Y.one('#'+this.name + '_clearbutton');
            this.clearbutton = Y.Node.create('<input type="button" value="'+clearbtn.get('value')+'" />');
            clearbtn.replace(Y.Node.getDOMNode(this.clearbutton));
            this.clearbutton.set('id', this.name+"_clearbutton");
            this.clearbutton.on('click', this.handle_clear, this);
            this.clearbutton.set('disabled', (this.get_search_text() == ''));

            this.send_query(false);
        },
        /**
         * Key up hander for the search text box.
         * @param {Y.Event} e the keyup event.
         */
        handle_keyup : function(e) {
            //  Trigger an ajax search after a delay.
            this.cancel_timeout();
            this.timeoutid = Y.later(this.querydelay*1000, e, function(obj){obj.send_query(false)}, this);

            // Enable or diable the clear button.
            this.clearbutton.set('disabled', (this.get_search_text() == ''));

            // If enter was pressed, prevent a form submission from happening.
            if (e.keyCode == 13) {
                e.halt();
            }
        },
        /**
         * Handles when the selection has changed. If the selection has changed from
         * empty to not-empty, or vice versa, then fire the event handlers.
         */
        handle_selection_change : function() {
            var isselectionempty = this.is_selection_empty();
            if (isselectionempty !== this.selectionempty) {
                this.fire('user_selector:selectionchanged', isselectionempty);
            }
            this.selectionempty = isselectionempty;
        },
        /**
         * Trigger a re-search when the 'search any substring' option is changed.
         */
        handle_searchanywhere_change : function() {
            if (this.lastsearch != '' && this.get_search_text() != '') {
                this.send_query(true);
            }
        },
        /**
         * Click handler for the clear button..
         */
        handle_clear : function() {
            this.searchfield.set('value', '');
            this.clearbutton.set('disabled',true);
            this.send_query(false);
        },
        /**
         * Fires off the ajax search request.
         */
        send_query : function(forceresearch) {
            // Cancel any pending timeout.
            this.cancel_timeout();

            var value = this.get_search_text();
            this.searchfield.set('class', '');
            if (this.lastsearch == value && !forceresearch) {
                return;
            }

            Y.io(M.cfg.wwwroot + '/user/selector/search.php', {
                method: 'POST',
                data: 'selectorid='+hash+'&sesskey='+M.cfg.sesskey+'&search='+value + '&userselector_searchanywhere=' + this.get_option('searchanywhere'),
                on: {
                    success:this.handle_response,
                    failure:this.handle_failure
                },
                context:this
            });

            this.lastsearch = value;
            this.listbox.setStyle('background','url(' + M.util.image_url('i/loading', 'moodle') + ') no-repeat center center');
        },
        /**
         * Handle what happens when we get some data back from the search.
         * @param {int} requestid not used.
         * @param {object} response the list of users that was returned.
         */
        handle_response : function(requestid, response) {
            try {
                this.listbox.setStyle('background','');
                var data = Y.JSON.parse(response.responseText);
                this.output_options(data);
            } catch (e) {
                this.handle_failure();
            }
        },
        /**
         * Handles what happens when the ajax request fails.
         */
        handle_failure : function() {
            this.listbox.setStyle('background','');
            this.searchfield.addClass('error');

            // If we are in developer debug mode, output a link to help debug the failure.
            if (M.cfg.developerdebug) {
                this.searchfield.insert(Y.Node.create('Ajax call failed. Click here to try the search call directly.'));
            }
        },
        /**
         * This method should do the same sort of thing as the PHP method
         * user_selector_base::output_options.
         * @param {object} data the list of users to populate the list box with.
         */
        output_options : function(data) {
            // Clear out the existing options, keeping any ones that are already selected.
            var selectedusers = {};
            this.listbox.all('optgroup').each(function(optgroup){
                optgroup.all('option').each(function(option){
                    if (option.get('selected')) {
                        selectedusers[option.get('value')] = {
                            id : option.get('value'),
                            name : option.get('innerText') || option.get('textContent'),
                            disabled: option.get('disabled')
                        }
                    }
                    option.remove();
                }, this);
                optgroup.remove();
            }, this);

            // Output each optgroup.
            var count = 0;
            for (var groupname in data.results) {
                this.output_group(groupname, data.results[groupname], selectedusers, true);
                count++;
            }
            if (!count) {
                var searchstr = (this.lastsearch != '')?this.insert_search_into_str(M.str.moodle.nomatchingusers, this.lastsearch):M.str.moodle.none;
                this.output_group(searchstr, {}, selectedusers, true)
            }

            // If there were previously selected users who do not match the search, show them too.
            if (this.get_option('preserveselected') && selectedusers) {
                this.output_group(this.insert_search_into_str(M.str.moodle.previouslyselectedusers, this.lastsearch), selectedusers, true, false);
            }
            this.handle_selection_change();
        },
        /**
         * This method should do the same sort of thing as the PHP method
         * user_selector_base::output_optgroup.
         *
         * @param {string} groupname the label for this optgroup.v
         * @param {object} users the users to put in this optgroup.
         * @param {boolean|object} selectedusers if true, select the users in this group.
         * @param {boolean} processsingle
         */
        output_group : function(groupname, users, selectedusers, processsingle) {
            var optgroup = Y.Node.create('<optgroup></optgroup>');
            var count = 0;
            for (var userid in users) {
                var user = users[userid];
                var option = Y.Node.create('<option value="'+userid+'">'+user.name+'</option>');
                if (user.disabled) {
                    option.set('disabled', true);
                } else if (selectedusers===true || selectedusers[userid]) {
                    option.set('selected', true);
                } else {
                    option.set('selected', false);
                }
                optgroup.append(option);
                if (user.infobelow) {
                    extraoption = Y.Node.create('<option disabled="disabled" class="userselector-infobelow"/>');
                    extraoption.appendChild(document.createTextNode(user.infobelow));
                    optgroup.append(extraoption);
                }
                count++;
            }

            if (count > 0) {
                optgroup.set('label', groupname+' ('+count+')');
                if (processsingle && count===1 && this.get_option('autoselectunique') && option.get('disabled')) {
                    option.set('selected', true);
                }
            } else {
                optgroup.append(Y.Node.create('<option disabled="disabled">\u00A0</option>'));
            }
            this.listbox.append(optgroup);
        },
        /**
         * Replace
         * @param {string} str
         * @param {string} search The search term
         * @return string
         */
        insert_search_into_str : function(str, search) {
            return str.replace("%%SEARCHTERM%%", search);
        },
        /**
         * Gets the search text
         * @return String the value to search for, with leading and trailing whitespace trimmed.
         */
        get_search_text : function() {
            return this.searchfield.get('value').toString().replace(/^ +| +$/, '');
        },
        /**
         * Returns true if the selection is empty (nothing is selected)
         * @return Boolean check all the options and return whether any are selected.
         */
        is_selection_empty : function() {
            var selection = false;
            this.listbox.all('option').each(function(){
                if (this.get('selected')) {
                    selection = true;
                }
            });
            return !(selection);
        },
        /**
         * Cancel the search delay timeout, if there is one.
         */
        cancel_timeout : function() {
            if (this.timeoutid) {
                clearTimeout(this.timeoutid);
                this.timeoutid = null;
            }
        },
        /**
         * @param {string} name The name of the option to retrieve
         * @return the value of one of the option checkboxes.
         */
        get_option : function(name) {
            var checkbox = Y.one('#userselector_' + name + 'id');
            if (checkbox) {
                return (checkbox.get('checked'));
            } else {
                return false;
            }
        }
    };
    // Augment the user selector with the EventTarget class so that we can use
    // custom events
    Y.augment(user_selector, Y.EventTarget, null, null, {});
    // Initialise the user selector
    user_selector.init();
    // Store the user selector so that it can be retrieved
    this.user_selectors[name] = user_selector;
    // Return the user selector
    return user_selector;
};

/**
 * Initialise a class that updates the user's preferences when they change one of
 * the options checkboxes.
 * @constructor
 * @param {YUI} Y
 * @return Tracker object
 */
M.core_user.init_user_selector_options_tracker = function(Y) {
    // Create a user selector options tracker
    var user_selector_options_tracker = {
        /**
         * Initlises the option tracker and gets everything going.
         * @constructor
         */
        init : function() {
            var settings = [
                'userselector_preserveselected',
                'userselector_autoselectunique',
                'userselector_searchanywhere'
            ];
            for (var s in settings) {
                var setting = settings[s];
                Y.one('#'+setting+'id').on('click', this.set_user_preference, this, setting);
            }
        },
        /**
         * Sets a user preference for the options tracker
         * @param {Y.Event|null} e
         * @param {string} name The name of the preference to set
         */
        set_user_preference : function(e, name) {
            M.util.set_user_preference(name, Y.one('#'+name+'id').get('checked'));
        }
    };
    // Initialise the options tracker
    user_selector_options_tracker.init();
    // Return it just incase it is ever wanted
    return user_selector_options_tracker;
};<commit_msg>MDL-35680 user selector: more usable when queries are slow.<commit_after>/**
 * JavaScript for the user selectors.
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 * @package userselector
 */

// Define the core_user namespace if it has not already been defined
M.core_user = M.core_user || {};
// Define a user selectors array for against the cure_user namespace
M.core_user.user_selectors = [];
/**
 * Retrieves an instantiated user selector or null if there isn't one by the requested name
 * @param {string} name The name of the selector to retrieve
 * @return bool
 */
M.core_user.get_user_selector = function (name) {
    return this.user_selectors[name] || null;
};

/**
 * Initialise a new user selector.
 *
 * @param {YUI} Y The YUI3 instance
 * @param {string} name the control name/id.
 * @param {string} hash the hash that identifies this selector in the user's session.
 * @param {array} extrafields extra fields we are displaying for each user in addition to fullname.
 * @param {string} lastsearch The last search that took place
 */
M.core_user.init_user_selector = function (Y, name, hash, extrafields, lastsearch) {
    // Creates a new user_selector object
    var user_selector = {
        /** This id/name used for this control in the HTML. */
        name : name,
        /** Array of fields to display for each user, in addition to fullname. */
        extrafields: extrafields,
        /** Number of seconds to delay before submitting a query request */
        querydelay : 0.5,
        /** The input element that contains the search term. */
        searchfield : Y.one('#'+name + '_searchtext'),
        /** The clear button. */
        clearbutton : null,
        /** The select element that contains the list of users. */
        listbox : Y.one('#'+name),
        /** Used to hold the timeout id of the timeout that waits before doing a search. */
        timeoutid : null,
        /** Stores any in-progress remote requests. */
        iotransactions : {},
        /** The last string that we searched for, so we can avoid unnecessary repeat searches. */
        lastsearch : lastsearch,
        /** Whether any options where selected last time we checked. Used by
         *  handle_selection_change to track when this status changes. */
        selectionempty : true,
        /**
         * Initialises the user selector object
         * @constructor
         */
        init : function() {
            // Hide the search button and replace it with a label.
            var searchbutton = Y.one('#'+this.name + '_searchbutton');
            this.searchfield.insert(Y.Node.create(''+searchbutton.get('value')+''), this.searchfield);
            searchbutton.remove();

            // Hook up the event handler for when the search text changes.
            this.searchfield.on('keyup', this.handle_keyup, this);

            // Hook up the event handler for when the selection changes.
            this.listbox.on('keyup', this.handle_selection_change, this);
            this.listbox.on('click', this.handle_selection_change, this);
            this.listbox.on('change', this.handle_selection_change, this);

            // And when the search any substring preference changes. Do an immediate re-search.
            Y.one('#userselector_searchanywhereid').on('click', this.handle_searchanywhere_change, this);

            // Define our custom event.
            //this.createEvent('selectionchanged');
            this.selectionempty = this.is_selection_empty();

            // Replace the Clear submit button with a clone that is not a submit button.
            var clearbtn = Y.one('#'+this.name + '_clearbutton');
            this.clearbutton = Y.Node.create('<input type="button" value="'+clearbtn.get('value')+'" />');
            clearbtn.replace(Y.Node.getDOMNode(this.clearbutton));
            this.clearbutton.set('id', this.name+"_clearbutton");
            this.clearbutton.on('click', this.handle_clear, this);
            this.clearbutton.set('disabled', (this.get_search_text() == ''));

            this.send_query(false);
        },
        /**
         * Key up hander for the search text box.
         * @param {Y.Event} e the keyup event.
         */
        handle_keyup : function(e) {
            //  Trigger an ajax search after a delay.
            this.cancel_timeout();
            this.timeoutid = Y.later(this.querydelay*1000, e, function(obj){obj.send_query(false)}, this);

            // Enable or diable the clear button.
            this.clearbutton.set('disabled', (this.get_search_text() == ''));

            // If enter was pressed, prevent a form submission from happening.
            if (e.keyCode == 13) {
                e.halt();
            }
        },
        /**
         * Handles when the selection has changed. If the selection has changed from
         * empty to not-empty, or vice versa, then fire the event handlers.
         */
        handle_selection_change : function() {
            var isselectionempty = this.is_selection_empty();
            if (isselectionempty !== this.selectionempty) {
                this.fire('user_selector:selectionchanged', isselectionempty);
            }
            this.selectionempty = isselectionempty;
        },
        /**
         * Trigger a re-search when the 'search any substring' option is changed.
         */
        handle_searchanywhere_change : function() {
            if (this.lastsearch != '' && this.get_search_text() != '') {
                this.send_query(true);
            }
        },
        /**
         * Click handler for the clear button..
         */
        handle_clear : function() {
            this.searchfield.set('value', '');
            this.clearbutton.set('disabled',true);
            this.send_query(false);
        },
        /**
         * Fires off the ajax search request.
         */
        send_query : function(forceresearch) {
            // Cancel any pending timeout.
            this.cancel_timeout();

            var value = this.get_search_text();
            this.searchfield.set('class', '');
            if (this.lastsearch == value && !forceresearch) {
                return;
            }

            // Try to cancel existing transactions.
            Y.Object.each(this.iotransactions, function(trans) {
                trans.abort();
            });

            var iotrans = Y.io(M.cfg.wwwroot + '/user/selector/search.php', {
                method: 'POST',
                data: 'selectorid='+hash+'&sesskey='+M.cfg.sesskey+'&search='+value + '&userselector_searchanywhere=' + this.get_option('searchanywhere'),
                on: {
                    success:this.handle_response,
                    failure:this.handle_failure
                },
                context:this
            });
            this.iotransactions[iotrans.id] = iotrans;

            this.lastsearch = value;
            this.listbox.setStyle('background','url(' + M.util.image_url('i/loading', 'moodle') + ') no-repeat center center');
        },
        /**
         * Handle what happens when we get some data back from the search.
         * @param {int} requestid not used.
         * @param {object} response the list of users that was returned.
         */
        handle_response : function(requestid, response) {
            try {
                delete this.iotransactions[requestid];
                if (!Y.Object.isEmpty(this.iotransactions)) {
                    // More searches pending. Wait until they are all done.
                    return;
                }
                this.listbox.setStyle('background','');
                var data = Y.JSON.parse(response.responseText);
                this.output_options(data);
            } catch (e) {
                this.handle_failure(requestid);
            }
        },
        /**
         * Handles what happens when the ajax request fails.
         */
        handle_failure : function(requestid) {
            delete this.iotransactions[requestid];
            if (!Y.Object.isEmpty(this.iotransactions)) {
                // More searches pending. Wait until they are all done.
                return;
            }
            this.listbox.setStyle('background','');
            this.searchfield.addClass('error');

            // If we are in developer debug mode, output a link to help debug the failure.
            if (M.cfg.developerdebug) {
                this.searchfield.insert(Y.Node.create('Ajax call failed. Click here to try the search call directly.'));
            }
        },
        /**
         * This method should do the same sort of thing as the PHP method
         * user_selector_base::output_options.
         * @param {object} data the list of users to populate the list box with.
         */
        output_options : function(data) {
            // Clear out the existing options, keeping any ones that are already selected.
            var selectedusers = {};
            this.listbox.all('optgroup').each(function(optgroup){
                optgroup.all('option').each(function(option){
                    if (option.get('selected')) {
                        selectedusers[option.get('value')] = {
                            id : option.get('value'),
                            name : option.get('innerText') || option.get('textContent'),
                            disabled: option.get('disabled')
                        }
                    }
                    option.remove();
                }, this);
                optgroup.remove();
            }, this);

            // Output each optgroup.
            var count = 0;
            for (var groupname in data.results) {
                this.output_group(groupname, data.results[groupname], selectedusers, true);
                count++;
            }
            if (!count) {
                var searchstr = (this.lastsearch != '')?this.insert_search_into_str(M.str.moodle.nomatchingusers, this.lastsearch):M.str.moodle.none;
                this.output_group(searchstr, {}, selectedusers, true)
            }

            // If there were previously selected users who do not match the search, show them too.
            if (this.get_option('preserveselected') && selectedusers) {
                this.output_group(this.insert_search_into_str(M.str.moodle.previouslyselectedusers, this.lastsearch), selectedusers, true, false);
            }
            this.handle_selection_change();
        },
        /**
         * This method should do the same sort of thing as the PHP method
         * user_selector_base::output_optgroup.
         *
         * @param {string} groupname the label for this optgroup.v
         * @param {object} users the users to put in this optgroup.
         * @param {boolean|object} selectedusers if true, select the users in this group.
         * @param {boolean} processsingle
         */
        output_group : function(groupname, users, selectedusers, processsingle) {
            var optgroup = Y.Node.create('<optgroup></optgroup>');
            var count = 0;
            for (var userid in users) {
                var user = users[userid];
                var option = Y.Node.create('<option value="'+userid+'">'+user.name+'</option>');
                if (user.disabled) {
                    option.set('disabled', true);
                } else if (selectedusers===true || selectedusers[userid]) {
                    option.set('selected', true);
                } else {
                    option.set('selected', false);
                }
                optgroup.append(option);
                if (user.infobelow) {
                    extraoption = Y.Node.create('<option disabled="disabled" class="userselector-infobelow"/>');
                    extraoption.appendChild(document.createTextNode(user.infobelow));
                    optgroup.append(extraoption);
                }
                count++;
            }

            if (count > 0) {
                optgroup.set('label', groupname+' ('+count+')');
                if (processsingle && count===1 && this.get_option('autoselectunique') && option.get('disabled')) {
                    option.set('selected', true);
                }
            } else {
                optgroup.append(Y.Node.create('<option disabled="disabled">\u00A0</option>'));
            }
            this.listbox.append(optgroup);
        },
        /**
         * Replace
         * @param {string} str
         * @param {string} search The search term
         * @return string
         */
        insert_search_into_str : function(str, search) {
            return str.replace("%%SEARCHTERM%%", search);
        },
        /**
         * Gets the search text
         * @return String the value to search for, with leading and trailing whitespace trimmed.
         */
        get_search_text : function() {
            return this.searchfield.get('value').toString().replace(/^ +| +$/, '');
        },
        /**
         * Returns true if the selection is empty (nothing is selected)
         * @return Boolean check all the options and return whether any are selected.
         */
        is_selection_empty : function() {
            var selection = false;
            this.listbox.all('option').each(function(){
                if (this.get('selected')) {
                    selection = true;
                }
            });
            return !(selection);
        },
        /**
         * Cancel the search delay timeout, if there is one.
         */
        cancel_timeout : function() {
            if (this.timeoutid) {
                clearTimeout(this.timeoutid);
                this.timeoutid = null;
            }
        },
        /**
         * @param {string} name The name of the option to retrieve
         * @return the value of one of the option checkboxes.
         */
        get_option : function(name) {
            var checkbox = Y.one('#userselector_' + name + 'id');
            if (checkbox) {
                return (checkbox.get('checked'));
            } else {
                return false;
            }
        }
    };
    // Augment the user selector with the EventTarget class so that we can use
    // custom events
    Y.augment(user_selector, Y.EventTarget, null, null, {});
    // Initialise the user selector
    user_selector.init();
    // Store the user selector so that it can be retrieved
    this.user_selectors[name] = user_selector;
    // Return the user selector
    return user_selector;
};

/**
 * Initialise a class that updates the user's preferences when they change one of
 * the options checkboxes.
 * @constructor
 * @param {YUI} Y
 * @return Tracker object
 */
M.core_user.init_user_selector_options_tracker = function(Y) {
    // Create a user selector options tracker
    var user_selector_options_tracker = {
        /**
         * Initlises the option tracker and gets everything going.
         * @constructor
         */
        init : function() {
            var settings = [
                'userselector_preserveselected',
                'userselector_autoselectunique',
                'userselector_searchanywhere'
            ];
            for (var s in settings) {
                var setting = settings[s];
                Y.one('#'+setting+'id').on('click', this.set_user_preference, this, setting);
            }
        },
        /**
         * Sets a user preference for the options tracker
         * @param {Y.Event|null} e
         * @param {string} name The name of the preference to set
         */
        set_user_preference : function(e, name) {
            M.util.set_user_preference(name, Y.one('#'+name+'id').get('checked'));
        }
    };
    // Initialise the options tracker
    user_selector_options_tracker.init();
    // Return it just incase it is ever wanted
    return user_selector_options_tracker;
};<|endoftext|>


<commit_before>/**
 * jQuery list plug-in 1.2.2
 * Copyright 2012, Digital Fusion
 * Licensed under the MIT license.
 * http://teamdf.com/jquery-plugins/license/
 *
 * @author Sam Sehnert, Phil Taylor
 */

(function($){
	"use strict";

	// The name of your plugin. This is used to namespace your
	// plugin methods, object data, and registerd events.
	var plugin_name = 'list';
	var plugin_version = '1.2.2';
	var plugin_logging = true;

	// Set up the plugin defaults.
	// These will be stored in $this.data(plugin_name).settings,
	// and can be overwritten by having 'options' passed through
	// as the parameter to the init method.
	var defaults = {
		headerSelector	: 'dt',
		zIndex			: 1
	};

	var scrollTimeout = null;

	// Any private methods that the plugin uses, such as event handlers,
	// or small utility functions for parsing data etc that isn't useful
	// outside the context of the plugin code itself.
	var _private = {

		log : function(){
			// SAFELY call console.log without fear of errors
			// also provides an override for turning off all plugin console output.
			if( plugin_logging && window.console && window.console.log ){
				try {
					// IE 8 doesn't implement apply on the console methods.
					window.console.log.apply(console,arguments);
				} catch (e) {
					// Because IE8 doesn't implement apply for console methods,
					// we simply pass the arguments directly to the log.
					window.console.log( arguments );
				}
			}
		},

		// Update the whole header object on the fly so all properties will be reserved. So setting
		// different styles for specific titles, such as different font color for today or weekends,
		// holidays, etc. becomes posible.
		updateHeader: function(target, newHeader) {
            var data = target.data(plugin_name);
            if (data) {
                data.fakeHeader = newHeader.clone().removeAttr('id').addClass('-' + plugin_name + '-fakeheader');
                data.fakeHeader.css({
                    position: 'absolute',
                    top: 0,
                    width: data.headers.width(),
                    zIndex: data.settings.zIndex
                });
                target.data(plugin_name, data).children(0).eq(0).replaceWith(data.fakeHeader);
            }
        },

		// Contains events for this plugin.
		events : {

			/**
			 * Window resize (should also be called when resizing the target element).
			 */
			resize : function(){
				var $this = $(this),data = $this.data(plugin_name);

				if( data ){
					data.fakeHeader.width(data.headers.width());
					data.wrapper.css('maxHeight',$this.css('maxHeight'));
				}
			},

			/**
			 * List element scroll handler event. Called to animate and substitute heading blocks.
			 */
			scroll : function(){
				var $this = $(this),data = $this.data(plugin_name);

				if( data ){

					var newHeader		= null,
						currentHeader	= data.headers.eq( data.currentHeader ),
						nextHeader		= data.currentHeader >= data.headers.length-1 ? null : data.headers.eq( data.currentHeader+1 ),
						prevHeader		= data.currentHeader <= 0 ? null : data.headers.eq( data.currentHeader-1 ),
						trigger			= false;

					// Make sure the container top position is fresh.
					data.containerTop	= $this.offset().top + parseInt($this.css('marginTop'),10) + parseInt($this.css('borderTopWidth'),10);
					data.fakeHeader.css('top',0);

					// Check the position of the current header rather than the previous header.
					if( prevHeader !== null ){

					 	var top		= currentHeader.offset().top,
					 		height	= currentHeader.outerHeight();

					 	if( top > data.containerTop ){

					 		data.fakeHeader.css('top',(top-height)-data.containerTop);
					 		_private.updateHeader($this, prevHeader);
					 		data.currentHeader = data.currentHeader-1;
					 		trigger = true;
					 	}

					 	if( (top-height) > data.containerTop ){

					 		data.fakeHeader.css('top',0);
					 		newHeader = data.currentHeader-1;
					 	}

					}

					// Check the position of the next header element.
					if( nextHeader !== null ){

					 	var top		= nextHeader.offset().top,
					 		height	= nextHeader.outerHeight();

					 	if( (top-height) < data.containerTop ){

					 		data.fakeHeader.css('top',(top-height)-data.containerTop);
					 	}

					 	if( top < data.containerTop ){

					 		data.fakeHeader.css('top',0);
					 		newHeader = data.currentHeader+1;
					 	}
					}

					// Now assign the contents of the previous header.
					if( newHeader !== null ){

						var $header = data.headers.eq(newHeader);

						data.currentHeader = newHeader;
						_private.updateHeader($this, $header);
						trigger = true;
					}

					var max = ( data.wrapper.scrollTop() >= data.wrapper.prop('scrollHeight') - data.wrapper.height() );

					if( trigger || max || data.max && !max ){
						// Trigger the headingChange event.
						$this.trigger('headingChange',[data.currentHeader,data.headers.eq(data.currentHeader),max]);
					}

					data.max = max;

					// Save the new data.
					$this.data(plugin_name,data);
				}
			}
		}
	}

	// The methods array will allow you to define public methods that
	// can be called using the plugin function using the following syntax;
	//
	// $('.selector').plugin_name( 'my_method'/*, optional arguments */);
	//
	// The 'init' method is special, and will be called when the user calls;
	//
	// $('.selector').plugin_name(/*{ options object }*/);
	var methods = {

		/**
		 * Initialises the plugin.
		 *
		 * @param options object : An object containing any overrides to the default settings.
		 *
		 * @return collection : Returns the jQuery collection
		 */
		init : function( options ){

			// Loop through each passed element.
			return $(this).each(function(){

				// Settings to the defaults.
				var settings = $.extend({},defaults);

				// If options exist, lets merge them
				// with our default settings.
				if( typeof options == 'object' ) $.extend( settings, options );

				// Create shortcuts, and get any existing data.
				var $this = $(this), data = $this.data(plugin_name);

				// If the plugin hasn't been initialized yet
				if ( ! data ) {

					// Create the data object.
					data = {
						target			: $this,			// This element.
						wrapper			: $this.wrapInner('<div class="ui-'+plugin_name+'" />').find('.ui-'+plugin_name+''),
						settings		: settings,			// The settings for this plugin.
						headers			: [],
						containerTop	: 0,
						currentHeader	: 0,
						fakeHeader		: null,
						scrolllist		: [],
						original		: {
							position	: '',
							overflowX	: '',
							overflowY	: ''
						},
						max				: false
					}

					// Add the container class, and the base HTML structure
					$this.addClass('-'+plugin_name+'-container').css({
						position	: $this.css('position') == 'absolute' ? 'absolute' : 'relative',
						overflowY	: 'hidden'
					});

					// Grab some variables to set up the list.
				    data.headers		= $this.find(data.settings.headerSelector);
				    data.fakeHeader		= data.headers.eq(0).clone().removeAttr('id').addClass('-'+plugin_name+'-fakeheader');

				    // bind a scroll event and change the text of the fake heading
				    data.wrapper.bind('scroll.'+plugin_name,$.proxy(_private.events.scroll,$this)).css({
				    	height		: '100%',
				    	maxHeight	: $this.css('maxHeight'),
				    	overflowY	: 'scroll',
				    	position	: 'relative'
				    });

					// Set the fake headers
				    data.fakeHeader.css({
				    	position	: 'absolute',
				    	top			: 0,
				    	width		: data.headers.width(),
				    	zIndex		: data.settings.zIndex
				    });

					// Bind the resize event to the window.
					$(window).bind('resize.'+plugin_name,$.proxy(_private.events.resize,$this));

				    // Add the fake header before all other children, and set the HTML.
				    $this.data(plugin_name,data).prepend( data.fakeHeader );
				}
			});
		},

		/**
		 * Retrieves the current header index that the user is scrolled to.
		 *
		 * @return int : The index position of the header (relative to the headers collection).
		 */
		header : function(){

			var $this = $(this),
				data = $this.data(plugin_name);

			// Only bother if we've set this up before.
			if( data ){
				return data.currentHeader;
			}
		},

		/**
		 * Used to scroll to a new header element, or to retrieve the currently set header.
		 *
		 * @param int newHeader			: The index position of the header (relative to the headers collection).
		 * @param mixed speed			: The animation speed of scrolling to the new header.
		 * @param mixed easing			: The animation easing to use.
		 * @param function completion	: The animation completion function to call.
		 *
		 * @return collection : The collection that this was called with
		 */
		scrollTo : function( newHeader, speed, easing, completion ){

			return this.each(function(){

				var $this = $(this),
					data = $this.data(plugin_name);

				// Only bother if we've set this up before.
				if( data ){

					// If we've got the header, and its a number
					if( newHeader !== undefined && !isNaN( newHeader ) && newHeader >= 0 && newHeader < data.headers.length ){

						// Get the new header.
						var $header = data.headers.eq(newHeader),
							scrollTo = $header.position().top + data.wrapper.scrollTop() + parseInt($header.css('borderTopWidth'),10) + parseInt($header.css('borderBottomWidth'),10);

						// If we're not animating, we need to set the element directly.
						if( !speed ){

							data.wrapper.stop().scrollTop( scrollTo );

							// Set as the current header.
							data.currentHeader = newHeader;
							_private.updateHeader($this, $header);

							// Trigger the headingChange event.
							$this.trigger('headingChange',[newHeader,$header]);

							// Store the new header data.
							$this.data(plugin_name,data);

						} else {
							// If we are animating, the scroll event will fire.
							data.wrapper.stop().animate({scrollTop:scrollTo},speed,easing,completion);
						}
					}
				}
			});
		},

		/**
		 * Used to modify settings after initialisation has taken place.
		 * an example switch construct has been written to show how you
		 * might fire off an update procedure for certain defaults.
		 * Should only be called on one element at a time.
		 *
		 * @param key string	: The option name that you want to update.
		 * @param value mixed	: (opt) The value that you want to set the option to.
		 *
		 * @return mixed : If no value is passed, will return the value for the passed key, otherwise, returns the jQuery collection.
		 */
		option : function( key, value ){

			var $this = $(this),
				data = $this.data(plugin_name);

			// Only bother if we've set this up before.
			if( data ){

				// Return settings array if no key is provided.
				if( typeof key == 'undefined' ) return data.settings;

				// The key has to exist, otherwise its invalid.
				if( !key in data.settings ) return false;

				// Check if we're adding or updating.
				if( typeof value == 'undefined' ){
					return data.settings[key];
				} else {
					data.settings[key] = value;
					return $this;
				}
			}
		},

		/**
		 * Get the current name and version number of this plugin.
		 *
		 * @param num bool : Whether to return the version number, or string.
		 *
		 * @return string | number : The version string or version number.
		 */
		version : function( num ){
			// Returns the version string for this plugin.
			if( num ){
				// Calculate the version number.
				var v		= plugin_version.split('.'),
					major	= ( Number( v[0] ) || 1 )+'',
					minor	= ( Number( v[1] ) || 0 )+'',
					bugfix	= ( Number( v[2] ) || 0 )+'',
					fill	= '000';

				// Return the version as a comparable number.
				return Number( fill.slice(0,3-major.length)+major+fill.slice(0,3-minor.length)+minor+fill.slice(0,3-bugfix.length)+bugfix );

			} else {
				// Return the version string for this plugin.
				return plugin_name+' v'+plugin_version;
			}
		},

		/**
		 * Remove all data and events associated with the plugin, and restore
		 * the status of any manipulated elmenets to pre-plugin state.
		 *
		 * @return collection : Returns the jQuery collection.
		 */
		destroy: function(){
			/* Remove the ui plugin from these elements that have it */

			return this.each(function(){

				var $this = $(this),
					data = $this.data(plugin_name);

				// Only bother if we've set this up before.
				if( data ){

					// Remove wrapper and fakeheader.
					data.wrapper.children().unwrap();
					data.fakeHeader.remove();

					// Now, remove all data, etc, then
					// reattach this element to its parent, then delete list div.
					$this.css(data.original)
						 .removeData(plugin_name)
						 .removeClass('-'+plugin_name+'-container')
				    	 .unbind('.'+plugin_name);
				}
			});
		}
	};

	/**
	 * Plugin method calling handler.
	 *
	 * @param method string : (opt) Calls the defined method (or init function if omitted).
	 * @param arguments		: Any remaining arguments will be passed as arguments to the recieving method.
	 *
	 * @return mixed : Returns the result of the called method.
	 */
	$.fn[plugin_name] = function( method ){
		// Method calling logic
		if ( methods[method] ) {
			return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
		} else if ( typeof method === 'object' || ! method ) {
			return methods.init.apply( this, arguments );
		} else {
			$.error( 'Method ' +  method + ' does not exist on jQuery.' + plugin_name );
		}
	}

	// Initialise $.plugin_name for methods below.
	$[plugin_name] = {};

	/**
	 * Allow console logging from this plugin?
	 *
	 * @param l bool : (opt) Turn logging on or off...
	 *
	 * @return bool : Whether this plugin will print to the log or not.
	 */
	$[plugin_name].log = function(l){ if(l!==undefined){ plugin_logging=l; } return plugin_logging; };

})(jQuery);<commit_msg>Add a new setting for the fakeheader class<commit_after>/**
 * jQuery list plug-in 1.2.2
 * Copyright 2012, Digital Fusion
 * Licensed under the MIT license.
 * http://teamdf.com/jquery-plugins/license/
 *
 * @author Sam Sehnert, Phil Taylor
 */

(function($){
	"use strict";

	// The name of your plugin. This is used to namespace your
	// plugin methods, object data, and registerd events.
	var plugin_name = 'list';
	var plugin_version = '1.2.2';
	var plugin_logging = true;

	// Set up the plugin defaults.
	// These will be stored in $this.data(plugin_name).settings,
	// and can be overwritten by having 'options' passed through
	// as the parameter to the init method.
  var defaults = {
    headerSelector  : 'dt',
    zIndex          : 1,
    fakeHeaderClass : 'ui-' + plugin_name + '-fakeheader'
  };

	var scrollTimeout = null;

	// Any private methods that the plugin uses, such as event handlers,
	// or small utility functions for parsing data etc that isn't useful
	// outside the context of the plugin code itself.
	var _private = {

		log : function(){
			// SAFELY call console.log without fear of errors
			// also provides an override for turning off all plugin console output.
			if( plugin_logging && window.console && window.console.log ){
				try {
					// IE 8 doesn't implement apply on the console methods.
					window.console.log.apply(console,arguments);
				} catch (e) {
					// Because IE8 doesn't implement apply for console methods,
					// we simply pass the arguments directly to the log.
					window.console.log( arguments );
				}
			}
		},

		// Update the whole header object on the fly so all properties will be reserved. So setting
		// different styles for specific titles, such as different font color for today or weekends,
		// holidays, etc. becomes posible.
		updateHeader: function(target, newHeader) {
            var data = target.data(plugin_name);
            if (data) {
                data.fakeHeader = newHeader.clone().removeAttr('id').addClass(data.settings.fakeHeaderClass);
                data.fakeHeader.css({
                    position: 'absolute',
                    top: 0,
                    width: data.headers.width(),
                    zIndex: data.settings.zIndex
                });
                target.data(plugin_name, data).children(0).eq(0).replaceWith(data.fakeHeader);
            }
        },

		// Contains events for this plugin.
		events : {

			/**
			 * Window resize (should also be called when resizing the target element).
			 */
			resize : function(){
				var $this = $(this),data = $this.data(plugin_name);

				if( data ){
					data.fakeHeader.width(data.headers.width());
					data.wrapper.css('maxHeight',$this.css('maxHeight'));
				}
			},

			/**
			 * List element scroll handler event. Called to animate and substitute heading blocks.
			 */
			scroll : function(){
				var $this = $(this),data = $this.data(plugin_name);

				if( data ){

					var newHeader		= null,
						currentHeader	= data.headers.eq( data.currentHeader ),
						nextHeader		= data.currentHeader >= data.headers.length-1 ? null : data.headers.eq( data.currentHeader+1 ),
						prevHeader		= data.currentHeader <= 0 ? null : data.headers.eq( data.currentHeader-1 ),
						trigger			= false;

					// Make sure the container top position is fresh.
					data.containerTop	= $this.offset().top + parseInt($this.css('marginTop'),10) + parseInt($this.css('borderTopWidth'),10);
					data.fakeHeader.css('top',0);

					// Check the position of the current header rather than the previous header.
					if( prevHeader !== null ){

					 	var top		= currentHeader.offset().top,
					 		height	= currentHeader.outerHeight();

					 	if( top > data.containerTop ){

					 		data.fakeHeader.css('top',(top-height)-data.containerTop);
					 		_private.updateHeader($this, prevHeader);
					 		data.currentHeader = data.currentHeader-1;
					 		trigger = true;
					 	}

					 	if( (top-height) > data.containerTop ){

					 		data.fakeHeader.css('top',0);
					 		newHeader = data.currentHeader-1;
					 	}

					}

					// Check the position of the next header element.
					if( nextHeader !== null ){

					 	var top		= nextHeader.offset().top,
					 		height	= nextHeader.outerHeight();

					 	if( (top-height) < data.containerTop ){

					 		data.fakeHeader.css('top',(top-height)-data.containerTop);
					 	}

					 	if( top < data.containerTop ){

					 		data.fakeHeader.css('top',0);
					 		newHeader = data.currentHeader+1;
					 	}
					}

					// Now assign the contents of the previous header.
					if( newHeader !== null ){

						var $header = data.headers.eq(newHeader);

						data.currentHeader = newHeader;
						_private.updateHeader($this, $header);
						trigger = true;
					}

					var max = ( data.wrapper.scrollTop() >= data.wrapper.prop('scrollHeight') - data.wrapper.height() );

					if( trigger || max || data.max && !max ){
						// Trigger the headingChange event.
						$this.trigger('headingChange',[data.currentHeader,data.headers.eq(data.currentHeader),max]);
					}

					data.max = max;

					// Save the new data.
					$this.data(plugin_name,data);
				}
			}
		}
	}

	// The methods array will allow you to define public methods that
	// can be called using the plugin function using the following syntax;
	//
	// $('.selector').plugin_name( 'my_method'/*, optional arguments */);
	//
	// The 'init' method is special, and will be called when the user calls;
	//
	// $('.selector').plugin_name(/*{ options object }*/);
	var methods = {

		/**
		 * Initialises the plugin.
		 *
		 * @param options object : An object containing any overrides to the default settings.
		 *
		 * @return collection : Returns the jQuery collection
		 */
		init : function( options ){

			// Loop through each passed element.
			return $(this).each(function(){

				// Settings to the defaults.
				var settings = $.extend({},defaults);

				// If options exist, lets merge them
				// with our default settings.
				if( typeof options == 'object' ) $.extend( settings, options );

				// Create shortcuts, and get any existing data.
				var $this = $(this), data = $this.data(plugin_name);

				// If the plugin hasn't been initialized yet
				if ( ! data ) {

					// Create the data object.
					data = {
						target			: $this,			// This element.
						wrapper			: $this.wrapInner('<div class="ui-'+plugin_name+'" />').find('.ui-'+plugin_name+''),
						settings		: settings,			// The settings for this plugin.
						headers			: [],
						containerTop	: 0,
						currentHeader	: 0,
						fakeHeader		: null,
						scrolllist		: [],
						original		: {
							position	: '',
							overflowX	: '',
							overflowY	: ''
						},
						max				: false
					}

					// Add the container class, and the base HTML structure
					$this.addClass('-'+plugin_name+'-container').css({
						position	: $this.css('position') == 'absolute' ? 'absolute' : 'relative',
						overflowY	: 'hidden'
					});

					// Grab some variables to set up the list.
				    data.headers		= $this.find(data.settings.headerSelector);
				    data.fakeHeader	= data.headers.eq(0).clone().removeAttr('id').addClass(data.settings.fakeHeaderClass);

				    // bind a scroll event and change the text of the fake heading
				    data.wrapper.bind('scroll.'+plugin_name,$.proxy(_private.events.scroll,$this)).css({
				    	height		: '100%',
				    	maxHeight	: $this.css('maxHeight'),
				    	overflowY	: 'scroll',
				    	position	: 'relative'
				    });

					// Set the fake headers
				    data.fakeHeader.css({
				    	position	: 'absolute',
				    	top			: 0,
				    	width		: data.headers.width(),
				    	zIndex		: data.settings.zIndex
				    });

					// Bind the resize event to the window.
					$(window).bind('resize.'+plugin_name,$.proxy(_private.events.resize,$this));

				    // Add the fake header before all other children, and set the HTML.
				    $this.data(plugin_name,data).prepend( data.fakeHeader );
				}
			});
		},

		/**
		 * Retrieves the current header index that the user is scrolled to.
		 *
		 * @return int : The index position of the header (relative to the headers collection).
		 */
		header : function(){

			var $this = $(this),
				data = $this.data(plugin_name);

			// Only bother if we've set this up before.
			if( data ){
				return data.currentHeader;
			}
		},

		/**
		 * Used to scroll to a new header element, or to retrieve the currently set header.
		 *
		 * @param int newHeader			: The index position of the header (relative to the headers collection).
		 * @param mixed speed			: The animation speed of scrolling to the new header.
		 * @param mixed easing			: The animation easing to use.
		 * @param function completion	: The animation completion function to call.
		 *
		 * @return collection : The collection that this was called with
		 */
		scrollTo : function( newHeader, speed, easing, completion ){

			return this.each(function(){

				var $this = $(this),
					data = $this.data(plugin_name);

				// Only bother if we've set this up before.
				if( data ){

					// If we've got the header, and its a number
					if( newHeader !== undefined && !isNaN( newHeader ) && newHeader >= 0 && newHeader < data.headers.length ){

						// Get the new header.
						var $header = data.headers.eq(newHeader),
							scrollTo = $header.position().top + data.wrapper.scrollTop() + parseInt($header.css('borderTopWidth'),10) + parseInt($header.css('borderBottomWidth'),10);

						// If we're not animating, we need to set the element directly.
						if( !speed ){

							data.wrapper.stop().scrollTop( scrollTo );

							// Set as the current header.
							data.currentHeader = newHeader;
							_private.updateHeader($this, $header);

							// Trigger the headingChange event.
							$this.trigger('headingChange',[newHeader,$header]);

							// Store the new header data.
							$this.data(plugin_name,data);

						} else {
							// If we are animating, the scroll event will fire.
							data.wrapper.stop().animate({scrollTop:scrollTo},speed,easing,completion);
						}
					}
				}
			});
		},

		/**
		 * Used to modify settings after initialisation has taken place.
		 * an example switch construct has been written to show how you
		 * might fire off an update procedure for certain defaults.
		 * Should only be called on one element at a time.
		 *
		 * @param key string	: The option name that you want to update.
		 * @param value mixed	: (opt) The value that you want to set the option to.
		 *
		 * @return mixed : If no value is passed, will return the value for the passed key, otherwise, returns the jQuery collection.
		 */
		option : function( key, value ){

			var $this = $(this),
				data = $this.data(plugin_name);

			// Only bother if we've set this up before.
			if( data ){

				// Return settings array if no key is provided.
				if( typeof key == 'undefined' ) return data.settings;

				// The key has to exist, otherwise its invalid.
				if( !key in data.settings ) return false;

				// Check if we're adding or updating.
				if( typeof value == 'undefined' ){
					return data.settings[key];
				} else {
					data.settings[key] = value;
					return $this;
				}
			}
		},

		/**
		 * Get the current name and version number of this plugin.
		 *
		 * @param num bool : Whether to return the version number, or string.
		 *
		 * @return string | number : The version string or version number.
		 */
		version : function( num ){
			// Returns the version string for this plugin.
			if( num ){
				// Calculate the version number.
				var v		= plugin_version.split('.'),
					major	= ( Number( v[0] ) || 1 )+'',
					minor	= ( Number( v[1] ) || 0 )+'',
					bugfix	= ( Number( v[2] ) || 0 )+'',
					fill	= '000';

				// Return the version as a comparable number.
				return Number( fill.slice(0,3-major.length)+major+fill.slice(0,3-minor.length)+minor+fill.slice(0,3-bugfix.length)+bugfix );

			} else {
				// Return the version string for this plugin.
				return plugin_name+' v'+plugin_version;
			}
		},

		/**
		 * Remove all data and events associated with the plugin, and restore
		 * the status of any manipulated elmenets to pre-plugin state.
		 *
		 * @return collection : Returns the jQuery collection.
		 */
		destroy: function(){
			/* Remove the ui plugin from these elements that have it */

			return this.each(function(){

				var $this = $(this),
					data = $this.data(plugin_name);

				// Only bother if we've set this up before.
				if( data ){

					// Remove wrapper and fakeheader.
					data.wrapper.children().unwrap();
					data.fakeHeader.remove();

					// Now, remove all data, etc, then
					// reattach this element to its parent, then delete list div.
					$this.css(data.original)
						 .removeData(plugin_name)
						 .removeClass('-'+plugin_name+'-container')
				    	 .unbind('.'+plugin_name);
				}
			});
		}
	};

	/**
	 * Plugin method calling handler.
	 *
	 * @param method string : (opt) Calls the defined method (or init function if omitted).
	 * @param arguments		: Any remaining arguments will be passed as arguments to the recieving method.
	 *
	 * @return mixed : Returns the result of the called method.
	 */
	$.fn[plugin_name] = function( method ){
		// Method calling logic
		if ( methods[method] ) {
			return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
		} else if ( typeof method === 'object' || ! method ) {
			return methods.init.apply( this, arguments );
		} else {
			$.error( 'Method ' +  method + ' does not exist on jQuery.' + plugin_name );
		}
	}

	// Initialise $.plugin_name for methods below.
	$[plugin_name] = {};

	/**
	 * Allow console logging from this plugin?
	 *
	 * @param l bool : (opt) Turn logging on or off...
	 *
	 * @return bool : Whether this plugin will print to the log or not.
	 */
	$[plugin_name].log = function(l){ if(l!==undefined){ plugin_logging=l; } return plugin_logging; };

})(jQuery);<|endoftext|>


<commit_before>// jquery.pjax.js
// copyright chris wanstrath
// https://github.com/defunkt/jquery-pjax

(function($){

// When called on a link, fetches the href with ajax into the
// container specified as the first parameter or with the data-pjax
// attribute on the link itself.
//
// Tries to make sure the back button and ctrl+click work the way
// you'd expect.
//
// Accepts a jQuery ajax options object that may include these
// pjax specific options:
//
// container - Where to stick the response body. Usually a String selector.
//             $(container).html(xhr.responseBody)
//      push - Whether to pushState the URL. Defaults to true (of course).
//   replace - Want to use replaceState instead? That's cool.
//
// For convenience the first parameter can be either the container or
// the options object.
//
// Returns the jQuery object
$.fn.pjax = function( container, options ) {
  if ( options )
    options.container = container
  else
    options = $.isPlainObject(container) ? container : {container:container}

  return this.live('click', function(event){
    // Middle click, cmd click, and ctrl click should open
    // links in a new tab as normal.
    if ( event.which > 1 || event.metaKey )
      return true

    var defaults = {
      url: this.href,
      container: $(this).attr('data-pjax'),
      clickedElement: $(this)
    }

    $.pjax($.extend({}, defaults, options))

    event.preventDefault()
  })
}

// Loads a URL with ajax, puts the response body inside a container,
// then pushState()'s the loaded URL.
//
// Works just like $.ajax in that it accepts a jQuery ajax
// settings object (with keys like url, type, data, etc).
//
// Accepts these extra keys:
//
// container - Where to stick the response body.
//             $(container).html(xhr.responseBody)
//      push - Whether to pushState the URL. Defaults to true (of course).
//   replace - Want to use replaceState instead? That's cool.
//
// Use it just like $.ajax:
//
//   var xhr = $.pjax({ url: this.href, container: '#main' })
//   console.log( xhr.readyState )
//
// Returns whatever $.ajax returns.
$.pjax = function( options ) {
  var $container = $(options.container),
      success = options.success || $.noop

  // We don't want to let anyone override our success handler.
  delete options.success

  var defaults = {
    timeout: 650,
    push: true,
    replace: false,
    // We want the browser to maintain two separate internal caches: one for
    // pjax'd partial page loads and one for normal page loads. Without
    // adding this secret parameter, some browsers will often confuse the two.
    data: { _pjax: true },
    type: 'GET',
    dataType: 'html',
    beforeSend: function(xhr){
      $container.trigger('start.pjax')
      xhr.setRequestHeader('X-PJAX', 'true')
    },
    error: function(){
      window.location = options.url
    },
    complete: function(){
      $container.trigger('end.pjax')
    },
    success: function(data){
      // If we got no data or an entire web page, go directly
      // to the page and let normal error handling happen.
      if ( !$.trim(data) || /<html/i.test(data) )
        return window.location = options.url

      // Make it happen.
      $container.html(data)

      // If there's a <title> tag in the response, use it as
      // the page's title.
      var oldTitle = document.title,
          title = $.trim( $container.find('title').remove().text() )
      if ( title ) document.title = title

      var state = {
        pjax: options.container,
        timeout: options.timeout
      }

      // We can't persist $objects using the history API so we need to store
      // the string selector.
      if ( $.isPlainObject(state.pjax) )
        state.pjax = state.pjax.selector

      // If there are extra params, save the complete URL in the state object
      var query = $.param(options.data)
      if ( query != "_pjax=true" )
        state.url = options.url + (/\?/.test(options.url) ? "&" : "?") + query

      if ( options.replace ) {
        window.history.replaceState(state, document.title, options.url)
      } else if ( options.push ) {
        // this extra replaceState before first push ensures good back
        // button behavior
        if ( !$.pjax.active ) {
          window.history.replaceState($.extend({}, state, {url:null}), oldTitle)
          $.pjax.active = true
        }

        window.history.pushState(state, document.title, options.url)
      }

      // Google Analytics support
      if ( (options.replace || options.push) && window._gaq )
        _gaq.push(['_trackPageview'])

      // Invoke their success handler if they gave us one.
      success.apply(this, arguments)
    }
  }

  options = $.extend(true, {}, defaults, options)

  if ( $.isFunction(options.url) ) {
    options.url = options.url()
  }

  // Cancel the current request if we're already pjaxing
  var xhr = $.pjax.xhr
  if ( xhr && xhr.readyState < 4) {
    xhr.onreadystatechange = $.noop
    xhr.abort()
  }

  $.pjax.xhr = $.ajax(options)
  $(document).trigger('pjax', $.pjax.xhr, options)

  return $.pjax.xhr
}

// Used to detect initial (useless) popstate.
// If history.state exists, assume browser isn't going to fire initial popstate.
var popped = ('state' in window.history), initialURL = location.href

// popstate handler takes care of the back and forward buttons
//
// You probably shouldn't use pjax on pages with other pushState
// stuff yet.
$(window).bind('popstate', function(event) {
  // Ignore inital popstate that some browsers fire on page load
  var initialPop = !popped && location.href == initialURL
  popped = true
  if ( initialPop ) return

  var state = event.state

  if ( state && state.pjax ) {
    var $container = $(state.pjax+'')
    if ( $container.length )
      $.pjax({
        url: state.url || location.href,
        container: $container,
        push: false,
        timeout: state.timeout
      })
    else
      window.location = location.href
  }
})

// Add the state property to jQuery's event object so we can use it in
// $(window).bind('popstate')
if ( $.event.props.indexOf('state') < 0 )
  $.event.props.push('state')

// Fall back to normalcy for older browsers.
if ( !window.history || !window.history.pushState ) {
  $.pjax = $.noop
  $.fn.pjax = function() { return this }
}

})(jQuery);
<commit_msg>Fixed an error in Internet Explorer 8 or lower<commit_after>// jquery.pjax.js
// copyright chris wanstrath
// https://github.com/defunkt/jquery-pjax

(function($){

// When called on a link, fetches the href with ajax into the
// container specified as the first parameter or with the data-pjax
// attribute on the link itself.
//
// Tries to make sure the back button and ctrl+click work the way
// you'd expect.
//
// Accepts a jQuery ajax options object that may include these
// pjax specific options:
//
// container - Where to stick the response body. Usually a String selector.
//             $(container).html(xhr.responseBody)
//      push - Whether to pushState the URL. Defaults to true (of course).
//   replace - Want to use replaceState instead? That's cool.
//
// For convenience the first parameter can be either the container or
// the options object.
//
// Returns the jQuery object
$.fn.pjax = function( container, options ) {
  if ( options )
    options.container = container
  else
    options = $.isPlainObject(container) ? container : {container:container}

  return this.live('click', function(event){
    // Middle click, cmd click, and ctrl click should open
    // links in a new tab as normal.
    if ( event.which > 1 || event.metaKey )
      return true

    var defaults = {
      url: this.href,
      container: $(this).attr('data-pjax'),
      clickedElement: $(this)
    }

    $.pjax($.extend({}, defaults, options))

    event.preventDefault()
  })
}

// Loads a URL with ajax, puts the response body inside a container,
// then pushState()'s the loaded URL.
//
// Works just like $.ajax in that it accepts a jQuery ajax
// settings object (with keys like url, type, data, etc).
//
// Accepts these extra keys:
//
// container - Where to stick the response body.
//             $(container).html(xhr.responseBody)
//      push - Whether to pushState the URL. Defaults to true (of course).
//   replace - Want to use replaceState instead? That's cool.
//
// Use it just like $.ajax:
//
//   var xhr = $.pjax({ url: this.href, container: '#main' })
//   console.log( xhr.readyState )
//
// Returns whatever $.ajax returns.
$.pjax = function( options ) {
  var $container = $(options.container),
      success = options.success || $.noop

  // We don't want to let anyone override our success handler.
  delete options.success

  var defaults = {
    timeout: 650,
    push: true,
    replace: false,
    // We want the browser to maintain two separate internal caches: one for
    // pjax'd partial page loads and one for normal page loads. Without
    // adding this secret parameter, some browsers will often confuse the two.
    data: { _pjax: true },
    type: 'GET',
    dataType: 'html',
    beforeSend: function(xhr){
      $container.trigger('start.pjax')
      xhr.setRequestHeader('X-PJAX', 'true')
    },
    error: function(){
      window.location = options.url
    },
    complete: function(){
      $container.trigger('end.pjax')
    },
    success: function(data){
      // If we got no data or an entire web page, go directly
      // to the page and let normal error handling happen.
      if ( !$.trim(data) || /<html/i.test(data) )
        return window.location = options.url

      // Make it happen.
      $container.html(data)

      // If there's a <title> tag in the response, use it as
      // the page's title.
      var oldTitle = document.title,
          title = $.trim( $container.find('title').remove().text() )
      if ( title ) document.title = title

      var state = {
        pjax: options.container,
        timeout: options.timeout
      }

      // We can't persist $objects using the history API so we need to store
      // the string selector.
      if ( $.isPlainObject(state.pjax) )
        state.pjax = state.pjax.selector

      // If there are extra params, save the complete URL in the state object
      var query = $.param(options.data)
      if ( query != "_pjax=true" )
        state.url = options.url + (/\?/.test(options.url) ? "&" : "?") + query

      if ( options.replace ) {
        window.history.replaceState(state, document.title, options.url)
      } else if ( options.push ) {
        // this extra replaceState before first push ensures good back
        // button behavior
        if ( !$.pjax.active ) {
          window.history.replaceState($.extend({}, state, {url:null}), oldTitle)
          $.pjax.active = true
        }

        window.history.pushState(state, document.title, options.url)
      }

      // Google Analytics support
      if ( (options.replace || options.push) && window._gaq )
        _gaq.push(['_trackPageview'])

      // Invoke their success handler if they gave us one.
      success.apply(this, arguments)
    }
  }

  options = $.extend(true, {}, defaults, options)

  if ( $.isFunction(options.url) ) {
    options.url = options.url()
  }

  // Cancel the current request if we're already pjaxing
  var xhr = $.pjax.xhr
  if ( xhr && xhr.readyState < 4) {
    xhr.onreadystatechange = $.noop
    xhr.abort()
  }

  $.pjax.xhr = $.ajax(options)
  $(document).trigger('pjax', $.pjax.xhr, options)

  return $.pjax.xhr
}

// Used to detect initial (useless) popstate.
// If history.state exists, assume browser isn't going to fire initial popstate.
var popped = ('state' in window.history), initialURL = location.href

// popstate handler takes care of the back and forward buttons
//
// You probably shouldn't use pjax on pages with other pushState
// stuff yet.
$(window).bind('popstate', function(event) {
  // Ignore inital popstate that some browsers fire on page load
  var initialPop = !popped && location.href == initialURL
  popped = true
  if ( initialPop ) return

  var state = event.state

  if ( state && state.pjax ) {
    var $container = $(state.pjax+'')
    if ( $container.length )
      $.pjax({
        url: state.url || location.href,
        container: $container,
        push: false,
        timeout: state.timeout
      })
    else
      window.location = location.href
  }
})

// Add the state property to jQuery's event object so we can use it in
// $(window).bind('popstate')
if ( $.inArray('state', $.event.props) < 0 )
  $.event.props.push('state')

// Fall back to normalcy for older browsers.
if ( !window.history || !window.history.pushState ) {
  $.pjax = $.noop
  $.fn.pjax = function() { return this }
}

})(jQuery);
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2010 Lyconic, LLC.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

(function($){

    // jQuery doesn't provide a better way of intercepting the ajax settings object
    var _ajax = $.ajax, options;

    function collect_options (url, data, callback, type) {
        options = { dataType: 'json' };
        if (arguments.length == 1) {
            options = $.extend(options, url);
            if (options['url'] && options['data']) options.url = fill_url(options.url, options.data);
        } else {
            // shift arguments if data argument was omitted
            if ($.isFunction(data) || $.isArray(data)) {
                type = type || callback;
                callback = data;
                data = null;
            }

            var success, error;
            if ($.isArray(callback)) {
                success = callback[0];
                error = callback[1];
            } else {
                success = callback;
            }

            url = fill_url(url, data);

            options = $.extend(options, {
                url: url,
                data: data,
                success: success,
                error: error,
                dataType: type
            });
        }
    }

    function fill_url (url, data) {
        var key, u, val;
        for (key in data) {
            val = data[key];
            u = url.replace('{'+key+'}', val);
            if (u != url) {
                url = u;
                delete data[key];
            }
        }
        return url;
    }

    $.extend({
        ajax: function(settings) {
            settings.type = settings.type || "GET";

            if (typeof settings.data !== "string")
            if (settings.data !== undefined)
            if (settings.data !== null) {
                settings.data = $.param(settings.data);
            }

            settings.data = settings.data || "";

            if (!/^(get)$/i.test(settings.type))
            if (!/(authenticity_token=)/i.test(settings.data)) {
                settings.data += (settings.data ? "&" : "") + "authenticity_token=" + encodeURIComponent(AUTH_TOKEN);
            }

            if (!/^(get|post)$/i.test(settings.type)) {
                settings.data += (settings.data ? "&" : "") + "_method=" + settings.type.toLowerCase();
                settings.type = "POST";
            }

            return _ajax.call(this, settings);
        },

        read: function() {
           collect_options.apply(this, arguments);
           return $.ajax(options);
        },

        create: function() {
           collect_options.apply(this, arguments);
           options = $.extend({ type: 'POST' }, options);
           return $.ajax(options);
        },

        update: function() {
            collect_options.apply(this, arguments);
            options = $.extend({ type: 'PUT' }, options);
            return $.ajax(options);
        },

        destroy: function() {
            collect_options.apply(this, arguments);
            options = $.extend({ type: 'DELETE' }, options);
            return $.ajax(options);
        }
    });

})(jQuery);<commit_msg>Making authenticity token optional<commit_after>/*
 * Copyright (c) 2010 Lyconic, LLC.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

(function($){

    // jQuery doesn't provide a better way of intercepting the ajax settings object
    var _ajax = $.ajax, options;

    function collect_options (url, data, callback, type) {
        options = { dataType: 'json' };
        if (arguments.length == 1) {
            options = $.extend(options, url);
            if (options['url'] && options['data']) options.url = fill_url(options.url, options.data);
        } else {
            // shift arguments if data argument was omitted
            if ($.isFunction(data) || $.isArray(data)) {
                type = type || callback;
                callback = data;
                data = null;
            }

            var success, error;
            if ($.isArray(callback)) {
                success = callback[0];
                error = callback[1];
            } else {
                success = callback;
            }

            url = fill_url(url, data);

            options = $.extend(options, {
                url: url,
                data: data,
                success: success,
                error: error,
                dataType: type
            });
        }
    }

    function fill_url (url, data) {
        var key, u, val;
        for (key in data) {
            val = data[key];
            u = url.replace('{'+key+'}', val);
            if (u != url) {
                url = u;
                delete data[key];
            }
        }
        return url;
    }

    $.extend({
        ajax: function(settings) {
            settings.type = settings.type || "GET";

            if (typeof settings.data !== "string")
            if (settings.data !== undefined)
            if (settings.data !== null) {
                settings.data = $.param(settings.data);
            }

            settings.data = settings.data || "";

            if ("AUTH_TOKEN" in window)
            if (!/^(get)$/i.test(settings.type))
            if (!/(authenticity_token=)/i.test(settings.data)) {
                settings.data += (settings.data ? "&" : "") + "authenticity_token=" + encodeURIComponent(AUTH_TOKEN);
            }

            if (!/^(get|post)$/i.test(settings.type)) {
                settings.data += (settings.data ? "&" : "") + "_method=" + settings.type.toLowerCase();
                settings.type = "POST";
            }

            return _ajax.call(this, settings);
        },

        read: function() {
           collect_options.apply(this, arguments);
           return $.ajax(options);
        },

        create: function() {
           collect_options.apply(this, arguments);
           options = $.extend({ type: 'POST' }, options);
           return $.ajax(options);
        },

        update: function() {
            collect_options.apply(this, arguments);
            options = $.extend({ type: 'PUT' }, options);
            return $.ajax(options);
        },

        destroy: function() {
            collect_options.apply(this, arguments);
            options = $.extend({ type: 'DELETE' }, options);
            return $.ajax(options);
        }
    });

})(jQuery);<|endoftext|>


<commit_before>////////////
// Pemdas Code
///////////

function solveThis(numsString) {
  return solveStr(reformat(numsString));
}

function replaceAll(haystack, needle, replace) {
    return haystack.split(needle).join(replace);
} // replace all fx

function reformat(s) {
    s = s.toLowerCase();
    s = replaceAll(s, "-(", "-1*(");
    s = replaceAll(s, ")(", ")*(");
    s = replaceAll(s, " ", "");
    s = replaceAll(s, "-", "+-");
    s = replaceAll(s, "--", "+");
    s = replaceAll(s, "++", "+");
    s = replaceAll(s, "(+", "(");
    for (var i = 0; i < 10; i++) {
        s = replaceAll(s, i + "(", i + "*" + "(");
    }
    while(s.charAt(0) == "+") s = s.substr(1);
    console.log(s);
    return s;
} // standardize string format

function strContain(haystack, needle) {
    return haystack.indexOf(needle) > -1;
} // custom true/false contains

function isParseable(n, minus) {
    return (!isNaN(n) || (n == "-" && !minus) || n == ".");
} // determine if char should be added to side

function getSide(haystack, middle, direction, minus) {
    var i = middle + direction;
    var term = "";
    var limit = (direction == -1) ? 0 : haystack.length; // set the stopping point, when you have gone too far
    while (i * direction <= limit) { // while the current position is >= 0, or <= upper limit
        if (isParseable(haystack[i], minus)) {
            if (direction == 1) term = term + haystack[i];
            else term = haystack[i] + term;
            i += direction;
        } else { return term; }
    }
    return term;
} // general fx to get two terms of any fx (multiply, add, etc)

function allocFx(eq, symbol, alloc, minus) {
    minus = (typeof minus !== 'undefined'); // sometimes we want to capture minus signs, sometimes not
    if (strContain(eq, symbol)) {
        var middleIndex = eq.indexOf(symbol);
        var left = getSide(eq, middleIndex, -1, minus);
        var right = getSide(eq, middleIndex, 1, false);
        eq = replaceAll(eq, left+symbol+right, alloc(left, right));
    }
    return eq;
} // fx to generically map a symbol to a function for parsing

function solveStr(eq) {
    firstNest:
    while (strContain(eq, "(")) { // while the string has any parentheses
        var first = eq.indexOf("("); // first get the earliest open parentheses
        var last = first + 1; // start searching at the character after
        var layer = 1; // we might run into more parentheses, so this integer will keep track
        while (layer != 0) { // loop this until we've found the close parenthesis
            if (eq[last] == ")") { // if we run into a close parenthesis, then subtract one from "layer"
                layer--;
                if (layer == 0) break; // if it is the corresponding closing parenthesis for our outermost open parenthesis, then we can deal with this expression
            }
            else if (eq[last] == "(") { // if we see an open parenthesis, add one to "layer"
                layer++;
            }
            last++; // increment the character we're looking at
            if (last > eq.length) break firstNest;
        }

        var nested = eq.substr(first + 1, last - first - 1); // get the expression between the parentheses

        if (last + 1 <= eq.length) { // if there is exponentiation, change to a different symbol
            if (eq[last + 1] == "^") {
                eq = eq.substr(0, last + 1) + "&" + eq.substr((last+1)+1);
            }
        }

        var solvedStr = solveStr(nested);
        var preStr = "(" + nested + ")";
        eq = eq.replace(preStr, solvedStr); // replace parenthetical with value
    }
    while (strContain(eq, "^")) eq = allocFx(eq, "^", function(l, r) { return Math.pow(parseFloat(l),parseFloat(r)); }, false);
    while (strContain(eq, "&")) eq = allocFx(eq, "&", function(l, r) { return Math.pow(parseFloat(l),parseFloat(r)); }); // account for things like (-3)^2
    while (strContain(eq, "*") || strContain(eq, "/")) {
        var multiply = true;
        if (eq.indexOf("*") < eq.indexOf("/")) {
            multiply = (strContain(eq, "*"));
        } else {
            multiply = !(strContain(eq, "/"));
        }
        eq = (multiply) ? allocFx(eq, "*", function(l, r) { return parseFloat(l)*parseFloat(r); }) : allocFx(eq, "/", function(l, r) { return parseFloat(l)/parseFloat(r); });
    }
    while (strContain(eq, "+")) eq = allocFx(eq, "+", function(l, r) { return parseFloat(l)+parseFloat(r); });
    return eq;
} // main recursive fx + PEMDAS

/////////////
// URL parameter
/////////////////
var getUrlParameter = function getUrlParameter(sParam) {
    var sPageURL = decodeURIComponent(window.location.search.substring(1)),
        sURLVariables = sPageURL.split('&'),
        sParameterName,
        i;

    for (i = 0; i < sURLVariables.length; i++) {
        sParameterName = sURLVariables[i].split('=');

        if (sParameterName[0] === sParam) {
            return sParameterName[1] === undefined ? true : sParameterName[1];
        }
    }
};
/////////////////
// BOt Code
//////////////
$(document).ready(function() {
  var expr = $.trim($('.bot-question').text().substring(7).split("?")[0]);
  var answerField = $('input[name^="bot_answer"]')
  if(expr) {
    expr = expr.replace('X', '*').replace('x', '*');
    console.log("Expression", expr);
    var answer = solveThis(expr);
    console.log("Answer is", answer);
    answerField.val(answer);
  }

  if( $('.square-matrix').length > 0) {
      console.log('square-matrix');
      answerField.val($('.square-matrix b').length);
  }
});
<commit_msg>New bot file<commit_after>////////////
// Pemdas Code
///////////

function solveThis(numsString) {
  return solveStr(reformat(numsString));
}

function replaceAll(haystack, needle, replace) {
    return haystack.split(needle).join(replace);
} // replace all fx

function reformat(s) {
    s = s.toLowerCase();
    s = replaceAll(s, "-(", "-1*(");
    s = replaceAll(s, ")(", ")*(");
    s = replaceAll(s, " ", "");
    s = replaceAll(s, "-", "+-");
    s = replaceAll(s, "--", "+");
    s = replaceAll(s, "++", "+");
    s = replaceAll(s, "(+", "(");
    for (var i = 0; i < 10; i++) {
        s = replaceAll(s, i + "(", i + "*" + "(");
    }
    while(s.charAt(0) == "+") s = s.substr(1);
    console.log(s);
    return s;
} // standardize string format

function strContain(haystack, needle) {
    return haystack.indexOf(needle) > -1;
} // custom true/false contains

function isParseable(n, minus) {
    return (!isNaN(n) || (n == "-" && !minus) || n == ".");
} // determine if char should be added to side

function getSide(haystack, middle, direction, minus) {
    var i = middle + direction;
    var term = "";
    var limit = (direction == -1) ? 0 : haystack.length; // set the stopping point, when you have gone too far
    while (i * direction <= limit) { // while the current position is >= 0, or <= upper limit
        if (isParseable(haystack[i], minus)) {
            if (direction == 1) term = term + haystack[i];
            else term = haystack[i] + term;
            i += direction;
        } else { return term; }
    }
    return term;
} // general fx to get two terms of any fx (multiply, add, etc)

function allocFx(eq, symbol, alloc, minus) {
    minus = (typeof minus !== 'undefined'); // sometimes we want to capture minus signs, sometimes not
    if (strContain(eq, symbol)) {
        var middleIndex = eq.indexOf(symbol);
        var left = getSide(eq, middleIndex, -1, minus);
        var right = getSide(eq, middleIndex, 1, false);
        eq = replaceAll(eq, left+symbol+right, alloc(left, right));
    }
    return eq;
} // fx to generically map a symbol to a function for parsing

function solveStr(eq) {
    firstNest:
    while (strContain(eq, "(")) { // while the string has any parentheses
        var first = eq.indexOf("("); // first get the earliest open parentheses
        var last = first + 1; // start searching at the character after
        var layer = 1; // we might run into more parentheses, so this integer will keep track
        while (layer != 0) { // loop this until we've found the close parenthesis
            if (eq[last] == ")") { // if we run into a close parenthesis, then subtract one from "layer"
                layer--;
                if (layer == 0) break; // if it is the corresponding closing parenthesis for our outermost open parenthesis, then we can deal with this expression
            }
            else if (eq[last] == "(") { // if we see an open parenthesis, add one to "layer"
                layer++;
            }
            last++; // increment the character we're looking at
            if (last > eq.length) break firstNest;
        }

        var nested = eq.substr(first + 1, last - first - 1); // get the expression between the parentheses

        if (last + 1 <= eq.length) { // if there is exponentiation, change to a different symbol
            if (eq[last + 1] == "^") {
                eq = eq.substr(0, last + 1) + "&" + eq.substr((last+1)+1);
            }
        }

        var solvedStr = solveStr(nested);
        var preStr = "(" + nested + ")";
        eq = eq.replace(preStr, solvedStr); // replace parenthetical with value
    }
    while (strContain(eq, "^")) eq = allocFx(eq, "^", function(l, r) { return Math.pow(parseFloat(l),parseFloat(r)); }, false);
    while (strContain(eq, "&")) eq = allocFx(eq, "&", function(l, r) { return Math.pow(parseFloat(l),parseFloat(r)); }); // account for things like (-3)^2
    while (strContain(eq, "*") || strContain(eq, "/")) {
        var multiply = true;
        if (eq.indexOf("*") < eq.indexOf("/")) {
            multiply = (strContain(eq, "*"));
        } else {
            multiply = !(strContain(eq, "/"));
        }
        eq = (multiply) ? allocFx(eq, "*", function(l, r) { return parseFloat(l)*parseFloat(r); }) : allocFx(eq, "/", function(l, r) { return parseFloat(l)/parseFloat(r); });
    }
    while (strContain(eq, "+")) eq = allocFx(eq, "+", function(l, r) { return parseFloat(l)+parseFloat(r); });
    return eq;
} // main recursive fx + PEMDAS

/////////////
// URL parameter
/////////////////
var getUrlParameter = function getUrlParameter(sParam) {
    var sPageURL = decodeURIComponent(window.location.search.substring(1)),
        sURLVariables = sPageURL.split('&'),
        sParameterName,
        i;

    for (i = 0; i < sURLVariables.length; i++) {
        sParameterName = sURLVariables[i].split('=');

        if (sParameterName[0] === sParam) {
            return sParameterName[1] === undefined ? true : sParameterName[1];
        }
    }
};
/////////////////
// BOt Code
//////////////
$(document).ready(function() {
  var answerField = $('input[name^="bot_answer"]')

    if( $('.square-matrix').length > 0)  {
        console.log('square-matrix', $('.square-matrix b').first().text());

        if ( !$('.square-matrix b').first().text() ) {
          answerField.val($('.square-matrix b').length);
        } else {
            var sum = 0;
            var bs = $('.square-matrix b');
            for (i=0;i<bs.length;i++)  {
                sum += parseInt(bs[i].text());
            }
            answerField.val(sum);
        }
    } else {
        var expr = $.trim($('.bot-question').text().substring(7).split("?")[0]);
        expr = expr.replace('X', '*').replace('x', '*');
        console.log("Expression", expr);
        var answer = solveThis(expr);
        console.log("Answer is", answer);
        answerField.val(answer);
    }
});
<|endoftext|>


<commit_before>function Abstract() {

}

/**
 *
 * @type {Image}
 * @private
 */
Abstract.prototype._thumb = Image;

Abstract.prototype._log_output = "";

Abstract.prototype.log = function() {
	var msg = "";
	for (var i in arguments) {
		msg += arguments[i] + ' ';
	}

	this._log_output += msg + "\n";

	console.log(msg);
};

// GETTER / SETTER

Object.defineProperty(Abstract.prototype, "thumb", {
	get: function () {
		return this._thumb;
	},

	set: function(val) {
		var img = new Image();
		img.src = val;
		this._thumb = img;
	}
});<commit_msg>added log-output<commit_after>function Abstract() {

}

/**
 *
 * @type {Image}
 * @private
 */
Abstract.prototype._thumb = Image;

Abstract.prototype._log_output = "";

Abstract.prototype.log = function() {
	var msg = "";
	for (var i in arguments) {
		msg += arguments[i] + ' ';
	}

	this._log_output += msg + "\n";

	console.log(msg);

	document.getElementById('log').innerHTML += msg +"<br/>";
};

// GETTER / SETTER

Object.defineProperty(Abstract.prototype, "thumb", {
	get: function () {
		return this._thumb;
	},

	set: function(val) {
		var img = new Image();
		img.src = val;
		this._thumb = img;
	}
});<|endoftext|>