We call Surface Events – or Client-side Events – events that trigger fast and only alter the aspect of the display and/or static contents, and thus don’t require an interaction with the data on the server.

In short Surface Events only affect the surface of the application without requesting the server.

In FoxInCloud Live Tutorial’s Controls and Events form, surface events are those listed in the bottom left box, namely .MouseEnter(), .MouseMove(), .MouseLeave() and .KeyPress() 1.

Surface Events In FoxInCloud Live Tutorial's 'Controls and Events' form

As Surface Events don’t address the server, the VFP code can’t run as for the default FoxInCloud behavior: some ‘pure web’ code – JavaScript and/or CSS – is required!

This post explains how to take advantage of the powerful Web languages – especially CSS – to replace several VFP instructions and methods by a handful of CSS ‘directives’.

The adaptation statistics shared by developers reveal that Surface Events adaptation account for an average 10% of the total adaptation effort, so hopefully this post can help you save time in this area.

From a Web perspective, Surface Events (SE) break down into 2 categories:

  • SE altering display of contents only
  • SE altering contents itself

As usual for this blog, this post relies on the FoxInCloud Live Tutorial, namely the Surface Event form:

FoxInCloud Live Tutorial's *Surface Event* form

Surface Events altering display only

Influenced by the Web look and feel, it is quite common for a Desktop Application to suggest an interaction to the user using visual effects; eg. when user hovers a control with the mouse, change its forecolor and/or backcolor.

If you have such effect in your Desktop Application, you obviously want a similar effect to happen in your FoxInCloud Web Application.

Of course, due to the speed of such event, the standard FoxInCloud mechanism – execute the corresponding FoxPro Event code on the server – would be unusable to achieve the effect: things must happen right on the client browser.

Buttons in the Surface Event form are derived from this a class: modify class ficCmdHover of home(1) + 'tools\ab\aw\sample\fic\class\ficSample'. After running FAA step 2, its code looks like this 2:

PROCEDURE Init
* {en} create properties to save current aspect
this.AddProperty('FontBold_', this.FontBold)
this.AddProperty('BackColor_', this.BackColor)
this.AddProperty('ForeColor_', this.ForeColor)
return DoDefault()

* ---------------------------------------------
PROCEDURE MouseEnter
lparameters nButton, nShift, nXcoord, nYcoord && {en} doc in Parent Code

IF (Type('m.thisForm.wlHTMLgen') == 'L' AND m.thisForm.wlHTMLgen) && {en} Added by FoxInCloud base classes Manager (source mode)
  return .F.
endif

* {en} save current aspect
this.FontBold_  = this.FontBold
this.BackColor_ = this.BackColor
this.ForeColor_ = this.ForeColor

* {en} set hover aspect
this.FontBold  = !this.FontBold
this.BackColor = 0 && black
this.ForeColor = Rgb(255,255,255) && white

* ---------------------------------------------
PROCEDURE MouseLeave
lparameters nButton, nShift, nXcoord, nYcoord && {en} doc in Parent Code

IF (Type('m.thisForm.wlHTMLgen') == 'L' AND m.thisForm.wlHTMLgen) && {en} Added by FoxInCloud base classes Manager (source mode)
  return .F.
endif

* {en} restore previous aspect
this.FontBold = this.FontBold_
this.BackColor = this.BackColor_
this.ForeColor = this.ForeColor_

By default in Surface Events code, FAA has added RETURN .F. to prevent FAS from implementing an event handler that would send the event to the server.

Below we’ll see how to implement a similar effect on the web using JavaScript, CSS, or both 3.

Solution 1. Using JavaScript only

Using JavaScript is the method closest to what we used to do in VFP; however it takes limited advantage of more powerful Web techniques.

