How can I create a dynamically interpolated string in javascript? - javascript

I'm working on creating a reusable UI component and am trying to figure out how to allow the consumer of the component to provide their own template for a particular area of the component.
I'm using typescript and am trying to utilize string interpolation to accomplish this as it seemed the most appropriate course of action.
Here is what I have so far:
export class Pager {
pageNumber: number = 1;
getButtonHtml(buttonContentTemplate?: string, isDisabled?: boolean): string {
buttonContentTemlpate = buttonContentTemplate || '${this.pageNumber}';
isDisabled = isDisabled || false;
return `<button id="button-id" type="button" ${!isDisabled ? '' : disabledAttribute}>
${buttonContentTemplate}
</button>`;
}
}
I have some other methods that will update the page number based off user input/interaction, but I want it to work that when getButtonHtml gets called, the return value would be <button id="button-id" type="button">1</button>, but instead I'm getting <button id="button-id" type="button">${this.pageNumber}</button>.
Is there a way to get javascript to evaluate the string again, and interpolate the remaining place holders?
I've looked at the MDN article on this topic and think that the String.raw method might possibly be what I need to use, but I wasn't sure and no matter what I try, I haven't gotten it to work.
Any help would be greatly appreciated.

The problem is that Template literals are interpreted immediately.
What you want to do is lazy load the template. So it would be best to pass in a function that returns a string.
export class Pager {
pageNumber: number = 1;
getButtonHtml(template?: () => string, isDisabled=false): string {
template = template || function() { return this.pageNumber.toString() };
return `<button id="button-id" type="button" ${!isDisabled ? '' : disabledAttribute}>
${template()}
</button>`;
}
}
Additionally, you can take advantage of default parameters to avoid the || trick.

Related

Conditionally set v-model in Vue

