[NEW] Servoy Tutorials

General Forum for posting info for Servoy books, ezines or training cd's

[NEW] Servoy Tutorials

Postby GaryDotzlaw » Sat Nov 23, 2013 5:07 pm

Last edited by GaryDotzlaw on Wed Jan 22, 2014 2:21 am, edited 13 times in total.
Gary Dotzlaw
Dotzlaw Consulting
dotzlaw.com
Version: 7.3.0 - build 2018
Win 7, 64
User avatar
GaryDotzlaw
 
Posts: 38
Joined: Fri Jun 17, 2011 10:00 pm

Re: [NEW] Servoy Tutorials

Postby Roberto Blasco » Sat Nov 23, 2013 9:04 pm

Thnaks a lot GaryDotzlaw

Best regards. Roberto Blasco.
Un saludo. Roberto.

Madrid - Spain
Tfno: (+34) 625653066
E-mail: [email protected]
User avatar
Roberto Blasco
007
 
Posts: 341
Joined: Tue Apr 08, 2008 7:18 pm
Location: Madrid / Spain

Re: [NEW] Servoy Tutorials

Postby Harjo » Sun Nov 24, 2013 7:00 pm

This is great stuff!! Thanks Gary
Harjo Kompagnie
Direct ICT / Servoy Hosting / ServoyCamp
Servoy Certified Developer
Servoy Valued Professional
SAN Developer
User avatar
Harjo
 
Posts: 4271
Joined: Fri Apr 25, 2003 11:42 pm
Location: DEN HAM OV, The Netherlands

Re: [NEW] Servoy Tutorials

Postby Peter de Groot » Sun Nov 24, 2013 7:06 pm

Hi,

Good tutorials especially the foundset tip is great, I did some testing with 677000+ records,
here are some results run with this code,

Code: Select all
var $start = new Date()
/** @type {JSFoundSet<db:/northw/orders>} */
var $fs = databaseManager.getFoundSet('db:/northw/orders');
$fs.loadAllRecords();

/**this line is used in the first test only**/
$fs.getRecord(databaseManager.getFoundSetCount($fs)) 
/**this line is used in the first test only**/
   
for(var i = 1 ; i <= $fs.getSize() ; i++){
    $ff = $fs.getRecord(i).shipname + 'a'
}
   
   var $end = new Date();
   
   
   application.output('Loading 200 records every time ' + (new Date() - $start)+'' + 'ms');
   application.output('Records : '  + $fs.getSize())



this one is from the java console
The Load all records 75749ms
Records : 677011
Loading 200 records every time 403611ms
Records : 677011


This whats happening on the servoy server using your foundset tip, the Performance Data page has only 1 "Load foundset" entry
2013-11-24_1750_cons.png
2013-11-24_1750_cons.png (6.64 KiB) Viewed 3210 times


This is what happens when not using your foundset tip the list is very long , the Performance Data page shows 200
2013-11-24_1753_cons1.png
2013-11-24_1753_cons1.png (33.23 KiB) Viewed 3210 times


Regards,

Peter
Servoy 7.3.x || 7.4.x

NEM ENERGY BV
Peter de Groot
 
Posts: 207
Joined: Thu Jan 10, 2008 8:38 pm
Location: Not sure...

Re: [NEW] Servoy Tutorials

Postby GaryDotzlaw » Sun Nov 24, 2013 7:54 pm

Thanks Guys.

To expand on Peter's observations:

The tutorial on Using an Object as a Cache demonstrates how to load the entire foundset one-time, store it, and retrieve it, whenever you need it. This further reduces load tremendously. It contains all the methods you need to get started and can easily be extended to cache more than just foundsets.

The tutorial on Function Memoization is also a key one to boasting performance. The tutorial shows you how to cache values in an object within your function, so that if you call the method multiple times, you can look-up the computed value from the function's cache, rather than recomputing it all over again. This one has proven itself as a massive performance boast over and over again.

I will put up some more articles soon.
Gary Dotzlaw
Dotzlaw Consulting
dotzlaw.com
Version: 7.3.0 - build 2018
Win 7, 64
User avatar
GaryDotzlaw
 
