How to pass nested data structures as properties in LitElement? - lit-element

In a parent component I have something like:
render() => {
const data = {a:1,b:[1,2,3]}; // of course this is a simplified version of the code
return html`<child-component data=${data}></child-component>`
}
Which is basically equivalent to:
render() => {
const data = {a:1,b:[1,2,3]}; // of course this is a simplified version of the code
return html`<child-component data="[object Object]"></child-component>`
}
Which is basically useless...
Is there a simple way to pass complex object hierarchies into litElement components?
As far as I can tell, my options are:
Option 1. Use attributes: I'm a bit of a litElement noob so I'm not sure if this will work and I'm not sure how to make it work without having to make extra function calls. It would be nice if I could just do all the necessary work inside html.
Research in progress.
Option 2. Use Json.
Stringify the object in the parent component
render() => {
const data = {a:1,b:[1,2,3]}; // of course this is a simplified version of the code
return html`<child-component data=${JSON.stringify(data)}></child-component>`
}
then parse the json in the child component.
This just seems a bit inelegant to me though.
But it works.

In this case what you probably want is to pass the object as a property rather than as an attribute. For complex data such as objects, arrays, functions, etc. that's the preferred method.
You can do it with the following syntax:
render() => {
const data = {a:1,b:[1,2,3]};
// note the period (.), that's the token used to identify that you're passing data as a property
return html`<child-component .data=${data}></child-component>`
}
In general, you should probably give Lit's templating guide a read as some of the most common use cases are covered throughout it.

Related

Can you use a Map instance as an easy-peasy store property?

