FoxPro Programming
wwHtmlHelpers: HtmlDataGrid
Gravatar is a globally recognized avatar based on your email address. wwHtmlHelpers: HtmlDataGrid
  Kathy
  All
  Apr 9, 2020 @ 10:01am

Hello,
I was wondering if I can use HtmlDataGrid with its paging, sorting and other facilities in IE browser control in my Foxpro desktop application.
So, is there any way to do it without running a server at the back or if it's needed to do it only with the localhost and a virtual directory?
Thanks,
Kathy

Gravatar is a globally recognized avatar based on your email address. re: wwHtmlHelpers: HtmlDataGrid
  Rick Strahl
  Kathy
  Apr 9, 2020 @ 12:16pm

The wwHtmlHelpers in general can render without any server side support. They are mostly self contained.

So this works:

DO WCONNECT

USE Customers

lcHtml = HtmlDataGrid("Customers")
ShowHtml(lcHtml)

This assumes a few things though - you render this and have to make sure some of the script and css dependencies are available when you display the output (ie. render into a page that has bootstrap and font-awesome loaded).

The problem is with operations like paging and sorting etc. - those things do rely on server side code, but if you really want to get creative you can capture that information from the WebBrowser control (like clicking a page link) and setting up the DataGrid config manually to specify the page to display for example. Possible yes, easy - probably not unless you just need to display the grid in a one shot display.

Gravatar is a globally recognized avatar based on your email address. re: wwHtmlHelpers: HtmlDataGrid
  Kathy
  Rick Strahl
  Apr 9, 2020 @ 01:00pm

Thank you so much Rick,
I'm so excited about this and I've already created a test page like this:

HtmlDataGrid is working beautifully as always.
Now, I know it might be too much but is it possible to have something like a partial render only for one of the grids shown above after capturing the click event for sorting or paging you've mentioned?

Gravatar is a globally recognized avatar based on your email address. re: wwHtmlHelpers: HtmlDataGrid
  Rick Strahl
  Kathy
  Apr 9, 2020 @ 05:23pm

Hi Kathy,

Nice!

+++ Rick ---

Gravatar is a globally recognized avatar based on your email address. re: wwHtmlHelpers: HtmlDataGrid
  Steve
  Kathy
  Apr 9, 2020 @ 05:59pm

Hi Kathy,
This is very nice indeed. I have used the IE Browser Control in my Desktop VFP App for a few things, but have never used the HtmlDataGrid. Could you share how you accomplish this? Maybe a small sample? I will review the WWWC help file to learn more about HtmlDataGrid. I also need to add a Dashboard to my VFP Desktop App sometime in the near future. This looks like a great way to go. Great work!!

Thanks,
Steve

Gravatar is a globally recognized avatar based on your email address. re: wwHtmlHelpers: HtmlDataGrid
  Kathy
  Steve
  Apr 9, 2020 @ 10:57pm

Hi Steve,
That's so nice of you.
I really did nothing more than creating an html template with a bunch of div placeholders and then replace them with the htmlDataGrid output. And as Rick has mentioned above, we need to make sure some of the script and css dependencies are loaded in the template (ie. bootstrap, font-awesome and google chart dependencies in my case).
But I still need to learn more on this before go further. Rick's documents and examples on "Web Browser Control" would be a great source.
We should still be able to find all the code samples, the white paper and session slides on BitBucket:
SWFOX_WebBrowser

I'm still waiting to see if we can have a partial render on the page or not because that'll make a big difference.
I'll post the template and the Foxpro code as soon as I'm done with a clean up.
Regards,
Kathy

Gravatar is a globally recognized avatar based on your email address. re: wwHtmlHelpers: HtmlDataGrid
  Kathy
  Rick Strahl
  Apr 9, 2020 @ 11:25pm

