I am using InertiaJs library to create links as in the following:
<InertiaLink href={`/item/`+ itemId}>Go to the page</InertiaLink>
Clicking on this, the link will look like this: /item/156654
Question: How to get the itemId(156654,54654654, ... ) from the link above using Inertia?
I know that in React there is a function let history = useHistory();, but this approach doesn't work for me. What alternative exists?
<InertiaLink /> don't provide any context to opened page, so you need to parse URL manually. In the example below you can find parsePageId() function which actually parse location path in an optimal way.
// solution from https://stackoverflow.com/questions/4758103/last-segment-of-url-in-jquery
const parsePageId = (path) => path.substring(path.lastIndexOf('/') + 1)
const Page = () => {
const pageId = parsePageId(window.location.pathname)
// will render "Page id: js", because snippet href is "https://stacksnippets.net/js"
return (
<p>Page id: <b>{pageId}</b></p>
)
}
ReactDOM.render(<Page />, document.querySelector("#root"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root" />
You can use the window.location object to get the actual URL and then apply a simple parsing to obtain the item id:
const { pathname } = window.location
const splitPathname = pathname.split("/")
const itemId = splitPathname[splitPathname.length - 1]
We are just getting all the parts of the URL separated by "/" and getting the last one, which will be the item id.
Related
Imagine I have a hierarchy of react components, e.g. meals of the day
|------- breakfast --------|
| Meal1_image.png banana |
| Meal2_image.png porridge|
|------- lunch-------------|
| Meal3_image.png toast |
| Meal4_image.png apple |
I can add meals to a meal group (e.g. to the lunch group), I can add more lunch groups (e.g. midday snack) and so on.. it's a variable list of components and I give the user the ability to add meals and meal groups with '+' buttons, or to delete them and so on.
How would I go to save these in a text json and to read them back?
I read about "serializing react components" but maybe I don't need all of that stuff, I would just need to save a json like
{
"breakfast": {
{"food": "banana", "image": "Meal1_image.png"},
{"food": "porridge", "image": "Meal2_image.png"},
},
"lunch" : ... and so on ...
}
is there any simple way to achieve this or should I just go with components serialization with things like https://github.com/zasource-dev/React-Serialize ?
Question is basically too wide, but anyway, just split your task by subtasks.
Design the data models you are going to work with. Create a prototype of it, try to render it as is. Regarding your prototype - that is not valid js object, so i changed it a bit in my example. Note - for .map methods you need to use unique keys.
Figure out what will be the best place to keep your data (in state). You can store it in some component, you can store it in context and add all the needed methods to it and pass them down to your components. I did everything in a component for simplicity of example.
Add download/upload/parsing functions and wire everything up.
If I got your question right - you want each Client to be able to download and upload the Data to a local JSON file on Client, so no server interaction.
const { useState } = React;
function download(content, mimeType, filename) {
const a = document.createElement("a");
const blob = new Blob([content], { type: mimeType });
const url = URL.createObjectURL(blob);
a.setAttribute("href", url);
a.setAttribute("download", filename);
a.click();
}
function upload(event, callback) {
const input = event.target;
const reader = new FileReader();
reader.onload = function () {
const text = reader.result;
callback(text);
};
reader.readAsText(input.files[0]);
}
const DEFAULT_DATA = {
breakfast: [
{ food: "banana", image: "Meal1_image.png" },
{ food: "porridge", image: "Meal2_image.png" }
],
lunch: [{ food: "banana", image: "Meal1_image.png" }]
};
function App() {
const [data, setData] = useState(DEFAULT_DATA);
const onDownloadClick = (e) => {
download(JSON.stringify(data), "application/json", "file1.json");
};
const onUpload = (e) => {
upload(e, (text) => setData(JSON.parse(text)));
};
return (
<div className="App">
{Object.entries(data).map(([section, items]) => (
<div key={section}>
<p>{section}</p>
<ul>
{items.map((item) => (
<li key={item.food}>
<p>{item.food}</p>
</li>
))}
</ul>
</div>
))}
<button onClick={onDownloadClick} type="button">
Download JSON
</button>
<input type="file" accept="application/json" onChange={onUpload} />
</div>
);
}
// v18.x+
ReactDOM.createRoot(document.getElementById("root")).render(<App />);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>
<div id="root"></div>
How to test - Download JSON file, open it and change something in it, like "banana" to "banana111", save, upload the file back and observe the updated values.
Not sure what is the plan about Images, but in worst case - you can store images in JSON as base64 strings.
If you mean physical saving in a file, then you need to have node js to access the file system of your phone or PC, your server:
// file system module to perform file operations
const fs = require('fs');
// json data
var jsonData = '{"persons":[{"name":"John","city":"New York"},{"name":"Phil","city":"Ohio"}]}';
// parse json
var jsonObj = JSON.parse(jsonData);
console.log(jsonObj);
// stringify JSON Object
var jsonContent = JSON.stringify(jsonObj);
console.log(jsonContent);
fs.writeFile("output.json", jsonContent, 'utf8', function (err) {
if (err) {
console.log("An error occured while writing JSON Object to File.");
return console.log(err);
}
console.log("JSON file has been saved.");
});
If we are talking about transformation for axios request, then you need to use the following javascript methods JSON.stringify() for json transformation and JSON.parse() for reading to transformation in javascript.
If you just want to transform the object in the hook state, this is also fine.
I'm making a simple marketplace, that has a home page with product cards - the cards are generated from an internal JS file (Data.js). It goes like this because I need to generate random names, and I use a function for that. The array goes like this:
let Data= [{
id: i,
nomeCompleto: nomeCompleto,
valor: preco(),
isFavorite: false,]}
When I click on the card, I use a dynamic route:
<Route path ='produto/:id' element={productSpecs}/>
It is working fine, the xxx/produto/id always matches the product id. The problem is, it's returning all of the array from Data. I need it to return only Data[(id - 2]).
As it is now, productSpecs is defined on the App.js as below, replacing props on ProductInfo:
const productSpecs = Data.map(item => {
return (
<ProductInfo
key = {item.id}
id = {item.id}
img = {item.img}
nomeCompleto = {item.nomeCompleto}
valor = {item.valor}
favorite = {item.isFavorite}
/>
)
})
Should I define the props on the Product Info JSX? If so, how?
Following your question, you could retrieve the id param inside of the ProductInfo and do a find() on the Data to get the product you're trying to display.
function ProductInfo() {
const { id } = useParams()
const product = Data.find(product => product.id === id);
...
}
and on the Routing level you could have
<Route path ='product/:id' element={<ProductInfo />}/>
I want to manage the content of the page from a content editor where I am getting page content from the API.
Check this screenshot.
I used two different react modules for this react-html-parser and react-string-replace but it is still not working.
Here is my code.
let pageData = '';
pageData = ReactHtmlParser(page.content);
// replacing contact us form with a contact us form component
pageData = reactStringReplace(pageData, '{CONTACT_US_FORM}', (match, i) => (
<ContactUsForm />
));
return <div>{pageData}</div>;
react-html-parser -> It is used to parse HTML tags which are in string format into tree of elements.
react-string-replace -> It is used to replace a string into react a component.
Note: If I use react-html-parser or react-string-replace individually then it works fine but it does not work together.
Any suggestion?
Depends on the expected structure of page.content. If it contains HTML you are right in using react-html-parser, which has a replace option.
import parse from 'html-react-parser';
const macro = '{CONTACT_US_FORM}';
const replaceContactUsForm = (domhandlerNode) => {
if (domhandlerNode.type === 'text' && domhandlerNode.data.includes(macro))
return <ContactUsForm />;
};
// ...
const elements = parse(page.content, { replace: replaceContactUsForm });
return <div>{elements}</div>;
Additionally, If the string {CONTACT_US_FORM} is embedded in text you could use react-string-replace to keep the rest of the text intact:
const replaceContactUsForm = (domhandlerNode) => {
if (domhandlerNode.type === 'text' && domhandlerNode.data.includes(macro))
return <>{reactStringReplace(domhandlerNode.data, macro, () => (<ContactUsForm />))}</>;
};
If page.content does not contain HTML you do not need react-html-parser. But judging from your screenshot some markup is probably contained.
I am new to react and trying to create breadcrumbs with dynamic Ids between path.
My props to component is:
const breadcrumbsData = {
path: "facilities/65743/facilitycontact",
breadcrumbNameMap: {
"/facilities": "Facility",
"/facilities/:facilityId": ":facilityId",
"/facilities/:facilityId/facilitycontact": "Facility Contact"
}
};
code which renders breadcrumbs is
<Breadcrumbs arial-label="Breadcrumb">
{paths.map((path, index) => {
const url = "/" + paths.slice(0, index + 1).join("/");
const last = index === paths.length - 1;
return last ? (
<Typography color="inherit"> {breadcrumbNameMap[url]} </Typography>
) : (
<Link to={url}>{breadcrumbNameMap[url]}</Link>
);
})}
</Breadcrumbs>
I tried different approaches but could not figure out how to do this. It works perfect without Ids in between path. for example
const breadcrumbsData = {
path: "facilities/facilityprofile/facilitycontact/workcontact",
breadcrumbNameMap: {
"/facilities": "Facility",
"/facilities/facilityprofile": "Facility Profile",
"/facilities/facilityprofile/facilitycontact": "Facility Contact"
}
};
P:S - Tried 'match' but did not go anywhere with that, and my use case is I am manually passing path from props.
Here is complete code
Code Sandbox - BreadCrumbs using react and material UI
I am not really sure why you need to this that way, but here is an ad hoc solution. Don't use your breadcrumbsData.breadcrumbNameMap directly to find the name. Pre-process it and generate your map with simple string replace.
For example, define a utility to pre-process the map:
generateNameMap = (id, idKey, map) => {
// takes the actual id i.e. '65743',
// the template key that need to be replaced with i.e. ':facilityId'
// and the initial map i.e. breadcrumbsData.breadcrumbNameMap
const clone = {};
Object.keys(map).forEach(k => {
clone[k.replace(idKey, id)] = map[k].replace(idKey, id) // replace the template in both key and value with actual id
})
return clone;
}
Now define your breadcrumbNameMap to use that utility rather than using breadcrumbsData.breadcrumbNameMap directly:
const breadcrumbNameMap = this.generateNameMap(paths[1], ':facilityId', breadcrumbsData.breadcrumbNameMap);
Here is a demo:
const generateNameMap = (id, idKey, map) => {
const clone = {};
Object.keys(map).forEach(k => {
clone[k.replace(idKey, id)] = map[k].replace(idKey, id)
})
return clone;
}
const breadcrumbNameMap = generateNameMap('65743', ':facilityId', {
"/facilities": "Facility",
"/facilities/:facilityId": ":facilityId",
"/facilities/:facilityId/workcontact": "Facility Contact",
"/facilities/facilityprofile/facilitycontact/": "Work Contact"
});
console.log(breadcrumbNameMap);
How do I convert Deltas to pure HTML? I'm using Quill as a rich text editor, but I'm not sure how I would display the existing Deltas in a HTML context. Creating multiple Quill instances wouldn't be reasonable, but I couldn't come up with anything better yet.
I did my research, and I didn't find any way to do this.
Not very elegant, but this is how I had to do it.
function quillGetHTML(inputDelta) {
var tempCont = document.createElement("div");
(new Quill(tempCont)).setContents(inputDelta);
return tempCont.getElementsByClassName("ql-editor")[0].innerHTML;
}
Obviously this needs quill.js.
I guess you want the HTML inside it. Its fairly simple.
quill.root.innerHTML
If I've understood you correctly, there's a quill thread of discussion here, with the key information you're after.
I've quoted what should be of most value to you below:
Quill has always used Deltas as a more consistent and easier to use (no parsing)
data structure. There's no reason for Quill to reimplement DOM APIs in
addition to this. quill.root.innerHTML or document.querySelector(".ql-editor").innerHTML works just fine (quill.container.firstChild.innerHTML is a bit more brittle as it depends on child ordering) and the previous getHTML implementation did little more than this.
Simple, solution is here:
https://www.scalablepath.com/blog/using-quill-js-build-wysiwyg-editor-website/
The main code is:
console.log(quill.root.innerHTML);
This is a very common confusion when it comes to Quilljs. The thing is you should NOT retrieve your html just to display it. You should render and display your Quill container just the same way you do when it is an editor. This is one of the major advantages to Quilljs and the ONLY thing you need to do is:
$conf.readOnly = true;
This will remove the toolbar and make the content not editable.
I have accomplished it in the backend using php.
My input is json encoded delta and my output is the html string.
here is the code , if it is of any help to you.This function is still to handle lists though and some other formats but you can always extend those in operate function.
function formatAnswer($answer){
$formattedAnswer = '';
$answer = json_decode($answer,true);
foreach($answer['ops'] as $key=>$element){
if(empty($element['insert']['image'])){
$result = $element['insert'];
if(!empty($element['attributes'])){
foreach($element['attributes'] as $key=>$attribute){
$result = operate($result,$key,$attribute);
}
}
}else{
$image = $element['insert']['image'];
// if you are getting the image as url
if(strpos($image,'http://') !== false || strpos($image,'https://') !== false){
$result = "<img src='".$image."' />";
}else{
//if the image is uploaded
//saving the image somewhere and replacing it with its url
$imageUrl = getImageUrl($image);
$result = "<img src='".$imageUrl."' />";
}
}
$formattedAnswer = $formattedAnswer.$result;
}
return nl2br($formattedAnswer);
}
function operate($text,$ops,$attribute){
$operatedText = null;
switch($ops){
case 'bold':
$operatedText = '<strong>'.$text.'</strong>';
break;
case 'italic':
$operatedText = '<i>'.$text.'</i>';
break;
case 'strike':
$operatedText = '<s>'.$text.'</s>';
break;
case 'underline':
$operatedText = '<u>'.$text.'</u>';
break;
case 'link':
$operatedText = ''.$text.'';
break;
default:
$operatedText = $text;
}
return $operatedText;
}
Here's a full function using quill.root.innerHTML, as the others didn't quite cover the complete usage of it:
function quillGetHTML(inputDelta) {
var tempQuill=new Quill(document.createElement("div"));
tempQuill.setContents(inputDelta);
return tempQuill.root.innerHTML;
}
This is just a slight different variation of km6 's answer.
For Quill version 1.3.6, just use:
quill.root.innerHTML;
Try it online: https://jsfiddle.net/Imabot/86dtuhap/
Detailed explaination on my blog
This link if you have to post the Quill HTML content in a form
quill.root.innerHTML on the quill object works perfectly.
$scope.setTerm = function (form) {
var contents = JSON.stringify(quill.root.innerHTML)
$("#note").val(contents)
$scope.main.submitFrm(form)
}
I put together a node package to convert html or plain text to and from a Quill Delta.
My team used it to update our data model to include both Quill's Delta and HTML. This allows us to render on the client without an instance of Quill.
See node-quill-converter.
It features the following functions:
- convertTextToDelta
- convertHtmlToDelta
- convertDeltaToHtml
Behind the scenes it uses an instance of JSDOM. This may make it best suited for migration scripts as performance has not been tested in a typical app request lifecycle.
Try
console.log ( $('.ql-editor').html() );
Here is how I did it, for you Express folks. It seems to have worked very well in conjunction with express-sanitizer.
app.js
import expressSanitizer from 'express-sanitizer'
app.use(expressSanitizer())
app.post('/route', async (req, res) => {
const title = req.body.article.title
const content = req.sanitize(req.body.article.content)
// Do stuff with content
})
new.ejs
<head>
<link href="https://cdn.quilljs.com/1.3.2/quill.snow.css" rel="stylesheet">
</head>
...
<form action="/route" method="POST">
<input type="text" name="article[title]" placeholder="Enter Title">
<div id="editor"></div>
<input type="submit" onclick="return quillContents()" />
</form>
...
<script src="https://cdn.quilljs.com/1.3.2/quill.js"></script>
<script>
const quill = new Quill('#editor', {
theme: 'snow'
})
const quillContents = () => {
const form = document.forms[0]
const editor = document.createElement('input')
editor.type = 'hidden'
editor.name = 'article[content]'
editor.value = document.querySelector('.ql-editor').innerHTML
form.appendChild(editor)
return form.submit()
}
</script>
express-sanitizer (https://www.npmjs.com/package/express-sanitizer)
document.forms (https://developer.mozilla.org/en-US/docs/Web/API/Document/forms)
My view only has one form, so I used document.forms[0], but if you have multiple or may extend your view in the future to have multiple forms, check out the MDN reference.
What we are doing here is creating a hidden form input that we assign the contents of the Quill Div, and then we bootleg the form submit and pass it through our function to finish it off.
Now, to test it, make a post with <script>alert()</script> in it, and you won't have to worry about injection exploits.
That's all there is to it.
Here is a proper way to do it.
var QuillDeltaToHtmlConverter = require('quill-delta-to-html').QuillDeltaToHtmlConverter;
// TypeScript / ES6:
// import { QuillDeltaToHtmlConverter } from 'quill-delta-to-html';
var deltaOps = [
{insert: "Hello\n"},
{insert: "This is colorful", attributes: {color: '#f00'}}
];
var cfg = {};
var converter = new QuillDeltaToHtmlConverter(deltaOps, cfg);
var html = converter.convert();
Refer https://github.com/nozer/quill-delta-to-html
For a jQuery-style solution that allows getting and setting the Quill value I am doing the following:
Quill.prototype.val = function(newVal) {
if (newVal) {
this.container.querySelector('.ql-editor').innerHTML = newVal;
} else {
return this.container.querySelector('.ql-editor').innerHTML;
}
};
let editor = new Quill( ... );
//set the value
editor.val('<h3>My new editor value</h3>');
//get the value
let theValue = editor.val();
quill-render looks like it's what you want. From the docs:
var render = require('quill-render');
render([
{
"attributes": {
"bold": true
},
"insert": "Hi mom"
}
]);
// => '<b>Hi mom</b>'
If you want to render quill using nodejs, there is a package quite simple based on jsdom, usefull to render backside (only one file & last update 18 days from now) render quill delta to html string on server
Just use this clean library to convert from delta from/to text/html
node-quill-converter
example:
const { convertDeltaToHtml } = require('node-quill-converter');
let html = convertDeltaToHtml(delta);
console.log(html) ; // '<p>hello, <strong>world</strong></p>'