I have a series of inputs that could be either checkboxes or radio buttons, depending on a value in the data of my vue component.
In particular, I have a Question component, and questions may accept only one answer or multiple answers. I have a selected_answers array in my data, and I was thinking I could have the checkboxes target it as their v-model, while the radio buttons could target selected_answers[0]. This way, I don't have to copy-paste che input elements and just change their type and v-model.
So, my solution would look something like this:
<input
:type="question.accepts_multiple answers ? 'checkbox' : 'radio'"
:id="'ans-' + answer.id"
:value="answer.id"
v-model="question.accepts_multiple_answers ? selected_answers : selected_answers[0]"
/>
However, eslint complains about my code:
'v-model' directives require the attribute value which is valid as LHS
What's a way I can accomplish what I'm trying to do?
You cannot use any advanced code inside of v-model (just a basic string), you could export question.accepts_multiple_answers ? selected_answers : selected_answers[0] to a computed and plug the computed to the v-model.
If you need to have a setter, you will need to write a computed setter, this looks like this
computed: {
fullName: {
// getter
get() {
return this.firstName + ' ' + this.lastName
},
// setter
set(newValue) {
var names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
}
Meanwhile, since v-model is just some sugar syntax, you could also replace it with usual :value + #input (depending of the type of the field). I do prefer to use those 2 than v-model nowadays, especially for the kind of limitations that you do have right now.
You can't really do it like that.
I recommend you to set up a variable when loading your component like that:
data()
{
return {
model_string: '',
}
}
Then you give your variable some values depending on your own conditions
created() {
if (your.first.condition) {
this.model_string = your.value;
} else if (your.other.condition) {
this.model_string = your.value;
}
}
After this, you can use it in your view as you wish
<input v-model="model_string" ..... // your attributes>
Ended up figuring it out by myself.
<input
:type="question.accepts_multiple_answers ? 'checkbox' : 'radio'"
:id="'ans-' + answer.id"
:value="question.accepts_multiple_answers ? answer.id : [answer.id]"
v-model="selected_answers"
/>

React render html from a string with a dynamic variable

I am working with react app in typescript. From API, I have this input:
a) list of variable names ["name", "surname"]
b) few strings in form of simple html with variables "<p>Hello, how are you {name}?</p>"
c) number of inputs with variables such as {input1: "name"}
everything as a string/JSON
what i need to do is: render simple html (only few tags) received from API but "create" binding between those dynamic inputs and variables in strings
in static world, result would look like:
[name, setName] = useState("")
<p>Hello, how are you {name}?</p>
<input type="text" onChange={e => setName(e.target.value)}/>
However, all of this is dynamic. String "<p>Hello, how are you {name}?</p>" doesnt get binded to the input on its own.
I tried:
setting variable [vars, setVars] = useState({}), property for each dynamic variable, with
a) dangerouslySetInnerHTML - renders only html (cannot bind the variable inside to the input)
b) react-html-parser - same as above
c) babel.transform - couldnt make it work as this is done dynamically and in browser, it cannot find the right preset, i couldnt make the mimified babel.js work with typescript How to render a string with JSX in React
do you see some easy way? For example how could i use React.createElement to render html with "live" variable inside, represented as {variableName}? Or maybe something out of the box? giving each elemnt a class and finding the class in DOM and editing the text with input change would be probably very non-optimal?
I hope this could be a better example:
{response:
{
variables: ["name", "name2", "mood"],
texts: [
"<em> Hello! My name is {name}</em>",
"<p> Hi {name} ! I am <strong>{name2}</strong> and I feel {mood} today</p>"
],
inputs: [
{
label: "How do i feel?"
input: {mood}
}
]
}
}
EDIT:
This should give you a good idea:
https://stackblitz.com/edit/react-ts-cwm9ay?file=DynamicComponent.tsx
EDIT #2:
I'm pretty good with React and interpolation, it's still in progress (specifically the docs, but the readme is complete) but I'm going to shamelessly plug my ReactAST library
EDIT #3 - If you're interested in doing crazy dynamic interpolation, then you might also want to check out a neat dynamic interpolation (and it's reverse) library
Let's assume this:
{
vars: ['name','surname'],
strings: ["<p>Hello, how are you {name}?</p>","<p> It's night to meet you {name} {surname}"],
inputs: {
input1: 'name',
input2: 'surname'
}
}
Create a component (or set of components) that can do this.
I haven't even ran any of this, but here's the idea. I'll put it in a stackblitz in a bit and polish it up to make sure it works:
const MyDynamicComponent = ({vars,strings,inputs}) => {
const [state,setState] = useState(vars.reduce((acc,var) => ({...acc,[var]:undefined}));
return (
<>
<MyDynamicText vars={vars} strings={strings} state={state}/>
<MyDynamicInputs onChange={(name,val) => setState({[name]:val}) state={state} inputs={inputs}/>
</>
)
}
const MyDynamicText = ({vars,strings,state}) => {
return strings.map((s,i) => s.replaceAll(`{${vars[i]}}`,state[vars[i]])
}
const MyDynamicInputs = ({onChange,inputs,state}) => {
return Object.entries(inputs).map(([inputName,varName]) => <input key={inputName} onChange={e => onChange(varName,e.target.value)} value={state[varName]}/>
}

Is there a better solution than adding || [ ] to each field for a null check in ES6

I am working with a child component where an array item is passed as prop. However, that item also contains another array where there should be null and undefined check before rendering. Adding || [] to each filed didn't seem the best way. Is there a better way that I am missing?
const ChildComponent= ({ newsItem, textColor, newsItemHeadingColor }) => {
const imageFormats = newsItem?.itemImage?.formats;
const imageSrc = useProgressiveImage({ formats: imageFormats });
const history = useHistory();
const { state } = useContextState();
const { schemeMode, colorSchemes } = state;
const { itemTagColor, itemTagTextColor } = getThemeColors({
schemeMode,
colorSchemes,
colors: {
itemTagColor: newsItem?.itemTagColor?.name,
itemTagTextColor: newsItem?.itemTagTextColor?.name,
},
});
return (
<NewsItem
onClick={() => {
const url =
newsItem?.itemTag === 'Lab'
? getPageLink({
page: newsItem?.itemLink || [], <<<<<< this check
})
: newsItem?.itemLink || []; <<<<<< this check
url && history.push(url);
}}
>
<ImageContainer>
<Image src={imageSrc} alt={`${newsItem?.itemImage || []}`} /> <<<<<< this check
</ImageContainer>
<NewsItemTag color={itemTagColor} itemTagTextColor={itemTagTextColor}>
<Body4>{`${newsItem?.itemTag || []}`}</Body4> <<<<<< this check
</NewsItemTag>
<NewsItemName newsItemHeadingColor={newsItemHeadingColor}>{`${
newsItem?.itemTitle || [] <<<<<< this check
}`}</NewsItemName>
<Description textColor={textColor}>{`${
(newsItem.description &&
newsItem?.itemDescription) || [] <<<<<< this check
}`}</Description>
</NewsItem>
);
};
In fact yes, you have many many ways for improving your code and the readabilty of it.
In your <NewItem /> onClick function:
I have nothing to say for the first one, it seems correct since you want the item link or an empty array
For the second one, your are already using the optional chaining operator, the "?". This operator check if there is a value, if not, it returns null. That means that should be enough, since you check after your url variable is defined or not. So simply write that : : newsItem?.itemLink
For the third one, the one on your <Image /> component, First there is no need at all to put everything in backquote. Simply alt={newsItem?.itemImage || []} should work fine. Then, alt attribute is taking a string, so maybe change it with alt={newsItem?.itemImage || ""}
For the three last one, they are basically the same, you have content to display and you want to display nothing if there are empty. The fact here is that your are rendering some elements even if there is no content and this is not an optimized way. The cleanest way in my opinion in order to make your template more readable will be to define variable before your return function and based on this variable, you will display the appropriate section :
Before your return function:
const hasTag = !!newsItem?.itemTag;
const hasTitle = !!newsItem?.itemTitle;
const hasDescription = !!newsItem.description && !!newsItem?.itemDescription;
In your template:
{hasTag && <NewsItemTag color={itemTagColor} itemTagTextColor={itemTagTextColor}>
<Body4>{newsItem.itemTag}</Body4>
</NewsItemTag>}
{hasTitle && <NewsItemName newsItemHeadingColor={newsItemHeadingColor}>
{newsItem.itemTitle}
</NewsItemName>
{hasDescription && <Description textColor={textColor}>
{newsItem.itemDescription}
</Description>
I hope this was clear, hit me up if this wasn't.
Happy coding :)
There are multiple ways that you could handle this in javascript, assume that we have a component that receives a prop called newsItem which is an object
You could do that in the following ways
Using the logical operator or (es6)
const itemLinks = newsItem.links || []
you dont need the optional chaining operator (?.) here if you know that newsItem is an object and not undefined.
This is the shortest syntax, and it works fine as long as links is either undefined or an array
however it becomes problematic if later links become a boolean variable, which will yield incorrect results
This method is not safe, and it becomes cumbersome to handle as properties become nested deeply
Using lodash get method or some other library or a custom solution (es6)
because in es6 the only practical way was to either the logical operator and this was a common problem, custom solutions were brought it, basically any solution should have two advantages over the logical or operator
They should work fine with boolean values
They should be easier to work with deeply nested values
custom someDeepleyNested = get(newsItem, 'property1.property2.property3', 'defaultValue')
// there is no need for property1, property2, property3 to exist at all
Optional chaining operator and Nullish coalescing Operator (stage 4 finished proposals)
Because this was a common problem for all javascript developers,the language had to bring a native solution
const someNestedProperty = newsItem?.property1?.property2?.property3 ?? 'someDefaultValue'
// notice that both the Optional chaining operator and Nullish coalescing Operator, work with both undefined and null
Specific to react: use default props
const Component = ({newsLink}) => {}
Component.defaultProps = {
newsItem: {
itemLinks: []
}
}

ReactIntl.FormattedPlural Component to String

I am new to React.
I used ReactIntl.FormattedPlural to format the plural.
<FormattedPlural
value={10}
one='message'
other='messages'
/>
It works when I place this component in the render() function. However, I have a scenario that I want to get the String and passed into a function.
Instead of Using {value ==1 ? 'message' : 'messages'}, can I use ReactIntl.FormattedPlural to achieve this?
In any case, in the end you need to put it into the render.
Because the FormattedPlural is a component. To display it, you need to render it. It's how React works.
You can save it as variable:
const text = (<FormattedPlural value={10} one='message' other='messages' />)
For example if you have a condition and need to decide which string to render.
But in the end, the text should passed to the render to be displayed for user.
Derived from FormattedPlural implementation:
const { formatPlural } = useIntl()
// get the plural category ("one" or "other")
const pluralCategory = formatPlural(count);
const options = {
one: "message",
other: "messages",
}
const plural = options[pluralCategory as 'one'] || options.other;
See also: https://formatjs.io/docs/react-intl/api/#formatplural

Truncating processed html in vuejs

I am using v-html to strip html tags but still show the processed html. Problem is, I only want 200 characters to show. I can build truncating scripts but v-html doesn't seem to take filters. How can I achieve this?
For example:
// This
<div class="excerpt" v-html="body_html"></div>
// Into this
<div class="excerpt" v-html="body_html | truncate(200)"></div>
I tried building a striptag filter and truncate + strip the tags of a regular <p> but it didn't process the html. In other words, I got raw HTML back without any bold, italics, etc
For example, like this:(not my preferred method, unless you can find a way for me to improve it so I can still get rendered HTML.
<div>{{strippedContent | truncate(200)}}</div>
Vue.filter("truncate", function(value, length) {
if (!value) return "";
value = value.toString();
if (value.length > length) {
return value.substring(0, length) + "...";
} else {
return value;
}
});
export default {
data() {
return {
body_html: this.post.body_html
};
},
computed: {
strippedContent() {
let regex = /(<([^>]+)>)/gi;
return this.body_html.replace(regex, "");
}
}
};
Can you try another (local) way from the docs https://v2.vuejs.org/v2/guide/filters.html ?
Global (your way) registering of the filter should be made before creating the Vue instance. Can you check it?
Upd.
Also there are bugs on working together v-html and filters:
"v-html does not work with filters"
https://github.com/vuejs/vue/issues/4352
"Vue filter dont work on v-html"
https://github.com/vuejs/vue/issues/6056
SOLUTION
In the template, replace the v-html line with
<div :inner-html.prop="body_html | truncate(250)"></div>
The striphtml filter is not needed any longer

Categories

Resources