Here are some hints for understanding the code below:

  1. FoxInCloud considers non-empty string value that event method returns from within if m.thisForm.wlHTMLgen … endif as JavaScript code to be executed in the browser when a similar event occurs,
  2. We use the textmerge keyword to easily combine JavaScript code and VFP values
  3. Each adapted VFP object inherits a .wcID property that FoxInCloud populates with a unique ID at runtime 4; it also uses this ID to populate the id and class attributes when generating the corresponding HTML element.
  4. jQuery is a popular JavaScript framework (library) that FoxInCloud installs and loads automatically; jQuery('#id') selects the element in the page having ‘id’ as id="" attribute (see previous point), and exposes methods that alter the HTML element and/or its CSS
  5. To change the HTML element’s aspect, we use the jQuery .css() method with an object as parameter; each property of this object is named as the corresponding CSS property
  6. {"property": value, "property": value, …} is called JavaScript Object Notation (JSON), in other word a literal representing an object 5
  7. Regarding line termination, JavaScript and VFP work the other way round: JS uses ‘;’ as instruction terminator, instructions without a closing ‘;’ continue on next line
  8. Block comment /* comment */ can be located anywhere, even inside an instruction
PROCEDURE MouseEnter
lparameters nButton, nShift, nXcoord, nYcoord && {en} doc in Parent Code

DoDefault() && see code in parent class

* {en} We change the HTML element's style directly with jQuery.css() with an object using JSON syntax as parameter
* {en} This technique is the closest to what you're used to do in VFP, however not very 'web-aware'…
* {en} Note that you can see the HTML changing in the browser development tools

IF (Type('m.thisForm.wlHTMLgen') == 'L' AND m.thisForm.wlHTMLgen) && {en} Added by FoxInCloud base classes Manager (source mode)

  local lcJS
  text to lcJS textmerge noshow flags 1 pretext 15
    jQuery(".<<this.wcID>>").css({ /* jQuery() creates an object around the HTML element(s) with a '.css()' method */
        "font-weight": "bold"
      , "color": "white"
      , "background-color": "black"
     }); /* End of instruction starting with 'jQuery(' */
  endtext
 
  RETURN m.lcJS && {en} FoxInCloud interprets a non-empty string as JavaScript to be executed when event occurs
endif

* ----------------------------------
PROCEDURE MouseLeave
lparameters nButton, nShift, nXcoord, nYcoord && {en} doc in Parent Code

DoDefault() && see code in parent class

* {en} We change the HTML element's style directly by passing an object using JSON syntax to jQuery.css()
* {en} This technique is the closest to what you use to do in VFP, however not very 'web-aware'…
* {en} Note that setting the back color back to what is in VFP may contradict what you expect from a CSS framework such as Bootstrap…
* {en} Also note that you can see the HTML changing in the browser development tools

IF (Type('m.thisForm.wlHTMLgen') == 'L' AND m.thisForm.wlHTMLgen) && {en} Added by FoxInCloud base classes Manager (source mode)

  local lcJS
  text to lcJS textmerge noshow flags 1 pretext 15
    jQuery(".<<this.wcID>>").css({ /* jQuery() creates an object around the HTML element(s) with a '.css()' method */
        "font-weight": "<<Iif(this.FontBold_, 'bold', 'normal')>>"
      , "color": "<<HTMLColor(this.foreColor_)>>"
      , "background-color": "<<HTMLColor(this.backColor_)>>"
     }); /* End of instruction starting with 'jQuery(' */
  endtext

  RETURN m.lcJS && {en} FoxInCloud interprets a non-empty string as JavaScript to be executed when event occurs
endif

Note that this code changes the HTML of the element, and this change can be observed in the browser developer tool:

HTML of the button before hovering:

<button type="button" id="eventclient_scx-pgf-pagaspect-cmdjs"
class="eventclient_scx-pgf-pagaspect-cmdjs btn btn-default btn-lg"
tabindex="330" data-original-title="eventclient_scx.pgf.pagAspect.cmdJS">
JavaScript only
</button>

After hovering the button, the style attribute is set:

<button type="button" id="eventclient_scx-pgf-pagaspect-cmdjs"
class="eventclient_scx-pgf-pagaspect-cmdjs btn btn-default btn-lg"
tabindex="330" data-original-title="eventclient_scx.pgf.pagAspect.cmdJS"
style="font-weight: normal; color: rgb(0, 0, 0); background-color: rgb(240, 240, 240);">
JavaScript only
</button>

