Web Connection
unbindformvars issue
Gravatar is a globally recognized avatar based on your email address. unbindformvars issue
  VFPJCOOK
  All
  Mar 16, 2020 @ 08:02am

Well I am still trying to grasp web connect. Small problem today but been trying for hours and wondering if someone may see what I am doing wrong.

Basically I have copied the step thru steps for a customer list, show customer and edit customer replicating that with my Products table. Works perfectly fine with edit customer when I change a customer name it saves the new name in the table. Not with products. The code is identical except the field that contains a product's name is prodname rather than customer.

I am in the editproduct method. I step thru the code. I see where the record to be edited is selected and the scatter name poProduct takes the underlying table values and puts then in the object. The properties are named poProducts.id, prodname and unitprice as they should be because those are the table's field names. When I change the prodname on the form from Snickers to SnickersXXX and click Submit, and step thru the code If Request.Isformvar("btnsubmit") is true so the poErrors = Request.UnbindFormVars(poProduct) should take the values for the HTML form and overwrite the poProduct properties values. It dos NOT do that and I can't for the life of me tell you why. I would almost have to believe the name on the form is not the same as the name in the table but I have checked that at least 100 times. To make this even more complexing, you will notice I am also allowing editing of the unitprice and I can modify it perfectly fine (works just like it should).

The following is my code from the ShowProduct and EditProduct methods.

FUNCTION ShowProduct()
LOCAL lcId,lcHtml

lcId = Request.Params("id")

SELECT * FROM Products ;
   WHERE id = lcId ;
   INTO CURSOR TQuery

IF _Tally < 1
   *** Create an error page
   this.ErrorMsg("Invalid product Id",;
   "The product couldn't be retrieved. Make sure the URL is correct " + ;
   "and points at a valid product record.hr/>Please return to the <a href='Productlist.wp'>product list</a>...")
   RETURN 
ENDIF

*** Helper to generate templated HTML Header
Response.Write(this.PageHeaderTemplate("Product List"))


TEXT TO lcHtml TEXTMERGE NOSHOW
<a href="productlist.wp" 
   class="btn btn-success btn-sm pull-right" 
   style="margin-top: 20px;">
	<i class="fa fa-arrow-left"></i> Product List
</a>

<h3>
   <i class='fa fa-user'></i> <<ProdName>>
</h3>   

<hr />
ENDTEXT
Response.Write(lcHtml)

*** Render the Record into a table
loConfig = CREATEOBJECT("HtmlRecordConfig")
loConfig.Width = "700px"

*loConfig.HeaderCssClass = "col-sm-3 my-record-header"
*loConfig.ItemCssClass = "col-sm-7"

*** Create columns manually for each field

loCol = CREATEOBJECT("HtmlRecordColumn","prodname","Item Description")
loConfig.AddColumn(loCol)

loCol = CREATEOBJECT("HtmlRecordColumn","UnitPrice","Unit Price")
loCol.Format = "$$,$$$.99"
loCol.FieldType = "N"
loConfig.AddColumn(loCol)

*!*	loCol = CREATEOBJECT("HtmlRecordColumn",[ShortDate(Entered,2)],"Entered")
*!*	loCol.FieldType = "C"
*!*	loConfig.AddColumn(loCol)

lcHtml = HtmlRecord("TQuery",loConfig)
Response.Write(lcHtml)
*!*	lcHtml = HtmlRecord("TQuery")
*!*	Response.Write(lcHtml)

Response.Write(this.PageFooterTemplate())

ENDFUNC



FUNCTION EditProduct()
PRIVATE pcerrormsg,poerrors
pcerrormsg=""
poerrors=CREATEOBJECT("htmlerrordisplayconfig")

lcId = Request.Params("Id")

IF !USED("Products")
   USE Products IN 0
ENDIF
SELECT Products

IF !EMPTY(lcId)
   LOCATE FOR Id=lcId
ELSE
   GO BOTTOM 
   SKIP 
ENDIF

SET STEP ON 
   
PRIVATE poProduct
&& put the values from the current record (existing rec or empty rec) into the object poProduct.
&& each filed is a property of the object. MEMO means include memo fields when doing all this.
SCATTER NAME poProduct Memo

IF Request.Isformvar("btnsubmit")
   SELECT products
   
   *** Unbind all matching fields into properties of this object
   poErrors = Request.UnbindFormVars(poProduct)
   
   IF (poErrors.Count > 0)
	      pcErrorMsg = "There are binding errors." + poErrors.ToHtml(.t.)
   ENDIF

   *** Validation goes here     
   
   IF EMPTY(poProduct.Id)
      poProduct.Id = SYS(2015)
      APPEND blank
   ENDIF
   
   GATHER NAME poProduct MEMO 

   IF poErrors.Count = 0 AND EMPTY(pcErrorMsg)
	   pcErrorMsg = "Product info saved."   	
	   Response.AppendHeader("refresh","3;url=ProductList.wp")
   ELSE
       pcErrorMsg = "Please correct the following errors..."
   ENDIF
