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.
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
TEMPORARY RESTRICTION
The rest of this documentation below was written for an older version of the Moose software, a version that allowed all the plugin files to be installed anywhere. However, the new current version of the Moose has installed into a somewhat protected system folder called C:\ProgramData\Moose\abc\Plugins. It is hidden by default, but you can unhide it by opening the File Manager to the C drive, using the View menu, choose Hide selected items. You can edit the existing plugin files using a text editor.
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.
- An HTML file. Eg. My PluginName.html
- A Javascript file. Eg. MyPluginName.js
- A Settings file. 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
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.
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.
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.
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.
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 usetheStrings.length
.
all the time.
The GetValue() and GetAllValues() functions
These functions get actual string values. All values arrive in javascript variables as 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()
andGetAllValues()
andGetRandomValue()
andGetShuffledValue()
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”);
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.
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.
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.
Say
command, is how you make the Moose talk.
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.0Moose.Random(mean,std,min,max);
// Returns gaussian random.
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.)
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>
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>
<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=.
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 input
s 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. }
- 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.
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.
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.
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
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.
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
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 becauseMoose.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()
andExports.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 forExports.GetValue()
, while some other plugins .js files are busy usingExports.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.
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.
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" );
}
});
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.
Volume during videos
Speech.SetAudioMode(audioMode)
- where audioMode has the following values:
0 - ignore if Audio is playing or not. Moose uses normal volume.
1 - Moose's volume is reduced and there are no captions.
2 - Moose's volume is reduced and captions are visible. (default)
3 - Wait for Audio to stop playing and then 2 seconds extra, like in previous your current build.
Speech.SetAudioModeVolumeModifier(volumeModifier)
where volumeModifier is range 0-100 (not 0.0 to 1.0) which will be applied when audio is playing and AudioMode is set. Default value is 25.
var theAudioMode = Speech.GetAudioMode();
var theAudioModifier = Speech.GetAudioModeVolumeModifier();
AutoStartup control
Just documenting here, the functions that create a startup shortcup, or delete it. This has UI in Home.html and a plugin called AutoStartup.htmlMoose.StartupShortcutExists()
// true or falseMoose.CreateStartupShortcut();
Moose.DeleteStartupShortcut();
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. Then I retired as a radiologist in June 2019, and hardly any coding work was done until fall 2023, and at that point, a small amount of new features and documentation was added. There are a few things missing from this documentation, hopefully remedied in the future.