Thank you so much for the boost Rick!
I apologize if my last question (under the screenshot) was not clear enough.
So, HtmlDataGrid is working beautifully as always.
But is it possible to have something like a partial render for the grids shown above to work by capturing the click event for sorting or paging you've mentioned earlier?
Thanks,
Kathy

Gravatar is a globally recognized avatar based on your email address. re: wwHtmlHelpers: HtmlDataGrid
  Rick Strahl
  Kathy
  Apr 10, 2020 @ 12:45am

Sort of yes...

You can inject HTML into the existing content by setting the element.innerHTML property. Create something like this in your HTML:

<div id="PartialContent">

<...dataGrid goes here...>

</div>

Then when you want to update you do:

lcHtml = HtmlDataGrid(...) && or whatever

el = Browser.Document.GetElementById("PartialContent")
el.innerHTML = lcHTML   && or el.Content = lcHtml

+++ Rick ---

Gravatar is a globally recognized avatar based on your email address. re: wwHtmlHelpers: HtmlDataGrid
  Kathy
  Rick Strahl
  Apr 10, 2020 @ 08:05am

Thank you so much for the great help Rick.
And please forgive my limited knowledge but does this have anything to do with RenderPartial(...) or RenderSection() at all?
Thanks,
Kathy

Gravatar is a globally recognized avatar based on your email address. re: wwHtmlHelpers: HtmlDataGrid
  Steve
  Kathy
  Apr 10, 2020 @ 10:46am

That is very kind of you Kathy!! Thank you!!

Steve

Gravatar is a globally recognized avatar based on your email address. re: wwHtmlHelpers: HtmlDataGrid
  Rick Strahl
  Kathy
  Apr 10, 2020 @ 02:37pm

Partial and Layout Pages

That depends on what you are willing to pull into your application. Out of the box, no partial and layout pages don't work since that's a feature of Web Connection internally.

But...

You can do a couple of things:

  • Use wwPageResponse directly
  • Use wwScripting directly

Using wwPageResponse basically fakes out Web COnnection and sets up a request.

This works to render an HTML page just like a Web Connection script page does:

DO wwRequest
DO wwPageResponse
DO wwScripting
DO wwCollections
DO wwXml
DO wwHtmlHelpers

*** Have to create Request object since that's used by the Response
PRIVATE Request, Server

*** Create a Fake Server object that are used by ExpandScript
Server = CREATEOBJECT("EMPTY")
ADDPROPERTY(Server,"lDebugMode",.T.)
ADDPROPERTY(Server,"nScriptMode", 1)

*** Create local Request so that Physical Path and Application Path can be resolved
Request = CREATEOBJECT("wwRequest")
Request.cPhysicalPath = "\webconnection\fox\web\noscript.wcs"
Request.cApplicationPath = "\webconnection\fox\web"

*** Create the Response to ExpandScript()
loResponse = CREATEOBJECT("wwPageResponse")
loResponse.ExpandScript("C:\webconnection\Fox\Web\wcscripts\nocode.wcs")
lcHtml = loResponse.GetOutput()

ShowHtml(lcHtml)
RETURN

It's also possible to use the wwScripting class directly to avoid having to pull in all the Web Connection objects into a non-Web application.

Class wwScripting

Here's what that looks like:

DO wwScripting
DO wwHtmlHelpers && not required used only if used in the scripts

loScript = CREATEOBJECT("wwScripting")
loScript.lSaveVfpSourceCode = .T. && Optional for demo so we can see the code
loScript.lEditErrors = .T. && Allow editing errors immediately in the Fox Editor (goes to line of code if possible)
loScript.lShowFullErrorInfo = .T.

loScript.cBasePath = "c:\webconnection\fox\web"

lcHTML = loScript.RenderAspScript("C:\webconnection\Fox\Web\wcscripts\nocode.wcs")

IF loScript.lError
      ? loScript.cErrorMsg

      IF !ISNULL(loScript.oException)
         ? loScript.oException.LineContents
         ? loScript.oException.Details
         ? loScript.oException.LineNo
      ENDIF
