How can I detect the screen resolution in Angular Universal? - node.js

I am trying to detect the screen resolution of the browser in Angular Universal but I'dont get it, on the side of the browser, I use BreakpointObserver of Angular Material, but on the server is not working:
this.breakpointObserver.observe(['(max-width: 575px)']).pipe(filter(result => result.matches == true)).subscribe((result: any) =>
{
console.log('(max-width: 575px)', result)
this.imageBackground = this.imageBackgroundPortrait;
});
This It is ignored on the server. Any suggestions?
Thanks.

When you want to update on resize you have to mention something like this :
#HostListener('window:resize', ['$event'])
onResize(event) {
this.innerWidth = window.innerWidth;
}
Something like this should definitely work in any devices you encounter:
export class Dashboard {
mobHeight: any;
mobWidth: any;
constructor(private router:Router, private http: Http){
this.mobHeight = (window.screen.height) + "px";
this.mobWidth = (window.screen.width) + "px";
console.log(this.mobHeight);
console.log(this.mobWidth)
}
}
Detect window size using Angular 4
How to get height and width of device display in angular2 using typescript?

This wouldn't work on the server, because on the server there is no "window", there is no "viewport" so it won't be defined. Hence why it's ignored on the server. In reality, you don't need it there.
What you can consider doing is using the picture tag. This allows the browser to use the most appropriate image for the viewport.
<picture>
<source media="(min-width: 1100px)" srcset="https://cdn.dribbble.com/users/31348/screenshots/4892068/newport_1x.jpg" />
<source media="(min-width: 650px)" srcset="https://cdn.dribbble.com/users/107759/screenshots/4891695/iphonex-simple-3_1.gif" />
<img src="https://cdn.dribbble.com/users/1185900/screenshots/4890498/shopping_online_03.png" />
</picture>
Demo on codepen

you can try this on your Component
getScreenSize(event?) {
this.scrHeight = window.innerHeight;
this.scrWidth = window.innerWidth;
console.log(this.scrHeight, this.scrWidth);
}
// Constructor
constructor() {
this.getScreenSize();
}

Related

Render PrimeFaces <p:focus> component only if the browser window has a certain size

Is there a way to render the PrimeFaces <p:focus> component (or let it do its job) only when the browser window has a certain size? To do this I understand that maybe I have to have access the browser window size in server code and use the component rendered attribute to access this code.
In client code, I get the window width with code like this: $(window).width() > 480.
The reason for this is that I don't want to focus the first component in mobile devices, which most of the time have small screens.
Today I'm doing this with the client code below, but I'd like to use the <p:focus> component for the task, as it also has the benefit to focus the first invalid component when validation fails.
$(
function()
{
if (bigWindow())
{
focusFirstInput();
}
}
)
function bigWindow()
{
return $(window).width() > 480;
}
function focusFirstInput()
{
$("#form input:text, #form input[type=number], #form input[type=password], #form textarea").
first().focus();
}
I've found a way using the <p:focus> component:
$(
function()
{
let primeFacesOriginalFocusFunction = PrimeFaces.focus;
PrimeFaces.focus =
function(id, context)
{
if (!isMobile())
{
primeFacesOriginalFocusFunction(id, context);
}
}
}
)
function isMobile()
{
return ...
}

React + Material-UI - Warning: Prop className did not match

