Beta 2018 - what's new for plugin writers

Post your questions and problem reports here

Moderator: kfury77

Forum rules
Please try to follow these guidelines. This will help to receive faster and more accurate response.

  • Check the Support section of the corresponding product first. Chances are you will find your answer there;
  • Do not create new topics for already reported problems. Add your comments to the existing topics instead;
  • Create separate topic for each problem request. Do NOT post a number of non-related problem reports in a single topic;
  • Give your topic a meaningful title. Titles such as "A question," "Bug report" and "Help!" provide others no clue what your message is about;
  • Include the version number of the software you are using;
  • This is not an official customer support helpdesk. If you need a prompt and official response, please contact our support team directly instead. It may take a while until you receive a reply in the forum;

Beta 2018 - what's new for plugin writers

Postby Aivars » Thu Mar 15, 2018 12:07 pm

This thread will contain some samples with the new plugin API.
Blumentals Software Programmer
User avatar
Aivars
Blumentals Software Developer
 
Posts: 2453
Joined: Thu Aug 22, 2002 1:40 pm
Location: Latvia

Re: Beta 2018 - what's new for plugin writers

Postby Aivars » Thu Mar 15, 2018 12:29 pm

Dock Panels

Plugins can now create dock panels (like File Explorer, Inspector etc)

pluginsdockpanels.png
pluginsdockpanels.png (45.54 KiB) Viewed 13342 times


Plugin API:

  • var panel = new TDockPanel("Panel_Name", "dockPanelFileExplorer");
    Creates new docking panel. Must be done in script's initialization (about the same when Script.RegisterAction() is called). First parameter specifies unique name that is used to save/load dock's position after user moves it around and restarts the IDE. The second parameter specifies the default location when dock is created for the 1st time. Valid values: "" (this will place dock on the general right side of the editor), "dockPanelFileExplorer", "dockPanelInspector", "dockPanelOutput", "dockPanelLanguageBrowser", "dockPanelLibrary", "dockPanelSQL", "dockPanelCodeExplorer"
  • panel.Redock();
    Call this on "ready" signal if you want the panel be visible as soon as the script is installed
  • panel.Show()
    Show dock
  • panel.Close()
    Hide dock
  • panel.Active()
    Is panel currently active
  • panel.Activate(ShowParentForm)
    Activate dock. ShowParentForm should be true in default case.
  • panel.Deactivate()
    Deactivate dock.

Notes:

When you create controls (e.g. buttons, webbrowser etc) do not put them directly on the dock. Put TForm on the dock and then put the controls on the form. See the sample below.

Sample:

Code: Select all
var panel1;
var panel2;





function OnReady() {
  panel1.ReDock(); //if loading layouts eleminated this panel, the redock will restore it
  panel2.ReDock(); //this is important on the 1st run of the plugin
}



function ShowDock1(Sender) {
  panel1.Show();
}

function HideDock1(Sender) {
  panel1.Close();
}

function ShowDock2(Sender) {
  panel2.Show();
}

function HideDock2(Sender) {
  panel2.Close();
}

function OnButtonClick(Sender) {
  Script.Message("Button was clicked");
}



//create these on script initialization so that their position can be loaded from layouts if previously saved there
panel1 = new TDockPanel("DockTest_Panel1", "dockPanelFileExplorer");
panel1.Caption = "Test Script Panel";
panel2 = new TDockPanel("DockTest_Panel2", "");
panel2.Caption = "Secondary Script Panel (Empty)";

//create some form to show in the dock
var f = new TForm(WeBuilder);
f.parent = panel1;
f.BorderStyle = bsNone;
f.Align = alClient;
f.Visible = true;

var btnOk = new TButton(f);
btnOK.Parent = f;
btnOk.SetBounds(Round(12 * Script.DpiScale), Round(12 * Script.DpiScale), Round(80 * Script.DpiScale), Round(36 * Script.DpiScale));
btnOk.Caption = "OK";
btnOk.OnClick = &OnButtonClick;

//this is required for docks
Script.ConnectSignal("ready", &OnReady);

