Talking Moose

Home of the famous Talking Moose. She makes me laugh.

Welcome!

Home of the Talking Moose

Creating Plugins

February 14, 2019 By Steven Halls

Create new things for the Moose to say!

The Talking Moose software has a built-in Javascript interpreter which allows new abilities to be added, without re-compiling the moose project. It’s a great feature.

Dr. Halls Dr. Halls
Anyone can make the Moose say anything.

Talking Moose
Talking Moose
And if you don’t know Javascript, hire on freelancer.com


 
 

Here is the documentation.

Contents

  • Data in Settings.xml
  • Types of Plugins
  • Moose.Say
  • Moose.Random
  • data-setting=
  • User Information
  • Entry Points
  • RunNextAt
  • Local files, @File:
  • XMLHttpRequest
  • setTimeout
  • Shared Timing
  • OpenPluginView
  • OnOpen
  • Require(‘..js’)
  • Exports
  • <Include .html>
  • Events
  • Hash and Encrypt

The 3 basic files

Each plugin has 3 files. An HTML file for it’s control panel, a Javascript file for coding the Moose to speak, and a Settings file for storing data.

  1. An HTML file. Eg. My PluginName.html
  2. A Javascript file. Eg. MyPluginName.js
  3. A Settingslfile. Always named Settings.xml

The Easy Way.

The easiest way to create a plugin, is to copy the folder of an existing plugin, then paste it to create a copy of it. Then,

  • Change the folder name to your new plugin name.
  • Change the .html and .js filenames inside the folder.
  • Open the Settings.xml file in a text editor, and change the <name> and <Source> and <View><Source> filenames. Use the new filenames.

Using Data

Dr. Halls Dr. Halls
Now some details, starting with storing and using data.

Talking Moose
Talking Moose
Storing data, like whether it is On or Off, and how often it talks.


 
 

Reading the Settings.xml file

All the files necessary for a plugin, are stored in a folder named for the plugin.

A special file called Settings.xml is in each plugin’s folder, and it can flexibly store quite a lot of kinds of data, and the Moose’s javascript provides some special functions to make it easy to get that data.

Click to Expand – to read about Settings.xml

To get Settings from the Settings.xml file in the plugin’s folder.

var settings = Resources.Settings;

The file Settings.xml is read from disk into a C# data structure for the plugin, even before the plugin does it’s StartUp function, so this call inside javascript to settings = Resources.Settings; does not cause a disk read.

Dr. Halls Dr. Halls
Just so you know, our Settings.xml, is a security feature. Normally Javascript can’t save files on your local computer, and Plugins aren’t allowed to change your computer either.

Talking Moose
Talking Moose
But we allow your plugin data to be stored in this one file: Settings.xml, which is safe. That’s why we’re discussing using Settings next.


 
 

The GetSetting() and GetAllSettings() functions

In the GetSetting() function, the parameter is a Tag name (the name of a setting)(optionally preceded by a path ).

GetSetting() can have a single parameter name like “holidaylist”, or it can also be a list of parameters that describe a path to a final Setting value or Settings group. ( more about paths below. )

var holidaylist = settings.GetSetting("holidaylist"); // an example with a single parameter.

Dr. Halls Dr. Halls
I rarely use this. But further below, I use GetValue() a lot.


 
 

GetSetting() returns an individual SettingGroup, ( type is Element ) that contains subordinate XML settings. It won’t return the actual value of an individual setting. (Use GetValue() for values.)

For example holiday.GetSetting(“todayphrase”); won’t work when It’s Friday. is holding only a value (a string). GetSetting does work when there are nested settings-Groups inside it.

Dr. Halls Dr. Halls
I hardly [email protected] use nested settings. But if you need them, this lets you work that way.


 
 

To test for success from theSetting = mySettings.GetSetting("WhatIWant");, use if ( theSetting ). ( Note that theSetting.length won’t work. )

GetAllSettings() returns an array of settings that share the same name. Then you can For-Loop through them.

Here’s an example, of looping through a group of settings.

var usHolidays = Resources.Settings.GetAllSettings('holidays', 'us_holidays', 'holiday');
for(var i = 0; i < usHolidays.Length; i++) {
    var currentHoliday = usHolidays[i];
    if(currentHoliday.GetValue('date') == '01/01/01') {
        Say('Go home!');
    }
}

Paths as multiple parameters

As the above example shows, both GetSetting() and GetAllSettings() can have multiple parameters, that name the various levels going deeper into the XML structure. The final parameter is always the desired one to retrieve.

Resources.Settings.GetSetting('holidays');
Resources.Settings.GetSetting('holidays', 'holiday');
Resources.Settings.GetSetting('root', 'level2' , 'level3' , 'level4' , 'level5' , 'level6', etc…);

Random and Shuffled

One extra function is GetRandomSetting(path) that will pick one value at random and return it.

Another function is GetShuffledSetting(path) which will random-shuffle the Settings, and return a Setting sequentially, which is nice to give a random feeling, but prevent getting the same thing twice in a row. It will automatically random-shuffle them again when the first sequence is used up.

Dr. Halls Dr. Halls
Settings.xml is a good place to store strings intended to be spoken by the Moose, because Random and Shuffled spoken sentences, is a nice automatic feature for users.


 
 


Resources.Settings.GetLength("Jokes","String");

