0

I have a form where people can add more input with a button.

The javascript function clones the "origin-item", but I can't seem to get the function to correctly update the value in #id_nested-TOTAL_FORMS and __prefix__ does not get replaced with a counter-number, It just copies and adds __prefix__ instead of 1 or 2 and so on.

Anyone know what is wrong with the function?

The script was found here: https://github.com/rotaris/dynamicformdjango/blob/master/todo/templates/edit_items_in_list.html

<input id="id_nested-TOTAL_FORMS" name="nested-TOTAL_FORMS" type="hidden" value="1">    
<input id="id_nested-INITIAL_FORMS" name="nested-INITIAL_FORMS" type="hidden" value="0">
<input id="id_nested-MAX_NUM_FORMS" name="nested-MAX_NUM_FORMS" type="hidden" value="1000">


<div id="origin-item" class="hide item">
    <div id="div_id_nested-__prefix__-name">
        <label for="id_nested-__prefix__-name">Name</label>
    <div class="controls col-lg-10">
        <input id="id_nested-__prefix__-name" name="nested-__prefix__-name" type="text" />
    </div>
</div>


<p><a id="add-new-item" href="#">Add new person</a></p>




<script type="text/javascript">

var prefix = 'nested';
var MAX_FORMS = '1000';
var MIN_FORMS = 1;

/*
 * Perform any enhancements you'd like to a given item here
 * e.g. add datepicker widgets, popovers etc.
 */
function enhanceItem(item) {    
    $('.btn.delete', item).popover({
            offset: 10
    });
}

function enhanceItems() {       
        $('.item').each(function() {
                enhanceItem(this);
        });
}

/*
 * The opposite of enhanceItem()
 */
function diminishItem(item) {   
        $('.btn.delete', item).unbind();
}

function diminishItems() {      
        $('.item').each(function() {
                diminishItem(this);
        });
}


$(document).ready(function() {
        enhanceItems();
});


function getFormCount() {
    return parseInt($('#id_' + prefix + '-TOTAL_FORMS').val());
}

function updateFormCount(newValue) {
    $('#id_' + prefix + '-TOTAL_FORMS').val(newValue);
}



/*
 * ====================================================
 * General Notes: 'Form' and 'Item' are used somewhat interchangeably.
 * ====================================================
 */
$.fn.clearForm = function() {
  return this.each(function() {
    var type = this.type, tag = this.tagName.toLowerCase();
    if (tag == 'form')
      return $(':input',this).clearForm();
    if (type == 'text' || type == 'password' || tag == 'textarea')
      this.value = '';
    else if (type == 'checkbox' || type == 'radio')
      this.checked = false;
    else if (tag == 'select')
      this.selectedIndex = -1;
  });
};


/*
 * Given an element, this function replaces a given string/regex
 * with another specified replacement (new_value) within the element
 */
function updateElement(el, target_regex, replacement) {
        var id_regex = target_regex;

        if ($(el).attr("for")) {
                $(el).attr("for", $(el).attr("for").replace(id_regex, replacement));
        }
        if (el.id) {
                el.id = el.id.replace(id_regex, replacement);

                // Update the value of the hidden ID
                // This hidden ID represents the ID of the model instance
                var hidden_ID_patt = new RegExp('id_(' + prefix + '-\\d+)-id');
                // Only update if an ID exists (i.e. if a corresponding model instance exists)
                if (hidden_ID_patt.test(el.id)) {
                    $(el).val(new_value + 1);
                }
        }
        if (el.name) {
                el.name = el.name.replace(id_regex, replacement);
        }
}


/*
 * Given an element, this function replaces (the first?) occurence of a number
 * that follows a specific prefix (e.g. 'exampleprefix-n')
 * with another specified number (new_value) within the element
 * where n is a number
 */
function updateElementIndex(el, prefix, new_value) {
        var id_regex = new RegExp('(' + prefix + '-\\d+-)');
        var replacement = prefix + '-' + new_value + '-';

        updateElement(this, id_regex, replacement);
}



function reapplyEnhancements() {
    // Apply some fresh enhancements 
    diminishItems();
    enhanceItems();
}



/*
 * btn = the button (or link or some source object) that instigated this action
 * prefix = the prefix used in the formset (?)
 */
