How to pass data from +page.server.js to an underlying +page.svelte in Sveltekit - server

I have recently started learning Sveltekit and am working on a very basic project to practise. Here is the structure of the project:
|-routes/
| |-nextpage/
| └ +page.svelte
|+page.svelte
|+page.server.js
I got stuck trying to pass data from the +page.server.js to the +page.svelte located inside the nextpage/ route and I have no idea what to do.
In the main +page.svelte there is a component with a button that when pressed sends a FormData via POST request to the /results endpoint, triggering a server action called results within the +page.server.js. Then redirects to /nextpage.
Component in +page.svelte:
let myObject = {
//stuff
}
const handleSubmit = () => {
const formData = new FormData();
for(const name in myObject){
formData.append(name, myObject[name]);
}
let submit = fetch('?/results', {
method: 'POST',
body: formData
})
.finally(() => console.log("done"))
window.location = "/nextpage";
}
+page.server.js:
let myObject = {};
export const load = () => {
return {
myObject
}
}
export const actions = {
results: async({ request }) => {
const formData = await request.formData();
formData.forEach((value, key) => (myObject[key] = value));
console.log(myObject);
}
}
Now I would like to be able to show myObject in the +page.svelte in /nextpage, but the usual export let data does not work:
/nextpage +page.svelte:
<script>
export let data;
</script>
{data.myObject} //undefined`
What can I do? Thank you for your help.

OK guys, I guess all I needed was to use cookies. Since the object I was trying to pass between pages didn't need to be stored for longer than a page load, I don't think it would make sense to save it in a database. Instead, what did it in my case was to set a cookie with cookies.set('name', JSON.stringify(obj)); inside the action function in the main +page.server.js, and then get it back inside the load function of the /nextpage +page.server.js with const obj = cookies.get('name');. I'm not sure it was the cleanest way to do it, but it worked for me.

That does not work. Pages are fully separate, you cannot load data from one page into another.
If you want to share loaded data use a layout load function.

You can use +layout.js or sveltkit/stores.
sveltekit/stores are above the layout layer because they don't depend on the flow of the pages compared to the +layout.js layer which does depend.
cookies are processed locally.

Related

Update global state after RTK Query loads data

