diff --git a/docs/advanced.adoc b/docs/advanced.adoc new file mode 100644 index 00000000..939caa52 --- /dev/null +++ b/docs/advanced.adoc @@ -0,0 +1,5 @@ += Advanced topics +:toc: right +:imagesdir: media/ + +This section contains chapters on using client-side rendering as well as using React4XP when building webapps. \ No newline at end of file diff --git a/docs/client-side.adoc b/docs/advanced/client-side.adoc similarity index 92% rename from docs/client-side.adoc rename to docs/advanced/client-side.adoc index 2c2c5131..12e76fae 100644 --- a/docs/client-side.adoc +++ b/docs/advanced/client-side.adoc @@ -4,6 +4,8 @@ We will now look at how to use React4xp in combination with XP's graphQL api. +WARNING: This chapter is currently based on the deprecated Guillotine library. We are working on updating it. + == Lesson overview This chapter will focus on setting up the API and the first usages: @@ -65,7 +67,7 @@ This first stage should be easy enough, almost entirely repeating steps you've b [NOTE] ==== -This entire chapter builds on the <>: _react4xp.config.js_, _webpack.config.react4xp.js_ and the extra NPM packages should be set up like that. +This entire chapter builds on the <<../imports-and-dependency-chunks#webpack_config, config setup from the previous lesson>>: _react4xp.config.js_, _webpack.config.react4xp.js_ and the extra NPM packages should be set up like that. If you haven't completed that section already, better take a couple of minutes and do that before proceeding. ==== @@ -78,7 +80,7 @@ When the setup is ready, we'll start by adding a _movie_ *content type*, with an .site/content-types/movie/movie.xml: [source,xml,options="nowrap"] ---- -include::../src/main/resources/site/content-types/movie/movie.xml[] +include::../../src/main/resources/site/content-types/movie/movie.xml[] ---- === React components @@ -90,7 +92,7 @@ The *entry*, _Movie.tsx_, will take care of rendering a preview of each movie co .react4xp/myEntries/Movie.tsx: [source,typescript,options="nowrap"] ---- -include::../src/main/resources/react4xp/myEntries/Movie.tsx[] +include::../../src/main/resources/react4xp/myEntries/Movie.tsx[] ---- This is a pure entry wrapper that just imports the next react component from _react4xp/shared_. @@ -100,20 +102,20 @@ Why import code from _shared_ instead of keeping it all in the entry? Firstly, i .react4xp/shared/Movie.tsx: [source,typescript,options="nowrap"] ---- -include::../src/main/resources/react4xp/shared/Movie.tsx[] +include::../../src/main/resources/react4xp/shared/Movie.tsx[] ---- Not a lot of functionality here, just a TSX file that contains some structural units nested inside each other: the exported root level in the component, `Movie`, contains a movie poster image, and nests an `InfoContainer` component that displays the rest of the movie data. There, each movie data section is wrapped in an `Info` component (which just displays a header), and finally each actor name is mapped out in a list in the `Cast` component. Take a moment to note the *props signature* of _Movie.tsx_. `Movie` clearly expects the `imageUrl` prop to be a URL, so we'll need to handle the `image` field from the content type. The props `description`, `title` and `year` are expected to be simple strings, but `actors` should be handled as a string array. As you'll see, we'll make sure that each data readout of a movie item is adapted to this signature. -Moving on, _Movie.tsx_ also imports some *styling* that'll be handled by webpack the same way as in <>: +Moving on, _Movie.tsx_ also imports some *styling* that'll be handled by webpack the same way as in <<../imports-and-dependency-chunks#webpack_rules, the previous chapter>>: .react4xp/shared/Movie.scss: [source,sass,options="nowrap"] ---- -include::../src/main/resources/react4xp/shared/Movie.scss[] +include::../../src/main/resources/react4xp/shared/Movie.scss[] ---- @@ -121,7 +123,7 @@ include::../src/main/resources/react4xp/shared/Movie.scss[] [[controller_mapping]] === Controller mapping -Here comes a little variation: in this example, we want to connect a movie content item to with the rendering of the _Movie.tsx_ entry. But we don't want to mess around with setting up a <> the way we've done so far. Instead, we can use a link:https://developer.enonic.com/docs/xp/stable/cms/mappings[controller mapping] to make that connection in code. +Here comes a little variation: in this example, we want to connect a movie content item to with the rendering of the _Movie.tsx_ entry. But we don't want to mess around with setting up a <<../pages-parts-and-regions#page_template_setup, template with a part>> the way we've done so far. Instead, we can use a link:https://developer.enonic.com/docs/xp/stable/cms/mappings[controller mapping] to make that connection in code. Let's open _site.xml_ and add a mapping: @@ -159,7 +161,7 @@ Now, with that mapping set up, we can add the _previewMovie_ controller: .controllers/previewMovie.ts: [source,typescript,options="nowrap"] ---- -include::../src/main/resources/controllers/previewMovie.ts[] +include::../../src/main/resources/controllers/previewMovie.ts[] ---- After the previous chapters, not much in this controller should come as a surprise, but a quick overview anyway: @@ -167,7 +169,7 @@ After the previous chapters, not much in this controller should come as a surpri <2> `image` comes from an ImageSelector and is just an image item ID, so we use `imageUrl` to get the URL that the prop signature expects. <3> Normalizing the `actor` data to guarantee that it's an array. <4> `React4xp.render` needs a unique ID to target a container in the surrounding `body`. -<5> `"Movie"` is of course the <> reference to the entry, _react4xp/myEntries/Movie.tsx_. +<5> `"Movie"` is of course the <<../appendix/jsxpath#, jsxPath>> reference to the entry, _react4xp/myEntries/Movie.tsx_. <6> This controller is the only one triggered for rendering _movie_ items. That means that the `body` that the rendering is inserted into, has to be a *full root HTML document* including a `` section (or otherwise React4xp won't know where to put the rendered page contributions, and the component won't work properly). <7> Workaround for a current link:https://github.com/enonic/lib-react4xp/issues/107[inconvenient bug]. @@ -176,7 +178,7 @@ After the previous chapters, not much in this controller should come as a surpri With all this in place, we're about to finish the groundwork stage: let's add some _movie_ content items to list. -<>. +<<../hello-react#first_setup_render, Build the project as usual and start XP>>. Create a site content item and connect it to your app. Create some new Movie items: @@ -211,7 +213,7 @@ The most central of the helpers and the first one we'll use, is *_headless/guill .headless/guillotineApi.ts: [source,typescript,options="nowrap"] ---- -include::../src/main/resources/headless/guillotineApi.ts[] +include::../../src/main/resources/headless/guillotineApi.ts[] ---- <1> At the core is the function `executeQuery`. Here, a guillotine `SCHEMA` definition is combined with a graphQL `query` string and an optional `variables` object. These are used with XP's graphQL library to `execute` the query. The result, a JSON object, is returned. @@ -234,7 +236,7 @@ Let's go ahead an write this: .headless/helpers/movieListRequests.ts: [source,typescript,options="nowrap"] ---- -include::../src/main/resources/headless/helpers/movieListRequests.ts[] +include::../../src/main/resources/headless/helpers/movieListRequests.ts[] ---- <1> The function `buildQueryListMovies` returns a string: a *guillotine query* ready to use in the API. Colloquially, you can read this query in 3 parts: + @@ -259,7 +261,7 @@ Armed with these helpers, we can build an XP part controller that runs a guillot .site/parts/movie-list/movie-list.xml [source,xml,options="nowrap"] ---- -include::../src/main/resources/site/parts/movie-list/movie-list.xml[] +include::../../src/main/resources/site/parts/movie-list/movie-list.xml[] ---- [[movie-list-part-controller]] @@ -268,7 +270,7 @@ The actual controller: .site/parts/movie-list/movie-list.ts: [source,typescript,options="nowrap"] ---- -include::../src/main/resources/site/parts/movie-list/movie-list.ts[] +include::../../src/main/resources/site/parts/movie-list/movie-list.ts[] ---- <1> Import the functionality from the helpers that were <<#guillotine_helpers, just described>>, <2> Use the part's config to build a sort expression for the query, @@ -286,7 +288,7 @@ We're still missing that *_MovieList_ entry* that will display the list of movie .react4xp/myEntries/MovieList.tsx: [source,typescript,options="nowrap"] ---- -include::../src/main/resources/react4xp/myEntries/MovieList.tsx[] +include::../../src/main/resources/react4xp/myEntries/MovieList.tsx[] ---- The only notable things here: @@ -298,14 +300,14 @@ Most of the styling is already handled at the single-movie level, so just a mini .react4xp/myEntries/MovieList.scss: [source,sass,options="nowrap"] ---- -include::../src/main/resources/react4xp/myEntries/MovieList.scss[] +include::../../src/main/resources/react4xp/myEntries/MovieList.scss[] ---- === Render the list We can now set up the parent site with the movies, with a _movie-list_ part. Rebuild the app, enter/refresh Content Studio, and make the _movie-list_ part handle the visualization of the _MovieSite_ item. -TIP: You can either do that <> to render _all_ sites with this part controller. Or better, edit _MovieSite_ directly and add the _movie-list_ part to the region there, the same way as when adding a part to the region of a template. With this last direct-edit approach, only _MovieSite_ will be rendered like this; other sites won't. +TIP: You can either do that <<../pages-parts-and-regions#adding_parts_to_new_content, with a template as before>> to render _all_ sites with this part controller. Or better, edit _MovieSite_ directly and add the _movie-list_ part to the region there, the same way as when adding a part to the region of a template. With this last direct-edit approach, only _MovieSite_ will be rendered like this; other sites won't. Correctly set up, you can now select the list in the edit panel, and a part config panel will appear on the right. *Edit the config fields to control the guillotine query*: how many movies should be rendered, and in what order? @@ -372,7 +374,7 @@ This too has some convenience error handling and boilerplate like default parame .headless/guillotineRequest.ts: [source,typescript,options="nowrap"] ---- -include::../src/main/resources/headless/guillotineRequest.ts[] +include::../../src/main/resources/headless/guillotineRequest.ts[] ---- In short, run `doGuillotineRequest(params)` where `params` is an object that has at least a `.url` and a `.query` attribute (and optional `.variables`), and it will send the query to the guillotine API and handle the returned data (or errors). How that's handled is up to callbacks in `params`. @@ -400,7 +402,7 @@ Now we're ready to *add a guillotine call from the frontend*, specifically to _M .react4xp/myEntries/MovieList2.tsx: [source,javascript,options="nowrap"] ---- -include::../src/main/resources/react4xp/myEntries/MovieList2.tsx[] +include::../../src/main/resources/react4xp/myEntries/MovieList2.tsx[] ---- The changes are: @@ -432,7 +434,7 @@ React is _very_ eager to do this whenever a component state is updated, so we'll .react4xp/myEntries/MovieList3.tsx: [source,typescript,options="nowrap"] ---- -include::../src/main/resources/react4xp/myEntries/MovieList3.tsx[] +include::../../src/main/resources/react4xp/myEntries/MovieList3.tsx[] ---- Changes: @@ -471,7 +473,7 @@ With one additional change to the procedure: the trigger should disable the scro .react4xp/myEntries/MovieList4.tsx: [source,typescript,options="nowrap"] ---- -include::../src/main/resources/react4xp/myEntries/MovieList4.tsx[] +include::../../src/main/resources/react4xp/myEntries/MovieList4.tsx[] ---- <1> `listenForScroll` is the scroll-listener's enabled-switch. <2> Threshold value: if the distance between the bottom of the screen and the bottom of the movielist DOM container is less than this number of pixels, `makeRequest` should be triggered. @@ -502,5 +504,6 @@ Rebuild, refresh the preview of _MovieSite_, and instead of clicking, just scrol TIP: This section is not a vital part of the rest of this or the next chapter. Feel free to skip it and miss out. -To dive deeper into guillotine and graphQL, you can always check out the link:https://developer.enonic.com/templates/headless-cms[headless starter]. +To dive deeper into Guillotine and graphQL, you can always check out the link:https://developer.enonic.com/docs/intro[Intro], or our +link:https://developer.enonic.com/docs/developer-101[Developer 101 tutorial]. diff --git a/docs/media/add_movies.png b/docs/advanced/media/add_movies.png similarity index 100% rename from docs/media/add_movies.png rename to docs/advanced/media/add_movies.png diff --git a/docs/media/click-data.png b/docs/advanced/media/click-data.png similarity index 100% rename from docs/media/click-data.png rename to docs/advanced/media/click-data.png diff --git a/docs/media/click-fill-dom.png b/docs/advanced/media/click-fill-dom.png similarity index 100% rename from docs/media/click-fill-dom.png rename to docs/advanced/media/click-fill-dom.png diff --git a/docs/media/edit_movie.png b/docs/advanced/media/edit_movie.png similarity index 100% rename from docs/media/edit_movie.png rename to docs/advanced/media/edit_movie.png diff --git a/docs/media/movie-list-part-config.png b/docs/advanced/media/movie-list-part-config.png similarity index 100% rename from docs/media/movie-list-part-config.png rename to docs/advanced/media/movie-list-part-config.png diff --git a/docs/media/webapp_applications.png b/docs/advanced/media/webapp_applications.png similarity index 100% rename from docs/media/webapp_applications.png rename to docs/advanced/media/webapp_applications.png diff --git a/docs/media/webapp_url.png b/docs/advanced/media/webapp_url.png similarity index 100% rename from docs/media/webapp_url.png rename to docs/advanced/media/webapp_url.png diff --git a/docs/standalone.adoc b/docs/advanced/standalone.adoc similarity index 92% rename from docs/standalone.adoc rename to docs/advanced/standalone.adoc index 053142eb..00e9d09a 100644 --- a/docs/standalone.adoc +++ b/docs/advanced/standalone.adoc @@ -1,9 +1,8 @@ -= R4XP webapps += React4XP Webapps :toc: right :imagesdir: media/ - [TIP] ==== This chapter builds on the source code from <>, especially the entry and dependencies, and the assets that they're compiled into. @@ -13,8 +12,6 @@ If you've completed that lesson, nice. But that code is not in focus here and we *What counts here are the general principles and usage patterns*. They should become pretty clear pretty soon. ==== - - == Background === The story so far @@ -40,8 +37,8 @@ Now that we've seen how to set up an API that serves XP content data, and that u The takeaway of this chapter is this: React4xp components (entries and dependencies) can be used without any XP controller or `React4xp.render` call. To do that, the base HTML (the response of a first page request, or some script loaded by it) *must do these tasks*: 1. Load React and ReactDOM from somewhere. For example a CDN, -2. Load all the <> that the entries need (before loading the entry assets!), -3. Load all <>. This will make each entry available in the browser's JS namespace as a react-component-creating function, at `React4xp[jsxPath].default`, for each entry's <>. +2. Load all the <<../appendix/chunks#, dependency chunks>> that the entries need (before loading the entry assets!), +3. Load all <<../appendix/entries#, entry assets>>. This will make each entry available in the browser's JS namespace as a react-component-creating function, at `React4xp[jsxPath].default`, for each entry's <<../appendix/jsxPath#, jsxPath>>. 4. Construct a props object for each entry that uses props. This is where we'll contact the <> in this lesson, for fetching the data from XP. 5. Use the props with each entry's component-creating function to create a react-renderable component: + @@ -72,7 +69,7 @@ We'll take a look at *two variations* of how to use React4xp-compiled components 1. *Completely standalone:* this first variation is the manual, hardcoded, vanilla-js-and-react webapp approach. The HTML and script do everything explicitly: asset URLs and initial values are *handled and organized manually in the HTML itself*, and the script at the end fetches data from guillotine, organizes them into props and makes a *regular `ReactDOM.render` call*. In this approach, XP's role is mainly to serve content data through the guillotine API. Pretty independent but there are no helpers; so getting things right is up to you. + -2. *Webapp with XP helpers:* this second variation is "slightly standalone": we use XP to wrap a little boilerplate for convenience: *XP and thymeleaf provides some initial values*. The script at the end is still loaded and used to fetch data and create props, but instead of having the HTML load all the entry and dependency assets and call `React4xp.render`, the React4xp client wrapper is loaded in order to use a *React4xp helper function:* `.renderWithDependencies`. This is an all-in-one rendering trigger that takes one or more <> with props and target container ID's, and uses *XP services* for auto-tracking and loading all the assets needed (including dependency chunks) before rendering them. +2. *Webapp with XP helpers:* this second variation is "slightly standalone": we use XP to wrap a little boilerplate for convenience: *XP and thymeleaf provides some initial values*. The script at the end is still loaded and used to fetch data and create props, but instead of having the HTML load all the entry and dependency assets and call `React4xp.render`, the React4xp client wrapper is loaded in order to use a *React4xp helper function:* `.renderWithDependencies`. This is an all-in-one rendering trigger that takes one or more <<../appendix/jsxPath#, entry jsxPaths>> with props and target container ID's, and uses *XP services* for auto-tracking and loading all the assets needed (including dependency chunks) before rendering them. @@ -114,7 +111,7 @@ The webapp begins with some basic HTML, setting it all up in the browser. .standalone.html: [source,html,options="nowrap"] ---- -include::../src/main/resources/webapp/standalone.html[] +include::../../src/main/resources/webapp/standalone.html[] ---- <1> We start by running React and ReactDOM from a CDN. <2> All the required css and js assets are linked. See controller below. @@ -146,13 +143,13 @@ Okay, moving on, a *webapp controller* is needed for XP to serve this HTML, and .webapp.ts: [source,typescript,options="nowrap"] ---- -include::../src/main/resources/webapp/webapp.ts[] +include::../../src/main/resources/webapp/webapp.ts[] ---- .standalone.ts: [source,typescript,options="nowrap"] ---- -include::../src/main/resources/webapp/standalone.ts[] +include::../../src/main/resources/webapp/standalone.ts[] ---- @@ -182,7 +179,7 @@ TIP: If you've been through the lesson in the previous chapter, you might recogn .script.js: [source,javascript,options="nowrap"] ---- -include::../src/main/resources/assets/webapp/script.js[] +include::../../src/main/resources/assets/webapp/script.js[] ---- @@ -190,7 +187,7 @@ include::../src/main/resources/assets/webapp/script.js[] [[output1]] === Output -Assuming you've been through the <>, you can now rebuild the project. But instead of opening Content Studio, open the XP main menu in the top right corner, choose _Applications_, and in the Applications viewer, select your app: +Assuming you've been through the <>, you can now rebuild the project. But instead of opening Content Studio, open the XP main menu in the top right corner, choose _Applications_, and in the Applications viewer, select your app: image:webapp_applications.png[title="Select your app in the Applications viewer", width=1024px] @@ -200,15 +197,9 @@ At the bottom of the app info panel, you'll see a URL where you can preview the image:webapp_url.png[title="URL to preview the webapp.", width=1024px] - - Clicking this link should now show you the working webapp - listing 3 initial movies and filling in more as you scroll down, just like in the preview at the end of the previous chapter. - - - - == 2. Webapp with XP helpers This is all nice and well, but a cumbersome part is that it requires you to supply values and asset URLs yourself, or ways to figure them out. Hashes in file names is a neat way of content-based cache busting, but keeping track of the resulting file names can be a chore. Even if that's not an issue, it could be handy to have a way to just supply the name (jsxPath) of the entry (or entries) you want to render, and let the system itself figure out what dependency chunk(s) are needed to load alongside the entry asset(s). Not to mention prevent them from being downloaded twice. @@ -224,7 +215,7 @@ Just like before, we get react/react-dom from a CDN, provide a pinch of styling .withHelpers.html: [source,html,options="nowrap"] ---- -include::../src/main/resources/webapp/withHelpers.html[] +include::../../src/main/resources/webapp/withHelpers.html[] ---- <1> This is where we <<#html1, previously>> put URLs to each specific dependency chunk and entry asset we want to use. Here, we only load the React4xp client-wrapper, and make it available in the browser’s namespace as `React4xp.CLIENT`. <2> As before, we set a few initial values for the final script to use. Two things are different here, though: first, we let the XP controller (right below) supply the appname-dependent content type and the content path to the site with the movies below it. And second: `serviceUrlRoot`. This value is the URL root of the XP services, and lets the script know where to look for the service that tracks the entries' assets and dependencies. @@ -237,7 +228,7 @@ The *webapp controller* needs to provide that extra little info to the values in .withHelpers.ts: [source,typescript,options="nowrap"] ---- -include::../src/main/resources/webapp/withHelpers.ts[] +include::../../src/main/resources/webapp/withHelpers.ts[] ---- @@ -251,7 +242,7 @@ The script asset is almost identical to before. All that's changed is in `render .script2.js: [source,javascript,options="nowrap"] ---- -include::../src/main/resources/assets/webapp/script2.js[] +include::../../src/main/resources/assets/webapp/script2.js[] ---- <1> No need to create the react components explicitly. As soon as the `props` are created, everything from there is handled by the `React4xp.CLIENT.renderWithDependencies` wrapper function. + diff --git a/docs/appendix.adoc b/docs/appendix.adoc index 753f3b93..05d48f4b 100644 --- a/docs/appendix.adoc +++ b/docs/appendix.adoc @@ -1,6 +1,6 @@ = Appendix :toc: right -In this section you'll find a collection of chapters that dive deeper into various aspects of React4XP. +In this section you'll find chapters that dive deeper into React4XP, such as build, configuration, entries and jsxPath - including the React4XP API. diff --git a/docs/hello-react.adoc b/docs/hello-react.adoc index bcd8c5c7..0e6de9dc 100644 --- a/docs/hello-react.adoc +++ b/docs/hello-react.adoc @@ -38,7 +38,6 @@ include::../src/main/resources/site/pages/hello-react/hello-react.xml[] ---- - [[react_component]] === React component diff --git a/docs/index.adoc b/docs/index.adoc index 8c7b9cb6..80194e63 100644 --- a/docs/index.adoc +++ b/docs/index.adoc @@ -35,8 +35,7 @@ The following five lessons present a more *advanced use cases*: - <>: Which react components are available to React4xp (called _entries_), and how to reference them. - <>: A more detailed and explicit syntax, for when your controller needs more control. - <>: Importing things into your entries, and controlling the compilation rules and output. -- <>: Introduction of how to use React4xp client-side, set up a data endpoint that serves XP content as pure data, and have the already-rendered React4xp components add more content asynchronously with that content data. -- <>: Here we build a webapp that take the same React4xp components out of Content Studio, and render the content with assets and data served from anywhere. +- <> dives deeper into client-side rendering and using React4XP when building Enonic webapps. == Dive deeper Have an urge for details beyond the basic tutorial, check out the <>. diff --git a/docs/media/graphql-playground.png b/docs/media/graphql-playground.png deleted file mode 100644 index 93cbce6c..00000000 Binary files a/docs/media/graphql-playground.png and /dev/null differ diff --git a/docs/media/guillotine.jpg b/docs/media/guillotine.jpg deleted file mode 100644 index 961d63e9..00000000 Binary files a/docs/media/guillotine.jpg and /dev/null differ diff --git a/docs/menu.json b/docs/menu.json index 94017e2d..ee9f6142 100644 --- a/docs/menu.json +++ b/docs/menu.json @@ -29,12 +29,18 @@ "document": "imports-and-dependency-chunks" }, { - "title": "8 - Client side", - "document": "client-side" - }, - { - "title": "9 - Standalone", - "document": "standalone" + "title": "Advanced topics", + "document": "advanced", + "menuItems": [ + { + "title": "Client side rendering", + "document": "client-side" + }, + { + "title": "Webapps", + "document": "standalone" + } + ] }, { "title": "Appendix",