How to dynamically populate tailwindcss grid-cols-x property? - reactjs

I am currently working on a page using NextJs and TailwindCss. The user has the ability of loading an image locally and setting the number of pieces horiz/vert (rows/cols) that they wish the image to be split in. To properly display this, I need to set the grid to the proper number of columns in the parent element.
I have an API I call locally that uses sharp to perform the split and calculate the width and height and I sort the images in my array so that they are in order since it is async. I then also generate a dynamic class string that just populates the proper number of columns for later assignment to my parent grid elements class.
CLASS STRING POPULATION
const gridClass = `grid grid-cols-${numCols} gap-2 pt-2`;
//*** At this point, the value of gridClass, if columns were set to 3, using template literals is :
'grid grid-cols-3 gap-2 pt-2'
//The proper string is now populated and passed back in the API response via classCss key
res.status(200).json({ msg: 'Success splitting', tileData: tiles, classCss: gridClass})
PAGE SNIPPET:
<div id="final">
<div className={tileCss} > //<--This is where I pull in the generated class string
{
imageData.map((nft, i)=>(
<div key={i} className='border shadow rounded-x1 overflow-hidden'>
<Image src={nft.imgSrc} alt="image" layout="responsive" width={nft.tileDimX} height={nft.tileDimY}/>
</div>
))
}
</div>
</div>
This sometimes works but other times it doesn't. Usually if I set the columns to 3, it seems to work properly, but if I set it to 5 lets say, regardless of the input image size, it just puts them all in a single column with large images. Oddly however, the parent grid class on the page is correct, it just seems that it isn't adhered to. I will provide some snapshots below to show what I'm talking about. I've been trying to figure this out for a couple days, however I haven't had luck and since I'm new to NextJs I thought I would share here and see if I'm just doing something stupid. Thanks!
The below results also don't seem to care if the viewing window is stretched wide or reduced in size. I just took the snapshots below so that you could see what was happening in a smaller viewing window.
This is the expected result where the image columns should match the columns entered by the user:
Notice how the css class shows up under styles as well:
This is the improper result, where the user selected 5 columns, the image was split into the correct number of columns, but the display of this in the front end grid does not follow the css.
As you can see grid-cols-5 is correct from a class standpoint, but the viewed result doesn't adhere to this.
Grid-cols-5 is in html class but missing under styles applied:

So I finally found the source to the issue. It seems that tailwindcss performs an optimization on classes brought in and if you are not using the class anywhere in your html, it doesn't include those classes. So I not only needed to return my dynamic string with the expected class, but I also needed to add all the classes I would possibly be using into my tailwind.config.js file under the safelist key.
So my config now looks like this and it is working as expected:
module.exports = {
content: [
'./pages/**/*.{js,ts,jsx,tsx}',
],
safelist: [
{
pattern: /grid-cols-./,
}
],
theme: {
extend: {},
},
plugins: [],
}
More information can be found here:
https://tailwindcss.com/docs/content-configuration#using-regular-expressions
Thanks again Mohit for your assistance.

One of the problem I noticed is in grid-cols-${numCols} in the line
const gridClass = `grid grid-cols-${numCols} gap-2 pt-2`;
TailwindCSS doesn't allow you to generate classes dynamically. So when you use the following to generate the class… grid-cols-${numCols} as a string.
…TailwindCSS will not pick that up as a valid TailwindCSS class and therefore will not produce the necessary CSS.
You can use the function from where you are getting numCols and instead of returning the value of numCols, simply return grid-cols-${numCols}.
Suppose let say your function be getNumofCols(), then modify it like this
function getNumofCols() {
...
...
...
...
...
return "grid-cols-" + numCols ;
}
So that it returns the complete string .
And use it like again
const gridClass = `grid ${getNumofCols()} gap-2 pt-2`;
If your function uses any parameter then you can create a new function and call this function and just add grid-cols- to the return value.
By doing it this way, the entire string for every class is in your source code, so Tailwind will know to generate the applicable CSS.

Related

add line break after certain amount of characters in JSX