ENDIF   

ShowHtml(lcHtml)

The latter is arguably simpler and actually gives much more control and doesn't require access to all of the Web Connection objects. Takes a bit more tweaking to get the settings you want right though.

The latter direct access to the wwScripting class is also what I use in Help Builder for rendering the help content which uses partials, and layout pages extensively to render the help content and master templates.

+++ Rick ---

Gravatar is a globally recognized avatar based on your email address. re: wwHtmlHelpers: HtmlDataGrid
  Kathy
  Rick Strahl
  Apr 10, 2020 @ 02:39pm

It works!
You are amazing!

Gravatar is a globally recognized avatar based on your email address. re: wwHtmlHelpers: HtmlDataGrid
  Rick Strahl
  Kathy
  Apr 10, 2020 @ 02:54pm

Note that this will let you render the content but it won't automatically help with routing the incoming event data like a postback or other navigation to redraw the content when something changes.

It's possible to do this by taking over the WebBrowser BeforeNavigate event and reading the request data (querystring and POST buffer if sending). It can be done for relatively simple scenarios (ie. navigation like paging), but gets really hairy if you need to handle complex data like forms with tons of input fields.

+++ Rick ---

Gravatar is a globally recognized avatar based on your email address. re: wwHtmlHelpers: HtmlDataGrid
  Kathy
  Rick Strahl
  Apr 10, 2020 @ 04:01pm

Thank you for the tip.
We're already using BeforeNavigate.
It's still a hybrid system using FoxPro data entry forms but I'll keep your advice in mind for the next step.
Thanks again,
Kathy

Gravatar is a globally recognized avatar based on your email address. re: wwHtmlHelpers: HtmlDataGrid
  Kathy
  Rick Strahl
  Apr 19, 2020 @ 04:09pm

Thank you for the detailed instructions above.
It's magnificent.

Gravatar is a globally recognized avatar based on your email address. re: wwHtmlHelpers: HtmlDataGrid
  Kathy
  Steve
  Apr 19, 2020 @ 08:29pm

Hi Steve, I'm so sorry for being late on this. I was working on the original idea and with Rick's detailed instructions above there are even much more cool ones to come.
But, here is the very first test page that I began with, I hope it helps a bit.
I'm posting:
* An html template page (kind of a dashboard) for having a plain

tag, HtmlDataGrid and a Google pie chart
* The FoxPro code for generating the final .html file
* The result page in browser

