Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Headless docs/start #3

Merged
merged 25 commits into from
Nov 6, 2020
Merged

Headless docs/start #3

merged 25 commits into from
Nov 6, 2020

Conversation

espen42
Copy link
Contributor

@espen42 espen42 commented Oct 1, 2020

Chapter 8 (guillotine requests) first draft: basically just https://github.com/enonic/doc-react4xp/blob/headlessDocs/start/docs/guillotine.adoc

@espen42 espen42 requested review from sigdestad and poi33 October 1, 2020 17:57
@poi33
Copy link

poi33 commented Oct 2, 2020

Ser ut som en bra start Espen. Har du tenkt å gjøre det mulig å gjøre react helt headless alså en webapp?

@espen42
Copy link
Contributor Author

espen42 commented Oct 8, 2020

Ja, skriver på kapittel 9 nå - webapp. Available now at a CR near you!

* commit 'cea53c88d8afe1889a46cadc525b1851cb484ecd':
  Emphasize that react4xp components don't need to be inserted into react-rendered XP regions, and vice versa.
  Important NPM install note during the setup.
@espen42 espen42 mentioned this pull request Nov 2, 2020
Copy link

@poi33 poi33 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The more i read on this guide i think we should create 2 different ones.
I see that this is only sued for the poor developers thrown into a headless cms and need to learn how our system works. Might ask Thomas about it.
One guide for pure headless.
And one for XP rendering and healess fetching?

Review not completed. about 50% done.

.site/content-types/movie/movie.xml:
[source,xml,options="nowrap"]
----
<content-type xmlns="urn:enonic:xp:model:1.0">
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do want the decleration of namespace, but i wonder if it might confuse people.
Since it can be tricky to setup in anything that is not intelliJ

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or just getting into the xml model etc. Keep it if you think people will mostly just ignore it and not ask questions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, fixing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<occurrences minimum="1" maximum="1"/>
</input>

<input name="description" type="HtmlArea">
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exclude all
Inlucde text
TextArea?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What did you mean, it should be a text area (or that it practically is)?

I just thought it'd be neat to both allow bold/italic/underline, which requires a html area (and demonstrates that transferring unescaped data and rendering with dangerouslyInsertHtml works).

It's not really a big deal, of course I can fix it if you think it makes things clearer.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This practically is a text area. I just don't understand why you excluded 90% of the html features. Just allow all no need to restrict on a demo.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excluding those features enables me to skip the processHtml step in XP...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about you @sigdestad , do you have an opinion? Between:

  1. using a textarea and keeping it real simple,
  2. like it is now: a simplifiled htmlarea with only bold/italic/underline, so that it processHtml isn't needed and we keep the server side of things simpler... but still have one content point that delivers html tags so that it demonstrates an instance of unescaped rendering (dangerouslyInsertHtml = data-th-utext) still working in a headless context, OR
  3. allowing full html features, so that the entire flow of that is demonstrated in a headless/hybrid context? On the downside: increases the complexity of the lesson a bit, and will require more writing/polish on these docs

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-> #7

Copy link
Contributor Author

@espen42 espen42 Nov 6, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


This is a pure entry wrapper that just imports the next react component from _react4xp/shared_.

Why import code from _shared_ instead of keeping it all in the entry? Two reasons:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove two reasons. And add a numbered list below. If you think it looks fine now, ignore comment 😄

Copy link
Contributor Author

@espen42 espen42 Nov 3, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, how about this:

Why import code from _shared_ instead of keeping it all in the entry? Firstly, it's a good rule of thumb to keep entries slim, for better optimization. And secondly, in addition to a Content Studio preview for single movies, we're going to use the *imported components* in the actual movie list too, for each single movie in the list.

It's more of a side note actually, bullets and numbered list both made it seem too central. 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


<Info heading="Description">
<div className="description"
dangerouslySetInnerHTML={{ __html: description }}>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this actually a react method dangerouslySetInnerHTML? 👍
If we change it to a textArea we might not scare people that much.
But they will not learn how to insert unformated text with react components.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it's the standard way - equivalent to thymeleaf's data-th-utext. Strictly speaking it's not a necessary part of this lesson, I just thought it would be interesting to show that this context handles that too. Hmm, scary, I guess it's supposed to be... :D Maybe worth a comment at least?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for more comments everything is so defailed allready. We probably want people to learn how to use html area.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sigdestad , same issue here.

Copy link
Contributor Author

@espen42 espen42 Nov 6, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two important points when using a controller mapping like this:

[NOTE]
====
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent node 👍 Easy to confuse the two.

====
First, the controller reference in a mapping in _site.xml_ must always refer to *the runtime name of the controller*. In our case, the source file of our controller is _/controllers/previewMovie_ *_.es6_*, but at compile time, this is compiled into *_.js_* which is used at XP runtime.

Second, controller mappings use qualified content type names that have *the name of the app* in it: `com.enonic.app.react4xp`. If/when you use a different name for your app, make sure to update content type references like this, e.g. `<match>type:'my.awesome.app:movie'</match>`
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could maybe even be part of the main text? I assume everyone will not use the default name in production?

