Jump to content

Ron

uniGUI Subscriber
  • Posts

    375
  • Joined

  • Last visited

  • Days Won

    31

Posts posted by Ron

  1. 7 hours ago, mhmda said:

    Easy, use column layout

    I have tried that, but as the fields rearrange with smaller window width, the height of the containing panel is not automatically adjusted to fit the fields.

    To get variable field width, I have this arrangement:

    1. A panel - CPanel - with layout "border", with maxheight and minheight, which acts as a container for the next panel

    2. A panel - FPanel - with layout "auto", with maxwidth, minwidth, maxheight and minheight

    3. A fieldcontainer with layout "column", with maxwidth and minwidth

    But the height of the containing panels are not adjusted.

    Here is the code, and I am trying to create everything at runtime, and it starts with createForm, which calls setupPanel, which calls createPanel, which calls createFC (fieldcontainer), which calls the fields creation methods.

    Not sure if this is really possible, but I am not giving up easily.

     

    function TUniMainModule.createPanel(aowner, aparent:TUniPanel;aHeight:integer;title, subtitle:string):TUniPanel;
    var sTitle, lTitle:string;
        cPanel, FPanel:TUniPanel;
        heightStr, heightStr2, chStr:string;
    begin
      CPanel:= TUniPanel.Create(aowner);
      with CPanel do
      begin
        parent:=aparent;
        name:= 'panCPanel'+getUniqueID;
        color:=$00EFEFEF;
        borderStyle:=ubsNone;
        layout:='border';
        titleVisible:=false;
        layoutConfig.Width:='100%';
        layoutConfig.Margin:='0 0 0 0';
        JSInterface.JSConfig('minHeight', [200]);
        JSInterface.JSConfig('maxHeight', [370]);
      end;
    
      sTitle:=title;
      lTItle:='<b>'+title+'</b><br>'+subtitle;
      heightStr:=inttostr(aHeight);
      heightStr2:=inttostr(aHeight+1);
      chStr:=inttostr(81);
    
      FPanel:= TUniPanel.Create(aowner);
    
      with FPanel do
      begin
        parent:=CPanel;
        name:= 'panFPanel'+getUniqueID;
        borderStyle:=ubsNone;
        layout:='auto';
        layoutAttribs.Align:='top';
        layoutConfig.Margin:='7 10 9 15';
        color:=clWhite;
        collapsible:=true;
        collapsed:=false;
        title:=sTitle;
        titleVisible:=true;
        layoutConfig.Region:='center';
        JSInterface.JSConfig('minWidth', [445]);
        JSInterface.JSConfig('maxWidth', [990]);
        JSInterface.JSConfig('minHeight', [200]);
        JSInterface.JSConfig('maxHeight', [370]);
        JSInterface.JSConfig('animCollapse', [250]);
        with clientEvents.UniEvents do
        begin
          values['afterCreate']:='function afterCreate(sender){sender.addCls("FPanel");sender.addCls("FPanelT");sender.addCls("shadowPanel");sender.addBodyCls("FBodyPanel")}';
          values['beforeInit']:='function beforeInit(sender, config){config.defaults={margin:"0px 15px 0px"}}';
        end;
        with clientEvents.ExtEvents do
        begin
          values['mouseover']:='function mouseover(sender, eOpts){sender.addCls("effectPanel2");}';
          values['mouseout']:='function mouseout(sender, eOpts){sender.removeCls("effectPanel2");}';
          values['collapse']:='function collapse(p, eOpts){'+FPanel.JSName+'.setTitle("'+lTitle+'");'+CPanel.JSName+'.setHeight('+chStr+',{duration:5,easing:"easeIn"});'+FPanel.JSName+'.removeCls("FPanelT");'+
            FPanel.JSName+'.addCls("FPanelTC")+'+aparent.JSName+'.updateLayout();}';
          values['beforeexpand']:='function beforeexpand(p, eOpts){var c='+CPanel.JSName+';c.setHeight('+heightStr2+');c.expand();'+aparent.JSName+'.updateLayout();}';
          values['expand']:='function expand(p, eOpts){'+FPanel.JSName+'.removeCls("FPanelTC");'+FPanel.JSName+'.addCls("FPanelT");'+FPanel.JSName+'.setTitle("'+sTitle+'");var c='+
            CPanel.JSName+';c.setHeight('+heightStr+');c.expand();'+aparent.JSName+'.updateLayout();}';
        end;
        JSInterface.JSConfig('titleCollapse', [True]);
      end;
      result:=FPanel;
    end;
    
    
    
    function TUniMainModule.createFC(owner, aParent:TUniPanel; aHeight:integer):TUniFieldContainer;
    var fc:TUniFieldContainer;
    begin
      fc:=TUniFieldContainer.Create(owner);
      with fc do
      begin
        parent:=aParent;
        name:='fc'+getUniqueID;
        //title:='';
        //width:=900;
        fieldLabel:='';
        layout:='column';
        //layout:='form';
        //layout:='fit';
        height:=aHeight;
        layoutConfig.Margin:='10 75 0 25';
        layoutConfig.width:='100%';
        //JSInterface.JSConfig('labelAlign', [top]);
        //JSInterface.JSConfig('minWidth', [205]);
        //JSInterface.JSConfig('maxWidth', [410]);
        JSInterface.JSConfig('minWidth', [425]);
        JSInterface.JSConfig('maxWidth', [950]);
      end;
      result:=fc;
    end;
    
    
    
    
    procedure TUniMainModule.createForm(aOwner, aParent:TUniPanel;formID:integer);
    var tPan:TUniPanel;
        fc:TUniFieldContainer;
        queryComp, sqlParam, formSQL, aTitle, fLabel:string;
        queryID, fMaxLength, fWidth, fieldID, fTYpe, aHeight, panID:integer;
        dbQuery:TMySQLQuery;
    
    function getFormQuery(s:string):TMySQLQuery;
    var i:integer;
    begin
      for i:=0 to componentCount-1 do
        if (Components[i] is TMySQLQuery) and sameText(uppercase(Components[i].Name), uppercase(s)) then
        result:=Components[i] as TMySQLQuery;
    end;
    
    begin
      createSpacer(aOwner, aParent);
      //formID determines form setup
      //select * from forms where id=:form_id;
      with formQuery do
      begin
        paramByName('form_ID').AsInteger:=formID;
        open;
        if not (recordCount=0) then
        begin
          formSQL:=fieldByName('sql').AsString;
          sqlParam:=fieldByName('parameter').AsString;
          queryComp:=fieldByName('querycomp').AsString;
        end;
        close;
      end;
    
      //get correct query parameter ID from formID
      case formID of
        1: queryID:=customerID;
      end;
    
      //get correct query from form setup table
      dbQuery:=getFormQuery(queryComp);
      with dbQuery do
      begin
        sql.Text:=formSQL;
        paramByName(sqlParam).AsInteger:=queryID;
        open;
      end;
    
      //loop through panels table
      //select * from panels where form_id=:form_id order by id;
      with panelsQuery do
      begin
        paramByName('form_ID').AsInteger:=formID;
        open;
        if not (recordCount=0) then
        begin
          while not eof do
          begin
            aTitle:=fieldByName('title').AsString;
            aHeight:=fieldByName('height').AsInteger;
            panID:=fieldByName('id').AsInteger;
            setupPanel(aOwner, aParent, dbQuery, formID, panID, aHeight, aTitle,'subtitle');
            Next;
          end;
        end else begin
          //Log error
          close;
          exit;
        end;
        close;
      end;
      createSpacer(aOwner, aParent);
    end;
    
    
    
    
    procedure TUniMainModule.setupPanel(aowner, aparent:TUniPanel;query:TMySQLQuery;formID, panID, aHeight:integer;title, subtitle:string);
    var tPan:TUniPanel;
        fc, fc1, fc2:TUniFieldContainer;
        dSource, fName, fLabel:string;
        fMaxLength, fWidth, fieldID, fTYpe:integer;
        ds:TDataSource;
        containerID:integer;
    
    function getDS(q:TMySQLQuery):TDataSource;
    var i:integer;
    begin
      for i:=0 to componentCount-1 do
        if (Components[i] is TDataSource) and ((Components[i] as TDataSource).Dataset=q) then
        result:=Components[i] as TDataSource;
    end;
    
    begin
      tPan:=createPanel(aOwner, aParent, aHeight, Title,'subtitle');
    
      //create fieldcontainer - local variable as this has to be unique for each panel...
      fc1:=createFC(aOwner, tPan, aHeight);
      //fc2:=createFC(aOwner, tPan, aHeight);
    
      //loop through fields connected to selected panel
      //select * from fields where form_ID=:formId order by panel_id, id;
      with fieldDataQuery do
      begin
        if active then close;
        paramByName('form_ID').AsInteger:=formID;
        paramByName('panel_ID').AsInteger:=panID;
        open;
        if not (recordCount=0) then
        begin
          while not eof do
          begin
            fType:=fieldByName('fTYpe').AsInteger;
            fieldID:=fieldByName('id').AsInteger;
            fLabel:=fieldByName('label').AsString;
            fWidth:=fieldByName('width').AsInteger;
            fMaxLength:=fieldByName('maxlength').AsInteger;
            fName:=fieldByName('fieldname').AsString;
            containerID:=fieldByName('container').AsInteger;
            {case containerID of
              1: fc:=fc1;
              2: fc:=fc2;
            end; }
            ds:=GetDS(query);
            case fTYpe of
              1: createCB(fc1, ds, fName, fLabel, fWidth);
              2: createEdit(fc1, ds, fName, fLabel, fWidth, fMaxLength);
            end;
            Next;
          end;
        end else begin
          //Log error
          close;
          exit;
        end;
        close;
      end;
    end;

    In addition there are functions for creating the fields:

    procedure TUniMainModule.createEdit(aParent:TUniFieldContainer;aDataSource: TDataSource; fName:string; aLabel:string;aWidth, aMaxlength:integer);
    var ed:TUniDBEdit;
    begin
      ed:=TUniDBEdit.Create(aParent.owner);
      with ed do
      begin
        name:='ed'+getUniqueID;
        parent:=aParent;
        text:='Kaj Ronny Nilsen';
        fieldLabel:=aLabel;
        datafield:=fName;
        dataSource:=aDataSource;
        width:=aWidth;
        maxLength:=aMaxLength;
        font.Size:=11;
        tag:=0;
        borderStyle:=ubsSingle;
        layoutConfig.Margin:='10 20 0 0';
        layoutConfig.ColumnWidth:=0.45;
        fieldLabelSeparator:='';
        fieldLabelAlign:=laTop;
        fieldLabelFont.Color:=clDkGray;
        with clientEvents.UniEvents do
        begin
          values['afterCreate']:='function afterCreate(sender){sender.addCls("SPEdit");}';
        end;
      end;
    end;
    
    procedure TUniMainModule.createCB(aParent:TUniFieldContainer;aDataSource: TDataSource; fName:string; aCaption:string;aWidth:integer);
    var cb:TUniDBCheckBox;
    begin
      cb:=TUniDBCheckBox.Create(aParent.owner);
      with cb do
      begin
        name:='cb'+getUniqueID;
        parent:=aParent;
        tag:=1;
        caption:=aCaption;
        datafield:=fName;
        dataSource:=aDataSource;
        width:=aWidth;
        font.Size:=11;
        layoutConfig.Margin:='10 20 0 0';
        layoutConfig.ColumnWidth:=0.45;
      end;
    end;

     

     

     

  2. I want fields to be in two columns at a window width greater than a certain width,
    and then only a single column when the window is smaller than this certain width.

    How to do this?

    I guess that I cannot use two containers, as in the first image, but if I use only
    one container, how to make it overflow to a second column when the container
    is too small for the fields?

    PS: Unfortunately I was unable to upload images.

     

  3.  

    I have found a compromise: if I reload the application about 3 seconds after the deploy file 
    is swallowed up by the HyperServer, then I will catch the new version in most of the cases:

     

    procedure TMainForm.checkTimerTimer(Sender: TObject);
    begin
      if fileExists('c:\antirust\timebok\deploy\timebok.dep') then
      begin
        UniMainModule.log('Fast Reloading application after 3000ms delay...');
        sleep(3000);
        showToast('Fast Reloading application - time: '+TimeToStr(now));
        uniSession.AddJS('document.location.reload();');
      end;
    end;

     

    From the log:

    3:00:20 PM Deploy file discovered
    3:00:32 PM Deploy file moved after 11 secs. - reloading application
    3:00:32 PM Application startup - version: 0.1.0.17
    3:00:43 PM Deploy file discovered
    3:00:54 PM Deploy file moved after 11 secs. - reloading application
    3:00:55 PM Application startup - version: 0.1.0.18
    3:01:02 PM Deploy file discovered
    3:01:04 PM Deploy file moved after 2 secs. - reloading application
    3:01:05 PM Application startup - version: 0.1.0.19
    3:01:19 PM Deploy file discovered
    3:01:29 PM Deploy file moved after 9 secs. - reloading application
    3:01:29 PM Application startup - version: 0.1.0.20
    3:01:50 PM Deploy file discovered
    3:01:51 PM Deploy file moved after 0 secs. - reloading application
    3:01:51 PM Application startup - version: 0.1.0.21
    3:02:03 PM Deploy file discovered
    3:02:13 PM Deploy file moved after 10 secs. - reloading application
    3:02:14 PM Application startup - version: 0.1.0.22
    3:02:32 PM Deploy file discovered
    3:02:36 PM Deploy file moved after 3 secs. - reloading application
    3:02:36 PM Application startup - version: 0.1.0.23
    3:02:45 PM Deploy file discovered
    3:02:46 PM Deploy file moved after 0 secs. - reloading application
    3:02:46 PM Application startup - version: 0.1.0.24
    3:02:54 PM Deploy file discovered
    3:02:58 PM Deploy file moved after 3 secs. - reloading application
    3:02:58 PM Application startup - version: 0.1.0.25
    3:04:27 PM Deploy file discovered
    3:04:32 PM Deploy file moved after 5 secs. - reloading application
    3:09:06 PM Application startup - version: 0.1.0.26
    ...
    3:17:35 PM Fast Reloading application after 1000ms delay...
    3:17:37 PM Application startup - version: 0.1.0.50
    3:17:58 PM Fast Reloading application after 2000ms delay...
    3:18:01 PM Application startup - version: 0.1.0.51
    3:18:18 PM Fast Reloading application after 2000ms delay...
    3:18:20 PM Fast Reloading application after 2000ms delay...
    3:18:21 PM Application startup - version: 0.1.0.51
    3:18:36 PM Fast Reloading application after 2000ms delay...
    3:18:38 PM Fast Reloading application after 2000ms delay...
    3:18:39 PM Application startup - version: 0.1.0.52
    3:18:40 PM Fast Reloading application after 2000ms delay...
    3:18:40 PM Fast Reloading application after 3000ms delay...
    3:18:44 PM Application startup - version: 0.1.0.53
    3:19:13 PM Fast Reloading application after 3000ms delay...
    3:19:17 PM Application startup - version: 0.1.0.54
    3:19:33 PM Fast Reloading application after 3000ms delay...
    3:19:36 PM Fast Reloading application after 3000ms delay...
    3:19:40 PM Application startup - version: 0.1.0.55
    3:19:40 PM Application startup - version: 0.1.0.55
    3:19:47 PM Fast Reloading application after 3000ms delay...
    3:19:51 PM Application startup - version: 0.1.0.56

     

  4. For development, I am using a special setup, trying to reduce time from compilation to browser refresh without any issues.

    For this I have to use HyperServer, due to is ability to automatically reload a new application file, from the deploy folder.

    So I run a webserver on my development PC, apache 2.2, and I have a post-compile event in Delphi which copies the EXE file
    to the \deploy folder, renaming it to *.dep.

    I have a timer in the mainForm which discovers that the HyperServer has loaded the new application file:

    procedure TMainForm.reloadTimerTimer(Sender: TObject);
    begin
      with uniMainModule do
        if newFileDate<>fileDate then
        begin
          reloadTimer.Enabled:=false;
          showToast('Reloading application...');
          uniSession.AddJS('document.location.reload();');
        end;
    end;

    This works fine, and it runs this newFileDate function in the MainModule:

    function TUniMainModule.newFileDate:TDateTime;
    var fileDateInt:Integer;
    begin
      fileDateInt := fileAge('c:\antirust\timebok\timebok.exe');
      if fileDateInt > -1 then
        result:=fileDateToDateTime(fileDateInt);
    end;

    Of course this function is also run at MainModule startup:

    procedure TUniMainModule.UniGUIMainModuleCreate(Sender: TObject);
    
    begin
      fileDate:=newFileDate;
    end;

    The MainForm timer runs at 500ms intervals, so this all works fine, as you can see in the screenshot.

    But - there is of course another timer running in the HyperServer, and I wonder how I can reduce its interval?

    It seems like the HyperServer timer runs at 10 secs interval, and I would like to lower it to about 1 second.

    Then I can reduce the time from compile to browser reload, to about 3 seconds - maybe.

     

     

     

    reloading.png

    • Like 2
  5. I am not able to make extevents linked to a runtime created panel to work,
    although unievents linked to runtime created objects seem to be working fine.

    I have made a testcase with two panels, one design time created and one runtime created,
    with the same mouseover event, triggering a console.log function just for testing.

    The events are stored OK in the runtime created panel object, something you will
    see if you check the value for mouseover, but the events are somehow not triggered.

     

     

    runtime extevents.png

    project1.zip

  6. On 3/21/2021 at 2:25 PM, mhmda said:

    This is what we exactly do. 🙂

    Me too, and the service app rarely gets updated, in contrast to the unigui app.

    Also, it is nice to have the service app running if you have to take the webserver down,
    keeping all those regular executions working fine during downtime.

    Code separation is a great principle in software philosophy.

    • Like 1
  7. You can do an http call to start the server, after the webserver reboot.

    I use the windows task manager to do the webserver and dbserver reboot
    each night at 2 a.m., and I could set up a task running 5 minutes after this,
    which calls the unigui app over http. Just make a call and then terminate it.

    • Like 1
  8. When it comes to storing the user's first login information (email at least), there are basically two choices:

    1. Not store the info in the db, but only in the link as a parameter, and then pick it up at verification and send it to the login form/complete account registration tab. 
    But this makes it insecure, so token and email should really be encrypted into a single parameter and decrypted at verification. Or you can hash it, but then you need the original data to check the incoming hashed parameter, and that brings us to the next option.

    2. Store the info in the db, with the token id, and look it up at verification, for instance in the login form after having fetched the token id in the mainModule.
    Or you can store the account id in the token table and get it from there.

    The point of email verification is of course to make sure the first registered email data is secured through the whole process.

  9. If there is an authentication or verification error, the user should always end up at the Login form.

    So that is where you have to place whichever messages to such users, and specifically in the onShow event,
    as it fires after the mainModule's beforeLogin event in case the return var handled is not set to true.

  10. Just try to manually add a token to the db, using 

    INSERT into token values(0, now(), 'test')

    and then do a select on the same token, using

    select id from token where created<adddate(now(), interval 15 minute) and token='test';

    If you get a result id, then there is nothing wrong with the queries.

    Since you get transported to the error page, a parameter is picked up during the beforeLogin event,
    but I have no idea why the query does not return a result set. Try and remove the date criteria, like

    select id from token where token=:token;

    and see what happens.

  11. function TCompObject.SendSMSMessage(number, msg:string):boolean;
    const
      cUSER_AGENT = 'Mozilla/4.0 (MSIE 6.0; Windows NT 5.1)';
    var
     authtoken, url, SID, StatusText: string;
     aResponse: TStringStream;
     aParams:TStringList;
     mHTTP:TidHTTP;
     LHandler: TIdSSLIOHandlerSocketOpenSSL;
     fullCallbackURL, ResponseText:string;
    
    begin
      result:=false;
      fullCallbackURL:='http://www.mydomain.com/'+callbackURL;
    
      try
      mHTTP:=TIdHTTP.Create(nil);
      mHTTP.Request.BasicAuthentication:=true;
      mHTTP.Request.Username:=accountsid;
      mHTTP.Request.Password:=accountpw;
      url := Format('https://%s/%s/Accounts/%s/Messages/', [apiurl, apiversion, accountsid]);
      aParams := TStringList.Create ;
      try
        LHandler := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
        aParams.add('From='+alfanumID);
        aParams.add('To='+number);
        aParams.add('Body='+msg);
        aParams.add('StatusCallback='+fullCallbackURL);
        aResponse := TStringStream.Create;
        try
          mHTTP.IOHandler:=LHandler;
          mHTTP.ReadTimeout:=15000;
          mHTTP.Post(url, aParams, aResponse);
        finally
          ResponseText  := aResponse.DataString;
          LHandler.Free;
        end;
      finally
        aParams.Free;
        aResponse.Free;
        mHTTP.Free;
      end;
      except
        on E:Exception do 
          logMsg('SendSMSMessage Exception: '+ E.Message)
      end;
    
      //get SID and status
      SID:=getSID(ResponseText);
      statusText:=getStat(ResponseText);
      if pos('queued', ResponseText)<>0 then 
      begin
        Result:=true; 
        logMsg('SMS Message sent to: '+number+', msg: '+msg);
      end else 
        Syslog('MainModule: SMS Sending Error - response: '+responseText);
    end;

    I use Twilio.com, and they have a REST service where you POST the message, using basicauth and SSL.

    • Like 1
×
×
  • Create New...