I'm having difficulty with differences between client-side and server-side rendering of styles in Material-UI components due to classNames being assigned differently.
The classNames are assigned correctly on first loading the page, but after refreshing the page, the classNames no longer match so the component loses its styling. This is the error message I am receiving on the Console:
Warning: Prop className did not match.
Server: "MuiFormControl-root-3 MuiFormControl-marginNormal-4
SearchBar-textField-31"
Client: "MuiFormControl-root-3 MuiFormControl-marginNormal-4
SearchBar-textField-2"
I've followed the Material-UI TextField example docs, and their accompanying Code Sandbox example, but I can't seem to figure out what is causing the difference between the server and client classNames.
I experienced a similar issue when adding Material-UI Chips with a delete 'x' icon. The 'x' icon rendered with a monstrous 1024px width after refreshing. The same underlying issue being that icon was not receiving the correct class for styling.
There are a few questions on Stack Overflow addressing why the client and server might render classNames differently (e.g. need to upgrade to #Material-UI/core version ^1.0.0, using a custom server.js, and using Math.random in setState), but none of these apply in my case.
I don't know enough to tell whether this Github discussion might help, but likely not since they were using a beta version of Material-UI.
Minimal steps to reproduce:
Create project folder and start Node server:
mkdir app
cd app
npm init -y
npm install react react-dom next #material-ui/core
npm run dev
edit package.json:
Add to 'scripts': "dev": "next",
app/pages/index.jsx:
import Head from "next/head"
import CssBaseline from "#material-ui/core/CssBaseline"
import SearchBar from "../components/SearchBar"
const Index = () => (
<React.Fragment>
<Head>
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css?family=Roboto:300,400,500"
/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta charSet="utf-8" />
</Head>
<CssBaseline />
<SearchBar />
</React.Fragment>
)
export default Index
app/components/SearchBar.jsx:
import PropTypes from "prop-types"
import { withStyles } from "#material-ui/core/styles"
import TextField from "#material-ui/core/TextField"
const styles = (theme) => ({
container: {
display: "flex",
flexWrap: "wrap",
},
textField: {
margin: theme.spacing.unit / 2,
width: 200,
border: "2px solid red",
},
})
class SearchBar extends React.Component {
constructor(props) {
super(props)
this.state = { value: "" }
this.handleChange = this.handleChange.bind(this)
this.handleSubmit = this.handleSubmit.bind(this)
}
handleChange(event) {
this.setState({ value: event.target.value })
}
handleSubmit(event) {
event.preventDefault()
}
render() {
const { classes } = this.props
return (
<form
className={classes.container}
noValidate
autoComplete="off"
onSubmit={this.handleSubmit}
>
<TextField
id="search"
label="Search"
type="search"
placeholder="Search..."
className={classes.textField}
value={this.state.value}
onChange={this.handleChange}
margin="normal"
/>
</form>
)
}
}
SearchBar.propTypes = {
classes: PropTypes.object.isRequired,
}
export default withStyles(styles)(SearchBar)
Visit page in browser localhost:3000 and see this:
red border around TextField component
Refresh the browser and see this:
TextField component's styles are gone
Notice that the red border around TextField disappears.
Relevant Libs:
"react": 16.4.0
"react-dom": 16.4.0
"next": 6.0.3
"#material-ui/core": 1.2.0
The problem is the SSR rendering in Next.js, which produces the style fragment before the page is rendered.
Using Material UI and Next.js (as the author is using), adding a file called _document.js solved the problem.
Adjusted _document.js (as suggested here):
import React from 'react';
import Document, { Html, Head, Main, NextScript } from 'next/document';
import { ServerStyleSheets } from '#material-ui/styles'; // works with #material-ui/core/styles, if you prefer to use it.
import theme from '../src/theme'; // Adjust here as well
export default class MyDocument extends Document {
render() {
return (
<Html lang="en">
<Head>
{/* Not exactly required, but this is the PWA primary color */}
<meta name="theme-color" content={theme.palette.primary.main} />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
// `getInitialProps` belongs to `_document` (instead of `_app`),
// it's compatible with server-side generation (SSG).
MyDocument.getInitialProps = async (ctx) => {
// Resolution order
//
// On the server:
// 1. app.getInitialProps
// 2. page.getInitialProps
// 3. document.getInitialProps
// 4. app.render
// 5. page.render
// 6. document.render
//
// On the server with error:
// 1. document.getInitialProps
// 2. app.render
// 3. page.render
// 4. document.render
//
// On the client
// 1. app.getInitialProps
// 2. page.getInitialProps
// 3. app.render
// 4. page.render
// Render app and page and get the context of the page with collected side effects.
const sheets = new ServerStyleSheets();
const originalRenderPage = ctx.renderPage;
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) => sheets.collect(<App {...props} />),
});
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
// Styles fragment is rendered after the app and page rendering finish.
styles: [...React.Children.toArray(initialProps.styles), sheets.getStyleElement()],
};
};
This problem is related to MUI using dynamic class name which contain an ID. The IDs from the server side rendered CSS are not the same as the client side CSS, hence the mismatch error. A good start is to read the MUI SSR documentation
If you have this problem with nextjs (as I did) follow the example provided by the MUI team, which can be found here: material-ui/examples/nextjs
The most important part is in "examples/nextjs/pages/_app.js":
componentDidMount() {
// Remove the server-side injected CSS.
const jssStyles = document.querySelector('#jss-server-side');
if (jssStyles) {
jssStyles.parentElement.removeChild(jssStyles);
}
}
the related ticket can be found here: mui-org/material-ui/issues/15073
what it does, is remove the server side rendered stylesheet and replace it by a new client side rendered one
The issue is the server side generates the class names but style sheets are not automatically included in the HTML. You need to explicitly extract the CSS and append it to the UI for the server side rendered components. The whole process is explained here: https://material-ui.com/guides/server-rendering/
There is one other important, separate issue here: Material UI V4 is not React Strict Mode compatible. Strict mode compatibility is slated for version 5 with the adoption of the Emotion style engine.
Until then, be sure you disable React Strict Mode. If you're using Next.js, this is turned on by default if you've created your app using create-next-app.
// next.config.js
module.exports = {
reactStrictMode: false, // or remove this line completely
}
I had the same problem with Next.js and styled component, with the transpilation by Babel. Actually, the class names are different on the client and the server side.
Fix it in writing this in your .babelrc :
{
"presets": ["next/babel"],
"plugins": [
[
"styled-components",
{ "ssr": true, "displayName": true, "preprocess": false }
]
]
}
I met this problem on Material-ui V5. The solution to fix this problem is to make sure that class name generator needs to behave identically on the server and on the client.
so adding the code below in your _app.js:
import { StylesProvider, createGenerateClassName } from '#mui/styles';
const generateClassName = createGenerateClassName({
productionPrefix: 'c',
});
export default function MyApp(props) {
return <StylesProvider generateClassName={generateClassName}>...</StylesProvider>;
}
// 1 . Warning: prop classname did not match. Material ui with React Next.js
// 2 . Use your customization css here
const useStyles = makeStyles((theme) => ({
root: {
flexGrow: 1,
},
title: {
flexGrow: 1,
},
my_examle_classssss: {
with: "100%"
}
}));
// 3 . Here my Component
const My_Example_Function = () => {
const classes = useStyles();
return (
<div className={classes.root}>
<Container>
<Examle_Component> {/* !!! Examle_Component --> MuiExamle_Component*/}
</Examle_Component>
</Container>
</div>
);
}
export default My_Example_Function
// 4. Add name parameter to the makeStyles function
const useStyles = makeStyles((theme) => ({
root: {
flexGrow: 1,
},
title: {
flexGrow: 1,
},
my_examle_classssss: {
with: "100%"
},
}), { name: "MuiExamle_ComponentiAppBar" });
{/* this is the parameter you need to add { name: "MuiExamle_ComponentiAppBar" } */ }
{/* The problem will probably be resolved if the name parameter matches the first className in the Warning: you recive..
EXAMPLE :
Warning: Prop `className` did not match.
Server: "MuiSvgIcon-root makeStyles-root-98"
Client: "MuiSvgIcon-root makeStyles-root-1"
The name parameter will be like this { name: "MuiSvgIcon" }
*/ }
I like to share this mismatching case:
next-dev.js?3515:32 Warning: Prop className did not match. Server:
"MuiButtonBase-root MuiIconButton-root PrivateSwitchBase-root-12
MuiSwitch-switchBase MuiSwitch-colorSecondary" Client:
"MuiButtonBase-root MuiIconButton-root PrivateSwitchBase-root-12
MuiSwitch-switchBase MuiSwitch-colorSecondary
PrivateSwitchBase-checked-13 Mui-checked"
On client there are two more classes which means that the behavior on client-side is different. In this case, this component shouldn't render on server-side. The solution is to dynamically render this component:
export default dynamic(() => Promise.resolve(TheComponent), { ssr: false });
I had a problem with different classNames for client and server. I was using React, Material-UI, makeStyles and SSR (server-side rendering).
The error was:
Warning: Prop `className` did not match. Server: "jss3" Client: "App-colNav-3"
I spent several hours before I figured out that I had discrepancy in webpack mode for client and server. The scripts in package.json were:
"devServer": "webpack --config webpack.server.config.js --mode=production --watch",
"devClient": "webpack --mode=development --watch",
After I changed both to have development mode, the problem was solved :)
"devServer": "webpack --config webpack.server.config.js --mode=development --watch",
"devClient": "webpack --mode=development --watch",
If somebody is still struggling even after trying above solutions, Try this
If you have used noSsr prop in any of your components or theme, then remove it.
I had the following config in mui theme object, which was causing this problem.
import { createTheme, responsiveFontSizes } from "#mui/material/styles";
let theme = createTheme({
components: {
MuiUseMediaQuery: {
defaultProps: {
noSsr: true,
},
},
},
palette: {
mode: "light",
common: {
black: "#000",
white: "#fff",
},
primary: {
main: "#131921",
contrastText: "#fff",
},
secondary: {
main: "#fb6a02",
contrastText: "#fff",
}
}
})
RemovingnoSSr fixed all of the issues in my app including style mismatch between client and server.
The problem is cause by Nextjs server side rendering. In order to solve I do as following:
Make a component to detect whether is it from Client side
import { useState, useEffect } from "react";
interface ClientOnlyProps {}
// #ts-ignore
const ClientOnly = ({ children }) => {
const [mounted, setMounted] = useState<boolean>(false);
useEffect(() => {
setMounted(true);
}, []);
return mounted ? children : null;
};
export default ClientOnly;
Wrap my page component using ClientOnly component
export default function App() {
return (
<ClientOnly>
<MyOwnPageComponent>
</ClientOnly>
);
}
So the idea is, if it is client side then only render the component on the page. Therefore if current rendering is from Client side, render <MyOwnPageComponent>, else render nothing
In my case the issue happened because of different compilation modes of webpack for client-side code and server-side: client's bundle was generated by webpack using "production" mode, while server ran some SSR code from a package optimized for "development". This created a different "className" hash in styled-components in generateAndInjectStyles():
if (process.env.NODE_ENV !== 'production') dynamicHash = phash(dynamicHash, partRule + i);
So my fix was just to align the webpack modes.
You can add the name in anywhere you use makeStyles, like this:
const useStyles = makeStyles({
card: {
backgroundColor: "#f7f7f7",
width: "33%",
},
title: {
color: "#0ab5db",
fontWeight: "bold",
},
description: {
fontSize: "1em"
}
}, { name: "MuiExample_Component" });
I am not sure how it works, but I found it here: Warning: Prop `className` did not match ~ Material UI css arbitrarily breaks on reload
I'm also using NextJS + MUI v5 and I ran into this exact error right after merging Git branches. I suspect the merge corrupted something in the cache. I deleted the contents of .next/ and restarted the dev server and the error went away.
#Leonel Sanches da Silva's answer didn't work for me, as #material-ui/styles is deprecated, but using a snippet I found for another (non-material UI) project seems to have worked just fine for me:
Hat tip to Raul Sanchez on dev.to for the answer to this one.
Next doesn't fetch styled-components styles on the server, to do that you need to add this page to pages/_document.js:
import Document from 'next/document'
import { ServerStyleSheet } from 'styled-components'
export default class MyDocument extends Document {
static async getInitialProps(ctx) {
const sheet = new ServerStyleSheet()
const originalRenderPage = ctx.renderPage
try {
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) =>
sheet.collectStyles(<App {...props} />),
})
const initialProps = await Document.getInitialProps(ctx)
return {
...initialProps,
styles: (
<>
{initialProps.styles}
{sheet.getStyleElement()}
</>
),
}
} finally {
sheet.seal()
}
}
}
This code may update, so check Next's styled-components example for the latest.