Note that this JavaScript-driven alteration of HTML style attribute (AKA inline style) takes precedence over the rules defined in CSS files. In the Bootstrap version of the ‘Surface event’ form, we’ve implemented a reminder that appears when mouse leaves the ‘JavaScript only’ button:

Using JavaScript to change aspect may conflict with CSS

Solution 2. Using JavaScript and CSS

In this second example, we’ll define a class-based CSS rule in the application’s custom CSS file tuto.css 6 and, when mouse enters/leaves, dynamically add/remove this class to/from the HTML object.

Again, as the class attribute is part of HTML, we need JavaScript to alter its value, and jQuery will also come to the rescue to simplify the code, namely its .addClass() and .removeClass() methods 7

Here are some hints for understanding the code below:

  • As a variation, in jQuery(".<<this.wcID>>"), we use a class selector (‘.’) instead of an id selector (‘#’). You can use either one as FoxInCloud automatically assigns the same unique string (eventclient_scx-pgf-pagaspect-cmdjscss) as ID and class:
<button type="button" id="eventclient_scx-pgf-pagaspect-cmdjscss"
class="eventclient_scx-pgf-pagaspect-cmdjscss btn btn-default btn-lg"
title="eventclient_scx.pgf.pagAspect.cmdJSCSS" tabindex="331">
JavaScript + CSS
</button>
  • a rule is defined in tuto.css with the class selector .cmd_Hover 8:
.eventclient_scx button:not([disabled]).cmd_Hover {
  font-weight: bold;
  color: white;
  background-color: black;
}

The selector .eventclient_scx button:not([disabled]).cmd_Hover means: any button that is a descendant of an element of class eventclient_scx (the form), is enabled and has class cmd_Hover.

PROCEDURE cmdJSCSS.MouseEnter
  lparameters nButton, nShift, nXcoord, nYcoord && {en} doc in Parent Code

  DoDefault() && see code in parent class

  * {en} In this method, we add a HTML class on .MouseEnter(), and remove it on .MouseLeave()
  * {en} We define the CSS rule applicable to class 'cmd_Hover' in the application CSS file:
  * {en}   modify file tuto.css ; we can update this file separately from the application.
  * {en} Note that we use '_' and capital letters in the custom class name; it can't conflict with framework class name that never use these characters

  IF (Type('m.thisForm.wlHTMLgen') == 'L' AND m.thisForm.wlHTMLgen) && {en} Added by FoxInCloud base classes Manager (source mode)
    RETURN Textmerge([jQuery(".<<this.wcID>>").addClass('cmd_Hover');])
  endif
ENDPROC

PROCEDURE cmdJSCSS.MouseLeave
  lparameters nButton, nShift, nXcoord, nYcoord && {en} doc in Parent Code

  DoDefault() && see code in parent class

  * {en} In this method, we add a HTML class on .MouseEnter(), and remove it on .MouseLeave()
  * {en} We define the CSS rule applicable to class 'cmd_Hover' in the application CSS file:
  * {en}   modify file tuto.css ; we can update this file separately from the application.
  * {en} Note that we use '_' and capital in custom class name; it can't conflict with framework class name that never use these characters

  IF (Type('m.thisForm.wlHTMLgen') == 'L' AND m.thisForm.wlHTMLgen) && {en} Added by FoxInCloud base classes Manager (source mode)
    RETURN Textmerge([jQuery(".<<this.wcID>>").removeClass('cmd_Hover');])
  endif
ENDPROC

Note that the browser (re)evaluates the CSS rules dynamically – and immediatly – whenever any component of the rules changes in HTML.

Solution 3. Using ‘pure’ CSS, version 1

The previous case has unveiled the power of CSS selector syntax that can work on:

  • element position in the page hierarchy tree: descendant, child, sibling, first-of, last-of, etc.
  • element attributes: #id, .class and any other HTML attribute
  • element state :disabled, :checked, :hover, etc. 9

The :hover state fulfills what we need in this case: change the element’s aspect when mouse hovers it.

In this solution, we’ll add a rule to the CSS rules that FoxInCloud generates into a file named awDefault*.css 10.

We add this rule in the .wcHTMLgen() method that the object inherits from the aw??? FoxInCloud ‘base class’ and that FoxInCloud runs when generating the HTML for this object, at first time the containing form is required, and using the .CSScustomAdd() method.

Here are some hints for understanding the code below:

  1. toHTMLgen AS awHTMLgen OF awHTML.prg is a reference to the FoxInCloud HTML generator that drills into the form and containers until each individual member;
  2. Depending on the value that this method RETURNs and/or writes into .wcHTML, the HTML generator toHTMLgen generates standard or custom HTML;
  3. .wcHTMLgen() can be implemented at any level of the VFP class hierarchy, making it possible to make the Web behavior specific to an object or all objects of a given VFP class;
  4. The .CSScustomAdd() method adds custom CSS rules to the standard awDefault*.css style sheet, that the custom style sheet xxx.css can override if needed.
PROCEDURE cmdCSS1.wchtmlgen
  LPARAMETERS toHTMLgen AS awHTMLgen OF awHTML.prg, tlInnerHTML && {en} doc in Parent Code

  DoDefault(m.toHTMLgen, m.tlInnerHTML)

  * {en} Add a ':hover' CSS rule on this object (could be several objects using a class instead of an ID)
  * {en} FoxInCloud adds this rule to the generated default CSS style sheet for application: awDefault*.css
  && {en} note 1: in CSS pure language, ':hover' is called a 'pseudo-class'; browser development tools use this wording
  && {en} note 2: that JS and CSS syntaxes are close; however in CSS directives are unquoted and separated by ';' instead of ','
  && {en} note 3: this rule automatically restores initial aspect when mouse leaves the button

  local lcCSS

  text to lcCSS textmerge noshow flags 1 pretext 15
    #<<this.wcID>>:hover {
      font-weight: bold;
      color: white;
      background-color: black;
     }
  endtext

  m.toHTMLgen.CSScustomAdd(m.lcCSS)
