You are viewing limited content. For full access, please sign in.

Question

Question

Populating a table with a sql lookup values via dropdown options without user interaction.

asked on May 7, 2014 Show version history

I understand this feature will be available in an upcoming release but was wondering if a workaround exists.

 

After browsing answers.laserfiche I stumbled on Eric's code for populating a dropdown field and then using JavaScript to populate the table when a change occurs on the dropdown field.

 

However, I'd like to be able to populate this table as soon as the document loads and when I do this only the pre-lookup options show up. 

 

Do look-ups only occur only when a user clicks on the drop down? Is there a way to trick this into loading?

 

Or what am I missing here =p

 

Behavior on field change:

Code set to monitor change in field

 

.. then select drop down and desired result appears

Behavior on load:

Settings of drop down field:

Settings of Lookup:

Here is the code: 

$(document).ready(function () {

// Modifies table row label based on drop down options populated from sql 
    function tableAwesome() {
  

        var resultNumber = $('.filledField option').size();
        for (var i = resultNumber -2; i > 0; i--) {
            $('#q27').trigger('click');
        }
		
        for (var i = 1; i <= resultNumber; i++) {
          var k = i+1;
            $('td:contains("Row ' + i + '")').text($('.filledField option:nth-child(' + k + ')').text());
        }
    }

// Modifies when the dropdown field changes
    $('.filledField').change(tableAwesome);

// Modifies on document load
//$(window).load(tableAwesome);


});

 

 

All require the drop down field to have the class "'filledField", a Lookup Rule w/o a condition, and q27 is the "add" button of the table element. Also change code commentary to flip between behavior handlers.

0 0

Answer

APPROVED ANSWER SELECTED ANSWER
replied on May 9, 2014 Show version history

This is tricky because you don't know when the lookup rule has finished. The following workaround deals with this issue by periodically checking to see if the lookup is finished before updating all the row labels.

 

Give this workaround a try. For the drop-down field that will be filled in, give it only one choice by default. Replace your JavaScript with the following:

 

$(document).ready(function () {
  
    function labelUpdate() {
        if ($('.filledField option').length < 3) {
            setTimeout(function () {
                labelUpdate();
            }
              , 200);
        }
        else

            var resultNumber = $('.filledField option').size();

        //see how big the table is. If it's an approval step, don't add more rows
        if (!($('tbody tr').length > 1)) {
            for (var i = resultNumber - 2; i > 0; i--) {
                $('#q3').trigger('click');
            }
        }


        for (var i = 1; i <= resultNumber; i++) {
            var k = i + 1;
            $('td').filter(function () {
                return $(this).text() === 'Row ' + i;
            }).text($('.filledField option:nth-child(' + k + ')').text());
        }

    }
    labelUpdate();
});

 

Keep in mind that Forms isn't saving the row labels, so you have to redo them each time the form is viewed. The above code accomplishes this, but it's also possible that, if the lookup takes a while, the form might be saved to the repository with standard row labels.

2 0
replied on May 12, 2014

Ah a timer and great workaround. Got it to work. Thank you!

 

 

0 0
replied on May 13, 2014

You're welcome!

0 0
replied on July 11, 2014

Good Morning!

 

Is there a way to also update the table column data with the sql data matching the individual?

0 0
replied on July 11, 2014

Yes, you'll need to adjust the JavaScript slightly and add the filledColumn class to the column you want to update.

 

$(document).ready(function () {

    function tableUpdate() {
        if ($('.filledField option').length < 3) {
            setTimeout(function () {
                tableUpdate();
            }
              , 200);
        } else
          
            var resultNumber = $('.filledField option').size();

        //see how big the table is. If it's an approval step, don't add more rows
        if (!($('tbody tr').length > 1)) {
            for (var i = resultNumber - 2; i > 0; i--) {
                $('#q3').trigger('click');
            }
        }


        for (var i = 1; i <= resultNumber; i++) {
            var k = i + 1;
            $('td.filledColumn input').eq(i-1).val($('.filledField option:nth-child(' + k + ')').text());
        }

    }
    tableUpdate();
});

 

1 0
replied on November 26, 2014

Hi,

I am trying to implement this solution and am about 80% there.

I have a PO system designed with LF Forms. The user needs to be able to amend an existing PO. I need to be able to fill a table with the same data and rows as per the original PO.

 