Leaflet markers not always applying

So I'm using leaflet-react and I need to add some circle markers.
Now this code works...sometimes. On map click a circle marker should be added but sometimes it is not. Seemingly randomly it will just not add a visible marker. Sometimes the marker will become visible if I change the zoom level but not always. All the code runs each time so it's not that addMarker() isn't been called, also the removal of the last marker(by clearing the mark layer) always runs.
Thanks, Ed.
It appears that you aren't using the react-leaflet package. I'd recommend trying that out. Here is some example code for how you'd add markers to the map on click events:
const React = window.React;
const { Map, TileLayer, Marker, Popup } = window.ReactLeaflet;
class SimpleExample extends React.Component {
constructor() {
super();
this.state = {
markers: [[51.505, -0.09]]
};
}
addMarker = (e) => {
const {markers} = this.state
markers.push(e.latlng)
this.setState({markers})
}
render() {
return (
<Map
center={[51.505, -0.09]}
onClick={this.addMarker}
zoom={13}
>
<TileLayer
attribution='© OpenStreetMap contributors'
url='http://{s}.tile.osm.org/{z}/{x}/{y}.png'
/>
{this.state.markers.map((position, idx) =>
<Marker key={`marker-${idx}`} position={position}>
<Popup>
<span>A pretty CSS3 popup. <br/> Easily customizable.</span>
</Popup>
</Marker>
)}
</Map>
);
}
}
window.ReactDOM.render(<SimpleExample />,
document.getElementById('container'));
And here's a jsfiddle showing the implementation: https://jsfiddle.net/q2v7t59h/413/

