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

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.

Related

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 }

is it possible to override a theme override with component classes prop?

I am wondering if there is a neat trick to use a component classes prop to override some CSS defined in a theme's overrides.
For instance :
If I want all Button components to have a different font-size than the default one. I can use the theme.overrides props to do so :
// this works, all Buttons text is 1.1rem
let theme = createMuiTheme({
overrides: {
MuiButton: {
label: {
"&": {fontSize: "1.1rem"}
}
}
}
})
Now if for some reason one of my button needs to have a different font-size, I was hoping using classes prop would do the job :
const useClasses = makeStyles({
smallerFontSize: {
fontSize: "0.9rem"
}
})
...
const classes = useClasses()
...
<Button
classes={{
// unfortunately this doesn't work, theme overrides is taking precedence
label: classes.smallerFontSize
}}
>
Some smaller text
</Button>
...
Since using classes prop allows us to target and override some component's CSS if default theme values have not been overridden, I find it confusing that theme overrides end up behaving somewhat differently and have a higher specificity than a one time rule.
I'd argue it kind of defeats the purpose of having a customisable theme.
But hopefully I'm missing something and your wisdom will help !
UPDATE
My mistake was to export the created theme and the makeStyles hook from the same module file.
Doing that made Mui insert theme <style> after the hook <style>.
To fix the issue and be able to use classes component props as I wanted to :
export theme and hooks from separate modules
make sure theme module has no dependency on the module exporting the hook
make sure when using ThemeProvider that it has no parent component importing the hook
I still don't quite understand why things worked before I added the overrides property on the theme object though.

Vuejs class component fields undefined

https://jsfiddle.net/78mLj9vb/
class App extends Vue {
message = 'Hello!';
shrek;//属性 property
constructor(){
super();
this.shrek = "This is my swamp!";//代入-assignment
//"This is my swamp!"がコンソールで出力されている Prints as it should
console.log(this.shrek);
this.print();
}
print(){
console.log(this.shrek);//[undefined]が出力されている Undefined printed to console
}
}
I'm learning Vuejs with class components and typescript. I do not understand why I can't access the fields of my class within methods. They are always undefined. I have tried doing the initial assignment to the field inline along with the property declaration, and I have also tried doing the assignment in the constructor. I imagine it's the Vuejs data binding mangling the class fields in a way that I do not understand, I have tried accessing them through this.$data to no avail. I understand it's probably not good design to have data that is unrelated to presentation in a component class, but this time around I don't have a database so I'm trying to hard code some data into a class method to fake it, so that I can then loop over the data w/v-for to create a select list. I've included a fiddle that looks nothing like what I'm actually trying to do, but illustrates the "issue" (my lack of understanding really).
How do you declare normal class fields outside of the Vuejs data-binding magic, or alternatively, how do you access the data that has been bound and changed by Vue?
For the best use change you code to this:
//...
#Component({
template: `
<div>
<h1>{{ message }}</h1>
<h1>{{ shrek }}</h1>
</div>
`
})
class App extends Vue {
data(){
return {
shrek: 'test',
message: 'Hello world!'
};
}
created(){
this.shrek = "This is my swamp!";
console.log(this.shrek);
this.print();
}
print(){
console.log(this.shrek);
}
}
new Vue({
el: '#app',
render: h => h(App)
})
//...
On your link here
After having practiced using Vue for a few more weeks and researching various topics on the internet I have come to the conclusion (which should have been obvious to me at first) that the answer is to use components only for what components are good for. That is to say, if you have some logic that gets gnarly and you want to alleviate it with helper functions, those helper functions can be declared outside the class as regular functions and used within the component code. Another upside of this approach is that if multiple components would get use out of your helper function or initialization code, you can instead move it into a typescript or javascript module and import it into those components that need it.
データ初期化や普通の論理を実施するヘルパー関数をコンポーネント内に置くより別の標準なTypescriptやJavascript関数に置いた方が良さそうです。Vuejsのコンポーネント・クラスの良い点はview作りだけでviewと関係ない論理をコンポーネント以外に置くべきです。それにそうするとその論理を別のファイルに置いてモジュール形で複数のコンポーネントはその論理を使えるようになります。

How to use CSS API of Grid Component

I'm trying to use the Grid component but I can't find out how to use the CSS API. Ths docs doesn't help me. I just don't get it..
Can someone please help me ?
I know this is not a really good place, sorry, but I can't find any answer anywhere confused
Ideally, you'd set direction to row and override the direction-xs-row class with the name of a class you define (which would set direction to column-reverse), but there are no classes exposed for overriding row for any breakpoint.
You could go the other way, setting direction to column-reverse and overriding direction-*-column-reverse (for all other breakpoints), but that would be tedious and somewhat insane.
The only way to do this at the moment would be to set the className prop to apply some responsive styling via JSS and withStyles:
// create a class that will set flex-direction for the xs breakpoint
const styles = theme => ({
[theme.breakpoints.down('xs')]: {
responsiveDirection: {
flexDirection: 'column-reverse',
},
},
});
// use withStyles to make the class available via the `classes` prop
export default withStyles(styles)(InteractiveGrid);
Then pass your class name, classes.responsiveDirection in this example, as the Grid's className prop:
{/* we would normally destructure classes from this.props */}
<Grid
container
className={this.props.classes.responsiveDirection}
>
Check this codesandbox for a working example.