//some test actions for toggling docks
Script.RegisterAction("Test", "Show Dock 1", "", &ShowDock1);
Script.RegisterAction("Test", "Hide Dock 1", "", &HideDock1);
Script.RegisterAction("Test", "Show Dock 2", "", &ShowDock2);
Script.RegisterAction("Test", "Hide Dock 2", "", &HideDock2);


Installable sample:
docktest.zip
(1.04 KiB) Downloaded 494 times
Blumentals Software Programmer
User avatar
Aivars
Blumentals Software Developer
 
Posts: 2453
Joined: Thu Aug 22, 2002 1:40 pm
Location: Latvia

Re: Beta 2018 - what's new for plugin writers

Postby Aivars » Thu Mar 15, 2018 12:42 pm

Execute PHP scripts and collect output

Due to the fact that WeBuilder and RapidPHP will come with PHP 5.6 included, it is safe to assume that most installations of 2018 will have access to php.exe. There are many useful php scripts for php code formatting, checking etc that might be useful for plugins, and you might simply be more comfortable with writing some more complex portions of the plugin in php.

Plugin API:

  • var phpIsAvailable = CheckIsPhpAvailable()
    Checks if user has path to php.exe set. If not set or not found, a dialog will be displayed offering to specify the path to php.exe. Due to this, do not call this function in interval or frequently.
  • var phpScriptWasExecuted = ExecutePhpScript(phpExeCmdLineOptions, pathToPhpScript, scriptCmdLineOptions, Output)
    Executes php script and collects output in Output variable.

Sample:

Code: Select all
function TestPHP(Sender) {
  if (CheckIsPhpAvailable()) {
    var s = "";
    if (ExecutePhpScript("", Script.Path + "test.php", "", s)) {
      Script.Message("php script output: " + s);
    } else {
      Script.Message("php script failed");
    }
  }
}
Blumentals Software Programmer
User avatar
Aivars
Blumentals Software Developer
 
Posts: 2453
Joined: Thu Aug 22, 2002 1:40 pm
Location: Latvia

Re: Beta 2018 - what's new for plugin writers

Postby Aivars » Thu Mar 15, 2018 2:13 pm

Execute JavaScript scripts with callback

Executing JavaScript scripts can be extremely useful because a lot of good tools and scripts are available that are written in JavaScript. This was already possible with the previous versions of WeBuilder but it was more complicated because you had to create a browser control and then hide it, which wasn't convenient for everybody. Now you can still use browser for visual stuff but also use ScriptableJsExecuter for backend processing.
You can even use node.js scripts if you process them through Browserify + sometimes some light modifications.

Plugin API:

  • Script.CreateScriptableJsExecuter("")
    creates and returns TScriptableJsExecuter. It acts like browser but is invisible. The parameter allows specifying default URL if required.

TScriptableJsExecuter members
  • ExecuteJavaScriptRequest(Js, Channelname, Params: string; Callback: string)
    Js - javascript code to execute
    Channelname - leave empty to generate
    Params - string to send to javascript as parameter for WeBuilder_OnData. Note: JSON can be used to send multiple data items.
    Callback - procedure to execute when javascript calls WeBuilderData.SendNative(...)
    Executes specified javascript code and then calls WeBuilder_OnData(Channelname, Params) in javascript, so make sure your javascript file defines WeBuilder_OnData function.
  • ExecuteJavaScriptFileRequest(FileName, Channelname, Params: string; Callback: string)
    Same as ExecuteJavaScriptRequest but reads javascript code from the specified file
  • ExecuteJavaScriptSimple(Js: string)
    Does not set up communication channel or call WeBuilder_OnData, simply executes specified javascript code
  • ExecuteJavaScriptFileSimple(FileName: string)
    Same as ExecuteJavaScriptSimple but reads javascript code from the specified file
  • OnLoadEnd
  • OnStatusMessage
  • OnConsoleMessage
  • OnDestroy
  • Url
  • Status
    These are same as for TScriptableChromium
  • Ready
    Check if ready to execute JavaScript.

API in JavaScript files:
  • WeBuilder_OnData = function(param_channel, param_message)
    Function that gets triggered in javascript when ExecuteJavaScriptFileRequest or ExecuteJavaScriptFileRequest is called from the pluginscript.
  • WeBuilderData.SendNative(channel, answer, error)
    Call this function to send message back to pluginscript from javascript and trigger the callback function in pluginscript. Channel must contain the same value that was received by WeBuilder_OnData function. You can call this multiple times and thus trigger the callback multiple times.