As you can see below, I am able to automatically add the number of rows according to the number of options in the "Line No" field. There are 4 options, hence 4 rows in total. I am also able to automatically select each row's Line No value as per the options in the drop-down. However, the other fields to not perform their lookup. If I manually select a value from the Line No field, the Lookup works fine.

I am guessing that I need to force a trigger for the change event in order for the lookup to take place, or something along those lines.

Anyone have some idea on how to get my lookup working?

 

 

function tableUpdate() {

// Don't bother unless we have data

  if ($('.filledField option').length < 2) 
  {
			
            setTimeout(function () {
		
                tableUpdate();

            }

             , 500);

  } else {

       

            


    // Get the number of <option> tags in filledField (q11) -1 because there's a blank option

    var resultNumber = $('.filledField option').size() - 1;

    // Get all the rows of the table

    var tableRows = $('#q4 table tr');

    // Determine whether we need to add or subtract rows (-1 for the header row)

    var rowDelta = 0; 
    rowDelta = resultNumber - (tableRows.size() - 1);
alert(resultNumber + ' ' + rowDelta + ' ' + tableRows.size());
    // Add rows

    if(rowDelta > 0) {

      // Click the Add link that number of times

      repeatXTimes(rowDelta, function() {

        $('#q5').click();

      });

    // Subtract rows

    } else if(rowDelta <= 0) {

      // Find the delete buttons, do the following with eacH

      $("#q4 span.form-del-field").each(function(id, del_btn) {

        // id = row number (excluding header, starting at 0), del_btn = the delete button
		
         

        // If the row number (id) is more than we should have

        if(id >= resultNumber-1) {

          // Click the delete button
		  alert('Deleting Row ' + id + ' ' + ' ' + resultNumber);
          $(del_btn).click();

        }

      });

    }

    // Do the following for each option in filledField (q11)

    $('.filledField option').each(function(oid,opt) {

      // oid = the option line number, opt = the actual <option> element

       for (var i = 1; i <= resultNumber; i++) {

            var k = i + 1;

            $('td.filledColumn select').eq(i-1).val($('.filledField option:nth-child(' + k + ')').text());
			$('td.filledColumn').eq(i-1).trigger('lookup');
        }

      

    });

  }

}

 

1 0

Replies

replied on August 27, 2014 Show version history

Just wanted to drop an update.

 

The scenario that we were trying to create was using multiple fields in SQL to populate a Forms Table.

 

We accomplished it with the following steps and Javascript:

 

  1. Drop down field = "'filledField" (From above)
  2. Lookup Rule (with or without condition based on filtered results or not) (Also from above)
  3. #q17 = "add" button of the table element. (Also from above)
  4. #q16 = cf-table-block element
  5. SQL view of the data we want with a combined field and delimiters
    1. Our Example used Name+V1+V2+V3
  6. Laserfiche Forms Table with 1 Field for each delimited value in your sql view.
  7. The Javascript part.
  // Quick and dirty function to perform an action X times
  function repeatXTimes(X, fn) {
    for(var i = 0; i < X; i++) fn();
  }
  
  function tableUpdate() {
 
    // Don't bother unless we have data
    if ($('.filledField option').length >= 3) {
      // Get the number of <option> tags in filledField (q11) -1 because there's a blank option
      var resultNumber = $('.filledField option').size() - 1;
      // Get all the rows of the table
      var tableRows = $('#q16 table tr');
      // Determine whether we need to add or subtract rows (-1 for the header row)
      var rowDelta = resultNumber - (tableRows.size() - 1);
      // Add rows
      if(rowDelta > 0) {
        // Click the Add link that number of times
        repeatXTimes(rowDelta, function() {
          $('#q17').click();
        });
      // Subtract rows
      } else if(rowDelta < 0) {
        // Find the delete buttons, do the following with eacH
        $("#q16 span.form-del-field").each(function(id, del_btn) {
          // id = row number (excluding header, starting at 0), del_btn = the delete button
          
          // If the row number (id) is more than we should have
          if(id >= resultNumber) {
            // Click the delete button
            $(del_btn).click();
          }
        });
      }
      // Do the following for each option in filledField (q11)
      $('.filledField option').each(function(oid,opt) {
        // oid = the option line number, opt = the actual <option> element
        
        // Make sure we're only doing this for fields with data
        // It would probably be better to test if there is data instead of just checking the item number
        if(oid > 0) {
          // Get all the cells in row # oid
          var cells = tableRows.eq(oid).find('input');
          // Split the data in opt into it's fields
          var fields = $(opt).text().split("+");
          // Do the following with each cell in the current row
          cells.each(function(cid, cell) {
            // cid = the cell column number, cell = the actual <input> element
            
            // Make sure there is data for the column number (cid)
            if(cid < fields.length) {
              // Put the data we 'split' into the current cell
              $(cell).val(fields[cid]);
            }
          });
        }
      });
    }
  }
  
  // Create trigger
  $(document).bind("ajaxComplete", tableUpdate);

