Nested Properties and dynamically adding rows

145 views
Skip to first unread message

cpassmore

unread,
May 7, 2015, 11:57:24 AM5/7/15
to cfwh...@googlegroups.com
Just built a application with 1.3.4 and seems as though nested properties are identified by their id now… 
example:
name="object[nestedProperty][1][property]"

I'm wanting to dynamically add rows by cloning a 'off-form' template.

Any ideas on how to accomplish this now?

chris passmore

unread,
May 8, 2015, 10:23:21 AM5/8/15
to cfwh...@googlegroups.com
To more clearly illustrate, if I count rows and replace the integer with currentRow count, it replaces existing records in the db with the new id based on the id. If I try with javascript tick count, it's setting the id as the tick count value.

Unsure what the solution is to this.

Per Djurner

unread,
May 13, 2015, 5:07:40 AM5/13/15
to cfwh...@googlegroups.com
I'm also unsure but if you provide code showing how you do it in earlier versions of Wheels I can at least tell if it's a bug or not.

--
You received this message because you are subscribed to the Google Groups "CFWheels" group.
To unsubscribe from this group and stop receiving emails from it, send an email to cfwheels+u...@googlegroups.com.
To post to this group, send email to cfwh...@googlegroups.com.
Visit this group at http://groups.google.com/group/cfwheels.
For more options, visit https://groups.google.com/d/optout.

Chris Peters

unread,
May 13, 2015, 9:29:57 AM5/13/15
to cfwh...@googlegroups.com
Chris,

I've been meaning to respond to this and will try to get some code together for you soon. The way we tend to do this is have the server send the HTML that needs to be appended to the form. When you have a new record added to the form, I believe it uses a tick count as a placeholder ID.

Before I spend some time to dig in, would you be interested in seeing this?

--
You received this message because you are subscribed to the Google Groups "CFWheels" group.
To unsubscribe from this group and stop receiving emails from it, send an email to cfwheels+u...@googlegroups.com.
To post to this group, send email to cfwh...@googlegroups.com.
Visit this group at http://groups.google.com/group/cfwheels.
For more options, visit https://groups.google.com/d/optout.



--

Chris Peters
Web Developer
Liquifusion Studios

chris....@liquifusion.com
Skype: liquifusion.support
www.liquifusion.com

chris passmore

unread,
May 14, 2015, 10:45:16 AM5/14/15
to cfwh...@googlegroups.com
Yes I would… here's what I have. So the below, when submitted… if you edit a record and add a new 'facet' and replace the XX with any integer, it uses that as the id when saving to the db.

CFML
#endFormTag()#
<div id="repFacetTemplate">
<!--- I was including the same partial with a generic 'current' number immediately after the close of the form tag --->
#includePartial(partial="repFacet", current=1)#
</div>

<!--- now I'm manually doing it replacing the id with xx value --->
<div class="row rep-contact-method-row clone-row">
<div class="col-xs-4 form-group">
#selectTag(label="Type", options=labelOptions, name="rep[repfacets][XX][label]", labelPlacement="before", class="form-control rep-facet-label")#
</div>
<div class="col-xs-7 form-group">
#textFieldTag(label="Value", name="rep[repfacets][XX][value]", labelPlacement="before", class="form-control value-input")#
</div>
<div class="col-xs-1 form-group">
<label>&nbsp;</label><br />
<a href="" class="remove-row btn btn-danger">&times;</a>
</div>
#hiddenFieldTag(name="rep[repfacets][XX][_delete]", class="delete_field", default=false)#
#hiddenFieldTag(name="rep[repfacets][XX][repid]", class="repid")#
</div>


------------------------------------------------------------------------------------------


JS
function makeid(length) {
var id = "";
var possible = "0123456789";

for( var i=0; i < length; i++ ) {
id += possible.charAt(Math.floor(Math.random() * possible.length));
}

return id;
}


$(document).on('click', '.add-method-row', function(event) {
var contactMethodContainer = $('#contactMethodContainer');
// as said, was getting count of rows and using that
// var currentRowCount = contactMethodContainer.find('.rep-contact-method-row').length;
// currentRowCount++;
var currentRowCount = makeid(15);
var template = $('#repFacetTemplate').children().clone(true, true);


var rowLabelInput = template.find('.rep-facet-label');
var rowValueInput = template.find('.value-input');
var rowDeleteInput = template.find('.delete_field');
var rowRepIdInput = template.find('.repid');
rowLabelInput.attr('name',"rep[repFacets][" + currentRowCount + "][label]");
rowLabelInput.attr('id',"rep-repFacets-" + currentRowCount + "-label");
rowLabelInput.val("");

rowValueInput.attr('name',"rep[repFacets][" + currentRowCount + "][value]");
rowValueInput.attr('id',"rep-repFacets-" + currentRowCount + "-value");
rowValueInput.val("");

rowDeleteInput.attr('name',"rep[repFacets][" + currentRowCount + "][_delete]");
rowDeleteInput.attr('id',"rep-repFacets-" + currentRowCount + "-_delete");
rowDeleteInput.val("");

rowRepIdInput.attr('name',"rep[repFacets][" + currentRowCount + "][repid]");
rowRepIdInput.attr('id',"rep-repFacets-" + currentRowCount + "-repid");


var appendedRow = template.appendTo(contactMethodContainer).hide();
appendedRow.slideDown('fast');
event.preventDefault();
});


On Wednesday, May 13, 2015 at 8:29:57 AM UTC-5, Chris Peters wrote:
Chris,

