
Looking to learn more about data modeling and back-end development using Brightspot? Start with this training, led by our world-class team of experts.
Click here to download a copy of the back-end training deck.
Transcript
0:00
The broadcast is now starting.
0:01
All attendees are in listen only mode.
0:07
Hello, everyone.
0:08
We will get started in a few minutes just to let any latecomers or I guess for a minute, early, few late come, come in, maybe like three minutes or so.
1:05
Hello, everyone and welcome to Brightspot back end training.
1:08
My name is Brennan and I will be guiding you through a tour of Brightspot as well as a sort of exercise for you to get your feet wet with working in Brightspot.
1:28
So we're gonna start off with a demo of the Brightspot CMS specifically looking at it through the lens of a developer.
1:37
Then I will talk a bit about some terminology with Brightspot and diary.
1:42
We'll review root style guide concepts which were covered in the front end training as well with Tom.
1:50
Then we will have an extended exercise.
1:52
We're gonna build out a content type and sort of the entire process for that.
2:01
And we'll get into some more advanced data modeling as well as a bit at the end graph QL and it sort of working with headless.
2:12
So we'll start with the demo here.
2:18
Everything we're gonna be doing today is in this training repository, which is publicly available.
2:23
So everyone should be able to clone this And follow along or refer back to it later.
2:30
If you want to see kind of what of everything we did.
2:33
So to start off here, in my terminal checked out that rebo And I'm going to start my, the Docker container.
2:47
I've already got a build here that I built earlier to save time.
2:55
So basically when you have a build, then the doctor will just automatically pick up that build and deploy it.
3:00
And I always recommend watching your logs just to see what's going on.
3:13
So once this kind of is done deploying, then we can actually go ahead and the local host C MS, Miss Docker has some data preloaded to kind of give you an example of what a real environment would look like that editors have been working in.
3:37
So there's a couple of content types already, in there and the data that's been published using those types and you could ignore these errors here from hunts spell, I'm running AM One Mac and there's not a hunts build for, the arm processor that it uses, but you won't see that error if you're running non M One Mac or on Windows.
4:05
And obviously, on your actual like Q A environments.
4:12
They'd be running a different processor as well.
4:15
So this box it makes you sign in, but there's it will basically any credentials you put in.
4:20
It'll just remember those and create an account for you automatically.
4:25
So let me in.
4:27
So when I first sign into Brightspot, I have what's called the dashboard and this is just sort of like the landing page when you sign in.
4:35
it's configurable.
4:36
So your own project could decide what widgets they want to have display here.
4:41
What you see here is the sort of the default that Brightspot provides.
4:48
So it's possible to write your own custom dashboard widgets as well.
4:54
We won't cover that in the training, but that is a possibility if you have certain requirements around needing to display things or make things easier for editors, you could write a widget that would, you could set up here and would display for the editors.
5:06
, a primary way that you find content is by going to the C MS, search up here at the top.
5:16
It's broken down with filters on the left hand side and then the actual results on the right hand side and then we can break this down by filtering like article and you'll see that the filters adjust based on the article content type.
5:31
So what's available.
5:31
So articles have a parent section as well.
5:35
As a tag.
5:38
And so depending on the content type, you'll see different filters there, those are kind of key back into what indexes are available on the content type.
5:44
So when we get to indexes later, that's gonna drive into this as well.
5:51
And a useful thing for developers is this advanced query here.
5:54
So it uses the same syntax, more or less that you would use in your java code when you're writing queries.
6:02
And so if a filter is not available, like displaying here, but you can still actually type in a query here to get sort of more fine grained filtering of your content.
6:12
And then finally, if an editor is, you know, looking for something and doesn't find it, they can just go right down here and create something like create a new gallery.
6:20
You have that same option by pushing the plus button here so you can create content without even having to open the search at all.
6:33
So when I click on a piece of content, it takes me to the content, edit page and this is basically what the editors would to actually upgrade and update the content.
6:47
So you can see there's a couple of main areas here.
6:50
We've got sort of the label at the top here.
6:52
We've got the main area with the fields that are broken into tabs as well as within the tabs that can be grouped into clusters.
7:04
And then we also have widgets.
7:06
So these are right here on the right hand side are all widgets.
7:10
There's also a widget at the bottom here and there can be widgets at the top sort of between the the label and the tabs.
7:22
So out of the box price but has several different types that it supports.
7:27
we can see here we've got a text field.
7:32
Oh This is a special text field because it's rich text.
7:34
You'll note that by the toolbar at the top here is a plain text field.
7:41
but it has a placeholder.
7:42
So there's sort of a default value that the editors can accept or they can overwrite it.
7:50
We have references to other content in the database.
7:53
In this case, this is going to be a reference to an author, content type and that will basically link the article to, you know, is being authored by that that author.
8:05
Then we have what's called an embedded field.
8:12
So this image here is actually another content type is an image lead.
8:18
You'll note that by the sort of like this drop down behavior where when I select it, I get a bunch of fields together and this is basically a separate java class that, that backs this.
8:32
But from the editor's perspective, it's just kind of a group of fields that are that are kind of grouped together.
8:36
It's a little bit like a cluster where we have a bunch of fields that are grouped together.
8:39
But in the code, it's different.
8:41
And so there's sort of developer reasons why you might do this as well as editorial reasons why you might want to group the fields within here, we have an image field.
8:54
This is actually a reference to an image in the database and we have a placeholder image that displays here.
9:00
So you can see what it looks like.
9:02
And I can then click in to edit it and actually pull up a sort of a pop up with the content, edit page for that image.
9:17
And so here we actually have what's called a storage item field.
9:20
So this is a reference to an asset in storage like three or Google cloud storage depending on what your project is using.
9:30
And for images specifically, we have sort of an extra editorial So you can come in here and manipulate what I this a new tab.
9:47
Yeah, not sure why that pop up is not working.
9:57
There we go.
10:00
The there is an edit U I where you can kind of, you know, add some changes to it and some filters, whatever as well as manipulate the crop sizes.
10:10
So an image might be used in many different contexts and it will have a different size in each one.
10:17
And so the editors can actually control how it should look.
10:22
And, you know, can sort of break it down if you only want to highlight this one part of it or keep the default and the default is gonna be based on some sort of logic, but you can manipulate that logic by putting a place what's called a focus point.
10:41
So we can say this is the most important part of the image and then all the crops will kind of be centered around that.
10:46
And that's a little bit easier than like manipulating.
10:48
You saw how many crops they're, we're in here.
10:51
You know, a few dozen probably that a faster way of doing it.
11:03
Another field that has some special rendering is dates.
11:12
If I create a new article here, I can set up an embargo which will be, you know, until sometime I won't be allowed to be published until.
11:23
And so I get a date field here and this allows you the editor to, you know, select a date as well as the time.
11:36
We talked about placeholders, there's also notes.
11:42
Oh This, this thing keeps popping up here as I had an unsaved change.
11:45
So I kind of typed in the value here but it didn't save it.
11:50
And so Brightspot kind of saves that and, and restores that back when I come back to this page.
11:55
So my its aren't lost.
11:57
But in this case, I don't really care about it so I can clear it out.
12:05
So notes trying to find here's a note here.
12:09
So basically, it's just some text that displays above the field that allows you to kind of explain to the editors how it's gonna work or give some.
12:17
This case is actually gonna be a preview of what the the text for that co which will show up in like the browser or toolbar.
12:24
will be this is another rich text field, but you can see here, it's got a different toolbar in this.
12:32
So there's different kinds of tool bars that you can configure in that world and control what the editors can do with that field.
12:39
So this is sort of our large toolbar, which has sort of the kitchen sink of all the options.
12:45
And you can see here within the rich text we have what's called an enhancement which basically is sort of structured data within the rich text that you can then edit as well.
12:56
So in this case, it's a video.
12:57
So this is a reference to a video as well as the ability to kind of set the title and description and make some images that will replace the defaults or a quote.
13:09
And so these couldn't have their own kind of rendering and their own.
13:14
behavior on the front end because we can kind of manipulate how they're gonna render.
13:21
This is another reference field to another object in the database.
13:25
In this case, it's a section.
13:28
And here we have sort of a different look, these, these ones, the authors above and this section and tags here are sort of multi select so I can have multiple sec secondary sections.
13:40
This is part of but I can only have a single parent section.
13:44
And so that's why this is only a single drop down with no like plus button.
13:50
And with all of these kind of reference fields down here, we have these references as well as up here.
13:55
You also get the option to do like a full C MS search kind of similar to the one we had up top as well as an edit pop up to sort of work on that content directly without having to like open a new tab or anything.
14:15
And finally, for the widgets, Brightspot comes with a bunch of widgets by default.
14:22
And we actually talk about how you can have some control over what widgets will display.
14:26
You can see here, there's like a URL S widget, the sites widget revisions as well as this conversation widget.
14:33
And again, you can kind of write your own widgets as well.
14:40
With we're not gonna cover in this in this training, but you would be able to write your own widgets and then control what content types they display on and, and what, logic, you know, maybe only certain, articles would have a widget display or something like that.
14:57
And then as well, it's the, it's pretty often common for editors to work with the preview open here.
15:03
What's just hitting this, sort of eyeball icon here and this is a, it's a little bigger for you.
15:13
So as I type here on the left live updates, so you can see exactly what the content will look like as you're working on it.
15:25
And that's gonna be very important later on when we actually are doing our coding because you know, when I create a brand new article, yeah, I haven't filled anything in yet.
15:37
You can see where this headline is required.
15:39
But I still want it to render without me typing in the headline.
15:42
So, you know, I need to deal with that headline being missing even if I haven't typed it in yet.
15:52
So that's something to consider when you're writing your writing your code in the back end.
16:01
Moving on in this hamburger menu up here, we have admin area and sites and settings is a an area underneath that and this is where you can put settings.
16:19
So Brightspot breaks content down into what we call a site which is basically a bunch of content that lives under the same URL.
16:27
So in this case, where they have the inspired content site and the URL is gonna be the local host.
16:34
So every piece of content that's within that site will have a URL, you know, local host slash something.
16:42
And so on that site, we have a bunch of settings we can attach to it.
16:46
And so they'll be active when someone is viewing that site.
16:49
So for example, we can control the navigation for like for the header or the footer or the search link, you know, the search box, things like that.
17:04
So it's possible for you to inject additional settings onto the site that would be for custom functionality that you're building, they could have control if they display on the tab and what cluster and whatnot.
17:19
And we also have something called global settings which apply to all sites, not just any specific site.
17:26
And you'll see there are some similar fields in here.
17:31
It's like we can have a sort of global navigation and so some of these fields have a sort of fallback behavior where if you didn't set it up on the site, it will fall back to the global value.
17:53
I think that's basically everything for the tour here.
18:04
Yeah, we got everything that moving on.
18:09
So now you might hear the terms Brightspot and Dari mentioned right, but obviously has a lot of different meanings.
18:20
But in our case, we're talking about it being the actual C MS application and sort of the code that backs that.
18:26
So that would be the editorial uiux that we just looked at the request handling of view system which, you know, takes requests and, and responds with, you know, using your rendering logic to respond with html or, or whatever you're serving.
18:43
It includes things for managing content like permissions, workflows, notifications and a lot of other stuff as well.
18:51
is a framework that powers Brightspot.
18:54
So it's sort of a lower level thing, it has things like database abstractions.
18:59
It actually enables the data modeling that we use with using java types and annotations and then lots of API S for querying for storing and retrieving files and, and like S3 or Google cloud, managing tasks and so on.
19:17
So oftentimes we just use a Brightspot to kind of mean the entire ecosystem including both of these.
19:21
And sometimes we kind of be more specific and say something it's coming from Brightspot or do.
19:31
So the roots style guy, I think we talked, we talked a bit about yesterday in the front end training.
19:37
It's sort of the glue between the back end and the front end.
19:41
So it originally lived in the project root, which is where the name comes from.
19:46
But typically in a newer projects, we're gonna put it in the front end slash style guide directory.
19:51
Although depending on how old your project is, you might have it in the root directory and style guide is kind of an overloaded term.
19:58
We use it for a lot of different things.
19:59
So don't confuse it with the style guide directory that is within a theme or the bundle.
20:05
Those each have their own style guide directories and there's also the star guide application, which is the tool that the front end developers use to code up, you know, their javascript styles, templates and whatnot.
20:23
So yeah, I just don't get roots.
20:24
So that is a very specific term.
20:28
So what it actually does is specifies views which are collections of fields that each have types, specific types.
20:35
So it's kind of like a strongly typed program.
20:42
Each view consists of a Json file and a handlebar's file.
20:46
The handlebars file only exists due to legacy purposes and should always be empty.
20:51
But the name of the handlebars file is important is that will actually be the name of the view.
20:56
The JSON file name should match the handlebars file name, but it doesn't really matter on its own.
21:04
And it also have, we have something called groups which allow fields to support multiple types.
21:09
So you might have a media field that can be an image or video.
21:12
And so you could create a group for that and specify that image and video, both count as the media group or whatever.
21:20
And the end result of all this is each view gets turned into a java interface that's built automatically and kept in sync with the roots style guide files.
21:32
So as you change things into the style guide the java interface changes and then your implementation of that interface would have to change as well.
21:42
Otherwise your bill would break.
21:45
So that's how we keep things in sync with the front end separately.
21:50
The front end have there is a task that basically make sure that as they're working in their bundle, that any changes they make in there are compatible with the dog item.
22:00
So that's how they stay in sync.
22:05
So things to keep in mind with the roots style guide is it's, it was built for use with handlebars, which is, you know, traditional rendering stack.
22:13
And so everything in there isn't intended for that purpose.
22:16
So if you're using graph QL or you know API S of some sort doing your headless, you probably shouldn't use the root star guide because it's gonna create interfaces and classes that are just gonna create kind of a a ungainly api in, in graft, all for example.
22:37
But again, every product is unique and, and so depending on your circumstances, it may make sense to use the handlebars views for both like handlebars and graph QL depending on what you're doing.
22:49
But you shouldn't come in expecting to be able to do that and we'll talk about that more when we get to graph QL at the end.
22:59
So from the rest of our, our time here, we're gonna be going through an extended exercise, building out a recipe content type.
23:08
But before we do that, let me give you a quick tour of the code base just to kind of get a sense of where things go.
23:15
So I'm checked out the project here.
23:20
And sort of this route directory is where everything's gonna live.
23:25
We use to build the project, so you'll see there's a settings do rail file.
23:31
This is where you would control things like the version of Brightspot as well as some other dependencies in your Java version.
23:42
And obviously the standard Gradel set up as well as in here.
23:47
So the project is broken into multiple Gradel projects.
23:52
The primary one from the back end perspective is the core project that is where all of your back end classes will live.
24:03
So under the source main Java, here's all of our back end classes.
24:09
And then the Build Dogra is where you would go to update any, you know, third party dependencies you need or whatnot.
24:18
Then the front end project is what builds The Roots Do guide.
24:22
So this here is the Roots do guide directory.
24:24
We can see here, we've got, you know, bars and JSON files in here as well as here's those groups I was talking about.
24:33
And then as well as under the front and director, we have the bundles.
24:36
So a bundle is basically the templates, styles javascript and whatnot to that all kind of hangs together and you would then publish in your site as theme, some kind of bundle and theme or two terms that are kind of used interchangeably.
24:54
So that's where all the front end development happens under there.
24:58
You can have multiple bundles and then basically like per site, you could then choose which bundles.
25:03
So you'd have different sites that have different looks because they have different bundles programmed for their theme.
25:12
And then finally, the web project is what builds the actual war file that you would then deploy to Docker or one of your servers.
25:23
And so basically this is this is you wouldn't put any java code in here.
25:28
As a normal rule, you would put it all in the core.
25:30
The web is primarily for just assembling all different dependencies into a war file.
25:41
And then we've got, we do for local development so you can test everything and that's configured with the the Docker compose here.
25:48
So be Yeah.
25:56
And then, this is the same, but we, in the, we've got sort of some basic set of instructions for what dependencies you need.
26:06
so if you wanna again, check this out locally, it should be everything that you need to do.
26:11
, so let's get on to the exercise now.
26:18
So we have some high level requirements.
26:21
we want editors to be able to create recipes, search for recipes, future recipes and articles and then tag recipes.
26:30
So then we need to translate those into actual concrete development steps in Brightspot.
26:36
So we're gonna need a standalone recipe content type so the editor can create and search for it.
26:42
We're gonna create a recipe article content type.
26:45
And that will then reference an existing recipe.
26:49
And there's a couple of reasons why we're gonna break it down like that, partly because later on with graph fuel, we're gonna be on, we wanna be able to search recipes directly and not really deal with the articles at all.
27:01
And then we're gonna play a recipe module content type that will again point to a recipe and this is just going to interface with the Brightspots module system which I'll discuss later.
27:11
And then finally, we're going to create a special tag just for recipes.
27:15
We could also use the existing built in Brightspot tag, but by using recipe tag, we can kind of keep our recipe stuff, separate from the rest of the site and then refer recipes will then reference those tag, recipe tags.
27:31
Again, all the code is going to be in this, training wipo, there's a branch called, exercise slash recipe.
27:41
So you should be able to follow along.
27:43
basically just I know 10 commits on there and we just kind of go through all of them and they show all the steps.
27:51
So first step is going to be the root style guide.
27:56
And the reason for this is because this is the glue that kind of binds the back end and the front end together.
28:00
So it makes sense to start there and once this is done then the back end in front can mostly just work independently and in parallel.
28:10
So we're going to have two views that we're gonna have build recipe module and the recipe article page and the recipe module is not gonna be exactly the same as the recipe module in the back end content type.
28:26
The recipe module view is gonna be a bit broader than that.
28:29
It's basically what you would think of as a recipe.
28:30
So it'll just be the the template that you would need to have, you know, ingredients and the instructions and some pictures and whatnot.
28:39
And if we want to use it with the module system, we need to add it to the module group so that it can be used anywhere that Brightspot modules can go.
28:48
So with that, I will go to my terminal here, check out our first commits here.
29:00
So we can say we can start on develop.
29:02
We're just gonna start stepping down the exercise recipe branch and look at each commit and turn.
29:07
So our first commit here, we're adding to these specifications.
29:11
So if I go here to the front end in the root style guide, we've got a new directory here called recipe and we've got four new files.
29:26
So the first one here is a recipe module dot J.
29:31
So a JSON file in the roots style guide is going to describe the view.
29:35
So it's an object and the keys of the object represent the names of the field and the view and then the values of those keys represent the type of that field.
29:46
So for example, the title field here is going to be if you have an object, it's basically gonna be like a reference to some other view.
29:58
In this case, we're, we're including a group.
30:01
So we have a group called rich text elements timing, which is basically a small subset of all the possible rich text elements that we support.
30:11
So we this will then correspond to like on the back end, we'll have like a, a rich text deal with a specific tool bar that would line up with these elements here.
30:22
The image is going to be a image template.
30:28
So there'll be some other view basically that would then be sort of referred to here and that will then go off to render itself independently.
30:39
Got several string fields, difficulty, prep time and extra prep time, cook time, total time.
30:46
And then some more rich text fields at the bottom.
30:47
But these are using different subsets of the rich text.
30:52
So small includes maybe things like lists and large would include everything.
30:59
And you'll see here, a couple of these keys have underscores those are special keys.
31:03
They don't, they don't correspond to a field in the view.
31:05
So the first one here is that is the actual template that this JSON file goes to.
31:09
So this is pointing at that handlebars file here, which is just empty because any markup is going to be in the bundle.
31:16
So this file doesn't need to contain anything because it's not gonna be ever used for anything.
31:21
But again, it does have to exist for legacy purposes.
31:24
So if I didn't create this file, I would get a build because it would say, you know, I can't find this file.
31:30
And this value here, you know, excluding the the suffix is going to be the name of the java interface that gets generated from this file.
31:38
So this name is important and there's a convention you should name the Jason, the same as the Hannah Bars just so that it's obvious that they belong together.
31:49
The includes here basically are gonna copy in another JSON file.
31:57
So this case, we're pulling one of the JSON files out of the group and then the JSON files in the group are special.
32:03
They're actually, if you actually open this file here, she'll do the small one.
32:08
It's a better example.
32:11
These are arrays rather than objects, which means it's any one of the objects inside here.
32:17
So we can support string text as well as link enhancement, text substitution.
32:28
So any of those three count as a rich text element small.
32:31
And so we have all three of those options in our field here.
32:37
And then the key here is actually just again, this is sort of a legacy a bit.
32:40
It's which of those it basically just tells the system that you understand this is a, an array and that you want it to be an array.
32:50
because letting, by default, it's gonna interpret it's gonna expect an object.
32:54
And so in certain situations, you can kind of get into a weird state with like whether you, whether you're trying to include.
32:59
So by putting the key here, you're just telling the system that this is an array and I expect it to be an array, but the value here doesn't matter.
33:07
All of the, all the keys all the objects in that array will be valid types for this field.
33:16
The rest of the article page is a little bit different because as a page it's gonna stand alone whereas the recipe module is gonna be just part of a larger web page.
33:29
And that's what this first key here means.
33:32
So this is gonna look a little bit complicated and is gonna play into the preview stuff we talk about a little bit in the C MS.
33:38
So by the expects these views to be like modular, some kind of small piece that was set on a larger web page.
33:48
And so it's gonna wrap it in a larger web page for purposes of previewing the C MS.
33:54
But for things that stand alone like web, you know, article page is gonna have its own html element and provide all that stuff that it needs for it to be a full blown web page.
34:05
We don't want it to be wrapped in anything because then we'll have invalid html.
34:09
And so we need to pass in underscore wrapper False to tell Brightspot, don't wrap this up in something else.
34:15
It's gonna stand alone.
34:18
So whenever you see underscore Rapper False, then you will know that it will be a stand on a web page and it needs to provide its own html and your head and body and all that kind of stuff Here's another include.
34:37
So this is how Brightspot does sort of like view inheritance.
34:41
So there's a view called Creative Work page provides things like authors and whatnot, you know, bread crumbs, things like that.
34:50
And so, by including that, we're gonna extend that and then add, add additional fields on top.
34:58
So that's how we can reuse code.
34:59
And on the, on the Java side, this will actually result in our view interface, extending the creative work page one.
35:09
Again, we've got to still specify our template.
35:14
And then we've got three fields here using the lead, who's gonna use the leads group, we've got rich text for the article body.
35:22
And then here finally is our reference to our recipe module.
35:25
So the recipe article will have a recipe module on it and that's what this field is going to supply.
35:30
And so this is just pointing the template to the one we just created.
35:38
So once you have built or made any changes in the roots style guide, to actually see the job interfaces, you need to do a a build either of the whole project or of just the front end project.
35:56
And that should be pretty quick depending on you know how big your roots style guide is.
36:03
But with that done now, I should be able to go in here and see the rest of your module view interface.
36:15
So you can see here.
36:17
It's a java interface.
36:19
We'll talk about this extends in a little bit, a couple of important annotations on it.
36:26
This tells Brightspot that these basically all the methods, public methods and well they all public in an interface.
36:34
But all the methods in this class are valid to expose to handlebars.
36:39
Or if we're doing like a graphical API those are valid to expose in the API and then the view template handlebar's template tell, writes about where the template lives.
36:50
So in this case, the handlebars template is the recipe model one, these are a little bit redundant.
36:56
But the dad's both and then within the interface here, we've got all of our methods will then correspond to the fields in that JSON file.
37:13
And you can see here in the, in the Java Dock, it says this came from the recipe module, Json line 14 column 13.
37:20
And if you go find that, what was that?
37:29
What did I say?
37:34
I do that?
37:35
How close that line four teams is the cook time theory of the cook time.
37:49
And that was a string and we thought it was a chart sequence, difficulty similar then here the directions was a large Ritz text field, I believe.
38:07
Yeah.
38:08
So now this is actually an iterable of this interface here.
38:19
And you can see here in the Java Doc basically what Brightspot did is for every field you had in that JSON it created a java interface to mark all the things that could put in that field.
38:31
So in this case, we have this what we call a marker interface or field interface.
38:37
And then there's always an implementations of it which correspond to all the things that can go into that slot.
38:44
So I guess we looked at the we looked at the the small field of was that one that to inside finger?
38:57
Yeah.
38:58
So the ingredients was only text that was the string in the in the group file, we had tax substitution and we had a link enhancement.
39:09
So those three kind of get bundled underneath this and that's how everything stays in sync.
39:19
time.
39:20
The image here was an image view and this is a little bit maybe a little bit confusing.
39:25
So image we said was the image template but you'll see here in the view interface is actually still an iterable.
39:32
And basically what Brightspot is doing here is just for generalization.
39:36
It's just saying everything can be zero or more rather than saying like image is only one whereas like the directions is multiple just for simplicity, everything kind of gets changed into iterable.
39:51
But in this case, we would still only expect there to be one image.
40:07
Yeah.
40:08
It's just a question in the chat here.
40:10
Basically, yeah, these interfaces have a direct correspondence to the JSON files in the boots do end and then whenever you build either specifically the front end project or the project as a whole, the the interfaces will update based on any changes that you made to these JSON files that kind of stay in sync together.
40:32
So yes, that is correct.
40:35
So our next step is we talked about the module, the recipe module to work with a Brightspot modules kind of anywhere.
40:45
The Brightspot supports them.
40:47
So we actually have to add our rece module to the modules group.
40:53
So if we go up here in the roots style guide to the group directory, we have the modules dot JSON.
41:00
So modules in by are just some kind of self contained thing where you can place, you know, in an arbitrary place on the on the website.
41:11
And so you can see there's all kinds of different things that we support like containers, various kinds of lists, carousels, fa Qs and so on.
41:21
So we add it in here our where is it recipe module?
41:29
And so you remember this is an array.
41:31
So basically any of the types inside of this count as this, you know, valid for this group.
41:37
So in case we're saying the recipe module template is a valid module.
41:43
And then for like leads, we can say that the page heading is a valid lead.
41:47
That's basically what this is saying.
41:54
That's it for the root style game.
41:59
And then, so next step is going to be it doesn't have to be in this order, but we're gonna just apply the bundle work as a single commit.
42:08
And then just move on so that we have it in place.
42:11
But in your loan work, you might actually be working before the front end has been done.
42:16
And so you would need to work without that in place.
42:20
I'll talk a little bit about how you can do some testing even without the front end being in place.
42:29
So just check that out.
42:37
I will do a quick bill just to get that going.
42:41
Great old cash thing.
42:43
So in my local here, I've already built this project and so everything is probably gonna come out of cash, but as you build locally for the first time, it'll probably take a little longer.
42:50
The bundle build is typically the longest because it has to process, you know, all the, all the, all the Omar templates, all the javascript styles and compile all this together.
43:02
So usually depending on how big your your bundle is that can take potentially several minutes again based on how fast your computer is.
43:12
But then once it's built as a back end developer, if you're not making any changes to it it should just come out of the great cache.
43:19
So successive bills should be a lot faster.
43:21
So in that case, it was pretty quick.
43:28
So moving on data modeling, some basics.
43:34
So every content type that you want to save to the database in Brightspot needs to extend the record class which comes from Dari.
43:41
And that is what enables Dari to actually serialize the data in your class and then save it to the database and then retrieve it back out and put it back into a class when you know you query for it in the future.
43:54
But depending on your use case, you might extend the class content instead which comes from Brightspot and it kind of adds some capabilities on top of record, the full text search some additional widgets in the right hand side on the content, edit page and then as well as A U I changes, I think the the there was a save button that changes to publish.
44:14
There might be some a couple of other things as well as a rule of thumb.
44:19
Anything which an editor should be able to independently create by just like going to that, you know, create, drop down and the search should extend content.
44:29
It's not uncommon to want full text search, but you don't want it to be something the editors can work with directly.
44:36
If you don't want it to show up in the Brightspot search.
44:40
So you can add the searchable annotation as well as the exclude from global search annotation to your record class.
44:46
And then, you know, you'll be able to get achieve that.
44:50
So maybe like 10% 20% of classes, right, less than that, you might want to do something like that.
44:59
And then the thing to keep in mind when you're doing your data modeling is the editorial experience is paramount.
45:04
So, you know, you're gonna write this code and then be done with it.
45:07
Whereas an editor is gonna then be using that content you created for years every day, potentially.
45:13
And so you wanna make common action as easy as possible, organize things, you know, move fields that are less important, less gonna less commonly used into a different tab or hide them in a cluster so that they don't clutter things up.
45:27
You can add placeholders and notes to clarify what things do and tell their editors kind of how things are gonna work, hide things that they're not gonna need and then ensure that that label that displays at the top is useful.
45:41
That's also what displays in search.
45:44
And if you're looking for like a, you know, trying to do a reference to something it's in that dropdown, You, it's all the same label.
45:51
So make that useful for the editors and another rule of thumb if you have an internal name field.
45:57
You should probably use it as a label because then the editors can use that to control exactly what will display.
46:04
You also want to think about what should be embedded, what should be not embedded and directly saved in the database.
46:10
What should be searchable, full text search.
46:15
And then any additional needs you have for global setting site settings or singletons, we'll talk about singletons later.
46:27
So then the data models, here's our first commit.
46:42
So let me do a bill again.
46:47
I'm gonna skip test for save time and I wanna get this building so that will deploy to my docker while we're talking and then we can look at it without having to wait because the docker deploy usually takes a little while.
47:01
So we're doing back end code now.
47:03
So we're gonna be in the source in the core directory.
47:06
So we created a class or a package called Brightspot dot Recipe.
47:13
And here we've got some new classes.
47:19
So first class here is recipe.
47:23
Remember we said that editors can create recipes directly.
47:26
So we were gonna extend content.
47:31
The fields in the class will directly correspond to fields in the bright in the Brightspot U I.
47:41
So for instance, here we've got a tile field, it's gonna be a string and then more specifically, it's gonna be a rich text using the tiny rich text toolbar.
47:51
we've also got some other annotations controlling the behavior of this field.
47:55
So it's gonna be required and it's gonna be indexed.
48:02
We'll talk more about what index means later on.
48:04
Basically, it just means you can, you can search for a recipe by the title in the database.
48:11
Some other fields have got an internal name.
48:13
This is another string but it's just plain text, not RTZ text.
48:18
Difficulty is an enum.
48:21
So though this is gonna result in a dropdown so they can choose easy, intermediate or expert.
48:34
So our rich text fields using different toolbars, you'll note here this in line false.
48:39
This is an important thing to understand and rich text elements can be in line or block level.
48:46
So block level might be like an image or something and in line would be something like a or something that's gonna be like mark up on a specific you know, section of text.
48:56
So like a link would be in line because you can select some text to make that a, a link.
49:04
And so by default, my spot assumes that rich text fields are in line.
49:10
So it will exclude all the black level elements.
49:15
When you want those for like directions, for instance, you might want to put some images.
49:19
So you can see them what the image recipe, you know, steps look like we want in line fall.
49:23
So that then we can get things like image and you know, whatever other options are available and we're using here, the largest toolbar we have a toolbar is this an interface that Brightspot provides and you basically just return a list of all the different elements that you want to support.
49:39
And there's, you can see there's a ton of them.
49:42
So things like bold tag underlying, you know, order on order lists, things for editors to, you know, editorial workflow, things like tracking the changes and putting comments in then undo and redo things like that.
49:59
So, you know, these all just correspond to different buttons that display in the in the toolbar image here is going to be a reference to another image in the database.
50:15
And it might not be like totally clear, you can determine that you have to inspect into the web image.
50:19
And you can see that this extends content and doesn't have the embedded annotation on it.
50:31
And we don't have, we don't have the embedded an application here either like that, which means that this is gonna be a reference to a web image that's already in the database.
50:39
So the editors will have like a search pop up so they can go use to find the image that they want.
50:46
recipe tagged.
50:48
is a new kind of type we look at in the set.
50:51
But again, this is gonna be a reference to a recipe tag or in this case, multiple refer, you know, it can support zero or more recipe tag references due to this being a list and we've got some integers.
51:05
So we can select the time, you know, it takes 15 minutes to prep and 20 minutes to cook or whatever.
51:12
And then finally, we wanted to have a total time.
51:18
and something we want to actually have is like this be override and it'll fall back to just the sum of these three.
51:24
So the editors don't have to do the math themselves if it's gonna be the same as just this, this sum.
51:31
So after our fields, we got all our gutters and sets, these are just kind of plain vanilla tova gutters and sets one thing to note is, you should never return null from a collection getter.
51:43
So this map set instead, you should, if the field is null, you know, set it to an empty collection and then return that empty collection.
51:54
And the reason for this step is you could just do like this and not have this.
52:01
But now if I ever like add things to that list, I might call, get recipe tags and start adding things to it.
52:06
If I do this, all those editions will be lost.
52:10
So if I do it this way, now they're linked.
52:12
And so any time I return the list, I can then edit it, manipulate it and then all that stuff will, will persist back to this recipe.
52:22
So I recommend setting up your every any ID you're using should be able to support, get our setter templates and they should be able to set these up if they're not already.
52:34
So you don't have to think about this every time you create something.
52:38
So after our gets and sets, we have our API methods in this case is just the total time.
52:44
So this is gonna calculate the sum of those values as a fallback or use the override value if the editors programmed it.
52:53
So here we're just using some logic to, you know, get the total total time override if it's not mole otherwise, you know, calculate the fallback and the fallback itself.
53:05
We have to deal with because these are capital I integer fields.
53:10
So they could be null as a value.
53:12
So we need to handle that.
53:14
And only some, the non null values we could also have used lower case is which means the default value would be zero, which maybe would have been a fine choice in this case.
53:24
But typically, I usually just do null for you know, capital I with no is the default value.
53:36
One thing I will note though is a Brightspot provides, supports both capital B balloon and lower case B bullying and I recommend always using the lowercase B bullying because that's when you can end up with weird situations where you can get like a three way logic where it's true, false or null and have to deal with all three or whereas this is only true or false.
53:59
So it's a little easier to understand.
54:05
I take a look, a quick look at recipe tag as well.
54:09
Again, we're extending content because the editors are gonna create these on their own.
54:13
And this is pretty simple.
54:14
We just have a name and internal name.
54:18
And what's the sort of some orientations that I looked at index is important because we want to be able to find these by name.
54:27
So I already built those changes and you can see here, the doctor has sort of picked up that the war file changed automatically and started deploying.
54:38
It might have to, I let it finish booting up.
54:51
Here's that hunt a again, you can ignore.
54:57
So yeah, now that we've built that we should have here a recipe and recipe tag.
55:03
So they create a new recipe.
55:05
We can see it as a direct correspondence between the fields and the Java class of the title we said was a mixed text field with a small tool bar and was required.
55:16
So there's, you can see it's required.
55:18
Internal name was plain text difficulty was that, you know, dropped down greetings and directions for rich text with larger toolbars image is a reference to an image in the database.
55:32
So here I got my search to find one recipe tags.
55:37
I've actually already published some data in this environment.
55:39
So it kind of just shows up again.
55:42
Once I deploy the correct code, you might have noticed earlier when I was in the search that all of these said untitled and didn't know the type because I didn't have the right code in place.
55:51
But now, all these recipes I've published previously are showing up again.
55:59
If you want to see those in your local m publish those for, you know, just training purposes basically, but you should be able to publish your own.
56:09
And finally, at the end here, we've got our times, they used to be a little bit confusing because they look like text fields.
56:14
But supply like, you know, if I do that for instance, now I'm gonna get some errors.
56:26
So for instance, you required, I need to provide a title and then here it's saying that this is not a valid integer.
56:35
So I need to put some something correct in there too.
56:42
Supply.
56:43
So now we've, you can notice that there's some, some things we could do to improve this.
56:47
We said earlier that if there's an internal name that should be the written label, obviously, it's not doing that.
56:54
Currently, we haven't even typed into value here.
56:57
It's actually just by default, it will by we find the first text field and use that as the label and which can actually be somewhat undesired.
57:06
If we do like it in here, it actually will give us the raw like markup value rather than, and just, you know, making this italic or just kind of stripping that out.
57:15
So, we definitely need to override the label logic and, and that would be the internal name and it'd probably be nice if the intern name falls back to the title.
57:26
But dealing with this encoding issue so that we don't have to type in a value if it doesn't make sense to the editors can save some time.
57:34
And then as well this total time, there's not really obvious to the editors that this is gonna be the sum of these if I if they don't do anything.
57:43
So there should be like a placeholder that would let them know that there's a default value.
57:48
And then we should probably put a note here to say, you know, these are all numeric values, you know, they might think they type in on five minutes or something like that, but it's expected to be a numeric value.
57:58
That would be the minutes and then the system can add the minutes text in on its own.
58:02
If, if that's necessary, we clean up and as well as like the widgets over here.
58:09
the, URL S legit, is unnecessary.
58:13
We're not, we're gonna use the article, page, recipe, article, page to actually, publish these on the site and give them a permanent and a page to live within.
58:22
because recipes on their own are not standalone pages.
58:25
So we don't need URL.
58:26
So we should just, you know, remove this entirely.
58:31
So that's what our next commit is.
58:33
, and I'll get that building while we look at it.
58:49
So let's now take a look at some of the changes we just made.
58:55
So the first thing here is for hiding widgets or controlling widgets that display, there's price book provides something called content edit widget display, which content edit is the content edit page that we just looked at this lets you control whether the widgets are going to display.
59:09
So how this works is you implement this method here and it gets called with every widget that's about to be rendered and then you can return to a false whether it should render.
59:20
we noticed however, though that basically like 95% of the time that anyone was implementing that interface, they were just hiding the Euros widget.
59:29
So we actually just made an interface extends the content widget display and just does that one thing to save some time.
59:36
So if you only need to hide the Euros widget, you can just implement this rather than going back to the full interface.
59:42
And here is how that works.
59:43
You just compare the the name would be the fully qualified Java class name of the widget.
59:51
So in that case, if it's not, it rolls widget, then you can display it.
59:59
We have added a placeholder to the internal name.
1:00:07
And we've added some logic down here for the fallback of the internal name.
1:00:11
So it's gonna be the title, but the title is Rich Text.
1:00:14
So we need to convert that to plain text.
1:00:18
So that it's valid to use as the internal name, which is plain text.
1:00:22
And then when it shows up in the label and what we won't see those up anymore in the label.
1:00:28
And the pilot actually get that to show as the label, you have to override the get label method.
1:00:32
And then we're gonna do either the internal name, if it's been set or we're gonna use the fallback.
1:00:41
And one thing to know here is we're using this as a custom method, but typically we compare strings as against being blank rather than against being null or empty because it's pretty, if I'm in the C MS here, it's really hard to tell the difference between that and that, you know.
1:01:00
And so, it's just easier to compare against blank and then it's obvious, you know, whether something is blank or not to the editors So there's a batch of string details, methods of check against blank.
1:01:16
So we typically just use those.
1:01:23
So here's all of our time fields.
1:01:25
We've actually grouped these together in a cluster called timing.
1:01:32
So that way they kind of be hidden together or held together.
1:01:36
And so it's obvious that they all kind of belong in the same concept.
1:01:40
Another thing we've done is we've added the CS S class an what this allows you to do is have multiple fields on the same line.
1:01:47
So there's a couple of different, I think there's like his, half, his third.
1:01:54
So basically with the third, as long as there's multiple that are next to each other that have the same or compatible class, then they can kind of all fit on the same line.
1:02:05
So with this, we'll have like three smaller fields and it'll take up less space.
1:02:09
We also added notes to explain that it's not a value.
1:02:12
So it's more obviously to type in integer.
1:02:16
And then finally, for the total time override, we've added a placeholder for the fallback.
1:02:29
So with that in place, now, if we go to our recipe again, you can see here that we have now our placeholder here and the label showing up.
1:02:50
Now we've got our label using the internal name.
1:02:53
We don't have our URL S with it anymore on the right and our timing cluster is now here so we can add stuff together.
1:03:04
And this happened last time too.
1:03:07
There's something wrong with the placeholder when it's not displaying.
1:03:10
I might say it might be that because yeah, I wonder if this is, this is returning a number and this needs a string maybe or something because the placeholder is probably a string.
1:03:21
Anyway, so, oh, we also did some cleanup in the recipe tag as well.
1:03:34
Again, just like internal name, the fallback for that converting the plain text as well as the label logic.
1:03:44
So moving on now, we have the recipe article, I won't spend too much time on this.
1:03:59
Basically, what I did is I just copied the existing article that comes with this Brightspot installation and then just added a recipe field to it.
1:04:09
So that's required added together et cetera and whatnot and that's basically everything.
1:04:13
So, what you would probably do more in a real project is you would have an abstract article and then you would have multiple extensions.
1:04:21
So it might be like, you know, regular article and then recipe article or something like that.
1:04:26
And then each one would have whatever fields differentiated it.
1:04:30
In this case, we just kind of kept things simple by just copying.
1:04:38
And then finally here, we've got our recipe module now.
1:04:55
So one thing to know here is red spot just we factored a little bit about how module, how modules in general are set up.
1:05:04
And so what you see here may or may not line up with what you see in your project based on how old your product is.
1:05:14
And this is basically like bleeding edge.
1:05:15
So I don't know that any projects would see this.
1:05:17
But basically, if your project is about to start, we'll see this new, set up, the only difference.
1:05:23
Well, I'll talk about the difference later.
1:05:24
So the way this works is sort of we have a standard approach to modules.
1:05:30
And the reason for this is editors might have a module that is just for one specific page and doesn't, isn't gonna be used anywhere else.
1:05:40
And so they might just like to publish that as an embedded thing on that page.
1:05:45
Whereas it's also very common to want to be able to share a module among multiple places.
1:05:54
And so we want to be able to support both of those, but due to the way that Brightspot is set up, a field can't intermix embedded in reference objects.
1:06:06
The field has to be to be all references are all embedded and just to show you what this actually looks like if I create a page here, OK.
1:06:17
At the bottom we have our content so we can add module which then lets us pick.
1:06:24
So I'm in global, I'm gonna get into a site, I didn't have a, a bundle selected so that dropdown wasn't, it's broken down by template.
1:06:33
So we can see all the author list templates here.
1:06:37
Different styles are available or like different logo options.
1:06:47
So these are all the embedded values and then if I want to add a shared one, I can then click here and then find all of my shared ones.
1:06:54
And so, you know, if I do an embedded one, like do download this, I just get the fields directly in line.
1:07:00
That's what embedded means.
1:07:01
But if I also wanted to mix in a share our shared recipe module, I've already published here, This actually is an embedded that then points to a shared is what how that works.
1:07:11
So everything in this in this outer common field is embedded, but some of them are just basically references to something else.
1:07:18
So because of that set up, you actually need a couple of classes to enable that full capability.
1:07:27
So the standard approach is you would have an abstract class, which would be you know, in our case, it's a recipe module and that will have whatever fields you need to create a recue module.
1:07:40
So in this case, it's just a recipe where you have an image they can override the image at the placement level.
1:07:45
Like maybe the regular recipe image is not gonna be good for like a side bar or something so they can override that.
1:07:54
And then there's going to be two extensions of this one is gonna be the shared and one is going to be the embedded.
1:08:01
So the embedded one is pretty straightforward, it just extends the abstract class and implements this in line module placement.
1:08:13
And the module placement itself is actually marked as being embedded and normally interfaces don't inherit annotations.
1:08:20
But in in this one specific case of embedded, we've actually done some sort of magic to make that actually work.
1:08:26
So that the sorry, I want to go the placement so that this, that in in that embedded annotation will inherit down to this this module here.
1:08:41
And then we have to kind of tell Brightspot how to then link it to a shared module if we wanted to like swap back and forth.
1:08:50
So then the shared module case is this one here.
1:08:52
So again, we're extending the abstract class, but then we're going to implement the shared module instead of the in line interface, we're also doing the neural no, no URL S widget because you know, these aren't standalone pages, they don't need URL S.
1:09:08
And then finally, we are gonna add an internal name so that the editors can give us a useful name and be able to find it later.
1:09:19
So that's kind of the full setup and the, the main change from this versus what we did before is before you needed the fourth class, which was basically this, but the shared case, it would actually actually would have had like this would have been like shared here instead of in line and it would have been slightly different.
1:09:41
So you might see four classes in your project instead of just the three that I just described depending on what module system you're using.
1:09:55
So that's basically it for data modeling.
1:10:01
I have some links here to annotation documentation.
1:10:06
We don't really talk a whole lot but about annotations, but there's dozens of data modeling annotations.
1:10:13
And so I would suggest, you know, giving those a quick perusal and then kind of keeping them in the back of your mind as you're doing your data modeling, I think.
1:10:20
Oh yeah, there's an annotation that might be useful for this.
1:10:25
But I wouldn't expect anyone to have like all of these memorized.
1:10:30
So now we're gonna move on to the view models.
1:10:35
D miles are basically how Brightspot transforms the data model that we just looked at into a format suitable for public use.
1:10:44
So in an API or the headless or bars, if we are doing a traditional rendering stack.
1:10:52
So Brightspot uses what's called model UV model or MVVM.
1:10:58
which is I think a, a rendering technique that came out of Microsoft, I think and the basically the goal here is we're gonna separate business logic from the data model versus business logic from the rendering.
1:11:16
And you can think of a view model as basically a like a function or a converter.
1:11:20
So it takes in your data model and then it converts it into some data that will then go off to the front end handle bars or, or API So view models and Brightspot maybe a little bit different from how Microsoft originally architected them.
1:11:39
But we have what's the model which would be any class, but it's often a recordable which recordable is basically record and and content and all your extensions of those implement recordable, which is just the interface that describes all the functionality that they have.
1:11:58
view model is going to be a class which extends the view model class from Brightspot and that is a generic parameterized class.
1:12:06
So the is gonna be the model.
1:12:08
So we view model of article we have article here.
1:12:13
And then with a view model, the model is never null.
1:12:17
So you don't need to do any null checks in there.
1:12:20
And it actually like if you try to create a one with a null, it just returns null back and doesn't even bother with your view model.
1:12:28
And then the view is from the back ends perspective, just an interface And then it's bound to a specific method of rendering like html or json, you handle bars or whatever and with those annotations, so they go back to like our recipe module view, go back to the top.
1:12:51
This basically is the binding to the render.
1:12:58
So Han Bars template is the hand bars render.
1:13:01
There's also json view for JSON rendering.
1:13:07
And if you're using graph QL, you actually don't need to put any of this, you don't need to put a, you know, any kind of render on at all because graphical knows how to render on its own.
1:13:20
And then often with graphic, we don't even bother with the interfaces, we just code view models directly.
1:13:24
And so the view is kind of implicit but we'll talk about that later in more detail.
1:13:31
So an important thing is the entry point to the view system.
1:13:35
So when you type in, you know Brightspot dot com, Brightspot is gonna map that URL to a data model and then it needs to find a view model that goes with that data model that is for the entry point.
1:13:51
In this case, it's gonna be page entry view.
1:13:53
So page entry views of interface that Brightspot provides and it just it's what it looks for when it tries to render like the initial request of something.
1:14:02
So on our article page view model, we would need to implement page interviews so that when you go to an article, page URL, it then finds that view model and we wouldn't need this for our modules because they don't stand alone as pages.
1:14:18
There's another entry view called preview, page view and is for the preview of the C MS and it's basically for things that aren't pages.
1:14:25
So anything that is not a page and you want to work with preview, you would have to have a preview, page view view model for it.
1:14:32
So that the C MS preview can then find that view model to render out your preview.
1:14:39
So it's mainly gonna work with modules and I'll talk about that a little bit more when we get to exquisite example.
1:14:48
So we'll apply our next me here.
1:15:03
The recipe module view, we now have the implementation of that which is recipe view model.
1:15:15
So here's our recipe view model.
1:15:17
It's extending the view model class, providing recipe as the data model or just not the data model, just the model of the view model.
1:15:25
And then we're implementing the recipe module view, which is what we created earlier.
1:15:33
And so what this consists of is just implementations of all of the interface methods.
1:15:38
So we had a cook time, that's a char sequence.
1:15:40
So here's how we're going to render that we have a difficulty which is a tar sequence.
1:15:44
Here's how we're gonna render that and so on all the way down.
1:15:50
Couple of things to note is So first of all, we've just kind of factored out the logic for we have like a bunch of different times, right?
1:16:00
There's cook time, prep time.
1:16:01
So they're all gonna look the same so we can have the same logic for all of them.
1:16:04
So we've got a private method here which just abstracts out that logic.
1:16:09
And here we're actually just gonna print out like a, like a human readable text of like five minutes or whatever.
1:16:15
So we've got a library here that's gonna do that converting our numeric value into the duration of minutes.
1:16:23
And then here we actually want to pass in a locale because maybe, you know, the site's gonna support like English and Spanish or something.
1:16:29
And so we want to the text to be formatted in the correct language.
1:16:36
So that local actually comes from this field up here.
1:16:39
And so basically fields and view models are values that get injected based on the request.
1:16:46
So they have an annotation which provides the logic for, you know how to do that injection.
1:16:53
And so in our case, we're going to use the current locale annotation which comes from Brightspots localization plug in to inject the correct local.
1:17:03
So if we're on an English page, this will be English if we're on a Spanish page Spanish and so on.
1:17:07
And that way we can use that reference that any time we need to localize something and everything stays in sync.
1:17:16
The difficulty was an enum so we need to convert that to a string.
1:17:22
And then the go we have to deal with Noel because the difficulty might be null.
1:17:27
So this is an optional to help with that.
1:17:35
By default, we just use the you know, just this, this text here as the string value.
1:17:42
So typically, if that's not, you know something you want to use, you can override it to string method.
1:17:47
And Brightspot will use this value in the C MS, for example, rich checks need special handling.
1:18:02
Remember we've got these market interfaces that have a bunch of, you know, implementations like this one, I have an implementation for, you know, we all support block quote, we support links, we support lists, you know, quotes and so on.
1:18:19
So we need to be able to handle all of these different possibilities in our Ritz text.
1:18:26
So there's a utility that handles that called rich text UTS.
1:18:30
And there's sort of two methods here, there's build html, which is what we see here.
1:18:34
Sorry is coming up.
1:18:37
How this works is you pass in for complex reasons, you have to pass in like a reference to the field rather than the actual data.
1:18:46
So in our case, the directions is a field on the model.
1:18:50
So we're gonna pass in the model and then we're gonna pass in you know a description of how to get from the model to distract the directions field or valued.
1:19:02
So in this case, we're passing a method reference to the getter.
1:19:06
And then finally, we need to pass in Orlando's telling Brightspot if I find the brit text element, like you know a a link or a picture or image or something, how do I actually then render that out find a view model for it.
1:19:22
And so this is this would here would be the rich text element and then we're telling it to process that into a view model.
1:19:31
So cray view is and create views are methods from view model.
1:19:36
And they're basically how you delegate rendering logic to some other view model, right.
1:19:40
So they take two parameters, they take the the view or the view interface or whatever you're gonna be using, you want a view model for and then the model itself.
1:19:53
So here we're doing a Ritz text element passing that in as the model finding a view model that satisfies the directions field.
1:20:00
Here we are finding a view model for the image that satisfies the image field.
1:20:05
So this case right, but we could go and find this image view and then find a new model of that, that matches our, our image ingredients is very similar to the directions the titles are different.
1:20:28
Remember the title was in line instead of block level because it's just the heading and we don't need to have any special block level elements in there.
1:20:36
So we'll use the build in line html rather than build html.
1:20:41
Other than that though, it's the same signature and the main difference here is Brightspot will basically in this build HD monkeys that will process paragraphs into paragraph tags.
1:20:54
Whereas in this build H two on html, they will stay as like brick tags.
1:20:58
And that's basically the main difference.
1:21:03
It doesn't have anything to do with the block level rich text elements that's handled by whatever implements the interface that you pass in.
1:21:10
So this really only has to do with like a preprocessing step.
1:21:23
So with that in place, we actually won't be able to have any preview in the C MS because we did not, recipes are not pages on their own.
1:21:34
And this recipe view model doesn't implement the page entry view.
1:21:40
So what we have to actually do is add a specific preview view model for recipe.
1:21:45
And this goes back into that interface I talked about earlier.
1:21:54
So this is again a review model of recipe and this time we're implementing the preview entry view as well as the preview page view.
1:22:09
Now, preview page view is this goes back to that underscore underscore rapper thing we talked about on the front end.
1:22:18
So in the root dog, I, anything that you don't have underscore wrapper false will then use this underscore wrapper dot JSON file.
1:22:28
And this is basically like any other Omar's be except it's meant only for use with preview.
1:22:33
So here we got our template preview page and we've got some stuff that we've used to fill out the head element mainly on that page.
1:22:45
So by implementing that preview page view, now everything's gonna work with the standard sort of Brightspot preview in our infrastructure.
1:22:54
So this is all just basically just some water plate, just kind of filling in the details except here at the top.
1:23:01
The main method is what would actually be what we're trying to preview.
1:23:05
So in this case, we'll pass in the model which is a recipe and tell it to use the preview page main field, which is basically everything that had underscore false would implement that.
1:23:16
So in this list up here, we can see in here, it's a bunch of things.
1:23:24
You'll see recipe view model recipe module here as a valid option.
1:23:30
So this that's how I kind of you can see here is basically all the different modules that Brightspot.
1:23:36
But they all would need to work with previews So it might think like, wow, I need to write a bunch of preview models.
1:23:45
but actually, all pretty much all those are modules.
1:23:52
And so if we, only need to write a single preview view model for all the modules, and then that basically saves us like 95% of all the effort we would need to do in this kind of thing.
1:24:04
So it's actually not that common that you would need to write a preview view model.
1:24:08
You see there's only four and one of them is the one we just wrote for recipe.
1:24:13
So this module type one, basically anything that's a shared module just will get preview for free.
1:24:19
You don't have to do any additional work.
1:24:21
But recipe is not a model, we had a separate recipe model.
1:24:25
And so that's why we need to write a preview view model for it.
1:24:37
So then take a look at the recipe article, email real quick.
1:24:48
This is just a copy of the the regular article view model basically.
1:24:53
So it's not, I'm not gonna go over all the details in here, but we have page interview this time.
1:25:00
so that this is gonna work on a standalone page recommending our recipe article, page view that we just created from the roots style guide recipe article is our data model.
1:25:11
Remember the in the JSON we included the content or yeah, content page view, content page dot Json.
1:25:21
So there's a corresponding view model for that.
1:25:24
So we, you know, get some stuff for free.
1:25:26
You don't have to like implement the stuff on every single view that has that, you know, extension.
1:25:37
We can just extend this abstract content page D model and there's actually an abstract page model.
1:25:41
So the way we set things up in Brightspot is everything kind of builds off of this sort of like page dot Jason, which is like a just playing middle of page.
1:25:50
This is where all the stuff for like language meta tags.
1:25:54
FCO you know, title all that kind of stuff kind of live in here.
1:25:58
And so we can reuse it for all of our page content types.
1:26:03
And so any time you're writing a view model for a page, you should seriously consider extending this and you'll save yourself a lot of time and having to, you can see it, there's a lot of stuff in there.
1:26:20
And then, so the only kind of special thing in here again was your, we have a recipe.
1:26:24
And so we're gonna use to create views to find the view model for it, pass in the recipe off the article and then tell it to use the recipe recipe article, page, recipe field, which is gonna be the recipe module view.
1:26:39
And so we find our view model and use that to enter it out.
1:26:53
So with that in place, we should actually be able to preview now, both recipe article and recipe, just stand on recipes.
1:27:03
So I've got like a couple of recipes in here.
1:27:06
I think I got the rest of the article if they're already published.
1:27:08
, so here's a recipe, you know, I got some instructions, ingredients, the image.
1:27:15
So now if I open up this preview here, I actually get a preview of that.
1:27:18
Whereas before I did, I just would have just this button, wouldn't they existed.
1:27:22
And so there would have been no option to preview and similarly with the recipe article.
1:27:27
So, I mean, this is going through that preview recipe view model that we wrote and then this is just going through the regular article view model because it's a standalone page.
1:27:36
And so it can just be used directly for the preview.
1:27:47
So finally here, real quick, we'll look at recipe model model.
1:27:57
So this is actually taking an abstract class.
1:27:59
So the the class you pass in here for the class you specify as the view model model.
1:28:06
doesn't have to be a a concrete specific class.
1:28:09
It can be an abstract class, it can be an interface and Brightspot will sort of do some conflict resolution to figure out what view model to use.
1:28:19
Although it's definitely possible to end up with two view models that match a model and so Brightspot, we'll just return like a null and log in error.
1:28:29
So, if you're not seeing something, you, you expect look in the logs because it very well could be a duplicate view model situation.
1:28:38
There is a test that, will catch that.
1:28:43
So, you could also just run the tests and make sure that, you don't get any failures from, it's called ambiguous view model test.
1:28:52
But if you're doing what I was doing just now in skipping test, then you won't get those errors.
1:29:00
So, there's not really much special in here with beyond what we've already looked at except here at the top.
1:29:08
This should create a method.
1:29:10
So when view models are first instantiated, when you, you call to use my specs and finds the view model and creates one, there's sort of a life cycle they go through.
1:29:20
the should create gets called.
1:29:22
And this allows you to do some kind of sanity checks and see, does it even make sense to render this?
1:29:27
Like maybe we have some missing data or maybe the user doesn't have the right permissions or whatever it is.
1:29:33
And so we can return false from this method and then the view model just kind of gets thrown away and it will return no back to your create views call.
1:29:43
So by overwriting that we can actually say, OK, well, recipe modules should have a reference to a recipe inside of it.
1:29:49
And so if we don't have that, that's null for some reason, even though it's required, it might be null for some reason.
1:29:55
We'll just bail here and not render anything.
1:30:01
And then we're gonna set that in a field so that we can then reference the recipe everywhere else because remember this is implementing the recipe module view.
1:30:07
And so everything is gonna come out the recipe and not the recipe modules.
1:30:10
So we don't really even need the model here except to just get to the recipe except for the image because remember the image, we have an override on the module so that we need to pull back to the model.
1:30:22
But everything else could come off the recipe.
1:30:25
This is mostly a copy of the recipe we already wrote because again, it's providing the same view and most of the data is coming from the same model.
1:30:35
But because the model is different, we do need to have like a duplicate additional view model here.
1:30:40
It's not fully a duplicate, it's mostly duplicate but not fully.
1:30:47
So I think that's probably a pretty good place to take a quick break.
1:30:53
And we'll come back in five minutes and we'll pick up with some more data modeling.
1:31:00
So I'll see you all in a few minutes.
1:31:14
Welcome back.
1:31:25
I have a quick question here.
1:31:27
Before we move on to more data modeling, we have a question.
1:31:32
The model have more than one view.
1:31:35
Yes.
1:31:35
So a model can have any number of views and each one of them have its own view model.
1:31:41
But for any given slot, there can only be one possible email.
1:31:46
So what I mean by that is so we talked about like they like the, the, the like the modules group for instance.
1:32:02
So we have the modules group.
1:32:07
There's any number of possible options in here.
1:32:10
But for this given context, the modules, if I have a model like a quote, I expect there to be only one possible template in this list and then only one possible implementation of that for a view model.
1:32:23
So like for instance, maybe we also want quote to work in quote list, like it's just a list of one, but that would cause a problem because when we say, you know, create views modules and then pass in a quote, it won't know if it's gonna pick the quote or the quote list.
1:32:40
That's what I mean by that ambiguous V model test.
1:32:42
So it might will find that situation and build and air out.
1:32:47
So you don't end up with the situation like in production.
1:32:52
But it's fine for like only have quoting here, but then you might have over, you have a quote lead.
1:32:56
And so you have a quote lead on this side and so quote could work with both of those, but those are different contexts.
1:33:03
So that's kind of, it's a little bit complicated, but basically, yeah, as, as long as you can have multiple view models and multiple views, as long as for any given slot, there's only one possible option.
1:33:18
OK, so now we will move on to more data modeling.
1:33:26
So we're gonna look at indexes and the save life cycle.
1:33:29
So we've gotten some more requirements and the editors want to be able to search for recipes by difficulty by recipe tags by the prep, inactive, prep, cook total times all of our different times.
1:33:42
They wanna be search recipes by typing in a tag name and actually getting results for that.
1:33:48
So they might type in like appetizers and they will get all the appetizers.
1:33:52
even if they don't have appetizer in the text.
1:33:54
, and then what they wanted to go support sort the recipes by difficulty.
1:33:58
, so to support that, we're gonna need a bunch of additional indexes.
1:34:07
, let's do a build so that loads while we're talking,, to go back to recipe.
1:34:24
Now,, we've indexed the difficulty, we've indexed the recipe tags.
1:34:33
We've indexed all of the different times and then we would, we want to index the total time too, but we actually can't index this field because it's possible that we're just using the fallback.
1:34:44
And so this would be null.
1:34:47
So we actually want to index the method which is where we put our logic.
1:34:51
And actually, luckily Brightspot supports that.
1:34:53
So index works on both fields and methods.
1:34:56
Although for the methods, there's some restrictions it has to can't take any parameters and it has to domain has to start with get is or has just as like a sanity check.
1:35:06
Basically.
1:35:08
So by adding index on this, now, whenever we save a recipe, Brightspot will find all the indexes and co and save them.
1:35:15
But for index methods, it'll actually call this method dynamically.
1:35:18
and whatever we valued returns, it will index that as if it had been in the field.
1:35:23
So that way, whatever the total time is, whatever we're using the override or we're using the fallback, we get a value in the index and we have a, you know, we, we have to index both of those independently and like do an or for across both.
1:35:34
you can just have the one, the one index for it.
1:35:40
We had it hidden here because when you index the method but actually will display it in the C MS.
1:35:45
by default, it will ignore all the methods.
1:35:47
But if you add index, it doesn't, it no longer ignores the method.
1:35:50
And so it'll show up in the C MS as like a read only field.
1:35:53
Now, you might want that in some cases.
1:35:55
But in our case, we already have the total time as a field.
1:35:57
So we don't need the method to show up as well.
1:35:59
So we add hidden and then we won't see it in the C MS anymore.
1:36:05
We've also got some more index methods down here for the difficulty level because the difficulty itself is an enum.
1:36:11
And so we can't really sort on that very well.
1:36:13
So we actually need to sort on the difficulty of the Enon, which is this value here.
1:36:19
And so that get saved in a field on the difficulty itself.
1:36:26
And so in our index method here, we can then get the difficulty, get the code off the difficulty and return that in the index.
1:36:34
we've also added sort because the requirement was they want to be able to sort them.
1:36:39
And, and this is only gonna apply to C MS search by the way, if you wanted to sort in in the Brightspot, front end, you know, user facing search, There's additional work you have to do that we don't cover in the training.
1:36:52
So the two U I anything two UY is basically for the C MS U I.
1:36:55
So this is for sorting in the C MS search.
1:37:01
We had the title indexed, but we actually, we really should index the plain text version of the title because if someone's gonna type in, you know, like some title.
1:37:08
They don't wanna have to worry about whether there was a or not in the title.
1:37:11
So we're actually gonna index the plain text version of the title.
1:37:15
And then, we wanted to be able to search for article recipes by the tag text.
1:37:21
So we actually have to de normalize the tag names onto the recipe in an index.
1:37:27
That's what this method is doing.
1:37:29
And it's a little bit complicated because the recipes are recipe tags or references.
1:37:34
And so by default, right spot, you know, whenever you query for a recipe, it will then resolve all the references kind of on the fly automatically for, you don't have to really think about that.
1:37:46
But in certain cases, they actually won't be resolved for performance reasons mainly.
1:37:51
And indexing is one of them.
1:37:53
And so, just the main story that we always have data on those, we get resolved, the, we get the same tags resolved.
1:38:05
So we actually have the data, you know, the tag name in hand, we can index that we can use this utility to actually force that resolve to occur.
1:38:14
So we pass in the recipe here and then we pass in to get to the thing we want it to resolve and then it will then ensure that if it's not resolved already, it will go and force it to be resolved.
1:38:30
So then with that, then we have a, we have a resolved list of recipe tags and we can map to the, the name, the name of those is rich text.
1:38:39
So we're gonna map that to plain text and then we'll collect that at a set.
1:38:42
So we have our strings of all the different tag names and then we can search for them.
1:38:49
So by indexing this, it'll go into like the full text, search index.
1:38:52
And so if we type in, you know, appetizer, then it will match, any tag we have at an appetizer as well as appetizer anywhere else in the text.
1:39:05
So when we've done that, you know, adding indexes like that is pretty, pretty easy, you just go and type the code and deploy the production, but it will only affect recipes going forward.
1:39:16
So any time we save a recipe from now on and say we load this, and those recipes will be pop those, those indexes will be populated when we save the recipe.
1:39:28
But any existing recipes won't have those indexes, but like it's on server side, right?
1:39:36
It's not gonna go and discover all the new indexes and start indexing them automatically that's full and safe actually, because you might want to be able to back out of a production, deploy and what not.
1:39:44
So, it's sort of a manual step.
1:39:48
Once you've, you know, deployed some changes to add new indexes, you need to tell a spot to populate those indexes.
1:39:54
And so the way you can do that is if you go to the burger menu up here under the developer cluster, there is bulk operations and you won't see this without special permission.
1:40:08
So if I go to the users and roles area, we see here and we've got a developer role I've created here.
1:40:26
So by default, you won't be able to default roles don't have, give you access to the developer because, you know, there's kind of dangerous, you know, re indexing may or may not, you know, you don't know what you're doing, you might cause problems and whatnot.
1:40:40
So overload the database and whatnot.
1:40:44
So we've created a special role that adds the developer permission here and then I'm not sure if actually, yeah, so this is a local environment.
1:40:53
So it's a bit unrestricted but on like a Q A environment or production, you would actually have to add yourself to the developer role in order to get access to this dropdown.
1:41:04
But on your local things are a bit more fast and loose.
1:41:07
And so it's not necessary here anyway, so if I go to the bulk operations, I get two options.
1:41:14
So the first thing you would want to do is index and you would choose the recipe content type and then we'll just do one writer because I only have like 10 recipes.
1:41:25
But you might want more writers depending on, you know, how much load you're gonna put on the database and how much data you have to, to move through when you hit start, that'll actually launch a task to go and index all of the, recipes.
1:41:39
So you can see here it ran pretty quickly in the next nine.
1:41:42
, but if we had 10,000 recipes or 10 million recipes, this would, could take some time potentially.
1:41:51
But we actually have to do a second step because that index only applies to the mysql database, which is Brightspots, Brightspot runs on top of two day two databases.
1:42:02
My sequel is sort of the source of truth.
1:42:04
That's where the actual data lives.
1:42:06
All your field, all the field data from all your fields will live in that database.
1:42:10
But then we also run with a solar database that is for full text search.
1:42:15
And so that has a separate index, you're using full text search stemming and all that kind of stuff.
1:42:19
And so the index here only applies to my sequel.
1:42:23
So we actually have to then do a second step where we copy the recipes from sequel to Solar.
1:42:34
Again, we'll just do one.
1:42:36
you may or may not want to do delete but dele code base will clear out sol for this content type or if you have all it will clear out all of solar and then start to populate it again.
1:42:46
So if you, if you click this, you're gonna have some time where sor doesn't have any data in it.
1:42:50
And so you very rarely would want to check that in production.
1:42:56
So we will leave that unchecked and then start the same again.
1:42:59
It's gonna show up here on the task page as a bulk copy from sequel to solar again, it did nine.
1:43:07
So with those both done now, we've got our indexes populated.
1:43:09
And so if we query or like I can go up to search now and say, find the recipes, you know, we can sort by the difficulty and that will actually be sensible because then we won't just have null.
1:43:23
If you don't have, if you haven't populated the indexes, then all these will just be.
1:43:26
And so this sort here would just be random basically.
1:43:29
But you can see we've got easy and intermediate have any hard ones, I guess.
1:43:37
Another thing you can use to help debug this kind of thing is if I go to a recipe in this triple dot menu here on the right.
1:43:48
There's this view state data.
1:43:51
Most projects started within the last to his, she would have this page.
1:43:59
older projects have a different page that doesn't have quite as much info on it.
1:44:04
When I scroll down here to the bottom, I can actually see the index values.
1:44:07
This is coming out of sequel.
1:44:08
So this won't show me anything that's in solar.
1:44:10
But here's all the indexes that are populated in the SQL database.
1:44:13
So we can see here, we've got our difficulty is easy.
1:44:16
Difficulty level is one, here's our recipe tag names is just so we have the one tag.
1:44:22
Here's our title, Cook Title.
1:44:25
So all these indexes have been populated so we can get this as a good debugging tool.
1:44:28
If you're not seeing something you expect you can come here and see if maybe the index wasn't populated properly for some reason.
1:44:38
Another tool that's very useful though, it's really only gonna be available on your local is this query tool.
1:44:47
This basically is like a, it's like AC MS search, but it's a lower level.
1:44:51
And the C MS search has often has like filters that get injected automatically just for editorial convenience or control.
1:44:59
Whereas this data, this query is just playing the.
1:45:02
So I don't have to, you know, I know for sure, I'm gonna get only the results that all the results that match with no hidden filters or anything.
1:45:12
But a useful thing here is you can actually choose between the SQL and Solar database.
1:45:17
And so if you're seeing issues or maybe you think they've gotten out of sync or something, this is a good place to debug.
1:45:22
But it's gonna be a lot more difficult to get to this in production because, or Q A because it's pretty locked down.
1:45:28
Whereas on your local, it's just everything is available.
1:45:31
So, mostly expect to use this when you're doing local development.
1:45:35
, so it's all fine and good that we did that at those indexes.
1:45:47
But, we've kind of got a data dependency now where our recipes, refer to or have a copy of the data on the recipe tags.
1:45:56
And so, if someone changes the recipe tag and he renames it or something, then our recipes are gonna get out date.
1:46:06
So we actually need to do is any time a recipe tag changes, we need to force a re index like we just did in the C MS, we need to force like an automatic re index of the recipes with that tag so that they stay in sync.
1:46:22
And so, we basically covered all this stuff already so we can use something called the save life cycle, which is something that provides.
1:46:35
And so anytime you say something to the database, it goes through this life cycle.
1:46:40
And these are sort of, these are methods and hooks that you can use to control what happens or alter the behavior or change the data or whatever you want to do.
1:46:50
So the first one that gets called is called for save.
1:46:53
Although that's a misnomer, it should really be called on key press because basically any time you're typing in the CM SI showed earlier, like it saves your data as you're going.
1:47:01
And so any time those saves happen, that's what this before save is gonna run.
1:47:05
So this isn't like a full like published database save.
1:47:08
This is like any kind of saving the progress save.
1:47:11
So this runs a lot which means you wouldn't want to put anything complex or expensive to compute in this method, but like just really small set up, you know, clean up type stuff.
1:47:23
So then the rest of these methods only run when you actually like hit publish.
1:47:26
So on validate is the first one.
1:47:29
It allows you to do custom validation, which basically means I I whenever possible, you should use an annotation to do the validation.
1:47:36
So like we have required, there's a minimum and maximum, there's things like that.
1:47:39
So those already kind of have their own built in validation.
1:47:42
But if any of those things don't work for you, you can override the on validate and do your validation checks in there.
1:47:50
And if you find any errors, you can add an error to the state.
1:47:54
We haven't really talked about state, but basically state is a job diary class that is the, the raw data that backs your content type.
1:48:03
And so it's, it's basically just the JSON map because that's Jason is what gets saved to the database ultimately.
1:48:09
So on that state, you have all your, your JSON, you know, you know, you also have the ability to add like, and so by adding an error, then you can associate to a field.
1:48:21
And so then you'll get like that red error that we saw earlier in the C MS, which is on the field.
1:48:25
That is the problem and tells the editor exactly what they need, what they need to do to fix.
1:48:30
, what you don't want to do is throw an exception.
1:48:33
You want to add the errors to the states.
1:48:34
So you get that nice U I yeah, throw an exception, just get like a general red error in the C MS and it's a lot less useful to the editors.
1:48:45
So after validate is called regardless of validation, success failure.
1:48:50
This is a new method.
1:48:51
I'm not really sure what the use case of is it for?
1:48:54
It's for, but it's, it's new and rarely used.
1:48:57
So I had it here just for completeness.
1:49:00
The most common one you're gonna want to use is before commit.
1:49:03
This is what you would probably think of as before.
1:49:05
Save is actually being.
1:49:06
So basically, right before we're actually gonna save the database, we can if the foundation was successful, we can do stuff.
1:49:14
And before committing on duplicate, allows you to handle uniqueness, constraint violation.
1:49:22
So we have like a unique, we could have like a unique title or unique, you know, Perma link or something.
1:49:26
And if there's a violation of that is existing data with that Perma link, we can override on, on duplicate and, and take some action to resolve the duplication, which usually just means, you know, adding a dash two or something, you know, whatever whatever your business logic or calls for.
1:49:43
And then finally, if the save was successful, there were no validation errors or no duplicates, then after save will, will be called.
1:49:51
And you can use that so it like trigger, you know, and you know, maybe have some external analytics, you're gonna update or whatever you wanna do, you know, only if the save was successful after save is where you would do that.
1:50:07
So I guess there's a delete life cycle as well, which is a lot simpler we just have before and after delete.
1:50:15
one thing to note here is delete is not, might maybe not quite be what you think of it.
1:50:19
So normally in the C MS people archive which is not a delete, it's just like a hiding on the content.
1:50:27
So it's no longer publicly visible.
1:50:29
But it still exists.
1:50:31
And so this before delete is after delete is like an actual true delete.
1:50:34
So you have to archive and then you can delete.
1:50:36
So if you have anything that should happen because something goes from visible to not visible, using published to archived, you would need to do that in the save life cycle because that's, that's not a delete.
1:50:52
So let me show you an example of some of the stuff.
1:50:54
So here's our recipe tag.
1:50:55
So whenever we change the name, we want to go find all the articles that are tagged with this recipe tag and update that index method which has theorized tag name.
1:51:07
So we have two pieces for this.
1:51:10
We're gonna before commit.
1:51:14
And the reason for this is we need to know if it actually changed.
1:51:16
So we can't, we need to do some logic before the save has occurred because we need to have a previous version to compare it to.
1:51:23
So if we did, if we did this check after the save was successful, then we would get what we saved back and we wouldn't know if there was a change.
1:51:30
So we need to get the previous version before we save the new version.
1:51:36
And so here's our first data query.
1:51:39
So we're gonna query from all means over the entire database, archived draft.
1:51:45
Anything possible?
1:51:46
It's gonna query over everything.
1:51:49
Then we're gonna query my ID.
1:51:51
So the where here is where, how we, how we filter our results down.
1:51:54
So we're gonna filter where the id is.
1:51:58
This is a like a substitution parameter and we're gonna pass in the recipe tag.
1:52:02
And so we will convert this sort of automatically into an id for you so that you don't have to like these, these question marks.
1:52:09
, anything you pass here, it kind of knows how to convert to the correct syntax and the query.
1:52:14
, and then we're gonna have no cash here because Brightspot is heavily cashed.
1:52:20
And so what, what would probably happen if you did this query without the no cash is it would find the, the recipe tag that we have right here in some cash and say, oh, here you and return that back.
1:52:30
And so we actually wouldn't see any differences at all because it's exactly the same one we're looking at.
1:52:35
So when you've got no cash to tell, but, you know, really go back to the database and get like the real version of it.
1:52:41
And then we just need to get the first, we could also do like a select all or a select, offset.
1:52:49
So it's possible that this would be null because this is a brand new recipe tag.
1:52:56
So we need to do an old check, but it's also possible to actually change the type of something.
1:53:02
It's pretty rare for this to occur, but there are some projects that you have.
1:53:07
like you love the editors choose their team, multiple different content types that are all compatible or something.
1:53:13
So it's actually possible, although very unlikely that the recipe tags type was changed.
1:53:17
And so a more obsessive complete check would be to actually check to make sure that it's a recipe tag, the previous version of the recipe tag.
1:53:27
So if it doesn't match all that, we just return, we don't need to do anything.
1:53:30
Otherwise we need to compare the previous name with the current name.
1:53:37
And if those are not equal, then we want to flag that in the after save, we need to do some indexing.
1:53:44
We don't want to index now because it's possible to save will fail.
1:53:47
In which case, then we would need to undo that indexing.
1:53:50
So we just need to save a flag to say in the after save.
1:53:55
You know, if the flag exists, then you can go to the re index.
1:53:58
So the state has, in addition to the errors we talked about, there's also an extras map which is just like generic parameters.
1:54:06
You can store things in there, but they don't get saved to the database, but they will persist throughout the saved life.
1:54:13
So we can add a value to the extras saying that we need to, we need to, the name is modified and so then we can check for that in the after safe.
1:54:26
And this is just a typically the pattern here is you would use like the content nights fully qualified class name and then some other stuff just to prevent any duplicates because again, that's just a generic map.
1:54:36
So anybody could be injecting stuff into that.
1:54:39
So you don't want to have conflicts on your keys.
1:54:43
That's all we have to do in the before commit, then if the save is successful, the after save will run and we can check, pull that extra out of the state and if it was true, then we can recalculate.
1:54:56
And so has a a built in framework that has a path and will, you can sort of queue things up to be re indexed and then it will work through that queue or re index the stuff.
1:55:11
So how this works is we pass in a query and then we pass in the name of the index method that we want to recalculate.
1:55:18
So our query again, we're gonna use from all because we want to get maybe there's some recipes that are in draft, maybe there's some that are archived, we wanna update them all.
1:55:29
But because we're gonna query from all, we have to use like a fully qualified name for the index we're using normally in a normal query.
1:55:37
This would be like and then we wouldn't need this part because sorry for not to hear because it, it knows it's a recipe.
1:55:50
So it kind of can do that qualification for you automatically.
1:55:53
But in our case, we're querying class, the entire database, all types.
1:55:56
And so we have to fully qualify all of our index names and the way you get this name is.
1:56:01
It's basically the whatever class the index lives in.
1:56:04
It's the fully qualified class name.
1:56:06
So package plus the name plus the name of the fielder index.
1:56:13
So in our case, it's the, the index method is get recipe cat names.
1:56:22
So that's our, that's our recipe fully qualified name.
1:56:26
Then we have a slash, then we have the name of the field or in this case, index method and then we still have the equals substitution just like we had earlier and we're passing in the recipe tag.
1:56:37
So this is gonna find all the recipes that are tagged with this recipe tag.
1:56:41
And I recommend any time you have an, an index that you're gonna be querying on, you should put a your metastatic field with the name and just use that as opposed to having the name typed out everywhere.
1:56:53
This way is easier to rename things and makes it easier to track what's actually being queried over.
1:57:05
And then we got the same thing with delete.
1:57:08
In this case, we don't really need to do a check with something changed.
1:57:11
I know it's changing, it's been deleted.
1:57:12
So we have the same thing.
1:57:15
Although probably a more robust solution here would be to also check for if it went from published to archived here and do the trigger here as well.
1:57:26
So it's sort of an exercise for the for the reader if you wanted to come in and add that logic.
1:57:32
somebody we already have here, which is you have an extra check here and you would only do this line if it went from.
1:57:42
You know, I guess it would, if you would do this check or you, you have another check after you would do like if the, the status changed from published to archives, then you would also add this flag.
1:57:59
I guess we can do a quick example of that.
1:58:04
So I forgot to build.
1:58:06
Oh, so I didn't skip tests.
1:58:13
Hm.
1:58:21
So get test going on here, a clean bill.
1:58:27
Not sure it's got in a bad state or something.
1:58:29
Hm.
1:58:31
So I think I probably made some changes.
1:58:35
Yeah, accidentally changed something.
1:58:50
I was demoing that.
1:59:10
So you can see here when the Docker starts to reload.
1:59:14
First thing you see is the I guess we've lost it up here.
1:59:19
It says it says something like un deploying context.
1:59:21
Then you see it's deploying the context and then once you see the has finished, then you know that it's ready to reload.
1:59:30
If you, if you try to like load the C MS before that step, you'll get, you'll probably get an error from Apache saying like service unavailable or something.
1:59:41
We have Apache sit in front of tomcat, tomcat running Brightspot, Tom and Patri is sitting in front of that in the actual web traffic.
2:00:04
So one quick thing, I need to check just to make sure this, I think this is already set up properly, but just in case, so for that task to run, it needs to bright has the concept of a task host.
2:00:17
So it's, it's an environment that's, you know, in the production or Q A or environment or whatever that's dedicated to running all of your tasks.
2:00:24
And so it has a special name and then you need to then tell Brightspot,, to use the correct task code.
2:00:39
Yes, here it is.
2:00:42
So the training, got a Brightspot is the tax code.
2:00:46
I'm not sure why it's not already.
2:00:51
Also in that one, these other ones done by, we're not using any of these features, but this one should be filled in.
2:01:00
that's how his name for your doctor comes from the doctor, compose the host name here.
2:01:09
So Docker, it's there in any other environment.
2:01:12
You'll have to ask ops, you can't figure out what it's supposed to be.
2:01:20
so we have that in place.
2:01:22
Now, I can actually go to a recipe tag like salad.
2:01:32
You can see that we've got recipe with that and we actually can do a check now.
2:01:36
So we can look at the raw data and see, OK, recipe tags names is salad.
2:01:42
So maybe we're gonna go and like pluralize all of our tags.
2:01:44
This is actually gonna be salads.
2:01:47
So they published that I should be able to go to the background tasks and we've got our method recalculation task.
2:01:57
So I should see this run.
2:01:59
We probably missed it at 309.
2:02:01
But when this gets up to two, it should run again in a minute and, pick up that change and re index all the, recipes tagged with salad and then they should be recalculated into,, salads plural.
2:02:25
We can actually look for that log.
2:02:30
So it already processed actually.
2:02:32
So I guess it did get picked up at 109 for 309.
2:02:35
Sorry.
2:02:35
So now I'm gonna refresh here.
2:02:37
We've got solids.
2:02:38
So I already recalculated it and reprocessed it.
2:02:45
Yeah.
2:02:46
Anyway, so we had a lot of recipes that would take a lot longer to process, but that was pretty quick.
2:02:51
So that now that that's in place, you don't have to worry about things getting out of sync because any time you change a recipe tag, rice will then keep all the recipes in sync.
2:03:04
And there's more documentation on that.
2:03:06
those persistence api s if you need to take a look at it.
2:03:09
So this entire save life cycle is documented here.
2:03:14
So now we got some more advanced data modeling.
2:03:19
So Brightspot for rather provides something called modification.
2:03:24
And the reason for this.
2:03:25
So we kind of have a problem with Java for very good reasons.
2:03:30
Java does not support multiple inheritance which basically means that you can't put fields on interfaces, you can only put them on abstract classes and concrete classes and every class can only ever have one super class parent.
2:03:44
So you're kind of limited with where your fields can go.
2:03:51
And so that kind of forces you into these kind of deep inheritance trees.
2:03:56
And you know, you might want to be able to share fields across multiple content types without having to share a common base class.
2:04:06
So in regular Java, you just kind of stuck, you can't do anything.
2:04:11
But modifications are sort of a diary work around to allow you to attach fields and methods to interfaces or set a classes or whatever.
2:04:22
And so, this is useful both for kind of keeping your class hierarchies clean as well as just the code we use as well as injecting fields into classes.
2:04:34
You don't control like the site settings, thing that I talked about earlier where you can inject additional fields into the site settings works via modifications.
2:04:44
So there's a lot of different use cases for these.
2:04:47
So what a modification actually is, it's an abstract class which extends record, but it's kind of special in that it's not a normal record.
2:04:54
You can't directly like query for a modification or get one you have to kind of go through whatever they modify.
2:05:05
It's a Proud Earth class kind of like the view model.
2:05:08
The T here is the thing we're gonna modify.
2:05:10
So it could be anything you want, abstract concrete interface, whatever.
2:05:16
And just anything that is instance of that would then also get whatever fields and methods you have inside your modification.
2:05:25
But it's still a separate class from what you're modifying.
2:05:27
There's no bili manipulation here.
2:05:30
So if you have like an article and you want to get to the modification, there's a method called as on recordable and then on record and content as well.
2:05:41
And you would pass in here the modification class and I will give you back the linked modification for that article.
2:05:51
So that's all kind of abstract.
2:05:53
So let's actually look at some code so you can see what this actually looks like.
2:05:59
So in our case, our requirement is we want to have a uniform index for all the things that can be associated to recipes.
2:06:09
So we have a recipe article for example, but maybe we also have like we could add like favorite recipes to like the authors or something.
2:06:19
So we might have a bunch of different kind of types that all have references to recipes.
2:06:22
And we want to have a singular index that's just kind of find all the things that are related to us, a recipe, a specific recipe.
2:06:35
So this is a pretty common use case.
2:06:41
And so to give the maximum flexibility, we've come up with a pattern that, that sort of involves four classes.
2:06:49
Most modifications wouldn't necessarily need to be this complicated.
2:06:54
But I just kind of wanted to show you like the full package deal because you will see it a lot in, in code.
2:07:02
So the base of it is this has recipes interface.
2:07:06
So we have an interface that just encapsulates like having some kind of association or something.
2:07:11
So in this case, is being associated to recipes and it's pretty simple, you just have, you know, give me the recipes that I'm associated to.
2:07:19
Anybody who invents.
2:07:20
This can just determine how the, how they get the recipes.
2:07:23
So like the rest of the article, we could just return the one recipe on author.
2:07:28
If we're adding favorite recipes, we could just return all their favorite recipes.
2:07:34
But then the secret sauce is the has recipes data here.
2:07:39
So this is a modification.
2:07:41
It's a modification of that interface we just look at.
2:07:44
So anybody who implements interface also gets the stuff inside has recipes data just for free.
2:07:53
And that stuff in this case is just an index method called get recipes.
2:07:58
And so what we're doing is we're just gonna get, you know, call that method on the interface and get all the recipes and then index them.
2:08:06
And so now we have a single index that can be supplied from multiple sources.
2:08:13
Again, we have to use that unresolved state thing because it's an index method.
2:08:18
We don't know how they're getting the recipes.
2:08:20
So they might need to be resolving references to get to them.
2:08:23
And so just to be safe, we should always just resolve the recipes, force them to be resolved.
2:08:30
We've added hidden again, we don't want this to show in C MS and filterable means that this will show up as a filter in the C MS search.
2:08:38
So with this in place now, anything that has recipes, we can, then we can search for them by recipes.
2:08:43
We could find all the things associated to that onion salad article I looked at earlier or onion salad beef salad thing recipe.
2:08:53
One thing we need to note here is we have this annotation called field internal name prefix.
2:08:58
So, how this works under the hood is I've said that Json, all the data of your like article or recipe gets serialized to JSON and stored in the database.
2:09:08
So this modification is just injecting additional keys into that Json.
2:09:12
So it's possible, although it may be unlikely that you would have conflicts for multiple modifications are adding fields with the same name.
2:09:22
So those will clash in the in that JSON.
2:09:26
So by adding this field internal name prefix, what that's doing is basically saying instead of get recipes, you know, being the name of this index method.
2:09:33
It's actually gonna be this prefix plus get recipes.
2:09:37
So here's the prefix.
2:09:40
So has recipes dot is the prefix and then the actual index method name is gonna be has recipes dot get recipes.
2:09:49
So that way much less likely to have a conflict.
2:09:51
Now, we'd have to have like another modification that's also called has recipes and that's much less likely.
2:09:58
So, basically any time you're doing a modification of like an interface or an abstract class, you want to put a field into a main prefix and we actually have tests that will enforce that.
2:10:09
So in case you forget, test will fail your bill and you can then go at it.
2:10:14
And this is important because this is what this name is, what gets saved in the database.
2:10:17
So if you change this name, you have to go re index.
2:10:23
So you want to kind of get it right the first time.
2:10:28
So then the next part you could just stop right there.
2:10:33
And that would be enough, but we have another sort of layer on top of this, which is just for really convenience and, and reducing code duplication because very often we just, you know, we have has recipes and we still have to supply them from somewhere, but usually we just want to supply them from a field that the editors can just pick from.
2:10:53
And So we have a modification, it just has a field the editors can pick from.
2:10:58
and then we can associate that modification to another interface which extends has recipes.
2:11:03
And then we get all the capabilities from has recipes for free.
2:11:06
And then we get a free field, excuse me.
2:11:10
So with these four classes, two interfaces modifications, we get an index, we get a field and we just kind of a minimal code duplication.
2:11:20
So we can just kind of add as recipes with field on a bunch of different kinds of types and then forget about it and not have to do any other coding.
2:11:28
So a good example of this is has tags which comes just sort of out of the box with the Brightspot.
2:11:37
And so we have the has tags just like we had with has recipes and then we have hash tags with field and it has its data modification with the tags field.
2:11:47
And then the nice thing about this is if I go to like article, for example, to add tag, the article is one line of code and that adds to the field, the interface, the math index method, all that stuff, we just have to do one line.
2:12:01
So that's kind of the the goal of this is you have four classes, but then you have only one line in any of your content types.
2:12:08
So it reduces codification and clutter.
2:12:16
But based on your use case, it may not make sense to have all four, you might just have the interface and the modification or maybe just the modification directly.
2:12:29
So the next step is to add that to actually implement the interface on something.
2:12:40
So the only thing I've implemented on in this example is the recipe article because this has this has the recipe field already, we have a single recipe that the article is associated to.
2:12:53
So we're gonna implement has recipes and then we're gonna have a get recipe method which will just take the recipe that we have.
2:13:03
Wrap it up in a list.
2:13:04
We have to return a list and return that.
2:13:06
Otherwise we return an empty list.
2:13:08
again, recipe could be mole.
2:13:11
So we wanna make sure we deal with that case.
2:13:14
And then we don't want to return null from a collection method usually.
2:13:18
So we return an empty list rather than null.
2:13:21
So with this, now we can, we can do another re index on our article and it'll populate this but whatever a recipe we've already selected and now we can like filter across all different things that could have recipes and we'll get our article.
2:13:35
Maybe we can add it to authors in the future, we can get authors back and they all kind of be returned in the same queer.
2:13:56
So it wasn't a index but I don't think you guys, you already saw that we don't need to do that again anyways, because we have a new index.
2:14:02
We'd have to go in here and do the whole rein process again.
2:14:07
So let's get over later.
2:14:14
Yeah, so this just describes that pattern.
2:14:16
We just did.
2:14:19
OK.
2:14:20
So now our final thing is graph dual.
2:14:25
So we're kind of talk a bit about API S now.
2:14:29
So handle bars, it's an API in some sense, but it's an internal API, it's only used within red spot.
2:14:35
So it's not designed or intended for, you know, to use in the general public, you know, you got, who knows who your API consumers are gonna be.
2:14:44
So the root style guide and the views and the view models that are associated to it.
2:14:50
We designed for handlebars.
2:14:52
And so like they don't really create a clean, maintainable API that you would want in a real, like graphical API or something because API S need to be robust and curated and maintainable.
2:15:05
You don't, I mean, you might have no control over your consumers.
2:15:09
And so you can't just arbitrarily change things or modify things, you kind of have to evolve the API over time and, and version it or whatever you end up doing to your strategy is to, to maintain the API.
2:15:21
And so the handlebar is set up is just not really intended for that level of use.
2:15:27
And additionally, oftentimes the data, you want to expose in an API it can be different from what you would expose in ham bars in the HTML.
2:15:34
So a good example of that is we had like the durations in our recipes.
2:15:38
And so in Hamar, we were just gonna, you know, and the view model will just, you know, create some nice human readable text and then the view handle, our stomach can just spit out that text.
2:15:48
Whereas an API we probably want to return like the raw energy or maybe we return an iso you know, duration stamp or something.
2:15:57
That's easy to process you know, computer readable rather than human readable.
2:16:03
So oftentimes your data format is gonna be totally different whether it's gonna be handlebars or whether it's gonna be an external API.
2:16:08
So our general recommendation is that if you're going to write an API, you would write custom view models specifically for that API.
2:16:16
And then you have full control over what's exposed and how you're gonna maintain it and evolve it over time.
2:16:21
And you're not gonna pull in any of the baggage from handle bars, but depending on your use case, the Hanna Mars views might be appropriate.
2:16:29
So it's just gonna depend on your specific needs, your specific timelines and all that.
2:16:34
But the general recommendation, you know, the best approach, the best path is going to be writing custom view models for graph QL.
2:16:44
So to actually do that, the graphical view models are basically the same as the view models you would write for handlebars.
2:16:50
But they're not going to use the auto generated view interfaces for the root root style guide because those are built with handlebars in mind.
2:17:00
And oftentimes you don't even need view interfaces entirely, you can just code view models directly.
2:17:06
But you will need a view interface if you're going to use like a graphical interface or a union.
2:17:13
Because those basically map two interfaces in Brightspot.
2:17:20
And then how it works is you're just gonna code a direct VM for graph QL.
2:17:24
It'll just expose all of the public no parameter methods as fields in your API.
2:17:30
So that'll show up in your schema.
2:17:32
And you need to add the view interface annotation which I talked about earlier on the VM class directly, which will tell the view system that yes, these are appropriate to expose in the API.
2:17:44
If you're doing a JSON API, you need that JSON AN I showed earlier.
2:17:48
But if you're doing graph too well, you don't need anything special at all.
2:17:50
Just any V model will work.
2:17:56
And we talked earlier about page entry view.
2:17:58
So that's the standard like HCP request entry view.
2:18:02
But for graph QL, rather than having an entry view, you actually gonna create an endpoint and on the end point, you specifically describe the entry point or points, you can have multiple entry points.
2:18:13
so you could use page entry view there if you wanted to, but you're not obligated to, you can do whatever you want.
2:18:17
, so let's actually look at some code.
2:18:31
Hm Sorry.
2:18:36
Ok.
2:18:39
so the first thing here is our recipe in point.
2:18:46
So what we're gonna be building here is like a search api for recipes.
2:18:50
, so this is why we made recipes standalone because we want to be able to expose them in this API rather than having to go through the article, you can just get to the recipes directly.
2:19:01
So we didn't want them to be embedded or contained in a module, we just want them to be standalone things.
2:19:08
So Brightspot has two kinds of graphical API S it's called the content delivery API and the content management API.
2:19:16
And the difference between those is a content delivery API is based on view models.
2:19:20
That's what we're gonna be looking at today.
2:19:22
The content management API is based on the raw data models.
2:19:26
So if you have headline field in your data model, that headline field will just be exposed directly in the content management API.
2:19:33
whereas in the content delivery API, we can have control in the view model about whether that headline gets displayed directly or converted to playing text or who knows what.
2:19:44
So the content management API is meant really for like internal use, you know, ingesting data into Brightspot or maybe supplying, you know, analytics, data out or something that's gonna understand Brightspots data format.
2:19:54
So it doesn't do any kind of processing or cleaning up or you know, papering over anything of the Brightspots internal formats.
2:20:04
So you, you're coupling yourself pretty strongly to Brightspot if you use the, the C MA, whereas the content delivery api you have pretty wide latitude as far as what you can do in that.
2:20:15
And so you could use it to replicate an existing graphical API, for example, As long as you write your view models to match the existing schema, you could kind of port something over without having to change any of the clients.
2:20:27
For example, you just have full control with this, but obviously, it's gonna be a bit more work because you have to write all the models.
2:20:35
anyway, so we're doing content delivery API today.
2:20:39
We're implementing singleton, I'll talk about that.
2:20:40
Why in a little bit.
2:20:44
Basically, we just have to supply the, the path that this end point is gonna live at as well as the entry fields.
2:20:53
So this is why I talked about with the entry points.
2:20:55
So you can have multiple entry point fields and each one is basically the view model or the view interface.
2:21:05
If you have multiple, you know that kind of come under the same.
2:21:09
We could have maybe some kind of abstract class here or something.
2:21:12
Basically, just, it's gonna be like the entry point, all the view models will, just add it from that and then we can give it a name and I guess I'm not sure exactly what docs is, but, we can have something show up in there too.
2:21:23
, so we can have multiple of these.
2:21:28
In this case, we only have one, we're gonna have a gray graphical end point view model is gonna be our entry point and then it'll show up as recipes in this graph girls.
2:21:42
This up here, I believe is only for C MS, this will display on the C MS, but it doesn't matter for anything else.
2:21:49
So now the reason why we need singleton is so our sort of out of the, the default set up for the CD A would be, you would have these view models that are associated directly to like piece of comments.
2:22:01
We have like an article view model show up here and then you go gallery and whatnot and then by default, those will be like the entry point would be filterable by ID and path or URL.
2:22:18
So if we just had the idea of the recipe, we could just kind of get to it directly.
2:22:22
But in our case, we actually want to have a search api we wanna be able to say OK, fine, you know, recipes with medium difficulty and with this tag and this, you know, level of, you know, effort or whatever.
2:22:34
So to do that sort of a special setup.
2:22:39
So rather than this being a new model of recipe, it's actually ad model of the endpoint itself.
2:22:48
So when you do that and the end point is a singleton and what Brightspot does is let me make sure I'm start with Bill.
2:23:00
What does is it will basically expose this is the entry point and you'll have any kind of filters you want will show up here.
2:23:08
And then you actually have to then use those to make your own query and actually find the recipes that match and return that as the graphical response.
2:23:16
So normally the graphical response would just be the single recipe that had the idea that you passed in or the single recipe that had the path that you passed in.
2:23:24
But in our case, we're gonna search API so you can get multiple recipes back based on your printers.
2:23:32
So here are the parameters, web parameter is a view model annotation.
2:23:35
So in a handle bars, you know, HTDP type view model, these would be query string parameters in a graphic field view model.
2:23:43
These are going to be injected from the request that came in.
2:23:49
So we can filter on the title, the difficulty, the tags, some search terms as well as you can with you know, page two, page three and so on.
2:24:02
So these will then be filled in, these will be exposed into schema and then they will be filled in based on the request that was made.
2:24:09
And then, then we can use them to actually build our query.
2:24:15
So I talked a little bit earlier about the view model life cycle.
2:24:18
So here's on is another method we talked about in there.
2:24:23
You can kind of think of it as like a constructor.
2:24:24
It's not really a constructor, but you can just, just sort of a general place you can kind of use for setting stuff up, you also get passed the view response, which is gonna be more useful probably for handlebars rather than graphical.
2:24:34
But it basically lets you set the response code cookies As well as you can actually throw it if you want to like bail and say this request shouldn't be handled for some reason you can, you can throw, it's like an exception, you can throw it and it will kind of a short circuit the whole rendering stack.
2:24:50
But again, not as useful and graphical, but it's still here.
2:24:53
So in our case, we're gonna ignore it, but we can use this to set up our query.
2:25:00
So we just have some sanity checks making sure we can't have a negative page.
2:25:03
We based on the page, we can calculate an offset and then we're actually gonna build a query and select the first, you know, 20 results at offset, you know, whatever based on the page.
2:25:14
So zero or 20 or 40 or whatever.
2:25:16
, so we're just kind of basically setting up a page result in the constructor here on create and then we can use that because we're gonna need to refer to it twice.
2:25:26
We need to use it to get the items.
2:25:29
So we're gonna have, you know, some number of recipes we're gonna render out as well as for telling the in the response we want to tell them, hey, there's more, there's more data you can get if you want.
2:25:38
So we need to tell them that there's the next page.
2:25:39
So the page result gives you the items, it also gives you page stuff like page number, you know, as there as there previous results.
2:25:48
Is there more results after that kind of stuff?
2:25:52
So the way you saved it here is we don't have to make the same query twice.
2:25:56
because queries are expensive, of course.
2:26:01
So now the query itself, query from recipes.
2:26:07
So here we're gonna rather than query from all, we don't want to get archives, draft stuff.
2:26:12
We only want the actual published recipes so we can query from recipes specifically.
2:26:17
This might be a little weird.
2:26:18
So by default, Brightspot will query my sequel, which is like I said, the source of truth database and only when you have a full text search, will it force it to go to?
2:26:28
So, because my sequel doesn't support full tech search.
2:26:32
But we don't know whether basically if we had search terms, we would be a full tech search, otherwise we would be a, a mysql, you know, regular search.
2:26:41
But for consistency and performance, we're gonna force it to solar always.
2:26:46
So star match to star is sort of a shortcut.
2:26:47
It just, it matches everything but it forces it to go to solar.
2:26:50
So it won't restrict your results, but it also won't let it go to my sequel.
2:26:56
And the reason for performance is that you can see basically any time you have an end on the query that's a join and joins in my sequel are expensive and my solar has much better performance dealing with those kinds of queries.
2:27:10
So typically any time you have more than like one or two joins, you're gonna want to force that to solar.
2:27:16
We also have the site here.
2:27:21
So that will be filled in by graph QL.
2:27:24
And if we have a site, we want to filter the results to only those in that site.
2:27:31
The site has a method for that and then the rest of this is just going through each parameter.
2:27:36
If it was provided, we add it to the query.
2:27:38
So if we add a title, then we're gonna do a matches on the title.
2:27:43
If we have a difficulty, then we're gonna match on the, on the equals.
2:27:46
So this matches is gonna, is a solar.
2:27:48
So if we had this, we would force the solar, we wouldn't actually need this, but we don't know for sure.
2:27:52
So we just kind of do this at the beginning for safety and then we might have additional matches if we had parameters provided.
2:27:58
, Then the difficulty, you know, we just use it equals because that's just a regular, you know, equality comparison, more matches on the tags and then more matches on the search terms.
2:28:16
And what we're doing here is actually supporting like a phrase search.
2:28:20
So each of the search terms, search terms is a list.
2:28:23
Each one could have multiple words in it.
2:28:26
And so we're gonna use a query phrase to force it to match those words.
2:28:30
as if you had like typed quotes around them.
2:28:34
We have this proximity though which allows them to be like in, you know, swapped or interpolated or whatever to some degree, but doesn't have to be the exact match, but it has to be a close match.
2:28:46
So then with all that, we can return the query and then up here, the query actually gets executed and we get, you know, whatever results match, store that result.
2:28:57
And then you can actually use or get items and get next page to fill out the response.
2:29:01
So this is what actually gets exposed in the response is it'll be items field, there'll be a next page field and everything else in here is just, you know, I guess the parames get exposed in the but this is all being exposed in the response.
2:29:16
So now here, every time we've seen a a create views before, it's been like some kind of field interface here, we don't have any field interfaces.
2:29:25
We're just coding direct with V models.
2:29:27
So we're just saying recipe article V models API V model is what we're gonna use.
2:29:31
And so it's kind of up to us to make sure that what we pass in here is actually compatible with that.
2:29:36
So this API V model is a recipe.
2:29:42
So we need to make sure that whatever we pass in here is compatible recipe.
2:29:50
In our case, it's a passionate result recipe.
2:29:54
So we're fine.
2:29:55
But by kind of passing in the model directly, it's up to us to make sure that it's actually compatible.
2:29:59
Whereas if we pass in the fuel interface then, right, I can go and find something that works.
2:30:06
Yeah.
2:30:08
So here's our recipe API V model, but in the java Doc don't confuse it with the handlebars one because they are look kind of similar, but they are serving different purposes.
2:30:19
We have the field, the view interface, annotation directly on the view model.
2:30:23
And this name here is what will show up in the schema for graph.
2:30:26
You will, if we didn't provide this, then I think it'll be based on this or maybe this.
2:30:34
so it'll kind of clean, it'll just use the class name and maybe strip off some stuff.
2:30:38
, so in our case, we wanted to just be a recipe.
2:30:42
, so it's a view model and takes recipes as a model.
2:30:47
And we're gonna basically in that here, we're gonna create views once for each recipe that was returned from the query.
2:30:57
So this view model will get created maybe up to 20 times for each request once for each view mo once for each recipe that was returned from the query.
2:31:10
So here, this is very similar to what we saw before where we've got, you know, cook time is an integer.
2:31:15
Now, difficulties, a tar sequence directions is a tar sequence.
2:31:21
With this case, it's a rich text.
2:31:23
So we're actually cheating a little bit here maybe.
2:31:31
So we could use the, the earlier we were using witch text details, build html.
2:31:43
What we're doing here is actually just like the raw rich text processing that this is doing for us.
2:31:50
So this is just kind of a wrapper around this rich text view builder.
2:31:57
The reason for that, it's probably just to show you how I guess we wanted to get a string out of it.
2:32:02
So the one this is going to return a list of views, whereas we need a string for HTML.
2:32:10
So we have to, we can't use the rich texture to because it's not really intend, it's really intended for handlebars use.
2:32:16
So I guess you want just to HTML string.
2:32:20
So we're doing our own rich text handling ourselves, but it's very similar to what we did before.
2:32:27
Here's that great view we saw here, we're passing in the model as well as the reference to the field that contains the rich text.
2:32:36
What's special is here.
2:32:37
These are the preprocessing.
2:32:38
So Brightspot rich text has an internal is an internal format has to be processed before we can expose it to the public.
2:32:45
So we're gonna strip out editorial markup like, you know, the track changes and the comments that we saw on the toolbar earlier.
2:32:55
This is processing the paragraphs into paragraph tags and this is adding smart quotes.
2:33:01
So instead of straight quotes, it'll convert everything into curly quotes.
2:33:07
And then finally, we're using HTML to give us a spin at the end.
2:33:10
And so this is what I was saying, it was kind of a cheat.
2:33:12
So here we're actually still using the handle bars stuff because this is the actual, this is actually the handlebars interface field, field interface from the handlebars view model or the handlebars view, sorry So this mayor may not be appropriate for what you're doing in our case just to keep things simple.
2:33:33
I'm just still gonna use the markup from handlebars for any of the rich text but your API maybe you don't want html, maybe you want to have this be more structured.
2:33:45
Maybe XML or who knows what.
2:33:47
So this is kind of an open question how you would actually want to expose rich text in your API.
2:33:57
So here we want to have the image in handlebars, it had its own view, which had its own handling of, you know, the text and whatnot.
2:34:06
Whereas here we have to kind of do all that ourselves.
2:34:08
So we want the text to be displayed for accessibility, of course.
2:34:13
So we need to put that out explicitly and then we actually want the image to show up as well And also be configurable.
2:34:24
So I talked earlier about how the image sizes work.
2:34:27
And so here we actually want the consumer, the API to be able to specify what size they want.
2:34:34
So this image attributes is kind of a magic an annotation.
2:34:37
It just tells Brightspot that this data is going to be exposed as an image to add image ability to filter like which image size you want as well as giving you a option, you want the source or the source set or different kind of ways of presenting the image out.
2:34:53
And the way you provide that is you need to use this image size, get attributes, which will do like all that handling for you.
2:35:00
Figuring out what image size they requested doing the correct data conversions and it returns a map of strings to strings.
2:35:08
So it just kind of would be your signature in the in the view model.
2:35:15
And to get file here is this is the storage item that's actually in like a three or whatever.
2:35:20
So that's what you actually passed into that.
2:35:26
Moritz text here.
2:35:29
We're delegating to another view model in this case for the recipe tags.
2:35:33
We'll take a look at that in a minute now, but this is kind of similar to what we just did in the, in the other view model where we call cra view is passing in a view, a specific view model.
2:35:43
I guess that's everything in here.
2:35:44
Oh, and these, these strings I believe are supposed to show up in the graphical schema.
2:35:50
I think the last time I checked that they weren't.
2:35:52
So I'm not sure why that was.
2:35:57
Anyway, so the recipe tag V model, this is for the API so we've got a recipe tag here.
2:36:04
Yeah.
2:36:05
And then again, this is the name that'll show up in the schema.
2:36:07
And here we're just the only thing we're returning is the name, but returning it as plain text.
2:36:15
So with all that done, we should be able to see some see the API now actually made a couple of test queries.
2:36:32
So the first thing I'll show you is here under the admin area, there's an API section.
2:36:38
And so here's the one we just created.
2:36:45
So because it's a singleton, I guess I didn't wanna talk about singletons.
2:36:54
So single means that there's only a single instant saved in the database on the So creating graphical and class, it's a singleton.
2:37:09
We, when we just started docker up and to and then Brightspot, started up.
2:37:13
Brightspot found this class and automatically created in and saved it to the database.
2:37:18
And that's what showing up here.
2:37:19
I didn't have to do anything to create it.
2:37:23
So actually, I guess this is probably got some legacy data from the last time we did this training is I actually do have some data on here.
2:37:32
Anyway, so how this works is we have end points and then we have clients.
2:37:36
So when I create an end point, I then need to create a client and assign it to the end point so that they can access, access it.
2:37:47
And I also need to give it permission to go into different sites.
2:37:52
And then I create a key and then that way I can authenticate to the API and you can give multiple end points, of course.
2:38:06
So another thing here in the developer area is the graph code explorer.
2:38:11
So I can select our graph Q end point here.
2:38:16
And now I actually kind of get like a just a standard graphical kind of career builder capability.
2:38:22
So I can say, you know, give me, you know, want to get back the title the total time in the next page and then we can just do that and see, OK, we got a bunch of results.
2:38:38
I really wanna filter it by thing about how our salads tag.
2:38:45
So we don't have the one.
2:38:47
This is full tech search.
2:38:49
So even though I, I should still get that result because it's gonna be stemming that anyway.
2:38:52
, oh, and then I can also filter on difficulty.
2:38:59
And again, this is an enum.
2:39:00
So enum we did, in our view model here, we did difficulties om direct pass directly or this is not as strange as, as an so that it gets exposed here in as an enum.
2:39:13
And so I can actually see a nice little drop down there.
2:39:19
Yeah, so that's sort of the basics of OK, sorry, didn't mean to do that.
2:39:33
Still sharing.
2:39:47
So we've got actually another, some more filtering to add.
2:39:56
So before we, we just kind of filtering on strings, you know, matching the title, matching the tags, but we also want to be able to match on the times.
2:40:04
But you know, who knows what kind of, you know, we have recipes that are 31 minutes, 32 minutes to three minutes.
2:40:10
We, we wanna be able to match a range, not just a specific number.
2:40:13
And so we actually have to do some extra work to support range.
2:40:18
So I'm gonna show you how you do that.
2:40:19
This is sort of like a more advanced graph well manipulation.
2:40:29
So the first piece to this is this annotation raft grange Graft 12 parameter.
2:40:38
So this is a custom annotation we've written that will then control how this shows up in the graph code human and how it behaves in the graph code responses.
2:40:49
Oh So I get the process from the graphical request.
2:40:53
So it's just an annotation and then we have this associated annotation processor.
2:41:00
So this is coming, this is coming from Brightspots, graph QL set up.
2:41:03
So we need to have a processor for our annotation and there's two halves to this, there's describing how it shows up in the schema and then there's processing the data that came in off the graphical request.
2:41:18
So for the schema, we're gonna have an object that's gonna have two fields of M and A max and those are both gonna be integers.
2:41:26
So that way they can provide, you know, min five max 20 or they can just provide only one of them they can say anything less than 30.
2:41:33
So, you know, min is nothing blank and then max is 30.
2:41:36
And that would be all the recipes that are relatively quick to prepare and then to actually process the request.
2:41:46
So here's the request that came in.
2:41:51
The input here is the actual fields.
2:41:55
This would, this would correspond to our input type here.
2:41:59
The field here is from the view model and this is the annotation itself.
2:42:04
So we actually can like provide fields on the annotations.
2:42:06
You could have fields in New York.
2:42:09
That would then you could fill it in the view model, tell it, you know what name to use or something.
2:42:13
So you have access to that here.
2:42:15
I guess we don't need anything that complicated.
2:42:17
We just need to get the input and we need to get them in and the max out of it.
2:42:21
So the input is gonna be it says it's an object but it's not, it's gonna be exposed as like a JSON map to us.
2:42:30
So price but provides a utility which just you can pass in any object and it kind of figures out that it's a map and, and traverses the map to get data out.
2:42:43
So we can tell it, get by path, get the main key out of this map and it's not safe and does all that deals with a lot of stuff for us.
2:42:50
So it's kind of a cleaner way of doing it than having to do a bunch of, of chain.
2:42:54
and then we need to, that comes out as an object and then we need to convert that to an integer.
2:42:58
So this object tells two,, you pass in an object and you pass in what you want it to be converted to and it will figure out how to convert it if that's possible and it's also null safe.
2:43:09
So you don't have to worry about any null checks here.
2:43:11
And then, so we're gonna get back a in and a max one or more which could be null.
2:43:15
They might have only given a max, they might have only given them in.
2:43:20
So then we're gonna gonna just bundle it up in a object just to hold them together and this range parameters that we actually peed off.
2:43:30
So these, all these fields are, are the, the type is gonna be range parameters.
2:43:34
So this is gonna inject, it's gonna control this displays in the schema, but it's also gonna inject this field with the value that we returned.
2:43:43
Sure.
2:43:48
So in the range parameter itself, just a pretty simple object just has the M and the max here's our constructor.
2:43:56
And then we, we actually have a utility method here which will add the M and the max to the query.
2:44:02
If we didn't need to do this, we could have done it in the view model, but it just say some code duplication.
2:44:09
So how this actually looks is down here.
2:44:11
Now, here's all of our times.
2:44:14
And so if we have a time, we use it to update the query passing in the query and we had to tell it what field to use.
2:44:21
So we don't know, is this, is this the total time, is this the prep time Each one has its own index?
2:44:25
And so if we pass in the index we want to use in the query and then this will, if there was a min has to be greater than equal to the min, if it's a max has to be less than equal to the max.
2:44:39
That way so that we can update all our queries.
2:44:42
So update our query with all of our parameters and then the rest of this is still the same.
2:44:49
So the query just has more, more predicates on it now.
2:44:58
Then our last step here.
2:45:06
you know, we got, we're building an API here.
2:45:08
So we need to think about some additional things like who's gonna be using it?
2:45:12
Do we need to have authorization authentication around this API?
2:45:16
Do we need to, is it gonna be used in a browser?
2:45:18
We need to have support core.
2:45:20
We need to think about backwards compatibility.
2:45:24
And then in more in general, not just with the specific API but like what kind of entry points do you need?
2:45:30
Like, do you, you need to build something as robust as we do?
2:45:32
But like a whole search API or do you just need like ID and path?
2:45:36
And you know, the consumer of the API figures out what the I DS are, the paths are from other way.
2:45:42
And so I just need to get the data for that specific thing.
2:45:45
So it really depends on the use case, how that entry point should kind of look.
2:45:50
I actually have a question related to that about using next Js or react.
2:46:05
Yes, if you wanted to work with next Js, that's gonna be some kind of client side you know, not client side browser side client that API or for Brightspot.
2:46:21
And so yes, if you wanted to use that, you would, you would probably want to build a graph QL APR maybe it's just AJ on API.
2:46:28
But if you just the only other way to get the data out of price would be to use handle bars in which case it's already serving U html.
2:46:36
So what's the point of doing all that extra work on the client side?
2:46:40
So yes, if you're using a client side rendering, you know, react next to us or whatever, then Brightspot is gonna be responding with some kind of more raw form of data which would need to be exposed by an API it doesn't have to be graphical.
2:46:53
Like I said, you could use JSON, but that would be how you would expose the data.
2:47:01
We also asked about needing a separate server with next, I'm not really sure exactly what you mean by that.
2:47:06
I mean, you do need some kind of like entry point to your clients.
2:47:10
So like if someone needs to go to like example dot com, they need somewhere to get like the original Js bundle.
2:47:17
So yes, you would need some kind of server that could be still served from Brightspot.
2:47:21
And that would probably use handlebars, I guess to just render like, you know, your html with like a in it, something for your, your client to sort of start entry or building off of.
2:47:31
But yeah, you would need some kind of entry point for your client side as well as an entry point for your graph QL or API.
2:47:51
So another thing that runs as part of the bill is the check style, just kind of make sure that your code doesn't get too crazy.
2:47:57
And so actually have an unused import for some reason, I guess I must have been typing.
2:48:02
Oh yeah, I was doing an example.
2:48:15
Yeah, didn't have that.
2:48:24
So our last thing here is just kind of making the a a bit more robust go back to the end point.
2:48:37
We've added this content delivery API theme of this basically allows you to associate the end point to a graph or to a theme which will then give you the image sizes.
2:48:49
So that's important for if you have images.
2:48:52
we've also added the ability to control the access, whether you need a specific client to access it or whether anybody can use it as well as the ability to configure core.
2:49:04
If you're gonna be using this with a browser, just implementation, just kind of pipe mostly dealing with the allowed origins and they allowed headers.
2:49:20
So we can take a look at what those look like.
2:49:46
So you're now on our API we have so we have a loud origin so we can control who can access it with headers they can use as well as like associating a theme.
2:50:02
So again, I published this graph on point earlier.
2:50:05
So I already have this thing, but you would have had to come here, this would have been blank, you have to come in here and select the specific theme.
2:50:14
And then with those in place, we can actually go back to our graphical explorer and also filter the image and give a specific size as well as deciding what you want.
2:50:31
The size we want to heighten the woods back and maybe we want URL.
2:50:39
The size here is gonna come from your Hanna Mars theme that we selected earlier.
2:50:44
So that is if we go here to the front end directory, this is the bundle that we selected.
2:50:51
And then in here on the directory, we've got our image sizes confit.
2:50:57
So these keys here would be what we would have then.
2:51:00
So this is 100 by 57.
2:51:03
So, so we want the 100 by 57 image.
2:51:10
And then now here we get all the images are 100 by 57 and here's the URL.
2:51:16
Well, actually, that's, that's the public era.
2:51:18
So we actually would want to use, he actually wanted to get the Yeah, we want this one not sure that you want the source because that's actually gonna be the crop trl, know you can tell because it's got the resize stuff in it.
2:51:38
So this is resizing the image down to like if we load this, that's been cracked down.
2:51:45
Well, the original image was much bigger.
2:52:02
OK.
2:52:02
So that's that's all the material we have for today.
2:52:08
So we have some more resources for you.
2:52:09
As I said, this, all the exercises that we just did today are in this training depot.
2:52:14
The branch is Exercise recipe.
2:52:16
So at the end of it, here, I'll show you our get history, you know, here's all the stuff we just looked at.
2:52:21
So here now we're on the Exercise recipe branch.
2:52:23
So if you check out that branch, you'll see the completed project.
2:52:28
We have a bunch of documentation on our website.
2:52:31
So basically, everything we've talked about today is probably covered in some detail or more detail there.
2:52:37
And then specifically for the doctor, we've got to read me on that as well because it does take some special parameters.
2:52:41
If you needed to have any custom like needs there, there might be some way to achieve that with the doctor.
2:52:48
And then we also have a support portal and now actually, I forgot to add here.
2:52:51
We have a like a DEV community as well.
2:52:54
So if you want to get access to those, you can contact your product manager.
2:53:01
Yeah, I guess I didn't see any more questions.
2:53:03
There's another question here.
2:53:05
Anybody who has any other final questions, you know, that's your chance.
2:53:14
Yes, there's no more questions about the using React.
2:53:18
Hm.
2:53:21
And it sounds like there's a bit of confusion actually here because so React, you'll be using React on the front end, not in the C MS.
2:53:27
So the C MS is still gonna be the same U I that we've looked at this entire time.
2:53:31
So like this here is the C MS.
2:53:33
If you don't, you wouldn't be building this in React, you'd be building a front end for your content.
2:53:40
And so you still, you wouldn't lose by using Reac you wouldn't lose the ability to like do any publishing in here.
2:53:48
Yeah.
2:53:49
So I, I'm not sure if I understand your question.
2:53:51
But yeah, you, you still have the full editorial capabilities.
2:53:58
You just are presenting your content to the public in a different way.
2:54:11
All right.
2:54:11
So another question just clarifying about the root style.
2:54:14
I let's go back to that.
2:54:21
This is all complicated.
2:55:41
So the underscore rapper Jason provides that that wrapping markup that's only for preview.
2:55:50
And so it looks like a regular view, you know, we've got keys, we got values.
2:55:56
It's got a template but this is gonna be kind of used for things that are not standalone pages.
2:56:05
And this is so this is, it's gonna have a template.
2:56:11
oh And the main thing here is this name here.
2:56:13
So this underscore delicate is like the correspond to the underscore wrapper.
2:56:17
So basically how this is gonna work is in our recipe module.
2:56:25
This is a module.
2:56:26
So we want this to be wrapped up into that preview page.
2:56:30
So the underscore rapper key I don't need to provide because it defaults to being yes.
2:56:37
But in the case of the page, the article page, I do not want this to be wrapped up in this preview page mark up here with this with the rapper Jason because it already has its own HTL body tag, all that stuff.
2:56:50
It's a standalone page.
2:56:51
So I need to tell it false.
2:56:53
Don't wrap this up in anything.
2:56:55
It stands alone don't wrap it up.
2:56:57
Whereas the recipe module we can use the, we can just, I mean, there is an implicit rapper here which I think the syntax would actually be like this.
2:57:10
And so this tells it to use the rapper Jason to wrap itself.
2:57:15
I don't need, I don't need to type that in, but that's just sort of the default value.
2:57:19
And it's only when I only need to override the default.
2:57:20
Do I say false?
2:57:25
So then with this because, because I have, I'm using the default whenever I want to render this in preview, it'll just use this as the entry point, Jason.
2:57:36
And then this name here will say this is where you put the recipe module or whatever else it is that we're trying to create that gets kind of injected in here.
2:57:44
And so this was, it's delegated to some other view model to inject to render out the actual thing we're trying to preview and the rest of this is just boiler plate.
2:57:55
And so how this works on the on the flip side is we have our recipe preview view model and this implements preview entry view.
2:58:07
And this preview page view is what came out of that underscore wrapper equals Jason.
2:58:12
So underscore rapper Json.
2:58:14
So this this preview page address, this is this is gonna create preview page view and here it is, here's our implementation.
2:58:29
We've got some boiler plate down here, I have to look at that.
2:58:32
But then the main here is that we need to then render out the actual thing we're trying to preview, which is a recipe module view.
2:58:40
So here's the model of the recipe.
2:58:42
We've already got a recipe in hand because that's what we're trying to preview.
2:58:45
But I need to tell it, go render out the view model that goes in the main field of preview for recipe.
2:58:52
And so we can see here there, there's a bunch of options for that, but the only one that should match is recipe module view right here.
2:59:04
So that'll find our recipe module, a recipe view model.
2:59:11
And here's the model recipe.
2:59:12
And so that I'll then use this to render out the main area.
2:59:15
And so what that actually looks like is this?
2:59:23
So here we have, here's our list, our recipe module, but then it's been wrapped up inside that preview template.
2:59:33
Don't know if that helps.
2:59:35
I answer the question.
2:59:37
Look, I won't have any other questions.
2:59:41
So thank you all for joining.
2:59:44
And best of luck with developing the Brightspot.