ENDPROC

This solutions works fine for a small number of controls, however, if all controls of a given BaseClass must have a similar behavor, the solution 4 hereafter is more efficient.

Solution 4. Using ‘pure’ CSS, version 2

This solution combines several features that we’ve gone through in the previous solutions:

  1. .class CSS selector: we will a class to the generated HTML element and build a CSS rule based on this custom CSS class
  2. :hover pseudo class; our custom CSS rule will focus on the :hover state of the custom CSS class above
  3. custom CSS class upon HTML generation besides .wcHTMLgen(), each adapted VFP object inherits a .wCSSclassAdd property where you can define additional CSS classes that the FoxInCloud HTML generator will add to the generated HTML element. You can manipulate this property either at design time, using the VFP visual designer, or at run time, in .Init() or form.Load() 11
PROCEDURE cmdCSS2.Init
  * {en} add a CSS class to this object
  * {en} note 1: a 'cmd-hover2:hover' rule is defined in tuto.css (custom style sheet for this application)
  * {en} note 2: could also be done at design time in property sheet; we prefer do it programmatically in case the parent class also has this property defined
  * {en} note 3: we could also add this CSS class to the parent class (ficCmdHover) to have all derived buttons behave this way

  this.wCSSclassAdd = this.wCSSclassAdd;
    + Iif(empty(this.wCSSclassAdd), '', ' ');
    + 'cmd_Hover2'

  return DoDefault()
ENDPROC

tuto.css defines the corresponding CSS rule:

.eventclient_scx button:not([disabled]).cmd_Hover2:hover {
  font-weight: bold;
  color: white;
  background-color: black;
}

As tuto.css can be updated in production separately from the app, changing the behavior (eg. background-color) does not require updating the application.

Surface Events altering contents

FoxInCloud Live Tutorial's *Surface Event* form

FoxInCloud Live Tutorial's *Surface Event* form

Altering HTML contents always require JavaScript; CSS can alter the aspect of contents only, not the contents itself.

To change HTML contents, we also use jQuery and its utility methods:

  • .prop(): creates or reads a property of the HTML element
  • .text(): replaces or reads the text inside HTML paired tags 12