ENDIF

*** Render EditCustomer.wp
Response.ExpandScript()
ENDFUNC

Thanks, John

Gravatar is a globally recognized avatar based on your email address. re: unbindformvars issue
  VFPJCOOK
  VFPJCOOK
  Mar 16, 2020 @ 12:18pm

I figured this one out myself. I was looking in the ShowProduct method for where the field was named. It is named in there using htmlrecordcolumn BUT that is what is being used to SHOW the product, not to EDIT the product. Looking in my EditProduct.wp file, I see where the field is named Product rather than prodname and this is where the field is named for the EDIT functionality.

John

Gravatar is a globally recognized avatar based on your email address. re: unbindformvars issue
  Rick Strahl
  VFPJCOOK
  Mar 16, 2020 @ 01:26pm

UnbindFormVars() by design has to match db field names to form field names - plus an optional prefix for subobjects (ie. Customer_Product using Customer_ as a prefix).

If you have fields with other names you'll need to retrieve those and assign them individually.

This is why it's often useful to create custom View Model objects that contain only the data you need to actually work with, rather than binding directly to the .oData (or other bus entity data) object.

+++ Rick ---

Gravatar is a globally recognized avatar based on your email address. re: unbindformvars issue
  VFPJCOOK
  Rick Strahl
  Mar 16, 2020 @ 01:40pm

Thanks Rick, I will keep that in mind.

Quick followup:

I used VS to create EditPeoduct.wp. I added a file using the Template Content addin.

Looking at EditProduct.wp I see where the layout gets "included" by, Layout="~/views/_layoutpage.wcs"

Just below that I see the following code:

<div class="container">
    <a href="productlist.wp" class="btn btn-success btn-sm pull-right" style="margin-top: 20px;">
        <i class="fa fa-arrow-left"></i> Product List
    </a>

    <h3>
        <i class='fa fa-edit'></i> Edit  Product
    </h3>
    <hr />

    <!-- Conditionally display an error message  -->
    <% if !Empty(pcErrorMsg) %>
    <div class="alert alert-warning">
        <i class="fa fa-warning error"></i>
        <%= pcErrorMsg %>
    </div>
    <% endif %>

    <form action="editproduct.wp" method="POST"
          class="form-horizontal container"
          style="padding: 0 15px 30px;">

        <div class="form-group">
            <label class="col-sm-2">Product:</label>
            <div class="col-sm-7">
                <input name="Prodname" value="<%= poProduct.prodname %>"
                       class="form-control"
                       placeholder="Enter a product name" />
            </div>
        </div>

        <div class="form-group form-horizontal">
            <label class="col-sm-2">Unit Price:</label>
            <div class="col-sm-7">
                <input name="UnitPrice" value="<%= poProduct.UnitPrice %>"
                       class="form-control"
                       placeholder="Enter the unit price in dollars" />
            </div>
        </div>

        <hr />

        <button type="submit" name="btnSubmit" class="btn btn-primary">
            <i class="fa fa-check"></i> Save Product
        </button>

        <input type="hidden" name="id" value="<%= poProduct.id %>" />
    </form>
</div> <!-- container -->

My question: When I created the EditProduct.wp file from VS, I did not get asked anything like "what table is this being created for" so how does EditProduct.wp have any code in in (above) about the products table's fields?

Thanks, John

Gravatar is a globally recognized avatar based on your email address. re: unbindformvars issue
  VFPJCOOK
  VFPJCOOK
  Mar 16, 2020 @ 02:25pm

Perhaps a better question would be how to write foxpro code that would replace all that "customer" specific stuff and I assume that will become evident as I get a better understanding of how I would do so using the framework.

Not sure what you mean by that. The templates you create are always specific page implementations that are pretty much always going to map directly against data. They are not meant as generic tools to be created on the fly - ie. there's no object model and the layout is relatively fixed based on what you create in HTML. You can embed code inside of templates that generate HTML but that's not really a design goal.

The Web Control Framework (WCF) had a complex page object model (similar but different than FoxPro Forms) where you could dynaimcally add things into a page, but that model was too complex for most people to use effectively and unfortunately also very difficult to maintain from my end. Very complex and also rather slow. The biggest problem with it was that there were a lot of moving parts to learn and some idiosyncrasies that could be confusing. That framework allowed for dynamic generation of components into a page - lots of flexibility but at the cost of complexity and performance. Alas, the WCF has been deprecated because hardly anyone used it - due to the complexity.

For HTML rendering templates and MVC are the way to go...

If you want to build generic/reusable tooling you can do that with pure code either inside of a process method or - more appropriate - by creating a custom component or render function akin to HtmlDataGrid() and HtmlRecord().

+++ Rick ---

© 1996-2020