Its commented up if you want to understand it better, but this should allow anyone who wants extended SQL data to come up in Laserfiche forms.

 

Our scenario was being used for mass approval of employee leave and expenses by managers, where they wanted to be able to approve the entire department at a time.

2 0
replied on April 16, 2015

We are trying to duplicate your process above.  What we get is on initial load the script creates the correct number of rows but only populates one row.  But upon refresh the rest of the rows populate.  Or if we change the content of the initial query the rows from the previous query populate the table but no the current values of the dropdown field (filledField class)

 

Any ideas?  Here is our code, used to populate two tables with a seperator of | instead of +.  

 

Table fields are:

       Positions Table-  Add button = q153, Table = q154

      Endorsements Table- Add button =  q158, Table = q159

$(document).ready(function() {
  // Create trigger to populate the table.
  $(document).bind("ajaxComplete", tableUpdates);
});
// ------------------------------------------------------------------------------------------------------------

 // Populate the History Table -------------------------------------------------------

function tableUpdates() {
  positionUpdate();
  endorsmentUpdate();
};
  
// Quick and dirty function to perform an action X times
function repeatXTimes(X, fn) {
  for(var i = 0; i < X; i++) fn();
};

function positionUpdate() {

  // Don't bother unless we have data
  if ($('.filledPosition option').length >= 3) {
    // Get the number of <option> tags in filledField (q11) -1 because there's a blank option
    var resultNumber = $('.filledPosition option').size() - 1;
    // Get all the rows of the table
    var tableRows = $('#q154 table tr');
    // Determine whether we need to add or subtract rows (-1 for the header row)
    var rowDelta = resultNumber - (tableRows.size() - 1);
    // Add rows
    if(rowDelta > 0) {
      // Click the Add link that number of times
      repeatXTimes(rowDelta, function() {
        $('#q153').click();
      });
    // Subtract rows
    } else if(rowDelta < 0) {
      // Find the delete buttons, do the following with eacH
      $("#q154 span.form-del-field").each(function(id, del_btn) {
        // id = row number (excluding header, starting at 0), del_btn = the delete button
        
        // If the row number (id) is more than we should have
        if(id >= resultNumber) {
          // Click the delete button
          $(del_btn).click();
        }
      });
    }
    // Do the following for each option in filledField (q11)
    $('.filledPosition option').each(function(oid,opt) {
      // oid = the option line number, opt = the actual <option> element
      
      // Make sure we're only doing this for fields with data
      // It would probably be better to test if there is data instead of just checking the item number
      if(oid > 0) {
        // Get all the cells in row # oid
        var cells = tableRows.eq(oid).find('input');
        // Split the data in opt into it's fields
        var fields = $(opt).text().split("|");
        // Do the following with each cell in the current row
        cells.each(function(cid, cell) {
          // cid = the cell column number, cell = the actual <input> element
          
          // Make sure there is data for the column number (cid)
          if(cid < fields.length) {
            // Put the data we 'split' into the current cell
            $(cell).val(fields[cid]);
          }
        });
      }
    });
  }
};