AEM 6.0: Additional parameters when using data-sly-resource?

I am trying to implement something which I hope is relatively straight forward... I have one component (lets call it the wrapper component) which contains another component (lets call it the inner component) inside it via the data-sly-resource tag:
<div data-sly-resource="${ 'inner' # resourceType='/projectname/components/inner' }"></div>
I would like to pass in some additional parameters with this tag, specifically a parameter that can be picked up by sightly in the inner component template? I am trying to specify whether the inner templates outer html tag is unwrapped based on a parameter being passed in when the component is called via data-sly-resource.
After experimenting and perusing the sightly documentation, I can't find a way of achieving this.
Does anyone know if this is possible?
Many thanks,
Dave
You can use the Use-API to write and read request attributes if the alternatives proposed here don't work for you.
A quick example of two components where the outer component sets attributes that are then displayed by the inner component:
/apps/siteName/components/
outer/ [cq:Component]
outer.html
inner/ [cq:Component]
inner.html
utils/ [nt:folder]
setAttributes.js
getAttributes.js
/content/outer/ [sling:resourceType=siteName/components/outer]
inner [sling:resourceType=siteName/components/inner]
/apps/siteName/components/outer/outer.html:
<h1>Outer</h1>
<div data-sly-use="${'../utils/setAttributes.js' # foo = 1, bar = 2}"
data-sly-resource="inner"></div>
/apps/siteName/components/inner/inner.html:
<h1>Inner</h1>
<dl data-sly-use.attrs="${'../utils/getAttributes.js' # names = ['foo', 'bar']}"
data-sly-list="${attrs}">
<dt>${item}</dt> <dd>${attrs[item]}</dd>
</dl>
/apps/siteName/components/utils/setAttributes.js:
use(function () {
var i;
for (i in this) {
request.setAttribute(i, this[i]);
}
});
/apps/siteName/components/utils/getAttributes.js:
use(function () {
var o = {}, i, l, name;
for (i = 0, l = this.names.length; i < l; i += 1) {
name = this.names[i];
o[name] = request.getAttribute(name);
}
return o;
});
Resulting output when accessing /content/outer.html:
<h1>Outer</h1>
<div>
<h1>Inner</h1>
<dl>
<dt>bar</dt> <dd>2</dd>
<dt>foo</dt> <dd>1</dd>
</dl>
</div>
As commented by #AlasdairMcLeay, this proposed solution has an issue in case the inner component is included multiple times on the request: the subsequent instances of the component would still see the attributes set initially.
This could be solved by removing the attributes at the moment when they are accessed (in getAttributes.js). But this would then again be a problem in case the inner component is split into multiple Sightly (or JSP) files that all need access to these attributes, because the first file that accesses the request attributes would also remove them.
This could be further worked-around with a flag telling wether the attributes should be removed or not when accessing them... But it also shows why using request attributes is not a good pattern, as it basically consists in using global variables as a way to communicate among components. So consider this as a work-around if the other two solutions proposed here are not an option.
There is a newer feature that request-attributes can be set on data-sly-include and data-sly-resource :
<sly data-sly-include="${ 'something.html' # requestAttributes=amapofattributes}" />
Unfortunately it doesn't seem to be possible to construct a Map with HTL (=Sightly) expressions, and I don't see a way to read a request attribute from HTL, so you still need some Java/Js code for that.
unfortunately, no. there is no way to extend sightly functionality. you cannot add new data-sly attributes or modify existing ones. The best you can do is write your own helper using the USE API
If you just need to wrap or unwrap the html from your inner component in different situations, then you can just keep the html in the component unwrapped, and wrap it only when needed by using the syntax:
<div data-sly-resource="${ 'inner' # resourceType='/projectname/components/inner', decorationTagName='div', cssClassName='someClassName'}"></div>
If you need more complex logic, and you need to pass a value to your inner component template, you can use the selectors. The syntax for including the resource with selectors is:
<div data-sly-resource="${ 'inner' # resourceType='/projectname/components/inner', selectors='mySelectorName'}"></div>
The syntax to check the selectors in the inner component is:
${'mySelectorName' in request.requestPathInfo.selectorString}"
or
${'mySelectorName' == request.requestPathInfo.selectorString}"