Note that FoxInCloud detects the user browser’s preferred language as a 2-letter ISO 639 code and stores it into:

  • client side: FoxInCloud.lang
  • server side: thisForm.wcLangUser (inherited from aw.vcx!awFrm)
PROCEDURE cmd.Init
  this.AddProperty('Caption_', '')
  return DoDefault()
ENDPROC

PROCEDURE cmd.MouseEnter
  lparameters nButton, nShift, nXcoord, nYcoord && {en} doc in Parent Code

  local caption_FR, caption_DE, caption_IT, caption_ES, caption_PT, caption_EN

  caption_FR = "Vous me survolez"
  caption_DE = "Du schwebst mich"
  caption_IT = "Mi stai sorvolando"
  caption_ES = "Me estás rondando"
  caption_PT = "Você está me pairando"
  caption_EN = "You're hovering me"

  IF (Type('m.thisForm.wlHTMLgen') == 'L' AND m.thisForm.wlHTMLgen) && {en} Added by FoxInCloud base classes Manager (source mode)

    && {en} This code executes first time server instantiates the form, once for all users
    && {en} As this code executes out of a user context, the user language is not known and must be evalutated at run time
    && {en} At run time, FoxInCloud detects user browser's preferred language and stores it in FoxInCloud.lang (coded on 2 letters as per ISO 639-1 https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes)
    && {en} The JavaScript code below adjusts the contents to the user's language

    local lcJS

    text to lcJS textmerge noshow flags 1 pretext 7
      var cmd = jQuery('#<<this.wcID>>'); /* get a reference to the jQuery object wrapped around this HTML element */
      cmd.prop('Caption_', cmd.text()); /* Save current Caption to a new property of this element created on the fly */
      var newText = '';
      switch (FoxInCloud.lang) { /* https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Instructions/switch */
      case 'fr':
        newText = <<cLitteralJS(m.Caption_FR)>>;
      case 'de':
        newText = <<cLitteralJS(m.Caption_DE)>>;
      case 'it':
        newText = <<cLitteralJS(m.Caption_IT)>>;
      case 'es':
        newText = <<cLitteralJS(m.Caption_ES)>>;
      case 'pt':
        newText = <<cLitteralJS(m.Caption_PT)>>;
      default:
        newText = <<cLitteralJS(m.Caption_EN)>>;
      }

      /* We can also use a syntax similar to ICase() by nesting ternary operators similar to Iif() [condition ? value if true : value if false] …
      slight downside: nested operators need be wrapped inside parentheses for some browsers (eg. IE).
      Below we indent the closing parentheses the same as the opening counterpart to visually prevent nesting errors */

      newText = FoxInCloud.lang === 'fr'
        ? <<cLitteralJS(m.Caption_FR)>>
        : (FoxInCloud.lang === 'de'
          ? <<cLitteralJS(m.Caption_DE)>>
          : (FoxInCloud.lang === 'it'
            ? <<cLitteralJS(m.Caption_IT)>>
            : (FoxInCloud.lang === 'es'
              ? <<cLitteralJS(m.Caption_ES)>>
              : (FoxInCloud.lang === 'pt'
                ? <<cLitteralJS(m.Caption_PT)>>
                : <<cLitteralJS(m.Caption_EN)>>
          ) ) ) );

      cmd.text(newText + '…!'); /* Assign new caption to the button */
    endtext

    RETURN m.lcJS && {en} FoxInCloud interprets a non-empty string as JavaScript to be executed when event occurs
  endif

  && {en} This code executes in desktop mode only

  this.Caption_ = this.Caption

  this.Caption = ICase(;
    thisForm.wcLangUser = 'fr', m.caption_FR,;
    thisform.wcLangUser = 'de', m.caption_DE,;
    thisform.wcLangUser = 'it', m.caption_IT,;
    thisform.wcLangUser = 'es', m.caption_ES,;
    thisform.wcLangUser = 'pt', m.caption_PT,;
    m.caption_en;
    ) + '…!'
ENDPROC