Crop image and send it to jsf bean without unnecessary data transfer

I'd like to upload an image generated by a jquery cropper to a bean field.
Client side I ve found this:
<p:fileUpload
id="imgInp"
mode="simple" allowTypes="/(\.|\/)(gif|jpe?g|png)$/"/>
<img id="blah" src="#" alt="your image" />
<p:imageCropper image="" />
<script>
var reader = new FileReader();
reader.onload = function (e) {
$('#blah').attr('src', e.target.result);
}
function readURL(input) {
if (input.files && input.files[0]) {
reader.readAsDataURL(input.files[0]);
}
}
$("#imgInp").change(function(){
readURL(this);
});
</script>
It displays the image without uploading it but I can't get it in the cropper. So I use a jquery cropper but then I'm not sure how to get it in the bean (without going through servlets, because it's not a one time use). In other words I need to send an img through ajax to the bean.
Otherwise I'd use primeface but it has to go through the wire which I'd like to avoid if possible. From the example I've seen the image is static content on the server. Do I really have to save the image on the server ? Can't I keep it as object and convert the UploadedFile to something the cropper will accept ?
Soomething like this:
<p:fileUpload
mode="advanced" allowTypes="/(\.|\/)(gif|jpe?g|png)$/"
fileUploadListener="#{bean.uploadPicListenner}"
update="cropper"/>
<h:panelGroup id="cropper" >
<p:imageCropper image="#{bean.img}"/>
</h:panelGroup>
public void uploadPicListenner(FileUploadEvent e) {
img = e.getFile();
RequestContext.getCurrentInstance().update("ptform:cropper");
}
It's actually quite simple once the process is understood despite some misleading answers that can be found online. I hope this is gonna help someone in the future.
The technique I used is to :
Pick image
Once image is picked, display it in a cropper without sending it through the wire.
Here there are a few option and I choosed to : When user moves the cropper rectangle around, the coordinates of the rectangle populate an hidden input field.
Send the coordinate to bean and crop it on server side.
I did it this way because the cropper jquery lib I wanted to use didn't transform the image to base 64 and just gave the coordinates of a rectangle. However if someone want to send the cropped image directly in the future I figured it would be really easy. Just like I did except you have to put the cropped image as string base 64 in a hidden input text (instead of rectangle coordinates - this is explained under-) and transform it back on the server side, that's all. (I don't know how efficient / secure that is however). At least that resolved my issue I had with primefaces, which was not wanting to send unnecessary data over the wire multiple times.
1. First let's display the image without sending it to the server.
At this point when the image is displayed if you check inside the src tag of the img you will see that it is the data of the image as base 64:
src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAAQABAAD/2wCEAAYEBQYFBAYGBQ...
<h:form id="lolo" enctype="multipart/form-data">
<p:fileUpload
value="#{adminCreateTeam.teamImg}"
mode="simple" allowTypes="/(\.|\/)(gif|jpe?g|png)$/"/>
<img id="blah" src="#" alt="your image" />
</h:form>
<script>
var reader = new FileReader();
reader.onload = function (e) {
$('#blah').attr('src', e.target.result);
}
function readURL(input) {
if (input.files && input.files[0]) {
reader.readAsDataURL(input.files[0]);
}
}
$("#lolo\\:imgInp").change(function(){
readURL(this);
});
</script>
Once we have done that it becomes a bit dependent on the jquery cropping library used. I used the cropper lib. With that one we want to have the coordinate of the cropped rectangle. With the coordinates data we populate an hidden input and send it back to the bean to then recrop it on java side.
Alternatively a better solution (imo) would be to use a library that crop the image and give the data client side as base 64, populate an hidden field and send it back to the bean, to then convert base 64 to an image. Each of these steps is quiet easy and can be found on stackoverflow.
Since I wanted to use the cropper library I did it the first way:
This is added inside the form:
<h:inputHidden value="#{adminCreateTeam.rect}"/>
<p:commandButton value="submit" action="#{adminCreateTeam.picTest}" ajax="false"/>
This is the updated onload:
// with this the hidden field is gonna be populated by the
// cropping rectangle data.
var $imageCrop = $('#blah').cropper({
aspectRatio: 1/1,
viewMode: 1,
crop: function(e) {
// Output the result data for cropping image.
// string with all the data delimited by /
$('#lolo\\:hiddenB64').val(e.x + '/' + e.y + '/' + e.width + '/' + e.height);
}
});
//So the image changes in the cropper when a new image is picked
reader.onload = function (e) {
$imageCrop.cropper('replace',e.target.result);
}
We crop the image on with java
public void picTest() {
//getting coord.
String data[] = rect.split("/");
try (InputStream in = new ByteArrayInputStream(teamImg.getContents())) {
BufferedImage bImageFromConvert = ImageIO.read(in);
// line under this crops. It's possible there is a zoom to figure out, I didn't check yet. Seemed correct on first and only try. In any case you'll figure it out
// surely the parsing shouldn't be here but I need to sleep real bad.
BufferedImage dest = bImageFromConvert.getSubimage((int)(Double.parseDouble(data[0])), (int)(Double.parseDouble(data[1])),
(int)(Double.parseDouble(data[2])), (int)(Double.parseDouble(data[3])));
// path to the folder
Path folder = Paths.get(dirs.getString("imgTeams"));
String filename = "team_pic";
String extension = FilenameUtils.getExtension(teamImg.getFileName());
Path file = Files.createTempFile(folder, filename + "-", "." + extension);
ImageIO.write(dest, "jpeg", file.toFile());
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

How to Trouble Shoot and Fix React.js Checksum Invalid Warning

I am building an Isomorphic react app using the Isomoprhic React Starter kit project here : https://github.com/kriasoft/react-starter-kit
However I am running into a mysterious warning that I have not figured out how to trouble shoot.
React appears to render the code fine in my browser, but apparently there is some problem with rendering on the server.
Here is the warning that I get:
warning.js:45Warning: React attempted to reuse markup in a container
but the checksum was invalid. This generally means that you are using
server rendering and the markup generated on the server was not what
the client was expecting. React injected new markup to compensate
which works but you have lost many of the benefits of server
rendering. Instead, figure out why the markup being generated is
different on the client or server:
and Here is the code from my component that is the source of the problem:
class TestPage extends React.Component {
constructor () {
super()
this.state = { n: 0 }
}
static contextTypes = {
onSetTitle: PropTypes.func.isRequired
};
static defaultProps = {
blahblah: 'blah'
}
render() {
let title = 'Test Page';
this.context.onSetTitle(title);
return (
<div className="page">
<div className="pageContainer">
<h1>{title}</h1>
{this.state.n}
<TestComponent blahblah={this.props.blahblah} />
</div>
</div>
);
}
}

Resources