function endorsmentUpdate() {

  // Don't bother unless we have data
  if ($('.filledEndorsment option').length >= 3) {
    // Get the number of <option> tags in filledField (q11) -1 because there's a blank option
    var resultNumber = $('.filledEndorsment option').size() - 1;
    // Get all the rows of the table
    var tableRows = $('#q159 table tr');
    // Determine whether we need to add or subtract rows (-1 for the header row)
    var rowDelta = resultNumber - (tableRows.size() - 1);
    // Add rows
    if(rowDelta > 0) {
      // Click the Add link that number of times
      repeatXTimes(rowDelta, function() {
        $('#q158').click();
      });
    // Subtract rows
    } else if(rowDelta < 0) {
      // Find the delete buttons, do the following with eacH
      $("#q159 span.form-del-field").each(function(id, del_btn) {
        // id = row number (excluding header, starting at 0), del_btn = the delete button
        
        // If the row number (id) is more than we should have
        if(id >= resultNumber) {
          // Click the delete button
          $(del_btn).click();
        }
      });
    }
    // Do the following for each option in filledField (q11)
    $('.filledEndorsment option').each(function(oid,opt) {
      // oid = the option line number, opt = the actual <option> element
      
      // Make sure we're only doing this for fields with data
      // It would probably be better to test if there is data instead of just checking the item number
      if(oid > 0) {
        // Get all the cells in row # oid
        var cells = tableRows.eq(oid).find('input');
        // Split the data in opt into it's fields
        var fields = $(opt).text().split("|");
        // Do the following with each cell in the current row
        cells.each(function(cid, cell) {
          // cid = the cell column number, cell = the actual <input> element
          
          // Make sure there is data for the column number (cid)
          if(cid < fields.length) {
            // Put the data we 'split' into the current cell
            $(cell).val(fields[cid]);
          }
        });
      }
    });
  }
};

Any help would be great.  Thank you in advance.

0 0
replied on April 17, 2015 Show version history

Instead of binding the event to ajaxComplete, try using ajaxStop?

Multiple calls are being made ajaxComplete will fire after each one. ajaxStop will fire when there are no longer any calls running.

 

Here's a visualization of ajax events:

http://malsup.com/jquery/ajax/

 

0 0
replied on April 17, 2015

Otherwise try adding a timer in, mentioned way up above, to allow time for the ajax call to populate into the page if you're using that information in your other update.

0 0
replied on April 17, 2015 Show version history

We have actually made a lot of revisions and updates to how this works.  

Going to go into some pretty descriptive detail so that hopefully users of any level can read this and implement it.

The Javascript: (before the document.ready)

// eiTables.js version 2.0.6