Sample - this example will ask JavaScript to calculate cubic-root of 8.

JScript, script.js
Code: Select all
var scriptexec = null;
var script_loaded = false;

function JavascriptCallback(res, err) {
  Script.Message(res);
}

function TestJSExecuter(Sender) {
  if (scriptexec == null) {
     scriptexec = Script.CreateScriptableJsExecuter("");
  }
  var script_file = Script.Path + "javascript.js";
  if (script_loaded) {
    script_file = ""; //we do not need to load the script again (it's an optimization)
  }
  //load script (if script_file specified) and call WeBuilder_OnData in the script
  scriptexec.ExecuteJavaScriptFileRequest(script_file, "", "8", &JavascriptCallback)
  script_loaded = true;   
}

Script.RegisterAction("Test", "Test JavaScript", "", &TestJSExecuter);


JavaScript, javascript.js
Code: Select all
WeBuilder_OnData = function(channel, message) {
    var answer = Math.cbrt(message);
   
    WeBuilderData.SendNative(channel, answer, '');
}


Note: if you're interested in how to convert node.js script/utility to browser-executable javascript, take a look at our CSSComb repo: https://github.com/aivarsi/csscomb-with ... -webuilder
Attachments
javascripttest.zip
(984 Bytes) Downloaded 502 times
Blumentals Software Programmer
User avatar
Aivars
Blumentals Software Developer
 
Posts: 2453
Joined: Thu Aug 22, 2002 1:40 pm
Location: Latvia

Re: Beta 2018 - what's new for plugin writers

Postby Aivars » Thu Mar 15, 2018 2:20 pm

HiDef screens

New property allows sizing controls easily correspondingly to Windows Text Zoom. E.g. when you set button width, instead of 80 pixels you would set it to Round(80 * Script.DpiScale)

Plugin API:
Script.DpiScale - real value, 1 when regular DPI used, 2 when 200% Windows Text Zoom used etc.

Example:
Code: Select all
function TestDPI(Sender) {
  var i = Round(Script.DpiScale * 100);
  Script.Message("Dpi Scale: " + _T(i) + "%");
}
Blumentals Software Programmer
User avatar
Aivars
Blumentals Software Developer
 
Posts: 2453
Joined: Thu Aug 22, 2002 1:40 pm
Location: Latvia

Re: Beta 2018 - what's new for plugin writers

Postby Aivars » Thu Mar 15, 2018 2:30 pm

Auto Complete

New functionality allows handling and changing auto-complete inserts.

ac1.png
ac1.png (5.29 KiB) Viewed 13340 times


ac2.png
ac2.png (8.61 KiB) Viewed 13340 times


Plugin API:

New signal:

  • auto_complete_insert
    This signal is fired when user selects an item from auto-complete.
    Calls function (ACType, ListItemAtCursorAC, &s, &ACWordStart, &ACWordLength, &ACLineOffset, &Handled)
    ACType = autocomplete type depending on cursor location (e.g. in HTML, in HTML tag, in PHP etc)
    ListItemAtCursorAC = selected item's technical data string
    s = string to insert in code
    ACWordStart = where the string will be inserted
    ACWordLength = how long is the word that will be replaced with s
    ACLineOffset = if auto-complete is being inserted after the end of the line, this specifies how many columns are there between the line end and cursor
    Handled = set to true if auto-complete insertion is intercepted and handled by script

Functions:

  • Script.ExecAutoCompleteNow(CharStr: string)
    Charstr specifies trigger character. Leave charstr empty for default behavior.

Some of the older auto-complete stuff is described here: viewtopic.php?p=23944#p23944

Example:

Code: Select all
function OnAutoComplete(CodeType, ACType, Strings, AKey, &AllowPopup, ShowImages) {
  var i;
   
  if (ACType == 4) {
    //add this experimental HTML tag
    i = AutoComplete.AddItem(Strings, "bs3-table", "bs3-table");
    AutoComplete.SetImageIndex(i, 8);
  }
 
  if (ACType == 0) {
    //add this experimental AC in empty space
    AllowPopup = true;
    i = AutoComplete.AddItem(Strings, "bs3-table", "bs3-table");
    AutoComplete.SetImageIndex(i, 8);
  }

}