Resources.Settings.GetLength("Jokes","String"); would return the number of <String> inside <Jokes>...<Jokes>. There is a different way, which is to load all the strings into an array using:
theStrings = Resources.Settings.GetAllValues("Jokes","String");
and then use theStrings.length.

Dr. Halls Dr. Halls
I use Resources.Settings.GetValue('Something');
all the time.


 
 


The GetValue() and GetAllValues() functions

These functions get actual string values. All values arrive in javascript variables as strings.

Talking Moose
Talking Moose
All settings are Strings.
So sometimes you have to manually convert them to numbers.


 
 

var funcId = group.GetValue('functionid');
if(funcId) {
   // We have it.
} else {
   // We don’t.
}

GetAllValues("someValue") will return an array. Each array element contains a string.

GetRandomValue("someValue") will return one randomly, if there were several with same name. This is incredibly useful for Strings to be spoken by the Moose. It's lovely to have random variations in what is spoken.

GetShuffledValue("String") will return the next string in the shuffled-random sequence, which is great for jokes, so you don't hear a joke repeat until after you've heard them all.

Similar to GetSetting() and GetAllSettings(), the GetValue() and GetAllValues() and GetRandomValue() and GetShuffledValue() functions can accept multiple parameters to let you step deeper into an XML structure. For example:

var date = Resources.Setting.GetValue(“holidays”, “holiday”, “date”);


SetValue()

There is also a SetValue() command. Here's an example:

<Settings>
   <holidays>
     <us_holidays>
       <holiday>
         <date>01/01/01</date>
         <type>1</type>
       </holiday>
     </us_holidays>
   </holidays>
</Settings>

Resources.Settings.SetValue(“holidays”, “us_holidays”, “holiday”, “date”, “02/02/02”);

Talking Moose
Talking Moose
That's crazy! Just look at how that steps deeply into nested data, and the final parameter sets the value.

Dr. Halls Dr. Halls
I just try to avoid nested settings, they seem complicated.


 
 


What about Create, Remove, Rename, Copy of Settings?

Resources.Settings.Remove(path); is available. Useful only in OnUpdate entry point.

Resources.Settings.SetValue("DoesntExist","whatever"); is actually capable of creating a setting that doesn't yet exist.

We don't yet have Rename, or Copy implemented in javascript for settings.

The automatic updates process sometimes needs to completely replace a settings file on a users PC. This is only for BIG changes, and it would wipe out the user's chosen values, which is not good. So try to preserve those by saving the important ones into temporary vars, and setting them again after calling ReplaceSettings.

Resources.ReplaceSettings("NewSettings106.xml", true);

It will return true or false and second parameter is a flag if you want to delete the file when done or not.

Generally this is only used from inside of an OnUpdate() function.

Use Resources.Settings.GetValue() in javascript and in HTML

Repeat: You can GetValue() in Javascript and in HTML.

David David
Why did you tell me this?

Talking Moose
Talking Moose
It's one way for your .js file to communicate to your .html file.

Dr. Halls Dr. Halls
Another way to send data, is using Events.


 
 


Settings.xml is a text file.

Settings.xml is an XML format file. Use any tags you want. Edit in a text editor.


Shuffle Index

Shuffling is wonderful. It seems a little better than using Random(), and Shuffling will save it's position and ordering between sessions. To do this saving, it inserts extra XML tags into the Settings.xml file. They look like this:

<ShuffleData>
   <Target>String</Target>
   <Index>4</Index>
</ShuffleData>

If your javascript code uses GetShuffledValue(), then the <ShuffleData> gets automatically inserted into your Settings.xml, which is a suprise at first. And it can also re-order your values whenever the shuffle sequence needs to be re-shuffled again, and that is also a suprise, when you notice that the values you typed into Settings.xml, were re-ordered mysteriously.


Here's are simple XML examples:

<StringGroup Id="1" Comment="primary fun" />
<StringGroup Id="StringGroup" Comment="secondary pain" />

and here's a different example:
( And notice it shows us that empty lines of whitespace are OK, and shows that Id can look like a number or a word. )

<StringGroup>
   <StringGroup Id="1">
     <String Id="1">Group 1 String 1</String>
     <String Id="2">Group 1 String 2</String>
   </StringGroup>

   <StringGroup Id="Group 2">
     <StringGroup Id="Inner Group">
       <String Id="1">Group 2 Inner Group String 1</String>
       <String Id="2">Group 2 Inner Group String 2</String>
     </StringGroup>

     <String Id="1">Group 2 String 1</String>
     <String Id="2">Group 2 String 2</String>

     </StringGroup>

   <String Id="2">Root Group String 1</String>
   <String Id="2">Root Group String 2</String>

</StringGroup>

An important rule. There can be only 1 outer <Settings> tag in the Settings.xml file. If you try to put 2 or more at the root level, everything past #1 Settings group gets deleted, which is really confusing to see your work just disappear. The inventors of XML dictated, thou shalt have but 1 root tag in XML.

The characters inside of XML tags like <String>....</String> cannot have embedded < or > inside. But they can have {} or [] inside.

Types of Plugins

  • <Type>Scheduler</Type>
  • <Type>Action</Type>
  • <Type>Filter</Type>

The most common type of plugin, is the Scheduler. It runs on a schedule of time intervals specified in milliseconds.