The html template (testDash_Template.html)

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>

    <title>My Dashboard</title>

    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
    <meta name="description" content="" />
 
    <!-- Added: No caching -->
    <meta http-equiv="cache-control" content="no-cache" />
    <meta http-equiv="cache-control" content="no-store" />
    <meta http-equiv="cache-control" content="must-revalidate" />    
    <meta http-equiv="cache-control" content="max-age=0" />
    <meta http-equiv="cache-control" content="post-check=0" />
    <meta http-equiv="cache-control" content="pre-check=0" />
    <meta http-equiv="pragma" content="no-cache" />
    <meta http-equiv="expires" content="-1" />

    <meta name="apple-mobile-web-app-capable" content="yes" />
    <meta name="apple-mobile-web-app-status-bar-style" content="black" />
     

    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css" />
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" />
    <link rel="stylesheet" type="text/css" href="css\application_har.css" />

	<!-- <script src="~\lib\jquery\dist\jquery.min.js"></script>  -->
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
    <script src="https://www.gstatic.com/charts/loader.js" type="text/javascript"></script>

    <script type="text/javascript">
        // Load the Visualization API and the piechart package.
        google.charts.load('current', { 'packages': ['corechart'] });

        // Set a callback to run when the Google Visualization API is loaded.
        google.charts.setOnLoadCallback(drawPieChart);

        // Callback that creates and populates a data table, 
        // instantiates the pie chart, passes in the data and
        // draws it.
        function drawPieChart() {
            // Create the data table.
            var data = new google.visualization.DataTable();
            data.addColumn('string', 'name');
            data.addColumn('number', 'numberofbooks');
            data.addRows([
                ['Alex', 10],
                ['Bethany', 20],
                ['Chris', 30],
                ['David', 15],
                ['Elenor', 25]
            ]);

            // Set chart Options.
            var options = {
                // title: 'ChartTitle_01',
                // width: 600,
                // height: 400,
                legend: {
                    position: 'top',
                    maxLines: 3,
                    alignment: 'center',
                    orientation: 'vertical',
                    textStyle: { color: '#555', fontSize: 11 }
                }, 
                is3D: false,
                pieHole: 0.5,
                //pieStartAngle: 100,
                animation: {
                    startup: true,      
                    duration: 500,
                    easing: 'in',
                },
                //vAxis: { title: "Number of Books", minValue: 0, maxValue: 50 },
                //hAxis: { title: "Names" },
            };

            // Instantiate and draw our chart, passing in some options.
            var chart = new google.visualization.PieChart(document.getElementById('myChart'));

            function selectHandler() {
                var selectedItem = chart.getSelection()[0];
                if (selectedItem) {
                    var cname = data.getValue(selectedItem.row, 0);
                    alert('The user selected ' + cname + '\nYou are getting redirected to Google page!\nGood luck! :)');
                }
                window.open('https://www.google.com', '_blank', false);
            }

            google.visualization.events.addListener(chart, 'select', selectHandler);
            // chart.draw(data, options);
            chart.draw(data, options);      // Draw the chart with Options.
            // initial value
            var percent = 0;
            // start the animation loop
            var handler = setInterval(function () {
                // values increment
                percent += 1;
                // apply new values
                data.setValue(0, 1, percent);
                data.setValue(1, 1, 100 - percent);
                // update the pie
                chart.draw(data, options);
                // check if we have reached the desired value
                if (percent > 74)
                    // stop the loop
                    clearInterval(handler);
            }, 30);
        }
    </script>
</head>

<body>
        <div class="container">
        <p><br /></p>
        <div class="page-header-text">
            <i class="fa fa-list-alt"></i> 
                My Dashboard
        </div>

        <div class="row  border-bottom white-bg dashboard-header">

            <!-- 1st column of the grid  -->
            <div class="col-md-3">
                <h4>My Notifications</h4>
                    myDiv_Placeholder
            </div>

            <!-- 2nd column of the grid  -->
            <div class="col-md-6">
                <h4> My Grid </h4>
                HtmlDataGrid_Placeholder            
            </div>

            <!-- 3rd column of the grid  -->
            <div class="col-md-3">
                <div class="statistic-box">
                    <h4> My Chart</h4>
                    <div id="myChart" style="width:300px; height:250px;"></div>
                </div>
            </div>
        </div>

    </div>

</body>

</html>

The FoxPro code