I want to render some music data. I am saving the data i get back from an axios request into
an object called dailyPlaylist and destructure the values i need.
Since the artists and the name take a lot of space, i want to do a line break.
The artists come back as an array, so i could do:
artists: track.artists.slice(0, 5).map(artist => <p>{artist.name}</p>)
this works perfectly fine.
But now I am trying to do a line break also on the "name" property since some of the names are really long.
Is there an option to set a line break after a certain amount of characters?? I tried many methods I would use in vanilla JS but nothing worked.
For example i want to change the long name:
Dreamers (Music from the FIFA World Cup)
to:
Dreamers (Music from the <br>
FIFA World Cup)
or something like that.
My code so far:
const dailyPlaylist = {
tracks: response.data.tracks.items.map(({ track }) => ({
key: track.id,
name: track.album.name,
artists: track.artists.slice(0, 5).map(artist => <p>{artist.name}</p>), // line break between artists
image: track.album.images[1].url
}))
}
Thanks in advance
Unless there is a good reason why you want to achieve this using Javascript, I would do it with CSS instead.
Limit the width of the div or span containing the title. You could use max-width for example.
Something like this:
<span style={{maxWidth:100}} >
<p>{artist.name}</p>
</span>

Limit the number of results for autocomplete component?

I'm using React JS and I've created an autocomplete component that returns a list of results.
Here's my code for the autocomplete :
const [pokedexMatch, setPokedexMatch] = useState([]);
const searchPokedex = (text) => {
if (!text) {
setPokedexMatch([]);
} else {
let matches = pokedex.filter((pokedex) => {
const regex = new RegExp(`${text}`, "gi");
return pokedex?.name?.match(regex);
});
setPokedexMatch(matches);
}
};
Here's a working sandbox with some dummy text below. As you can see, when you type a single letter it returns a lot of results and move the text below the results.
What I'd like to do is limit the number of results to 5 and make the list of results go above the text (I guess that I should use absolute positioning but I'd like to know if there is another method without absolute positioning) ?
To limit your results to 5, use simple Javascript!
setPokedexMatch(matches.slice(0, 5));
For the positioning part of your question, you're on the right track - you can use absolute positioning of a div with a white background to make the results appear to be "above" the text.
EDIT: I've updated your sandbox here and made some very minor changes.
When you map over an array, make sure you include a key prop. Read more here.
I added the slice method to limit your results to 5
I added a simple above class to the parent <ul /> element to make the results appear "above" the text. This is just one way to accomplish this, I'll leave it to you to style it better if you'd like.

Angular 1 ng-class doesn't work as expected

I have a simple ng-class that switches two classes based on the condition. When the class is switched, the order of the classes is messed up not sure why. Has anyone a solution for this?
<div class="ui" ng-class="{'two column grid' : submitNow, 'one column grid' : defaultState}"></div>
Rendered HTML when submitNow is true. This works as expected
<!-- submitNow is true -->
<div class="ui ng-scope two column grid"></div>
Rendered HTML when defaultState is true. This messes up the order of classes added by ng-class
<!-- defaultState is true -->
<div class="ui ng-scope column grid one"></div>
*** Edit ****
Quite strange because it works on jsfiddle. But here's the screenshot of my rendered html code
Here is a demo
https://codepen.io/vibwaj/pen/KKPBdNp
OK...looking at the style rules in elements inspector, semantic ui uses selectors like .ui[class*="two column"].grid > .row > .column
Not sure why they do it that way which is unusual and does make the order important.
Also not sure if it is angular or the browser that sorts the order of those classes. I suspect it is the browser, but that is a guess.
Rather than try to figure out what causes the sort you can add the following rule to fix layout for non specific class order.
.ui.two.column.grid > .row > .column,
.ui.two.column.grid > .column:not(.row){
width:50%!important;
}
Working codepen
Update:
I didn't notice the semantic UI framework that is using this approach.
If you still need the same approach, you can check the forked Codepen which I created a custom directive to be alternative than the original NgClass directive.
app.directive("myNgClass", function() {
return {
link: function(scope, element, attrs) {
scope.$watch(
function() {
return attrs.isExpand;
},
function(isExpand) {
element.removeAttr("class");
if (JSON.parse(isExpand)) {
element.addClass("two column grid");
} else {
element.addClass("one column grid");
}
}
);
}
};
});
Original Answer
So diving deep into the NgClass directive implementation in Angularjs source code and checking how they update the classes, there is a function called updateClasses.
In this function, it finds which classes should be removed and added.
Instead of replacing all the classes when the Boolean flag gets inverted, NgClass keeps the overlapping classes and checks which classes should be added / removed.
So in your case,one column grid (the default case) and two column grid have the column grid classes in common, so it will keep them, but remove the one from the start and add two at the end. So the result will be column grid one.
I really don't suggest to use the order of the classes as CSS selectors. This will make it more harder to select elements and make things more complex.
I also have a comment regarding the CSS selectors that you are using. I really suggest you to read Keep your CSS selectors short article so you can have a better practice of using shorter selector and why keeping the CSS selectors short can help with many things.
For example, If you don't need the one, column and grid classes seprately, you can just use .one-column-grid as a class name / CSS Selector instead of .one.column.grid.