function OnAutoCompleteInsert(ACType, ListItemAtCursorAC, &s, &ACWordStart, &ACWordLength, &ACLineOffset, &Handled) {
 
  var table_snippet = "<table class=\"table\">\n"
        + "<thead>\n"
        + "  <tr>\n"
        + "    <th></th>\n"
        + "  </tr>\n"
        + "</thead>\n"
        + "<tbody>\n"
        + "  <tr>\n"
        + "    <td></td>\n"
        + "  </tr>\n"
        + "</tbody>\n"
        + "</table>\n";
 
  if (ACType == 0) {
    if (s == "bs3-table") {
      s = table_snippet;
      Handled = true;
    }
  } else if (ACType == 4) {
    if (s == "bs3-table") {
      //"eat" opening "<" by moving cursor to left; another way to do it would be to not include opening "<" in snippet
      ACWordStart = ACWordStart - 1;
      ACWordLength = ACWordLength + 1;
      s = table_snippet;
      Handled = true;
    }
  }
}


Script.ConnectSignal("auto_complete", &OnAutoComplete);
Script.ConnectSignal("auto_complete_insert", &OnAutoCompleteInsert);
Attachments
actest.zip
(938 Bytes) Downloaded 493 times
Blumentals Software Programmer
User avatar
Aivars
Blumentals Software Developer
 
Posts: 2453
Joined: Thu Aug 22, 2002 1:40 pm
Location: Latvia

Re: Beta 2018 - what's new for plugin writers

Postby Aivars » Thu Mar 15, 2018 2:47 pm

JSON

Allows creating and parsing simple JSON objects in pluginscripts.

Plugin API:

  • var json = new TScriptableJSON();

TScriptableJSON members

  • Parse(s: string)
    Parses JSON string to self
  • Stringify(): string
    Converts self to JSON string
  • SetValue(key: string; Value: string/Integer/Boolean)
    Adds key/value pair to self
  • GetValue(key: string): string/Integer/Boolean
    Gets value by key. Returns null if fails.
  • DeleteKey(key: string)
    Deletes key/value pair by key
  • HasKey(key: string): Boolean
    Returns true if key exists

Example:

Code: Select all
var json = new TScriptableJSON();
json.Parse(jsonstring);
var cmd = json.GetValue("cmd");
Blumentals Software Programmer
User avatar
Aivars
Blumentals Software Developer
 
Posts: 2453
Joined: Thu Aug 22, 2002 1:40 pm
Location: Latvia

Re: Beta 2018 - what's new for plugin writers

Postby Aivars » Thu Mar 15, 2018 2:50 pm

Minor tweaks & things

New assorted stuff:

  • Script.IsDarkTheme
    Returns true if Dark UI is active
  • TOpenDialog.Files: TStringList
  • TStrings.LoadFromFileWithEncoding(Filename: String; Encoding: TMyEncoding)
    Encoding can be enAnsi, enUtf8, enUtf8NoBom, enUcs2
  • TStrings.SaveToFileWithEncoding(Filename: String; Encoding: TMyEncoding)
Blumentals Software Programmer
User avatar
Aivars
Blumentals Software Developer
 
Posts: 2453
Joined: Thu Aug 22, 2002 1:40 pm
Location: Latvia

Re: Beta 2018 - what's new for plugin writers

Postby pmk65 » Thu Mar 15, 2018 2:53 pm

Great. There's a lots of new useful features that will help making plugin coding much easier.
Specially the ExecutePhpScript and CreateScriptableJsExecuter functionality.

And the new docking feature looks really promising.

Good work. :D
There are 10 types of people in the world: Those who understand binary and those who don't.
User avatar
pmk65
 
Posts: 678
Joined: Sun Dec 20, 2009 9:58 pm
Location: Copenhagen, Denmark

Re: Beta 2018 - what's new for plugin writers

Postby Aivars » Fri Mar 23, 2018 3:49 pm

Various.

