In my react app I need to return a line which will be created based on a list.
Here is the object,
searchCriteria: {
op_company: "039",
doc_type: "ALL"
}
and in my UI, i need to show it as a paragraph with bold values. So the hard coded code would be like below
<p>Download request for op_company: <b>{searchCriteria.op_company}</b>, doc_type: <b>{searchCriteria.doc_type}</b></p>
But the object(searchCriteria) will be changed based on the user request. So I tried like below.
const getSearchCriteria = (criteria) => {
let searchCriteria = []
searchCriteria.push('Download request for')
Object.keys(criteria).forEach((key) => {
if(criteria[key] !== '') {
searchCriteria.push(` ${key}: ${criteria[key]},`)
}
});
return searchCriteria;
}
return (
<p>
{getSearchCriteria(searchCriteria).map((item) => <span key = {item}>{item}</span>)}
</p>
);
here i'm getting the expected output. But I can't get the value as bold (highlighted). Is there another way to directly deal with html elements?
Two questions: I'm studying spfx and for this particular example I provide I don't understand how the Send button associates with the setComment() function. I've created an Update button which I want to use to update the item I've just created. This does nothing even though I've passed the props to it (maybe incorrectly!)
For the Update button I've tried using React to pass the props using an onClick within the JSX, but that doesn't work. I've also tried doing this without the $. I've created a seperate set of functions _readListItem() and _getListItem() to help me but not sure how to use them in combination with _updateListItem(), or whether I need them at all.
import { Environment, EnvironmentType, Version } from '#microsoft/sp-core-library';
import { ISPHttpClientOptions, SPHttpClient, SPHttpClientResponse } from '#microsoft/sp-http';
import { escape } from '#microsoft/sp-lodash-subset';
import { BaseClientSideWebPart, IPropertyPaneConfiguration, PropertyPaneTextField } from '#microsoft/sp-webpart-base';
import * as strings from 'FeedbackWebpartWebPartStrings';
import styles from './FeedbackWebpart.module.scss';
import { IFeedbackWebPartProps } from './IFeedbackWebPartProps';
import {ISPListItem} from './ISPListItem';
export default class FeedbackWebpartWebPart extends BaseClientSideWebPart<IFeedbackWebPartProps> {
public render(): void {
this._updateListItem = this._updateListItem.bind(this);
this._readListItem = this._readListItem.bind(this);
this.domElement.innerHTML = `
<div>
<p class='${styles.titleText}'>Job Evaluation Form</p>
<i class='ms-Icon ms-Icon--NoteForward' aria-hidden='true'></i>
<p class='${styles.labelText}'>Name</p>
<input type='text' class='${styles.input}' maxlength='255' placeholder='${escape(this.properties.hintText)}' />
<br>
<br>
<p class='${styles.labelText}'>Job Title</p>
<input type='text' class='${styles.input}' maxlength='255' placeholder='${escape(this.properties.hintText)}' />
<br>
<p class='${styles.successIndicator}'></p>
<br>
<br>
<br>
<button type='button' class='ms-Button'><span class='ms-Button-label'>Send</span></button>
<br>
<br>
<button type='button' class='ms-Button' onClick='${this._updateListItem}'><span class='ms-Button-label'>Update</span></button>
</div>`;
//The binding below allows setComment to be used in the render and outside of it.
this.setComment = this.setComment.bind(this);
this._updateListItem = this._updateListItem.bind(this);
this._readListItem = this._readListItem.bind(this);
//Grab ALL the <input> elements
// [0] - just want the first element
// cast this generic element to specific type of element (which I know that it is) as the variable is expecting a specific type
const textInput: HTMLInputElement = this.domElement.getElementsByTagName("INPUT") [0] as HTMLInputElement;
// setComment is used in an event listener here to be called upon whenever a user types into this input field.
textInput.addEventListener("keyup", this.setComment);
this.sendFeedback = this.sendFeedback.bind(this);
const submitbutton: HTMLButtonElement = this.domElement.getElementsByTagName("BUTTON") [0] as HTMLButtonElement;
submitbutton.onclick = this.sendFeedback;
submitbutton.disabled = true;
}
private _updateListItem(): void {
const url: string = this.context.pageContext.site.absoluteUrl+"/_api/web/lists/getbytitle('Feedback')/items(1)";
const itemDefinition : any = {
"Title" : "Modified title field value!"};
const headers:any = {
"X-HTTP-Method":"MERGE",
"IF-MATCH": "*",
};
const spHttpClientOptions: ISPHttpClientOptions = {
"headers": headers,
"body": JSON.stringify(itemDefinition)
};
this.context.spHttpClient.post(url, SPHttpClient.configurations.v1,spHttpClientOptions)
.then((response: SPHttpClientResponse) => {
if (response.status === 204) {
this._operationResults.innerHTML = "Update: List Item updated successfully.";
} else {
this._operationResults.innerHTML = "Update: List Item update failed. " +response.status+" - "+response.statusText;
}
});
}
private sendFeedback(): void {
this.context.statusRenderer.clearError(this.domElement);
const paragraphElement: HTMLParagraphElement = this.domElement.getElementsByClassName(styles.successIndicator) [0] as HTMLParagraphElement;
paragraphElement.innerHTML="";
if(this._commentText === undefined || this._commentText.length === 0) {
this.context.statusRenderer.renderError(this.domElement, "Please type in a comment or a suggestion.");
return;
}
if(Environment.type === EnvironmentType.Local) {
this.context.statusRenderer.renderError(this.domElement, "Feedback can't be saved when running in local workbench");
return;
}
const url: string = this.context.pageContext.site.absoluteUrl+"/_api/web/lists/getbytitle('Feedback')/items";
const item : any = {
"Title": this._commentText,
"URL": window.location.href,
"JobTitle": this._jobTitle
};
const spHttpClientOptions: ISPHttpClientOptions = {
"body": JSON.stringify(item)
};
I'm sorry there is a lot of code but I don't know how you would understand what I need to know if I don't provide it.
I expect to understand how the Send button works and the Update button to update the item of any changes when clicked.
Looks like the "Send" button onClick event is set with the following code:
const submitbutton: HTMLButtonElement = this.domElement.getElementsByTagName("BUTTON") [0] as HTMLButtonElement;
submitbutton.onclick = this.sendFeedback;
Here getElementsByTagName("BUTTON")[0] returns the first element created with in the above HTML.
I guess going with the same approach, the update button could be handled like so:
const updateButton: HTMLButtonElement = this.domElement.getElementsByTagName("BUTTON") [1] as HTMLButtonElement;
updateButton.onclick = this._updateListItem.bind(this);
Note the changed index from [0] to [1], to take the second button, as well as the usage of bind(). Without bind, "this" would be undefined when _updateListItem is called.
I have a custom image upload validator. This validator makes sure the mime type is correct, checks the file size, and that the dimensions are correct. Part of this validator creates an image element and gives it the datauri (as src) to check the width/height. Because of that last check, it uses the img.onload event, and thus, this is an async validator.
My validator will accept urls as well as data uris.
Here is the source of my validators. I've created a FileValidator base class that checks mimeType and file size while the ImageValidator checks dimensions.
The validation classes are as follows. I made these classes because they require state given the ValidationRules. The validator method is the actor
FileValidator
export class FileValidator {
constructor(protected _rules: FileValidationRules) {}
public validator(control: FormControl): Promise<any> {
return new Promise((resolve, reject) => {
var value = control.value;
if (value) {
if (this.isDataUri(value)) {
if (this._rules.acceptedMimeTypes && this._rules.acceptedMimeTypes.length) {
var mimeType = this.getMimeType(value);
var allowedMimeType = false;
for (var i = 0; i < this._rules.acceptedMimeTypes.length; i++) {
if (this._rules.acceptedMimeTypes[i] === mimeType) {
allowedMimeType = true;
break;
}
}
if (!allowedMimeType) {
resolve({
fileValidator: `File type not allowed: ${mimeType}. Allowed Types: ${this._rules.acceptedMimeTypes}`
});
}
if (this._rules.maxSize) {
var blob = this.dataURItoBlob(value);
blob.size > this._rules.maxSize;
resolve({
fileValidator: `File is too large. File Size: ${this.getFriendlyFileSize(blob.size)}, Max Size: ${this.getFriendlyFileSize(this._rules.maxSize)}`
});
}
}
} else if (!this.isUrl(value)) {
resolve({
fileValidator: 'Unknown format'
});
}
}
resolve();
});
}
... Helper Methods
}
ImageValidator
export class ImageValidator extends FileValidator {
constructor(_rules: ImageValidationRules) {
super(_rules);
}
public validator(control: FormControl): Promise<any> {
return new Promise((resolve, reject) => {
super.validator(control).then((results) => {
if (results && results.fileValidator) {
resolve({
imageValidator: results.fileValidator
});
}
var value = control.value;
if (value) {
var rules = <ImageValidationRules>this._rules;
if (this.isDataUri(value)) {
if (rules.width || rules.height) {
var img: HTMLImageElement = document.createElement('img');
img.onload = () => {
var validSize = true;
if (rules.width && img.width !== rules.width) {
validSize = false;
}
if (rules.height && img.height !== rules.height) {
validSize = false;
}
if (!validSize) {
resolve({
imageValidator: `Image must be ${rules.width || 'any'}x${rules.height || 'any'}. Actual Size: ${img.width}x${img.height}`
});
}
};
img.src = value;
}
}
}
resolve();
});
});
}
}
This all works. If I select an image that doesn't meet requirements I get the proper error message.
But this image uploader is in a tabbed area.
If I cycle between tabs I get this error-
Error: Expression has changed after it was checked. Previous value: 'true'. Current value: 'false'.
Simply put, my tabbed area markup is-
<li *ngFor="let page of pages"
[ngClass]="{active: page === activePage}">
<a (click)="setActivatePage(page)">
<i *ngIf="getFormGroup(page).invalid" class="fa fa-exclamation-circle font-red"></i>
{{page.title}}
</a>
</li>
And my tabbed content is (simply)-
<element *ngFor="let input of activePage.inputs"
... etc>
</element>
This line-
<i *ngIf="getFormGroup(page).invalid" class="fa fa-exclamation-circle font-red"></i>
Is causing the error. But I can't figure out why. I think it has something to do with the fact that I'm using async validators.
If I comment out all resolve calls in my async validators it works fine (but obviously my validation stops working).
So from what I can gather, changing the active page reapplies validators. And the async validators are updating the validity after the validity was checked. And for some reason this is a problem for angular, like it doesn't consider asynchronous tasks will update state.. asynchronously.
Has anyone any idea what that might be. Sorry I couldn't provide a simplified working example, my system is complex.
EDIT
Additionally, it only occurs if the value for the input is set. If I call reset on the control or set the value to null (in the validator), then this does not occur when cycling tabs.
The answer was that I was resolving the validator when nothing changed-
resolve();
I figured the async validator should be resolved no matter what. But that doesn't seem to be the case. Removing this empty resolve fixed the problem.
I'm clicking a table row to edit the fields in a modal. The modal must have 2 functionalities (Add or Edit) depending on the GET request data like below.
$scope.editInterview = function(id) {
$http.get('/api/getinterview/' + id).then(function(response) {
editedObject = response.data.item
}
HTML
<label ng-if="editedObject.email">{{editedObject.email}}</label>
<label ng-if="!editedObject.email">Email</label>
<input ng-model="newObject.email" />
I am able to display the object in the labels, but that's not much help, because the data needs to be shown in the input boxes to be Edited and Saved.
How can i show the data from editedObject.email in the input, so i can save it using newObject.email?
I tried ng-init="editedObject.email", but it doesn't work. Is there some other ng-something that does this or i should be doing it in another way?
Update:
Edit and Update Methods, both are in the mainController.
$scope.editInterview = function(id) {
$http.get('/api/getinterview/' + id).then(function(response) {
editedObject = response.data.item
})
}
//Controller for the Modal
function DialogController($scope, $mdDialog, editedObject) {
$scope.editedObject = editedObject
$scope.submitObject = function(newObject) {
$http.post('/api/interview', newObject)
}
}
You have to make a deep copy from editObject.email to newObject.email. This could be done this way in controller after editOject.email has a value assigned.
$scope.newObject.email = angular.copy($scope.editObject.email);
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>'