I'm using Quill editor on Livewire and trying to upload inserted images on editor with Livewire JavaScript Upload API. The problem is,
I can't insert temporary url to editor. If I use $image->temporaryUrl() or $url outside of editor, image shows. I can get image temporary url. But $image->temporaryUrl() and $url not working inside the editor. And images are still uploading to livewire-tmp directory. Just can't insert temporary url to editor.
My blade file:
<script>
var quill = null;
function selectLocalImage() {
const input = document.createElement('input');
input.setAttribute('type', 'file');
input.click();
// Listen upload local image and save to server
input.onchange = () => {
const file = input.files[0];
// file type is only image.
if (/^image\//.test(file.type)) {
imageHandler(file);
} else {
console.warn('You could only upload images.');
}
};
}
function imageHandler(image) {
var formData = new FormData();
var url = '{{ $url }}';
formData.append('image', image);
formData.append('_token', '{{ csrf_token() }}');
formData.append('pageId', '{{ $page->id }}');
//let file = document.querySelector('image').files[0];
#this.upload('image', image, (uploadedFilename) => {
insertToEditor(url, quill);
})
}
function insertToEditor(url, editor) {
// push image url to rich editor.
const range = editor.getSelection();
editor.insertEmbed(range.index, 'image', url);
}
$(document).ready(function() {
var options = {
modules: {
syntax: true,
toolbar: [
[{
'font': []
}, {
'size': []
}],
['bold', 'italic', 'underline', 'strike'],
[{
'color': []
}, {
'background': []
}],
[{
'script': 'super'
}, {
'script': 'sub'
}],
[{
'header': '1'
}, {
'header': '2'
}, 'blockquote', 'code-block'],
[{
'list': 'ordered'
}, {
'list': 'bullet'
}, {
'indent': '-1'
}, {
'indent': '+1'
}],
['direction', {
'align': []
}],
['link', 'image', 'video', 'formula'],
['clean']
]
},
placeholder: 'Content...',
theme: 'snow'
};
quill = new Quill('#editor', options);
quill.getModule('toolbar').addHandler('image', () => {
selectLocalImage();
});
quill.on('text-change', function() {
#this.set('pageBody', quill.root.innerHTML);
});
});
</script>
My component:
public $image;
public $url;
public function updatedImage()
{
if ($this->image) {
$this->url = $this->image->temporaryUrl();
// dd($this->url); <---- Showing temporary url
}
}
How can I solve this problem?
After some research I've changed my code like below.
After upload, fired an event called imageAdded with image parameter. And listen this event on Livewire component.
With imageAdded function I've defined image's temporary url. And dispatched a browser event called getUrl. With array_push I added new images to $images array. Because there could be multiple images to upload.
Finally, listened dispatched browser event getUrl on Java Script and then added to editor with insertToEditor function.
function imageHandler(image) {
var formData = new FormData();
formData.append('image', image);
formData.append('_token', '{{ csrf_token() }}');
formData.append('pageId', '{{ $page->id }}');
#this.upload('image', image, (uploadedFilename) => {
window.livewire.emit('imageAdded', image);
});
}
window.addEventListener('getUrl', e => {
e.detail.imageUrl;
console.log(e.detail.imageUrl);
insertToEditor(e.detail.imageUrl, quill);
});
function insertToEditor(url, editor) {
// push image url to rich editor.
const range = editor.getSelection();
editor.insertEmbed(range.index, 'image', url);
}
Component:
public $images = [];
public $image;
public $url;
protected $listeners = ['imageAdded'];
public function imageAdded()
{
$this->url = $this->image->temporaryUrl();
$this->dispatchBrowserEvent('getUrl', ['imageUrl' => $this->url]);
array_push($this->images, $this->image);
}
public function store()
{
foreach ($this->images as $key => $image) {
$image->store('images/page_images/'.$this->page->id);
}
}
Now I can upload images with Quill editor on Livewire. Suggestions would be pleased.
I've developed this part based in your code. With that you can replace the temporary url by stored images. If you want, you can save regex in variable.
public function store()
{
$this->validate();
foreach ( $this->images as $key => $image ) {
$temporaryUrl = $image->temporaryUrl();
preg_match('/(https?:\/\/.*\.(?:png|jpg|gif))/', $temporaryUrl, $tmp);
if ( $tmp[0] ) {
$temporaryUrl = $tmp[0];
$url = $image->store('imgs/help-center');
$url = config('app.url') . '/' . $url;
preg_match('/(https?:\/\/.*\.(?:png|jpg|gif))/', $url, $match);
if ( $match[0] ) {
$url = $match[0];
$this->article['content'] = str_replace($temporaryUrl, $url, $this->article['content']);
}
}
}
$this->images = [];
$article = HelpCenter::updateOrCreate([
'id' => $this->article['id']
], $this->article);
}
i hope it help you
Related
i can upload array of images to aws s3 succeefully without any issues, but i want to show these images in my application. I can upload images succeefuly to my s3 Bucket, but i want to add the metadata, or the url to dynamodb to show these images along with the other data in my application.
any support will be appreciated.
import { View, Text, Image, StyleSheet, ScrollView, Button, TextInput, Platform, PermissionsAndroid } from 'react-native';
import React, {useState, useEffect} from "react";
import { RadioButton } from 'react-native-paper';
import { useForm, Controller } from "react-hook-form";
import ImagePicker from 'react-native-image-crop-picker';
import DropDownPicker from 'react-native-dropdown-picker';
import { DataStore, Storage, API, graphqlOperation, Auth } from 'aws-amplify';
import { createService } from '../graphql/mutations';
import { RNS3 } from 'react-native-aws3';
const AddService = ()=>{
const [checked, setChecked] = React.useState('OFFERED');
const { control, handleSubmit, formState: { errors } } = useForm({
});
const [images, setImages]= useState([])
const uploadButtonClick = () => {
let promises = [];
images.map((image, i) => {
promises.push(uploadImageToS3(image));
});
}
const uploadImageToS3 = async image => {
const options = {
keyPrefix: "uploads/",
bucket: "alkhair-serviceimages142621-dev",
region: "us-east-1",
accessKey: "",
secretKey: "",
successActionStatus: 201
}
const file = {
uri: `${image.path}`,
name: image.path.substring(image.path.lastIndexOf('/') + 1), //extracting filename from image path
type: image.mime,
};
// I had to work around here to get the image url and add it to dynamoDB, but i can only add one image. I need to add the images that the user uploads max of 4 images
setImageUri(`http://${options.bucket}.s3.amazonaws.com/${options.keyPrefix}${file.name}`)
return new Promise((resolve, reject) => {
RNS3.put(file, options)
.then(res => {
if (res.status === 201) {
const {postResponse} = res.body;
resolve({
src: postResponse.location,
});
} else {
console.log('error uploading to s3', res);
}
})
.catch(err => {
console.log('error uploading to s3', err);
reject(err);
});
});
};
Promise.all(promises).then(uploadedImgs => {
console.log('Yayy, all images are uploaded successfully', uploadedImgs)
})
const onSubmit =async (data) =>{
const createNewService =
{
type: checked,
name: data.serviceName,
description: data.serviceDescription,
image: // i need to upload array of images here which is already uploaded to aws s3,
serviceProviderName: data.serviceProviderName,
serviceProviderAddress: data.serviceProviderAddress,
serviceProviderPhone: data.serviceProviderPhone,
notes: data.serviceProviderNotes,
serviceAreaId: areaLabel.value,
serviceCategoryId: categLabel.value,
}
API.graphql(graphqlOperation(createService, {input: createNewService}))
}
const pickImage = async()=>{
let isStoragePermitted = await requestExternalWritePermission();
if(isStoragePermitted){
ImagePicker.openPicker({
multiple: true,
waitAnimationEnd: false,
includeExif: true,
forceJpg: true,
compressImageQuality: 0.8,
maxFiles: 6,
includeBase64: true,
showsSelectedCount: true,
mediaType: 'photo',
}) .then(imgs=>{
if (imgs.length <= 6) {
setImages([...images, ...imgs]);
} else {
setImages([...images]);
ToastAndroid.show("Maximum of 6 images allowed", ToastAndroid.SHORT);
}
})}}
const [areaOpen, setAreaOpen] = useState(false);
const [areaValue, setAreaValue] = useState(null);
const [area, setArea] = useState([
{label: 'مصر القديمة', value: 'AAA'},
{label: 'المعادي', value: 'BBB'},
{label: 'القاهرة الجديدة', value: 'CCC'}
]);
const [serviceOpen, setServiceOpen] = useState(false);
const [serviceValue, setServiceValue] = useState(null);
const [service, setService] = useState([
{label: 'خدمات مواصلات ', value: '09339a8d'},
{label: 'اخرى', value: 'b4d227e3'}
]);
I am working on a project where we need to embed a Power BI Report but not with the EmbedType of a Report - it should be visual. What I did is to embed each visual of the report into a separate DIV and what I have to do now is to apply the filters on each.
Sample Code (cshtml):
var embedContainer = '';
var pageName = '';
var visualName = '';
var type = '';
var DisplayObject = [
{
embedContainer: $('#report-container_hSlicer')[0],
pageName: 'ReportSection',
visualName: '0e2d3a2ad25c8c8c224b',
type: 'visual',
},
{
embedContainer: $('#report-container_lSlicer')[0],
pageName: 'ReportSection',
visualName: "1f1841a2b8b3414a4318",
type: 'visual',
},
{
embedContainer: $('#report-container_fSlicer')[0],
pageName: 'ReportSection',
visualName: "3acac86be0dd995b34b1",
type: 'visual',
},
{
embedContainer: $('#report-container_index')[0],
pageName: 'ReportSection',
visualName: '802df8d5bc156f326b5a',
type: 'visual',
},
{
embedContainer: $('#report-container_cbCity')[0],
pageName: 'ReportSection',
visualName: "3cd8ddf8eb40dcc35d4d",
type: 'visual',
},
{
embedContainer: $('#report-container_All')[0],
type: 'report',
},
]
On the JS for embedding:
var resultModel = {};
var report = null;
var pages = [];
var config = {};
var reports = [];
function getParameters() {
var model = {
"workspace": "8b4f5f85-02a4-4afe-9104-3d4929d025c4",
"report": "038b5e5b-4b51-40ae-91f6-580c745b32c3"
}
$.ajax({
type: "POST",
url: "/embedinfo/getembedinfo",
contentType: 'application/json',
data: JSON.stringify(model),
success: function (data) {
resultModel = {
token: data.embedToken.token,
embedUrl: data.embedReport[0].embedUrl,
reportID: data.embedReport[0].reportId,
type: data.type
}
//Display each visual
DisplayObject.forEach(e => {
embedContainer = e.embedContainer;
pageName = e.pageName;
visualName = e.visualName;
type = e.type;
embedPowerBIReport(resultModel.token, resultModel.embedUrl, resultModel.reportID, resultModel.type);
});
console.log(resultModel);
console.log('Got tokens');
},
error: function (err) {
alert(err);
}
});
};
let loadedResolve, reportLoaded = new Promise((res, rej) => { loadedResolve = res; });
let renderedResolve, reportRendered = new Promise((res, rej) => { renderedResolve = res; });
models = window["powerbi-client"].models;
function embedPowerBIReport(accessToken_, embedURL, embedReportID, TokenType) {
// Read embed application token
let accessToken = accessToken_;
// Read embed URL
let embedUrl = embedURL;
// Read report Id
let embedReportId = embedReportID;
// Read embed type from radio
let tokenType = TokenType;
// We give All permissions to demonstrate switching between View and Edit mode and saving report.
let permissions = models.Permissions.All;
// Create the embed configuration object for the report
// For more information see https://go.microsoft.com/fwlink/?linkid=2153590
if (type == 'report') {
config = {
type: type,
tokenType: tokenType == '0' ? models.TokenType.Aad : models.TokenType.Embed,
accessToken: accessToken,
embedUrl: embedUrl,
id: embedReportId,
settings: {
panes: {
filters: {
visible: false
},
}
}
};
}
else {
config = {
type: 'visual',
tokenType: tokenType == '0' ? models.TokenType.Aad : models.TokenType.Embed,
accessToken: accessToken,
embedUrl: embedUrl,
id: embedReportId,
permissions: permissions,
pageName: pageName,
visualName: visualName,
navContentPaneEnabled: false,
settings: {
panes: {
filters: {
visible: false
},
}
}
};
}
// Get a reference to the embedded report HTML element
//let embedContainer = $('#report-container')[0];
// Embed the report and display it within the div container.
report = powerbi.embed(embedContainer, config);
// report.off removes all event handlers for a specific event
report.off("loaded");
// report.on will add an event handler
report.on("loaded", function () {
loadedResolve();
report.off("loaded");
});
// report.off removes all event handlers for a specific event
report.off("error");
report.on("error", function (event) {
console.log(event.detail);
});
// report.off removes all event handlers for a specific event
report.off("rendered");
// report.on will add an event handler
report.on("rendered", function () {
renderedResolve();
report.off("rendered");
});
reports.push(report);
}
async function main() {
await getParameters();
await reportLoaded;
console.log('Report Loaded');
// Insert here the code you want to run after the report is loaded
await reportRendered;
console.log('Report Rendered');
//console.log('got all page');
//var reportAll = reports.filter(function (report) {
// return report.embedtype == 'report';
//})[0];
console.log('got all page');
// Insert here the code you want to run after the report is rendered
const filter = {
$schema: "http://powerbi.com/product/schema#basic",
target: {
table: "tblLifeStage",
column: "Life Stage"
},
operator: "In",
values: ["F2 - Upscale Earners"],
filterType: models.FilterType.BasicFilter,
requireSingleSelection: true
};
//// Retrieve the page collection and get the visuals for the active page.
//pages = await reportAll.getPages();
//// Retrieve the active page.
//let page = pages.filter(function (page) {
// return page.isActive;
//})[0];
//const visuals = await page.getVisuals();
//console.log(visuals);
//// Retrieve the target visual.
//let slicer = visuals.filter(function (visual) {
// return visual.type === "slicer" && visual.name === "1f1841a2b8b3414a4318";
//})[0];
//console.log(slicer);
// Set the slicer state which contains the slicer filters.
for (const report of reports) {
console.log(report);
await report.setSlicerState({ filters: [filter] });
console.log("slicer was set.");
};
};
//Calling Async function
main();
We can Apply Visual level filters to every Visual. You can use updateFilters to set new filter to the Visual our Update the filters.
Simply add this piece of code On the js for embedding in the display each Visual section or you use the variable visualName and apply filters later to the visual.
await visualName.updateFilters(models.FiltersOperations.Replace, filtersArray);
Reference:
https://learn.microsoft.com/javascript/api/overview/powerbi/control-report-filters#visual-level-filters
I use croppie to upload images in my laravel application. The upload part works flawlessly. However, since the PNG image takes more space than the original images. I want to have jpeg images stored on the server instead of PNG and with my own quality parameters provided.
Here is the javascript code I use for uploading images using croppie.
but save image with PNG format
$(function() {
var croppie = null;
var el = document.getElementById('resizer');
$.base64ImageToBlob = function(str) {
// extract content type and base64 payload from original string
var pos = str.indexOf(';base64,');
var type = str.substring(5, pos);
var b64 = str.substr(pos + 8);
// decode base64
var imageContent = atob(b64);
// create an ArrayBuffer and a view (as unsigned 8-bit)
var buffer = new ArrayBuffer(imageContent.length);
var view = new Uint8Array(buffer);
// fill the view, using the decoded base64
for (var n = 0; n < imageContent.length; n++) {
view[n] = imageContent.charCodeAt(n);
}
// convert ArrayBuffer to Blob
var blob = new Blob([buffer], { type: type });
return blob;
}
$.getImage = function(input, croppie) {
if (input.files && input.files[0]) {
var reader = new FileReader();
reader.onload = function(e) {
croppie.bind({
url: e.target.result,
});
}
reader.readAsDataURL(input.files[0]);
}
}
$("#file-upload").on("change", function(event) {
$("#myModal").modal();
// Initailize croppie instance and assign it to global variable
croppie = new Croppie(el, {
viewport: {
width: 200,
height: 200,
type: 'circle'
},
boundary: {
width: 250,
height: 250
},
enableOrientation: true
});
$.getImage(event.target, croppie);
});
$("#upload").on("click", function() {
croppie.result('base64','original','jpeg',0).then(function(base64) {
$("#myModal").modal("hide");
$("#profile-pic").attr("src","/images/ajax-loader.gif");
var url = "{{ url('/demos/jquery-image-upload') }}";
var formData = new FormData();
formData.append("profile_picture", $.base64ImageToBlob(base64));
// This step is only needed if you are using Laravel
$.ajaxSetup({
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
}
});
$.ajax({
type: 'POST',
url: url,
data: formData,
processData: false,
contentType: false,
success: function(data) {
if (data == "uploaded") {
$("#profile-pic").attr("src", base64);
} else {
$("#profile-pic").attr("src","/images/icon-cam.png");
console.log(data['profile_picture']);
}
},
error: function(error) {
console.log(error);
$("#profile-pic").attr("src","/images/icon-cam.png");
}
});
});
});
// To Rotate Image Left or Right
$(".rotate").on("click", function() {
croppie.rotate(parseInt($(this).data('deg')));
});
$('#myModal').on('hidden.bs.modal', function (e) {
// This function will call immediately after model close
// To ensure that old croppie instance is destroyed on every model close
setTimeout(function() { croppie.destroy(); }, 100);
})
});
I faced similar issues a while ago.
I am using the jQuery way as per the docs. The OP is also using jQuery elsewhere, so...
Croppie is attached this way:
upload = $('#upload_div').croppie(method, args);
Instead of passing the parameters like this:
croppie.result('base64','original','jpeg',0).then(function(base64){...
You pass named parameters:
upload.croppie(
'result',
{type: 'base64',
size: 'original',
format: 'jpeg',
quality: 0
}).then(function(base64){ ...
Use:
croppie.result({
type: 'base64',
format: 'jpeg',
viewport:'original',
quality:1,
circle: false
}).then(function (base64) {
...
here's what I tried in model1.js:
model1.remotemethod1 = function(id, data, cb) {
var model2 = app.models.model2;
model2.remotemethod2(id, data).then(response => {
cb(null, true);
});
};
this is my model2.js :
it has the definition of remotemethod2 .
'use strict';
module.exports = function(model2) {
model2.remotemethod2 = function(id, data, cb) {
var promise;
let tags = data.tags ? data.tags.slice() : [];
delete data.categories;
delete data.tags;
promise = model2.upsertWithWhere({
or: [
{barcode: data.barcode},
{id: data.id},
],
}, data);
promise.then(function(model2) {
model2.tags.destroyAll().then(function() {
for (let i = 0; i < tags.length; i++) {
model2.tags.add(tags[i]);
}
cb(null, model2);
});
});
};
};
But it dos not work !
I think that app.models.model2 does not give me the model with its remote methods ! maybe I should get an instance of the model2 !
var (VAR-NAME)= (CURRENT-MODEL).app.models.(ANOTHER_MODEL);
you now can use the other model by calling one of its methods for example
EX:
VAR-NAME.create();
Declare remotemethod1 in server.js app.start and you'll have access to the correct app.models.model2 and you will be able to use its remote method.
app.start = function() {
model1.remotemethod1 = (id, data, cb) => {
var model2 = app.models.model2;
model2.remotemethod2(id, data).then(response => {
cb(null, true);
});
};
model1.remoteMethod(
'remotemethod1', {
http: { path: '/remotemethod1', verb: 'post', status: 200, errorStatus: 400 },
accepts: [{arg: 'id', type: 'number'}, {arg: 'id', type: 'object'}],
returns: {arg: 'status', type : 'string' }
}) ;
}
// The rest of app.start...
EDIT you can also create the remote method will the correct app context with a file located in myprojectname/server/boot
`module.exports(app) {
/* Create remote methods here */
}`
I am using ReactJs and Mocha and trying few unit tests. I have two mandatory form fields First Name and Last Name. I am trying to test the validation where if only one field is entered, the other field displays the missing value validation error.
Below code simulates the changes to the value and simulates the form submit.
TestUtils.Simulate.change(firstNameElement , {target: {value: 'Joe'}});
TestUtils.Simulate.submit(formElement)
The changed value is reflected in the event handlers on First Name. But, not in the test. So, both the fields display missing value validation failing my test.
What I could be doing wrong here?
Below are code:
//NameForm.jsx
'use strict';
var React=require('react')
var forms = require('newforms')
var NameForm = forms.Form.extend({
firstName: forms.CharField({maxLength: 100, label: "First name(s)"}),
lastName: forms.CharField({maxLength: 100, label: "Last name"}),
cleanFirstName(callback) {
callback(null)
},
render() {
return this.boundFields().map(bf => {
return <div className={'form-group ' + bf.status()}>
<label className="form-label" htmlFor={bf.name}>
<span className="form-label-bold">{bf.label}</span>
</label>
{bf.errors().messages().map(message => <span className="error-message">{message}</span>)}
<input className="form-control" id={bf.name} type="text" name={bf.name} onChange = {this.onChangeHandler}/>
</div>
})
}
, onChangeHandler: function(e){
console.log("onchnage on input is called ----- >> " + e.target.value)
}
})
module.exports = {
NameForm
}
Here is NamePage.jsx:
'use strict';
var React = require('react')
var {ErrorObject} = require('newforms')
var superagent = require('superagent-ls')
var {API_URL} = require('../../constants')
var {NameForm} = require('./NameForm')
var NamePage = React.createClass({
contextTypes: {
router: React.PropTypes.func.isRequired
},
propTypes: {
data: React.PropTypes.object,
errors: React.PropTypes.object
},
statics: {
title: 'Name',
willTransitionTo(transition, params, query, cb, req) {
if (req.method != 'POST') { return cb() }
superagent.post(`${API_URL}/form/NameForm`).send(req.body).withCredentials().end((err, res) => {
if (err || res.serverError) {
return cb(err || new Error(`Server error: ${res.body}`))
}
if (res.clientError) {
transition.redirect('name', {}, {}, {
data: req.body,
errors: res.body
})
}
else {
transition.redirect('summary')
}
cb()
})
}
},
getInitialState() {
return {
client: false,
form: new NameForm({
onChange: this.forceUpdate.bind(this),
data: this.props.data,
errors: this._getErrorObject()
})
}
},
componentDidMount() {
this.setState({client: true})
},
componentWillReceiveProps(nextProps) {
if (nextProps.errors) {
var errorObject = this._getErrorObject(nextProps.errors)
this.refs.nameForm.getForm().setErrors(errorObject)
}
},
_getErrorObject(errors) {
if (!errors) { errors = this.props.errors }
return errors ? ErrorObject.fromJSON(errors) : null
},
_onSubmit(e) {
e.preventDefault()
var form = this.state.form
form.validate(this.refs.form, (err, isValid) => {
if (isValid) {
this.context.router.transitionTo('name', {}, {}, {
method: 'POST',
body: form.data
})
}
})
},
render() {
return <div>
<h1 className="heading-large">Your name</h1>
<form action='name' method="POST" onSubmit={this._onSubmit} ref="form" autoComplete="off" noValidate={this.state.client}>
{this.state.form.render()}
<button type="submit" className="button">Next</button>
</form>
</div>
},
})
module.exports = NamePage
Here is NameTest.js :
//NameTest.js
var React = require('react')
var ReactAddons = require('react/addons')
var TestUtils = React.addons.TestUtils
var InputFieldItem = require('../../src/page/name/NamePage')
describe('Name page component', function(){
var renderedComponent;
before('render element', function() {
console.log("*** in before")
renderedComponent = TestUtils.renderIntoDocument(
<InputFieldItem />
);
});
it('Only First Name entered should display one error message', function() {
renderedComponent = TestUtils.renderIntoDocument(
<InputFieldItem />
);
var formElement = TestUtils.findRenderedDOMComponentWithTag(renderedComponent, 'form').getDOMNode()
var firstNameElement = TestUtils.scryRenderedDOMComponentsWithTag(renderedComponent, 'input')[0].getDOMNode()
var lastNameElement = TestUtils.scryRenderedDOMComponentsWithTag(renderedComponent, 'input')[1].getDOMNode()
var buttonElement = TestUtils.findRenderedDOMComponentWithTag(renderedComponent, 'button').getDOMNode()
TestUtils.Simulate.change(firstNameElement , {target: {value: 'Joe'}});
TestUtils.Simulate.submit(formElement)
var errorSpans = TestUtils.scryRenderedDOMComponentsWithClass(renderedComponent, 'error-message')
console.log("First name value is :|"+ firstNameElement.value + "|")
expect (errorSpans.length).to.equal(1)
})
});