Posts: 38
Joined: Fri Jun 17, 2011 10:00 pm

Re: [NEW] Servoy Tutorials

Postby jcarlos » Mon Nov 25, 2013 8:42 am

Gary. This is excellent work.

I think you should publish these tutorials on ServoyForge. (ServoyForge is a repository or clearinghouse for Servoy related tutorials and open source projects.)

For example, Patrick Talbot published there a link and intro to his Servoy-Stuff Tutorials. He also published there the article: "Where is your code executed?"

I think that ServoyForge is the best clearinghouse for the Servoy community.. Also your work will have more exposure at ServoyForge because, after a couple of month, your tutorials will not be readily visible in he forum, but they will be visible on the home page of ServoyForge as long as you want. This makes it easy for newbies and no-so newbies to find these magnificent tutorials.

Anyway, thank you very much for all of this.
Carlos
Juan-Carlos Sanchez, Stanford University - Law and Economics
jcarlos
 
Posts: 569
Joined: Thu May 04, 2006 8:55 pm
Location: Palo Alto, California USA

Re: [NEW] Servoy Tutorials

Postby GaryDotzlaw » Mon Nov 25, 2013 7:26 pm

Peter,

I'm curious if you will also see even more performance improvements if you cache the foundset.getSize() before your loop like this:
<edit> code edited to include loading the max records
Code: Select all
iMax = databaseManager.getFoundSetCount($fs);
$fs.getRecord(iMax) ;
   
for(var i = 1 ; i <= iMax ; i++){
    $ff = $fs.getRecord(i).shipname + 'a'
}


rather than how you ran it in your tests, like this:
Code: Select all
databaseManager.getFoundSetCount($fs);

for(var i = 1 ; i <= $fs.getSize() ; i++){
    $ff = $fs.getRecord(i).shipname + 'a'
}


My testing in Optimizing Code Performance found that the for loop ran slower if you used the foundset.getSize() in the loop. This would be because it has to check the foundset size every time through the loop, which is unnecessary if you are not adding records. This is similar to optimizing a loop through an array; cache array.length before the for loop and avoid Javascript having to go and look it up every iteration.

With small foundsets, this might be a micro-optimization, but I found it translates to significant performance improvements when working with larger foundsets. Bottom-line is that I now force myself to write all for loops with cached values; I think its a good habit to get into because you just never know when someone is going to shove a large volume of records down your method's throat.
Last edited by GaryDotzlaw on Wed Nov 27, 2013 3:36 pm, edited 1 time in total.
Gary Dotzlaw
Dotzlaw Consulting
dotzlaw.com
Version: 7.3.0 - build 2018
Win 7, 64
User avatar
GaryDotzlaw
 
Posts: 38
Joined: Fri Jun 17, 2011 10:00 pm

Re: [NEW] Servoy Tutorials

Postby ptalbot » Mon Nov 25, 2013 7:45 pm

if you cache getSize() it will return 200 for a foundset that can have millions of records.
So your loop will stop at 200 and not iterate upon the whole foundset... quite a performance enhancement though! ;)

You should do
var iMax = databaseManager.getFoundsetCount($fs);
to get the real number of records if you want that cached...
Patrick Talbot
Freelance - Open Source - Servoy Valued Professional
http://www.servoy-stuff.net
https://www.servoyforge.net
--------------------------------------------
Servoy 5.2.16 / 6.0.9 / 6.1.6 / 7.3.1
All OSes / Java 5 & 6 & 7
User avatar
ptalbot
 
Posts: 1612
Joined: Wed Mar 11, 2009 5:13 am
Location: Montreal, QC

Re: [NEW] Servoy Tutorials

Postby GaryDotzlaw » Mon Nov 25, 2013 8:03 pm

Agreed. He had loading all the records in his test scenario, but he should test it using the cached value in his loop. Best would be to rewrite his test like this:

Code: Select all
iMax = databaseManager.getFoundSetCount($fs);
$fs.getRecord(iMax) ;
   