I've noticed a problem with splitting responsibilities in React components based on the fetched data using RTK Query.
Basically, I have two components like HomePage and NavigationComponent.
On HomePage I'd like to fetch the information about the user so that I can modify NavigationComponent accordingly.
What I do inside HomePage:
import { setNavigationMode } from "features/nav/navSlice";
export default function HomePage() {
const {data: user} = useGetUserDataQuery();
const dispatch = useAppDispatch();
const navMode = user ? "all-options" : "none";
dispatch(setNavigationMode(navMode)); // here I change the default Navigation mode
return <MainLayout>
<Navigation/>
<Content/>
<Footer/>
</MainLayout>;
}
The HomePage is a special Page when the NavigationComponent shouldn't display any options for the not logged in user.
Other pages presents additional Logo and Title on Nav.
React communicates:
Warning: Cannot update a component (NavComponent) while rendering a different component (HomePage). To locate the bad setState() call inside HomePage, follow the stack trace as described in https://reactjs.org/link/setstate-in-render
Not sure what is the right way to follow.
Whether the state should be changed in GetUser query after it is loaded - that doesn't seem to be legit.
problem is dispatch calls every render. Instead you can create a navigationSlice (if you don't have already) and use extraReducers for matching your authorization action like:
extraReducers: (builder) => {
builder.addMatcher(
usersApi.endpoints.login.matchFulfilled,
(state, { payload }) => {
if (payload.user) {
state.navigationMode = "all-options"
}
}
);
}
This way, state.navigationMode will only change when authorization changes
The solution was too obvious. The dispatch should be run in useEffect.
import { setNavigationMode } from "features/nav/navSlice";
export default function HomePage() {
const {data: user} = useGetUserDataQuery();
const dispatch = useAppDispatch();
const navMode = user ? "all-options" : "none";
// changed lines
useEffect( () => {
dispatch(setNavMode(navMode));
}, [navMode, dispatch]);
// /changed lines
return <MainLayout>
<Navigation/>
<Content/>
<Footer/>
</MainLayout>;
}
Thank you #papa-xvii for the hint with changing the navMode after user login. That solves the second problem I had.
However I cannot accept the answer as it does not solve the problem I described above.

How to use the nextHandler functionality as shown in the Infinite Ajax Scroll JSON example

I’m hoping to be able to use Infinite Ajax Scroll for a project I’m working on.
I’ve been looking at the Infinite Scroll JSON example here (https://infiniteajaxscroll.com/examples/json/) and I’m finding it difficult to understand how it works. I was wondering if there is any further documentation or code examples on how to use a JS or jQuery handler as shown in the example.
Ultimately what I want to do is load my container "items" using my own ajax function and then have Infinite Ajax Scroll display them. I want to do this because my container "items" are not located at URLs, but are saved as Wordpress transients.
Any help I could get with this would be very much appreciated.
Thanks,
David.
Thank you for your question. The docs on using the nextHandler could indeed use improvement. Regardless, I'll try to explain how it works.
Normally IAS uses a selector to find the url of the next page. Then it loads the page and extracts the elements and appends them to the DOM. If you use the nextHandler, you will completely bypass this behavior. That means you will have to fetch data (in this case JSON) yourself and also insert new elements in the DOM.
Here is an example with comments to explain what it does.
First, let's assume our movie(1..n).json has the following format:
[
{
Title: 'item1',
Plot: 'description1'
}, {
Title: 'item2',
Plot: 'description2'
}
]
Now the implementation of the nextHandler:
import InfiniteAjaxScroll from "#webcreate/infinite-ajax-scroll";
function nextHandler(pageIndex) {
// we use the fetch api to load the next page. Any http client library will do, like axios.
return fetch("./static/movies"+pageIndex+".json")
// use the response as json
.then((response) => response.json())
// process the actual json data
.then((jsonData) => {
// create an array to store our html elements in memory
let elements = [];
// we loop over the items in our json and create an html element for each item
jsonData.forEach(function (item) {
const template = `<div class="item">
<h1>${item.Title}</h1>
<p>${item.Plot}</p>
</div>`;
const element = document.createElement("div");
element.innerHTML = template.trim();
elements.push(element.firstChild);
});
// now use IAS's append method to insert the elements
// it's import that we return the append result, as it's an promise
return this.append(elements);
})
// page 3 returns a 404, returning false here indicates there are no more pages to load
.catch(() => false);
}
window.ias = new InfiniteAjaxScroll(".container", {
item: ".item",
next: nextHandler,
pagination: false
});
I also prepared an interactive demo on Codesandbox:
https://codesandbox.io/s/serene-einstein-f73em

Express [413 too large] with QuillJS image

I am trying to use QuillJS to let the user write a rich text, and then store it as JSON to display later on. There are 2 of these rich text areas in a single form, and may include images. QuillJS encodes images as base64 strings, and my POST request results in 413 by Express.
I have tried to change the limits by adding express json parameters, even trying extreme numbers.
// app.js
//----------------------------------------------------
// Middlewares
//----------------------------------------------------
app.use(express.json({limit: '2000mb'}));
app.use(express.urlencoded({extended: true, limit:'2000mb'}));
Even this did not help and I think it is not logical to let these parameters with such values.
I tried with json and urlencoded enctypes. When I tried to post with multipart/form, req.body was empty.
// My html page (pugJS)
form(enctype='application/x-www-form-urlencoded', action='/editor/page',
method='POST', onsubmit='return addContent()')
.form-control
label Content-1
div#toolbar
div#editor
input#content(name='content', type='text', hidden)
addContent() function that runs before form submit simply changes input#content's value with JSON.stringify(#editor.getContents())
I want to be able to store two quill content in a single database row, to display later.
A better approach to this would be to overwrite the image upload function and then save the image in Amazon S3 or some cloud server. Then you paste it inside the editor as <img src="http://uploaded-image-url"> This would solve your problem of maximum memory issue.
I fixed my problem few hours before #argo mentioned and I did it that way. So I wanted to post little bit of detail to the solution. I have been also guided by a github issue but can't seem to find the link again, in case I find it I will edit the post and add it.
// Quill - EN content
var quillEn = new Quill('#editor-en', {
modules: {
toolbar: toolbarOptions
},
theme: 'snow'
});
// set custom image handler
quillEn.getModule('toolbar').addHandler('image', () => {
selectLocalImage(quillEn);
});
// create fake input to upload image to quill
function selectLocalImage(editor) {
const input = document.createElement('input');
input.setAttribute('type', 'file');
input.setAttribute('accept', 'image/png, image/jpeg')
input.click();
// Listen upload local image and save to server
input.onchange = () => {
const file = input.files[0];
saveImageToServer(editor, file);
};
}
// upload image to server
function saveImageToServer(editor, file) {
const fd = new FormData();
fd.append('image', file);
const xhr = new XMLHttpRequest();
xhr.open('POST', '/api/page/upload_image', true);
xhr.onload = () => {
if (xhr.status === 200) {
// this is callback data: url
const url = JSON.parse(xhr.responseText).data;
insertToEditor(editor, url);
}
};
xhr.send(fd);
}
// manipulate quill to replace b64 image with uploaded image
function insertToEditor(editor, url) {
// push image url to rich editor.
const range = editor.getSelection();
editor.insertEmbed(range.index, 'image', url.toString());
}
In the backend where you POST image, you must return json as { data: FullUrlToImg } with 200 response, if you want to change your status to 201 or something else, don't forget to update it in saveImageToServer function.
So to summarize, you set custom image handler for your quill editor, you post the image to server as soon as user chooses to insert, then you replace the URL with your uploaded image in the editor.
Thanks.

How to download mongo collections as file using iron-router (and ground-db)? [duplicate]

I'm playing with the idea of making a completely JavaScript-based zip/unzip utility that anyone can access from a browser. They can just drag their zip directly into the browser and it'll let them download all the files within. They can also create new zip files by dragging individual files in.
I know it'd be better to do it serverside, but this project is just for a bit of fun.
Dragging files into the browser should be easy enough if I take advantage of the various methods available. (Gmail style)
Encoding/decoding should hopefully be fine. I've seen some as3 zip libraries so I'm sure I should be fine with that.
My issue is downloading the files at the end.
window.location = 'data:jpg/image;base64,/9j/4AAQSkZJR....'
this works fine in Firefox but not in Chrome.
I can embed the files as images just fine in chrome using <img src="data:jpg/image;ba.." />, but the files won't necessarily be images. They could be any format.
Can anyone think of another solution or some kind of workaround?
If you also want to give a suggested name to the file (instead of the default 'download') you can use the following in Chrome, Firefox and some IE versions:
function downloadURI(uri, name) {
var link = document.createElement("a");
link.download = name;
link.href = uri;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
delete link;
}
And the following example shows it's use:
downloadURI("data:text/html,HelloWorld!", "helloWorld.txt");
function download(dataurl, filename) {
const link = document.createElement("a");
link.href = dataurl;
link.download = filename;
link.click();
}
download("data:text/html,HelloWorld!", "helloWorld.txt");
or:
function download(url, filename) {
fetch(url)
.then(response => response.blob())
.then(blob => {
const link = document.createElement("a");
link.href = URL.createObjectURL(blob);
link.download = filename;
link.click();
})
.catch(console.error);
}
download("https://get.geojs.io/v1/ip/geo.json","geoip.json")
download("data:text/html,HelloWorld!", "helloWorld.txt");
Ideas:
Try a <a href="data:...." target="_blank"> (Untested)
Use downloadify instead of data URLs (would work for IE as well)
Want to share my experience and help someone stuck on the downloads not working in Firefox and updated answer to 2014.
The below snippet will work in both firefox and chrome and it will accept a filename:
// Construct the <a> element
var link = document.createElement("a");
link.download = thefilename;
// Construct the uri
var uri = 'data:text/csv;charset=utf-8;base64,' + someb64data
link.href = uri;
document.body.appendChild(link);
link.click();
// Cleanup the DOM
document.body.removeChild(link);
Here is a pure JavaScript solution I tested working in Firefox and Chrome but not in Internet Explorer:
function downloadDataUrlFromJavascript(filename, dataUrl) {
// Construct the 'a' element
var link = document.createElement("a");
link.download = filename;
link.target = "_blank";
// Construct the URI
link.href = dataUrl;
document.body.appendChild(link);
link.click();
// Cleanup the DOM
document.body.removeChild(link);
delete link;
}
Cross-browser solutions found up until now:
downloadify -> Requires Flash
databounce -> Tested in IE 10 and 11, and doesn't work for me. Requires a servlet and some customization. (Incorrectly detects navigator. I had to set IE in compatibility mode to test, default charset in servlet, JavaScript options object with correct servlet path for absolute paths...) For non-IE browsers, it opens the file in the same window.
download.js -> http://danml.com/download.html Another library similar but not tested. Claims to be pure JavaScript, not requiring servlet nor Flash, but doesn't work on IE <= 9.
There are several solutions but they depend on HTML5 and haven't been implemented completely in some browsers yet. Examples below were tested in Chrome and Firefox (partly works).
Canvas example with save to file support. Just set your document.location.href to the data URI.
Anchor download example. It uses <a href="your-data-uri" download="filename.txt"> to specify file name.
Combining answers from #owencm and #Chazt3n, this function will allow download of text from IE11, Firefox, and Chrome. (Sorry, I don't have access to Safari or Opera, but please add a comment if you try and it works.)
initiate_user_download = function(file_name, mime_type, text) {
// Anything but IE works here
if (undefined === window.navigator.msSaveOrOpenBlob) {
var e = document.createElement('a');
var href = 'data:' + mime_type + ';charset=utf-8,' + encodeURIComponent(text);
e.setAttribute('href', href);
e.setAttribute('download', file_name);
document.body.appendChild(e);
e.click();
document.body.removeChild(e);
}
// IE-specific code
else {
var charCodeArr = new Array(text.length);
for (var i = 0; i < text.length; ++i) {
var charCode = text.charCodeAt(i);
charCodeArr[i] = charCode;
}
var blob = new Blob([new Uint8Array(charCodeArr)], {type: mime_type});
window.navigator.msSaveOrOpenBlob(blob, file_name);
}
}
// Example:
initiate_user_download('data.csv', 'text/csv', 'Sample,Data,Here\n1,2,3\n');
This can be solved 100% entirely with HTML alone. Just set the href attribute to "data:(mimetypeheader),(url)". For instance...
<a
href="data:video/mp4,http://www.example.com/video.mp4"
target="_blank"
download="video.mp4"
>Download Video</a>
Working example: JSFiddle Demo.
Because we use a Data URL, we are allowed to set the mimetype which indicates the type of data to download. Documentation:
Data URLs are composed of four parts: a prefix (data:), a MIME type indicating the type of data, an optional base64 token if non-textual, and the data itself. (Source: MDN Web Docs: Data URLs.)
Components:
<a ...> : The link tag.
href="data:video/mp4,http://www.example.com/video.mp4" : Here we are setting the link to the a data: with a header preconfigured to video/mp4. This is followed by the header mimetype. I.E., for a .txt file, it would would be text/plain. And then a comma separates it from the link we want to download.
target="_blank" : This indicates a new tab should be opened, it's not essential, but it helps guide the browser to the desired behavior.
download: This is the name of the file you're downloading.
If you only need to actually have a download action, like if you bind it to some button that will generate the URL on the fly when clicked (in Vue or React for example), you can do something as easy as this:
const link = document.createElement('a')
link.href = url
link.click()
In my case, the file is already properly named but you can set it thanks to filename if needed.
For anyone having issues in IE:
dataURItoBlob = function(dataURI) {
var binary = atob(dataURI.split(',')[1]);
var array = [];
for(var i = 0; i < binary.length; i++) {
array.push(binary.charCodeAt(i));
}
return new Blob([new Uint8Array(array)], {type: 'image/png'});
}
var blob = dataURItoBlob(uri);
window.navigator.msSaveOrOpenBlob(blob, "my-image.png");
This code was originally provided by #Yetti on this answer (separate question).
Your problem essentially boils down to "not all browsers will support this".
You could try a workaround and serve the unzipped files from a Flash object, but then you'd lose the JS-only purity (anyway, I'm not sure whether you currently can "drag files into browser" without some sort of Flash workaround - is that a HTML5 feature maybe?)
Coming late to the party, if you'd like to use a function without using the DOM, here it goes, since the DOM might not even be available for whatever reason.
It should be applicable in any Browser which has the fetch API.
Just test it here:
// declare the function
function downloadAsDataURL (url) {
return new Promise((resolve, reject) => {
fetch(url)
.then(res => res.blob())
.then(blob => {
const reader = new FileReader()
reader.readAsDataURL(blob)
reader.onloadend = () => resolve(reader.result)
reader.onerror = err => reject(err)
})
.catch(err => reject(err))
})
}
// simply use it like this
downloadAsDataURL ('https://cdn-icons-png.flaticon.com/512/3404/3404134.png')
.then((res) => {
console.log(res)
})
.catch((err) => {
console.error(err)
})
export const downloadAs = async (url: string, name: string) => {
const blob = await axios.get(url, {
headers: {
'Content-Type': 'application/octet-stream',
},
responseType: 'blob',
});
const a = document.createElement('a');
const href = window.URL.createObjectURL(blob.data);
a.href = href;
a.download = name;
a.click();
};
You can use a clean code solution, inform your url in a constant, and set it as param of open method instead in object window.
const url = "file url here"
window.open(url)

Extjs file upload progress

I have seen form file upload example in ExtJS4 and i need customize progress of the file upload.
I see waitMsg config property, but i don't want use that and i don't want use extjs 3rd party plugins.
So, how i can get simply current upload progress from upload form in extjs?
The waitMsg uses a message box with an infinitely auto-updating progress bar. So you can't just create a progressbar that displays the current upload.
You could create your own Ext.ProgressBar and estimate the upload time and when its done you set it to the max value. But I guess you don't want that.
To answer your question: You cannot simply track the current upload progress.
If you really need this user experience you can try a 3rd party component.
To quote the docs:
File uploads are not performed using normal "Ajax" techniques, that is
they are not performed using XMLHttpRequests. Instead the form is
submitted in the standard manner with the DOM element
temporarily modified to have its target set to refer to a dynamically
generated, hidden which is inserted into the document but
removed after the return data has been gathered.
To show real progress you can use onprogress callback of XMLHttpRequest:
Ext.override(Ext.data.request.Ajax, {
openRequest: function (options) {
var xhr = this.callParent(arguments);
if (options.progress) {
xhr.upload.onprogress = options.progress;
}
return xhr;
}
});
Then use like here:
Ext.Ajax.request({
url: '/upload/files',
rawData: data,
headers: { 'Content-Type': null }, //to use content type of FormData
progress: function (e) {
console.log('progress', e.loaded / e.total);
}
});
See online demo here.
buttons: [{
text: 'Upload',
handler: function () {
var form = this.up('form').getForm();
if (form.isValid()) {
form.submit({
url: '/upload/file',
waitMsg: 'Uploading your file...',
success: function (f, a) {
var result = a.result,
data = result.data,
name = data.name,
size = data.size,
message = Ext.String.format('<b>Message:</b> {0}<br>' +
'<b>FileName:</b> {1}<br>' +
'<b>FileSize:</b> {2} bytes',
result.msg, name, size);
Ext.Msg.alert('Success', message);
},
failure: function (f, a) {
Ext.Msg.alert('Failure', a.result.msg);
}
});
}
}
}]
Live demo with progress window is here