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.
|1. Demo of CMS||00:01:15 - 00:17:15||16 mins|
|2. Brightspot and Dari||00:17:15 - 00:18:45||1 min & 30 secs|
|3. Root styleguide||00:18:45 - 00:22:35||3 mins & 50 secs|
|4. Recipe exercise|
4.a. Root styleguide
|00:22:40 - 00:43:00||20 mins & 20 secs|
|4.b. Frontend bundle||00:43:00 - 00:44:13||1 min & 13 secs|
|4.c. Data modeling||00:44:13 - 01:21:15||37 mins & 02 secs|
|4.d. Viewmodels||01:21:15 - 01:47:45||26 mins & 30 secs|
|4.e. Indexes and save lifecycle||01:47:45 - 02:12:20||24 mins & 35 secs|
|4.f. Modification||02:12:20 - 02:25:18||12 mins & 58 secs|
|4.g. GraphQL API||02:25:18 - 02:51:00||38 mins & 40 secs|
|Conclusion||02:51:00 - 03:00:58||9 mins & 58 secs|
Hello and welcome to Bright Spot back-end training.
My name is Brennan, I'll be going through are examples today teaching you how to work on the right spot back end and doing job and development on that.
So our agenda today, we're first going to have a demo of the Bright Spot CMS and we're gonna be looking at it from the perspective of a developer, we'll talk a bit about terminology with Bright Spot versus Dari.
Then we're going to review the route style guide which was covered a bit in the front end training if you saw that yesterday and then we'll spend the rest of the time working through a sort of extended exercise where we're going to create a new content type, create the data models, figure out and describe how to render it.
and then we'll do some more advanced data modeling as well as touching on graphic well so for the CMS here first of all all of our examples are going to be in this training repository which is publicly available so everyone should be able to access that and see the code and run the Docker and front end development applications from this repo so I've got the repo checked out here.
I'm just done the develop development branch and I've already done a build but I'll show you what that looks like.
this is how you would run a normal build to save time.
I'm gonna skip the tests and we use a great deal to run the builds greater was heavily cached.
So since I've already run this bill, it should it should complete pretty quickly here within 30 seconds or so.
And then once that's done, I'm going to start our Docker container here for that.
So we have at least dr composed to manage a couple of different containers that run bright spot, there's a tomcat container and Apache container.
My sequel Solar.
So docker compose will start all those up automatically for me and then I recommend always following the logs somewhere so you can kind of see what's going on and helps with debugging and just being aware of what the server is up to.
And usually this logs will give you all the containers, logs kind of intermingled.
So usually I will filter those to only get the tomcat logs.
So I'll have that just running here the whole time and I'll be able to go and check if something's going on and once that starts up I can load the CMS by going to local host slash CMS.
You can see here, it's kind of booting up and sort of doing some pre launch processes to set up everything for a bright spot to run.
You can ignore this.
This is an error with M1 Macs which I'm running here.
This is our spell check library is incompatible with that processor.
if you see that area, you can do it when you first create the environment, there's no accounts list and you can just type in any user name and password and it will just automatically create an account for you.
So I've already got an account on this environment you can ignore this anyway, so this is our CMS.
When you first started up this is what we call the dashboard sort of this is configurable and you can choose which widgets will display here.
I haven't done anything in this so I actually don't really have any anything to see.
But if I kind of switch this filter to any user, I can see that we've already got some some content that's been published in this environment.
You can custom create custom widgets that will display on this page.
We're not gonna cover that as part of the training but you may be getting requirements to customize this in some way.
And that capability is available again, not covered in this train.
So kind of a starting point for editors to find things, is to go up here to the search.
If you click into that, you get this pop up and you can see we've got sort of some search filters on the left hand side here and on the right, I can see the results.
So I can filter by content type and I can see all the articles and I can filter by publish date and only things that are published recently.
I can also filter on some articles specific fields like tags or the section.
So we only want to look at the inspired perspective projection.
and then there's other content types and those might have different filters so the image doesn't really have anything specific.
That gallery has tags again.
So these are kind of contextual based on the content that you're searching.
one nice thing for developers is there's this advanced query here and this allows you to actually type in the same syntax that you would for a query in java code.
So something might not show up as a filter here, but if you know it's available you can actually just type in a specific query using that syntax and then it will actually filter things down.
So typically editors won't use that unless they're more, you know, technically advanced but as a developer it's really nice.
Probably the most common thing I use for that is if I want to search for something by I.
I can actually just type in here.
Equals U U I.
And I know I've got the exact result and not just something that happens to have a similar title if I was searching my title or something else.
And then finally if you don't find what you're looking for, you can always go and create something.
So down here at the bottom you got this create menu and you can just choose to create a new instance of a content type, That same menu shows up here in the plus.
If I didn't even open the search, I can justify.
No, I just want to create something, I can just hit the plus here and go straight to it.
and obviously if you click one of these, you'll create a new one of your new author of the article as well.
So let's take a look at an article that's already been published.
So this is what we call the content edit page.
I'm gonna make this a little wider so we can see all the pieces a little bit easier.
There's sort of three main sections here.
We have the sort of the content form or the fields on the left hand side.
And then in the center here we have the preview and then on the right we have the widgets and the widgets.
They're also widgets that can show up at the bottom here, like the conversation widget or the analytics widget.
You can actually with this that show up at the top here above these tabs, those are less common.
Usually use those for like alerts or warnings or things like that.
in the field to there's a couple of different types we support.
You'll see these first two here or what we call rich text fields and they have a toolbar which allow you to format.
So I could I could change this to be a talic you know here on the right hand side let me make this a little bit bigger.
You can see here that this sort of live updates as I as I change things on the on the left hand side here in the fields it will update on the right so I can see as I'm typing what it's gonna look like.
So those are just text but we also have plain text field.
This is a plain text field here so there you can see there's no there's no toolbar, you can't change the format and you just get some text.
and this one actually has a placeholder.
So even if I don't type in the value, there's some default value that's that's populated in there.
So now this is a reference field here so I can choose an author and this will be some other object in the database that will just be referred to from this location.
potentially there could be indexing associated with that, so we can search articles by the author.
And then this is a multi select so I can actually choose multiple authors.
A single reference field looks like this where we have, this is the parent section.
So we're in the inspired perspective section here and again you can china pick value but you only can pick one.
There's no plus button to add a second section.
And for all of these reference fields you'll see here, there's a magnifying glass and a edit option.
So magnifying glass will open a full search.
So if I don't like what I'm getting from this drop down search, I can go to the full search to then filter down, you know, more more finally than just typing in the value here.
Like this is has some kind of type of head benefit typing isn't finding what I want.
I can use the advanced search here basically to for the full search to filter that more effectively, and I can also hit the edit button to actually open up that hole piece of content in a pop up and edit that directly before returning back to the main thing that I'm working on, which is the article.
you'll notice here this lead.
it's kind of inset a little bit and you can see I can actually expand and collapse it and it's got more fields.
This is what we call an embedded record.
So whereas these authors are some other object in the database that I'm just pointing to this lead here is actually an object that's wholly contained within this article.
So there's no separate database object, but it still is a separate object in its separate class in the java code base.
but it's kind of stored within here.
And so the editors don't need to do a search to find the lead.
They just created every time, you know, from scratch when they create an article.
this is another reference field here to an image but we've got some sort of special handling on that field to actually show the image in line here, so I can actually see what it looks like without having to go open up the actual image to edit it in full.
another thing to notice how the fields are organized here, so we've got what we call tabs across the top and then within a tab you can organize fields into clusters.
There's not, there are no clusters on this main tab and if I go over to one of these other tabs, you can see here that we've grouped some fields and underneath this what we call cluster, which is just a bunch of fields and can expand and collapse together.
That's another thing to kind of think of when you, when you're creating your content type is how do you want to organize the fields across tabs?
And then within the tabs across clusters, this overrides cabin is sort of special, a lot of the fields on here, kind of our inherited from not quite inherited is the right word, but basically they kind of come from different locations and I'll talk a little bit later about the interface for that and then the styles tab is also kind of special, this interfaces with the front end to actually pull in style.
So in this case I don't really have anything but some content types might have, you know, different way of organizing.
You know, maybe they have like a big lead in a small lead or something and so you can kind of choose that style and you'll get a different look in the front end template.
we talked about placeholders already, you can also add notes.
I don't know if I have any notes in this, I'm having notes on that tab.
I believe there are some notes here.
Yeah, this is a note here, so like I can put an explanatory text to describe what's going on or in this case we've got some fallback logic, so we're describing what we're falling back to for the navigation.
and then in I want to look and take a look at this image again.
this sort of, there's another special feature here called the file field.
this is a reference to an asset in some kind of content management, content C D N or S three or something.
and so we actually can kind of display a preview of that and since it's an image we actually get some special ui where you can actually edit it, you know, the editors can come in here and you know, fix things.
actually decide when it's an image is cropped, you know, how what we should actually focus on.
So I can I can say this is the crop whenever we use this size this will be the way the image gets cropped down to actually display to the front of users and if you don't want to go through each one of these crops and manually move each one around, you can actually set what's called a focus point.
You know the most important spot of the picture and then that will be sort of centered in whatever crop gets gets made.
You know here is I edit things that we actually get some sort of an indication that something's changed.
This turns yellow and you see up here we have this work in progress created that so if you don't lose my internet connection my work will still be say that I can come back to this image and pick up where I left off and then if I was to save that then I will save that and clear that work in progress and actually just make that the new version.
I think that's it for the content edit page.
Now I'm gonna take you to the sites and sites and settings.
So if we go to this triple or hamburger menu up here on the top left under the admin cluster here I can choose sites and settings.
Here's another little safety feature just I missed and made some kind of change on the page.
And so it's saying hey do you wanna, do you wanna lose that page that work that changed.
I don't care.
In this case, I just leave.
so here's the sights and settings area.
right spot groups content into what we call a site, which is basically a bunch of web pages that all share the same U.
You know, it would be, you know, bright spot dot com would be potentially be a site.
you might have multiple sites in in one bright spot instance, each one has its own sediments and those are all configured here.
Again they're kind of like the continental form of the article where they're kind of organized into tabs and clusters.
and then we also something called global settings which are settings that applied across all sites.
And there's also some fallback behavior where you might see the same field in both sight and in global and there's typically some kind of fall back where if you haven't configured it in the site, then it will fall back to the global value.
Oh, one of the handy thing is you can actually search for a field.
So I'm looking for a cow, you know, I don't have to go figure out which tab and cluster.
It's kind of just search and it'll show me all the fields that match that search.
you can add your own fields to both the global settings and the site settings.
There's an interface for doing that.
I don't think we actually cover that explicitly in this training but we will go over the approach of how you would do that in the advanced data modeling section toward the end.
Okay, that's it for our tour of the CMS.
so let's move on to bright spot and die.
So what's the difference between these two?
So bright spot is the CMS.
so that's gonna encompass things like the editorial user interface, the request handling and the view system, which is how we render out html or graph QL or whatever we're serving to the users.
It encompasses permissions, workflows, notifications and other things that used to manage kind of the editorial process, content creation process.
Whereas Dari is sort of the underlying framework that powers a bright spot and it encompasses lower level things like database abstractions.
the data modeling using standard java types annotations which are going to cover in the data modeling section next and several A P I S for creating the database for storing and retrieving assets files from, you know, S three or something running tasks so on and so forth.
So you might see people or hear people mention these two terms that's kind of what they're referring to.
Oftentimes we just say bright spot generically and kind of mean that to include the entire bright spot ecosystem which would include Dari but sometimes you're being more specific and saying, I'm talking specifically about, you know, the code base and the part of it that's the running the CMS versus the part of it.
That's you know, running the database.
Okay, so Root style guy was covered in the training over a quick question here.
See yeah, there's a question here about how you would control the cluster order.
I believe there's an annotation for that.
I don't remember off the top of my head when we get to data modeling might might be able to find it real quick.
Okay, so rude style guide.
It's sort of a a P I contract between the back and the front end.
So it was originally lived in the project.
Root, you know like the repository.
Just the root directory of the repository, which is where the name comes from.
But now in newer projects we actually put it under the front end directory.
So we still call it the roots dog Guide, but it's no longer in the root directory.
and then the stock is kind of an overloaded term.
We use that to mean several different things.
So one other style guide directory in the project will be in the bundle.
and it kind of looks similar to the roof style guide but it has a different purpose.
So don't get confused between those two, You know, one the roots dog.
So in the roof dog i you're specifying views which are collections of fields that are strongly typed.
So we have a string field called headline and boolean field called display high high display or something.
so it's strongly typed and then each of you actually consists of Jason file and handlebars file.
The handlebars file should be empty and the dog because that would be your template file but the templates all go in the roots into into your bundle.
So and the roots dot guide.
The file has to be empty but it does have to be present again, just due to legacy reasons.
so make sure you create that file but just don't put anything in it.
We also support will be called groups which are basically allowed to have multiple types for a field.
So you might have a media field that could be an image or a video.
And so you can put a group for that and say this field could be an image or a video and then the front and can then render both out appropriately depending on which was actually configured by the editor and then the end result of all this is each of you is going to generate a job interface which then the back end can implement to actually supply the data to the front end.
And that's how we kind of stay in sync with the front end because if you change the style guide, then the back and build will break because the java interface has changed, which means you need to go up update all the implementations to make, to match whatever change that was if a new field was added or field type was changed or the name was changed, that will change the interface which will then break the build.
So keep in mind that the Roots style guide is really intended for use with handlebars.
So if you're working with graph QL or some other ap I you probably shouldn't use the Root style guide.
The interface you interface is that it generates are really designed for handlebars into the format of them and the way that they're abstracted often results in difficult to use A P.
So you might be able to use the roots dog guide for your api but you shouldn't expect to do that.
It's gonna be a case by case basis.
Okay, so let's get started on our recipe exercise.
So we have some requirements.
as an editor, editors need to be creative, be able to create recipes to search for recipes.
They want to be able to feature recipes in an article and then they want to be able to tag recipes.
So those are kind of high level requirements.
So we're gonna actually translate those into sort of bright spot, you know technical terminology.
So we're gonna because we wanna be able to create and search for the recipes that should be a standalone content type and not something embedded within some larger content type.
We're going to create a new content type called recipe article which will reference an existing recipe.
This will kind of be like one of those, you know what you seem like a recipe blog where they have a description of the you know process of coming with a recipe and at the bottom and you actually find the actual recipe itself.
We're also going to create a recipe module.
This isn't really called for in the requirements explicitly, but I do want to cover it because modules are important.
So I'm gonna just basically show you how that works.
And then finally we're going to create a new sort of taxonomy content type for recipe tag which will be referenced from recipes rather than using the existing tags.
Again, just to kind of show you what that might look like.
You could use the existing tags if you wanted to as well.
So as a reminder, all the code is in this repo as well as a Docker container.
So if you want to play around with this code on your own, it's available for you.
Okay part one, the style guide.
So again because the style guide is sort of the api contract between the back and the front end.
We were start we're gonna start with that so we're gonna actually gonna need to new views for this recipe module and recipe article page.
The recipe module is what you would really think of as the recipe will have the ingredients and directions and the process and timing and all that.
And then we're going to sort of make it into the module framework that bright spot provides so that it can be added where the other modules could go just kind of like a generic sort of concept that we have.
And then we'll also create recipe article page which will be based on the existing article page but it's gonna add a recipe.
So I'm gonna go over here now to the code and we'll take a look at that.
One thing I kind of forgot to notice.
We do have a lot of material to cover, so I'm gonna move pretty quickly.
But again we have a recording of this so you can go back on your own time and kind of slow things down and view it at your own pace but just to kind of get all the material and so we don't have to cut anything in the time that we have.
I will move pretty quickly so I've got a bunch of commits kind of stacked up with all the changes for each of these exercises, so I'm just gonna apply that next commit.
check out the next commit and you can see we're just starting on the develop branch and then we added 11 commit and then oh shoot, I forgot before we get started on this, let me just give a quick part of the coaches.
so here's where you know how to actually where to put your code so that we use it.
It's a great old project.
So we used to build the project and then each of these directories here are what's called a great old project or sub project.
so the front end directory here is where all the front end code lives, including the roots dog.
I we mentioned earlier as well as the bundles, which are also sometimes called themes.
and then we also have the roots dog, right here.
Again, they both have a style guide directory.
This one is a very different purpose from this one.
The root stock, so just make sure you don't get confused about where you're putting a code and within the star guy that's kind of grouped in by feature.
So, you know with the article feature and then there's kind of article page and maybe some other templates and views that are necessary for rendering articles, audio, another one.
And then I talked about the group's earlier.
So this is the group directory here.
These are all kind of the concepts we have.
So there's any number of different banners that are supported.
Any number of different forms, elements in a form or videos.
So each of these is kind of, it's sort of grouping a similar thing together and then these can be referenced from other templates to say, I just want an ad to go here, I don't care what kind of add and then any of the ads that are supported, will will work in that slot.
And then finally all of your back end code is going to go in the core directory and this is just a standard java setup.
So you have all the java code goes in source make java, you have properties files, those who go on source of resources and if you wanted to have tests which we don't have in this project, those will go in source test and then java or whatever.
, and then within the job directory we kind of group things again in packages based on the feature.
So we might have article package here with all the articles, content types and what the models and everything would go in that kind of combined directory to get grouping things by by feature.
Yeah, so that's kind of a code base at the bottom we just have a bunch of accessory files, like the docker compose is all set up in here.
so we won't go into any of that.
We do have a read me which kind of describes the general setup process of and want to install prerequisites.
You know, we need a java version and Docker and whatnot.
So that's all kind of described in here if you need to get to your setup going okay, so sorry for that interview.
So now we're gonna look at the style guide.
So we added a couple of new files.
So let's go take a look at those now.
So in the front end directory, because we're working on the road style guide, we've got a new directory now called recipe.
We've got some files in here.
So we'll start with the recipe module.
so views are just a song descriptions.
So a couple of important keys in here.
anything we can even underscore is basically a special key.
So the most important thing is the template key which describes which template this view is associated to and don't hear this file has to exist.
The recipe module that HBs is right here, but again, it's empty.
So any template code will go in the in the bundle itself or the file has to exist for this to work.
So you create the empty file there and then these other fields here are basically gonna be fields in the view, so we're gonna have a title and image difficulty and some other stuff and you can see here the types are described by the value here.
So difficulty is going to be a string.
The prep time is going to be a string all the times or strings as we want to display like five minutes or something in text.
We don't want to just have a number.
and then these includes here are basically pulling in other views.
So in this case these are all rich text.
So rich text can support any number of different elements.
So maybe supports, you know, images or, lists or you know, whatever different things can go in there.
And so we kind of group that again using the group system into different categories.
So we have like tiny, small, large as the medium as well.
So basically what, depending on what kind of purpose this field is, so like the title is really, really want to have maybe put a talent in there or something.
So we'll just use the tiny one.
Whereas the large we want to be able to have in the directions, they wanna be able to have a list with steps and maybe they want to put some images in there to describe each step.
So we'll allow a lot more items in the directions field and again what these files look like.
Let's open that up real quick.
it's just an array of other templates.
So rich text, you know, it can be an ad, it can be a carousel, can be an image pull quote.
So all of those are just kind of options that can be go into that slot.
And at the top here, this just means plain text so it can have text and it can have all these different enhancements.
So the other thing we added here was the recipe article page.
So a couple of special things in this one.
The underscore include helper here and underscore include key here basically is an inheritance.
So the recipe article page is going to extend the Creative work page and whatever fields that creative work page has will just be applied to the recipe article page as well.
Creative work page itself actually extend something.
So we have a sort of a hierarchy here.
Creative work page and that actually includes another one.
So there's several kind of things were extending by getting creative work page.
Again, we have the template key here.
Recipe article page.
We've got three fields, the lead article body recipe article bodies, rich text.
We looked at the already lied and the and the recipe are this, the lead is we're using an include here because there's any number of types of leads, there might be like an image and a video.
So we're going to support all of those.
whereas the recipe is only one kind of recipe, so we just have the template directly referenced here.
Whereas this one's referencing a group of several templates that can fit in the slot.
That's kind of distinction between include template use include when you want to include multiple templates.
And that points to a Jason file and that Jason file would be in the group directory and it would be an array of templates.
And then whereas the template just directly references the handlebars file.
And then the final thing here is this underscore rapper.
This is a little bit complicated to describe, but basically, the way bright spot works this is for preview.
if, so the module for instance, the module is not a standalone pages.
You expected to place a module within some page, but if we want to preview that we need to have some kind of page to put it in.
even though there isn't one, like the editors creating a module, they haven't created the page yet necessarily.
So, bright spot, the bright spot does is wraps this module in what's called a preview page, which is basically just a generic page that holds a single module and exists for purposes of preview.
So that's kind of the default when you create a a view in here credit Jason file.
the expectation is that this is going to be some kind of module or other, you know, component that's not a standalone page and it will be in place inside this preview page that will be generated.
Whereas if you're actually creating a page, you don't want that, you want this to stand alone.
So the underscore rapper false means don't use the default rapper behavior, which would be true under scrapper false means this is a standalone page.
I'm going to provide all the things I need to render a page even in preview.
And so, basically we're not gonna use that, that preview page wrapper.
, so it's a little bit confusing.
the terminology is a little bit strange, but that's kind of what this is going for.
So basically if you're creating a page you want to have under scrap or false.
Otherwise you want to have nothing in which we'll get to the default behavior of true.
, okay, just make sure I didn't miss any other changes.
So the next commit is actually adding.
So let's go ahead and build that.
So I'm telling it to build only the front end project.
So dash needs a specific greater project.
So the front end project is what builds the right style guide.
So when I run that it's going to process all of those files in that style guide and build the interfaces for them.
So if I come back here to intelligence picks up the new changes, we should have two new files.
One should be called recipe module view and then one will be called recipe article, page you.
and this name here comes from the name of the template.
So these names should match up just for consistency, but this could be called anything.
What matters is the recipe module at HBs file, This is the name that matters and that would be what is applied to the job of you interface.
So let's just kind of go through this real quick, just kind of see the correspondence between the Jason and the java.
So, couple of sort of things that got added.
These are what tell bright spot how to use this interview system.
So if you interface means that this is a few interface.
and then these two initiations tell it where to get the handlebars tablet from.
so then we've got a couple of methods in here.
these olive will correspond directly to fields we had in this Jason, so we've got the cook time, which is a char sequence remember here?
Cook time was a string.
So string gets converted into sharp sequence on the java side.
no difficulty in another char sequence directions was a large rich text, I think right directions.
Large rich text.
So you can see here it's kind of comment describing this is all the different things that were in that rich text elements, large Jason group, so adds quotes, block quotes, pull quotes, you know what not all the different things can go anywhere else.
Plain text at the end here.
it creates this marker interface for that so if you actually look at that, this will be added to all of those different views.
So you know now that we've said we're a large fish text now we get block quote and block quote actually got added the recipe module.
View directions field and a marker interface here.
Again, that's kind of how everything stays in sync.
The view system actually figures out the full list of places that block quote view can go and then adds all those interfaces to this extends clause.
Similarly with image here we said image was the image template.
So you can see here now it's created this marker interface recipe module, view, image field and then that is an image view.
and then similarly for the rest of the article page actually, one thing I wanted to show on That one recipe model view the top here.
We actually add some extends added to this model view and this is this is the preview setup, I talked about the rapper.
So basically anything that has underscored rapper True will have this preview page.
Main preview page, view main field annotation attitude and that means that it can go inside the preview page for use within preview.
and then we also have this recipe article page because remember we said that recipe can go into this recipe field here and so that's okay, that's why this the same phrase got added and then if we go to recipe module view itself again, we have those annotations at the top sorry, wrong one article page.
We have those annotations on the top and in this case we're extending creative work page and that's because we included creative work page here.
So we're extending that views.
We get all the, this has any number of methods and so we get all that stuff for free.
and again in here these fields will correspond to what we described here.
So like the lead article body and recipe.
Oh so there's a particle body, this is the large rich text.
The lead was the leads.
So we've got carousel figure page heading and video lead.
Those are all added to this invitation for this interface.
And then we've got the recipe itself.
Here's the recipe article marker that we talked about.
all of these interfaces have a builder created on the bottom sort of a legacy code at this point, we generally don't use that instead you I'll show you how we will use these few interfaces when we get to view models.
so the next commit here, we're going to add the rest of the module of the modules group.
So all the modules and quote search tabs.
Well not all the different modules, we've added recipe module here so that now anywhere you can put a module you can put a recipe module and if we have to build that again, pick up those changes and what that's gonna do is add a bunch of interfaces to our recipe module views.
Remember before we only had like two, I think now we've got a bunch these are all the places that can hold generic modules.
So Colin containers is basically column of modules above the side below are kind of slots on pages that can hold just generic modules, tabs can hold modules.
These are more containers and then the actual main field of page views.
So like if you have a generic page homepage that holds modules and so you can put the recipe module there and then we still have the preview page and we still have the recipe article.
So that's kind of how the restyle guide corresponds with what's going, what happens in the in the job interfaces, they always stay up to date and it saves you a lot of bookkeeping and figuring out all the places that something can go.
So that's kind of the advantage of of using it just it's a much simpler way of describing what your your structure of your content is.
Okay, so the next work is the front end bundle, we're not gonna cover any of this code in the session, just gonna apply a commit and move on.
but we want to have the work here so we can actually see what we're doing in the CMS and preview.
But again, depending on how your team functions you might have to do back in development without the front end being done yet.
Just depends on, you know, the sequencing of your project and that is possible to do.
I'll talk a little bit about some some techniques you can use for when you have when you have to do that.
But for this training we're gonna just have the front end code already in place just to save some trouble.
Make a little simpler so I'm going to apply that commit there.
I'm actually gonna go ahead and build just so that that front end code is there and we don't have to worry about building that later because great caches build once, we build it once.
So we're not we're not gonna make any additional changes, will just have all that code all ready to go because the front end build takes like a minute or so.
So I'll let that go while we move on here.
So next thing we're gonna do is the data models, every content type that you want to save to the database, it needs to extend the record class and that's the class that comes from Dari, that's what enables Dari to serialize the data and actually store the database and then pull it back out of the database and recreate the java objects that correspond to that data.
Depending on the use case though, you might extend the content class instead and that's something that comes from bright spot.
And basically it just adds a couple of things on top of record, extends record, it adds full text search capabilities, some additional widgets in the in the CMS.
And then the U.
I changed a little bit, you see you'll see like a publish button instead of a save button in the content and form.
So some rules of thumb, anything which an editor can should be able to find in the CMS the content.
Because again the CMS search runs uses full text search.
So anything that's not full text searchable won't show up in the CMS search.
Anything with an editor can should be able to create independently.
Should extend content.
So in this case we want to be able to create recipes just on their own, they don't have to go create a recipe article first.
And so since it's a standalone thing, they can create it on its own.
We need to have it show up in the CMS search needs to extend content if you need full text search capabilities.
but you don't want it to show up in the CMS search, you can extend Record directly, but then you can add this content.
Searchable annotation, that's what actually adds the full text search capabilities and the content class itself.
I think there's a bug in our we just upgraded our node version and it seems like when you do a build now it's have to force it to blow out the cash for some reason.
So we need to look into that.
as I was saying, the content class from Bright spot it extends record, adds these two annotations.
so the searchable one here, you can add, you can add on your own, you don't need to extend content, add that and that will give you full text search capability.
And then if you want your content not to show up in global search, you can actually add this annotation to to do that.
So you will be searchable but it won't show up in the global search and only show up in certain contexts.
so some things to keep in mind when you're doing data models is the editorial experiments is paramount.
So you're creating something for an editor to use, they're going to use it and people might create hundreds of articles a year.
so making common actions as easy as possible and organizing things into intuitive, you know, keeping fields too close together that are related adding placeholders and notes to guide the editors and how they should publish the content hide things that they're not gonna need.
ensuring the label is useful.
The label is what shows up in the this year is a label based, anything in the search or on that on the continent form itself.
When you have the main label of the content, that's you want that to be useful for the editors, it's not necessarily something that's going to display to end users.
It's something that's for the editors to be able to find content and understand what it is.
You need to think about what should be embedded and what should be saved independently into the database.
This has implications about like how the editors are gonna create the content, how they can reuse the content if it's embedded, it can't be reused and what things should be searchable.
And then if you're functionality is gonna need science or global settings, we also have single terms which will be like a single object that's in the database and there's no other instances of it.
So you can kind of have some more settings that don't really fit into the side of global settings, but you still want to have like a single field or value for them, so you can use the singleton class for that.
Okay, so let's go to the code now.
Yeah, I think this is still hanging here, be more emphatic about telling it to clear count.
Okay, so while that's going I don't want to I want to want to plan my next commitment to that done completely, I want to mess it up.
It's running this time.
This is the building before it wasn't.
You have a question.
Okay, that's a question about headless.
We actually get to that later when we do graphic.
Well, I'm going to create an endpoint and actually show you you're out.
I'm gonna get to that.
There's also a question about some new functionality with using a different language besides java, we'll get to that at the very end.
We're not gonna cover that in the string.
So these screenshots here, every template actually gets rendered during the build process so that in the CMS the editors can actually see a little picture of what that template looks like.
So it's filled in with some sample data from the stock or the union bundle.
and that's one of the things that makes the will take a while is because it has to render all these screenshots of every single template.
So usually it takes that's why the funding abilities it takes a minute or two and that will, that will depend on how big your project is.
If you have 5000 templates, it's gonna take a lot longer Than just 56.
Okay, I think that's pretty much done just building the war file.
Okay, I got that.
Just make sure I know all the things that are in this.
Okay, so the first thing we're gonna do is create the recipe.
So this is a recipe data model.
Again, we said recipe is gonna be standalone content type, so we're gonna extend content and then we're gonna have some fields and as you'll see these fields directly correspond with the fields that show up in the CMS.
So we're gonna have a title field.
It's going to be rich text using the tiny rich text toolbar.
if you wanna take a look at that as just describes the things that can be will show up in the toolbar.
So basically we're gonna have a standard formatting, bold italic underline Strikethrough superscript subscript.
and that's basically it.
the field is required, so the editors have to supply a value in order to save the content.
I'll talk a little bit more about interpreting what required really means from a technical standpoint in a little bit.
but it's not quite it's not quite what you think.
We also are going to index that, so we can actually search for recipes by title.
The title is meant for display on the front and we're actually gonna also supply an internal name which the editors can use to have something more useful for them in the CMS, they might have a difference, I might have a pattern they want to use for organizing recipes that they don't want to show to the end users.
So we'll supply a separate field for them to do that if they wish.
we have a difficulty, difficulty is actually an in so we've got three different options for that.
Intermediate expert and this is going to result in kind of a drop down in the CMS and so the editors can pick a value, ingredients and directions, seals are both rich text with different toolbars.
and you'll see that they have this in line false flag.
This one didn't have the flag that actually is a value that defaults to true.
and what that means is in line truly means we can only in line elements so we can have, you know, bold italic underline, but we won't be able to have paragraphs or things like that.
so that's the default and in which case is fine for the title, but for the ingredients and directions we'd really like to have paragraphs and so we'll put in line false when you get to view modeling, I'll describe the main thing you need to be aware of when something is in line false.
but other than that it's kind of just, it'll kind of control a couple of options from the toolbar that will show or not show.
So like the large rich text toolbar.
Only in line if you had if you use a large text toolbar within line.
True then not all of the rich text enhancements will display.
That's a pretty rare case.
Pretty much anytime you use large, you're gonna be in line false in line two is gonna be more common with the timing and the small we're gonna image field.
This is going to be a reference to another image in the database.
And the reason why you can tell that because there's no embedded annotation on this class and there's no embedded invitation on this field.
So since web image extends content, that's gonna be some other object in the database.
It didn't extend content and it didn't extend record.
This would actually be an error because you can't have some arbitrary object.
In a field it has to be something that dark understands, which would be something that extends.
Record directly or via some other class like content.
We're gonna have a list field.
This is gonna be a multi select.
So the editors will be able to pick any number of tags.
And again this is gonna be a reference recipe tag is a standalone content type.
So they'll be able to find an existing recipe tag or create a new one and then reference that from the recipe and then we've got a bunch of integer fields.
So these are the times.
so the prep time in active cook time in this whole time and you'll see here we've actually got another imitation.
So the field name is called Total override.
That's kind of just described to us as developers.
That this is, you know, there's some kind of fall back to hear what this field is gonna fall back probably to these three added up together.
but we don't want the editors to see that we'll just change the names of just total time and then the override will only be in the code base because it's only intended for the developers.
That's something to think about.
You should name your fields, think about naming your fields in a way that's useful for developers.
And then if that's not useful for editors, you can always use the display name again, writing code here.
So it makes to make this convenient for developers and then kind of do some things to make sure that it's also convenient for editors.
We've got our standard getters and setters.
One sort of rule of thumb we always follow is if you have a collection field.
So list sets and maps aren't really collections but maps and you kind of feel that, you know, sort of going to contain other objects inside of it could be empty.
We never want to return all for those fields.
It just saves a lot of null checks and and null pointer exceptions.
So general you're gonna have a check in your getter.
If it's no just create a new value and value and set it on there and then return it and make sure you always do this step because you don't want to just if you just return a new array list here, then if someone wants to modify this list, it's no it's not linked to this recipe, so those changes will just be lost.
And then next time someone calls the recipe tags will get a brand new list.
Since then you wanna make sure you link that list to the field and then return that so that everything kind of stays in sync.
I'm not at the bottom here.
We've added some additional methods here.
So here's our collect our calculating the actual total times.
Remember this is the override up here.
So the actual value is going to be coming out of this method because we got a computer based on the other values.
And then we've also got distracted out our fallback logic.
So that pattern here is get the override if it's null, then we get to fall back and the fallback is just all three other times added up together.
Make sure you always know, check any of these values could be null, even the title even though it's required.
That could still be no potentially.
So any time you access the field, you should be aware that it could be null and act accordingly.
Do a null check or whatever is appropriate.
So in our case we're gonna collect these into a stream filter out the null values.
Maybe there's no prep time, there's just no inactive prep time.
So the some of these could be no and then we just send them all up together really quick we'll take a look at the recipe tag.
This is very basic.
Just two fields name and internal name with the getters and setters for that.
And then the name is gonna be index which makes sense for a tag you wanna be able to find that easily.
It's required because having a tag without a name is kind of useless.
And internally in here is the same kind of thing with on the recipe, this name might display on the front end and then this name is and the CMS.
So let's build that can actually take a look at what this looks like A C.
So the doctor here running tomcat sort of watches the war file that you build and will automatically deploy.
the new war file once it builds so you can see here actually can see that the logs are starting up un deploying go back up to that on deploying the old context and then loading the new context.
So whenever you build you can take your lives and just make sure that it did pick that up that changes occasionally it will miss that.
And then we can load the CMS here.
This will take a little bit cause you're gonna have to sort of redo the startup process.
Once that's done we can see we should see some new content types and I can show you the platforms and how those correspond with with the code that we just wrote.
Okay so here's our recipe.
The title here is rich text with the satiny rich textile arts which only got multi talic underlying and a couple of other simple things.
here's our internal name.
This is plain text.
Here's our difficulty with our three values ingredients and directions are rich text with different toolbars and then we have our image which is a reference.
Go pick an image out of the database.
you can pick tags, there are none but I could create one here like appetizer when I can select that and then I can put some times in here and these are all supposed to be numbers.
So if I was to put in some text there we can see here that we got an error.
That's just on an integer.
We also got an error because I didn't supply the title so there we go credit a recipe so it's a couple of things we can improve here ideally this internal name would display as the label.
It's not though, it's using the title through the default behavior is to pick the first string field title in this case.
So I want this to be the internally label instead of the title.
and I'd like this to kind of fall back.
I don't type it internally be nice if it said some recipe here, just placeholders so they don't have to fill it in if I don't want to.
recipes are not pages, so we don't need these U.
That's that's an extremely steel that's not gonna do anything.
You create a recipe article and that will be where the lives.
And then this total time here would be nice if there was a placeholder to say that this is going to be 15 minutes if I don't change it.
So let's go ahead and add those.
So that is the new things here.
First off the urals widget, so bright spot provides an interface called content, edit widget display, which lets you control which widgets actually show up for a piece of content, how it works is you get passed in the widget name And then you can return true or false whether it should display.
One thing we noticed though, is that basically 90% of the time?
Even more than that?
Probably people were just hiding the urals widgets.
So we went ahead and made an interface that just does that for you, so you don't have to copy this code every time.
So chances are if you want to hide a widget widget and so you can just implement direct directly.
otherwise you can extend content with it.
Content, edit widget, display directly and put in your logic whatever you need.
Maybe you want to hide the widget only during certain times and certain certain fields or null or not null or whatever, you know, so we've added that to our recipe to hide those rails.
we added a placeholder to the internal name.
The placeholders, you create a method, it can be private and that's where you put your text, your return text out of here, which will be the placeholder text and then use the annotation to say where that text should go.
So in this case the internal name, we have this placeholder and use the value from that method to generate the placeholder.
We've also got and cleaned up some of the numbers, so if you remember before, there's a lot of screen real estate that's taken up here for just this tiny little number.
So we can actually do is use this CSS class annotation to add a CSS class to the seals, which will make them smaller.
So is third means it'll take up a third of the, which which means if I have three is third in a row, then they will just take up the full width.
but this is the work, you have to have every field that you want to kind of be on the same line has to have a class that would be compatible.
So there's like it is half, so you could use it as half and it is almost values, there's a couple of different values and so you make sure you kind of line things up so that it'll fit on the same line, if it if it doesn't fit, it'll just extend as if you didn't have the CSS class there at all and I can't remember, this is actually documented what the values are.
So I'm not sure if I can point to this full list but there's a couple of different options for that.
We've also added some notes.
It wasn't clear before what units, these were just said five.
So now we've added that, it's gonna be a minutes and then we actually added them all into the same cluster.
So cluster is basically just a name, so I can put that name into a static string field.
To the timing cluster and then have a reference that same value in these.
Each field has a cluster annotation and they will all get put into that same cluster.
And then finally, for the total time we've had to fall back for the placeholder for.
That as well and that will just be that fallback method that we already had earlier and similarly for the recipe tag, we had to play soldier for the internal name and then to actually use that as the label.
We have to override the label method and put in our logic to use the internal name or use the fallback and similarly on recipe, use the internal name or use the fallback, which would be the title.
And one other thing to note is the title was rich text, remember?
But the internal name is plain text.
The label is also plain text.
So in our fallback method we need to convert that rich text, plain text, otherwise we might see, you know, ampersand and he, you know, in the in the title of some of that type in and coding will be off basically.
So with those changes, you can build again, there's a question here about some duplications, so I think what you're talking about is Mhm.
Yes, so this is this is the old way of doing a placeholder that's deprecate id.
So you might see that in some code still supportive, but it's just you're supposed to use this newer approach instead of some more robust and easier to understand.
So yeah, good catch.
And there's also a question here about creating rich text field, a different tools, so that that goes back to this toolbar thing.
So we've talked about like the tiny rich text toolbar.
So if you wanted the toolbar that wasn't you wanted something that wasn't that didn't exist about it, you can implement the tool bridge text tool bar interface.
there's a couple that come out of the box and you can obviously create any number of your own.
basically has too many methods you want is to get items, which is you have to supply that one and that just describes which buttons you're going to show up.
Right, but I also have something called rich text element, which is basically any kind of custom enhancement.
So like if you want to have an image that would be a rich text element or a quote or something, those are all the rich text elements.
And so by default, all the elements will display except if it's in line True, then all the in line elements will display, but if you want to have more fine grained control over that, you can override this element classes method as well.
And the small toolbar does that.
So if you want to see an example of what that looks like.
So basically the small toolbar has all these buttons and then the elements is described by this element classes list in this case only The link will start.
So we won't see images, we won't see quotes, we want any of that.
We'll just see links.
So with that built, you know, we can reload.
So now we see are changes.
So the euros widgets gone here's my placeholder and the label updates or the label gets put in and I can put internally if we do text in here shows up properly encoded and then we've got our timing cluster at the bottom here and now I've got the placeholder and if I was to change this at a live update, should live out there.
Yeah, it's being slow for some reason.
Anyway, Okay, so our next thing is a recipe article and this is basically just, I'm not gonna go through this entire file, basically this is a copy of the built in article was just an additional field for the recipe.
So it has all the capabilities that a regular article has, and it also has the recipe.
The one thing I want to call out here is I guess I didn't do that.
If I wanted the rest of you to display the plot by default, it's gonna follow some kind of order based on what you describe in the class.
But if you don't like that order, you can use this orientation.
still display order and that will let you override that.
So in this case we're kind of forcing this this slug field up higher than it would be otherwise because you don't actually comes from a different class and that's why it's not in this order.
So because of that, I had to kind of add the recipe here so that the recipe would show up toward the top.
Yeah so we got that to build, we can take a look at that real quick and I'm gonna Published one for later.
So again doctor should pick up that Tomcats and pick up that new build.
When this happens, it means that Tonka hasn't finished loading yet.
So that means you're getting impatient basically.
Don't worry about that.
If you haven't done a build recently and you see this then that probably means Tomcat has crashed or something.
But if you just did a bill then this means that Tomcat still reloading, This is actually coming from.
Apache extends in front of Tomcat in our architecture.
And sort of routes, requests to Tomcat, so apaches always live, but then if tomcat goes down then Apache will respond with a 404.
Okay so now we've got our new recipe article content type.
And again, recipes required.
So there's a recipe there, I'm not gonna feel anything else, so just have that, I need that later for some examples.
And finally we've got the recipe modules.
there's actually a couple of classes involved with this.
this is a common pattern that we've come up with.
it's driven by the embedded versus non embedded kind of dichotomy where it's common with modules.
Sometimes you want to share it in multiple places and sometimes it's really only meant to be in one, you know, like in one article or something.
And so to get that flexibility to the editors is actually you needed a couple extra classes to support that both embedded.
So there's sort of four classes you need to make that work.
The base interface for all the module stuff is called module placement.
It's just a marker interface really, but it just means all the things that can be a module basically.
So then it's supposed to be embedded.
So modules are already always embedded, but if you want to have a shared model, you have to so to do some extra work.
So that's what these extra classes here for.
So when you create a module, the standard pattern, if you want to support both embedded and shared, which is common.
Most common approach.
you're gonna create an abstract class which will be have all the fields of the module requires.
So in our case we need a recipe reference and then we're gonna have the ability to override the image because maybe the recipe image doesn't look good in a smaller place.
And so you can put a different image there.
There's gonna be two extensions of this, there's going to be the in line extension which will implement the model placement and this will be the case where you just want to create an embedded, you know, module that's not gonna be shared anywhere else.
And the other extension is gonna be the shared one.
We just call that recipe module This one we've had no year olds with it because it's gonna have its own edit page.
And so we don't want that widget to show.
And it's also implement the shared module marker increase.
Just to kind of makes it easy to find all the different shared modules in the system, otherwise it doesn't really do much.
and then you really want to add an internal name to your shared one so that again, it's easier for the editors to find in the CMS because they can put some descriptive name there that won't display on the front end whenever you an internal name, make sure you override the get label and return to the internal name.
And then finally, we also need this third or fourth class and this is gonna be the shared module placement, so module placement is embedded, so we need to have a reference to the shared recipe module.
So we need this this fourth class to kind of enable that another side of it.
Otherwise this is a really basic class, it just has that reference, it doesn't really do much of anything.
So with those four in place, let me build and then you can just take a look at what that looks like in the CMS, you can understand what all these pieces do.
This example is a little bit contrived because the recipe itself is all already shared.
So we're not really getting a lot by having a shared module for it.
But I did want to call out the pattern because it's very common and it might be a little bit tricky to understand at first glance.
So fat done.
One recommendation for docker that I have is to make sure that you supply enough memory or your doctor.
If you're having stability issues, make sure you come in here and make sure you're not the default which is like two gigs.
eight might be a little overkill but you probably need more than four.
So you can fill around with that setting to a value that makes works for you.
But if you're saying instability it's probably it's a decent chance.
It's a memory issue.
We have our modules done.
So in the search here, I should now see a recipe module option.
And then just override that there's no preview yet because we haven't created the view model.
So that's one thing we need to do a little bit and then if I was to create a page, I had a module here, they have recipe and we also have shared recipe.
The recipe is the in line case we're supposed to create that it's basically the same form we just looked at but it's it's in line.
I didn't have to create a separate content, you know, piece of contact for it.
And then if I add the shared recipe, I can go pick one that already exists.
In which case the one I just published an option.
We have the same recipe twice on the same page.
The ones coming from the in line case, the ones coming from the shared case.
And this class here is that fourth one that we needed to make everything line up.
Because without this I have the shared recipe in the database that don't have a way of picking it on this page.
Whereas the in line case I do have a pick, I can pick that because we extended that abstract class collected directly for that.
Okay, I think this is probably a good place for a break.
Yeah, we'll take a quick break here.
So we'll come back at 2 30 Eastern US Eastern time in about five minutes.
the next thing we have is view models.
The view models are how bright spot translates the data model into representation.
That's useful for some external system, you know, generating html to show users or some kind of data for an api graph.
QL or Jason or whatever.
So bright spot uses the models to do that.
We followed the approach called M.
Which stands for model View view model.
I think this is originally something that came out of Microsoft.
basically it's it's basically just kind of really enforces a strong separation between development, graphical user interface, which would be, you know, html or Js on your external interface, not necessarily graphical.
And the back end, you know, database logic of saying here's how we store this data in the database, here's how the editors interact with this data.
So it's it's basically a value converter or you can think it was like a function that's going to take in a model and then transform it into some other data.
So in bright spot we've kind of taken this this paradigm from Microsoft and sort of adapted it to work in the in the sort of client server pattern that you see in web development.
I think the original version of it was meant for Berghuis, graphical user interfaces like on a computer.
you know, Windows development I guess.
So in bright spot the model can be any class but is often record or recordable is an interface that provides, it just means a record basically.
The View model is a class which extends this abstract class called View model and that class is generic is parameter.
Rised on this parameter m which is the model so it might be a view model of article or something like that.
And then within your View model the model is never know.
So you don't have to do any null checks on that.
If it did, if you try to create a knoll view view model with a null model, it would just return null and not even bother to create the view model in the first place.
And then the view itself is from the back ends perspective is just the interface.
We already talked about that from the style guide.
but it's actually bound to some method of rendering it, which those handlebars annotations.
We saw one way of doing that but you could potentially bind it to just generate Jason or other things and then with graphical oftentimes we don't even bother with the interfaces, we just kind of code the view models directly and I'll show you what that looks like when we get too graphic well towards the end.
so the view system, you need an entry point, which is where the first view the model that will start to render.
So there's an interface for that called page entry view.
So whenever, you know, someone types in UR l on your website, what bright spot does once it finds the model for that which is tied to the U R.
then it's going to look for a view model which implements page entry view and whose model is, you know, satisfies the main content.
The main content might be some concrete class and then the view model might be some abstract class interface but as long as it, you know, is a super sub class or super class relationship between those two, then a few model is appropriate.
There's also a separate entry point called preview page view.
and this is for preview only so you'll never use that to render content for the end users but it does get used for rendering preview of non page content and just kind of goes back into that thing we talked about with the preview page view and the rapper and the root style guide and all that.
This is another piece of that puzzle.
So usually I can see those modules because modules are not standalone pages, so with that we will go and apply our first female commit.
Mhm Right, so as part of this, I actually at a dependency and so I had to reload the greater project to pull that and that's why that was red for a little bit there.
So here's the pieces, so we've got a recipe view model, it's a class that extends the view model, abstract class comes out a bright spot and here's our model.
So the model's gonna be a recipe and then we're going to implement the recipe module view which is what we looked at earlier comes out of the style guide and this is our view and then this is bound to hammer bars with the handlebars template annotation.
So this will be rendered out using handlebars.
so that you interface had a bunch of methods, basically the only thing we have to do in this class is just implement all those methods and supply the values that are desired.
just a little narrow so you can see.
So we have our chart sequence values.
These are plain text strings, so we've got our cook time.
The difficulty is a couple of different cook times.
for those we just kind of made a private helper method which takes the energy value which could be Noel and doesn't old Jack.
And then we pulled in a library here to just kind of print out a sort of human readable Description.
So this might display five minutes or 15 minutes and it'll kind of be written out.
and then since this is text that's going to be displayed to the user, we want to make sure it's localized.
So we're actually gonna pass in this locale value so that it'll know although this is an English page or Spanish page or whatever and it will use that locale to render out the text.
So this is sort of one of our special things we have with the models is any field in the view model if you put certain annotations on, it will actually be populated off of the request.
there's several annotations available.
The most common ones are the current cows you see here, there's also current site, remember we talked about sites earlier.
So this election be filled in with whatever site you're currently in.
and then you can also get like http parameters, cookies and whatnot.
So all those presentations are available.
I'll have a link to the documentation on on all available values in the slides so you can take a look at what's available.
so they have difficulty remember that was in so we're just gonna print out the string value of that.
one thing to note is menem's default to their constructor names.
So this will be all caps and we've added this to string method to actually convert that down into title case.
So this would be instead of easy, you know, shouty easy.
It'll just be easy with a capital E but the rest of it will be lower case.
So it's nice to look at.
and again, this could be null.
So we gotta have some kind of null check in this case.
We're using an optional, you could also just do an explicit null check whatever is appropriate or whatever you prefer.
Which text is one of the things that requires special handling.
I talked earlier about the in line true or false on the rich text annotation on your data model so that that's something that that matters at this spot point when we get to rendering there's this utility class which has two helpers to help you render out the rich text due to some complex things that pop up due to some complex database issues you actually have to pass in the record class that contains the rich text field.
And then a this is a lambda expression or method reference which tells given the model, how do I get the rich text out in this case?
It says you called to get directions better?
so that in the system we use that and it has to do that to basically resolve further references out of the database properly.
That's why you have to do it that way.
And then the final piece here is given a rich text element.
If we find a rich text element in this text and this string.
Remember rich text is a string.
how do I render out a view model for that?
And so this this is a land of expression that describes how to do that.
So we call the create view method which is a method on the view model.
and then basically we talked to create a view for that rich text element.
This is the same pattern you kind of see right here where for the image to render out the image, we call we need to call this great views method and here's our model and this is a review and then it will go find the view model and render out the data for that field.
So in this case this is the marker interface that we see in all the view interfaces and we can see here that it's implemented by image view.
So whenever whenever we have an image value it'll render out based on that.
this is not safe so if there wasn't an image, I don't have to do a null check.
The previews itself will do a null check for me.
So I can pass in all here just fine when I looked at the string the times here's another rich text same as that one.
These were both to build html.
So that's that that method basically goes with the in line false case.
If you have in line true, you need to use the build in line html instead of build html.
And the main distinction here is paragraph handling.
So this method will generate paragraphs that will wrap each block of text whereas this will leave them without paragraphs, you'll just have break tags within the editor was typing a new lines.
Those will be translated to break tags here, so you won't have any paragraphs but you will just have a single block of text.
so that's all the pieces here.
And another thing we need is this is remember that recipes are not pages but they're also not modules.
they're kind of a standalone special thing that we built because of that, they are not going to work in preview currently because they're not pages so they don't have the page interview or page view model that will display in there, but they're not modules and so modules already are handled with the preview just built in the bright spot.
we've already done the work to to render modules app, but rescue itself doesn't fit into either of those cases.
So we actually have to do some special add a special email to render out recipes for preview.
it's not common to have to do this.
So there's a decent chance you'll never have to do this, it would be very rare, but I wanted to show you the pattern just so you kind of get a sense of how all the pieces fit together for preview.
so we've documented here, this is, you know, for preview because it's not a page level content type and it's also not a module as a similar to our recipe view model, except instead of implementing the recipe module view, we're implementing preview entry view which is the interview interface that I talked about earlier as well as the preview page view and this is what comes out of the root style guide for use in the preview.
So this is the whole rapper key that we talked about kind of plays into this and so there's most of this is just boilerplate, I'm not gonna go over but just just filling in some stuff that the preview page needs to render out.
The main thing here though is this main, so get main we pass in the model directly.
The model is a recipe but we can see here that what's one thing that implements this method is our where is it at the bottom?
Here is our recipe Absolutely is our recipe module view and then also our recipe view model.
So when we when we execute this method, we're going to pass in our model as the model.
So this will be the recipe and then it's gonna go find a view model that works with the recipe and implements this interface in our case, that would be the recipe view model that we just created.
And so this way when we run the preview, it will basically delegate out to that view model we've already written, we're just kind of just basically piecing together tell me about how to render our preview so I build that Oh oh I forgot to delete the, I forgot to delete the import when I did that example of the site station.
Yeah, then we can go to our recipe that I've already created, we should now see a preview render out for that.
We still need to do the modules though.
I said that we were looking at that shared model and it didn't display a preview with the work, we just did, we still don't have the previous for that yet because we need to write the standard recipe module view model and then that will work with the existing support for previewing modules in general.
so if we go to our recipe now about the preview now, we can see is bigger, you can see here, we've got our our stuff display.
so our next commit is the recipe articles.
I'm not really gonna go over this in any detail, just wanted to show a couple of important pieces.
so remember that our recipe article in our Jason, we extended the creative work and that itself extended content page and so there's an abstract view model that goes with the content page.
So if you can extend that to save some boilerplate of having to implement all the stuff from creative from content page, which is stuff like the tags and the breadcrumbs and that itself extends one that's attached to the base page, views, sort of our base page and this is where all like the, all the stuff you might need in the head like phenomenal link and and language and all that kind of stuff is all handled by this few miles.
So you definitely, you're making a page, you definitely want to be extending this.
so you don't have to copy all this border plate because rendering all that stuff, that is pretty much the same for any content type.
So there's no there's very little reason why very rare that you would need to customize any of that for a specific content type.
Here's some of those other annotations.
So the main content would be the whatever do you watch to you or all that that was requested.
So this case is probably gonna be the recipe article.
I'm not really sure why we actually need this.
I probably could just be a model actually but anyway, we use the same kind of pattern so you can see we, you know, we call it great views.
We pass in some some value which is gonna be a model and then we pass use the marker interface to it'll find go find the correct view model to render out.
And then also in this we've got the page entry view.
So this marks this as being a page and so whenever you chris request to you or all of the recipe article, it'll find this view model to start the whole process.
And then the last piece here is the recipe module.
So here's an example of the model Model doesn't have to be a concrete class.
Remember we have this abstract class with the two extensions, the in line and the shared.
So this view model will work with both of those cases.
We don't have to duplicate that, that code.
And then in here is basically we're also implementing the recipe module.
So this is basically the same view model as the recipe one we already wrote except in this case the model is the module which wraps the recipe.
Keep on hitting this button.
This is a utility method that the model provides allows you to do a sanity check.
So if you return false from this method, it won't even create this view model and it will return like a null value.
So we've ever called creative use to generate this.
They'll just get rolled back and the view system is no safe in that regard.
So it's okay for this to return null.
So we can allow us to do some some checks.
So we can say if we don't have a recipe, for whatever reason, there's no point in rendering this module.
So we'll just check if it's null.
If this returns false, then it won't even render out this model at all.
So it's not not uncommon to want to put some extra logic here just to make sure you do some extra sanity checks before rendering something out.
And the rest of this is basically just the same as that other email except now we're pulling things off the recipe or the model depending I think the image has.
So the model itself has an image override so we wanna be able to use that.
But most of the other stuff is coming off the recipe directly and then one other thing we need to have on this is the shared module placement.
So if we go back to that page that we created earlier, oh this is another thing that bright spot provides.
if you make a code change it can often pick that co change up without doing a rebuild.
So depending on your development, so you might prefer this approach to the one of explicit right to build.
Some people think it saves time, in my opinion, it takes about the same amount of time whether you do the reload or whether you do the greater build plus tomcat reloading the war.
So your mileage may vary but that's if you see this pop up, that's what's going on.
It just noticed you made a code change to the java.
And so it will kind of on the fly reload those those changed classes.
This does not work if you make changes in the root style guide or in your bundle.
So if you do either of those you'll need to do a full grade a bill to pick up those changes but just java only changes should get picked up by this and I think the more complex the change the less likely it's gonna work.
If you if you have a new class, it might not pick that up.
But if you just like change a string constant in an existing class, it will almost certainly pick that up here, we can see previous working now because we have our recipe module, few models are now done.
but one thing I wanted to say here is the types we have here.
So this first one here is the in line case, so that is an instance of rescue module placement in line which extends the abstract recipe module.
And so that's gonna work with the model that we just created but this one is the shared and this is actually wrapping the shared module.
So this one is the rusty module placement shared and we have a view model for this because that again this extends the abstract class.
So that's going to work with our view model but we don't have a view model for this and so we have to, if we, you know we want this to work, we have to then create a whole another view model.
It will be exactly the same as the one we just created and just duplicate all that code which is really annoying.
So there's actually a bright spot has called model rapper which allows us to circumvent that.
And what this does is basically whenever the view system when you call create views and you pass in something that implements model rapper when it first will do is execute this unwrap method and it'll keep calling that until it gets something that does not implement Model rapper, whatever that ending result is is what it will actually then look for the model for.
So in our case we're going to return the shared which is a rescue module and that already has a view model and so that one does not implement model rappers.
So the view system will get our shared placement rapper thing here, it will unwrap it to get the shared module and then it will render the module because it can find the view model for that.
So this way it just allows us to save a lot of code duplication.
So I'm not uncommon to use Model rapper but it's not it's not super common either, which is depending on your use case, if you have some kind of thing that wraps something else then that's something else is really what the view model goes with.
Then the model rappers is a great tool for that.
So that's it for the view models with all that work done.
Now we can render out our recipe articles, we can render out our recipes in a module or on the article.
So we're gonna move on to indexes and the same life cycles.
This is more advanced data modeling.
So we've gotten some new requirements.
I wanna be able to search for recipes by difficulty by their tags by the times and then we want to search recipes by typing a tag names, not just selecting recipe tag.
We want to just type in like appetizers and get a list of all the recipes which appetizers without having to go like filter by the tag explicitly.
And then we also wanna be able to sort recipes by difficulty.
There's a couple of pieces involved with this, I just remind myself about changes here, it's not gonna be smart that bill while they're waiting.
So the main thing we've added is a bunch of index annotations.
So we've indexed this this in we've index the recipe tags, we've indexed the times.
we have not index the total time.
Remember the total time might be supplied here or it might be the fall back.
So if I was to put index on this that with all the index when they've done, when they've overridden the default value.
But if they use the default is it will just be dull and they won't get any, it won't there won't be any value in the index.
So we actually have to do is these are something called index method.
And so this is feature that bright spot or dari provides I guess Where if you have a method that doesn't take any parameters and it follows a certain naming convention I think has to begin with.
Get or is or has then.
And you have the index annotation on it then bryce.
What what will happen is when you save this recipe.
It will find all of the index methods and actually execute them and whatever value they return it will use that as the index value.
So it's almost like it is like a fake field that it will populate with the value that this method returns and that's what actually will go into the database to be indexed.
So by doing this now we can index both override when they've done an override or the fallback whenever they've done that.
So what else is in there nexus?
Got next message.
The requirement is sort of difficulty level.
So difficulty was is an in and the alphabetical values don't correspond with the difficulty values which are these codes over here so easy.
Is the easiest one, the smallest value experts.
The hardest three.
So what we wanna do is we want to index the number not the name.
And so we can do that again with an index method where we're gonna get the difficulty if it's not at all we get the code and then we index that.
Otherwise we index and all for no difficulty at all I guess.
So with this every time we say this it'll it'll actually get that code, get the code actual numeric value and put that in the database.
And then we've added disorder annotation which makes it possible to sort by this field in the CMS search.
This only works for the CMS search.
So theaters wanted to be able to sort all the all the recipes by difficulty in the CMS.
Now they'll be able to do that.
We've also indexed the title but the plain text version I think earlier we had an index, we have indexed on this indexes on rich text fields are depending on what you're doing.
Not not as useful because there might be markup in there, you know that you could have a talent text in here or something and that having that index is probably not that useful.
So it's not uncommon you'd want to index the plaintext equivalent of a rich text value like the title.
So that's what this method is doing.
And then finally the requirement we had for searching by tag name.
So we need to actually index the tag names because right now all we indexes the tag I.
So up here at the top we've got recipe tags index but under the hood this is gonna index a bunch of U.
So we don't have any names from that.
So what we need to separately do is an index method we need to get all the recipe tags, get all their names.
The name is a rich text.
We're gonna convert that to plain text and then we'll that all into a set and index the set.
So just like you can have fields that have single values or collections, like sets or lists.
The same thing works with index methods.
So we were trying to set, it'll index every single value in that set.
So then if we type in, we have three different tags of like, appetizer and vegetarian or something, then if you type in any of those values, then that value will be in this index.
And so it will be able to find this recipe and then there's one final piece here.
So in next methods are a little bit special in that you know, they can execute code which might have other references that have to get resolved out of the database, and sort of as a performance optimization, I guess if you want to call it that when indexes are calculated, sometimes not all the time, but sometimes the in the references aren't resolved automatically.
So you might just get if you executed this code without resolving the references, you would get like recipe tags that didn't have any data inside them, which means you won't be able to get the name out.
And so you just index a bunch of goals here.
So we can use this utility here to actually force those references to be resolved only for this one.
So basically we're just passing in the recipe here and the description of how to get the recipe tags off of that recipe and then it'll it'll execute that, making sure that the references are resolved and then we'll get back this recipe list which we can then stream over to get the tag name and then convert the plain text and and put it in a set.
So the rule of thumb is anytime you have a next method and you're calling a getter or any kind of method on on the model, you just wrap it up in this resolve and get and then you don't have to worry about reference resolving.
Another thing you'll note here is all of these methods have hidden on them so by default, bright spot will actually display these as if they were a read only field so you'll see a difficulty level will have a number in it looks like a text field but it'll be great out because they can't edit it.
But most of the time the value of putting an index method is not really useful for the editors it's more lower level stuff for running managing, you know, infrastructure type stuff.
So typically we'll hide those from the ui so that they don't clear things up and without this they will without the hidden they will display by default.
So with that done we've already got the bill, the bill is deployed so by adding a new indexes that's only going to affect recipes going forward.
So if we have existing recipes they won't have their indexes populated automatically.
We have to manually trigger that.
And I can show that to you.
I go to this triple dot menu here on the right for on the recipe.
Is this option to view raw state data?
And what this does is it shows the raw like dari Jason is what this gets serialized down to.
And that came out of the database but also here at the bottom we see a list of description of all the index values.
So you can see we have the title index, we got the published date.
This is the U.
Of my tool user, my user this user in the CMS S.
So we know who published it and when This is the site.
So this is an inspired confidence site so that got attached.
but we don't you see we don't have the time, we don't have the recipe tag names.
None of that stuff got indexed.
so if I if I published manually it would populate which is fine for one recipe but if I have 50,000 recipes you know that's not really gonna be a feasible thing to go through and click all this so what we have here in the developer cluster here, this book operations so what I can do is choose the index option here and I can say I want to index recipes And I only need one right because I only have the one but five writers might be appropriate if I had 50,000 and then I can start that and it gives me a link here.
I can go to this task page now and actually show me what's going on.
So if we scroll down here we should see index, here's my index recipe, indexed one recipe it's done.
If there were 50,000 I might have stayed here for a while and wait for them all to get processed.
However I'm not done yet.
So this is one sort of confusing thing about this is the index only applies to the my sequel database.
So bright spot actually runs on top of two databases.
My sequel is like the main source of truth.
And then Solar is used for full text search.
So index only applies to two sequel.
So if I want them to also be re indexed in solar, I actually have to copy here as well.
so I need to copy recipes from sequel to solar.
Only one writer delete before copy would basically delete all of my recipes first before copying the new ones.
So you you may or may not want to do that.
Obviously in production.
If you hit this, it's gonna delete all your recipes and then for as long as it takes to re index then you'll have the recipes so you probably don't want to do that but there might be cases where you do need to do before copy.
So again we can go here and now our copy is done.
We copied one value.
So I go back here to my raw data page and refresh.
Now we can see I've got a bunch more values here.
So I've got the recipe tag names like an appetizer.
This is the plain text title, here's all of our times.
So this is that this is that index method got calculated these were the fields and you can see here the get the index methods actually have the get as part of their name and this index.
So that's something to note.
Is that the name If you want to query on this field you gotta use the name of the whole method, not just the field name.
And another thing I want to show you.
So you won't be able to get to this on every environment cause it's locked down.
But this is a career that basically allows you to run the same kind of query that you would you would type in java code.
So I can actually search for recipes in here and you can see my one recipe and then we can pick the database we want to be looking at.
So I can choose sequel solar explicit if I want to look at the differences between those.
I can also filter like I can do, you know tags or filter along with one of the fields.
No not equal missing or it is if it is missing we shouldn't have anything show up.
That's another tool that's available to you.
Obviously on your local environment, you can get to it on QA and production, it's much more lockdown so you would have to have the correct access and permission to be able to get to this.
So another thing we need to do remember that we're copying these tag names onto the recipe.
So if someone was to go and change one of the tag names and there was a typo or they want to kind of re describe, change the terminology or something.
Our recipes are gonna be out of date.
So what we need to do is trigger the recipes to be updated whenever the recipe tag changes.
This is a fairly common pattern.
So I wanted to cover it here.
It's a little bit complicated.
So we're gonna, this is part of the what we call the save life cycle.
So whenever you save a piece of content, there's a bunch of methods that run in a certain order at various stages of the safe process.
We're gonna use those to trigger the recipes, update whatever the recipe tag name changes.
So before committee is the is the first method here.
This is going to run right before the database rites are performed.
There's some other method.
There's another method called before save.
Which is a little confusingly named.
It's it's not when it gets written to the database it's actually gets executed quite a bit.
You could think of it.
The better name would be like on key press basically anytime the editors are typing in the CMS bright spot is saving their progress and that before say will run all of those saves.
So but that's not like the final database.
So before commit is when they actually push publish is when this would run.
so the first thing we're gonna do is we're gonna query for the previous version of this recipe because we want to see if the name change because probably most of the time someone's editing this.
Well in this case we only have two fields but if another piece of content might have 25 fields and very rarely is the name that it changed.
So you don't want to do this every time a recipe saved you a recipe tag is saved.
You only want to do this when the name changes to prevent needless re indexing.
So this is what how you run a query.
So we're gonna query from the entire database.
We're gonna query where the I.
Is equal to a value.
In this case the value is we're going to pass in the recipe tag and it will basically know that this is a record.
And so it will actually insert the I.
It will kind of substitute that parameter.
And whatever you're doing these queries, you want to make sure you're always using the parameter substitution.
So you do the question mark.
And then you pass the parameters is as an array of at the end here.
And then that will help deal with you know inc attacks or whatever where you know someone accidentally puts in sequel code here or something and and ruins the database.
So this will properly quote it so that you don't have to worry about that.
And then very important we did add this no cash if we don't add the no cash here and we run this query.
It actually will probably just pull out this very exact same recipe tag that we're already looking at.
It will have that in the cache somewhere.
And so it will say oh you want this recipe?
I've already got it right here but we want to get the we want to get uncashed so we get the old version and we can compare it.
We're gonna get the very first value.
We can also select you know an offset limit or we can select all of them in our case.
We we we we only expect there to be one requiring by D.
So we can get the first, it's possible that there isn't a previous version if this is the first time someone created this recipe tag.
So this could be null.
It's also possible in certain circumstances for the type of this to actually change not in this code base but it's possible that you might have the ability for editors to change the type of something.
There might be like five different article types and they're all compatible and they're allowed to change between them.
So it's actually possible that the previous version is a different type which is why we do this instance of check rather than just a null check.
That's pretty rare but I did want to cover that here just to kind of show you that you might need to do that.
So then now we've got our previous tag we can actually compare the names between the current one and the previous one and if those are different we could go ahead and just re save the recipe right here but it's possible that this table failed for some reason.
You know there could be a validation error.
Database could crash or something so we don't really want to trigger this until we know that the state has succeeded.
So what we're gonna do is we're just gonna flag that we need to update.
But we're not gonna do it yet.
So this is good extras state is like the backing data that dari is gonna put in the database.
It's just sort of a it's basically a map.
Jason map, Jason object.
No but I need to go there.
So the state here is actually just a map just like Jason will be attached to that is sort of like a scratch area called extras.
We can just put keys and values and they don't actually get saved to the database but they persist with that object so we can look at them later.
Anyway so before commit will run then hopefully the safe succeeds and everything's fine.
And then once that's once the safe has succeeded we run after safe.
And so here we can look for our our flag that we saved and if it's true then we can trigger a recalculation of the recipes.
So this is again we've got an aquarium here, we're querying for all the recipes where they have the recipe tags field that is equal to this current recipe tag.
So it will pass in that query and then we'll tell it to recalculate this index.
What we'll actually do is it'll go create a task and off in the background it'll run this query and recalculate this field for us.
This is this is our get index, get recipe tag names, index method that we have down here.
So it'll re execute this method and re save kind of some of what we just did in that over here, in this bulk operations, it'll be that same kind of thing, but it's done automatically.
so with that in place now, anytime an editor was to change the name, all of our recipes will be updated and stay in sync, and there's also the delete case.
So if you were to delete recipe tag, then we want to go remove that strain from that index so that you know our thing, if we delete appetizers, then we won't we don't want to have appetizers showing up on all our recipes anymore.
So this after delete, we'll go and do that, there's sort of a little bit of a gotcha here that I haven't dealt with.
it's so bright spot.
delete here means delete out of the database script to actually do that from the C.
wanted to delete something, just find some random article.
no offense to dr Heimlich, but I'm gonna I'm gonna delete this.
So the first thing you have to do is archive.
Archive is not delete archive.
Just flag that as being not visible anymore.
So we have this flag here.
If we search for Heimlich, it won't show up here, we get the image, we don't get the article actually have to include archived content to be able to see that, so it won't show up normally, so it's effectively deleted but it's not actually deleted.
and so in our case this after delete is actually deleted.
So we actually would have to put in a separate check in here to see if we're archives but not deleted and trigger the the after saved in that case because in that case it's a save not to delete even though it's an archive.
So I didn't do that work in this example but if that matters to you which may or may not you would need to accommodate that case here.
Okay so we'll move on now to our next requirement.
So we think we talked about all this, you can add an indexed annotation to methods getters or has has to be starting the method name, they get executed unsafe and typically want to hide them.
So we covered all that.
Here's all here's the full life cycle that we talked about.
So before save is really on key press is a better name than theirs on validate which allows you to do additional validations.
Like there are some validations that apply like we had the required annotation and actually added a minimum, I forgot to talk about that.
There's like annotations describe the minimum minimum acceptable value and things like that.
So any of those things can have their own validations attached but if none of those annotations that are built in can accommodate your requirements, then you can actually implement the invalidate method and put whatever logic you need to in there so it's gonna be as complex as you want.
And then to actually report airs there's this ad error method on the state so you would call that state at error and then you can put the information there.
You don't you don't throw an exception, you just do the Advair and actually will then attach it to the correct field and whatnot.
There's after validate which is a relatively new method, very rare to need that.
We already talked about before commit and after state there's also on duplicate.
So you can have on your index method on your index imitation, you can actually require the value to be unique.
Do we want a recipe tags and not have any duplicates?
We could add that and then in the case where there is a duplicate on duplicate can run and we can actually use that to change the value.
I think the only implementation that's built in right here is I can't know where it is, there's some, it's one built in that handles like duplicate you or else or tries to handle duplicate anywhere else.
and then deletes.
Lifecycle is similar, there's before deleting after delete.
It's a little simpler.
So in our case we didn't really need to put anything into before delete but we did use the after delete and then there's a link to the documentation on that I forgot to call, I think there was a I guess I didn't put a link to the the model annotations, so I'll add that to the slides before we share them with you.
Okay, more requirements first of all.
So what's called provide something called modification.
It comes out of dark and this is sort of a workaround I guess with java.
So java does not support multiple inheritance.
So basically that means you can't put fields on interfaces, you only put fields on classes and classes can only extend one class, which means you can't have multiple getting fields from multiple different classes that you implement interfaces that implement.
However, it's really nice to be able to share fields methods among multiple content types without having to have a common base classes.
If you know right now, if you would have to have like an abstract, you know, whatever that would have some fields in it and then everything would have to extend that, even if it doesn't really make sense.
And you can't you can't where do you put this field?
It doesn't really fit in any of these classes and there's no way to kind of extend another class.
So modifications are kind of a work around with that.
They allow us to share fields and methods among all the classes that implement an interface or extend some, you know, concrete or abstract abstract class.
The medication itself is an abstract class which extends record but it's not regular content types.
Normally when you extend record, you know, it's just another content type modification is special.
It's generic kind of like the view model was and this tea here parameter is the type that's gonna be modified.
So this could be an interface.
Could be an abstract class.
Could be a concrete class.
So we could modify recipe, we could modify an interface like haven't described interfaces but we could also we could modify an abstract class, like our abstract recipe module class that we did.
So one limitation though, it's still a separate class.
So it's we don't do any byte code manipulation or anything.
So it's still gonna be a separate class but it's gonna be linked to the main class.
And when you save all that data will be persistent.
So there's a method called as which allows you to get to the modification.
So if you have a recipe and you want to get to some modification, you can call recipe dot as passing the class of the modification, then you can get to those fields from there.
There's a very common pattern that we've come up.
We've just come up with that uses modifications.
So I'm gonna describe that here.
We're actually gonna implement it.
So typically it's a very common pattern.
We won't have an index that's shared across multiple content content types but some of the time we want to have the values from a field, the editors can just pick, you know, tags or something.
And other times we want to just hard code the values or have them come from some some code source and not something that the editors would directly affect.
So it's a little bit complicated because right now we know we can put an index on the field and we can put an index on the method.
We know we can have the same index be both if we did that, we would just have to indexes.
And also if you have multiple content types, each of them is gonna have their own indexes.
Like we've we've created the index on recipes.
So all those indexes are only a recipe, you'll never see that many on any other content type and you can kind of see that here from the name.
So you can see the name of like this recipe and cooked an index actually has recipes fully qualified class name in the name.
So it's not a recipe, it's not gonna have the cook time in the index.
So if we want to have the same index across multiple content types, we don't have a way of doing that.
What modification allows us to do that?
So basically there's sort of four classes that you need to satisfy this pattern of an index across multiple types.
Maybe it's coming from a field.
Maybe it's coming from some other logic.
Let's actually look at the code rather than reading this.
So the base interfaces in this case is gonna be has recipes based on requirements.
We want to be able to tag things with recipes and have them re searchable or you know, show up fill trouble by by the recipes that they're tagged with.
So like recipe articles would probably have the recipe that they're on.
Whereas maybe some other things would have you know, some of the recipe tag, maybe we'll be able to have like filter all the recipes that are by that tag or something.
So we have an interface called his recipes.
And it's just basically anything implements interface is something that can be associated to a recipe.
And so for this to work, we need to actually go to get the recipes.
So, here's our method to do that.
Anyone who implements this interface can just supply the recipes here and then we have this other class that lives alongside this.
This is a modification called has recipes data.
And what this is gonna do is anything that implements has recipes, is going to have this index method attached to it.
So this case has recipes as an interface, if anybody wants interface doesn't matter what their inheritance structure is that could extend any number of abstract classes or not.
They will still have this index method and how this works is to actually index the values, we need to get the recipes, we need to be able to call this method here to get the recipe and then we would then index them.
So this is an index method allows us to do that, you know, all that logic in the context of this index process.
So modification provides a method called get original object that basically returns whatever this thing is.
So in our case this might be a recipe article, it might be some other content type that we've implemented has recipes on and we're using that same unresolved state thing we talked about earlier.
So basically, here's just the description we pass in something that implements has recipes and then we tell it how to get the recipes off of that, and then this returns all those recipes and we can index them here.
so with this in place now, anytime we have something that we want to do recipes on and we want to index, we just implement this interface, implement that one method and you get the index and this index is shared across all of those content types.
So we can query across any number of content types that are not related at all, and still get results across that entire set.
So the next piece here is pretty common to want to have a feel that the editors can just pick the recipes in.
So rather than having to copy all that code around everywhere, we can just create another interface which as a field and then supplies the has recipes with the actual recipes from that field.
So we're gonna another modification called house recipes with field.
This is going to modify that other interface.
And here we actually have the field.
So here's the recipes.
Field getter and setter.
Remember always never returned all from a list or set a map.
So here the editors can select the recipes in the field.
And then when we save, the next method will run, it will call this method which we'll get those recipes.
Here's that as method I talked about.
So basically this says get me the house recipes with field data for this specific piece of content.
It'll create that and you'll have this value in the field here so that everything kind of stays in sync.
One other thing to help things stay in Sync is by default, the field name of this would just be recipes which may or may not be very common in your code base.
You might have 20 content types that I have the recipes field that's unrelated to this.
And so if someone accidentally adds this interface, there's gonna be a name conflict and who knows what would happen.
So what we do typically is we add this annotation which will prefix the name with whatever this value.
So basically the rest of the field name of this is actually going to be like that, that we we reduced name conflicts and similarly we did the same thing here, so this will be the index method here is actually gonna be has recipes dot get recipes so we just reduced name conflicts and make it more obvious what's going on.
so johnny recommended to add that field internally in prefix any modification but especially ones that are attached to interfaces because it's a lot easier for those to be added to things that you weren't expecting, you know, in a year or so.
I'm gonna build this while we wait and then so now what we've done is we've added this next commit, we've added the has recipes to recipe article and so to do that we needed to implement the recipes, their recipes method here.
We already have a recipe because the editor already picked the recipe on the article up here.
The recipe field.
So all we have to do is just put that into a list and then we can return it, it'll get index and it'll be it'll play nice with our whole has recipe set up and only when we need to have multiple recipes, could we implement the has recipes with field interface that we just looked at here.
Again null check so we get the recipe, we wrap it up in the list, we return an empty list because again we don't want to return an old value from a list method.
So that done we should be able to go to the CMS.
And just like before we just added a new index to recipe articles.
So we'll have to do that whole re index process again.
But I want to show you how this looks in the raw data.
So here's our recipe article we created earlier.
We go to the raw data.
We won't see that recipe index anywhere in here.
But if we go to the bulk operations or I could also just save it.
It's the one.
I'll just say that to say some time so we don't run a little story here.
We have to do the bulk if we have multiple.
In this case we only have one so I can just publish Not that we?
Re published that we actually have a new.
Where is it?
Here it is here.
So has recipes data.
Was our modification.
I contain the index method.
Remember we had the field prefects and then the full method name.
So in our case it's has recipes data slash has recipes dot recipes.
So this would be the full fully qualified name of an index.
I wanted to query on enough to use this full value but you can see in here now we've got a the index and this is actually I was to go up here and load this.
Here's our recipe.
That's the same recipe that's tagged or selected right here, that's how everything is kind of linked up together.
So then final section here is gravity well.
So we've talked a little bit about handlebars already and the root style guide.
and how those aren't really intended for use with an A.
They're designed these handlebars handlebars, there's kind of an internal, you know, rendering system so it's not really publicly consumable.
So an A.
You know, it really needs to be robust, you know, curated, it needs to be maintainable.
You've got external consumers using it.
They might not be even in your organization or if they are they might be under a different you know, leadership team or something and you don't necessarily have a whole lot of control over what they do.
So the A.
Kind of is you're you're kind of on the hook for for maintaining it and making sure that it's supplying what they need.
So with the handlebars it's really not intended for that use case of being externally consumable.
And oftentimes the data format for external use is gonna be different.
Like you might want to have things in more low level simple terms.
So like for instance in our examples we printed out the times as human readable.
So like five minutes as text.
Whereas you might want to output like an I.
Compliant duration value in your A.
And then the consumer of the API can then render that out however they want.
And they might be doing analytics on it which case they don't care what language it's in or whether it looks pretty to human.
You know they're looking just for something that's easily possible.
So our general recommendation is if you're writing an aPI you're going to write custom view models specifically for or whatever aPI that you're gonna be creating.
Again depending on your use case animals might be appropriate but that shouldn't be your default expectation.
So when you're writing view models for graph QL they're basically the same as the view models for handlebars but typically you're not going to be using the auto generated view interfaces from the style guide.
And so typically don't even bother with the interface is you really only need them in graphic.
Well if you have an interface or a union in the graph QL schema.
So when you create a view model you would need to add the view interface annotation directly on that view model.
Normally it's on the view interface.
And what that really tells a bright spot to do is that it allows you to expose those public public methods on that view model in the A.
And then also if you're going to do with Jason api you will need the Jason view annotation but you don't need that graphical, graphical already knows how to create its format, which I guess is basically but it doesn't need that invitation.
And so in graphical similar to like rendering Justin handlebars, we had the page interview, so graphical doesn't necessarily use that.
but basically you need to create an api endpoint and then you can explicitly specify the views in there and page interview could be one of those or you could have some other custom view or maybe you actually just list out like we just have recipe or whatever.
So you might not need an interface for that depending on your needs.
So take a look at this.
So here's our end point.
Bright spot provides this content delivery, api endpoint abstract class.
So we're extending that there's actually a built in ones, You can actually create one of these directly in the CMS, that's more intended for like docs or you know, quick testing and whatnot.
If you're actually gonna create like a public, you consider bp, are you really want to implement your own version?
So you would extend content delivery api endpoint for that.
We talked a little about singleton earlier.
Here's another use case for singleton.
what this means is that there's gonna be a single instance of graphical recipe recipe, graphical endpoint in the database and it'll be automatically created on server startup and then we don't have to like manually create it in the CMS.
It'll just exist.
We have to tell it the path that will be our end point will live at and then here's where we describe the entry points, so texas returns a list, so we can have any number of entry points in our case, we just have one.
It's going to be this view model.
We could this could also be some kind of abstract abstract class or could be an interface.
Basically anything that would implement that would then show up as a as the entry point By default, I think the name that will show up in the in the schema is this?
So it'll just it'll strip off the view model at the end.
but you can also pass in your own name and that will show up in the schema.
And then that the model itself, this is sort of a special handler here because we implemented singleton and because the view model here is of that end point itself.
we'll get a special set up in the in the schema where we can actually have have custom filtering on our end point.
So by default you only can filter on I.
And path of U.
you won't be able to filter on any other parameters but when you do this singleton trick.
And the view model of the endpoint itself, then you can also add your own filters in this case we wanna be able to filter by all those indexes we just added like difficulty and tags and title.
So this is a female and so we had those you know current site currently cow.
We also have web parameter which allows us to pull parameters off of either.
If this is a handlebars model these would be HD query string parameters and a graph QL view model.
These are parameters off of the graphic goal request.
Let's actually just go take a look at what this looks like in the CMS.
We're talking further about it Heads up I think we're scheduled to go until four PM us eastern We might be cutting it close so we might go over a little.
Might go over a little.
I apologize for that but we do again we'll have the recording so if you do have to drop it for we'll be able to come back and and watch the rest of the presentation.
Even if I don't quite get it done in the next 20 minutes.
The hamburger menu here under the developer we have the graphical Explorer.
And here I can select any number.
I have all my endpoints are configured under this on admin A.
And since that's a single tenant already was created automatically for me I didn't need to do it.
You might have other ones that you do have to create manually.
but all of them will show up and the graphical explorer here and then once it loads the schema you can see here's my entry point.
So I've got the recipes, here's all my parameters.
These all correspond to all of these fields in here.
So I've got the title difficulty of tags, title difficulty, tags and difficulties and in and knows about venoms.
So that kind of corresponds makes it easier to work with.
Some of these are lists, parameters, some of them are just regular, so the list of parameters.
I forgot the exact syntax and if you have to type in a list like this so yeah.
Anyway so that's the kind of how you work with that in the view model here.
So we we talked about should create earlier There's another method called on create.
you can kind of think it was like a constructor a little bit maybe if you wanted to.
It's just another method that gets called for the view model when the view model is first created.
So basically should create is gonna run the non create runs and then at some point after that all of your methods will run So you can use this to kind of set up things that your methods might need to reference.
So in our case we're going to execute a query and select the results based on all the parameters that were provided.
So we go down here to the build query method, here's another example of a query.
So remember before we did query from all that was because we wanted to make sure we could find any the version of that, we were looking for the previous version of the recipe tag.
In that case we wanted to make sure we found if it wasn't a draft that had been archived, if it was published any of the status is we wanted to include it.
So you have to use query from all when you're doing that, when you create from a specific type like recipe here in this example you're only going to get the publicly visible version copies or instances, so only the ones that are published that are published if it's in draft, if its archives, it's in workflow it won't be returned from this query without adding additional filtering in our case it's perfect.
We only want to have the publicly visible ones because this is an A P.
So we just acquired from recipe the next one here this query pension is not a lot of joints because we're gonna be filtering on any number, like someone could provide every single one of these parameters and so that's every parameters of joint basically so equal has four performance when you have a lot of joints, Solar does not so any kind of search api like this for like a search on the front end like a site search All those things should go to solar.
So to explicitly forced into solar we do this sort of like magic pattern here.
Star matches star which basically means for such a solar but otherwise it doesn't affect anything.
So all the results will be it won't it won't limit the results in any way other than the saying get the results from solar.
And then here if we have a site given then we want to make sure we filter the results by only things available to that site.
So this items predicate does that for us.
And then here we're basically just gonna go through every single parameter added to the query if it's given.
So if we have a title then we're going to match this is a full text search match the title field and we're doing the plaintext because you got no one's gonna type in html here.
If there was like a talent in the title, whatever we had that plain text index that we created the difficulty the tags and also just generic search terms.
So here's like a generic search match star matches and then we would put in the search terms, we actually support phrases.
So if you have quotes and with the phrase inside an old match only that whole phrase or approximately actual last words to be swapped a certain number of times and still match.
So we add all our parameters for a query then we return our query.
Then up here we're gonna select based on the offset and the limit the limit, the offset is based on the page parameter that was passed.
So they can see again in the second page, third page, the fifth page whatever.
And then we just force it to be only 20 results at a time.
And then that we can get a paginated result.
And this allows us to get the next the next page of the previous page and and account and whatnot.
We can get that all that data out of this.
In our case I would care about is the items and the next page.
So here's our great views.
We get all the items.
These are gonna be recipes because we queried from recipes paginated result the recipe.
And this is going to create a recipe, A P.
View model for every single one of those.
So here's a special view model.
We're gonna I'll show you that next that we've written that's only for use in the graph to L.
Does not use handlebars.
And then for the next page if there's a next page we'll just increment the page that they passed in by one and that gives us So they can they know that there's another page available and they can make another query if they need it.
The last thing here is the recipe A.
Again, the java doc describes.
This is not the recipe one for use with handlebars.
This is the A.
So we're expecting a view model of recipe just like we did before.
But no there's no view interface.
we're not implementing any of the view interfaces that came out of style guide and we have to actually add the view interface annotation directly.
We don't get it via any of the normally we get that via that you view interface but since there's no D interface we have to add it manually.
And one thing that allows us to do is control the name that shows up in the schema.
So by default this would be what shows up in the scheme, I think obviously that's not a great name.
So we can change that to recipe and then it'll look nicer in the graphical scheme and that's I think what we see if we were to get the Yeah so here at recipe will show up in here somewhere anyway.
So here we are getting similar values are similar fields to the handlebars female but here instead of like we're turning instead of string of like a text description at the time were just returning the actual energy value.
This could also we could also chosen to do like an I.
Duration here if we wanted.
rich text is a little special for a P.
Because you may or may not want html In our case we're kind of cheating and just using the handlebars view models for the html side.
This may or may not be appropriate for, you might actually have to do some more work here to get rich text to render out.
Here's another special thing.
So image, images This is the same way you would render image out in handlebars I guess, but by adding this image attributes annotation that will give a special display in the graphical FBI will actually allow people to select a specific size of image.
it'll be returned to them.
But I believe all these java ducks show in the scheme out.
Now I'm having a hard time remembering where the schema is.
So I think it's this recipe here.
It is, yes, I think that broke.
Used to display the drive it out of there.
Anyway, we can look at that later.
So again, when you're creating an A P.
I you want to make sure you're documenting things, you want to make sure that all the things in here are going to be maintainable.
You're not gonna be fiddling around with this every every month.
You know, you want to have this kind of be a good solid plan so that it's a robust api So our next example is going to be some more advanced filtering, Remember our time?
We wanna be able to filter on our times but like having to pick the exact number.
Numeric value isn't gonna be that useful, you want to be able to say finally all the recipes that are you know, 10 minutes of prep time or something.
So we've actually done some custom work here to support arrange value.
So they in graphical actually able to say give two numbers and it'll be the end points of a range and then we'll actually be able to filter on that here.
So There's sort of two pieces to this one is this annotation?
This is an annotation that works in the view models, just like the other ones that we've already looked at.
we wanted to work with graphical action to implement this, we have to attach the processor to our our annotation via this content delivery api web annotation processor.
That's a bit of a mouthful.
What this does is it basically described, let's just describe how this parameter should display in the schema.
And then given that the parameter has been provided in the schema, how do we actually interpret it and and get a value out.
So here's what we describe the schema types so we say that it's a new, it's an object called range and it has two values.
Fields men and max and these are injured.
And then given that we have a value of that we need to create one of our we have a little job object here that's gonna encapsulate those.
So the object here, input is going is going to be a map.
This is just sort of a built in utility to pull things out of the map in a sort of type agnostic way.
So we can pass in an object here and if it's a map it'll it'll pull out the the minimum key and then that would be an object and then we can convert that object to an imager With this object util.2 method.
It'll give us a minute max or no if they weren't given.
And then we can create our range parameter object and this just holds the two values and then has the logic to actually update the query.
So no safe.
If we have a men we can filter by men we have max you can filter by max and then in our view mile that's the field that we attach the graphical annotation to and then all of these values.
We can then if it's not normal we can then call the update query and it will add those predicates to our queries that will filter by those times I think that's all the pieces.
If we reload this you can see your quick time now.
I want a minimum or maximum 20 minutes no matter There.
We got a recipe but if I say a minimum of 15 I think it was like 10.
So we should get no results back and then you get the same pattern for the prep time for the total time.
So as you filter all those things in your api so now we basically build an api that will allow some external consumer to find recipes based on any kind of parameters that they wish and then they get those recipe data out.
I don't think I really feel that any and builds on that recipe so we can't see them in here but you get the idea so then finally we've got this is just a really quick follow up just adding some more parameters to our endpoint.
So you need to update this.
Got some deprecating things in there.
basically we just added course ability and the ability to restrict access to like only certain api clients but also had the ability to add the theme which is gonna add the image, crop selection I talked about earlier I'll show you what that looks like so on our on our image field here this one pick an image so we actually have something put a description so we can yes so I can see it.
So if we add the image here we can see I can pick a specific size and that value comes from the theme that I select actually have to go for this.
I got to go here for a pr and pick the theme.
So it's gonna be the start built in default theme and then I can also pick you know, set up the course if I need to access this api from the browser so if I so the image value here is gonna come from we go to our bundle style guide directory.
In the bundle we have this image sizes config So all of these keys are the names of these keys are under this image sizes object.
All these keys are the names of crops.
So I felt around the image, I can say I want this specific crop, give me the data.
So here's our html rendered out using the handlebars, view, models may or may not be appropriate for your A P.
And then here we've got all the image data.
So we got the Euro, we've got the source of the crop, you can see here here's our crop 1194 by 1194 and also some source said if you want to do this season a source set so you can use that to filter down to specific sizes that you want.
So we talked about graph QL it's maybe I've got other considerations beyond just you know rendering out stuff you need to think about off course backwards compatibility.
Make sure you design your api in a way that you can extend it going forward and maintain it and maintain backwards compatibility.
You don't have to like go fill in all your consumers and have them update their clients.
And then if you need to allow searching for content like we just did or is I.
And path sufficient out of the box you're only going to get I.
And path may or may not be what you need.
So the search ability to searching is all that extra work we did in that view model.
So if you need that there's a good pattern for you to follow.
Okay that's it.
We talked about everything was in that repo all the code, we just looked at it on the exercise recipe branch all the commits are there so if you want to follow step by step if you want to look at the full, you know completed example just check out that branch.
We're gonna make the slides and recording of this presentation.
I think I heard it was going to be mailed out on monday and then I'll update, I'll add that link to the View model annotation documentation in this in the slides here and then just in general we have a lot of documentation on our website so be sure to check that out if you're looking for how something works And then I've also got a link here to the doctor Read me if you need to deal with the more advanced settings that are provided by our tomcat Docker container.
And finally we have a support portal available to you.
You can contact your product manager to get access to that and that is basically allows you to file like a ticket and get a response back if you need advice on how something works or wondering if you know your design is appropriate for your use case.
You can get some feedback from that and we have all that's all staffed by Bright spot engineers.
So people who actually do what you're doing basically coding in Bright spot will be answering your questions a couple of questions in the chat here.
And then also if any other additional questions you have, you feel free to answer, ask those now.
just go back through a couple.
I didn't get to earlier.
Well I forgot to talk about the cluster annotation play or I forget exactly what it's called.
Yeah, so there's this cluster display order annotation and so you can kind of see here how this works.
So you tell it what tab it's on, you can tell if you want to alphabetize or if you wanna have certain ones at the beginning or certain ones at the end and then that will order all the clusters for you and the rest will just kind of go into arbitrary order.
So if you need to, if you need to reorder whatever, I guess the default orders by alphabetized, so that's not appropriate for you.
Can you can control that.
question about developing with node and bright spot as opposed to java.
I don't have any examples of that, that's very much still in beta.
so I don't think I haven't been destroyed at this point.
You have to talk to your product manager about more information on that.
Oh, ap eyes, yes.
Or else this is about having implementation, how the A p i U R L is generated.
So the the U R L of your end point is this so it would just be your server, host name slash the path that shows up here and then you would you posted that with your graph QL query anywhere else beyond that would be up to you to internet.
So I mean typically if you're doing a graphical, like you don't really have necessarily have a U R L for content, you might just have an I.
Or whatever depends on your approach or in the case of like the recipe endpoint, we just implemented, there are no U R L S at all.
You just have to query directly by parameters for the recipe.
So we could have added I D parameter to that or some other parameters like the U R.
A lot of that's up to you how you want to implement a graph QL as an api so we wanted to be pretty agnostic into the implementation because there's any number of ways you could do it and they are perfectly valid.
It's a question about values and aware education.
we didn't talk about where annotations is that is that what you were asking about?
Or are you talking about where in the weary because like we talked in the we used to wear here in the query and it's the same thing and and wear the same thing so we are filtering on a in on here.
Is that what you were asking about or is this about the wear annotation which we didn't actually cover in this in this training.
That's a question for copy freedom.
If I'm not pronouncing that right question about using the groups in graph QL just like we did in the style guide, so not really because you're generally not intended to use the style guide anything from the root style guide in graph QL So these groups create interfaces but generally the interfaces that come out of that are not really appropriate for use and graphical and I can kind of show you an example of what I mean by that.
So this is I'm gonna create this is just like a PSC graph, you'll so I can say we're gonna have an article page view models show up in the graphical and this is the handlebars view model what that ends up looking like it's just very abstracted and so it's really difficult to to write queries against it because there's so much variability that may actually not be present in your data but it's in the schema.
you can see it's taking a lot of load because the scheme is pretty complicated.
I remember we had was a leads group article uses that and then there's three or four different types that can go in there.
So we're gonna see all four of those types in the schema.
And if you wanted to query on, if you wanted to include the lead in your query, you would have to select all the possible values that you care about across all four of those types which gets pretty tedious when you have that level of variability on every single field, I'm not sure why this is taking so long.
Well let that alone and come back to that question about what version of Bright spot.
So actually if you open up the Js console, it spits out the version of Bright spot as well as the version of your project which in this case is the training project doesn't really have versions.
So it's just one of the snapshot writes about wrong version 45 11 and that's controlled.
I go to the build dot file, the other variable here called bright spot version and that's where you specify that.
So this should actually say 45 11 1.
I'm not sure it doesn't, but one version 45 11 1.
There's a couple other libraries that we pull in is this component library and there's this go version library and so both of those all three of those are version and we also this is our great old billed as a version as well for the great old scripts that actually build the project.
So here's the graph QL from the handlebars view model.
Let's take a look at the lead.
You can see, I've got four different five different possibles possible values for the lead.
I don't know which lead it.
Is that I mean basically each of this is going to correspond to something that the editor program.
So if I, if I don't know which one it is, I have to say.
Okay, well if it's a carousel lead, then I want the title.
I don't want the slides, I want the description and descriptions, rich text.
So I've got to deal with that and then the slides, you know, there might be multiple types of slides and then the title and then if it's a figure, then I have to get all the data off of that.
It's a paycheck So I have to deal with all this variability and my inquiries end up being very large.
And probably most of the time it's gonna be like an image or something so like all this variability might not even matter, but since it's in the schema you have to deal with it or you just have to have some kind of convention like oh you can just ignore that stuff but it's not really a good design for an eight P.
So you end up with this variability like every single field because handlebars is much better at dealing with abstraction than graph QL is.
So we can get away with it handlebars but we really can't get away with it in graphic.
So the general recommendation is not to use this.
But again your requirements may vary so you might consider doing it but it's not recommended.
That was all the questions I've got in here.
I haven't heard from UK via about the venom so if that wasn't what you're looking for you can we can get you in a support portal and you can ask your question there again for more elaboration.
Okay thank you all for joining.
I hope this was helpful.
Again we'll have the recording and slides available to you probably on monday.
Best of luck developing on bright spot