ie.
const store = {
values: new Map(),
// (gross trivial accessor)
setValue: action( (state, payload) => {
state.values.set(payload.key, payload.value);
}
}
I'm curious because easy-peasy uses a Proxy on the store object (and objects nested within) so that in your action you can safely mutate the state object directly (https://easy-peasy.now.sh/docs/tutorials/primary-api.html#modifying-the-state). I don't know if this also works when using non Plain Old JavaScript Objects, such as Maps.
It looks like this is possible on certain versions, but not without first declaring support for the feature (so the code above will not work right out of the box, as of now). See here for more info: https://github.com/ctrlplusb/easy-peasy/issues/440

Importing a modified String class from another file in nodejs

I have added to the String class three prototype classes on a file classed parse.js:
String.prototype.parseCropTo = function (needle) {
if (this.includes(needle) === false) return false;
return this.slice(0, this.indexOf(needle)).trim();
}
String.prototype.parseCropFrom = function (needle) {
if (this.includes(needle) === false) return false;
return this.slice(this.indexOf(needle) + needle.length).trim();
}
String.prototype.parseCropBetween = function (needleFrom, needleTo) {
let haystack = this.parseCropFrom(needleFrom);
if (haystack != false) return haystack.parseCropTo(needleTo);
}
As far as I can see, imported files have to expose specific functions and then they are called via a variable. However, I wish to import parse.js to other files so I could use these functions directly on strings:
let haystack = 'This is a lovely day';
console.log(haystack.parseCropBetween('is', 'day'));
Is this possible? thanks in advance.
By extending the prototype of String, you will have these methods on every string you'll ever use, however you need to load it somewhere in your code, because you won't be able to use that beforehand.
The reason that works is because your'e accessing String.prototype by reference, as with all non-primitive types in javascript so calling it once, will get you set for the rest of your code.
Generally speaking, it's not advised to extend native constructs.
See full example here:
https://codesandbox.io/s/epic-cerf-4qshv?file=/src/App.js
Additionally, I'd advise you to read some opinions about extending prototypes in javascript and considers the pros and cons of this approach:
Why is extending native objects a bad practice?

Rendering one of multiple pages with lit-element

The lit-element documentation describes conditional rendering via (condition ? a : b). I was wondering how to use that to render one of multiple pages, f.e. in combination with mwc-tab-bar from Googles material web components.
My current solution is something like this:
render() {
... other stuff ...
${this.selectedPage === 0 ? html`
<div>
...
</div>
` : html``}
${this.selectedPage === 1 ? html`
<div>
...
</div>
` : html``}
... further pages ...
}
I don't like the :html`` part but is that how it's meant to be?
Use more simple code like this.
constructor(){
super();
// don't forget add `prop` and `selectedPage` to `static get properties()`
this.prop = 1;
}
render() {
return this.getPage(this.selectedPage);
}
getPage(num){
switch(num){
default:
case 1:
return html`<div>P${this.prop}<div>`;
case 2:
return html`<div>P2<div>`;
}
}
There are multiple ways of achieving this, your solution is one, but as you mention, it's not the prettiest
One way you could modularize this somewhat is using an object/array and render functions, basically the idea is this:
First, define render functions for each page (this can be on the same file or on different files):
const page0Renderer = (context) => {
return html`<section>${context.someData}</section>`;
};
Then, you could define an object that has a match between the page identifiers and their respective functions, you are using numbers so the sample below uses numbers:
const pageRenderers = {
'0': page0Renderer,
'1': page1Renderer,
'2': page2Renderer,
// etc
};
And in your main render function you could use all these like this:
render() {
return html`
${pageRenderers[`${this.selectedPage}`](this)}
`;
}
This would basically call the render function that matches the selected page and send it a reference to the main web component so that you can access its properties.
Then again, this approach also has its flaws and I wouldn't really recommend it much if you need your child templates to be complex.
In that case, instead of rendering functions you probably would be better off creating other components for each view and that way you could also do some lazy loading and so on.
For that kind of approach, you might want to check out routers like vaadin router which help you both with routing and changing which component gets displayed accordingly

Is there a way to convert a graphql query string into a GraphQLResolveInfo object?

I have written a piece of software that parses and formats the fourth parameter of a graphql resolver function (the info object) to be used elsewhere. I would like to write unit tests for this software. Specifically, I do not want to build the GraphQLResolveInfo object myself, because doing that would be very cumbersome, error-prone and hard to maintain. Instead, I want to write human-readable query strings and convert them to GraphQLResolveInfo objects so I can pass those to my software.
After extensive googling and reading of the graphql-js source code, I have not found a simple way to do what they are doing internally. I'm really hoping that I am missing something.
What I am not trying to do is use the graphql-tag library, because that just generates an AST which has a very different format from the GraphQLResolveInfo type.
Has anyone done this before? Help would be much appreciated!
I will keep monitoring this question to see if a better answer comes along, but I've finally managed to solve my particular issue by creating as close an approximation of the GraphQLResolveInfo object as I need for my particular use case.
The GraphQLResolveInfo object is composed of several attributes, two of which are called fieldNodes and fragments. Both are in fact parts of the same AST that graphql-tag generates from a query string. These are the only parts of the GraphQLResolveInfo object that concern the software I wrote, the rest of it is ignored.
So here is what I did:
import gql from 'graphql-tag';
// The converter function
const convertQueryToResolveInfo = (query) => {
const operation = query.definitions
.find(({ kind }) => kind === 'OperationDefinition');
const fragments = query.definitions
.filter(({ kind }) => kind === 'FragmentDefinition')
.reduce((result, current) => ({
...result,
[current.name.value]: current,
}), {});
return {
fieldNodes: operation.selectionSet.selections,
fragments,
};
};
// An example call
const query = gql`
query {
foo {
bar
}
}
`;
const info = convertQueryToResolveInfo(query);
From the AST generated by graphql-tag, I extract and modify the operation and fragment definitions so that they look the way they do within the GraphQLResolveInfo object. This is by no means perfect and may be subject to change in the future depending on how my software evolves, but it is a relatively brief solution for my particular problem.

How to add context helper to Dust.js base on server and client

I'm working with Dust.js and Node/Express. Dust.js documents the context helpers functions, where the helper is embedded in the model data as a function. I am adding such a function in my JSON data model at the server, but the JSON response to the browser doesn't have the function property (i.e. from the below model, prop1 and prop2 are returned but the helper property is not.
/* JSON data */
model: {
prop1: "somestring",
prop2: "someotherstring",
helper: function (chunk, context, bodies) {
/* I help, then return a chunk */
}
/* more JSON data */
I see that JSON.stringify (called from response.json()) is removing the function property. Not sure I can avoid using JSON.stringify so will need an alternative method of sharing this helper function between server/client. There probably is a way to add the helper functions to the dust base on both server and client. That's what I'm looking for. Since the Dust docs are sparse, this is not documented. Also, I can't find any code snippets that demonstrate this.
Thanks for any help.
send your helpers in a separate file - define them in a base context in dust like so:
base = dust.makeBase({foo:function(){ code goes here }})
then everytime you call your templates, do something like this:
dust.render("index", base.push({baz: "bar"}), function(err, out) {
console.log(out);
});
what this basically does is it merges your template's context into base, which is like the 'global' context. don't worry too much about mucking up base if you push too much - everytime you push, base recreates a new context with the context you supplied AND the global context - the helpers and whatever variables you defined when you called makeBase.
hope this helps
If you want stringify to preserve functions you can use the following code.
JSON.stringify(model, function (key, value) {
if (typeof(value) === 'function') {
return value.toString();
} else {
return value;
}
});
This probably doesn't do what you want though. You most likely need to redefine the function on the client or use a technology like nowjs.

Resources