function addItem(btn, prefix) {
        var formCount = getFormCount();

        // You might like/want to do some validation before proceeding in this function
        // i.e. before adding an item
        // In this case, I'm limiting it to max number of forms
        if (formCount < MAX_FORMS) {
            // Clone a item (without event handlers) from the first item
            //var item = $(".item:first").clone(false).get(0);
            // Clone the origin item
            var item = $("#origin-item").clone(false).get(0);
            $(item).removeAttr("id");
            $(item).removeClass("hide");
            // Clear its values
            $(':input', item).clearForm();

            // Insert it after the last item
            $(item).removeAttr('id').hide().insertAfter("form .item:last").slideDown(300);

            $(':input, label', item).each(function() {
                    // Relabel/rename all the relevant bits
                    // '__prefix__' comes from #origin-item
                    // see 'empty_form': https://docs.djangoproject.com/en/1.4/topics/forms/formsets/#empty-form
                    var target_regex = new RegExp('(' + prefix + '-__prefix__-)');
                    var replacement = prefix + '-' + formCount + '-';
                    updateElement(this, target_regex, replacement);
                    // Remove error classes
                    $(this).removeClass("error");
            });

            reapplyEnhancements();

            // Update the total form count (in the management form)
            updateFormCount(formCount + 1);

        }
        else {
            // Feel free to notify the user using some other technique instead of an JS alert
            alert("Sorry, you can only have a maximum of " + MAX_FORMS + " goals.");
        }
}


/*
 * Relabel all items
 */
function relabelItems() {
    var forms = $('.item'); // Get all the forms

    // Update the total number of forms (likely 1 less than before)
    $('#id_' + prefix + '-TOTAL_FORMS').val(forms.length);

    var i = 0;
    // Go through the forms and set their indices, names and IDs
    for (formCount = forms.length; i < formCount; i++) {
        $(":input, label", forms.get(i)).each(function() {
            updateElementIndex(this, prefix, i);
        });
    }
}


/*
 * Removes an item from a list of items
 */
function removeItem(btn, prefix) {
        var formCount = getFormCount();

        // Do some validation before proceeding
        // In this case, just make sure there is at least one item
        if (formCount > MIN_FORMS) {
            var item = $(btn).parents('.item');

            // Delete the item
            $("*", item).fadeOut();
            $(item).animate({'backgroundColor':'#fb6c6c'}, 300).slideUp('slow', function() { 
                    $(item).remove(); 
                    relabelItems(); 
            });

            // Apply enhancements
            enhanceItems();
        }
        else {
            // Feel free to notify the user using some other technique instead of an JS alert
            alert("Come on now, you need to have at least a minimum of " + MIN_FORMS + " item(s).");
        }
}


// JavaScript to create a new items/entries
$(document).ready(function() {
        $('#add-new-item').click(function(e) {
                addItem(this, prefix);
                return false;
        });

        $('a.delete').live("click", function(e) {
                removeItem(this, prefix);
                return false;
        });
});

</script>
Tomas Jacobsen
  • 2,368
  • 6
  • 37
  • 81

1 Answers1

0

The bugs in this code seem to be caused by the variable named prefix being used for 2 different tasks.

Here it is used like this

$('#id_' + prefix + '-TOTAL_FORMS').val(newValue);

where I assume prefix is assigned '_prefix_', but later it is used like this

var target_regex = new RegExp('(' + prefix + '-__prefix__-)');

where I assume prefix is assigned 'id'

If you where to sort out the variable prefix to only contain one type of piece of information, by have 2 variables with different names. I'm pretty sure everything would fall into place.

Strings
  • 1,674
  • 10
  • 16
  • The function for updating the value in `id_nested-TOTAL_FORMS` was missing a `new_value`, so that is fixed now, but I can't seem to fix the function for making `id_nested-__prefix__-` into `id_nested-1-`. Any tips? – Tomas Jacobsen Sep 20 '13 at 07:52
  • The function for updating `id_nested-__prefix__-` was working on input and label, but not on the div, but I can live with that. – Tomas Jacobsen Sep 20 '13 at 08:02
  • that's probably because of this line $(':input, label', item).each(function() { – Strings Sep 20 '13 at 08:15
  • Yes, I added `div` to the line, and it works! Everything seems to work fine now, but the function is cloning one field wrong. `` becomes `` Any idea why that last `value` is added? The function does not put any `value` on the other fields it clones. – Tomas Jacobsen Sep 20 '13 at 09:12
  • I think the value="NaN" must come from here $(':input', item).clearForm(); and the function clearForm(); You might want to look at this http://stackoverflow.com/questions/3786694/how-to-reset-clear-form-through-javascript – Strings Sep 20 '13 at 10:22