Copy link
Contributor Author

@espen42 espen42 Nov 3, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, isn't notes supposed to be for emphasis, "above" the main text, like you-should-really-take-special-note-of-this? Or are they more like side notes, feel-free-to-skip-this stuff? Hope I don't have to rewrite too many of them...

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Differs from context. Its fine if we keep it like this. The most important is mentioning it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I'll just keep it then.


In order to make requests for a list of movies below a container item in the content hierarchy, we'll need a specific guillotine query string, as well as functionality to adapt the resulting data into the proper props structure for the react components.

And by using the same code on the frontend and backend, for this too, we gain a bit of isomorphism (the predictability of a singe source of truth, in short). So we'll make *a module with custom helper functionality* for our use case, and import from it in both places.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

singe -> single

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What did you mean?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(the predictability of a singe source of truth, in short)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, a typo! Hah, thanks!

Singed source of truth... 🔥

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.



// Not using util-lib, to ensure usability on frontend
const forceArray = maybeArray => Array.isArray(maybeArray)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Double ternary 😕

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you write your own forceArray just use that everywhere. No need to include lib-util then.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good points.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll need to restructure a little bit of code then, and subsequently the docs. Suggestion: we release the docs without this improvement, and create a polish-task for it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+
NOTE: Again, remember that this query hardcodes qualified names to a content type, that contain the name of the app: `com.enonic.app.react4xp:movie` and `com_enonic_app_react4xp_Movie`. Change these if your app name is not `com.enonic.app.react4xp`.
+
For more about the query language, see the link:https://developer.enonic.com/docs/headless-cms/2.x/api[guillotine API documentation].
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When i read query language i think abotu our implementation of elastic search. NoQl reference.
Maybe just say "For more about the guillotine query see ..."

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aha! Yep.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


const query = buildQueryListMovies(); <!--3-->

const variables = { <!--4-->
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the most meta variable i have seen.
A variable named variables containing varius variables used as variables. 😹
Jokes aside please rename this, maybe params?

Copy link
Contributor Author

@espen42 espen42 Nov 4, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haha, true dat. But the argument for keeping it like this: variables is what guillotine calls it, it expects the params query and variables. So, 1-to-1 (and also, it's sent through POST params, so then you'd get a params param. 😄 )

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm adding a parenthesis about this name, then

Copy link
Contributor Author

@espen42 espen42 Nov 4, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"<4> Build the variables object with the query's parameters (what's up with a variable called variables, you ask? This is for consistencty - the guillotine lib and its docs refer to the encapsulated object of values for the various variables in the query, as an argument called variables. Now we have that clarified),"

Copy link
Contributor Author

@espen42 espen42 Nov 4, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[source,sass,options="nowrap"]
----
.movieList {
margin: 0 auto; width: 1024px; max-width: 100%;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you set each value on a new line?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uhh, I don't really wanna? :D

In a regular code context I'd agree heavily. But here, the styling just takes heaps of space and creates a sidetrack for the reader's attention span in an already crowded doc...

Copy link
Contributor Author

@espen42 espen42 Nov 4, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

</site>
----

After rebuilding, the API is now up and running at `<site-url>/api/headless` (e.g. `http://localhost:8080/admin/site/preview/default/draft/moviesite/api/headless`).
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: Why did you choose to expose this as a controller mapping instead of a service?
I'm okay with both.

Copy link
Contributor Author

@espen42 espen42 Nov 4, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No great reasons.

  • I think it was @sigdestad 's suggestion.
  • I've never used one so I wanted to try. :D
  • Seemed at the time like it didn't matter much one way or the other. Now I guess I'm a bit locked-in.

handleResponseErrorFunc, <!--4-->
extractDataFunc, <!--5-->
handleDataFunc, <!--6-->
catchErrorsFunc // <!--7-->
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is 7 commented out? Poor 7.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


<1> `url` (string, mandatory): URL to the API endpoint, i.e. to the controller mapping of `headless/guillotineApi.es6`: `<site-url>/api/headless`.
<2> `query` (string, mandatory): valid link:https://developer.enonic.com/docs/headless-cms/2.x/api[guillotine query] string.
<3> `variables` (object, optional): object with key-value pairs where the keys correspond to parameters in the `query` string, e.g. the value of `variables.first` will be inserted into the query string as `$first`.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variables is to generic.
queryVariables will be more confusing, params could work. Values can we pass the query values?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above: guillotine itself calls it "variables", so this way it corresponds with everything else.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"<3> variables (object, optional): corresponds to the guillotine variables object: key-value pairs where the keys correspond to parameters in the query string. E.g. the value of variables.first will be inserted into the query string as $first."

Copy link
Contributor Author

@espen42 espen42 Nov 4, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@espen42 espen42 merged commit b47ec6f into master Nov 6, 2020
@espen42 espen42 deleted the headlessDocs/start branch November 6, 2020 12:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants