<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Ruel Gonzales on Medium]]></title>
        <description><![CDATA[Stories by Ruel Gonzales on Medium]]></description>
        <link>https://medium.com/@ruel.gonzales23?source=rss-acd9e779710b------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*A3Kp1Ni6DWxmeKoz9LN0gw.jpeg</url>
            <title>Stories by Ruel Gonzales on Medium</title>
            <link>https://medium.com/@ruel.gonzales23?source=rss-acd9e779710b------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 30 May 2026 09:18:52 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@ruel.gonzales23/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Slate DevLog: Event Promo Codes]]></title>
            <link>https://medium.com/@ruel.gonzales23/slate-devlog-event-promo-codes-13fdff4fa6a2?source=rss-acd9e779710b------2</link>
            <guid isPermaLink="false">https://medium.com/p/13fdff4fa6a2</guid>
            <category><![CDATA[forms]]></category>
            <category><![CDATA[technolutions]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[slate]]></category>
            <dc:creator><![CDATA[Ruel Gonzales]]></dc:creator>
            <pubDate>Wed, 14 Jan 2026 18:45:59 GMT</pubDate>
            <atom:updated>2026-01-14T18:45:59.354Z</atom:updated>
            <content:encoded><![CDATA[<p><em>*taps mic</em></p><p><em>Is this thing on? Hello! Long time no blog. I just wrapped up a whirlwind of a year where I was Slating more than I have ever Slated before (positive). Maybe I’ll write some reflection on that but I doubt that’s not why any of you are here. You’re here to see what random quirky solutions I came up that colors outside the lines of a standard Slate form. Well folks, I am back to give the people what they want.</em></p><p>If you are hosting an event in Slate that requires a registration fee, you may have tried to implement some sort of promotional code system where a registrant inputs a code and the fee then becomes waived. This can be achieved with Slate standard functionality through a combination of calculation formulas and form filters, but what if you wanted to limit how many times a certain code is used?</p><p>There isn’t really anything in the form builder that can do this, but this is possible through a translation table, a dictionary export and a tad bit of custom scripting.</p><h3>Determining the Requirements</h3><p>After some brainstorming, I concluded that these items are needed in order for this to work:</p><ul><li>A place to store all of the promotional codes and their criteria</li><li>A way for the form to access that list</li><li>A running count of used codes</li></ul><h3>Storing the promotional codes</h3><p>Using a translation table seemed like an obvious choice. Through this I can define the code itself and use the export value to store the count limit for how many times a code can be used. This will come into play later when we build our calculation formula. It will also be easy for any future user to maintain when they need to add codes or change the maximum limit.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xdEvHW-AY5gdbpq9Xc1SnA.png" /></figure><h3>Accessing the promo code table</h3><p>With the translation code set up, we now have a reference for our form to use. We just need a way to link it to the event form itself.</p><p>There is no actual join or direct mechanism in the form builder that can do this, so we’ll create a merge field that is a dictionary export that points to the translation table.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1CjOdYVsWVjSRTQTiWe-eg.png" /></figure><p>Remember, our ‘max’ export is just the export value of the translation table.</p><p>In order to check the number of times a code has been used, we have an additional export that is another independent subquery that is based in form responses.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*o6KeGHf3jMMptIXhSnPZmg.png" /></figure><p>We then have a filter that looks for the count where the form field value matches the promo code value in the translation table.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*XAxRHGoHcd57zHH8EAksVQ.png" /><figcaption>I am using ‘promo_code’ as my export key on the form field for this event template so technically I can check <em>every</em> promo code that was used across multiple events since I am querying all form responses.</figcaption></figure><p>From here, we can store the translation table as a regular HTML table in an instruction block on the form.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*OEF0ihI1P1Y4z8R_7gnayg.png" /></figure><p>This table will be hidden but with some DOM manipulation through custom scripting, we can leverage this table to determine if a code is valid or not. The main loop for this is show below.</p><pre>for (const row of rows) {<br>    // Get the data for each row in the table<br>    const cells = row.querySelectorAll(&quot;td&quot;);<br>    const codeCell = cells[0]?.textContent.trim().toLowerCase();<br>    const maxCell = parseInt(cells[1]?.textContent.trim()); <br>    const usedCell = parseInt(cells[2]?.textContent.trim()); <br>        // Check if there&#39;s a match between the code inputted code and one of the codes on the translation table<br>        if (codeCell === code &amp;&amp; usedCell &lt; maxCell) {<br>            // Set the payment value and messages.<br>            reg_fee.value = 0;<br>            valid_msg.style.display = &quot;&quot;;<br>            decline_msg.style.display = &quot;none&quot;;<br>            <br>            // Set the flag to true and exit the loop immediately<br>            foundValidMatch = true;<br>            break; // Stop the loop as soon as a match is found<br>        }<br>    }</pre><h3>Validation</h3><p>For the registrant to input their promo code, we just set up a regular free text form field. This form field then informs the payment widget that actually places the payment activity on the record.</p><p>There needs to be some feedback to let the registrant know if their code was valid or not, otherwise, they can end up submitting the form without knowing what fee they are going to be charged. To address this, I set up a message for each scenario that will appropriately display depending if the promo code was valid or not:</p><pre>&lt;div id=&quot;promo_accept&quot; style=&quot;display:none ; color:#27ae60;&quot;&gt;<br>  &lt;strong&gt;The promo code has been applied and you will not be charged the registration fee&lt;/strong&gt;.<br>&lt;/div&gt;<br><br>&lt;div id=&quot;promo_declined&quot; style=&quot;display:none; color:#c0392b;&quot;&gt;<br>  &lt;strong&gt;The promo code you entered is either invalid or no longer active. You will be required to pay the registration fee.&lt;/strong&gt;<br>&lt;/div&gt;</pre><p>The displaying of this message is addressed in the prior code block. You’ll notice that there is logic in there that resets the style display depending if there was a valid match or not.</p><h3>Final Demo</h3><p>With all the pieces in place, we now have a function that does the following:</p><ol><li>Checks to see if the inputted code matches anything in our translation table</li><li>Checks to see if the max number of used codes has not yet been reached</li><li>Display a message to alert the registrant if the the code is valid or not</li></ol><p>In the end, we have something like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*5I9FAKWlxxouEz07eTVN5Q.gif" /></figure><h3>Conclusion</h3><p>I thought this was an interesting request and had a lot of fun working through it. This ended up being part of a more elaborate build where I am also taking into account discount rates so that different promo codes apply different fees. I didn’t go into detail about that in this post because that is just more custom script (in short, I use another export value to store the discount rate and then use that in a formula to calculate the fee). My goal here is to showcase the utility of independent subqueries and translation tables and the different ways they can be leveraged in Slate. You may not have events that use promo codes, but my hope is that you can take this foundation and apply it to something else. I don’t know what that is, but I look forward to finding out.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=13fdff4fa6a2" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[A Pre-Summit Reflection]]></title>
            <link>https://medium.com/@ruel.gonzales23/a-pre-summit-reflection-e4adcb79fa57?source=rss-acd9e779710b------2</link>
            <guid isPermaLink="false">https://medium.com/p/e4adcb79fa57</guid>
            <category><![CDATA[crm]]></category>
            <category><![CDATA[technolutions]]></category>
            <category><![CDATA[slate]]></category>
            <dc:creator><![CDATA[Ruel Gonzales]]></dc:creator>
            <pubDate>Wed, 25 Jun 2025 00:10:01 GMT</pubDate>
            <atom:updated>2025-06-25T00:10:01.526Z</atom:updated>
            <content:encoded><![CDATA[<p>I am currently waiting for my flight to attend my 4th summit. Considering I’ve worked in higher ed for 12 years, that number actually feels relatively low, but a lot can happen in just a year. I posted a reflection after the last summit but it felt more appropriate to do one now.</p><p>It’s been a little over a year since I started this blog and it has been one of the most professionally gratifying things I’ve done. I keep it pretty low key when it comes to my job but I finally decided to write about some solutions I came up with. I didn’t have any specific goal…I guess I wanted people to have more resources but if I were honest about it, I was probably seeking validation.</p><p>I eventually got that validation last year in Chicago, in front of the FGI booth. Nick Conte found me and had some very nice things to say about my posts. He liked it so much that he encouraged me to become a community ambassador, which I hesitantly took him up on. It should’ve been a no brainer, but I am inherently lazy. Eventually, I figured that if I was serious about my career then I need to put myself out there and do things that would normally make me uncomfortable. If I already started posting content online, I might as well keep it pushing.</p><p>From there, I met Paige and Kim along with a group of ambassadors who were some of the sharpest and most thoughtful slate users and captains. I wasn’t in there long and I actually didn’t say much, but just being on those calls with them really widened my perspective on how much Slate captains really care for their institutions and making sure everyone is given the best possible opportunity to succeed. I am so grateful I got to be a part of it but from what I can tell, they are just getting started.</p><p>I ended up leaving the ambassador program because I joined the preferred partner, Kennedy and Company, as a Solutions Engineer — which is a fancy way of saying consultant. There’s a lot to be said about this transition but I’ll just say that I’m Slate-ing more than I’ve ever Slate-ed before (I mean that positively). I haven’t been writing much because I’ve been so focused on making sure I have my best foot forward for the clients we work with. The nature of the work has changed a bit too where I was previously just building things by myself on a whim. The process as a consultant is much more methodical and collaborative which has been a very welcome change of pace for me. It’s only been a few months but I’m excited to be learning and working with such a talented team.</p><p>So as you can see, a lot can happen in a year. None of which would’ve happened if I hadn’t decided to put myself out there. It also wouldn’t have happened if I didn’t have the wonderful people around me constantly encouraging me to keep going. So if you’re looking for a push, let this be your sign to network and find that solution you were told wasn’t possible while you’re at summit. If you don’t know where to start, come find me along with the rest of the Kennedy and company team at booth # 10 in the vendor showcase. At the very least, you’ll make a friend to meet up with at the next conference.</p><p>(…it would be me. I would be the friend.)</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e4adcb79fa57" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Slate DevLog: Report Building with Chart.js]]></title>
            <link>https://medium.com/@ruel.gonzales23/slate-devlog-report-building-with-chart-js-50fad0c89a56?source=rss-acd9e779710b------2</link>
            <guid isPermaLink="false">https://medium.com/p/50fad0c89a56</guid>
            <category><![CDATA[chartjs]]></category>
            <category><![CDATA[crm]]></category>
            <category><![CDATA[reporting]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[slate]]></category>
            <dc:creator><![CDATA[Ruel Gonzales]]></dc:creator>
            <pubDate>Thu, 06 Mar 2025 00:58:32 GMT</pubDate>
            <atom:updated>2025-03-06T00:58:32.181Z</atom:updated>
            <content:encoded><![CDATA[<p>One of my <a href="https://medium.com/@ruel.gonzales23/slate-devlog-mobile-friendly-reports-98e6bf88d311">earliest posts</a> was about how I leveraged independent subqueries in a Slate query to inject data onto KPI cards for a mobile-friendly report. I have since improved upon that by experimenting with the <a href="https://www.chartjs.org/docs/latest/">Chart.js</a> library. It is very much a fully custom solution but if you’re comfortable enough with web development and you do not have access to a robust reporting tool such as Tableau or PowerBI, then you may want to explore this solution.</p><p>For those unfamiliar, Chart.js is an open source JavaScript library that allows data visualizations to be easily rendered on your custom webpage. Using the Chart.js library just gives you a bit more of a dynamic visual for your Slate data. You can make pretty robust charts for proper data analysis but for my purposes, I just wanted some simple visuals for YTD comparisons so that the recruitment team can keep an eye on their territories.</p><p><em>Note: I spend most of this post talking about the Chart.js code since my previous post about this topic covers a lot of the portal/ query setup. You can refer back to that post for reference: </em><a href="https://medium.com/@ruel.gonzales23/slate-devlog-mobile-friendly-reports-98e6bf88d311"><em>‘DevLog: Mobile Friendly Reports’</em></a></p><h4><strong>Understanding the template code</strong></h4><p>Before we put this into a Slate portal, let’s take a look at the template code for a simple bar chart.</p><pre>const labels = Utils.months({count: 2});<br>const data = {<br>  labels: labels,<br>  datasets: [{<br>    label: &#39;My First Dataset&#39;,<br>    data: [65, 59],<br>    backgroundColor: [<br>      &#39;rgba(255, 99, 132, 0.2)&#39;,<br>      &#39;rgba(255, 159, 64, 0.2)&#39;,<br>    ],<br>    borderColor: [<br>      &#39;rgb(255, 99, 132)&#39;,<br>      &#39;rgb(255, 159, 64)&#39;,<br>    ],<br>    borderWidth: 1<br>  }]<br>};</pre><p>If you’re not super comfortable with JavaScript, this can look like a lot but here are a couple of bullet points to summarize what’s actually going on:</p><ul><li>​The chart’s configuration is handled through a JS object.</li><li>The actual numbers that are displayed on the chart are defined by the ‘data’ object property where it is given an array of values.</li><li>The background and border colors are also defined by arrays and the color defined at a given index will be the color displayed for the data in that same index. For example, 65 (the first bar in our graph) will have a background color of ‘rgba(255, 99, 132, 0.2)’</li><li>This example uses the Utils.months method to label the axes — this is just a more efficient way of using month names as your labels, especially if you’re looking at a whole year, but the labels can be anything you want.</li></ul><p>With this foundation, we can implement this with a Slate portal and connect it with a Slate query.</p><h4>Use Case</h4><p>The admissions staff were well-versed enough in Slate to be able to query and build reports, but they didn’t really have a quick way to pull and assess data for any of their given territories. I started playing around with the idea of a portal that passes through a parameter of a selected territory. Once I got that framework down, I decided to take it a step further and make it mobile-friendly with dynamic visuals.</p><p>One of the charts that is used in this portal is a comparison of the YTD number of applications submitted between the current and previous admissions cycles.</p><p>First, I grabbed these numbers by having independent subquery exports that filter off the application year (round) and submission date.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*fH7UiR4sXSoW6tQV" /><figcaption>Note: The ‘application goals’ exports are static values that are stored in an entity field that is linked to a custom dataset of recruitment territories.</figcaption></figure><p>From here, we can insert these exports as merge fields into the code for our chart. This is very similar to how we made simple KPI cards in my previous post. The only difference is that instead of inserting the merge fields into the HTML, we are putting it into the JavaScript so that it can render as a dynamic chart. We also want to use the stacked bar chart type from the library.</p><pre> new Chart(document.getElementById(&#39;myChart&#39;).getContext(&#39;2d&#39;), {<br>   type: &#39;bar&#39;,<br>   data: {<br>   labels: [&#39;Fall 2024&#39;,&#39;Fall 2023&#39;],<br>   datasets: [<br>               {<br>           label: &#39;Actual&#39;,<br>           data: [{{2024_app_count}}, {{2023_app_count}}],<br>           backgroundColor: &#39;#1f97e9&#39;,<br>           borderColor: &#39;rgba(54, 162, 235, 1)&#39;,<br>           borderWidth: 1<br>       },<br><br><br>                  {<br>           label: &#39;Goal&#39;,<br>           data: [{{2024_app_goal}},{{2023_app_goal}}],<br>           backgroundColor: &#39;rgb(34,139,34)&#39;,<br>           borderColor: &#39;rgba(54, 162, 235, 1)&#39;,<br>           borderWidth: 1<br>       }<br><br><br><br><br>   ]<br>},<br>   options:<br> {<br><br><br>       indexAxis: &#39;y&#39;,<br>       scales: {<br>           x: {<br>               stacked: false<br>           },<br>           y: {<br>               stacked: true,<br>             beginAtZero: true,<br>             stepSize: 100<br>           }<br>       }<br>   }<br>});</pre><p>The above code is inserted into a script element in a portal view where the chart is injected into a given &lt;div&gt; element. I copied this into a couple of other portal views that were looking at different metrics. The whole thing looks like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/0*fgtqDkf-BVcR9_OF" /><figcaption>Note: The data displayed here is modified data and does not reflect any real numbers of any institution.</figcaption></figure><h4><strong>Conclusion</strong></h4><p>I’ve mainly written about mobile-friendly reporting through Slate because the institution has PowerBI licenses to do more heavy reporting, so my use cases have always addressed the need for quick and concise reports. However, Chart.js is a nice alternative if you want to create a more interactive and digestible report for larger presentations to various stakeholders. I’ve just used simple bar and pie charts but there are plenty of other options in the library to do something more elaborate.</p><p>I do want to mention that having your portal queries scan through the person table multiple times can lead to slow-downs and even timeouts (which is why I am only comparing two years worth of applicants). It may not be ideal for daily reports, but it can be great for yearly summaries where you can just manually input the values instead of having the report calculate totals every time it is open.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=50fad0c89a56" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Slate DevLog: Configurable Connections]]></title>
            <link>https://medium.com/@ruel.gonzales23/slate-devlog-configurable-connections-7e96a0a4b55f?source=rss-acd9e779710b------2</link>
            <guid isPermaLink="false">https://medium.com/p/7e96a0a4b55f</guid>
            <category><![CDATA[slate]]></category>
            <category><![CDATA[technolutions]]></category>
            <category><![CDATA[crm]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[forms]]></category>
            <dc:creator><![CDATA[Ruel Gonzales]]></dc:creator>
            <pubDate>Wed, 11 Dec 2024 17:47:06 GMT</pubDate>
            <atom:updated>2024-12-11T17:47:06.406Z</atom:updated>
            <content:encoded><![CDATA[<p>Lately, I’ve been getting a lot more questions regarding queries. We’ve had a bunch of templates set up that were serviceable but as our staff look to get more creative with their admissions strategy and outreach, they are looking to sink their teeth into more robust query building. The problem I now have to solve is how to effectively train them to not only know the mechanics of configurable joins, but also ensure that they have a solid understanding of our relational database. I believe this is a solvable problem to address if given the proper time and training–which no one really has given the limited free time between their recruitment schedule and application reading.</p><p>I was playing around with the idea of gamifying training and thought that the New York Times connections game would be a good fit for drilling the database tables. I’m not entirely sure how effective it will be but thought it would be fun to try nonetheless. I actually posted my prototype on the facebook group and the feedback was overwhelmingly positive! Seems like a lot of you are interested and how I built it so here is a complete breakdown of how I built my own version of NYT connections.</p><p><em>As a disclaimer, I should mention that although this is hosted on a Slate form, the game is mostly built with HTML, CSS, and JavaScript. As a disclaimer to the disclaimer, I learned coding foundations from various online materials so the way I write my code may be seen as basic (lots of if statements) but at least it’s easier to read?</em></p><h4><strong>Overview</strong></h4><p>If you are not familiar with the original game, the premise is fairly simple:</p><ul><li>Players get 16 words in a 4 x 4 grid divided into four hidden categories</li><li>Figure out connections between words and group them into sets of four; grouped words become a category</li><li>Players must figure out all categories by grouping the words</li></ul><p>In the NYT game, you only get a limited number of attempts, but since this is supposed to be a training tool, I opted out of implementing that to encourage players to finish the puzzle.</p><p>In practice, it looks something like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/498/0*OsxJtbe1qmpFxMmq" /><figcaption>Players click the 4 words in the grid they think is a match. If the grouping is correct, those words disappear. This continues until all 4 groups are found.</figcaption></figure><h4>Setting up the grid</h4><p>I ended up setting up the foundation of the game by using a CSS grid that contains 16 HTML button elements. Each of these buttons has the word stored as well as attributes that store which group the word is in.</p><pre>&lt;div class=&quot;button-container&quot;&gt;<br>   &lt;button data-group=&quot;B&quot; id=&quot;btn1&quot; onclick=&quot;activeField(this.id)&quot;&gt;Jobs&lt;/button&gt;<br>   &lt;button data-group=&quot;A&quot; id=&quot;btn2&quot; onclick=&quot;activeField(this.id)&quot;&gt;Concat&lt;/button&gt;<br>   &lt;button data-group=&quot;C&quot; id=&quot;btn3&quot; onclick=&quot;activeField(this.id)&quot;&gt;SIS&lt;/button&gt;<br>   &lt;button data-group=&quot;B&quot; id=&quot;btn4&quot; onclick=&quot;activeField(this.id)&quot;&gt;Relationships&lt;/button&gt;<br>   &lt;button data-group=&quot;D&quot; id=&quot;btn5&quot; onclick=&quot;activeField(this.id)&quot;&gt;Priority&lt;/button&gt;<br>   &lt;button data-group=&quot;C&quot; id=&quot;btn6&quot; onclick=&quot;activeField(this.id)&quot;&gt;Banner&lt;/button&gt;<br>   &lt;button data-group=&quot;D&quot; id=&quot;btn7&quot; onclick=&quot;activeField(this.id)&quot;&gt;Updated Date&lt;/button&gt;<br>   &lt;button data-group=&quot;A&quot; id=&quot;btn8&quot; onclick=&quot;activeField(this.id)&quot;&gt;Formula&lt;/button&gt;<br>   &lt;button data-group=&quot;A&quot; id=&quot;btn9&quot; onclick=&quot;activeField(this.id)&quot;&gt;Existence&lt;/button&gt;<br>   &lt;button data-group=&quot;B&quot; id=&quot;btn10&quot; onclick=&quot;activeField(this.id)&quot;&gt;Addresses&lt;/button&gt;<br>   &lt;button data-group=&quot;D&quot; id=&quot;btn11&quot; onclick=&quot;activeField(this.id)&quot;&gt;Created Date&lt;/button&gt;<br>   &lt;button data-group=&quot;A&quot; id=&quot;btn12&quot; onclick=&quot;activeField(this.id)&quot;&gt;Coalesce&lt;/button&gt;<br>   &lt;button data-group=&quot;B&quot; id=&quot;btn13&quot; onclick=&quot;activeField(this.id)&quot;&gt;Sports&lt;/button&gt;<br>   &lt;button data-group=&quot;D&quot; id=&quot;btn14&quot; onclick=&quot;activeField(this.id)&quot;&gt;Street&lt;/button&gt;<br>   &lt;button data-group=&quot;C&quot; id=&quot;btn15&quot; onclick=&quot;activeField(this.id)&quot;&gt;CommonApp&lt;/button&gt;<br>   &lt;button data-group=&quot;C&quot; id=&quot;btn16&quot; onclick=&quot;activeField(this.id)&quot;&gt;Colleague&lt;/button&gt;<br>&lt;/div&gt;</pre><p>Each button is assigned a ‘data-group’ that corresponds to the 4 different categories that the player has to find. We also have a unique id for each button and a linked function that determines the ‘active state’.</p><h4><strong>Active State</strong></h4><p>A button’s ‘active state’ is defined by the button being highlighted as well as the selected word being considered as part of the grouping the player wishes to submit or check. This is handled through the following function:</p><pre>// SET ARRAY TO STORE SELECTED BUTTONS<br>let selected = [];<br>function activeField(id){<br>    // GET THE SELECTED BUTTON<br>    const btn = document.getElementById(id);<br>    // GET THE BUTTON&#39;S DATA GROUP (CATEGORY)<br>    const btn_group = btn.getAttribute(&#39;data-group&#39;);<br>    // HIGHLIGHT OR DE-HIGHLIGHT THE BUTTON DEPENDING ON THE CURRENT STATE<br>    if (btn.style.backgroundColor === &#39;rgb(0, 158, 135)&#39;) {<br>        btn.style.backgroundColor = &#39;&#39;;<br>        // IF DE-SELECTED, REMOVE THE RESPECTIVE DATA GROUP<br>        const indexToRemove = selected.indexOf(btn_group);<br>        selected.splice(indexToRemove,1)<br>        } else {<br>        // IF SELECTED, ADD THE DATA GROUP<br>            btn.style.backgroundColor = &#39;rgb(0, 158, 135)&#39; ;<br>            selected.push(btn_group);<br>        };<br>        <br>        };</pre><p>This function is handling a couple of things. First, it’s checking to see if the button is already clicked by checking the background color. If there is no background color, then add it in so that the selected button is highlighted. Additionally, store the grouping of the selected button in our ‘selected’ array. We use this array in order to check to see if the player has selected the correct grouping of words. We also have the function to reverse this if a player de-selects a word, meaning that the respective data grouping is removed and the background color is removed.</p><p>Here’s a demo of the console so you can visualize what is happening.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*y6nNk0HHGsLTKIiyBBizHA.gif" /></figure><h4>Checking the Answer</h4><p>So now that we are properly storing the data of the selected words, we can write our function to check to see if the grouping is actually correct. To put simply, we want to see if our array has 4 elements in it and all of those elements belong to the same grouping. Ex: [“B”,“B”,“B”,“B”]. This will be the function that will be attached to our ‘CHECK’ button.</p><p>To achieve this in JavaScript, I wrote a nested for loop that compares each element in the array to the next. If it all matches, then the code will remove the selected words and display the discovered category above the grid.</p><pre>let isWrong = false;<br>for(i=0; i &lt; selected.length; i++) {<br>    for (j=0; j &lt; selected.length; j++) {<br>      // IF ONE ELEMENT IN THE ARRAY DOESN&#39;T MATCH, SET THE ISWRONG VARIABLE TO TRUE<br>        if (selected[i] != selected[j]){<br>            isWrong = true;<br>            break;<br>        } <br>    }</pre><p>You’ll notice that we have a ‘isWrong’ variable that is set to true if there’s a found discrepancy within the array. If it is false, then we can trigger the actions that are connected with a correct answer. When a player has found a grouping, the following should occur:</p><ol><li>The correct grouping leaves the grid</li></ol><pre>if(!isWrong &amp;&amp; selected.length == 4){<br>    // WHEN THE ENTIRE ARRAY OF 4 MATCHES, HIDE THOSE WORDS FROM THE GRID<br>    allBtns.forEach(button =&gt; {<br>        if (selected.includes(button.dataset.group)) {<br>            button.style.display = &#39;none&#39;<br>        }<br>    });<br>}</pre><p>2. The discovered category should be displayed</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*lBbczRlAdRpFTkVOFvgUxw.png" /></figure><p>Each category display is set up as static HTML elements that are initially hidden in the beginning of the game.</p><pre>&lt;table border=&quot;1&quot; cellpadding=&quot;5&quot; cellspacing=&quot;1&quot; style=&quot;width:100%;&quot;&gt;<br>  &lt;tbody&gt;<br>    &lt;tr id=&quot;group_a&quot; style=&quot;display:none&quot;&gt;<br>      &lt;td style=&quot;text-align: center;&quot;&gt;<br>      &lt;h2&gt;<br>        Subquery Outputs<br>      &lt;/h2&gt;<br>      Coalesce, Concat, Existence, Formula&lt;/td&gt;<br>    &lt;/tr&gt;<br>    &lt;tr id=&quot;group_b&quot; style=&quot;display:none&quot;&gt;<br>      &lt;td style=&quot;text-align: center;&quot;&gt;<br>      &lt;h2&gt;<br>        One-to-Many Query Bases<br>      &lt;/h2&gt;<br>      Addresses, Sports, Jobs, Relationships&lt;/td&gt;<br>    &lt;/tr&gt;<br>    &lt;tr id=&quot;group_c&quot; style=&quot;display:none&quot;&gt;<br>      &lt;td style=&quot;text-align: center;&quot;&gt;<br>      &lt;h2&gt;<br>        Unique For Merging Examples (__ ID)<br>      &lt;/h2&gt;<br>      SIS, Banner, CommonApp, Colleague&lt;/td&gt;<br>    &lt;/tr&gt;<br>    &lt;tr id=&quot;group_d&quot; style=&quot;display:none&quot;&gt;<br>      &lt;td style=&quot;text-align: center;&quot;&gt;<br>      &lt;h2&gt;<br>        Address Table Rank Determination<br>      &lt;/h2&gt;<br>      Priority, Created Date, Updated Date, Street&lt;/td&gt;<br>    &lt;/tr&gt;<br>  &lt;/tbody&gt;<br>&lt;/table&gt;</pre><p>Our function un-hides these categories as they are discovered throughout the game. This is handled through a switch statement that looks at the common data-group.</p><pre><br>// USE JUST THE FIRST ELEMENT OF THE ARRAY <br>const correctGroup = selected[0];<br>switch(correctGroup) {<br>    case &quot;A&quot;:<br>        document.getElementById(&#39;group_a&#39;).style.display = &#39;&#39;;<br>        gameCheck += 1;<br>        gameScore += 25;<br>        break;<br>    case &quot;B&quot;: <br>        document.getElementById(&#39;group_b&#39;).style.display = &#39;&#39;;<br>        gameCheck += 1;<br>        gameScore += 25;<br>        break;<br>    case &quot;C&quot;: <br>        document.getElementById(&#39;group_c&#39;).style.display = &#39;&#39;;<br>        gameCheck += 1;<br>        gameScore += 25;<br>        break;<br>    case &quot;D&quot;: <br>        document.getElementById(&#39;group_d&#39;).style.display = &#39;&#39;;<br>        gameCheck +=1;<br>        gameScore += 25;<br>        break;<br>}</pre><h4>Scoring</h4><p>This is supposed to be a training tool but it is a game after all, so I felt it’d be an added level of fun to have some sort of scoring system to further incentivize players to try harder. Ultimately, I decided on the following:</p><ul><li>The player starts out with 100 points</li><li>Every incorrect guess subtracts 10 (score will not drop below zero)</li><li>Every correct grouping adds 25</li><li>The total possible score is 200</li></ul><p>To implement scoring, I initialize a global variable ‘gameScore’ and set it to 100. From there, I update the score depending on the scenario. In our switch statement above, you can see that 25 points get added for every correct category that is found. If there’s an incorrect match, then points are subtracted in the following line of code:</p><pre>let gameScore = 100<br><br>// IF ANSWER IS WRONG AND ONLY 4 BUTTONS ARE SELECTED, ALERT THE PLAYER AND SUBTRACT 10 POINTS<br>if (isWrong &amp;&amp; selected.length == 4){<br>    alert(&#39;Incorrect, try again!&#39;);<br>// MAKE SURE THE SCORE DOES NOT DIP BELOW 0<br>    if(gameScore &gt; 0) {<br>        gameScore -= 10<br>    };<br>}</pre><p>After I had done the test run with the users in the Facebook group, I saw that there were a LOT of players who were easily hitting the max score of 200. In order to have a bit more variation with the scores, I added in a time element where the score counts down until all categories are found.</p><pre>let timerInterval;<br><br>function startTimer() {<br>    if (timerInterval) return; // Prevent multiple intervals<br>    timerInterval = setInterval(() =&gt; {<br>      if (gameScore &gt; 0) {<br>      gameScore--;<br>      };<br>     // UPDATE THE SCORE FORM FIELD <br>      const formScore = document.getElementById(&#39;form_8a9aade3-6008-475c-9bf9-6a6dafd3ee94&#39;)<br>      formScore.value = gameScore;<br>    }, 2000); // Update every 2 seconds<br>  }<br><br>// Function to stop the timer (triggered when the game is complete)<br>      <br>function stopTimer() {<br>        clearInterval(timerInterval);<br>        timerInterval = null; // Allow restarting<br>      }</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/544/1*61UBsiHd9oiJy6x2-uHOjQ.gif" /></figure><h4><strong>Finishing the game (Completion State)</strong></h4><p>You’ll also notice that within each case statement, there is another variable called gameCheck that gets added with every discovered category. I use this to essentially track the progress of the player. When the gameCheck variable reaches 4 (+1 for every category), then that means the game is done and I can use that to put the game in a completion state. In this case, our completion state simply means that the score becomes locked in and a congratulatory message is displayed.</p><pre>function gameComplete(){<br>    // GET THE FORM FIELD ELEMENT<br>    const formScore = document.getElementById(&#39;form_8a9aade3-6008-475c-9bf9-6a6dafd3ee94&#39;)<br>    // INSERT THE ACTIVE GAME SCORE INTO THE FORM FIELD<br>    formScore.value = gameScore;<br>    if (gameCheck &gt;= 4) {<br>    // DISPLAY GAME END MESSAGE<br>        gameEnd.style.display = &#39;&#39;;<br>        checkBtn.style.display = &#39;none&#39;;<br>      stopTimer();<br>    }<br><br>}</pre><h4>Leaderboard</h4><p>To help feed everyone’s competitive side, I knew I wanted to implement a leaderboard that shows the player how they did in comparison to all the other submissions up to that point. To achieve this, I ended up creating a simple portal that pulled in all the form responses, along with the scores and put it into a simple table. I then embedded the portal onto the confirmation page of the form so when the player submits the form, they can immediately see the results.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*T-rK8Vd0b5QP4yFp0c78Gw.png" /></figure><h4>Demo</h4><p>And here is a full demo of the game!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*K-bvzbTe9I2rPgLzYA8XpA.gif" /></figure><h3>Closing</h3><p>I once saw a funny tweet that said something along the lines of “video games are so funny, you could be killing a bunch of zombies but in reality you’re just deleting lines from a table”. To some degree, that rings true here. All this code is really doing is storing and removing elements from an array and making sure they all match. It was a really fun project to work on and I’m also delighted that it was able to work within Slate’s framework.</p><p>I don’t actively study code or development. I’ve always just looked up how to do something based on the specific problem I’m trying to solve. This was really one of the first times where I had an idea pop in my head and I was immediately able to execute it without having to look up much. It’s not the cleanest or most efficient code but it <em>works</em> and everyone I’ve shared it with has enjoyed it. It’s a simple knockoff of an existing popular game but I am so so proud of it.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7e96a0a4b55f" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Slate DevLog: Processing Dynamic Fees]]></title>
            <link>https://medium.com/@ruel.gonzales23/slate-devlog-processing-dynamic-fees-e40d40a690fe?source=rss-acd9e779710b------2</link>
            <guid isPermaLink="false">https://medium.com/p/e40d40a690fe</guid>
            <category><![CDATA[technolutions]]></category>
            <category><![CDATA[slate]]></category>
            <category><![CDATA[forms]]></category>
            <category><![CDATA[fees]]></category>
            <category><![CDATA[crm]]></category>
            <dc:creator><![CDATA[Ruel Gonzales]]></dc:creator>
            <pubDate>Wed, 13 Nov 2024 19:05:50 GMT</pubDate>
            <atom:updated>2024-11-13T19:05:50.276Z</atom:updated>
            <content:encoded><![CDATA[<p>We charge a fee for our orientation and have historically offered full waivers that were granted on a case by case basis (although they were mostly approved). The orientation office proposed the idea of having the fee be a sliding scale that is based on certain factors such as their grant eligibility, as well as the amount that the student reports that they can realistically afford. There is also a written statement the student provides that is also considered. This is a nice idea but a bit hard to execute within Slate for two main reasons:</p><p><strong>1) You can have the registration form calculate a fee upon submission but this fee reduction requires a manual review and must be approved first.</strong></p><p>We do not want to have the students pay a fee that they may not even be eligible for. On the flip side, we don’t want to delay students who aren’t requesting a reduction and give them the ability to pay immediately.</p><p>To account for this, there is form logic attached to the payment widget where it only appears with the standard fee only if the student does NOT request a fee reduction. Otherwise, the widget is withheld so there will be no charge until the reduction is reviewed.</p><p><strong>2) The payment-due activity needs to be placed on the Slate record, but the personnel reviewing the requests are not Slate users.</strong></p><p>Typically, we would designate an operations staff member to handle this but the process is so fluid that it made more sense to figure out a way to automate this piece. We could’ve also trained up the staff members who are reviewing the reductions, but the window to turn this around was relatively short. Additionally, I don’t feel all that comfortable granting that level of access to non super users, especially when we’re dealing with fees.</p><p>So how do I reconcile all of this into one simple, streamlined process? In a portal, of course.</p><p><strong>Building the Portal</strong></p><p>First, I have a query that grabs all the registrants that have requested a fee reduction.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*ENYxlFZLi8v7McIs" /></figure><p>Then, I created a pop-up view that calculates and displays the expected amount. If necessary, the reviewer can still adjust the final reduced fee.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*CVxDq4-GlgncZ7Nl" /></figure><p>If you’ve kept up with my previous posts, you know that I love embedding forms into pop-ups. I like doing this because I can have the portal pop-up display whatever I want it to and have a portal query auto-fill the form while also keeping the form itself relatively minimal.</p><p>Hidden on this form is a payment widget that takes the value of the final total amount. The fee reduction does not account for guests so I had to ensure those costs are always included. If the payment activity were person scoped, the activity will get added to the record upon submission. However, we have this set up as an application scoped payment activity so these charges have to be added after the fact.</p><p>To automate this, I created an import/export where a query sends out a file with the new approved amounts into the SFTP. A source format then picks it up and adds it into the record as a new due payment activity. There is a final deliver mailing set up that notifies the student when the import is complete.</p><p><strong>Closing</strong></p><p>I’ve actually gotten some version of this request at previous institutions I’ve worked at. I’ve always hesitated in offering up a solution in Slate because it’s always been my assumption that the more sustainable solution lives within student accounts/billing. I’ve come to learn that student accounts are interconnected with so many different things that it takes time to revamp or even tweak a fee. Additionally, there’s always the question if this kind of solution is even worth diving into. Is there enough a financial benefit to go through the trouble of tinkering with an already established system? That’s not really for me to decide. I was asked to build out a process in a short period of time and Slate provided the tools to do so.</p><p>I will say that we saw enough of an increase in funds that went towards making a more robust orientation for the students and their parents so your institution may want to do this too.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e40d40a690fe" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Slate DevLog: Faculty Mentor Dataset]]></title>
            <link>https://medium.com/@ruel.gonzales23/slate-devlog-faculty-mentor-dataset-eea00ad3e81a?source=rss-acd9e779710b------2</link>
            <guid isPermaLink="false">https://medium.com/p/eea00ad3e81a</guid>
            <category><![CDATA[slate]]></category>
            <category><![CDATA[higher-ed]]></category>
            <category><![CDATA[crm]]></category>
            <category><![CDATA[technolutions]]></category>
            <dc:creator><![CDATA[Ruel Gonzales]]></dc:creator>
            <pubDate>Fri, 08 Nov 2024 14:02:05 GMT</pubDate>
            <atom:updated>2024-11-08T14:02:05.275Z</atom:updated>
            <content:encoded><![CDATA[<p>Lately, I’ve been thinking through how to structure certain continuing student data in Slate so that our student success team can better manage and facilitate certain processes and tasks. I knew I wanted to work-in faculty information but the problem was that I didn’t have enough of a use case yet. The lift to build this out was too large just for hypothetical cases. Luckily, student success had requested a process that is relatively low impact but can easily scale if we wanted to expand it.</p><p>Our institution has a process for students to formally request or change their faculty mentor. This was previously handled through a Microsoft Dynamic form which generally worked fine at a base level. It can accept form submissions and then route the request to the appropriate faculty member for approval. The problem was that there was no real mechanism for tracking the status of the request or sending out reminder notifications if it sat untouched over a length of time. Both of these gaps can be filled with Slate functionality.</p><p>Here is the outline of what I thought the process could look like in Slate:</p><ol><li>Student submits request via Slate form</li><li>Request gets sent to faculty member for approval</li><li>Student success is notified of approval and officially updates the roster in campus SIS</li></ol><p>Before getting into the actual workflow, I had to actually decide on how to store the faculty mentors and their roster of mentees. What I landed on was a custom dataset to store the faculty contact information that is then linked to an entity that stores their roster.</p><h4><strong>Faculty Mentor Dataset</strong></h4><p>For this project, the dataset is pretty minimal. All I really need is their SIS ID, name, and e-mail address. I hope to expand this in the future but for now, this is enough information.</p><h4><strong>Mentee Roster Entity</strong></h4><p>We then have a custom entity that is scoped to the faculty dataset. To link the appropriate student record, I have a related dataset row field that leverages our existing custom continuing student dataset. Additionally, I have a status flag to indicate if a student is active on the roster or not.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*aXpdvQhPJedK9OG7" /></figure><h4><strong>Request Form</strong></h4><p>The request form is simple and straight forward. We just ask for the student information (which is auto-filled via SSO) as well as the faculty member they’d like to request to be their mentor. The added benefit of having a dataset is that we can leverage the auto-suggest function. When the mentor is properly selected, we can ensure that the request is forwarded to the appropriate e-mail.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/688/0*OEYRxIZOUHdQuAk2" /></figure><h4><strong>Faculty Mentor E-mail and Approval Process</strong></h4><p>Since I wanted to utilize some dataset information that is stored outside of the form response, I built the notification e-mail in Deliver. I have the form response as the base and turned off deduping in order to ensure that the faculty member gets an e-mail for every request.</p><p>The content of the e-mail lets them know the student that submitted the request as well as a total count of their current roster. This is used as a quick reference for the faculty member to help inform their approval decision.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/582/0*LKD8avQfVWUH9QsT" /></figure><p>The ‘Review Request’ hyperlink is a URL to a separate Slate approval form. The URL also passes through parameters to auto-fill the student and faculty information. Additionally, I also pass through the form response GUID of the initial request so we can tie it to the approval decision.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*1Lr_FxO-9z5klhkL" /></figure><p>For various reasons, the mentor roster actually also has to be coded appropriately in our SIS. This is currently a manual process so we have to notify the appropriate staff member to make that update. To do so, there is a form communication that is set up that sends out an e-mail if the faculty mentor approves. If the request is not approved, then the student will be immediately notified.</p><h4><strong>Tracking Progress</strong></h4><p>On the form registrant page, I also have a subquery export that links the form response of the approval form and displays the final decision. If the faculty member hasn’t responded, the status will show ‘Pending’.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*HZi8J9U-irRf4WV3" /></figure><h4><strong>Final Processing</strong></h4><p>We do not want to send the student a confirmation of the approval until the SIS is updated so we actually have an admin field that is updated on the back-end of the form which then triggers the final confirmation e-mail to the student.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/548/0*0tXCEQaQFxJsElmX" /></figure><p>It is always a priority for me to minimize the amount of places something needs to be manually updated. To achieve this, there’s a daily scheduled SQL stored procedure to export the updates in the SIS and import it into Slate.</p><h4><strong>Closing</strong></h4><p>You may be reading this and wondering why I didn’t build this in a Slate workflow. As I mentioned earlier, this process is so low-impact that I didn’t want to go through the trouble of planning and building out bins. At this point it is much easier for the academic advisor who is managing this process to go into the form registrant page instead of clicking through the workflow <em>after</em> updating the SIS.</p><p>This is somewhat of a simple workflow but I was happy that I was able to establish the foundation of a faculty dataset. This potentially opens up options for faculty to interact with the student record more. I would eventually like to have Slate be the central hub where we facilitate other form approvals (add/drop, petitions, etc.) as well as alerts for when the student requires some additional assistance. A lot of discussions are needed with various stakeholders before that can happen but this is certainly a good start.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=eea00ad3e81a" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Slate and The Four Types of Work]]></title>
            <link>https://medium.com/@ruel.gonzales23/slate-and-the-four-types-of-work-000725d64c4d?source=rss-acd9e779710b------2</link>
            <guid isPermaLink="false">https://medium.com/p/000725d64c4d</guid>
            <category><![CDATA[slate]]></category>
            <category><![CDATA[crm]]></category>
            <category><![CDATA[system]]></category>
            <category><![CDATA[higher-education]]></category>
            <dc:creator><![CDATA[Ruel Gonzales]]></dc:creator>
            <pubDate>Thu, 03 Oct 2024 21:57:24 GMT</pubDate>
            <atom:updated>2024-10-03T21:57:24.362Z</atom:updated>
            <content:encoded><![CDATA[<p>I’ve seen/heard some discussions about burnout due to work being piled on Slate captains that are potentially out of the scope of their respective offices. I have been there before and it’s a tough space to navigate. There was a time where I wanted to prove my worth by getting the most out of the platform. Even if certain processes were unrelated to my role, I felt that earning campus buy-in for Slate was better than having NO buy-in. This made it hard to express that I was becoming overwhlemed, but the work just kept on coming.</p><p>What I am offering with this post is a framework you can keep in mind when having discussions with your leadership about workload and the potential need for more resources. You may not have control over how staff resources are allocated or even get to determine what actually goes into Slate, but what you should have is the ability to advocate for yourself and for your team.</p><p>I should clarify that the idea here isn’t to try to convince your leadership to give you less work. This is more about having effective discussions to ensure that you set proper expectations and are doing <em>productive</em> work that you can feel good about.</p><p><strong>Types of Work</strong></p><p>If you are active within the Slate community, you have probably seen jokes about how Slate is hard to define as a product (it’s an admissions CRM…but so much more!). In turn, this also makes it hard to define what the Slate captain role does. My mistake early on was considering all of my work to be just ‘Slate work’. I felt responsible for any process, no matter the size, that went through Slate. I didn’t realize I was doing myself a disservice until I came across a book called ‘<a href="https://itrevolution.com/product/the-phoenix-project/">The Phoenix Project</a>’.</p><p>The book tells a fictional story of a struggling car parts company that is mandated to improve their IT operations with no additional resources, while dealing with conflicting corporate priorities. In short, they need to not only improve their online store, but ensure it’s better than their competition while dealing with layoffs and limited time. Now, I will say that I wouldn’t recommend this book to everyone. The story aims to provide an illustration of the principles laid out in the <a href="https://itrevolution.com/product/the-devops-handbook-second-edition/">DevOps handbook</a> so the dialogue can be a bit…unrealistic, but effective nonetheless. Also, working as a Slate captain in higher education doesn’t necessarily align with working as a DevOps specialist in a software adjacent company. However, I still found that reading this book to be incredibly cathartic.</p><p>There’s a lot of concepts that are taught throughout the story but I wanted to highlight the lessons that were most applicable to me– the first one being the four types of work:</p><p><strong>Business Projects</strong> — Business initiatives, most of the development work.</p><ul><li>Slate implementation as a whole and any potential integrations with the rest of the campus enterprise systems.</li></ul><p><strong>Internal IT projects</strong></p><ul><li>This refers to projects that reduce technical debt and provide more automation. In the book, they specifically cite server maintenance and other database solutions. Luckily, Technolutions mostly manages that side of the house but tasks like building new Slate workflows, creating sets of rules, setting up automatic mailings to alert users of issues can also be considered.</li></ul><p><strong>Updates and Changes</strong></p><ul><li>This can apply to any change but every academic cycle usually comes with a consistent set of changes whether it is making updates to applications, accounting for new policies or handling changes from external parties such as the FAFSA.</li></ul><p><strong>Unplanned work or recovery work</strong></p><ul><li>You may also know this as ‘putting out fires’. This comes in many forms in Slate–maybe a query all of a sudden no longer runs as efficiently as it once did, you get an urgent request to pull data, or an incorrect mailing gets sent out and no one knows why.</li></ul><p>In hindsight, these seem like very obvious categories but seeing it written out on paper was a REVELATION for me. I finally had a framework where I can properly describe what was going on in my day-to-day workload. As the institution’s use of Slate grew, so did my responsibilities–which spanned across all four of these types of work. My title was just ‘Slate captain’ but in reality, I was a pseudo project manager, security administrator, integration developer, quality control specialist, helpdesk support, and even a salesman (I would be mandated to help transition a process into Slate but then when I actually got in the room with stakeholders, I still had to convince them that this is the best move for their process). I’ve found that framing your work this way, instead of throwing out general language about having a lot on your plate is a much more effective way of signaling to managers/stakeholders that you are being stretched thin. Why should they care? Well, because they are inadvertently creating a bottleneck, with you being the constraint.</p><p><strong>Identifying the constraint</strong></p><p>In the book, the company’s IT team was fairly large and had multiple teams. The main issue is that they really only had ONE person who knew the system well enough to solve any unplanned work or ‘fires’ that would come up, so when there would be multiple fires across different teams, every team had to wait for this one key member to put out one fire before moving on to the next, constantly creating bottlenecks. Sound familiar?</p><p>You may have already experienced this at your campus. I’d bet good money that you have gone to your campus IT with a new idea to improve a process but were either turned away or given a lower priority due to their limited bandwidth. This is by no means a knock on campus IT teams. I believe that they are also being stretched thin. I point this out because there’s an inherent understanding that implementing with other systems takes a lot of work and for whatever reason, that counter argument doesn’t work quite the same with Slate. That’s probably more of a testament to how dynamic a platform it is, but it shouldn’t mean that the approach should change.</p><p>Project proposals are usually (in my experience) presented to me as simple tasks but don’t take into account scope creep, potential features or changes that need to happen in the future. For example, a department may request for one of their forms to be recreated as a Slate form. The creation of the form itself is fairly simple and fast, but how often does the form change? How will the form responses be reviewed? Does a workflow need to be built? Are there communications involved? The list goes on from there. I think it should also be emphasized that you’re not just adding a new form or workflow into Slate–you are potentially replacing a decades old process that one has ever needed to change or analyze, until now. Alternatively, if you are creating a brand new process, it should be treated as a long term solution, or at least the foundation to one, rather than a band-aid fix. Whatever the case, there needs to be an appropriate amount of care and time devoted to it.</p><p>If the stakeholders are receptive to those questions, hopefully that leads to discussions about on-boarding other staff members into Slate or highlights the need to hire new staff. Otherwise, Slate captains are stuck managing multiple processes and have to deal with all of the types of work that go along with it. Aside from this being a lot of work for one person, this just doesn’t set the campus up for future success. Ironically, the more work that ends up being consolidated into Slate, the easier it is for Slate captains to become the same bottleneck they’ve been working so hard to avoid or eliminate.</p><p><strong>Making Your Work Visible</strong></p><p>There’s a quote from the ‘<a href="https://youtu.be/ObbVO3A3BvA?si=6iB6UTg40FpumI1m&amp;t=111">Godfellas</a>’ episode in Futurama that has always been stuck in my mind since my roomate played me the episode in college.</p><blockquote>“When you do things right, people won’t be sure you’ve done anything at all”</blockquote><p>The context of the episode doesn’t really apply, but that quote has become increasingly resonant to me as my career has gone on.</p><p>Working in Slate, or campus systems in general, is interesting because if you do your job well enough, you create this expectation that things just inherently work. You don’t have the benefit of seeing tangible results from your efforts. Admission counselors can celebrate receiving more applications than their anticipated goal, but in this role, simply <em>not</em> getting a panicked email about something being broken is probably the highest form of praise you can get. This is why it is absolutely crucial for you to spend some time to actually log your work. Write documentation, track updates, keep your leadership up-to-date with periodic milestones, etc.</p><p>I have a custom built ticket and project management system within Slate where I can take that data and present an analysis on time being spent on a given task or project. Before that, I tracked my work on Obsidian on a kanban board and later became a user of Notion. There is no shortage of tools that you can utilize, but I don’t think it really matters how you log your work, just as long as you do. Don’t get me wrong, I find doing this to be quite the drag…even a little boring. However, it will pay dividends when a new request comes in and you can provide a proper assessment of how much bandwidth is available. If this new project needs to take top priority, then you can set the expectation that completion of the existing pending projects will need longer timelines. Only you really know the amount of time and work it takes to get certain tasks done, so it’s vital that you can communicate that with some supporting evidence.</p><p><strong>Closing</strong></p><p>So will any of this actually amount to anything? It’s hard to know for sure. Nothing is guaranteed and it will depend on what your institution’s priorities are so you can take everything I say with a grain of salt.</p><p>I did spend a lot of time at my previous job rambling through this kind of thing with my boss at the time. He had always reassured me that changes were on the way but I ended up taking another job before those changes had come to fruition. After I had left, the team I was on was able to expand from two (including myself)…to FIVE. So yes, there is hope that if you are persistently advocating for your team, you will be given the proper resources.</p><p>Like with everything in higher education, everything moves in cycles. There will be some periods where budgets will be tight and everyone needs to do more while offices get expanded in more prosperous periods. All I know for sure is that no matter the amount of work, it is a much better feeling knowing that you are working with intention instead of aimlessly putting out fires. I wrote this in the hopes that this can help you get on that track.</p><p>Until next time.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/620/0*APy7guun7tMX7DUy" /></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=000725d64c4d" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Slate DevLog: Updating Entities w/ a POST Method]]></title>
            <link>https://medium.com/@ruel.gonzales23/slate-devlog-updating-entities-w-a-post-method-7288e8127b5d?source=rss-acd9e779710b------2</link>
            <guid isPermaLink="false">https://medium.com/p/7288e8127b5d</guid>
            <category><![CDATA[crm]]></category>
            <category><![CDATA[technolutions]]></category>
            <category><![CDATA[entity]]></category>
            <category><![CDATA[sql]]></category>
            <category><![CDATA[slate]]></category>
            <dc:creator><![CDATA[Ruel Gonzales]]></dc:creator>
            <pubDate>Fri, 13 Sep 2024 15:59:15 GMT</pubDate>
            <atom:updated>2024-09-13T15:59:15.632Z</atom:updated>
            <content:encoded><![CDATA[<p>Last week I talked about replicating the entity widget functionality in a portal with a form pop-up. This is the cleaner and simpler approach but it has its limitations. Mainly, it’s not ideal when dealing with long strings or paragraph text. You can have a paragraph text box on a Slate form, but what if you wanted to edit an already existing string? If you were using the form pop-up approach, you would think you can just pass through that string as a parameter like we do for the other fields. You technically could, but you’ll get mixed results due to the various characters that can be included that can break the URL parameter we’re trying to call. In short, it just doesn’t work.</p><p>The alternative method involves a combination of a custom HTML form and a custom SQL query. I should note that this practice is generally not recommended by Technolutions. As with any custom solution, there are plenty of variables that can go wrong which make it tougher to troubleshoot. Because of this, I tend to agree that you should always prioritize leveraging forms when wanting to post data to the record. At the very least, I recommend re-thinking your process and seeing if there is an alternative solution before going this route.</p><p>With all that being said, here is how to do the thing I’m not recommending.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/672/1*FflUhxlzhLSihMsAfx3Wjw.png" /></figure><p><em>Note: I will be building off of some components we established in my previous post so I will be referring back to that a couple of times.</em></p><h3>Use Case</h3><p>Let’s go back to our custom course entity. We have a field to store any advising notes that may be used for future reference. Instead of overwriting a new note every time an update occurs, we want to set this up so the initial note is displayed. This will allow the user to have the ability to edit the note.</p><h3><strong>Fields Table</strong></h3><p>First, I think it’s important to look at the table itself and illustrate what it is we’re actually going to do. Let’s take a look at one of our notes that already exist in the table:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*ww0e9haiUgIlhTZ-" /></figure><p>There are more columns in the table than the ones shown in the screenshot, but what’s most important to note here is that all custom fields live in the same table and each row has a [record] id that ties it to the actual Slate record, regardless of the scope.</p><p>In our case, the <strong>[record]</strong> corresponds with the GUID of our course entity row and the <strong>[id]</strong> is unique to every row in the FIELDS table. It’s ok if you don’t completely follow right now. This might make more sense when you see how we end up using these fields in practice.</p><h3><strong>Pop-Up and Course List Query</strong></h3><p>We are going to use the same two queries that we used when using the form approach. One will pull the information of our clicked-on course. Again, this takes the course ID and places the associated data and puts it in our pop-up after the linked method is called. The other will display all of the courses linked to the respective record. We will also use the same pop-up link that we used before.</p><p>For more info, you can refer back to the <a href="https://medium.com/@ruel.gonzales23/slate-devlog-updating-entities-with-a-form-pop-up-141196acaefa">previous post</a>.</p><h3>HTML Pop-Up Form</h3><pre> &lt;body&gt;<br>   <br>   &lt;!-- Add form element --&gt;<br>    &lt;form action=&quot;?cmd=updateCrs&quot; method=&quot;post&quot; style=&quot;width: 500px;&quot;&gt;&lt;input name=&quot;course_id&quot; type=&quot;hidden&quot; value=&quot;{{crs_id_update}}&quot; /&gt;&lt;input name=&quot;cmd&quot; type=&quot;hidden&quot; value=&quot;updateCrs&quot; /&gt;<br><br><br>       <br>    &lt;div class=&quot;header&quot;&gt;<br>      Update Course Information<br>    &lt;/div&gt;<br><br>&lt;!-- Display data from our pop-up query --&gt;<br>    &lt;div class=&quot;content&quot; style=&quot;height: 300px;&quot;&gt;<br>      &lt;table class=&quot;plain&quot;&gt;<br>        &lt;colgroup&gt;<br>          &lt;col style=&quot;width: 100px;&quot; /&gt;<br>          &lt;col /&gt;<br>        &lt;/colgroup&gt;<br>        &lt;tbody&gt;<br>          &lt;tr&gt;<br>            &lt;th style=&quot;text-align: left;&quot;&gt;School&lt;/th&gt;<br>            &lt;td&gt;{{crs_school_update}}&lt;/td&gt;<br>          &lt;/tr&gt;<br>          &lt;tr&gt;<br>            &lt;th style=&quot;text-align: left;&quot;&gt;Course Name&lt;/th&gt;<br>            &lt;td&gt;{{crs_name_update}}&lt;/td&gt;<br>          &lt;/tr&gt;<br>          &lt;tr&gt;<br>            &lt;th style=&quot;text-align: left;&quot;&gt;Course Note&lt;/th&gt;<br>&lt;!-- Add &lt;textarea&gt; element so that user can edit the actual note --&gt;<br>            &lt;td&gt;&lt;textarea cols=&quot;50&quot; id=&quot;course_note&quot; name=&quot;course_note&quot; rows=&quot;4&quot;&gt;{{crs_note_update}}&lt;/textarea&gt;&lt;/td&gt;<br>          &lt;/tr&gt;<br><br><br>        &lt;/tbody&gt;<br>      &lt;/table&gt;<br>    &lt;/div&gt;<br><br><br>    &lt;div class=&quot;action&quot;&gt;<br>      &lt;button class=&quot;default&quot; onclick=&quot;FW.Lazy.Commit(this);&quot; type=&quot;button&quot;&gt;Update&lt;/button&gt;&lt;button onclick=&quot;FW.Dialog.Unload();&quot; type=&quot;button&quot;&gt;Cancel&lt;/button&gt;<br>    &lt;/div&gt;<br>    &lt;/form&gt;<br> &lt;/body&gt;</pre><p>This HTML code was lifted from the knowledge base article on Portal Pop-ups and uses some functions and styles that are baked into Slate’s custom framework.</p><p>The keys here are the <strong>form </strong>and <strong>input </strong>elements. These allow for the information entered in the pop-up to be referenced in our POST method.</p><p>Breaking it down a little further, the ‘name’ attributes within these elements should end up corresponding with the custom parameters you are going to use in the query.</p><pre>&lt;input name=&quot;course_id&quot; type=&quot;hidden&quot; value=&quot;{{crs_id_update}}&quot; /&gt;</pre><p>For example, this will take the course ID field ({{crs_id_update}}) from our form pop-up query and run it through the custom SQL query as the <strong><em>@course_id </em></strong>parameter.</p><p>We do the same thing with the actual note.</p><pre>&lt;tr&gt;<br>  &lt;th style=&quot;text-align: left;&quot;&gt;Course Note&lt;/th&gt;<br>  &lt;td&gt;&lt;textarea cols=&quot;50&quot; id=&quot;course_note&quot; name=&quot;course_note&quot; rows=&quot;4&quot;&gt;{{crs_note_update}}&lt;/textarea&gt;&lt;/td&gt;<br>&lt;/tr&gt;</pre><p>Here, we are pulling the existing note from our pop-up query and placing it into a text box so it can be edited. We also define the id attribute with <strong><em>“course_note”</em></strong> so that it will also be passed through our POST query.</p><h3><strong>Custom SQL Query</strong></h3><p>When the form is submitted, we want to have the FIELDS table be updated with that edited note.</p><p>You could write an UPDATE statement in SQL to do exactly that. However, we don’t just want to <em>update </em>notes in our portal. We may need to ADD notes as well, if a note doesn’t already exist.</p><p>To account for that, we’re actually going to write a DELETE and INSERT statement in our query. So in practice, we’re doing a full replacement of the field instead of updating the value.</p><p>First, we need to set our parameters that we refer to in our HTML code.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/670/1*FRBaLmtiDDmxjwJUPE5S2Q.png" /></figure><p>We have these two parameters to take in the values of our course ID (to ensure the correct course is being updated) as well as the actual course note.</p><p>We can then write out our SQL statements.</p><pre>DELETE FROM FIELD WHERE [RECORD] = @course_id AND [FIELD] = &#39;pce_course_note&#39;<br><br>INSERT INTO FIELD<br>([FIELD],[RECORD],[VALUE])<br>(SELECT &#39;pce_course_note&#39;, @course_id, @course_note)</pre><p>These statements happen in the sequence they are written when the form is submitted. First, the query will find the field row that has the GUID of the course that is being reviewed and delete it. Then, it will add in a <em>new </em>field value with the new note.</p><p>The last step is linking this query with a portal method that is configured to POST. The ‘Action’ corresponds with the CMD we use in our HTML form.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/904/0*eT1An59VX5AjU55m" /><figcaption>As a reminder, the action you set here is going to be the action you set in the form element in the HTML code.</figcaption></figure><h3>Portal Methods</h3><p>To summarize, these are the portal parts we’re dealing with:</p><ol><li><strong>Course List</strong> — To display all relevant rows within our custom course entity.</li><li><strong>Pop-Up: Update Course </strong>— This hosts the view for that HTML form we built above.</li><li><strong>POST Course Update</strong> — Handles the custom SQL query that will update the fields table.</li></ol><h3>Demo</h3><p>Here’s what it looks like after we put all those pieces together:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/0*0BLM_SBjau25pXF2" /><figcaption>Note: This particular portal is Reader scoped. Also, I am aware Irvine Valley College is not under the quarter system — this is just for demo purposes only!</figcaption></figure><p>As you can see, the initial note is auto-filled in the text box. Then, we add some additional text that ends up being saved. Magic!</p><h3>Closing</h3><p>Being able to go with this custom approach adds a lot more flexibility to what you can do within your portals. However, I want to reiterate that you may be introducing more ways your portal can break. It could be something as simple as a browser update that changes the behavior of your code. Something like adding/editing notes may not be worth the trouble but you may want to do something more robust where this is the only viable option within Slate. It’s important to assess the risks against what you are trying to achieve before deciding on how to move forward.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7288e8127b5d" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Slate DevLog: Updating Entities with a Form Pop-Up]]></title>
            <link>https://medium.com/@ruel.gonzales23/slate-devlog-updating-entities-with-a-form-pop-up-141196acaefa?source=rss-acd9e779710b------2</link>
            <guid isPermaLink="false">https://medium.com/p/141196acaefa</guid>
            <category><![CDATA[slate]]></category>
            <category><![CDATA[crm]]></category>
            <category><![CDATA[entity]]></category>
            <category><![CDATA[portal]]></category>
            <category><![CDATA[technolutions]]></category>
            <dc:creator><![CDATA[Ruel Gonzales]]></dc:creator>
            <pubDate>Thu, 05 Sep 2024 18:23:05 GMT</pubDate>
            <atom:updated>2024-09-05T18:23:05.987Z</atom:updated>
            <content:encoded><![CDATA[<p>Entities are probably the most dynamic object within slate. It adds so much more flexibility to what can be stored on student records. I’ve used entities to store scholarship awards, course enrollment, notes…basically all of the things the Knowledge Base mentions as the best use cases.</p><p>Slate has a great widget that can be used within custom tabs on the Slate record that makes it easy for users to either edit or add a data row to the entity. Unfortunately, this widget isn’t available within portals, which is where most of my non-operations colleagues work out of. The good news is that you can replicate the widget within the portal by using a form pop-up that passes through appropriate parameters.</p><p>Let’s look at an example I talked about in a <a href="https://medium.com/@ruel.gonzales23/slate-devlog-course-evaluation-a1a6e2655581">previous post regarding course evaluation</a>.</p><p>I have a custom entity that stores evaluated courses and their grades. Let’s say we want to allow users to update the grade and credit value of any given course.</p><p>We first have a portal that displays the record’s associated courses in a simple table. Within that table we’ll have a form pop-up that passes through our required parameters to ensure that the clicked-on row gets updated.</p><h3>Building the Form</h3><p>The key when building your form is to choose the base that your entity is scoped to. My course evaluation entity is person-scoped so my form will also be person-scoped.</p><h4>Form Fields</h4><p>In order for the form to update the appropriate row, you’ll have to pass through the <strong><em>GUID of the person record and the GUID of the entity row</em></strong>. The rest of the form will depend on what you want to update within your entity. In our case, we want to update the course grade and credits.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/499/0*rKYw3-WDATP6Zy9K" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/946/0*jTREoaDO32rTVamJ" /><figcaption>Note: Be sure to check off ‘Allow Unsafe Import’ to allow for the changes to actually be saved! As with anything that involves record updating, be extra careful and make sure your form fields are mapped appropriately so that you don’t run into any unexpected changes.</figcaption></figure><p>After your form is built, you’ll need the embedded code to put into one of portal views that will be discussed in a later section.</p><h3>Portal Methods</h3><p>For this to work, we’ll need two separate portal methods. One will handle displaying all of the courses and the other will handle the form pop-up.</p><h3>Course List</h3><p>For our first query, we’ll start with our entity for our base and export out the different fields we want to display. Functionally, you’ll just need to make sure that you’re pulling the <strong>GUID of the course.</strong> This will be passed through as a parameter in our pop-up link. The rest of the query exports will be up to you.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*JoPjezYfyZaQ6zYo" /></figure><p>That last column will be where we’ll display the link to show our pop-up form. We’ll get back to that after we finishing setting up our portal methods.</p><h3>Form Pop-Up</h3><p>For the pop-up, we’ll need a second query that will pull in the relevant fields for the course that is actually clicked on.</p><p>To set this up, we’ll add in a custom parameter that will take in the course GUID from our course list query and use it in our filter.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/596/0*L92hlH4skNVw9tJx" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*rGwRVpX--PsvZ-db" /></figure><p>These exports will be passed through in our embedded form code so that the form pop-up will auto-fill with the clicked on course information.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/961/0*MDMRZ0hhSJvOmVGG" /></figure><p>This looks like a lot but all we’re doing here is taking the form field IDs and linking it with the exports from our pop-up query.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/950/0*OQh0dRU8RQYUlHJj" /><figcaption>Note: This is the same form we built above. I have the person and course ID as hidden fields.</figcaption></figure><h3>Pop-Up Link</h3><p>Now that our portal methods are set up, let’s go back to our course list view and add in our link for the form pop-up.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*qjSTP6FpnGo5EeCcmcjK1w.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/859/0*WOlz15a6aTK7q7b4" /></figure><p>The ‘onclick’ attribute is set to a baked in Slate function so you just have to worry about the ‘data-href’ part. Your CMD will be the action of your pop-up method and the second part is the @crs_id parameter we created in our Form Pop-Up query.</p><p>After that, your portal should now be ready to take in entity updates.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/0*XOUr_3Dr1CAZObve" /><figcaption>In this example, I changed the grade from a B to an A for the Bio Sci II course</figcaption></figure><h3>Closing</h3><p>If your staff and colleagues are already familiar with navigating Slate records, then I’d recommend sticking with using the built-in widget. However, as your processes in Slate get more elaborate, this is a good method to have in your back pocket that raises the ceiling on what you can offer other stakeholders.</p><p>In my experience, the main reason why I’ve needed to build this is so that the staff’s workflow isn’t disrupted. It’s much easier to provide an update if you’re working off a list instead of clicking into the Slate record, finding the appropriate tab, scrolling to the correct section, clicking on the entity row, and THEN updating. As I’ve joked about in previous posts, I consider my job to be minimizing the amount of clicking for users. This has been one of the key tools in achieving that.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=141196acaefa" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Slate DevLog: Managing Access within Portals]]></title>
            <link>https://medium.com/@ruel.gonzales23/slate-devlog-managing-access-within-portals-003a25f2e0d8?source=rss-acd9e779710b------2</link>
            <guid isPermaLink="false">https://medium.com/p/003a25f2e0d8</guid>
            <category><![CDATA[slate]]></category>
            <category><![CDATA[permission]]></category>
            <category><![CDATA[technolutions]]></category>
            <category><![CDATA[crm]]></category>
            <category><![CDATA[portal]]></category>
            <dc:creator><![CDATA[Ruel Gonzales]]></dc:creator>
            <pubDate>Wed, 28 Aug 2024 18:03:07 GMT</pubDate>
            <atom:updated>2024-08-28T18:03:07.777Z</atom:updated>
            <content:encoded><![CDATA[<h3>Slate DevLog: Managing Access Within Portals</h3><h3><strong>Use Case</strong></h3><p>I built out an athletics portal to process compliance. I wrote about the structure in one of my <a href="https://medium.com/@ruel.gonzales23/slate-athletics-compliance-portal-devlog-1-b9c3b277e2b3">first posts</a> but in short, student athletes need to be cleared by academic services, health services, and our athletic compliance officer in order to be eligible to compete. Each of these departments needed their own view to meet the requirements of their clearance process. They also generally work exclusive of each other so the views also need to be secure and only displayed to appropriate staff members. On top of that, coaches also need to see the status of their roster, but only for the teams they actually coach for. That’s all to say, access within a portal can get tricky so I thought it’d be helpful to talk about how I manage that.</p><h3><strong>User Roles</strong></h3><p>The easiest way to categorize and restrict access is to leverage Slate’s user roles. I ended up creating two:</p><p><em>Athletics-coaches</em></p><p><em>Athletics-staff (non-coaches)</em></p><p>Each of these grants access to the athlete portal but also determines which tabs are viewable. These are also used for certain access in Slate outside of the athletics compliance portal. I could’ve also made roles for health and academic services but they are relatively small teams who only access Slate through this portal, so I handle their access in a user query. My general rule of thumb is to only create roles for groups of users that have a set standard of permissions they need.</p><h3><strong>Portal User Query</strong></h3><p>I start with a user base and use the default @identity filter. This allows slate to know the user that is logged in.</p><p>Here, I have a bit more flexibility in what I can restrict. I create sub query existence exports that are based off of the username.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/602/0*xbzB3zQaldBHeDPr" /></figure><p>I then take this flag and restrict tabs in the view through liquid markup.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/766/0*AsS17ZN1oLiGqbQb" /></figure><p>This is also helpful when you need to go more granular with your access. For example, there are some assistant staff members that need view-only access. For them, we can just create a separate existence export for that. I use liquid markup to take out any form or pop-up links that can update the record.</p><h3><strong>Coach roster</strong></h3><p>Finally, I needed to ensure that coaches only see the roster of the teams that they are actually in charge of.</p><p>I could’ve just created a user role for each team but that permission list would’ve gotten very cumbersome. What I ended up doing was leveraging our sport prompt list. I took the usernames of each coach and plugged them into the export values of their respective team.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/756/0*Fupjou3IaDAGCx4c" /></figure><p>I then have a prompt query that will show any sports where there is a matched export value with the username.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/594/0*1oicuV7MvxPeMPRR" /></figure><p>In the portal, I have a JavaScript function that passes through the logged in user as the @coach_user parameter through the prompt query to determine which sports to show.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/663/0*Yhv8oIK-v2U951mY" /><figcaption>I use the FW.Lazy.Fetch function a lot in my portals to have more control over when and how a view loads. I talk about this in detail in my previous post about <a href="https://medium.com/@ruel.gonzales23/slate-dev-log-filtering-w-portals-543f44f297c1">filtering in portals</a>.</figcaption></figure><p>Here’s an example of the view of one of our coaches who is in charge of both the cross country and track &amp; field teams:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/888/0*ysGHL2vLlhnki47t" /></figure><p>And for comparison, here is a screenshot of what it looks like when a user has access to everything:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/545/0*qxsFBQBsMc7sVT3_" /></figure><h3><strong>Closing</strong></h3><p>I’ve built portals for other departments where having user roles was enough. There were also cases where the process was so nuanced that I set restrictions using only the user portal query. I chose to write about the athletics compliance portal because it’s a good combination of both approaches. As with anything I write about, you’ll have to assess for yourself what works best within your structure and what you’re most comfortable with.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=003a25f2e0d8" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>