The Action type of plugin only runs when the user performs an action like clicking a button.

The Filter type of plugin provides a method to modify what other plugins are about to say, for example to change the pronunciation of problematic words.

David David
Where does <Type> go?

Talking Moose
Talking Moose
It's one of the required tags in the Settings.xml file.


 
 

Important functions

To make the Moose speak something, that's the goal of a plugin. Here's how:

Say( theString, false );
Moose.Say( theString );

2nd parameter false means normal priority. true means high priority. It's optional.

Talking Moose
Talking Moose
The Say command, is how you make the Moose talk.

Dr. Halls Dr. Halls
Use Say() in the .js file,
use Moose.Say() in the .html file.


 
 

More parameters available in Moose.Say, but only in C#. These extra parameters haven't been extended to be usable in Plugins yet. Pass null to use defaults.
Moose.Say(string text, priority, voice, speechRate, volume, applyFilters);
// applyFilters true = apply the Say Name filter.
// speechRate -10 to +10
// volume 0 to 100
// voices are voice name.

Moose.Random(min,max);

Uses C#'s random generator, which is better seeded and doesn't reset to initial seed each time a new "Run" occurs of the javascript.

Moose.Random(); // Returns 0 to 1.0
Moose.Random(mean,std,min,max); // Returns gaussian random.

Dr. Halls Dr. Halls
Use Random() in the .js file,
use Moose.Random() in the .html file.


 
 

element.Length;

Notice that capital L on .Length.   Javascript variables also have a .length method with lower case. What's the difference? The capital L . Length is a C# function provided by the Moose, which has the added ability of being able to tell you how many sub-elements (array elements) it is holding, And it tells you the string length when it's only holding a single string.

User Information

User.Firstname; User.Lastname; User.Name; User.Gender; User.Birthday; User.Address; User.Address2; User.City; User.State; User.Zipcode; User.Latitude; User.Longitude; User.Altitude; User.Email; User.Country; User.Age; User.Timezone; User.Language; User.Locale; (All of these are read-only)

Whatever information the User has chosen to enter into the UserInfo plugin, is available to other plugins.

User.IsAuthenticated is true or false, showing login-to-cloud status.

Device.GetCountryName(), Device.GetCountryCode(), Device.GetCountryLongCode(), Device.GetLanguageName(), Device.GetLanguageCode(), Device.GetLanguageShortCode(). These might be useful somewhere, like during a reset when "New User Detected".


User Interface of plugins

The .html files

When your plugin folder contains a .html file with the same name of the plugin, it gets used as it's control panel.  We call this a "View", and I have no idea why.

In this window, you can place anything legal in HTML5, like checkboxes and buttons and text inputs.

There are a collection of Javascript and CSS files that get automatically loaded into the HTML file. Things like Bootstrap, jQuery, TweenMax, and Styles.css are intended to give the UI for moose plugins, a standard look and feature set. These automatically-loaded things are all in a folder called /Plugins/Scripts/

The rendering of HTML is using .NET WebClient. ( We're not using Chrome or Awesomium. We tried those but they weren't fast enough.)

David David
That sucks. Not Chrome and Javascript 6.

Betty Betty
So old. Internet Explorer 10 and Javascript 5 technology.

Dr. Halls Dr. Halls
The shame is unbearable.


 
 


Showing and editing Settings

A control panel (your .html file) should be able to edit the settings of the plugin.

We've made an "Easy" feature for this, called "data-setting="

data-setting=

Suppose a setting value in the Settings.xml file was name "CrossfadeDuration".

It's value would be brought into a text input using this line of code:

<input id="Crossfade" type="text" data-setting="CrossfadeDuration" style="width:45px;" >

and for a checkbox,

<input type="checkbox" id="OnTheHourOnOff" data-setting="OnTheHourOnOff" >Enabled<br>

Talking Moose
Talking Moose
Just look at that
data-setting="SettingName".
That's simple and powerful.
It edits and saves your Setting.


 
 

Click to Expand - More about data-settings=

The Moose's core C# loads all the Settings at startup.

Values in Settings.xml get loaded at startup by Moose.exe, which will also automatically insert the data-setting= values into the HTML file. ( Any tag that contains the attribute data-setting= is considered to be a special parameter, whose value it intended to be used by the HTML user interface. ) C# does the first step, of reading values from the Settings.xml file, and storing them into Moose.exe properties.

Moose.exe is a C# application that is "in charge". What it thinks the values are, is supposed to be authoritative.

In the Settings.xml file in a plugin's folder, here's an example of some tag values that are used for data-setting=. They look ordinary, don't they.

<Settings>
   <RootTest>False</RootTest>
   <User>
     <IsAdmin>False</IsAdmin>
   </User>
</Settings>

Dr. Halls Dr. Halls
Even though it is cosmetic, I find it is nice to put a comment into those tags, as shown below.


 
 

<Settings>
   <RootTest Comment="Reminds me of root beer">False</RootTest>
   <User>
     <IsAdmin Comment="Did you give yourself powers">False</IsAdmin>
   </User>
</Settings>

In the Run function in a plugin's javascript file, you can't directly use names like RootTest and IsAdmin as variable names, not until you define them and load them with values in the usual Javascript way. (see example below.) I often define them as globals, and then load values into them during the Startup function.