function eiTables() {
  this.self = this;
  self.workers = [];
  var rows = [];

  var fieldReplace = function(source, field, row) {
    var temp = $(field).html();
    if(temp !== undefined) {
      if(source['Fields'] !== undefined) {
        source['Fields'].forEach(function(item, idx) {
          if(item !== null) {
            if((row) && (row.constructor === Array) && (row[idx] !== undefined)) {
              if(row[idx] !== "") {
                temp = temp.replace("%" + item + "%", row[idx]);
              }
            }
          }
        });
        $(field).html(temp);
      }
    }
  }

  var resizeTable = function(selector, newSize) {
    var oldSize = $(selector + " tbody tr").length;
    if(newSize > oldSize) {
      for(i = 0; i < newSize - oldSize; i++) {
        $(selector + " a.cf-table-add-row").click();
      }
    } else if(newSize < oldSize) {
      for(i = 0; i < oldSize - newSize; i++) {
        $(selector + " span.form-del-field").last().click();
      }
    }
  }

  var resizeCollection = function(selector, newSize) {
  	var oldSize = $(selector + " .rpx").length;
  	if(newSize > oldSize) {
  		for(i = 0; i < newSize - oldSize; i++) {
  			$(selector + " a.cf-collection-append").click();
  		}
  	} else if(newSize < oldSize) {
  		for(i = 0; i < oldSize - newSize; i++) {
  			$(selector + " span.cf-collection-delete").last().click();
  		}
  	}
  }

  var popoluteTarget = function(target, source, data) {
    if(target['Type'] === "collection") {
    	resizeCollection(target['Selector'], data.length);
      var blocks = $(target['Selector'] + " .rpx");
      rows.forEach(function(row, index) {
        $(blocks.get(index)).find(":input[id], .eiField").each(function(idx, item) {
          if($(item).hasClass("eiField")) {
            fieldReplace(source, item, row);
          } else {
            $(item).val(row[idx]);
          }
        });
      });
    } else if(target['Type'] === "table") {
    	resizeTable(target['Selector'], data.length);
      var blocks = $(target['Selector'] + " tbody tr");
      rows.forEach(function(row, index) {
        $(blocks.get(index)).find(":input[id], .eiField").each(function(idx, item) {
          if($(item).hasClass("eiField")) {
            fieldReplace(source, item, row);
          } else {
            $(item).val(row[idx]);
          }
        });
      });
    } else if(target['Type'] === "text") {
      if(target.master === undefined) {
        target.master = $(target['Selector']).html();
      } else {
        $(target['Selector']).html(target.master);
      }
      fieldReplace(source, target['Selector'], rows[0]);
    }
  }

  var extractSource = function(source) {
    rows = [];
    if(source['Type'] === "dropdown") {
      $(source['Selector'] + " option").each(function(idx, item) {
        if((item.value === "") && (source['IgnoreBlank'] === true)) {
        
        } else {
          if(source['SplitOn'] === undefined) {
            rows.push(item.value);
          } else {
            row = []
            item.value.split(source['SplitOn']).forEach(function(item) {
            	row.push(item);
            });
            rows.push(row);
          }
        }
      });
    } else if(source['Type'] === "text") {
    	
      $(source['Selector']).val().split("\n").forEach(function(item) {
        if((item.value === "") && (source['IgnoreBlank'] === true)) {
        	
        } else {
          if(source['SplitOn'] === undefined) {
            rows.push(item.value);
          } else {
            row = []
            item.value.split(source['SplitOn']).forEach(function(item) {
            	row.push(item);
            });
            rows.push(row);
          }
        }
      });
    } else if(source['Type'] === "fields") {
      var row = [];
      $(source['Selector']).find("input").each(function(idx, item) {
        row.push($(item).val());
      });
      rows.push(row);
    }
    return rows;
  }

  var executeWorker = function(worker) {
    var rows = extractSource(worker['Source']);
    if(worker['Target'].constructor === Array) {
      worker['Target'].forEach(function(item) {
        popoluteTarget(item, worker['Source'], rows);
      });
    } else {
      popoluteTarget(worker['Target'], worker['Source'], rows);
    }
  };

  var changeTrigger = function() {
    self.workers.forEach(function(worker) {
      if(worker['Trigger']['Type'] === "change") {
        executeWorker(worker);
      }
    });
  };

  var ajaxTrigger = function() {
    self.workers.forEach(function(worker) {
      if(worker['Trigger']['Type'] === "ajax") {
        executeWorker(worker);
      }
    });
  };

  this.create = function(workerdef) {
    self.workers.push(workerdef);
    if(workerdef['Trigger']['Type'] === "ajax") {
      $(document).bind("ajaxComplete", ajaxTrigger);
    } else if(workerdef['Trigger']['Type'] === "change") {
      $(workerdef['Trigger']['Selector']).bind("change", changeTrigger);
    }
  };

}

eiTables.prototype['create'] = eiTables.prototype.create;

$ei = new eiTables();

The way it currently functions gives a lot of flexibility.

For example:

We populate a collection using a list of RFPs in a specific area, that we have in a database.

Data is presented to a drop down through a SQL StoredProcedure/View Lookup formatted : Project+RFPContact+Value+Addr1+City+State

Fields are populated with "Project", "RFPContact", and "Value".

Then we use %Addr1%, %City%, %State%, as variables in a Custom HTML field (having eiField class) within the collection to create dynamic google map searches.

You will need for this setup the following:
Source: field, or fields
Target: collection, table, or custom html
Data: source, filling Source with data, delimited by + in the current examples

Executing the script:

For Collections:

$(document).ready(function () {

  $ei.create({
    "Trigger": {
      "Type": "ajax"
    },
    "Source": {
      "Selector": "#q2",
      "Type": "dropdown",
      "IgnoreBlank": true,
      "SplitOn": "+",
      "Fields": [ "Var1", "Var2", "Var3" ]
    },
    "Target": {
      "Selector": "#q3",
      "Type": "collection",
    }
  });

For Custom HTML:
You will need a Custom HTML field with text, when you insert variables they are case sensitive and require % on both sides of your variable.

$(document).ready(function() {
  $ei.create({
    Trigger: {
      Type: "ajax"
    },
    Source: {
      Selector: "#q55",
      SplitOn: "+",
      IgnoreBlank: true,
      Type: "dropdown",
      Fields: [ "DogType", "ParkName", "Weather" ]
    },
    Target: [
      {
        Selector: "#q56",
        Type: "text"
      }]
  });

CustomHTML Example with above

Today we took our %DogType% dog to the %ParkName% on this %Weather% day.

 

For Tables:

$(document).ready(function() {
  $ei.create({
    Trigger: {
      Type: "ajax"
    },
    Source: {
      //source field
      Selector: "#q1",
      SplitOn: "+",
      IgnoreBlank: true,
      Type: "dropdown",
      Fields: [ "Var1", "Var2", "Var3"]
    },
    Target: {
       //Target cf-block
        Selector: "#q2",
        Type: "table",
      },
  });
});

Sources:

  • dropdown

    • Each <option> is a row and fields are parsed using the SplitOn option

  • text

    • Same as above, but uses plain old text instead of a drop-down

    • lines are parsed as rows and fields are parsed using the SplitOn option

  • fields

    • All <input> fields in an area specified by the Selector to create a single row of data.  

    • No separators or delimiters are needed or considered

Targets

  • Multiple Targets

    • Target: [ { ... target1 ... }, { ... target2 ... } ]

  • tables
    Adds and removes rows to the table and propagates each row with data from the source, additionally any items that contain the eiField class (i.e. links you want to make dynamic) will be processed with find and replace similar to below except that data will come from the current row of source data, not just the first

    • // this is the selector of the TABLE, not the ROW

    • Selector: "#q5", Type: "table"

  • collection
    Adds and removes "collection groups" to the collection and propogates each group with data from the source, additionally any items that contain the eiField class will be processed with find and replace similar to below except that data will come from the current row of source data, not just the first

    • // this is the selector of the TABLE, not the ROW

    • Selector: "#q5", Type: "collection"

  • text
    Text targets will not fill in any data, but instead find and replace %variables% in the text (specified by Selector) with the data from the first row of source data

    • Selector: "#q5", Type: "text"

Parameters:

  • IgnoreBlank - set this to true if you don't want blank lines (from the drop down) inserted into your table (this will be the default in the next version, for now you should specify it)

  • SplitOn - this is your delimiter for the fields on each row

  • Fields - this parameter is optional, but it labels the fields in the rows with a variable name.  You only need this option when using the find & replace options listed above

I hope this helps you get your project off the ground, if you want more help send me a message and I can connect with you.

3 0
replied on December 6, 2015

I think the new LF 10 forms can handle this natively.

-GK

2 0
replied on July 23, 2014

I have been playing with this quite a bit, in my example I am returning employee names to column 1, then using the lookup rules to populate the other fields in the table, however, it only actually fills the values about 50% of the time.

 

Is there a way I could, for example, create a view in SQL that has the fields I need delimited and then have the javascript create values from the delimited results, and then place them into the fields?

 

Just trying to get creative so that I am getting 100% results.  Or can I delay the additional Lookup Rules?

0 0
replied on March 11, 2016

Hi Carl,

 

Can you point me to the code you reference in your original post above?  I've done some searching today and can't seem to find what you are referencing.

Thanks in advance!

Melissa

0 0
replied on March 11, 2016

Here it is Melissa,

 

https://answers.laserfiche.com/questions/48840/Populate-Table-Row-Label-with-Look-Up

 

Took me a bit to find, then I went to google and did: site:laserfiche.com function tableAwesome() {.

 

Cheers,

Carl

 

1 0
replied on March 11, 2016

Hey Carl,

 

Thanks for the link!  I'm still having trouble getting the row labels to update to the results of my lookup. In your example above, where are the employee last names stored in the form that you are using for row labels?

I though this code would help me use the results of my look up (populated to a drop down field) as row label in a table, but I can't seem to get it to work.

Any tips you can share on how you solved this?

Thanks,

Melissa

0 0
replied on March 14, 2016

Melissa,

 

For me it always behaved kind of quirky so I ended up abandoning this in hopes official support for this type of feature would be added in.

 

The employee last names were stored in a database, which populated a drop down field with a css class of filledField. #q27 is the id of the table and I think this was on Forms 9.1?

 

I think if I were to do this again today, I'd use the Laserfiche option to populate a table column with SQL, make the populated fields read only, and then use javascript to change the formatting so it looks more like a label.

 

Best of luck,

Carl

 

 

 

 

 

 

0 0
You are not allowed to follow up in this post.

Sign in to reply to this post.