I've been meaning to respond to this and will try to get some code together for you soon. The way we tend to do this is have the server send the HTML that needs to be appended to the form. When you have a new record added to the form, I believe it uses a tick count as a placeholder ID.

Before I spend some time to dig in, would you be interested in seeing this?

Chris Peters

unread,
May 14, 2015, 1:31:31 PM5/14/15
to cfwh...@googlegroups.com
Here's an example of what we do for an invoicing app. I'm retyping as I modify some things for clarity, so excuse any typos. I hope it gets the point across.

The gist is that this works with and without JavaScript. Without JavaScript, the entire page reloads with a new invoice item appended.

If there is JavaScript, jQuery sends an AJAX request to get the HTML for only the new invoice item that needs to be added.

If you have any questions or if I left something out, let me know.

controllers/Invoices.cfc

component extends="Controller" {
  function init() {
    // This interrupts a create or update to add a new invoice item
    filters(through="$
addInvoiceItem", only="create,update");
  }

  function new() {
    // Regular new logic here
  }

  function create() {
    // Regular create logic here
  }

  function edit() {
    // Regular edit logic here
  }

  function update() {
    // Regular update logic here
  }

  private function $addInvoiceItem() {
    if (StructKeyExists(params, "newInvoiceItem") && Len(params.newInvoiceItem)) {
      // Method in model that loads submitted data and adds a new item
      invoice = model("invoice").
addInvoiceItem(argumentCollection=params.invoice);

      // For an AJAX request, we need to only pass the added item to the partial
      if (isAjax()) {
        invoice.invoiceItems = [
          invoice.invoiceItems[ArrayLen(
invoice.invoiceItems)]
        ]

        // This will load `views/invoices/_invoiceitem.cfm`
        renderText(includePartial(invoice.invoiceItems));
      }
      // Otherwise, handle regular HTML requests
      else {
        switch (params.action) {
          case "create":
            renderPage(action="new");
            break;
          case "update":
            renderPage(action="edit");
            break;
        }
      }
    }
  }
}


models/Invoice.cfc

component extends="Model" {
  function init() {
    // Associations
    hasMany("invoiceItems");

    // Nested properties
    nestedProperties(property="invoiceItems", allowDelete=true, sortProperty="position");
  }

  function addInvoiceItem() {
    // Load invoice object
    if (StructKeyExists(arguments, "id") && IsNumeric(arguments.id)) {
      local.invoice = model("invoice").findOne(where="id=#arguments.id#", include="invoiceItems");
      local.invoice.setProperties(arguments);
    }
    else {
      local.invoice = model("invoice").new(arguments);
    }

    if (!StructKeyExists(local.invoice, "invoiceItems")) {
      local.invoice.invoiceItems = [];
    }

    // Add new item to invoice
    ArrayAppend(
      local.invoice.invoiceItems,
      model("invoiceItem").new(position=ArrayLen(local.invoice.invoiceItems))
    );

    return local.invoice;
  }
}

javascripts/invoices.js

$(document).ready(function() {
  // Add button does AJAX post
  $("#new-invoice-item-button").on("click", function(e) {
    var $this = $(this),
        $invoiceForm = $("#invoice-form"),
        formData = $invoiceForm.serialize() + "&newInvoiceItem=%2B%20Add%20Item",
        $invoiceTableLastRow = $("#invoice-items tr:last");

    // Disable button
    $this.prop("disabled", true);

    // Call form action to get new invoice item row
    $.ajax({
      url: $invoiceForm.attr("action"),
      type: "post",
      data: formData,
      cache: false,
      success: function(data, textStatus, jqXHR) {
        data = $(data);
        $invoiceTableLastRow.after(data);
      },
      error: function(jqXHR, textStatus, errorThrown) {
        alert("There was an error adding the item.");
      },
      complete: function(jqXHR, textStatus) {
        // Re-enable field
        $this.prop("disabled", false);
      }
    });
   
    e.preventDefault();
  });
});


views/invoices/_invoiceitem.cfm

<!--- Just showing a couple example fields here for the table row that is generated --->
<tr>
  <td>
    #textField(
      label=false,
      objectName="invoice",
      association="invoiceItems",
      position=arguments.current,
      property="description",
      append=""
    )#
  </td>
  <td>
    #textField(
      label=false,
      objectName="invoice",
      association="invoiceItems",
      position=arguments.current,
      property="amount",
      append=""
    )#

    #hiddenField(
      objectName="invoice",
      association="invoiceItems",
      position=arguments.current,
      property="id"
    )#

    #hiddenField(
      objectName="invoice",
      association="invoiceItems",
      position="arguments.current,
      property="position"
    )#
  </td>
</tr>

Chris Peters

unread,
May 14, 2015, 1:39:00 PM5/14/15
to cfwh...@googlegroups.com
I suppose a quick and dirty form would give some context too..

Assume that this is set in the controller as the value for invoice in the new action:
invoice = model("invoice").new(invoiceItems=[]);

Here is the form at new.cfm then:

<cfoutput>

#startFormTag(route="invoices")#
  <table>
    <thead>
      <tr>
        <th>Description</th>
        <th>Amount</th>
      </tr>
    </thead>
    <tbody>
      #includePartial(invoice.invoiceItems)#
    </tbody>
  </table>

  <p>
    #submitTag(value="+ New Invoice Item", id="new-invoice-item-button")#
  </p>
#endFormTag()#

</cfoutput>
Reply all
Reply to author
Forward
0 new messages