var RootTest;
var IsAdmin;
Startup = function () {
RootTest = Resources.Settings.GetValue("RootTest");
IsAdmin = Resources.Settings.GetValue("User", "IsAdmin");
}


<input>'s in the HTML user interface, using data-setting=

Here are 2 examples of inputs. (Areas to type in some text.)

<input type="checkbox" data-setting="RootTest">
<input type="checkbox" data-setting="User->IsAdmin">


Notice the -> syntax

Did you see: data-setting="user->IsAdmin" ? The -> is the syntax to step deeper into the XML structure, when using data-setting=.

Dr. Halls Dr. Halls
I have a confession. I rarely use this -> method, I rarely use nested settings.


 
 


Special formatting for Checkboxes

Just above, was an example of html code for a checkbox. That would give a fairly bland looking checkbox. To apply some fancier features, the Moose UI provides Bootstrap Toggle, and to make it work, all you have to do is add data-toggle="toggle" into the input for type=checkbox.

<input type="checkbox" data-setting="RootTest" data-toggle="toggle">

The Bootstrap Toggle website has more documentation of ways to achieve variety of appearance of the checkboxes.


GetValue and SetValue and data-setting=

These 3 important commands, are all used on your settings data.

We use data-setting=. inside of inputs like checkboxes. We can put these commands into the .html or .js files to read or set these values.

Resources.Settings.SetValue( "aValue", aValue );
Resources.Settings.GetValue( "aValue" );

So Moose.exe gets it's values updated by onchange functions from the HTML page. That's good. But it doesn't help keep the Run function () updated with changes automatically. That's a little sad I suppose.

So, there is an OnChange = function() {} entry point in our Javascript, so it can manually fetch new values from C#, whenever UI changes happen to inputs like checkboxes or text areas.