********************************************************
*** This code is preparing data & html tags for testDash_template for Dashboard ***
*** and creates testDash_sample.html  ***
*** We also can use wwScripting and renderScript methods and simply call Foxpro functions ***
*** from the .html files using <%= %> , etc.         *****
**********************************************************
	DO wConnect

	*** preparing <div> tag for the Notifications div palceholder on the left ***
	TEXT TO lcMyDivTags
        <small>You have 10 messages and 7 notifications.</small>
        <ul class="list-group clear-list m-t">
            <li class="list-group-item fist-item">
                <span class="float-right">
                    09:00 pm
                </span>
                <span class="label label-success">1</span> Please contact me
            </li>
            <li class="list-group-item">
                <span class="float-right">
                    10:16 am
                </span>
                <span class="label label-info">2</span> Sign a contract
            </li>
            <li class="list-group-item">
                <span class="float-right">
                    06:22 pm
                </span>
                <span class="label label-primary">3</span> Open new shop
            </li>
            <li class="list-group-item">
                <span class="float-right">
                    10:06 pm
                </span>
                <span class="label label-default">4</span> Call back to Sylvia
            </li>
            <li class="list-group-item">
                <span class="float-right">
                    12:00 am
                </span>
                <span class="label label-primary">5</span> Write a letter to Sandra
            </li>
        </ul>	
	ENDTEXT 
	*** EOF preparing <div> tag for the Notifications div palceholder on the left ***	

		
	*** preparing Foxpro Cursor ***
	CREATE CURSOR ReadBooks (name c(20), numOfBooks n(10))
	INSERT INTO   ReadBooks (name, numOfBooks) VALUES ("Alex",10)
	INSERT INTO   ReadBooks (name, numOfBooks) VALUES ("Bethany",20)
	INSERT INTO   ReadBooks (name, numOfBooks) VALUES ("Chris",30)
	INSERT INTO   ReadBooks (name, numOfBooks) VALUES ("David",15)	
	INSERT INTO   ReadBooks (name, numOfBooks) VALUES ("Elenor",25)	
		
	*** preparing html string for the grid placeholder using the cursor ***
	loGridConfig = CREATEOBJECT("HtmlDataGridConfig")
	loGridConfig.CssClass = "table table-sm table-striped"
	IF RECCOUNT() > 15
		loGridConfig.PageSize = 15
	ENDIF 
	
	* NOTE: You may want to change the PageBaseLink to communicate with the FoxPro code behind the page for handling the grid sorting or paging.
	loGridConfig.PageBaseLink = "myFoxProLink://myFoxMethod?"		&& In case you need to capture and handle HtmlDataGrid events on the web page in browser
	loGridConfig.CurrentPageIndex = 1		&& In case you need to show the next page after capturing the page click event and reading the PageBaseLink Query string
		
	loCol1 = loGridConfig.AddColumn([HtmlLink("myFoxProLink://myFoxMethod?pk="+name,ReadBooks.name)],"Name","C")	
	loCol1.Sortable = .T.
	loCol1.SortExpression = "upper(ReadBooks.name)"
	
	loCol2 = loGridConfig.AddColumn("ReadBooks.numOfBooks","No. Of Books","N")
	loCol2.ItemAttributeString = [style="text-align: right"]
	loCol2.HeaderAttributeString = [ style="text-align: right" ]

	
	* loGridConfig.OnAfterRowsRendered = "myCalculateTotalBooks()"
	lcGridHtmlstring = HtmlDataGrid("ReadBooks",loGridConfig)  
	*** EOF preparing html string for the grid placeholder ****************************************************************
	

	*** Replacing the placeholders on Dashboard Template with FoxPro string variables ***
	lcDashboardHtml = FILETOSTR("testDash_Template.html")
	lcDashboardHtml = STRTRAN(lcDashboardHtml, "myDiv_Placeholder", lcMyDivTags)
	lcDashboardHtml = STRTRAN(lcDashboardHtml, "HtmlDataGrid_Placeholder", lcGridHtmlstring)
	
	STRTOFILE(lcDashboardHtml, "testDash_sample.html")	
	*** EOF Replacing the Dashboard Template placeholders with FoxPro string variables *******************************************

And they should give you something like this:

And with the help of Rick's instructions above, wwScripting, RenderScript, etc. we can also use Webconnect templates which are more beautiful and more professional.

All the best,
Kathy

Gravatar is a globally recognized avatar based on your email address. re: wwHtmlHelpers: HtmlDataGrid
  Steve
  Kathy
  Apr 20, 2020 @ 06:13pm

Wow!! This is very cool, and very nice of you!! Thank you Kathy for sharing!!
I am really looking forward to experimenting with this Dashboard and hopefully incorporating some of the techniques into my desktop app.

Thanks again and stay safe!
Steve

© 1996-2025