How to make the link is clickable in textarea in ReactJS? - reactjs

I have a page named Community where user can comment on another post
I have a problem when user comments the link but it's not displayed true
When user comments, the link is not displayed true
The function I used to convert the text to link
const convertToLink = (text) => {
var exp = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&##\/%?=~_|!:,.;]*[-A-Z0-9+&##\/%=~_|])/ig;
var text1 = text.replace(exp, "<a href='$1'>$1</a>");
var exp2 = /(^|[^\/])(www\.[\S]+(\b|$))/gim;
return text1.replace(exp2, '$1$2');
}

You cannot render HTML inside of textarea. Use <p> instead
<p dangerouslySetInnerHTML={{ __html: convertToLink(text) }}></p>

Related

How to retrieve information on a specific button when clicked?

I'm making a film review site where users can search for the title of a film and when entered, a list of films matching the entered title will appear.
I made it so that each film and its corresponding information is contained within a button. I want to add an event listener to the film buttons that will listen to when the button is clicked and retrieve an image link of the movie poster from that specific button that was clicked and plug it into a text field with the ID of "imageSrc". The problem is that currently, it only retrieves the image link of the last film button that is displayed on the page rather than retrieving a specific film button I clicked and the last image link is plugged into the text field "imageSrc" right after entering the search (no film buttons were clicked, it just automatically plugs in the image link associated with the last film result displayed).
function FilmCard({ film }) {
function displaySearchResults({ film }) {
let results = [];
for (let i = 1; i < 5; i++) {
if (film.results[`${i}`]) {
results +=
`<button class='singleFilmCards' onclick=${(document.getElementById(
"imageSrc"
).value = film.results[`${i}`].image)}>` +
"<div class='film-wrapper'>" +
`<img src="${film.results[`${i}`].image}">` +
"<h2>" +
film.results[`${i}`].title +
"</h2>" +
"<h3>" +
film.results[`${i}`].description +
"</h3>" +
"<div>" +
"</button>";
}
}
return results;
}
if (!film) return <></>;
return (
<>
<div
className="FilmList"
dangerouslySetInnerHTML={{
__html: displaySearchResults({ film }),
}}
>
{/* <img alt="Movie Posters" src={film.results[0].image} />
<h2>{film.results[0].title}</h2>
<p>{film.results[0].description}</p> */}
</div>
</>
);
}
This happens because the index variable i in your loop will always have value of 5 in all the onclicks. There is very good reason why setting a pure html in react is done with the dangerouslySetInnerHTML prop - to let the developer know that he/she's doing something that shouldn't be done (there are cases when it's necessary, extremely rare though...)
What you should do, is to make the button into it's own component and handle the events via react event callbacks.
You could try something like this:
function FilmButton({ result, onImageChange }) {
return (
<button
className="singleFilmCards"
onClick={() => onImageChange(result.image)}
>
<div className="film-wrapper">
<img src={result.image} />
<h2>{result.title}</h2>
<h3>{result.description}</h3>
</div>
</button>
);
}
function FilmCard({ film }) {
const handleImageChange = (imageUrl) => {
document.getElementById("imageSrc").value = imageUrl;
};
if (!film) {
return <></>;
}
return (
<div className="FilmList">
{film.results.map((result, index) => (
<FilmButton
key={index}
result={result}
onImageChange={handleImageChange}
/>
))}
</div>
);
}
...in fact, you definitely might want to consider also rewriting the document.getElementById("imageSrc").value = imageUrl in such way, that you won't be manipulating the DOM directly. Because this completely bypasses React.js. There are many ways you rewrite this to use React - from passing the new imageUrl via prop callback into higher component (same as the onImageChange in my example), to using a React context (context would probably be an overkill though :)

console log dangerouslySetInnerHTML={{ __html: article.description }}

I built a wyziwig following the instructions here: https://blog.learningdollars.com/2020/04/01/how-to-add-a-rich-text-editor-in-your-react-app-using-draft-js-and-react-draft-wysiwyg/
Now I want to add a show/hide container based on 3 lines of text in the output.
I'm just trying to console.log(dangerouslySetInnerHTML={{ __html: output }}), so I can test the show/hide as well as add an ellipsis. Anyone have any idea how to do this?
Edit:
If you don't want to follow the link, I have setup a sandbox here: https://codesandbox.io/s/react-draft-wysiwyg-fjfh9
More Details:
<div class="modal-body" dangerouslySetInnerHTML={{ __html: output }} /> comes from a library draftjs-to-html. It takes a string such as the following <p>This text is <strong><em>BOLD AND ITALIC</em></strong> with this wyziwyg.</p>, and outputs a string display to the html page such as the following...
"This text is BOLD AND ITALIC from a wyziwyg."
What I want to be able to is determine the .length() of the html displayed output string(above). To do this, I need to see if there is a way to console.log(dangerouslySetInnerHTML={{ __html: output }});
When you want to get the length to the text from a html string, you could do the following, since you assume the contents to be safe (coming from the user's editor):
const getTextLength = (html) => {
// This will never get added to the DOM.
const element = document.createElement("div")
element.innerHTML = html
return element.textContent.length
}
// Some test cases.
const outputs = [
"Some text",
"<p>Some text</p>",
"<p><strong>Some</strong> text</p>"
]
// Should ouput `9` for all test inputs.
console.log(outputs.map(getTextLength))

How can I format embeded tweets using ReactJs?

I'm receiving a document from the server that receives an embeded tweet and injecting it this way:
renderDetails() {
return (
<div className="new-content">
<h1 className="title-new">{this.state.new.title}</h1>
<div className="body-new"
dangerouslySetInnerHTML={{__html: DOMPurify.sanitize(this.state.new.content)}}
></div>
</div>
);
}
The content has this form:
<div class="new-content">
.
.
.
<blockquote>America(#VOANoticias) 27 de marzo de 2019</blockquote>
.
.
.
How can I process this data to show the blockquote formatted as a tweet?
You can pick the tweet-id from the anchor tag and use it to create the tweet programmatically.
const twitterItems = Array.from(document.querySelectorAll("a[href^='https://twitter.com']"));
twitterItems.forEach((item) => {
const extractedUrlStr = item.href;
const url = new URL(extractedUrlStr);
const tweetID = url.pathname.split("/")[3];
const parentQuote = item.parentNode;
parentQuote.innerHTML = "";
twttr.widgets.createTweet(tweetID, parentQuote);
});
For React, you would probably insert this code in componentDidMount
PS: If the blockquote is of correct defined format, just including the script in the document will format the tweet for you.
Codepen Demo

AngularJs Auto Complete Search

So this works with static data, but when I push data with a $http this autocomplete does not work. The data pushes to the empty array of airport_list but something is happening when I try to use airport_list in for the autocomplete. Not sure what is is. I can only find answers which pertain to static data.
This is updated per everyones help.
Here is the controller
app.controller('selectCtrl', function($scope, $http) {
$scope.airport_list = null;
$http({
url: 'someUrl.com',
method: 'GET'
})
.then((response) => {
angular.forEach(response.data.airports, function(value, key) {
$scope.airport_list = response.data.airports;
})
$scope.airports = $scope.airport_list;
});
$scope.selectAirport = function(string) {
$scope.airport = string;
$scope.hidelist = true;
};
})
Here is the template
<div class="control">
<div>
<input
type="text"
name="airport"
id="airport"
ng-model="airport"
ng-change="searchFor(airport)"
placeholder="From..."
/>
<div class="airport-container-dropdown" ng-hide="hidelist">
<div
class="airport-list"
ng-repeat="airport in airports"
ng-click="selectAirport(airport)"
>
{{ airport.name }}
</div>
</div>
</div>
</div>
I really would like to do this without using bootstrap typeahead.
Thank you for looking at this.
I have made changes as recommended by below answers and the $http request is feeding into the autocomplete as a whole list but searching by name does not work and clicking on name sets [object, object]
this would be the code which is specific to that functionality.
$scope.searchFor = function(string) {
$scope.hidelist = false;
const output = [];
angular.forEach($scope.airport_list, function(airport) {
if (airport[0].toLowerCase().indexOf(string.toLowerCase(airport)) >=
0) {
output.push(airport);
}
});
$scope.airports = output;
};
$scope.selectAirport = function(string) {
$scope.airport = string;
$scope.hidelist = true;
};
Try this:
$scope.airport_list = response.data.airports;
What I am seeing is that you have an array: $scope.airport_list = [];
When you make your http request, you push what I would understand to be an array of airports into that array. So you end up with your airport array from the backend at the first position of $scope.airport_list, vs. $scope.airport_list being the actual list.
For your search method, you should change the following:
In your HTML:
ng-change="searchFor(airport.name)"
In your JS:
I've renamed your function and changed the input variable to be more clear. You were passing in a full airport, but treating it as a string. You need to compare your provided airport name to that of the airports in the array. So you iterate over the array, and compare each element's name property to what you pass in.
$scope.searchFor = function(airportName) {
$scope.hidelist = false;
const output = [];
angular.forEach($scope.airport_list, function(airport) {
if (airport.name.toLowerCase() === airportName) {
output.push(airport);
}
});
$scope.airports = output;
console.log($scope.airports);
};
I have provided minimal changes to your code to implement this, however I suggest you look at this SO post to filter drop down data more appropriately.
Angularjs Filter data with dropdown
If you want to simply filter out what is displayed in the UI, you can try this in your HTML template. It will provide a text field where you supply a partial of the airport name. If at least one character is entered in that box, the list will display on the page, with the appropriate filtering applied. This will avoid having to call functions on change, having a separate array, etc.
<input type="text" name="airport" id="airport" ng-model="airportSearch.name" placeholder="From..." />
<div class="airport-container-dropdown" ng-hide="!airportSearch.name">
<div class="airport-list"
ng-repeat="airport in airport_list | filter:airportSearch"
ng-click="selectAirport(airport)">
{{ airport.name }}
</div>
</div>

Protractor - How to get the text of the <a> tag that was clicked

In other words, I have just selected a dropdown option successfully; however, I would like to now assert that the option text is the expected value.
Note that I have selected the dropdown option value with elementSwitcher.element(by.linkText(option)).click() below :
this.selectDropdown = function (name, option, uniqueId) {
var elem = element(by.id(uniqueId));
var elementSwitcher = elem.element(by.css("div[uib-dropdown]"));
elementSwitcher.element(by.css("button[uib-dropdown-toggle]")).click();
elementSwitcher.element(by.linkText(option)).click().then(function () {
var el = elementSwitcher.element(by.tagName('a')).getText();
console.log('*** JUST CLICKED. el text = ', el);
});
};
and the rendered HTML looks something like this :
<div class="btn-group dropdown open" uib-dropdown="">
<ul class="dropdown-menu accounts-dropdown" uib-dropdown-menu="" aria-labelledby="simple-dropdown">
<li ng-repeat="accountChoice in ctrl.accountOptions" class="ng-scope">
<a ng-click="ctrl.selectOption(accountChoice)" class="ng-binding">Assets</a>
</li>
</ul>
</div>
Problem is that I'm not getting access to the Assets text I would expect, and my console.log() is telling me:
W/element - more than one element found for locator By(css selector,
a) - the first result will be used
*** JUST CLICKED. el text = ElementFinder {
browser_:
ProtractorBrowser {
controlFlow: [Function],
Again, I would like to click the dropdown element (a tag), then get the text part of that same a tag which was clicked.
Advice is appreciated.
**** UPDATE ****
As per Alex's answer below, I post my final helper function:
/**
* Helper function to select an angular-ui dropdown option, and return the <a> link text which was selected.
*
* #param name
* #param option
* #param uniqueId
*
* #returns {Promise} A promise that returns the link text when resolved.
*/
this.selectUibDropDownOption = function (name, option, uniqueId) {
var deferred = protractor.promise.defer();
var elem = element(by.id(uniqueId)); // i.e. by.id('drpAccount')
var elementSwitcher = elem.element(by.css("div[uib-dropdown]"));
elementSwitcher.element(by.css("button[uib-dropdown-toggle]")).click();
// i.e. by.linkText('Liabilities and Equity')
//elementSwitcher.element(by.linkText(option)).click(); // also works, but doesn't get link text
var link = elementSwitcher.element(by.linkText(option));
// Important to first get the text of the <a> tag, then click on the link.
link.getText().then(function (linkText) {
link.click();
deferred.fulfill(linkText);
});
return deferred.promise;
};
and from the calling function, where my describe/it is defined :
var specItem = it(item.name, function () {
item.search.map(function (srchItem) {
// i.e. { "name": "Account", "option": "Assets", "uniqueId": "drpAccount" },
var optionText = pageObjects.selectUibDropDownOption(srchItem.name, srchItem.option, srchItem.uniqueId);
optionText.then(function (linkText) {
console.log('** Clicked on: ', linkText);
expect(srchItem.option).toEqual(linkText);
});
});
});
What you see on the console is the string representation of a promise - getText() returns a promise. If you want to see the actual link text value, resolve the promise:
elementSwitcher.element(by.tagName('a')).getText().then(function (linkText) {
console.log('*** JUST CLICKED. el text = ', linkText);
});
// or elementSwitcher.element(by.tagName('a')).getText().then(console.log);
Also, if you get the "stale element reference" or "element not found" error, you may need to get the text before actually clicking the link.
Or, why don't do it this way - find the link by the link text, get the text and click it:
var link = elementSwitcher.element(by.linkText(option));
link.getText().then(function (linkText) {
console.log(linkText);
link.click();
});

Resources