The Entry Points.

  • Startup = function () { // code here };
  • OnChanged = function () { // code here };
  • Run = function () { // code here };
  • Shutdown = function () { // code here };
  • Filter = function (thePhrase) { // code here; return thePhrase; };
  • OnUpdate = function () { // for adding/removing settings. }
Dr. Halls Dr. Halls
And there are also Events

Talking Moose
Talking Moose
Entry points, and Events, are the major ways that your javascript code gets called to make the plugin do stuff.


 
 

  • Prior to the Startup function, there can be some javascript code that gets run as soon as the .js file is loaded. Use this for defining global variables and creating handlers for events.
  • The Startup function, is called once ALL the plugins are loaded, so now it's safe to load your values from Settings.xml into your global variables, and it's safe to start triggering events and calling external functions from other plugins.
  • The OnChanged function, is called whenever your .html file has had a user type or click something into an <input> that changed a setting value. This allows your .js file to become aware of the change and get the new value.
  • The Run function, is called on time intervals, or whenever Plugin.RunNextAt is set for. Each time a Run happens, your code should set a new Plugin.RunNextAt.
  • The Shutdown function, is called when Quitting the Moose, and it gives your plugin a chance to reset a few settings, if needed.
  • A filter function receives a phrase, and gets a chance to modify it, before the Moose speaks it.
  • The OnUpdate function, is called after a plugin update has been installed. It gets called Before the Startup function. It allows code to add or remove some settings from the Settings.xml file.

Make the plugin Run, exactly whenever you want. If you want the Moose to say something every 10 minutes, this is what you use.

Plugin.RunNextAt

This controls when will be the next time the Run function will be called.

Talking Moose
Talking Moose
Just set a future time using a Javascript Date.


 
 

Click to Expand - About RunNextAt

Plugin.RunNextAt is "undefined" when the Moose starts up, and when this is the case, then 'scheduler' type plugins are Run at intervals set by XML. So if the interval was set to 1000 milliseconds, then the Plugin would be run every 1 second. That's a lot. Sometimes you just want to have the Moose say something at a specific future time. Here's how to do it.

Plugin.RunNextAt can be only assigned the value of a Javascript Date object. But if you try to read the value, it returns a .NET date format string. Thus, it's a little tempermental.

Talking Moose
Talking Moose
Half temper, half mental.


 
 

var runNextAt = Plugin.RunNextAt;
var currentDate = new Date();
if ( runNextAt.getTime() < currentDate.getTime() ) { // do stuff }

getTime() will return number of milliseconds since 1/1/1970
and comparing numbers is easier in JS than string comparisons, apparently. So the above example code will work.

But the example below, won't work.

if ( runNextAt == currentDate ) // do something, but won't work.

But this won't work (above). It will return false all the time because it is comparing an Object reference, and not an actual date value.

Jessica Jessica
Why tell us things that don't work?

Dr. Halls Dr. Halls
I keep forgetting, and making the same mistake. I refer to these notes, for myself.


 
 

This will work, below, and it's REQUIRED. It sets a future time for the plugin to Run.

// Set RunNextAt if not set already.
if ( !Plugin.RunNextAt ) {
   Plugin.RunNextAt = new Date();
}

// Add 2 minutes.
Plugin.RunNextAt = new Date(Plugin.RunNextAt.getTime() + 2 * 60000);
alert(Plugin.RunNextAt);

// Now the plugin Run function will only be called in 2 minutes from now,
// and after that, never again, unless you set a new future time into RunNextAt,
// or, change back to 'scheduler' intervals, by clearing RunNextAt.

To clear RunNextAt and resume using scheduler intervals, do this:

Plugin.RunNextAt = undefined;

Note: If you fail to change Plugin.RunNextAt to a new future time, or fail to set it to undefined, then the Moose C# will disable the whole plugin, because otherwise, times in present or past would make the plugin get called over-and-over on every main-event-loop cycle, which is a CPU massive waste. ( It must be set to current time + 1 second, or greater. Otherwise, the Plugin will be disabled.) I know it's weird. The work-around is to set RunNextAt to midnight if you don't need it right now, which will keep the plugin alive.

Note: The Plugin.RunNextAt value is not saved in a file, so it doesn't persist when the Moose is Exited and Restarted. It will be 'undefined' the next time the Moose is restarted. In other words, if you wanted it to remind you tomorrow of something, You'd have to keep the Moose running day and night.

Note: A native javascript date looks like this: "Sun, 08 May 2016 11:08:38 GMT-06:00"
A .NET date looks like this: "5/8/2016 11:25:00 AM"

If you assign a date into Plugin.RunNextAt, it must be a date object, not a string. So if you were saving a string showing a date in javascript format, you need to convert it to a date object before putting it into Plugin.RunNextAt

And it gets more ridiculous due to Javascript not always being able to know if something is a number or string or date. Look at this example code:

// I have to persuade javascript that holdthis is a string,
// not a number, by using the "" +
var holdthis = "" + Resources.Settings.GetValue("SaveRunNextAt");
SaveRunNextAt = new Date( holdthis ); // "Sun, 08 May 2016 11:08:38 GMT-06:00"
Plugin.RunNextAt = SaveRunNextAt;

If I want to store that date into a setting, I must convert it to a string, which creates local time.

holdthis = SaveRunNextAt.toString();
Resources.Settings.SetValue("SaveRunNextAt",holdthis);

You know what's also nice? Plugin.RunNextAt can be used from an HTML <script>, if you want to display it's value.

Make the Moose speak

Plugin.Run() is a function that is available inside of HTML pages. You can attach it to a button as an onclick= event. Here's an example, which would call the Run function of the plugin.

<button name="button" onclick="Plugin.Run()" class="btn btn-primary">Test</button>?

More Useful Functions

Plugin.RunType;

This property provided by C# into your javascript scripts, is useful inside the Run function. It has values of "Scheduler" if Run was called according to the timer settings ( interval, RunNextAt or Stream ), or "Action" if Run was called for other reasons, including: "Reload Script" menu command, Up/Down gestures and mouseclick causing Plugin.Run(); function called within an HTML view <script>.

Plugin.Pause();, Plugin.Resume();, Plugin.IsPaused(field).

These are useful inside the Run function, if you need to stop setting Plugin.RunNextAt for a while.

Moose.IsQuiet = false; // If you set 'true' it will turn on Quiet mode and not speak.

Plugin.OnChanged();

Suppose you wanted a Button, that would reset some settings for the Moose, and you want that to take effect immediately. The button could call a function like: ResetNewYear(). Notice that clicking a button doesn't trigger an OnChanged event, so you have to manually set the HTML input elements, and manually set the Moose's settings values, and manually call Plugin.OnChanged(). Also notice, inside of an HTML <script> we can use Resources.Settings.SetValue() and all the other methods of Resources.Settings.

function ResetNewYear() {
   document.getElementById('HappyNewYearPhrase').value = "Happy New Year!";
   Resources.Settings.SetValue("CountDownDuration", "Happy New Year!" );
   Plugin.OnChanged();
}

Using Local files.

Loading local files to run as javascripts, is possible, yay, but only if those files are stored in the same folder as the plugin, you can use this syntax to load them: @file: and @data:

<script src='@file:bootstrap-timepicker.min.js'></script>.

The above example of @file: is our Moose-only extension. Normally in a web browser, you can't load local files, you can only get them from the internet. For completeness, we also have another Moose-only extension, @data: which will convert a file into a base64 string. Here's an example that loaded an SVG file.

<object id="svg" type="image/svg+xml" data="@data:images\mySVGfile.svg" ></object>

For loading local image files into .src or url(), there is this:

View.LoadImage(local file or URL);

  • document.getElementById('TestingBackground').src = View.LoadImage(localFilename)
  • document.getElementById('testBack').style.backgroundImage = 'url(' + View.LoadImage("bluepic.jpg") + ')';

For reading a file to become JSON, use this.

Resources.ReadFile(filePath)

var file = Resources.ReadFile("language.json");
if (file !== undefined) { // Can return null.
var json = JSON.parse(file);
}

Commands for fetching webpages

Talking Moose
Talking Moose
It's a standard way to fetch webpages and do other internet tasks.


 
 

XMLHttpRequest is available in Javascript. Click to Expand

It works like this:

var request = new XMLHttpRequest();     // Create Web Request.
request.onerror = OnWebRequestError;     // Provided by C#.
request.onreadystatechange = onWebRequestStateChanged;
request.friendlyName = "Google"; // Spoken word for the URL.

// Open it. ( 'true' means asynchronous, the default. false isn't supported )
request.open("GET", TestWebURL, true); // TestWebURL = "http://www.google.com/"

request.send(); // And send the request.

This method requires 1 function as callback:

function onWebRequestStateChanged() { }

The error function callback, is provided by Moose.exe, so you don't need to include:

function OnWebRequestError() {}?

OnWebRequestError is a built-in function for error-handling

This Error handling function, OnWebRequestError is provided by C# and is usable by all plugins, and lets the Moose speak about errors instead of showing messageBox's or Alert's. It has the feature of only speaking once per minute, even if multiple errors happen a lot faster than that. After the first minute, it progressively increases the time between speaking about errors, because it would be annoying to be interrupted by speech every minute.

This is good for users, but not good for plugin coders, because it doesn't speak about every error. So if you are a developer trying to debug a plugin, you can set your own error handling and show alerts().

OnWebRequestError is the default, so these 3 code lines are redundant:
request.onerror = OnWebRequestError;
request.ontimeout = OnWebRequestError;
request.timeout = 60000;

Another way to call OnWebRequestError, is directly in the Javascript, like a command line, like this:
OnWebRequestError.apply(this);

( Using .apply(this) gives the function the content of .friendlyName and error codes/messages )

XMLHttpRequest properties and methods

Here is a URL to one of the offical-looking documentation of XMLHttpRequest. https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest.

Currently usable inside the Moose's javascript plugins are these properties:

onreadystatechange
readyState
response
responseText
responseType
responseURL
status
statusText
timeout
ontimeout

onerror ( this one isn't in the spec. It's an extra feature of the Moose. )
friendlyName ( this one isn't in the spec. It's an extra feature of the Moose. )

getAllResponseHeaders
getResponseHeader('Last-Modified')
setRequestHeader (Note, must be called after request.open )

We don't have: abort, overrideMimeType, send, or init or openRequest or
upload or withCredentials

Setting user agents

Normally it's not necessary, but if you need to change the useragent for specific web requests, this is how to do it.

request.setRequestHeader(“User-Agent”, “Talking Moose”);

The difference between .response and .responseText

When you use request.responseText, it is just a plain string.

When you use request.response, it can contain JSON that is ready to be used, like:
request.response.userID
request.response.message

Handling timeouts

There are times when your own PC might not be able to send out network requests, so a request for a URL might sit for a long time, and you'll not get an onerror. So for this situation, there's this:

// Handle timeout.
request.timeout = 2000;
request.ontimeout = function () {
   connecting = false;
};

WebSocket support

Here are the methods and attributes we support. ( see MSDN websocket documentation ).

webSocket = new WebSocket("wss://client.pushover.net/push");

webSocket.onopen
webSocket.readyState
webSocket.send()
webSocket.onmessage
webSocket.onerror
webSocket.onclose
webSocket.close()

Notes: Once you are connected to a websocket, you can test if webSocket.readyState != 1, which would indicate the the connection has been lost. Use Google to learn the readyState values.

Using APIs that return JSON

Suppose you fetched from an API using the above XMLHttpRequest.
If you set request.responseType = "json", then our Javascript will automatically
parse it and put the result into request.response;

request.responseType = "json"; ?
request.onreadystatechange = function () {

if (this.response && this.response.status && this.response.status === 1) {
userKey = this.response.id;
userSecret = this.response.secret; } };

The above is showing that the returned JSON had "id" and "secret", and those values are automatically usable. Yay.

Content-Type, application/json

Some services with APIs, want you to set this:

request.setRequestHeader("Content-Type", "application/json" );

Warning. API servers are unpredictably fickle. Sometimes, including this header will prevent it from working, even though you are absolutely sure that you are supposed to send json. Other times, you do have to include it.

Newer internet functions.

Http.Send(method, url, data, success, error, friendlyName, timeout, dataType)

Http.Get(url, success, error, friendlyName, timeout, dataType)

Http.GetJSON(url, success, error, friendlyName, timeout)

Http.Post(url, data, success, error, friendlyName, timeout, dataType)

Http.SendWebHook(data, success, error, friendlyName, timeout)

Most of the parameters are options. The success and error are callback functions. The error callback has a parameter indicating if it was a timeout or not. The timeout parameter is a value in milliseconds. For dataType see https://api.jquery.com/jquery.ajax/ . The data parameter for .SendWebHook can be a JSON object or a stringified string. The data parameter for .Post can only be a stringified json string.

Here is an example of success and error functions.

function OnPostError(theRequest,theType, theMessage ) { // theType is 'error' or 'timeout'
Events.TriggerEvent("Error log", theType + ': ' + theMessage, Plugin.name);
}
function OnPostSuccess( theResponse, theStatusText, theRequest) {
}

setTimeout and setInterval

When creating <script>'s inside of HTML pages, for the control panels of plugins, the full set of javascript can be used, including these normal commands: setTimeout() and setInterval().

But, in the javascript in the .js file, use Timers.setTimeout() and Timers.setInterval().

Streams, aka, Channels

We have implemented a method for plugins to use "shared timing", with a goal to reduce the overload of speech if all plugins wanted to speak. There is a "Minutes Channel" that is set to speak every 10 minutes. When 3 plugins are set to use the minutes channel, only 1 gets randomly chosen to speak, every 10 minutes.

Click to Expand - About Channels

The Minutes Channel has another feature, of starting the day with an interval of 10 minutes, but gradually increasing the interval until it reaches 50 minutes by the end of the day. So as the day goes on, you hear less and less from the Moose.

Facebook is an example of a "newsfeed" that prevents users from seeing EVERYTHING from every page they've "liked", and the Moose needed a similar way to prevent "too much speaking" from becoming annoying.

Internally in our javascript code, we call this "Streams", but for users we call them "Channels" (like TV channels or radio channels).

In a plugin's .js file, these commands are available:

var theStream = Moose.FindStream("Minutes Channel");
Plugin.SetStream( theStream.Name );

There are 5 pre-set streams available. "Minutes Channel", "Hours" and 3 others, "Morning", "Afternoon" and "Evening". These values can be read or written:


theStream.Name // yes, it is the name of it.
theStream.MinInterval // in milliseconds, so 600000 is 10 minutes.
theStream.MaxInterval // 3300000 is 55 minutes.
theStream.Duration // 36000000 is 10 hours.
theStream.StartTime // if 0 or null, then 24 hours a day.
theStream.EndTime // or if StartTime in milliseconds is 10 hours past midnight, it starts at 10am.
theStream.Easing // "EaseInOutCubic" S-shaped curve, 10 hours to go from 10 to 55.
theStream.ResetTime // global value, changing for one Stream will affect them all.
theStream.NextRunTime // read-only, tells you when it will speak next.

Other kinds of easing functions available: None - linear, EaseInCirc, EaseOutCirc, EaseInOutCirc, EaseInCubic, EaseOutCubic, EaseInOutCubic, EaseInExpo, EaseOutExpo, EaseInOutExpo, EaseInQuad, EaseOutQuad, EaseInOutQuad, EaseInQuart, EaseOutQuart, EaseInOutQuart, EaseInQuint, EaseOutQuint, EaseInOutQuint, EaseInSine, EaseOutSine, EaseInOutSine

While it is possible to create other custom channels, perhaps as a means to use Easing functions for speech time intervals, we haven't documented it (yet).

Setting Plugin.SetStream( null); is the method to disable a stream. You may wish to do this because of the priority system of "When to run next".
Highest Priority is: Plugin.RunNextAt
Second Priority is: Plugin.Stream
Lowest Priority is: the "Scheduler" which calls Run() at regular intervals.

A <script> in an HTML control panel for a plugin, can use Plugin.Stream.Name to access these values and functions related to Streams.

To show a list of all the plugins that are currently using a particular stream:
var list = Stream.ListAllPluginsOnThisStream();

Plugin.UseStream

The command Plugin.UseStream = true; ( or false), allows a plugin to be nicely configured to use Streams, but you can set Plugin.UseStream to be false, which will prevent it from being randomly chosen. This is handy when plugins are also disabled by the main checkbox, because it prevents the Streams from giving a disabled plugin an unnecessary chance to speak, which the user would hear nothing, and it would seems like Streams were broken.

Open other plugins


Moose.OpenPluginView("PluginName"); // returns bool
Moose.OpenPluginViewMinimized("PluginName");

theJokes = Moose.FindPlugin("Jokes Plugin");
theJokes.Run();

Moose.OpenWebBrowser("http://talkingmoose.ca");

Moose.ExitApplication();

Moose.ClosePluginView('Login');

Moose.IsViewOpened('Research');

OnOpen

The <script> inside a .html plugin view, can use a special entrypoint called:

OnOpen = function (any parameters) {
// Whatever code.
};

And any plugin.js or any other plugin.html can open that plugin view with

Moose.OpenPluginView("PluginName", [parameters]);.

OnOpen is called every time a .html plugin is opened. It is called AFTER $(window).load is finished.

Dr. Halls Dr. Halls
Useful when your .js needs to force the .html to open and show you something urgent, via a parameter.


 
 

Including other javascript code using Require

We can do this. It is restricted to only allow fetching from within the Moose folder. You can't go outside and search the whole PC.
Require("..\\Shared\\TestScript.js");

Require is for use inside the .js files that do Startup, OnChange, Run, Shutdown. It doesn't work in .html files using <script>.

Exports, exported functions and variables

Talking Moose
Talking Moose
One plugin can call some code from another plugin.

Billy Billy
Whaaaaat ?

Dr. Halls Dr. Halls
It's nice for useful services from one plugin, to be available to others, without duplicating the code.


 
 

Exported Functions

One plugin can call some code from another plugin.

val = Exports.Call("ExportPlugin", "ExportFunctionName", param1, param2);

Click to Expand - About Exports

But in order for this to work, the other plugin must "Export" the function. Here's how.
function exportFunctionName(var1, var2) {
  globalVar++;
  return globalVar + var1 + var2;
}
Exports.Add(exportFunctionName);

Exported Variables

Exports.SomeValue = SomeValue;

Please note that "SomeValue" name must be unique amongst all Plugins, because all Plugins can get an exported value, using:
var theValue = Exports.GetValue('PluginName','SomeValue');

Note that both PluginName and SomeValue are in quotes. I often forget that for SomeValue

  • To read values from other plugins, although it's faster to use theValue = Moose.FindPlugin('PluginName').Resources.Settings.GetValue('ValueName'); that is not recommended because Moose.FindPlugin() is not thread-safe. Another plugin might be calling the same command, but with a different PluginName, and you could get the value from the wrong plugin returned, which is a mysterious bad behavior.
  • Using Exports is not supported in .html <script>'s. The reason is that the Exports.Call() and Exports.GetValue() are thread-safe, meaning they lock the function until it's finished. Because the .html is run from the main UI thread, we don't want it to become thread-blocked while waiting for Exports.GetValue(), while some other plugins .js files are busy using Exports.Getvalue().
  • The workaround for .html files using <script>'s, is to use Events. Let the .js file call Exports.GetValue() and then sent the value to the .html file using an Event.

More notes about Exports.

  • Exported variables are a COPY of the variable and have no live link to the original. Every time you change the original, you also have to copy that change into the exported variable. Every time you change the exported variable, the original is unaffected. So you have to be explicit about putting values into variables.
  • Exported functions are calling the original function in it's original location. Thread locking is automatically implemented, to prevent 2 plugins from calling the same exported function at the same time.

<include> some html

<include src="@file:..\\Library\\Timing.html"></include>

This is very handy, for putting some common UI elements in just one file, and letting all other plugins include that html code. Why duplicate code in multiple plugins, when you can keep one copy in the Library.

Billy Billy
That's breaking the rules.

Talking Moose
Talking Moose
You can't include code from the internet.

Dr. Halls Dr. Halls
Only files containing .html, from within the Moose's folders, can be used. Not from your whole computer.

Billy Billy
It's like they don't even care about world peace.


 
 

Events

Both .html files ( in <script>'s ), and .js files can use Events to talk to each other, passing messages and data.

You give an event a specific name, and provide a function that will run when the event happens.
Events.OnEvent(EventName, function );

You can trigger that event to happen using:
Events.TriggerEvent(EventName, param1);

You can pass as many parameters as the event handler function can receive:
Events.TriggerEvent(EventName, param1, param2 );

You can use Events in .js files and in a plugin's .html file. When using it from an HTML file <script>, don't try to trigger an event until the page is fully loaded.
$( window ).load( function() {
   Events.TriggerEvent("Get something");
});

If multiple different plugins create an event handler using the same EventName, then all those plugins will receive copies of the same event, whenever 1 is triggered anywhere.

Dr. Halls Dr. Halls
Not only are events useful, they are also extremely fast.

Talking Moose
Talking Moose
Yup. When you trigger an event in one plugin, it gets received in the other plugin, usually before the next line of javascript code gets executed in the original plugin.


 
 

The following system events, can be listened to:

  • WindowActivated
  • WindowDeactivated
  • WindowRequestBringIntoView
  • WindowGotFocus
  • WindowLostFocus
  • WindowStateChanged

At the moment, only WindowStateChanged is useful. The rest are unreliable.

Events.OnEvent("WindowStateChanged", function (state) {
   if ( state == "Minimized" ) {
     Moose.Say( "State Minimized" );
   } else if ( state == "Normal" ) {
     Moose.Say( "State Normal" );
   }
});

Dr. Halls Dr. Halls
I had these windows system events added into the Events dispatching system, but never got around to using them properly.


 
 

Common Events, available to all plugins.

  • 'New User Detected' is triggered by Moose.exe to signal to plugins to reset all their settings back to default values.
  • 'Home.html sets language' is triggered by Home.html to signal to plugins to change their UI language.
  • 'Authenticated' is triggered by Moose.exe to signal when users log into the cloud, or log out.

Hash and Encrypt

theHash = Security.Hash(theString,64); will create a hash of a string, which is safe to store in Settings.xml, and useful for comparing to find if something changes later in time.

When you are bringing in strings from the internet, you never know what sort of weird Unicode characters or malformed data is there, and it might be incompatible with an XML file, and mess up Settings.xml, so Hashing it is a way to make it safe and still usable for detecting changes.

theCrypted = Security.Encrypt(thePhrase,EncryptPassPhrase);
thePhrase = Security.Decrypt(theCrypted ,EncryptPassPhrase);

The Security.Encrypt() uses Microsoft's AES encryption. The PrivateChat plugin uses this, and you can too.

Saving Plugin settings to the Cloud

Some settings are only stored on the local PC, in the Settings.xml files, which is a fine, dandy place for things to be.

But when you have several computers in your life, it's nice if certain settings could stay synchronized on all your PCs, for instance, so that you don't hear the same jokes at work, and again at home.

We put 'Cloud="True"' as an attribute inside the xml tags of settings that we wish to synchronize using cloud storage. Here's an example, defining a setting in a plugin's Settings.xml file:

<JokesOnOff Cloud="True">True</JokesOnOff>

New plugins - How to open

We have a problem. Newly create plugins aren't listed in the Home menus. So it's not easy to open up the control panel .html view, when you want to edit or test it.

We know this needs a solution, and there is an item on the to-do list for this. It will eventually detect and list it somewhere.

But in the meantime, the only thing you can do is to make your .js file always open the view at startup, using Moose.OpenPluginView(Plugin.Name);, placed into the Startup function.

Revision History

This page was created Oct 11, 2016, which corresponds to a timepoint in the development when the Moose emerged from C# and C++ only, into a state where it had a Javascript interpreter and HTML webbrowser, which made plugins possible. As development of plugins began, notes gradually accumulated here until Feb 14, 2019, when those notes underwent more organization and formatting. There are a few things missing from this documentation, hopefully coming soon.

Copyright © 1986–2021   ·   by Steven Halls, MD   ·   [email protected]   ·   1-780-608-9141 · Log in