/**
 * Plug-in: Dynamic Rows
 * Description: From a list of elements, creates an editor that allows the dynamic addition or removal or elements.
 *  Should be mostly used with forms when a user can add or remove additional 'rows' of fields dynamically.
 *
 * Two jQuery events are used :
 *  - rowAdded : raised when the user adds a row (clicks the 'add' action).
 *      Parameter: row element (container) that was added.
 *  - rowDeleted : raised when a row is deleted (note: rows are not deleted, they are simply hidden and moved at the bottom)
 *      Parameter: row element (container) that was removed.
 *
 * Author:
		David Marquis 2010 (david.marquis@nurun.com)
 */


;(function ($) {

    $.fn.dynamicRows = function(options) {

        var settings = $.extend({
            rowClass : "dynamic-row",
            addLinkSelector: null, // specify your own 'add' link selector (must be a children of the container)
            addLinkClass : "dynamic-row-add",
            addLinkText : "Add",
            deleteLinkClass : "dynamic-row-delete",
            deleteLinkText : "Delete",
            rowInitiallyVisibleClass : "dynamic-row-initial",
            onRowAdded : null,
            onRowDeleted : null,
            autoClearFields : true
        }, options);

        return this.each(function (idx, el) {
            new DynamicRowsEditor(el, settings);

            if (settings.autoClearFields) {
                $(el).bind("rowDeleted", function(event, row) {
                    $(":input", row).val("").blur();
                });
            }
        });
    };

    /**
     * Main editor object. An object construct is mainly used to encapsulate each 'row editor' that would be setup by
     * the plug-in.
     * @param containerElement the element that is going to contain the editor
     * @param settings options used to configure behavior of editor.
     */
    function DynamicRowsEditor(containerElement, settings) {
        this.containerElement = containerElement;
        this.settings = settings;
        this.addLink = this.createAndBindAddLink();

        this.setupEditor();
    }

    /*
      Name of the data key for storing a dynamic row's index relative to its parent container.
     */
    DynamicRowsEditor.prototype.DATA_ROW_INDEX = "row-idx";

    /**
     * Sets up the editor by adding the Delete action for every row except the first, then adding a single Add action
     * at the end of the editor container.
     */
    DynamicRowsEditor.prototype.setupEditor = function() {
        var container = this.containerElement;
        var settings = this.settings;
        var editor = this;
        var rowsToEnhance = $("." + settings.rowClass, container).not(":first");

        rowsToEnhance.each(function (idx, el) {
            var rowIdx = idx + 1;

            // append the 'hide' action to every element except the first, then hide them
            editor.createDeleteLink().bind("click", {editor: editor, idx: rowIdx}, function (event) {
                var editor = event.data.editor;
                editor.deleteRow($(event.target).closest("." + settings.rowClass));
            }).appendTo($(el));
        });

        // hide rows that are not initially visible
        rowsToEnhance.not("." + settings.rowInitiallyVisibleClass).hide();

        if (settings.addLinkSelector == null) {
            // create the "add row" link and append it at the end of the container
            this.addLinkSelector.appendTo($(container));
        }
    };

    /*
      Add a row by simply showing the next hidden row, and optionally fires the callback onRowAdded.
    */
    DynamicRowsEditor.prototype.addRow = function () {
        var container = this.containerElement;
        var settings = this.settings;
        var hiddenRows = this.getHiddenRows();

        // show first hidden row and raise 'rowAdded' event.
        hiddenRows.first().show().each(function (idx, el) {
            // raise the rowAdded event
            $(container).trigger("rowAdded", [el]);
        });

        this.ensureAddLinkVisibility();
    };

    /*
      Deletes the given row. Does not actually delete the row : it is simply hidden and moved at the end.
    */
    DynamicRowsEditor.prototype.deleteRow = function (rowElement) {
        var container = this.containerElement;
        var rowToDelete = $(rowElement);

        rowToDelete.hide();

        // fire a custom event notifying that the row has been 'deleted'
        $(container).trigger("rowDeleted", [rowToDelete]);

        this.ensureAddLinkVisibility();
    };

    /**
     * Creates the single 'Add' action link to be used for the current editor and binds it's click behavior.
     * @return anchor element
     */
    DynamicRowsEditor.prototype.createAndBindAddLink = function() {
        var container = this.containerElement;
        var settings = this.settings;
        
        // either create a link, or use provided link
        var addLink = (settings.addLinkSelector == null || settings.addLinkSelector == "") ?
                $("<a></a>").addClass(settings.addLinkClass).html(settings.addLinkText)
                : $(settings.addLinkSelector, container);

        return addLink.bind("click", {editor: this}, function (event) {
            var editor = event.data.editor;
            editor.addRow();
            event.preventDefault();
        });
    };

    /**
     * Creates a 'Delete' action link without binding its click behavior.
     * @return anchor element
     */
    DynamicRowsEditor.prototype.createDeleteLink = function() {
        var settings = this.settings;

        return $("<a></a>").addClass(settings.deleteLinkClass).html(settings.deleteLinkText);
    };

    /**
     * Makes sur the 'Add' action link is visible if any additional row can still be added to the dynamic rows.
     * Otherwise, hides it.
     */
    DynamicRowsEditor.prototype.ensureAddLinkVisibility = function () {
        var hiddenRows = this.getHiddenRows();

        if (hiddenRows.length == 0) {
            $(this.addLink).hide();
        } else {
            $(this.addLink).show();
        }
    };

    /**
     * Fetches the currently hidden rows in the editor.
     */
    DynamicRowsEditor.prototype.getHiddenRows = function () {
        return this.getAllRows().filter(":hidden");
    };

    /**
     * Fetches all rows in the editor.
     */
    DynamicRowsEditor.prototype.getAllRows = function () {
        var container = this.containerElement;
        var settings = this.settings;

        return $("." + settings.rowClass, container);
    };

})(jQuery);