React : best way to inject Component in dynamically loaded HTML?

I'm new on React (I more at ease w/ jQuery or AngularJS). I have a special case and I don't find a good way to resolve it...
My app contains an area which is like a "document viewer". It loads an HTML content from the backend (via API, using Fetch) and inject it in the "viewer" component. The HTML content loaded looks like an "university report" (it's just a formatted text, only <span> and <p> with class="..." attributes, nothing more).
Ex : <p>Lorem ispum <span>some text</span> loreb bis <span>ipsum</span></p> ...
I load the content, and inject it this way in the render() of my component <Viewer> :
<div dangerouslySetInnerHTML={ getFreshlyLoadedHTML() } />
Easy, it works just fine !
But... Now, I want to inject some "interactive" components in the loaded HTML. For example, some button to give a feedback etc. The API must decide where to place the component between the words/nodes of the formatted text (HTML).
Ex :
<p> Lorem ispum <span>some text</span>
loreb bis <span>ipsum</span>
<MyFeedbackButton paragraph="1.3"/>
</p><p>Other Lorem Ipsum<p><span>...</span>
There, I'm stucked because I cannot use dangerouslySetInnerHTML if there are components inside the loaded HTML...
First attempt : I've tried modifying the API, and instead of sending the HTML in a string to the app, I send a custom JSON structure that represents almost the final JSX structure that I want. Then, in my react page, the render function only have to parse the JSON and build the JSX (here, a JsFiddle example if it's not clear : https://jsfiddle.net/damienfa/69z2wepo/34536/ )
It works, but I can't believe it's the good way...
I see a major problem : all the HTML node (span, p...) that I build from the render function are referenced by reactJs, is it really necessary ? Mostly, there are "dead" nodes (I mean, dom node that won't never changed, this is static formatted text).
Just take a look a all those "data-reactid" on nodes that never will be interactive...
What would be your advice on that case ?
What about my attempt with a JSON-structure sent by the API ?
Is there a way to say to react "do not reference that element" ?
Do you clearly see a better solution to my problem ?
Your current workflow is not very secure and subject to many potential errors and open doors, especially concerning code injection ...
The overload due to react tracking the nodes is not an issue, React could track 10 000 nodes and not have a problem (well actually on many of my apps React has more than 100 000 nodes to care about and it still rurns perfectly).
I see different solutions here:
If there are only 3 or 4 possibilities of dynamic components and order, you might have components like "templates" to which you would simple send text arguments. This is the safest and easiest option.
If it doesn't suit your use-case but the JSON file can contain only a limited set of components, the components should be located in your main app, and then rendered with custom props from the JSON. Actually given the structure of data you could consider using xml instead of json and build a xml tree that you would parse and render. Only components from your white list would be rendered and it would limit drastically the potentials security issues. If needs quite some work on the XML parser though.
If the JSON file can contain many many different and unpredictable components or if the behaviour of those components is largely dynamic and independant of your app, you might as well consider using an iframe, with its own JS and HTML, so that this part of the code is isolated from the rest.
Try using an inline anonymous function within the inner content from within React using JSX. It works! Just be careful about how you wire up the data so there isn't a route where a user can inject HTML from an input or text field.
<div className="html-navigation-button">{(() =>
{
const CreateMarkup = ( sNavItemName :string ) => {
return {__html: sNavItemName };
}
var sTextToAddHtmlTo = props.nextNavItem.name.toString();
sTextToAddHtmlTo = sTextToAddHtmlTo.replace( "/", "/<wbr>" );
return (
<div dangerouslySetInnerHTML={CreateMarkup( sTextToAddHtmlTo )} >
</div>
);
})()}
</div>
I didn't override the React internals of 'render()', but only used a React Component with props wiring to pass down data to it for rendering.
I added the hook for 'dangerouslySetInnerHTML' deep within the return content of the React Component so there would be no easy way to intercept and manipulate it.
As such, there is no 100% guarantee on safety, but that's where adding good security to web services, databases, and use of CORS and CORB would be helpful to lock down security risks.

center form content modifying form.html template

I'm still using 4.1 and I'm trying to center a form content on screen.
When I add a new form it stays on the "left" of the screen.
I've tried to change form.html (temmplates/shared) adding a div with a style like this:
margin-left : 10%; margin-right : 10%;
up next to the form tag and closing it at the bottom.
The form goes to the right but not centered at all.
Anyone has any clue to help with this ?
Here is a screenshot of a simple Form (it's not basec on models)
thanks
Alejandro
Ok - i've gone to a default of atk4.2 to demonstrate.
If you add a new page with defaults as follows
<?php
class page_test extends Page {
function init() {
parent::init();
$p=$this;
$f=$p->add('MVCForm')->setModel('Customer');
}
}
?>
I get the following and the fields extend across the whole width of the page.
This is because the default page template takes the whole width and is expected.
In order to adjust the form, you can use view functionality so create a view under the /lib/View directory called Centre.php and put the following code in it
<?php
class View_Centre extends View {
function init(){
parent::init();
}
function defaultTemplate() {
return array('view/centre');
}
}
?>
Then create a new template for the view in yoursite/templates/default/view called centre.html and insert the following html code
<div style='width:50%; margin: auto;'>
<?$Content?>
</div>
and then in the page, we add the view first and the form into the view rather than straight into the page.
<?php
class page_test extends Page {
function init() {
parent::init();
$p=$this;
$v=$p->add('View_Centre');
$f=$v->add('MVCForm')->setModel('Customer');
}
}
?>
and this results in the following web page
The base ATK4 form is itself a view which means you can style the form however you want so if you get for example use a different style of form such as the one described here you can do this by copying yoursite/atk4/atk4/templates/shared/form.html to yoursite/atk4/templates/shared.form.html and changing the second line from
<?form?>
<div id="<?$_name?>" class="atk-form <?$class?>" style="<?$style?>">
<?$hint?>
<form class="<?$form_internal_class?>" id="<?$form_name?>" name="<?$form_name?>" action="<?$form_action?>" method="POST" <?$enctype?>>
<fieldset class="<?$fieldset?>">
to
<?form?>
<div id="stylized>" class="myform <?$class?>" style="<?$style?>">
<?$hint?>
<form class="<?$form_internal_class?>" id="<?$form_name?>" name="<?$form_name?>" action="<?$form_action?>" method="POST" <?$enctype?>>
<fieldset class="<?$fieldset?>">
Create a new form.css file in yoursite/templates/default/css which contains the styling
Copy yoursite/atk4/templates/shared/shared.html to yoursite/templates/shared/shared.html and add an extra tag
<?$css_include?>
just above the existing
<?$js_include?>
and in Frontend.php, let every page find the new css file.
$this->addLocation('templates',array(
'css'=>array(
'default/css',
),
));
$this->template->appendHTML('css_include','<link type="text/css" href="'.$this->api->locateURL('css','form.css').'" rel="stylesheet">');
which results in a styled form like this
There are some examples of different form layouts here that you can use to adjust the form layout itself. Not sure from your question if you want to center the form in the page or centre the fields.
If you just want to centre the Form within the webpage, you want to use margin: auto for centering css rather than setting a percentage on each side as shown here
Also note, if you amend files from the atk4 directory, you should make a copy into yoursite/templates/shared and amend it there so you can upgrade by overwriting the atk4 directory later without losing anything.
If i use the default form template (in this case in a CRUD), i get the following
If i make your change to the atk4/templates/form.html by adding
<div style="width: 50%; margin: 0 auto;">
as the second line in form.html and adding the corresponding
</div>
one line from the bottom, it shifts everything slightly to the right as follows
What is unclear in your question is what you are trying to achieve - do you want to move the fields within the form or do you want to centre the whole form on the page ?
I think the reason for the shifting is because of setting the width to 50% but I dont know what layout you are trying to obtain - do you just want to right align the labels to the fields maybe ? Please post screenshots in the original question of what you are getting and try to describe what you want to achieve.

Resources