Roll20 uses cookies to improve your experience on our site. Cookies enable you to enjoy certain features, social sharing functionality, and tailor message and display ads to your interests on our site and others. They also help us understand how our site is being used. By continuing to use our site, you consent to our use of cookies. Update your cookie preferences .
×
Advertisement Create a free account

Namespaces: Novice Seeks Help Exploring the Revealing Module Pattern

1532141895
Hi Everyone: My Goal and Proposed Process The Goal is to Understand and Implement the Revaling Module Pattern Template used by The Aaron (see code below). I hope to do this by thoroughly documenting in this thread my attempt to implement it and rigorously asking the community the questions that I have. Hopefully, this will be helpful to others that come along afterwards. A Brief Note about My Programming Experience I have a tiny bit of programming experience. Highlights include developing a couple of desktop applications some time ago in Foxpro. Some years later I took a look at Delphi/Pascal but I got busy with other things and never finished that project. Since that time I have done a little bit of macro and scripting here and there (mostly in LibreOffice Basic). A few years ago I took some Treehouse courses on html, css, php, ruby, git, etc. I've forgotten most everything except for some general concepts (i.e.) scope, variables, loops, conditional logic, etc.&nbsp; I put together a custom character sheet for my homemade RPG a couple of weeks ago. You can take at&nbsp; Screenshot 1 &nbsp; and Screenshot 2 &nbsp;if interested. Then I took a JavaScript tutorial from sololearn.com and have been playing around with JavaScript and the Roll20 API for about a week relying on mentoring from the Roll20 community here &nbsp;where the community helped my get my feet wet accessing the API character object and creating lookups and much much more. Source Material The Aaron's template as of today's date: // Github:&nbsp; &nbsp;&lt;WHERE YOU STORE IT&gt; // By:&nbsp; &nbsp; &nbsp; &nbsp;&lt;YOU&gt; // Contact:&nbsp; <a href="https://app.roll20.net/users/&lt;YOUR" rel="nofollow">https://app.roll20.net/users/&lt;YOUR</a> ID&gt; const NAME = (() =&gt; { // eslint-disable-line no-unused-vars &nbsp; &nbsp; const version = '0.1.0'; &nbsp; &nbsp; const lastUpdate = 1531675536; &nbsp; &nbsp; const schemaVersion = 0.1; &nbsp; &nbsp; const checkInstall = () =&gt;&nbsp; { &nbsp; &nbsp; &nbsp; &nbsp; log('-=&gt; NAME v'+version+' &lt;=-&nbsp; ['+(new Date(lastUpdate*1000))+']'); &nbsp; &nbsp; &nbsp; &nbsp; if( ! state.hasOwnProperty('NAME') || state.NAME.version !== schemaVersion) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; log(`&nbsp; &gt; Updating Schema to v${schemaVersion} &lt;`); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; state.NAME = { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; version: schemaVersion &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; }; &nbsp; &nbsp; const handleInput = (msg) =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; if (msg.type !== "api") { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; let args = msg.content.split(/\s+/); &nbsp; &nbsp; &nbsp; &nbsp; switch(args[0]) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case '!NAME': &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; }; &nbsp; &nbsp; const registerEventHandlers = () =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; on('chat:message', handleInput); &nbsp; &nbsp; }; &nbsp; &nbsp; on('ready', () =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; checkInstall(); &nbsp; &nbsp; &nbsp; &nbsp; registerEventHandlers(); &nbsp; &nbsp; }); &nbsp; &nbsp; return { &nbsp; &nbsp; &nbsp; &nbsp; // Public interface here &nbsp; &nbsp; }; })();
1532142067
My Beginning Steps Based upon the work G G and The Aaron have done&nbsp; here &nbsp;and here &nbsp;I begin implementation by taking the following steps: 1. From the Roll20.net landing page navigate to [Campaign] &gt; Settings &gt; API Script &gt; New Script where [Campaign] is the name of your campaign. 2. Paste the code above into the 'untitled.js' file.&nbsp; 3. Rename 'untitled.js' to 'rmptutorial.js'. [Note: One could develop the script in his or her favorite editor and copy and paste it into into the javascript file and use a different filename. I am describing the steps I took.] 4. Search for "NAME" and replace that with my chosen namespace of "rmptutor". RMP stands for Revaling Module Pattern and there is an entry for it in the Roll20 API Cookbook&nbsp; here . And that's as far as I want to go on my own and now it's question time. =) Current State of Tutorial Code const rmptutor = (() =&gt; { // eslint-disable-line no-unused-vars &nbsp; &nbsp; const version = '0.1.0'; &nbsp; &nbsp; const lastUpdate = 1531675536; &nbsp; &nbsp; const schemaVersion = 0.1; &nbsp; &nbsp; const checkInstall = () =&gt;&nbsp; { &nbsp; &nbsp; &nbsp; &nbsp; log('-=&gt; rmptutor v'+version+' &lt;=-&nbsp; ['+(new Date(lastUpdate*1000))+']'); &nbsp; &nbsp; &nbsp; &nbsp; if( ! state.hasOwnProperty('rmptutor') || state.rmptutor.version !== schemaVersion) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; log(`&nbsp; &gt; Updating Schema to v${schemaVersion} &lt;`); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; state.rmptutor = { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; version: schemaVersion &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; }; &nbsp; &nbsp; const handleInput = (msg) =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; if (msg.type !== "api") { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; let args = msg.content.split(/\s+/); &nbsp; &nbsp; &nbsp; &nbsp; switch(args[0]) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case '!rmptutor': &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; }; &nbsp; &nbsp; const registerEventHandlers = () =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; on('chat:message', handleInput); &nbsp; &nbsp; }; &nbsp; &nbsp; on('ready', () =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; checkInstall(); &nbsp; &nbsp; &nbsp; &nbsp; registerEventHandlers(); &nbsp; &nbsp; }); &nbsp; &nbsp; return { &nbsp; &nbsp; &nbsp; &nbsp; // Public interface here &nbsp; &nbsp; }; })();
1532142156
So I just want to go through here and touch on each section before even attempting to add any of my code. Regarding: &nbsp;const version = '0.1.0'; &nbsp;const lastUpdate = 1531675536; &nbsp;const schemaVersion = 0.1; Does anyone have any references to version numbering practices they follow or any suggestions? I don't understand what last update is -- maybe days from the Big Bang or something? =) How to calculate it. ? With respect to schemaVersion are we talking about Roll20.net schema or our own custom structures?
1532176548
GiGs
Pro
Sheet Author
API Scripter
lastupdate is what you get if you type Date.now() in the browser console (and divide by 1,000, IIRC). It's the current time, in the format used in javascript.&nbsp; See the log line in checkInstall: log('-=&gt; rmptutor v'+version+' &lt;=-&nbsp; ['+(new Date(lastUpdate*1000))+']'); Using Date() with the lastupdate constant will give you the current date. it's handy for checking which script is most recently updated. I dont divide by 1,000 in mine (and so dont have to multiple by 1000 in the log statement), and just type Date.now() in the browser console whenever i update a script to get the date value to enter in lastUpdate. The schemaVersion i believe is used for scripts which modify the roll20 State (a special object that persists between sandbox restarts). The state is used for long term variable storage, that you need to be persisitent. But during development you may need to modify the state object's construction. being able to check the version of the state allows you to know how to update the state. This is only needed if you are running the script in multiple campaigns, or you are sharing it with others, so that you can be sure you keep the state object of each version people are using updated properly. If you aren't using the state, you can delete that. and version is fairly straightforward. There's no set standard for how you will use version numbering, so the format can be anything.&nbsp;
1532182987
The Aaron
Forum Champion
API Scripter
G G is pretty much right.&nbsp; About Versioning The standard I try to follow is Semantic Versioning (&nbsp; <a href="https://semver.org/" rel="nofollow">https://semver.org/</a> &nbsp;).&nbsp; The basic idea is: Given a version number MAJOR.MINOR.PATCH, increment the: MAJOR version when you make incompatible API changes, MINOR version when you add functionality in a backwards-compatible manner, and PATCH version when you make backwards-compatible bug fixes. I'm not very good at applying that the "right" way.&nbsp; What I generally do is bump the PATCH number whenever I fix things, or when I make tweaks to existing thing. If I add big new things, I'll bump the MINOR number (and reset the PATCH to 0).&nbsp; If I rewrite from scratch, possibly fundamentally changing the script, I'll bump the MAJOR number and reset the other to 0. I think it's obvious why Versioning is useful, but it's especially useful when you've released scripts for other people to use.&nbsp; You might join someone's game to help them and find they've got a version from 3 years ago, and that's why the latest features in your script aren't working for them. =D About Time lastUpdate is the last time I saved the script.&nbsp; You mention external editors, and this is one of the reasons I use one!&nbsp; I've configured my editor of choice (Vim, cuz it's the best. ( Shut it, Brian! =D )) to find the lastUpdate variable in any Javascript file I save, and set the current time to it.&nbsp; Most programming languages store time as an integer number of seconds since "The Epoch".&nbsp; The Epoch is the "Thursday, January 1, 1970 12:00:00 AM GMT".&nbsp; (This is why Y2K wasn't as big a deal as the media made it out to be... but watch out for&nbsp;Tuesday, January 19, 2038 3:14:07 AM GMT...)&nbsp; This integer number of seconds format is usually referred to as a Unix Timestamp. If you're curious, you can try some conversions at: <a href="https://www.epochconverter.com/" rel="nofollow">https://www.epochconverter.com/</a> Javascript's Date class deals with time in the millisecond resolution, instead of seconds, which is why I multiple by 1000 before converting it to a human readable format.&nbsp; The Date class is perfectly happy to deal with a text format, so you could just use something human readable: const lastUpdate = '2018-07-21 9:56:20 am'; /* ... */ log('-=&gt; rmptutor v'+version+' &lt;=- ['+(new Date(lastUpdate))+']'); &nbsp;Make sure it's 'YYYY-MM-DD hh:mm:ss &nbsp;Me' so the month and day don't get swapped. Together with version, lastUpdate provides useful information to the humans using the script: "-=&gt; TokenMod v0.8.40 &lt;=- [Tue Jul 17 2018 01:03:56 GMT+0000 (UTC)]" "-=&gt; TurnMarker v1.3.8 &lt;=- [Tue Jul 18 2017 20:10:57 GMT+0000 (UTC)]" "-=&gt; AaronDebug v0.1.0 &lt;=- [Sun Apr 22 2018 13:54:54 GMT+0000 (UTC)]" "-=&gt; CharEdit v0.1.1 &lt;=- [Wed Feb 08 2017 14:13:10 GMT+0000 (UTC)]" "-=&gt; MoveLog v0.1.0 &lt;=- [Fri Jun 29 2018 21:45:44 GMT+0000 (UTC)]" "-=&gt; APISelection v0.1.0 &lt;=- [Tue Jul 17 2018 02:15:35 GMT+0000 (UTC)]" About schemaVersion One of the most important things I did early on was come up with writing a schemaVersion into the state.&nbsp; Where the above&nbsp; version is meant for humans to understand, the schemaVersion is meant for the script to understand.&nbsp; As G G alluded to, it doesn't version the script at all, it versions the schema (as in layout, pattern, structure, etc) of the state object itself, more specifically the part of the state that this script deals with.&nbsp; The checkInstall() function's purpose is to compare the schemaVersion in the script to the one stored in the state, and make adjustments as needed so that the script can function. The simplest adjustment is just replacing the state with the new format, but you can do much better!&nbsp; GroupInitiative is a great example of this: const checkInstall = function() { log('-=&gt; GroupInitiative v'+version+' &lt;=- ['+(new Date(lastUpdate*1000))+']'); if( ! _.has(state,'GroupInitiative') || state.GroupInitiative.version !== schemaVersion) { log(' &gt; Updating Schema to v'+schemaVersion+' &lt;'); switch(state.GroupInitiative &amp;&amp; state.GroupInitiative.version) { case 0.5: state.GroupInitiative.replaceRoll = false; /* break; // intentional dropthrough */ /* falls through */ case 0.6: state.GroupInitiative.config = { rollType: state.GroupInitiative.rollType, replaceRoll: state.GroupInitiative.replaceRoll, dieSize: 20, autoOpenInit: true, sortOption: 'Descending' }; delete state.GroupInitiative.replaceRoll; delete state.GroupInitiative.rollType; /* break; // intentional dropthrough */ /* falls through */ case 0.7: state.GroupInitiative.config.announcer = 'Partial'; /* break; // intentional dropthrough */ /* falls through */ case 0.8: state.GroupInitiative.config.diceCount = 1; state.GroupInitiative.config.maxDecimal = 2; /* break; // intentional dropthrough */ /* falls through */ case 0.9: state.GroupInitiative.config.diceCountAttribute = ''; /* break; // intentional dropthrough */ /* falls through */ case 0.10: if(_.has(state.GroupInitiative.config,'dieCountAttribute')){ delete state.GroupInitiative.config.dieCountAttribute; state.GroupInitiative.config.diceCountAttribute = ''; } if(_.has(state.GroupInitiative.config,'dieCount')){ delete state.GroupInitiative.config.dieCount; state.GroupInitiative.config.diceCount = 1; } /* break; // intentional dropthrough */ /* falls through */ case 1.0: state.GroupInitiative.savedTurnOrders =[]; /* break; // intentional dropthrough */ /* falls through */ case 'UpdateSchemaVersion': state.GroupInitiative.version = schemaVersion; break; default: state.GroupInitiative = { version: schemaVersion, bonusStatGroups: [ [ { attribute: 'dexterity' } ] ], savedTurnOrders: [], config: { rollType: 'Individual-Roll', replaceRoll: false, dieSize: 20, diceCount: 1, maxDecimal: 2, diceCountAttribute: '', autoOpenInit: true, sortOption: 'Descending', announcer: 'Partial' } }; break; } } }; By using a switch statement with cases that fall through, it picks the point where the stored schema is and applies all the changes necessary to bring it up to the current version.&nbsp; This is important because someone might have last used the script when the schema version was vastly different, so merely making the changes from "last time" isn't sufficient.&nbsp; And all these changes preserve the configuration and running state for the user.&nbsp; Imagine how annoying it would be if updating the Bump script meant that it lost you color choices for the GM layer and Object layer tokens, and forgot all of the tokens who have been Bump'd!! Having this schemaVersion concept also frees you up to change bad decisions you made earlier in development.&nbsp; If you realize that "and array of token ids" isn't sufficient, and you really wanted "a key/value mapping from page id to an array of token ids", you can seamlessly and easily change that.&nbsp; Most of the cases in my checkInstall() switch statement correspond to schemaVersion numbers and fall through to the cases below them, but there are two special cases: The 'UpdateSchemaVersion' case will only come up when it gets fallen into by the above cases.&nbsp; It serves 2 purposes: 1) write the current schemaVersion into the state object and 2) mark where the next schema version case should go, right above it. The default case is what gets hit when the script has never been installed (or the schemaVersion matches nothing). It writes the full schema to the state.&nbsp; Be sure when you add a new schemaVersion that you update this object to match what the current schema should be. That should about cover those 3 lines. =D&nbsp; What's next?
1532192961
Thank you gentlemen! G G says: If you aren't using the state, you can delete that. Well I'll definitely do something with it at some point -- if nothing else to explore! Aaron says: I've configured my editor of choice (Vim, cuz it's the best. ( Shut it, Brian!&nbsp; =D ))...&nbsp; That should about cover those 3 lines. =D&nbsp; What's next? Heh heh I love the banter! Okay, so for once I believe have a firm grasp on the generous offerings. So no followup questions as to the 3 lines and so moving on. I do want to cover the everything in quite some detail for everyone's benefit. So I'll be asking questions that I think I know the answer to.&nbsp; At some point though, I'll try to up the challenge level. =) Let's see what I get right and wrong here starting with the initial statement: const rmptutor = ( ()=&gt; {} ) () ; I have stylized the parenthesis so as to be able to discuss them. It looks like the script is wrapped in the above construction? And thus, anything declared inside of the wrapper is hidden from other scripts? If so: What is the purpose of the plain face parenthesis? I assume it is basically evaluate()? What is the purpose of the bold face parenthesis? I think it's a function in fat arrow syntax e.g. ()=&gt;{code goes here} and it is just wrapping the vast bulk of everything into a function? Also, it appears that if you wanted to pass a parameter from another script, the code's structure is already in place, so all you have to do is declare the parameter e.g. e.g. (par1)=&gt;{code goes here} What is the purpose of the italicized parenthesis?
1532194897
The Aaron
Forum Champion
API Scripter
Remember that discussion of IIFEs? &nbsp;=D The ()=&gt;{} is a function in the “fat arrow” syntax &nbsp;to the left of the =&gt; is the argument list, to the right is the function body. If a function takes no arguments, it’s argument list is just (). If a function does nothing, a no-op, it’s function body is just {}. The next ( ) out allows the implicit assignment (the creation of the function object) to occur before the next set of (), which are the function being executed. The heart of the Revealing Module Pattern is this creation of a Closure. The “Revealing” aspect is the revealing of a select interface out of that Closure by returning them from the Closure created by executing the IIFE.
1532286809
The Aaron &nbsp;said: Remember that discussion of IIFEs? &nbsp;=D Yes, in fact, I was hoping I had everything straight from that quite helpful discussion (which I've linked to here ).&nbsp;Thank you. When I said, "Let's see what I get right and wrong here starting with the initial statement" I was attempting to pay homage to how far the community has brought me and sort of let folks know that I was reviewing something that I had been taught. I was reviewing information in the form of a question because I am not experienced&nbsp; enough to speak with any sort of authority. So even when I am pretty sure I have something right, I am reluctant to say 'this' means 'that'. For example, take my question "What is the purpose of the plain face parenthesis? I assume it is basically evaluate()?" wasn't meant to disregard our earlier discussion but to review it and just double-check I have this conceptualized correctly. Basically, my take away from the other discussion was that the plain-faced parenthetical set -- or the first '(' and it's closing ')' -- was that it was evaluate() within the ordinary rules of precedence with no further purpose. (If you put a variable and the assignment operator in front of that, it returns whatever is being evaluated to said variable.) I believe I understood and fully accepted what you said. My shorthand for that concept is '()' basically means 'evaluate()'. That's probably too simple though because if I understand your response (see below) there is this added nuance in that an object is being created in this particular instance. Anyway, I apologize if I come across as unappreciative. Nothing could be further from the case. I just wanted to explain myself here, so please don't feel obliged to respond to what I've said above. On the other hand, if you think there is a better way for me to review and double-check with you and the community that I am following along, please let me know.&nbsp;Thanks. ()=&gt;{}&nbsp;is a function in the “fat arrow” syntax to the left of the =&gt; is the argument list, to the right is the function body. If a function takes no arguments, it’s argument list is just (). If a function does nothing, a no-op, it’s function body is just {}. I take that to mean that my take on it was correct (or at least close enough to correct to proceed). Sweet! =D The next ( ) out allows the implicit assignment (the creation of the function object) to occur before the next set of (), which are the function being executed. If I follow you correctly, with regard to the styling below, you are saying that the plain-faced parentheses creates the function object first, then italicized parentheses executes the created function and since it's empty nothing is passed into or applied the object (basically run the object "as is")? const rmptutor = ( ()=&gt; {} ) () ; Overall, I would say I do believe I understand this structure, except for the italicized (). Anyway, thanks Aaron and all.&nbsp;
1532291683

Edited 1566055117
The Aaron
Forum Champion
API Scripter
No worries at all!&nbsp; Repetition builds muscle memory. =D Rewriting that line less tersely yields: const rmptutorBuilderFunction = () =&gt; {} ; const rmptutor /* public interface */ = rmptutorBuilderFunction () ; When that first line is evaluated, the operations are applied in order of precedence, highest first.&nbsp; The Fat Arrow functions aren't technically operators, but they do participate in operator precedence (detailed on this page:&nbsp; <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions" rel="nofollow">https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions</a> &nbsp;): const rmptutorBuilderFunction /* Assignment is precedence 3 */ = /* Fat Arrow precedence is &gt;4 and &lt;19 */ () =&gt; {} ; So, the function object is created first (precedence &gt;4), and then assignment to the variable (precedence 3).&nbsp; In the second line, where it gets executed: const rmptutor /* public interface */ /* Assignment is precedence 3 */ = /* Function Call is precedence 19 */ rmptutorBuilderFunction () ; The function call happens first (precedence 19), and then assignment to the variable (precedence 3). With the original line, there would be an issue of precedence without clarifying it with grouping.&nbsp; Here's how that would play out: const rmptutor /* public interface */ /* Assignment is precedence 3 */ = /* Fat Arrow precedence is &gt;4 and &lt;19 */ ()=&gt; {} /* Function Call is precedence 19 */ () ; In this case, there would be a problem.&nbsp; The Javascript interpreter would first try to execute the function (precedence 19)... but it hasn't been created yet!&nbsp; You fix this by grouping: const rmptutor /* public interface */ /* Assignment is precedence 3 */ = /* Grouping is precedence 20 */ ( /* Fat Arrow precedence is now effectively &gt;24 and &lt;39 */ ()=&gt; {} ) /* Function Call is precedence 19 */ () ; Now, because of the grouping, the Function Object is created (precedence &gt;24), then executed (precedence 19), then the result is assigned (precedence 3) to rmptutor. Speaking of Objects... In Javascript, everything is an object.&nbsp; Part of the reason I write functions as: const foo = function() {}; Is that it's a reminder that it is an object. Even literal values are objects: 'Foo'.length; /* 3 */ 23.5.toFixed(2); /* "23.50" */ 1..toExponential(); /* "1e+0" */ (function qux(a){return `qux: ${a}`;}).name; /* "qux" */ (The 1.. Is necessary as the first . will be considered the radix point.&nbsp; You could write it as (1).toExponential() to avoid that. =D)
1532370908
I am pretty sure I follow you 98% but I need to bone up on literals. Also, I accept that I need () &nbsp;for proper precedence and that it is a "Function Call" but that's not how I am used to seeing a function call. e.g. function myFunction() {}; // what I would call a creating/defining/declaring a function myFunction(); // what I would call -- the function call Hmmm... as I understand it -- the rmp template is basically wrapped in a function that calls itself? So is that you only need the () when a function calls itself? To my recollection I haven't see anything like that in any other language and I didn't see that explained in the link to Mozilla.&nbsp;&nbsp;
1532372181
The Aaron
Forum Champion
API Scripter
You're almost there. =D&nbsp; This syntax is a bit peculiar, and you're unlikely to have seen it outside of this instance.&nbsp; The necessity for the grouping parentheses is just because of the operator precedence issue with Fat Arrow functions ( It only touches on it briefly in that link under the heading "Parsing Order", about 80% down the) What's being executed is the function that is being defined right there.&nbsp; Maybe this will help.&nbsp; Fat Arrow functions are mostly just syntactic sugar for anonymous functions.&nbsp; These two are basically the same: const foo = function(){ return 'bar'; }; const foo = () =&gt; 'bar'; However, there's no ambiguity with the parsing order with an anonymous function, so this is perfectly valid: const rmptutor = function(){} () ; Traditionally, you'd write that like this: const rmptutor = ( function(){} () );&nbsp; or const rmptutor = ( function(){} ) () ; The grouping parentheses are unnecessary in those two, but it was common courtesy to put them in so that a programmer seeing them would recognize "Oh, this is going to be Immediately Invoked at the end. (There was some argument as to whether the function call () belonged inside or outside the Grouping ( ). ) Because of the ambiguity with the Fat Arrow syntax though, you need the Grouping parentheses to allow the function to be constructed before it gets called.
1532450512

Edited 1532450547
Okay I see why now. Thanks. That was confusing as hell. =D FWIW I wish the interpreter/language would have used different sytax e.g. 'invoke' or something. LOL. So the next chunk I'd like to review/cover&nbsp; is: const registerEventHandlers = () =&gt; { &nbsp; &nbsp; on('chat:message', handleInput); }; I interpret the above code as follows: const registerEventHandlers = () =&gt; Indicates to me that this is a function/object. It's assigned name 'registerEventHandlers' indicates to me that within this block you are going to listen for events and then send the event to a specific function based on the type of 'event' that is triggered/heard. Since we have a fat arrow function here without it ending with the IIFE (), this is an ordinary function being called from elsewhere? That is to say, it's your run-of-the-mill function. on('chat:message',) Indicates to me that the event we are looking for is the Roll20 API chat:message event . on(, handleInput) Indicates to me that when the chat:message occurs that the handleInput function is called? I don't see anything else being passed to the handleInput function so I'm guessing we got closure and-or the fact that 'all functions are objects in JavaScript' at play here? Also, I am quite fuzzy as to what creates the handleInput object. I would think it would be in the const handleInput = (msg) =&gt; {}; block. But I am not following how the msg parameter is being passed? I think I got something backwards in my head here. =)
1532453540

Edited 1532453574
The Aaron
Forum Champion
API Scripter
No problem, you're mostly right. registerEventHandlers() is a function who's " one thing " is connecting events to the functions that want to know about them using the Roll20 on() function.&nbsp; The variable registerEventHandlers gets assigned the function bolded below.&nbsp; &nbsp; &nbsp; const registerEventHandlers = () =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; on('chat:message', handleInput); &nbsp; &nbsp; }; registerEventHandlers is "assigned the function," but if an IIFE was involved it would be "assigned the result of calling the function".&nbsp; No IIFE in this case. Generally, you don't want to respond to any events until after the 'ready' event has fired.&nbsp; If you look below where registerEventHandlers() is declared, you will see an on('ready',()=&gt; ... ) event registration which actually invokes it: &nbsp; &nbsp; on('ready', () =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; checkInstall(); &nbsp; &nbsp; &nbsp; &nbsp; registerEventHandlers(); &nbsp; &nbsp; }); This could just as easily be written as: &nbsp; &nbsp; on('ready', () =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; checkInstall(); &nbsp; &nbsp; &nbsp; &nbsp; on('chat:message', handleInput); &nbsp; &nbsp; }); However, functional decomposition allows for easily adding event registration later without cluttering "where we make the decision" with "how we perform the action". On on() The syntax for the on() function is: on( &lt;EVENT IDENTIFIER STRING&gt;, &lt;EVENT HANDLER FUNCTION&gt; ); In the case of the 'ready'&nbsp; EVENT IDENTIFIER STRING &nbsp;above, the&nbsp; EVENT HANDLER FUNCTION is provided inline using a Fat Arrow function.&nbsp; With the 'chat:message'&nbsp; EVENT IDENTIFIER STRING &nbsp; &nbsp;we instead pass the variable that represents the function for the&nbsp; EVENT HANDLER FUNCTION .&nbsp; In both cases, the value of the second parameter from the function on()'s point of view is a Function Object.&nbsp; You can imagine on() as looking something like: const eventMap = {}; const on = (event, func) =&gt;{ eventMap[event]=eventMap[event]||[]; eventMap[event].push(func); }; At some later point, there might be some internal notify(event,data,prev) like this: const notify = (event, data, prev) =&gt; { (eventMap[event]||[]).forEach( f =&gt; f(data,prev) ); }; You can actually try that out in a REPL , or the API sandbox. Hopefully that clears up how msg object gets to handleInput().&nbsp; Separating the event handler function code makes it easy to use the same event handler function with multiple events.
1532456557
&nbsp;if an IIFE was involved it would be "assigned&nbsp; the result of calling &nbsp;the function" Oh, that clear quite a bit up there!&nbsp;Woot! The syntax for the on() function is: on( &lt;EVENT IDENTIFIER STRING&gt;, &lt;EVENT HANDLER FUNCTION&gt; ); In the case of the 'ready' EVENT IDENTIFIER STRING above, the EVENT HANDLER FUNCTION is provided inline using a Fat Arrow function.&nbsp; With the 'chat:message' EVENT IDENTIFIER STRING&nbsp; we instead pass the variable that represents the function for the EVENT HANDLER FUNCTION.&nbsp; The rest of your (Aaron's) explanation is going above my head at the moment, but I think see the purpose of (msg) in&nbsp; const handleInput = (msg) =&gt; {}; It seems backwards to me, but essentially this enables msg. dot syntax within the handleInput function/object. As for the interrelationship of the functions, I think I should be okay (at least as a starting point) that I can follow the structure provided and this will link an event to an "event-object" (as a laymen's term if nothing else) and then hand that event-object over to a function for processing. I think that will get me started and as I start toying with stuff, hitting other references and other examples, that this will eventually sink in. Or at least I'll have a deeper understanding when I inquire further. With regard to: &nbsp; &nbsp; &nbsp; &nbsp; if (msg.type !== "api") { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return; &nbsp; &nbsp; &nbsp; &nbsp; } If the object is not equal to "api" do nothing. With regard to: &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; let args = msg.content.split(/\s+/); &nbsp; &nbsp; &nbsp; &nbsp; switch(args[0]) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case '!rmptutor': &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; }; Seems like you are using a split method here and creating an array -- delimited by /whitespace/ -- then looking at the first element of that array and if that element is not 'rmptutor' then break? So from there, basically, if you are going to do something with the message, you are probably going to have to familiarize with what args.array looks like (what is in each element) and then do further switch and-or case statements depending upon what you want to do with the chat message?
1532459715

Edited 1532460798
The Aaron
Forum Champion
API Scripter
On handleInput() So, this might make handleInput clearer.&nbsp; This is equivalent: const handleInput = function(msg){ if('api' !== msg.type) { return; } /* ... */ }; So is this: const handleInput = function( myFirstParamForMessageObject ){ if('api' !== myFirstParamForMessageObject.type) { return; } /* ... */ }; msg is just the conventional name for the parameter that holds the message object from a 'chat:message' event, but you can name it whatever.&nbsp; You can even forego it entirely if you only want to know if the events happen: let msgCount = 0; on('chat:message', ()=&gt;{ log(`Message Events: ${++msgCount}`); }); You generally care what the msg was for 'chat:message' , but some other events you are less concerned about the details, only that it happened.&nbsp; A good example is if you maintain some sort of cache, like a lookup for rollable table entries, it's often simpler to just reprocess everything than try to handle the specific change (if somewhat less efficient). On Message Type For msg.type , you almost always only care about 'api' messages.&nbsp; These will be messages who's first character is a ! to denote an API command.&nbsp; You might want to listen to other message types if you were looking for some sort of output to parse, like if you're managing the spell points for a character sheet and you watch for the spell template to do your adjustments to remaining slots, or ammo or whatever. On Args As you've surmised, args is an array of the contents of the message object, split by whitespace.&nbsp; The / / denotes a regular expression, the \s matches a single whitespace character and the + tells it to match 1 or more.&nbsp; So, it would turn this: '!rmptutor arg1 arg2 arg3 arg4' into: ['!rmptutor','arg1','arg2','arg3','arg4'] Just to clarify, the / / is not part of the match, so: '!rmptutor arg1/ /arg2' would result in: ['!rmptutor','arg1/','/arg2'] On the Switch Statement The switch statement is my way of handling multiple commands from one script.&nbsp; Generally, you could use something like: if(/^!rmptutor$/.test(args[0])){ /* do a thing */ } In fact, in some simpler scripts I don't even split until after matching: if(/^!rmptutor\b/.test(msg.content)){ /* do a thing */ } Switching on args[0] , each case is the name of a command. &nbsp; &nbsp; &nbsp; &nbsp; switch(args[0]) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case '!rmptutor': /* do stuff for command */ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break; case '!rmptutor-config': /* do stuff for config version of command */ break; &nbsp; &nbsp; &nbsp; &nbsp; } Each case is like a small if statement, so this could be rewritten as: if('!rmptutor' === args[0]) { /* do stuff for command */ } else if('!rmptutor-config' === args[0]){ /* do stuff for config version of command */ } The case statement is just more convenience and easier to read, in my opinion. It also lets you replace the args[0] part with the result of a function: &nbsp; &nbsp; &nbsp; &nbsp; switch( args.shift() ) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case '!rmptutor': /* args is now only the parameters to the command */ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break; &nbsp; &nbsp; &nbsp; &nbsp; } And I sometimes use it to add simple modifiers to commands: let whisper = false; &nbsp; &nbsp; &nbsp; &nbsp; switch( args.shift() ) { case '!wrmptutor': whisper = true; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case '!rmptutor': /* args is now only the parameters to the command */ sendChat('rmtutor', `${whisper ? '/w gm' : ''} Some output!`); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break; &nbsp; &nbsp; &nbsp; &nbsp; } The break statement just halts processing the switch case .&nbsp; It's like the else part of an if .&nbsp; One of the nice features of a switch statement (and sometimes controversial) is that omitting the break statement allows the flow of execution to pass down into the next case and so on until it comes to a break .&nbsp; I make use of that above by letting !wrmptutor set whisper to true and then fall down to the "normal" logic for !wrmptutor .&nbsp; Opponents of fall through logic would instead have you decompose each case into a function call and pass appropriate arguments.&nbsp; I can't say I disagree, but I don't think there's anything wrong with using a fall through if it's for a good reason. =D&nbsp;&nbsp; Keep 'em coming!
1532467024
Thanks Aaron: At this point I think I have the template figured out well enough to trace code through it (very roughly but I can now get a general sense of how the rpm structure works), catch events, and drop in or tweek code a bit (especially if I go back here for help on iterating through an object). For my first test I wanted to checkout the args array in the console. So I updated the tutorial: const rmptutor = (() =&gt; { // eslint-disable-line no-unused-vars &nbsp; &nbsp; const version = '0.1.0'; &nbsp; &nbsp; const lastUpdate = 1531675536; &nbsp; &nbsp; const schemaVersion = 0.1; &nbsp; &nbsp; const checkInstall = () =&gt;&nbsp; { &nbsp; &nbsp; &nbsp; &nbsp; log('-=&gt; rmptutor v'+version+' &lt;=-&nbsp; ['+(new Date(lastUpdate*1000))+']'); &nbsp; &nbsp; &nbsp; &nbsp; if( ! state.hasOwnProperty('rmptutor') || state.rmptutor.version !== schemaVersion) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; log(`&nbsp; &gt; Updating Schema to v${schemaVersion} &lt;`); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; state.rmptutor = { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; version: schemaVersion &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; }; &nbsp; &nbsp; const handleInput = (msg) =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; if (msg.type !== "api") { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; let args = msg.content.split(/\s+/); &nbsp; &nbsp; &nbsp; &nbsp; switch(args[0]) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case '!rmptutor': &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; log(args); /* &lt;= HERE IS THE ONLY CHANGE */ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; }; &nbsp; &nbsp; const registerEventHandlers = () =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; on('chat:message', handleInput); &nbsp; &nbsp; }; &nbsp; &nbsp; on('ready', () =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; checkInstall(); &nbsp; &nbsp; &nbsp; &nbsp; registerEventHandlers(); &nbsp; &nbsp; }); &nbsp; &nbsp; return { &nbsp; &nbsp; &nbsp; &nbsp; // Public interface here &nbsp; &nbsp; }; })(); And I went into the game and just typed in chat "!rmptutor teach me something" and you'll never guess what happened? ["!rmptutor","taught","me","something!"] LOL. Thanks a ton Aaron and Others! I think I am ready to start breaking my campaign now... err I mean add some scripts to it. I'll try to find a script to work from and-or a thread dealing with what I want for my campaign rather than continuing the discussion here.&nbsp;
1532468795
The Aaron
Forum Champion
API Scripter
Ha!&nbsp; Change that line to : log(args.map(w=&gt;/^t/.test(w)?'taught':(/g$/.test(w)?`${w}!`:w))); and !rmptutor teach me something really will ["!rmptutor","taught","me","something!"] =D
1532472124

Edited 1532472153
LOL I don't know how that is going to work but I bet I know what it is going to do... YEP!
1532472362
The Aaron
Forum Champion
API Scripter
=D