Render SolidJS component based on get parameter - solid-js

I have a simple SolidJS component:
export const PageContent: Component<{content: string}> = (props) => {
}
Now, since I would like to build a web app with many pages, and I would like to keep things like header, footer, nav, etc. the same across all pages, I thought that I could customize what it is displayed by PageContent based on a get parameter, like: https://my-app.com?content=home or ...?content=customers, ...
To keep thinks clean, I'd like to define a component for each possible value of the get parameter (i.e., for every page) in a separate folder (like: content/Home.tsx, content/Customers.tsx, etc.).
Can I return the appropriate component in PageContent based on the get parameter (with the content prop)?
Or there is a better way to do what I want to do?
I'm new to Solid and used to work with PHP-based websites, this is why the idea of the get parameter came to my mind (recalling index.php?page=).
Thanks
(I tried searching returning <props.content></props.content> but it didn't work.)

Best way to go using a router library like solid-js router. SolidJS Router lets you change your view based on the URL in the browser like you asked for. We use path instead of get parameters to match request to the page or component to display, however the logic is totally up to you weather to use query parameters or paths or any other request based value. It support wildcard routes to blanket match previously unmatched urls.
https://github.com/solidjs/solid-router
import { Routes, Route } from "#solidjs/router"
const Home = () => {
return (<div>Home</div>);
};
const Users = () => {
return (<div>Users</div>);
};
export default function App() {
return <>
<h1>My Pages</h1>
<Routes>
<Route path="/users" component={Users} />
<Route path="/" component={Home} />
<Route path="/about" element={<div>This site was made with Solid</div>} />
</Routes>
</>
}
Note: Currently SolidJS Playground does not support solid-js router, you need to use codesandbox or set your own local environment.

Related

Best way to pass props to a sibling component

I'm implementing a project with Next.js and I've been trying to find out the best way I could achieve the following.
I have a page /home that calls an API with getServerSideProps and receives information that is printed to the page in multiple of this components:
function PlaylistItem({playlistName,id,description,background} : { playlistName : string, id : string, description : string, background : string}) {
return (
<a className='flex-col justify-center flex text-center items-center cursor-pointer mb-6' href='/difficulty'>
<div className=' w-[100px] h-[100px] rounded-full' style={{backgroundImage: `url(${background})`,backgroundPosition:"center",backgroundSize:"cover"}}></div>
<p className='mt-2'>{playlistName}</p>
</a>
)
}
export default PlaylistItem
Depending on which of those components the user clicks, I want to pass the props (id, playlistName, etc) to the next page stated in the anchor tag.
I saw that I could achieve that with dynamic routes and passing the values as queries, but is it really the best way I could to this?
The ideal way to accomplish this is by using dynamic routes, also utilise cookies to save and get into the next page if you don't require share url capability for next page.
dynamic routes the only one way to achieve this

How to detect svelte component from DOM?

Currently making an google chrome extension to visualize svelte components, this would only be used only development mode. Currently I am grabbing all svelte components by using const svelteComponets = document.querySelectorAll(`[class^="svelte"]`); on my content scripts but it is grabbing every svelte element. What are some approaches to grab only the components?
Well you mostly can't get to the Svelte component from the DOM elements.
The reason, appart from Svelte won't give you / expose what's needed, is that there isn't a reliable link between components and elements.
A component can have no elements:
<slot />
Or "maybe no elements":
{#if false}<div />{/if}
It can also have multiple root elements:
<div> A </div>
<div> B </div>
<div> C </div>
By bending the cssHash compiler option a lot, you would probably be able to extract the component "name", maybe class name from the CSS scoping classes generated by Svelte. (Which, in turn could break CSS-only HMR updates with Vite, but that's another story.)
But from there, you won't be able to reliably get to the individual component instances... If we keep the component from the last example, once you've grabbed those 6 divs:
<div> A </div>
<div> B </div>
<div> C </div>
<div> A </div>
<div> B </div>
<div> C </div>
... how do you know where one component instance ends and where the other begins? Or even that there are two components?
I believe, the most reliable way to achieve what you want is probably to use internal Svelte APIs, including those that are used by the actual Svelte dev tools that you want to mimic. (Gotta love when private APIs are the "most reliable"!)
Necessary disclaimer: this only seems reasonable to do this in your case because it is a study subject, and because it's dev only. It would certainly not be wise to rely on this for something important. Private / internal APIs can change with any release without any notice.
If you go in the Svelte REPL and look at the generated JS after enabling the "dev" option, you'll see that the compiler adds some events that are provided for the dev tools.
By trials and experimentation, you can get a sense of how Svelte works, and what dev events are available. You'd also probably need to dig the sources of the compiler itself to understand what's happening with some functions... Being comfortable with a good debugger can help a lot!
For your intended usage, that is build a representation of the Svelte component tree, you'll need to know when a component instance is created, what is its parent component, and when it is destroyed. To add it to the tree, in the right place, and remove it when it goes away. With that you should be able to maintain a representation of the component tree for yourself.
You can know when a component is created with the "SvelteRegisterComponent" dev event (squared in red in the above screenshot). You can know the parent component of a component being instantiated by abusing { current_component } from 'svelte/internal'. And you can know when a component is destroyed by abusing the component's this.$$.on_destroy callbacks (which seems like the most fragile part of our plan).
Going into much more detail about how to proceed with this seems of bit out of scope for this question, but the following basic example should give you some ideas of how you can proceed. See it in action in this REPL.
Here's some code that watches Svelte dev events to maintain a component tree, and exposes it as a Svelte store for easy consumption by others. This code would need to run before your first Svelte component is created (or before the components you want to catch are created...).
import { current_component } from 'svelte/internal';
import { writable } from 'svelte/store';
const nodes = new Map();
const root = { children: [] };
// root components created with `new Component(...)` won't have
// a parent, so we'll put them in the root node's children
nodes.set(undefined, root);
const tree = writable(root);
// notify the store that its value has changed, even
// if it's only a mutation of the same object
const notify = () => {
tree.set(root);
};
document.addEventListener('SvelteRegisterComponent', e => {
// current_component is the component being initialized; at the time
// our event is called, it has already been reverted from the component
// that triggered the event to its parent component
const parentComponent = current_component;
// inspect the event's detail to see what more
// fun you could squizze out of it
const { component, tagName } = e.detail;
let node = nodes.get(component);
if (!node) {
node = { children: [] };
nodes.set(component, node);
}
Object.assign(node, e.detail);
// children creation is completed before their parent component creation
// is completed (necessarilly, since the parent needs to create all its
// children to complete itself); that means that the dev event we're using
// is fired first for children... and so we may have to add a node for the
// parent from the (first created) child
let parent = nodes.get(parentComponent);
if (!parent) {
parent = { children: [] };
nodes.set(parentComponent, parent);
}
parent.children.push(node);
// we're done mutating our tree, let the world know
notify();
// abusing a little bit more of Svelte private API, to know when
// our component will be destroyed / removed from the tree...
component.$$.on_destroy.push(() => {
const index = parent.children.indexOf(node);
if (index >= 0) {
parent.children.splice(index, 1);
notify();
}
});
});
// export the tree as a read only store
export default { subscribe: tree.subscribe }

How to extend HTML attribute interfaces when designing reasonml react components?

I'm learning reasonml and quite excited about it. Something I often do in typescript react code is:
type Props = React.HTMLProps<HTMLButtonElement> & { foo: boolean }
const SuperButton: React.FC<Props> = (props) => <button {/* stuff with props */ />
In this regard, I communicate to my users as a component library provider that this button extends normal HTML button attributes.
How can I express and extend normal html component attributes in my components?
I see that reason explicitly doesn't support spreading props: https://github.com/reasonml/reason-react/blob/master/docs/props-spread.md.
I do see that there is a composition strategy: How to compose props across component in reason-react bindings?, but not sure how to marry that up with normal HTML element component stuffs.
Any recommendations? Thanks!
It's possible to do something similar using ReasonReact.cloneElement, as Amirali hinted. The idea is to split up your component's props and the HTML button's props into two separate parameters for your component, render your button, and then clone it while also injecting the extra button props.
This page shows a component which encapsulates this clone-and-injection functionality:
module Spread = {
[#react.component]
let make = (~props, ~children) =>
ReasonReact.cloneElement(children, ~props, [||]);
};
Now, you can use this Spread component for your SuperButton component:
module SuperButton = {
[#react.component]
let make = (~foo, ~htmlButtonProps) =>
<Spread props=htmlButtonProps>
<button> (foo ? "YES" : "NO")->React.string </button>
</Spread>;
};
The htmlButtonProps prop will contain the regular HTML button props, while separately foo is your component's specific prop. The component can be used like this:
<SuperButton foo=true htmlButtonProps={"autofocus": true} />
Small housekeeping note: you don't actually need to define the modules with the module keyword. If you want you can put them in separate files called Spread.re and SuperButton.re. Reason files automatically become modules.

Given a Path get a reference to the Resource in Sightly

The component dialog has a pathfield widget where the authors can set a page path. In the Sightly component, I would like to look up that page resource and get (and display) properties from it.
The dialog...
<linkedPathLocation jcr:primaryType="cq:Widget"
fieldLabel="Linked Path"
name="./linkedPathLocation"
xtype="pathfield"
fieldDescription="Select a page. URL, Title, Description and Image are properties of the selected page"/>
The component code I would like to work (it's not).
<div class="row" data-sly-resource.page = "${properties.linkedPathLocation}">
<h1 >${page.title}</h1>
<p>${page.description}</p>
</div>
My question: Is there a way in Sightly to resolve and use some resource from a given path? If not, I could create a USE-API class and to do the following...
Page page = resourceResolver.resolve("/path/to/resource").adaptTo(Page.class);
I feel there should be a better answer which allows resources to be resolved directly from the Sightly, but the following USE-API solution works if not...
Java Use Class
public class PageHelper extends WCMUsePojo {
String pagePath = "";
#Override
public void activate() {
pagePath = get("path", String.class);
}
public Page getPage() {
return this.getPageManager().getPage(pagePath);
}
}
The component Sightly...
<div class="row" data-sly-use.linkedPage = "${'com.package.PageHelper' # path = properties.linkedPathLocation}">
<h1 >${linkedPage.page.title}</h1>
<p>${linkedPage.page.description}</p>
</div>
What you are trying to do, in essence, is render a Resource within the context of the rendering of another Resource. data-sly-resource seems the appropriate attribute to use, but instead of attempting to nest additional elements into the element containing the data-sly-resource you should define another Sightly .html file which dictates how the nested resource is to be rendered.
Let us say that your Resource is of type application/components/content/type. Within type.html you might have the following statement
<sly data-sly-resource="${properties.linkedPathLocation} # resourceType='application/components/content/type/subtype' />
You would then be able to define /apps/application/components/content/type/subtype/subtype.html containing the rendering to produce which would be invoked in the context of the Resource identified by your path.
The main purpose of Sightly templates is separation of responsibility (frontend & backend) so that we can have simple, clean and beautiful html markup which is designer friendly and easily readable.
For your case, writing a Class (Java/Javascript) to process dialog information, and supply it back to Sightly template is correct way. Read here for more details.

What is the proper way to integrate dynamic content into the layout.ejs file in a Sails.JS application?

Say I wrote a blog app in Sails.js.
On every page in this app, there is a sidebar widget called "Recent Posts", where it lists the titles of the 5 most recent posts and clicking on them takes you to the post in question.
Because this sidebar widget is present on every page, it should be in layout.ejs. But, here we have a conflict - dynamic content is only supposed to be pulled from the database in the controller action for rendering a specific view.
This dynamic content isn't for a specific view, it's for the whole site (via layout.ejs).
By the conventions that I understand, I'd have to get that dynamic content data for the sidebar widget in every controller action that renders a view (otherwise I would get an undefined error when I attempt to call that local in my layout.ejs file).
Things I've tried / considered:
Load that dynamic content in every controller action that renders a view (this solution is very bad) and calling that dynamic content in layout.ejs as if it were a local for the specific view. This works fine, but goes against D.R.Y. principles and quite frankly is a pain in the ass to have to run the same query to the database in every controller action.
As per another similar stackoverflow question, create a new config (E.G. config/globals.js), load my dynamic content from my database into that config file as a variable, and then calling sails.config.globals.[variable_name] in my layout.ejs file. This also worked, since apparently config variables are available everywhere in the application -- but it 's a hacky solution that I'm not a fan of (the content I'm loading is simply the titles and slugs of 5 recent posts, not a "global config option", as the solution implies).
Run the query to get the dynamic content inside the .EJS file directly between some <% %> tags. I'm not sure if this would work, but even if it did, it goes against the separation of concerns MVC principle and I'd like to avoid doing this if at all possible (if it even works).
As per a lengthy IRC discussion # http://webchat.freenode.net/?channels=sailsjs, it was suggested to create a policy and map that policy to all my controllers. In that policy, query the database for the 5 most recent posts, and set them to the req.recentposts. The problem with this solution is that, while the recent posts data will be passed to every controller, I still have to pass that req.recentposts data to my view -- making it so I still have to modify every single res.view({}) in every action. I don't have to have the database query in every action, which is good, but I still have to add a line of code to every action that renders a view... this isn't D.R.Y. and I'm looking for a better solution.
So, what is the proper solution, without needing to load that dynamic content in every controller action (a solution that adheres to D.R.Y. is what I'm lookng for), to get some dynamic content available to my layout.ejs file?
In folder /config you should create a file express.js and add something like that:
module.exports.express = {
customMiddleware: function(app){
app.use(function(req, res, next){
// or whatever query you need
Posts.find().limit(5).exec(function(err, posts){
res.locals.recentPosts = posts;
// remember about next()
next();
});
});
}
}
Then just make some simple loop in your view:
<% for(var i=0; i<recentPosts.length; i++) { %>
<% recentPosts[i].title %>
<% } %>
Here are some links to proper places in documentation:
https://github.com/balderdashy/sails-docs/blob/0.9/reference/Configuration.md#express
and
https://github.com/balderdashy/sails-docs/blob/0.9/reference/Response.md#reslocals
I found out another way to do this. What I did was to create a service that could render .ejs files to plain html by simply taking advantage of the ejs library already in sails. This service could either be invoked by the controller, or even passed as a function in the locals, and executed from within the .ejs. The service called TopNavBarService would look like:
var ejs = require('ejs');
exports.render = function() {
/* database finds goes here */
var userInfo = {
'username' : 'Kallehopp',
'real_name' : 'Kalle Hoppson'
};
var html = null;
ejs.renderFile('./views/topNavBar.ejs', {'locals':userInfo}, function(err, result) { html = result; });
return html;
}
In the constroller it could look like:
module.exports = {
testAction: function (req, res) {
return res.view('testView', {
renderNavbar: TopNavBarService.render // service function as a local!
});
}
};
This way you can create your customized ejs-helper that could even take arguments (although not shown here). When invoked, the helper could access the database and render a part of the html.
<div>
<%- renderNavbar() %>
</div>