Another sample. This one demonstrates multiple things, some of them unrelated:

1) How to handle dock panel during plugin initialization, disabling, exiting etc
2) How to focus component that is placed on dock form as soon as dock panel is shown
3) How to add text to Memo and RichEdit and scroll to the end

Note that this plugins is quite useless functionally, it just demonstrates how to use some of the features from the scripts.

JScript, script.js
Code: Select all
var panel1;
var edMemo;
var edRich;
var dockForm;


function OnReady() {
  panel1.ReDock(); //if loading layouts eleminated this panel, the redock will restore it
 
  //remember to restore dock visibility if dock enabled/disabled
  var wasvis = Script.ReadSetting("Dock visible", "0");
  if (Wasvis == "1") {
    panel1.Show();
    Script.WriteSetting("Dock visible", "");   
  }
 
  FocusMemo();
}

function OnExit() {
  //this is always a good idea with forms
  delete dockForm;
}

function OnDisabled() {
  //save docking panel visibility so that we know whether to forecefully show panel when plugin is re-enabled
  var wasvis = "0";
  if (panel1.Visible)
    wasvis = "1";
  Script.WriteSetting("Dock visible", wasvis);
  panel1.Close();
}

function ShowDock1(Sender) {
  panel1.Show();
}

function HideDock1(Sender) {
  panel1.Close();
}

function OnButtonClick(Sender) {
  //just testing adding text to TMemo and TRichEdit and scrolling to the end
  edMemo.Lines.Add(_t(Round(Random() * 1000)));
  edMemo.SelStart = Length(edMemo.Lines.Text);
  edMemo.SelLength = 0;
 
  edRich.Lines.Add(_t(Round(Random() * 1000)));
  edRich.SetCaretPos(0, edRich.Lines.Count);
}

function FocusMemo() {
  if (panel1.Visible)
    WeBuilder.ActiveControl = edMemo;
}

function OnPanelShow(Sender) {
  //at this point panel is showing but the form is not visible yet, so we cannot focus the field yet
  //let's do it with a timer since it's not a critical action
  Script.TimeOut(0, &FocusMemo);
}


function CreatePanelForm(panel1) {
  //create some form to show in the dock
  dockForm = new TForm(WeBuilder);
  dockForm.parent = panel1;
  dockForm.BorderStyle = bsNone;
  dockForm.Align = alClient;
  dockForm.Visible = true;

  var btnOk = new TButton(dockForm);
  btnOK.Parent = dockForm;
  btnOk.SetBounds(Round(12 * Script.DpiScale), Round(12 * Script.DpiScale), Round(80 * Script.DpiScale), Round(36 * Script.DpiScale));
  btnOk.Caption = "OK";
  btnOk.OnClick = &OnButtonClick;

  edMemo = new TMemo(dockForm);
  edMemo.Parent = dockForm;
  edMemo.ScrollBars = ssVertical;
  edMemo.SetBounds(Round(12 * Script.DpiScale), Round(52 * Script.DpiScale), Round(80 * Script.DpiScale), Round(60 * Script.DpiScale));

  edRich = new TRichEdit(dockForm);
  edRich.Parent = dockForm;
  edRich.ScrollBars = ssVertical;
  edRich.SetBounds(Round(12 * Script.DpiScale), Round(116 * Script.DpiScale), Round(80 * Script.DpiScale), Round(60 * Script.DpiScale));
}



//create these on script initialization so that their position can be loaded from layouts if previously saved there
panel1 = new TDockPanel("DockTest_Panel1", "dockPanelFileExplorer");
panel1.Caption = "Test Script Panel";
panel1.OnShow = &OnPanelShow;

CreatePanelForm(panel1);

//this is required for docks
Script.ConnectSignal("ready", &OnReady);
Script.ConnectSignal("exit", &OnExit);
Script.ConnectSignal("disabled", &OnDisabled);

//some test actions for toggling docks
Script.RegisterAction("Test", "Show Test Dock", "", &ShowDock1);
Script.RegisterAction("Test", "Hide Test Dock", "", &HideDock1);


INI, properties.ini
Code: Select all
[Properties]
Dock visible=check

[Values]
Dock visible=

