RX 24 - Prepping for Obsidian - Daniel Hazelbaker
Transcribed Content
Alright. Welcome, everybody. The final session we have for today is prepping for Obsidian with, Daniel Hazelbaker. Let's give him a round of applause. Thank you.
So while we wait for the screen to switch over, I just want to set expectations that this is prepping for Obsidian. This is not a step by step of how to write Obsidian plug ins or anything like that. We will go into some stuff in the latter half of how you can modify some of your existing plug ins to be Obsidian ready, Obsidian native, but it will not be the whole thing yet. So if you get up and leave, you're not going to hurt my feelings. Click.
There we go. I said, my name is Daniel Hazelbaker. I am a software developer. I've been working at Triumph Tech with the Spark team for four or five years. Before that, I worked with and for churches for twenty years in IT and then even some audio video stuff prior to that with the churches.
So I have a lot of experience with Chiriquis and the kind of things they want and the unrealistic timeframes they want them in. So what is Obsidian? If you've been to some of the other developer classes over the last day or two, you've probably heard some of this again so we're going kind of go a little bit fast on the front half. But Obsidian is TypeScript with a little bit of Vue but it is not Vue. So we're utilizing Vue as a base of our library on top of it.
We will try to keep Vue up to date But at some point, they're gonna make a breaking change in Vue that we can't upgrade for, not because we don't want to, but because it'll break every single plug in. So we will keep it up to date as much as we can, but there will be come a time at some point, we just can't. And we'll either have to fork it and update that way or do something else if we need to. But this is the basic Obsidian framework. On top of that, we've built an entire UI library and logic library which really is what we would call Obsidian.
So it's all your person picker, data view picker, various other things, code JavaScript code classes to deal with dates and time so that you can deal with organization, date time versus browser, time and all sorts of stuff. So that is what Obsidian really is. It's a framework library that we have built based on other technologies or on top of other technologies. And we're coming from something called Webforms, ASP dot NET Framework Webforms, and we're going to Obsidian. So I wanted to give you a little idea of a little bit of why but a little bit of just kind of what we're coming from and the process we're doing.
A block in Rock is made up of kind of three things. We got the UI and layout, the logic of the block deciding like what to display and how to display it, and some more logic that's in a service which means it's shared amongst multiple blocks or multiple pages or at least it's intended to be shared. In Webforms, these blocks look kind of like this. It's just it's all intermixed. It's you got UI, logic and service layers all in the same file.
With Obsidian, we're trying to make it look like this. We're trying to make everything clean, separate and well designed so that it's easier to reuse stuff. One of the ways we're doing that is by further splitting that block into multiple pieces. So in this case, we have the campus detail OBS file, which is the Java browser JavaScript side file. The campus detail is the C file that runs on the server.
And then those shared pieces that should have been shared, we're trying to get those moved into like the campus service, person service, all these service layer functions so they can be used by all of our blocks as well as your blocks that you will eventually build. The reason for that is when we calculate full name to display it, it should be the same across every block. We shouldn't have sometimes it's using nickname, sometimes it's using full name. It should be a standardized thing. So how Obsidian blocks work?
If you're in Nick's session earlier, you saw this. Basically, the blue box blue boxes are C sharp code and the green boxes would represent browser logic or JavaScript. When the page loads on the server, it calls a c sharp method to get the data that'll be used to initialize the block. That's sent down in the page to the JavaScript where it will set everything up, do an initial paint of the screen, and then it just kind of sits there waiting for your user to do something just like today. When you load a workflow, it's not constantly processing in the background waiting for you to hit the submit button.
Once you do hit the submit button, you know, our on form submit would get called in JavaScript and it will call back to the server to a block action to do something with that data. It will return that data back to the JavaScript, update the screen, and then just again wait for the next thing to happen. How this happens? Those block actions are essentially API calls. You don't need to worry about all this.
This is wired up for you because you kind of have a little bit of understanding of what's happening. This is basically an API call that gets translated into a block action as seen in the C which is just a public method you create. You decorate it a certain way so that it knows it's valid to be called. That is partially a security mechanism so somebody on the browser who's trying to be nefarious can't call methods that you did not intend them to call. And in this case, we're just doing a simple error check, make sure the string's not invalid and then returning the length.
Obviously, this could be done in the browser, so it's like a contrived example, but it's meant to show how simple it is other than the logic that you need to do. So that's a little bit of the overview of Obsidian. There are some things you can learn now to be ready for Obsidian. One is node. And all of these are high level learning.
Like you don't need to go deep. You don't need to really understand what node is or how it works. But you should be familiar enough to go, Yeah, I know how to install Node. I went to their site. I saw how to install it.
Know, possibly things like check the version number, know what version number you're on. NPM. If you're familiar with C development, this is essentially NuGet for Node and JavaScript. Same kind of thing. You just, you know, know how to use the command line to install a package, remove a package, check versions, simple stuff.
You don't need to go real deep. .Net CLI tools. You'll probably want to get a little deeper here because they're very powerful. They give you a lot of functionality and a lot of automation of things. But again, you just you need to know how to run the basic commands.
Microsoft documentation is really excellent in showing what these do and when to use them. You want to get familiar with TypeScript. As I said, Obsidian is built on TypeScript. TypeScript basically is JavaScript with some extra error checking that happens at compile time rather than after you deploy it. And again, you don't need to go deep, but understand, you know, what a class is, how it defines a number versus a string, simple things like that.
View. Again, this is not view but we're building on top of it. If you go to the view guide, it's pretty massive. There's probably 40 topics on the left hand bar of their guide. You only need to read two of them, the Essentials and the Components in-depth.
That covers probably 95% of what we use in Vue. And that is basically the reactivity part of when you set a value, it'll automatically update the UI. It's essentially all we're doing with it. Code analyzers. This is less about something that you will need to learn but something that you should learn because the code analyzers that Microsoft and others have built are just phenomenal at telling you about errors before you even hit compile.
As you're writing the code, it's analyzing what you've done and will tell you when you've possibly made a mistake or something is not as optimal as it could be. Again, you don't need to go in-depth, but, you know, if you go to this link, they'll kind of give a list of a whole bunch of what they have and you can get an idea of what's out there and available. And then Visual Studio Code, all the Obsidian stuff is written in Visual Studio because it has the plugins that we need to basically do everything. So as a development environment, you have Visual Studio IDE, like twenty twenty two running to run Rock, and then Visual Studio Code would be running alongside of it to let you build and deploy the Obsidian files. Then Obsidian itself.
This is the documentation that we have for Obsidian. It is a work in progress. I did recently go through and try to mark a lot of the sections on what stuff that's really meant for the core team that we just kind of documented for ourselves so we don't forget it, and what's meant for general consumption and what's, hey, we know this is lacking. We plan to expand on it. You know, you don't need to tell us about this section.
It's already on our to do list. But this is a work in progress. It's probably 99% accurate, but you might find some things that we haven't updated yet that might be a little, not necessarily wrong, but outdated and maybe showing an older pattern for something. We will be keeping this updated and we'll probably eventually split into multiple books to cover, you know, probably more of the browser side type side of it and more of the server side and then how they interconnect. So what can you do now?
This is probably a little bit more of what anybody wants to know. There's two things you can do right now that I'll cover today of how you can update your existing plug ins or even if in the middle of writing plugins and want to just make sure you're doing it to follow the new pattern. The first is update REST actions. And this is a small update that you'll see in a moment. But basically to make them follow the new pattern that we're putting in Core to track the REST actions better, and then how to update your custom field types if you have any so that they will actually work in Obsidian.
Right now, if you have a custom field type in a plug in, I can guarantee you it will not work in any Obsidian block because there's no way for you to present the UI. There's no way to actually draw that field type on the screen. What you'll see here is how to resolve that so you can actually see your field type. So the first thing is the REST actions. Right now, we try to automatically track your REST actions internally.
So what that means is when you go to the REST controller security page so you can set security on actions, we're doing some magic on the back end that is becoming less magical as Rock grows. And we're starting to have issues where we have things that are conflicting with each other or if you make a change, all the security gets lost. So doing this will fix that. Adding these goods will not break anything currently, but it will keep it from breaking in the future if you ever need to make a change or and it'll prevent possible collisions. These need to be unique so you can't put the same good on multiple actions because they're similar that will cause problems.
The second thing you can do, and I would probably be surprised if anybody has this in their plug ins, But it's possible to have a single c sharp method that handles an API call, be called for multiple HTTP method verbs, so a GET and a POST. Don't do that anymore. That will cause a problem. Why? Because that method has a single good to identify it, but it needs two so that you can set security properly.
So in this case, what I would probably do in my plugin is end up with a get some method and a post some method and then a private method that actually has the the meat of what it's gonna do and just call that from the other two. That will solve all the problems as well as kinda keep it clean and let you easily, you know, track what's going on. So field types. These were fun. If you've built any field types, you know that there's a lot of Webforms code baked into them that you have to write.
Obviously, that doesn't work with Obsidian. And what we wanted to avoid was, hey, everybody, and our own field types on the core team, you know, please convert all your field types to this new UI format. And then in ten to fifteen years, because the UI format got updated or a new framework came along or we wanted you to add support for Rock Mobile or something, hey, everybody, please update your plugins again. Hey, Roy, please update your plugins again. We wanted to avoid that.
So we're basically setting these up so that you don't have control of the UI directly. Just like the server side of the block really doesn't have anything to do with the UI, it just provides the data to the UI. These universal field types would be that same pattern. You provide the data, you give us a number of hints for how it should be displayed and then the framework will handle the actual display and editing and most of the saving for you. So your as I said, your legacy field types won't work in Obsidian.
So if you have a field type and an admin who has installed your plug in goes to the campus entity and adds a new attribute with your field type, and then they go to the campus detail block, they're going say, where's my field type? Like, attribute isn't working correctly. And they flat out will not show in mobile apps at all, period. There's just no chance of that. Universal field types will show up in mobile coming soon, will work in Obsidian.
The UI is handled by you, which makes them far easier to build. The old field types had 65 or more properties and methods that you needed to override to get full functionality for viewing, editing, filtering, etcetera. The new ones have less than 10. So there's much less work for you to do. Code file is much smaller and they're, we believe, much simpler to build and then maintain down the road.
And here's me forgetting to click. Okay. So we have three basic field types that can be implemented in terms of the UI. And we looked at, at the time, because I think mid February, we looked at all the plugins, all the custom field types and tried to figure out like what options there were and we kind of boil it down to these three options with possibly a fourth that we're still talking about internally of is it feasible or not. You have item pickers which are basically single, not single.
You're picking an item of something. It might be an entity, it might be an enum value, it might be something arbitrary, but you're picking something. It can be displayed as a radio button if it's a single select, a checkbox if it's multi select, or as a single select dropdown or a multi select dropdown. So that is one field type base that you can start with. Another one on the left which kind of bleeds out a bit in the projector is your tree view.
So think like data view picker. You got your categories for your tree and then your items under it. That one is also single or multi select. Then on the right, you have a search field type. So the way you pick the item is by searching for it, you get a list which will look very similar to the person picker in all the Webforms blocks where you get your list back and then you can kind of scroll down or select it and move on.
So the item field type. Hopefully this code is big enough for you to see. If you're in back, you might need to scoot up a little bit. The slide deck, as far as I know, will be available after the conference. If not, hit me up on Rocketcat or something and I can get it to you.
But we're going kind of go through now how to build these. And hopefully it's not so much a step by step process, but you'd for you to see like, oh, that is pretty easy. That will not be hard to do. So this is the item picker, so your checkbox radio style inputs. You have an option to override a is multiple selection to determine is this a single select or a multi select item.
You can override a get display style. This lets you this lets you tell the UI if you want it to be a list of items, which is your radio list or your checkbox list or a I forgot what you called it, but the the drop down list style. By default, it will be in auto mode, which means the UI decides. We recommend sticking with auto mode if you can because that makes the most sense when you think of a web page versus a mobile app. The web page might go, Oh, you've got seven items, that'll be a nice list of check boxes, we'll just paint it that way.
The mobile app will go, You got seven items, I got really limited screen space, so let's do that as a drop down list instead. And then we have this method called getListItems. This is the list of items that can be selected. Get which is basically your edit values like when you're in edit mode, what options are there. GetItemBags would be essentially the view version of that, the read only view version.
It is passed the currently selected values and you return the text values that represent that essentially. So we're going to look at these methods. This is our get item bag. So this is our read view version. It's quite simple.
In this case, we're kind of pretending to be a campus picker to do this so we create our campus service. We have a simple where clause that says we want all of the campuses whose GUID value matches one of the selected values. We convert that to list item bag which you'll see all over RIP sitting. It's kind of our common thing where we just have a key value pair essentially and we return that as a list. That is all you have to do to make your field type actually display values once they're selected.
Everything else is handled by the framework and then by the UI. Get list items, you'll probably know it looks very similar because it is. All we did was remove the where clause. We just want all the items. In reality, you'll probably want some filtering to exclude inactive campuses or things like that.
But basically, you just want all the items possible. Again, convert them to a list item bag and return them to the caller. So that is all the code that we wrote. And I will tell you, I wrote those that code is copied and pasted from a field type that I built for this presentation, and that's all I had to write and it works. It generates the UI.
You're done. If you've had to build these before yourself, you know that there's a lot more code normally involved to do this. You have to create the pickers, you have to decide which one to do. So it's a lot more complex. So that's that first field type.
We're going to go to the search field type next. You would think a search would be more complex than trees but it's not. Search is actually simpler. Similar kind of setup. There's a couple of things you can override.
Starting at the bottom, is include inactive visible. Basically, it tells the UI if you want a include inactive checkbox. If you don't have inactive items, like that's not a thing, by default it returns false, you don't need to worry about it. Get image icon CSS class, that's basically the icon to put inside the picker when it's in the closed state. Are details always visible?
So normally, it's collapsed and you see just the name of the item. You click on it once, it expands to show all the details. You can say, No, we want it always to be expanded maybe because there's expected to be a lot of duplicate names and it's just going to make more sense to be expanded. Then we have get search URL moving up. This will return the URL that will be called by the UI and get item bags above that.
We're going to obviously the bottom three are kind of straightforward. I don't think I need to show an example of them. Get item bags, I'm also going to skip because the code is literally a copy and paste from the last field type. No change whatsoever because all we're doing is saying, here's the selected values. Please give me back the bags that represent them.
And here's our massive field type for the search field to say here's the URL to call to get me the data to perform the search. So we'll jump over to a REST controller. This is our very fancy widgets item controller. And obviously there's no body here yet, but you can see we're this is a post method. The UI is going to do a post to your endpoint.
And I will note here, since I forgot to mention it earlier, you'll notice the route is API slash v two. You're going to hear more about that down the road. You'll either want your API to be in this API v two namespace or you're going have to do a little bit of extra magic because you need to return the data in camel case. If you use API v two, it will do that for you. If you use just API, you're gonna have to do some work, and that's just because JavaScript is case sensitive and C sharp is not when it's decoding JSON.
Lovely little thing to deal with. So it's a post method. It's going to be passed this universal item search picker options bag. If you want to have nightmares, try to figure out naming conventions that work because it's not pleasant. You always second guess yourself.
And then we're going to jump now into the actual method to see what's inside. So we're going to start with a RockContext, again, CampusService. One of the things your options bag isn't contained is the search value. So it's just to be in value property. Then we're going to to list that which means we're pulling it into memory from the database, just the entire campus as is.
There's a reason for that you'll see in a second. We're going to do a select statement to convert it to something that the UI understands and then return that back to the UI. So inside our select statement is this. We're going to start with a universal item search picker item bag. Again, naming is hard.
Set a value because the raw value, that's what'll that value will be what's passed to your get item bags later to view it. Title, that's what you want that main title to be of the item that matches the search. Description is optional. You probably want to include something. If you're searching for a room, maybe it's what, you know, describing where the room is so you have a little more context.
Details and labels, we're going to expand on the next slide, but we're starting with empty lists of those. And then we return the item. So now we're going to expand to see what we put in that details and labels. So we're going to add one detail, one label. Our detail will be the campus status.
And this code right here is basically why we're doing that to list call twice because we can't do this in SQL so we have to bring it into memory first and then we can process it. So our value of status means that's basically the key of the field type name that we're going to display for the detail and the text is the value that's displayed with it. So in this case, it's going to be the status, display the campus status, we could be I believe active or inactive. And then we have labels. We'll look at where these are on the next screen.
Value is the bootstrap class name that kind of identifies the color. So info, success, warning, danger, primary, I think that might be it. If you try to throw some random thing in there, it's just not going to work. And then text is what gets displayed inside that little pill of a Bootstrap label. And that will look something like this.
So main campus is, you know, the title. This is the main campus for the organization, is our description. Below that is our details, status open. You can have as many details as you want. They can be as long as you want.
Please be realistic. Don't put full paragraphs in there because you'll annoy your user more than us. But you can have as many details as you want that you think are needed to help identify this item versus another similarly named item. Then in the top right where it says physical, those are our labels. We recommend one label, maybe two if they're kind of one word things because you don't want to queue up a lot of space and then have weird padding things going on.
We don't have anything that enforces this, so just, you know, use your best judgment, look at the data. If it's a user entered field that you're displaying, maybe consider truncating it to a certain number of characters so it doesn't get too wild. But that's our search field. That's all the code we had to write to have a functional search field. So that's, again, hope you see it pretty straightforward, simple, not a lot of work there.
You don't have to build a UI yourself anymore. Tree Item Picker, oddly enough, the hardest of the three. So is multiple selection, it can be single or multiple select again. We have our get item bags, which again, I'm not going to cover because it's the exact same thing from that first field type. It's just here's what's selected, please give me the items that match it and get root rest URL.
The reason this one is called root is in the future, we will probably have a way for you to say in the return data, this node of the tree has a different URL to call. Right now, we call the same one because if you're wondering why we use different method names for the URL, that's why. That method looks something like this, very similar, just here's the API to call. Again, it's a v two style API. And the REST controller will start by looking something like this.
There are basically two things that this endpoint is going to do. The first is it's going to figure out which nodes to auto expand. That's that get expand GUIDs call. The second thing it's going do is actually load the locations. In this case, we move to named locations because those inherently have a hierarchical structure.
Get expand goods, why is this needed? If you open a data view picker and something is already selected, it needs to auto expand to show you what's selected. You don't want the user having to try to figure out what's currently selected by expanding themselves. So when this is first called, your universal item tree picker options bag, naming is fun, will contain basically the list of what's currently selected. And then you need to convert that to how to get to that, which we'll see in a moment.
Once we have that, we just load the locations and just kind of loop through till we get all the locations. Our get expand items will look something like this to start with and then we'll expand that foreach. We're doing some basic error checking, make sure our expand to goods is not null and not going to cause an error. Then we're going to loop over each one and do something and then return a final list. Inside our foreach is this.
We load the location. Now, realistically, this case, use cache because you can and that will make it faster. But we're going to load the location and start with its parent. We're going to add that parent to our new list of things to be tracked and then move up to its parent and keep going in a loop until we get to the root. So we've taken item B that's nested down here and found that its parent is A which needs to be automatically expanded.
So then our load locations method will look something like this. We start with our basic query, which is going to first limit us to only named locations. That second where statement clause is a little more complex but it's pretty straightforward. We're basically saying if we have a parent good to start with, make sure we only load locations who have that as their parent. If we don't have a parent good, just load all the root locations that don't have a parent yet.
And then we'll loop over those and do something which we'll see next. That something is this. We're going to create a tree item bag. I wish all of our object names were this simple. And we're going to kind of do the same thing we did with the search field.
We're to set our value, our text which is basically the title that's displayed. I'm going to skip to line eight, is active. If you have active, inactive states, this will kind of lay set that so the UI can show I think it like dims the text a bit to show that it's an inactive thing. Is folder and has children. These are related but have subtle differences.
Is folder is set to true because every single named location can have children. So is folder is determining does this object or can this object possibly have children? Has children is does this option act or this object actually have children? Meaning, do I do I need to display that little triangle for them to click on it to try to expand it? Or can we hint to them already, oh, we know this doesn't have children already.
Then we simply check, does our expand GUIDs, the things that we need to auto expand include the node we're looking at? If so, load those items and stuff them in item dot children. If you leave children null, then it will automatically call your API again to say, hey, I need the children of this. If you make it an empty list, it's gonna know you've already loaded it. I don't need to do anything or you can actually put stuff in it.
This is all meant to be dynamic so as they click things, it's automatically loading. If you have a pretty static set but it does show up as a tree, you can certainly eager load everything, especially if it's in cache and it's going to be fast, you can eager load everything. If it might be a few thousand items, please don't eager load everything. So that's all the code we wrote. That's how it looks, which again is a little washed out so you can't see some of the subtleties.
But that's fairly simple code, gives us our tree view. So I mentioned earlier that you don't get control of the UI anymore. There's some pluses to that that I mentioned of it will work on any UI framework that we build. These will eventually work on mobile once we get that built in. We had this little annoying thing called a MAUI conversion that needed to get done.
Only took a few months longer. But you might be wondering if you've done field types yourself, okay, great, but how do I configure them? Like how do I paint my UI to configure these things so we can say or the admin can decide if they want to include inactive items or only physical campuses or whatever. Well, we came up with a way to do that. I'm a little embarrassed that it took us so long to realize we could do this.
But you know how when you write a block, can put some attributes above it to define some configuration options? You can do that with these new universal field types. So you just decorate just like you do a block what configuration options you want and the UI will automatically take care of it for you. The one piece that you lose over what you have now is you can't have dynamic configuration options. Meaning you can't say, well, once they turn on include inactive then show something else after that.
What you would do for now is just show them both and in your help text kind of call out, hey, this only applies if included and active is turned on. We have some ideas for ways to make that a little bit cleaner. We still need to kick it around and play with it but realistically, there's probably not a lot of places where you're really needing it. It's more of a convenience that makes it a little bit cleaner in the UI. But hopefully, this makes things very simple, very easy.
We've looked at some of our own field types that are seven, eight hundred lines of code and we could probably drop them to about 150. So we're looking forward to getting some of ours converted to this for simplicity and making it easier to maintain. So any questions?