for(var i = 1 ; i <= iMax ; i++){
    $ff = $fs.getRecord(i).shipname + 'a'
}
Gary Dotzlaw
Dotzlaw Consulting
dotzlaw.com
Version: 7.3.0 - build 2018
Win 7, 64
User avatar
GaryDotzlaw
 
Posts: 38
Joined: Fri Jun 17, 2011 10:00 pm

[ANN] New Servoy Tutorials

Postby GaryDotzlaw » Mon Nov 25, 2013 8:46 pm

I posted two additional tutorials, and updated the list at the top of this thread. The two new tutorials are:


I hope you enjoy them.
Gary Dotzlaw
Dotzlaw Consulting
dotzlaw.com
Version: 7.3.0 - build 2018
Win 7, 64
User avatar
GaryDotzlaw
 
Posts: 38
Joined: Fri Jun 17, 2011 10:00 pm

Re: [NEW] Servoy Tutorials

Postby deezzub » Tue Nov 26, 2013 10:50 am

GaryDotzlaw wrote:


Code: Select all
function Person (firstName, lastName, title){
    this.firstName = firstName;
    this.lastName = lastName;
    this.title = title;
}

var Person_proto = function(){
    Person.prototype = {
        fullName : function(){
            return this.firstName + " " + this.lastName;
        },
        fullNameTitle : function(){
            return this.fullName() + ", " + this.title;
        }
    }
}();

var person = new Person('Gary','Dotzlaw','JavaScript Servoy Blogger');


Is it possible to get code completion for the Person.prototype functions?

2013-11-26 09_46_21-Servoy_Developer_person_prototype_code_completion.png
person no code completion for prototype functions
2013-11-26 09_46_21-Servoy_Developer_person_prototype_code_completion.png (53.45 KiB) Viewed 3046 times


P.S.: Thanks, for the tutorials. :)
Sebastian Schlatow
deezzub
 
Posts: 328
Joined: Tue May 28, 2013 3:02 pm
Location: Oldenburg, Germany

Re: [NEW] Servoy Tutorials

Postby GaryDotzlaw » Tue Nov 26, 2013 4:24 pm

Sebastian,

I'm glad you are enjoying the articles. There are more to come, and the next few will kick it up a notch.

I am not aware of any way to get Servoy to do code completion on the prototype functions. Servoy does not handle prototype very elegantly, at this time. The problem is demonstrated by the fact that we have to use a self-invoked, immediate function call, that we assign to a variable, just to add to the prototype. Servoy doesn't know the prototype is there, and what functions you have added, until the client is running and it reads the .js file. As a result, in the editor, you are left with no code completion for the functions, and you will have your typical annoying JSDoc warnings (suppress or use bracket notation like person["fullName"]()).

Honestly, I think Servoy needs to support objects and the prototype better. There is really no good reason it can not see the functions; every other editor can.
Gary Dotzlaw
Dotzlaw Consulting
dotzlaw.com
Version: 7.3.0 - build 2018
Win 7, 64
User avatar
GaryDotzlaw
 
Posts: 38
Joined: Fri Jun 17, 2011 10:00 pm

Re: [NEW] Servoy Tutorials

Postby grahamg » Tue Nov 26, 2013 7:13 pm

Gary

I'm just catching up on these Tutorials and really appreciate you taking the time to share.

When I'm struggling on a problem or trying to optimise code it is really useful to see how others have approached similar situations. Your combination of code and explanations are excellent.

Thanks again.


Graham
Graham Greensall
Worxinfo Ltd
www.worxinfo.com
grahamg
 
Posts: 723
Joined: Fri Oct 03, 2003 3:15 pm
Location: Midlands UK

Re: [NEW] Servoy Tutorials

Postby GaryDotzlaw » Tue Nov 26, 2013 7:29 pm

Thanks Graham. I appreciate the feedback.
Gary Dotzlaw
Dotzlaw Consulting
dotzlaw.com
Version: 7.3.0 - build 2018
Win 7, 64
User avatar
GaryDotzlaw
 
Posts: 38
Joined: Fri Jun 17, 2011 10:00 pm

Re: [NEW] Servoy Tutorials

Postby david » Tue Nov 26, 2013 8:24 pm