[Hints]
Dock visible=Should dock appear automatically after plugin is installed/enabled.
Blumentals Software Programmer
User avatar
Aivars
Blumentals Software Developer
 
Posts: 2453
Joined: Thu Aug 22, 2002 1:40 pm
Location: Latvia

Re: Beta 2018 - what's new for plugin writers

Postby pmk65 » Fri Mar 30, 2018 5:31 am

Question regarding TScriptableJSON:

How do you get the length of an array?

I tried:
Code: Select all
var jsonObj = new TScriptableJSON();
jsonObj.Parse(taskJson);
var nodes = jsonObj.GetValue("nodes");
Script.Message("Length(): " + _t(Length(nodes))); // crash
Script.Message(".length: " + _t(nodes.length)); // Blank
Script.Message(".Count: " + _t(nodes.Count)); // Blank


And from your Script Tree plugin, there doesn't seem to be any other properties for iterating.
There are 10 types of people in the world: Those who understand binary and those who don't.
User avatar
pmk65
 
Posts: 678
Joined: Sun Dec 20, 2009 9:58 pm
Location: Copenhagen, Denmark

Re: Beta 2018 - what's new for plugin writers

Postby Aivars » Fri Mar 30, 2018 11:44 am

The new JSON object is not great arrays, I'm fully aware of that, but you can do this:

Code: Select all
  var jsonObj = new TScriptableJSON();
  jsonObj.Parse("{ \"nodes\" : [ \"one\", \"two\", \"three\" ] }"); 
  var i  = 0;
  while (true) {
    try {
      var node = jsonObj.GetValue("nodes[" + _t(i) + "]");
      Script.Message(node);
      i++;
    } except {
      break;
    }
  }
Blumentals Software Programmer
User avatar
Aivars
Blumentals Software Developer
 
Posts: 2453
Joined: Thu Aug 22, 2002 1:40 pm
Location: Latvia

Re: Beta 2018 - what's new for plugin writers

Postby pmk65 » Fri Mar 30, 2018 12:32 pm

Ok. Then I'll stick to using my own ActiveX implementation for parsing data, as it is easier to work with. And I can access the length property, and get the keys back as well.

Code: Select all
/**
* JSON parser using "htmlfile" OLE object.
* The JSON result object is extended with two custom methods, making data fully
* accessible from FastScript. Custom methods:
*      getProp(key/index) to access properties by index or name
*      getKeys(dummy) to get list of keys
*
* @param  string   jsonStr The JSON string to parse
*
* @return mixed    variant or empty string if failure
*/
function ParseJson(jsonStr) {

    // Create htmlfile COM object
    var HFO = CreateOleObject("htmlfile"), jsonObj;

    // force htmlfile to load Chakra engine
    HFO.write("<meta http-equiv='x-ua-compatible' content='IE=9' />");

    // Add custom method to objects
    HFO.write("<script type='text/javascript'>Object.prototype.getProp=function(t){return this[t]},Object.prototype.getKeys=function(){return Object.keys(this)};</script>");

    // Parse JSON string
    try jsonObj = HFO.parentWindow.JSON.parse(jsonStr);
    except jsonObj = ""; // JSON parse error

    // Unload COM object
    HFO.close();

    return jsonObj;
}
There are 10 types of people in the world: Those who understand binary and those who don't.
User avatar
pmk65
 
Posts: 678
Joined: Sun Dec 20, 2009 9:58 pm
Location: Copenhagen, Denmark

Re: Beta 2018 - what's new for plugin writers

Postby pmk65 » Fri Mar 30, 2018 5:48 pm

If you add a Count or Length property to TScriptableJSON, then it should be very similar to my ActiveX implementation. As the TScriptableJSON "GetValue" is identical to my "GetProp" function.
All there's missing then, is a "GetKeys" function. But that mostly needed if you are parsing a JSON object with unknown keys.
There are 10 types of people in the world: Those who understand binary and those who don't.
User avatar
pmk65
 
Posts: 678
Joined: Sun Dec 20, 2009 9:58 pm
Location: Copenhagen, Denmark


Return to HTMLPad / Rapid CSS / Rapid PHP / WeBuilder Support

Who is online

Users browsing this forum: No registered users and 13 guests