PROCEDURE cmd.MouseLeave
  lparameters nButton, nShift, nXcoord, nYcoord && {en} doc in Parent Code

  IF (Type('m.thisForm.wlHTMLgen') == 'L' AND m.thisForm.wlHTMLgen) && {en} Added by FoxInCloud base classes Manager (source mode)

    && {en} This code executes first time server instantiates the form, once for all users
    && {en} As this code executes out of a user context, the user language is not known and must be evalutated at run time
    && {en} At run time, FoxInCloud detects user browser's preferred language and stores it in FoxInCloud.lang (coded on 2 letters as per ISO 639-1 https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes)
    && {en} The JavaScript code below adjusts the contents to the user's language

    local lcJS

    text to lcJS textmerge noshow flags 1 pretext 7
      var cmd = jQuery('#<<this.wcID>>'); /* get a reference to the jQuery object wrapped around this HTML element */
      cmd.text(cmd.prop('Caption_')); /* Assign previous saved caption to the button */
    endtext

    RETURN m.lcJS && {en} FoxInCloud interprets a non-empty string as JavaScript to be executed when event occurs
  endif

  && {en} This code executes in desktop mode only

  this.Caption = this.Caption_
ENDPROC

Wrap-up

Hopefully this post has teached you the important basics of CSS and JS programming, and the main hooks where FoxInCloud lets you add this client-side code to your application:

  • CSS selectors
  • jQuery and its utility methods
  • FoxInCloud CSS: awDefault*.css and xxx.css
  • In VFP event methods, return JavaScript from within if m.thisForm.wlHTMLgen … endif
  • .wcHTMLgen() for generating custom HTML, CSS and/JS
  • .wCSSclassAdd for adding CSS class(es) to one object or all objects derived from a class
  • toHTMLgen.CSScustomAdd() to add custom CSS rules

Video


  1. on some controls like textBox, .InteractiveChange() also falls into the ‘Surface Event’ category, that FoxInCloud will soon support using the recent HTML DOM Event level 3 API .oninput() 

  2. Button instances in EventClient.scx call this form using dodefault() 

  3. As you may be aware of, HTML defines the contents and structure, and CSS defines how this content and structure appears to the user. Events occurring in the browser execute JavaScript code that can alter HTML, CSS or both. In earlier versions on HTML, some HTML tags and attributes could influence the aspect (such as <b>, <i>, width, align, background, etc.); today these tags and attributes are fully deprecated and should no longer be used. Visual FoxPro works with similar principles except Layout properties are just a category in the visual designers and follow the same logic as any other class or object property 

  4. this ID concatenates .Name of form, object parents and object itself 

  5. VFP does not offer a similar ability to represent an object as a literal; conversely JavaScript misses the date and date-time literal feature like in VFP. Note that JavaScript accepts the dash (‘-‘) character inside property names, which VFP rejects … each language has its pros and cons… 

  6. named after the FoxInCloud Web App xxx code – tuto.css in this case – the App custom CSS file xxx.css is loaded by FoxInCloud on each page/form, after all other CSS files, particularly the default, FiC-generated awDefault*.css. This gives the ability to (1) override any standard CSS directive and (2) define custom directives. 

  7. note the camel case convention: in the ‘Web world’, everything is lower case except the first letter of successive words. 

  8. note the block comment has the same syntax as in JavaScript 

  9. W3C calls these state modifiers ‘pseudo-classes’ 

  10. during development, FoxInCloud dynamically generates awDefault_<sys(2015)>.css, and awDefaultAll.css for production 

  11. altering .wCSSclassAdd at run time is advisable for easier maintenance in case .wCSSclassAdd changes in a parent class. 

  12. while some tags such as <br> or <img> are unpaired or self-closing, most HTML tags require a closing tag: <p>text</p>, <i>text</i>, <div>text</div>, etc. 



Watch FoxInCloud Marketing Videos :: Watch FoxInCloud Technical Videos :: Stay tuned on FoxInCloud Roadmap :: Learn how to use FoxInCloud :: Download FoxInCloud Adaptation Assistant for free :: Download FoxInCloud Web Application Studio for free