To start with, very nice write-ups! Not easy to be clear and understandable with complex topics and you're nailing it.

GaryDotzlaw wrote:Honestly, I think Servoy needs to support objects and the prototype better. There is really no good reason it can not see the functions; every other editor can.


I think you missed the relatively recent Servoy addition of the @constructor jsdoc tag. Basically fixes all of your workarounds with the prototype.

From a Servoy Magazine article that we just haven't gotten around to writing up (so I'll just post it here and let you explain in english!), some code. The constructor function "Resolution" is used to build up a a popup menu of items with each item passing along associated properties to the popup menu's action method. Which just so happens to be the same method so eventually at the end of the method, you have webutils stuff happening with parameters based on the selected menu item's instance of the prototype. Code credits to Troy:

Code: Select all
/**
* @param {JSEvent|Number} [event] the event that triggered the action/item index
* @param {Number} [menuParentIdx]
* @param {Boolean} [menuSelected]
* @param {String} [menuParentTxt]
* @param {String} [menuTxt]
* @param {String} [name] Name of item selected
* @param {Number} [width]
* @param {Number} [height]
* @param {Boolean} [getDims] Return dimensions for selected option
*
* @return {{width: Number, height:Number}|undefined}
*
* @properties={typeid:24,uuid:"2EFD8325-BF6F-40CC-8188-C445DAB04ECC"}
*/
function ACTION_resize(event,menuParentIdx,menuSelected,menuParentTxt,menuTxt,name,width,height,getDims) {
   /**
   * @constructor
   * @param {String} name Display value of resolution
   * @param {Number} big Largest side of resolution
   * @param {Number} little Smallest side of resolution
   */
   function Resolution(name, big, little) {
      /**
       * Display value of resolution
       * @type {String}
       */
      this.name = name
      /**
       * Largest side of resolution
       * @type {Number}
       */
      this.big = big
      /**
       * Smallest side of resolution
       * @type {Number}
       */
      this.little = little
      /**
       * Display "Name (horizontal x portrait)"
       * @return {String}
       */
      this.getName = function() {
         return name + ' (' + this.getDimHoriz() + ' x ' + this.getDimVert() +')'
      }
      /**
       * Returns horizontal dimension
       * @return {Number}
       */
      this.getDimHoriz = function() {
         return _resizeOrient ? little : big
      }
      /**
       * Returns vertical dimension
       * @return {Number}
       */
      this.getDimVert = function() {
         return !_resizeOrient ? little : big
      }
   }
   
   if (application.getApplicationType() != APPLICATION_TYPES.WEB_CLIENT) {
      plugins.dialogs.showErrorDialog(
            'Error',
            'Resize works only in the web'
         )
   }
   else {
      var map = {
            phone: [
               new Resolution('Normal',480,320),
               new Resolution('iPhone 5+',568,320),
               new Resolution('nHD',640,360)
            ],
            tablet: [
               new Resolution('iPad',1024,768),
               new Resolution('7-inch',1024,600),
               new Resolution('Large',640,480),
               new Resolution('Ginormous',960,720)
            ],
            desktop: [
               new Resolution('Most common',1366,768), //as of 2013 Oct
               new Resolution('Square',1280,1024),
               new Resolution('20-incher',1680,1050),
               new Resolution('24-incher',1920,1200)
            ],
            tv: [
               new Resolution('720p HD',1280,720),
               new Resolution('1080p Full HD',1920,1080),
               new Resolution('4K Ultra HD',3840,2160)
            ]
         }
      map.phone.icon = 'media:///ssstandard_mobile_small.png'
      map.tablet.icon = 'media:///ssstandard_tablet_small.png'
      map.desktop.icon = 'media:///ssstandard_desktop_small.png'
      map.tv.icon = 'media:///ssstandard_landscape_small.png'
      
      /**
       * @param {String} type
       */
      function mapLoop(type) {
         var displayName = type == 'tv' ? 'TV' : utils.stringInitCap(type)
         
         //divider with name and icon
         if (map[type].icon) {
            item = menu.addMenuItem(displayName)
            if (solutionModel.getMedia(utils.stringReplace(map[type].icon,'media:///',''))) {
               item.setIcon(map[type].icon)
            }
            item.enabled = false
         }
         
         for (var i = 0; i < map[type].length; i++) {
            /** @type {Resolution} */
            var res = map[type][i]
            if (res instanceof Resolution) {
               if (res.name == _resizeSelected) {
                  item = menu.addCheckBox(res.getName())
                  item.selected = true
               }
               else {
                  item = menu.addMenuItem(res.getName())
               }
               
               item.setMethod(ACTION_resize)
               item.methodArguments = [res.name,res.getDimHoriz(),res.getDimVert()]
            }
         }
      }
      
      //when right clicked, give a moment to grab focus elsewhere
      if (event instanceof JSEvent) {
         var menu = plugins.window.createPopupMenu()
         var item
         
         //fluiditize
         item = menu.addCheckBox('Fit to window')
         item.setIcon("media:///ssstandard_move_small.png")
         item.setMethod(ACTION_resize)
         item.methodArguments = ['Fluid']
         if (_resizeSelected == 'Fluid') {
            item.selected = true
         }
//         menu.addSeparator()
         //orientate
         item = menu.addMenuItem('Flip orientation')
         item.setIcon("media:///ssstandard_rotate_small.png")
         item.setMethod(ACTION_resize)
         item.methodArguments = ['Orient']
         
         menu.addSeparator()
         
         mapLoop('phone')
         mapLoop('tablet')
         mapLoop('desktop')
         mapLoop('tv')
         
         
         menu.show(elements.lbl_resize)
         
         TOGGLE_block_popup(true)
      }
      else {
         TOGGLE_block_popup(false)
         
         var id = plugins.WebClientUtils.getElementMarkupId(forms.WEB_0F_page__live__web__view.elements.lbl_page)
         
         //grab default name if nothing passed in
         if (!name) {
            name = _resizeSelected
            
            outer:
            for (var i in map) {
               for (var j = 0; j < map[i].length; j++) {
                  if (map[i][j].name == name) {
                     width = map[i][j].getDimHoriz()
                     height = map[i][j].getDimVert()
                     break outer
                  }
               }
            }
         }
         
         //return out selected dimensions
         if (getDims) {
            if (name != 'Fluid') {
               return {
                  width: width,
                  height: height
               }
            }
         }
         else {
            //orientation
            if (name == 'Orient') {
               //konami code (toggle orientation 3 times in a row)
               ACTION_resize.konami = typeof ACTION_resize.konami == 'number' ? ACTION_resize.konami + 1 : 1
               
               //flip orientation
               _resizeOrient = _resizeOrient ? 0 : 1
               
               //toggle orientation for selected resolution
               if (_resizeSelected != 'Fluid') {
                  ACTION_resize()
               }
               return
            }
            //fluid
            else if (name == 'Fluid') {
               plugins.WebClientUtils.executeClientSideJS(
                  '$("#' + id + '_cms").animate({width:"100%",height:"100%"}).removeClass("drunk");'
               )
            }
            //specific dimension
            else {
               //activate konami code
               var konami = ''
               if (event) {
                  ACTION_resize.konami = null
               }
               if (ACTION_resize.konami == 3) {
                  konami = '.addClass("drunk")'
               }
               
               plugins.WebClientUtils.executeClientSideJS(
                  '$("#' + id + '_cms").animate({width:"' + width + 'px",height:"' + height + 'px"})' + konami + ';'
               )
            }
            
            //save down what resolution we're on
            _resizeSelected = name
         }
      }
   }
}


Code completion on the constructor function:

constructor-code-completion.png
constructor-code-completion.png (80.45 KiB) Viewed 2985 times


End result in real world app:

constructor-in-action.png
constructor-in-action.png (149.59 KiB) Viewed 2985 times
David Workman, Kabootit

Image
Everything you need to build great apps with Servoy
User avatar
david
 
Posts: 1724
Joined: Thu Apr 24, 2003 4:18 pm
Location: Washington, D.C.

Next

Return to Books & Training

Who is online

Users browsing this forum: No registered users and 3 guests

cron