How to use the same form for Insert's and Update's?

Its seems to me, that the proper way of doing forms in Velo is to make a form with Write-only dataset, that Inserts data with a submit-button, and then have another form with Read/Write-dataset that can update the data, also with a submit-button.
I can certainly do that, and there is a good example on that like “Building Your Own Members Area”

It just bugs me, that for every table I want the end-user to populate, I have to make 2 forms (1 for inserts and 1 for updates), often with the same amount of fields and ordered the exact same way. If I want to expand my table with an extra field later on, I also have to update two forms instead of just one.

What I want is to use the same form for inserting data and for updating data, so I don’t have to maintain 2 forms per table instead of just 1.

I have thought of maybe always using Read/Write-dataset, and then (before the form is displayed) populating the form with blank data if none exist for the user, so that we are essentially always in “update”-mode, but it still seems like a bit of a clumsy way. My question is, is there a better/smarter/easier way of having 1 form do both insert and update of data?

I do not really know, if it is possible to connect 2-datasets to one form with the given wix-editor-options (because i never use them).

But always everything is possible when you use some Velo-Code.

Look here…
https://www.wix.com/velo/reference/wix-data/update

and here…
https://www.wix.com/velo/reference/wix-data/insert

or even this one…
https://www.wix.com/velo/reference/wix-data/save

I prefer —> SAVE! (some kind a mix of both).

Hi @lars76madsen :raised_hand_with_fingers_splayed:

You cannot do that with datasets, your only friend here will be some Velo code.

For instance, when a member visits the page, call a backend function to check if the user has already submitted any data or not, if not, do nothing, if yes, then extract their data, return it to the frontend and populate the form.

Here’s an example.

This code will run once the page is opened.

// A backend module called form.jsw;
import wixData from 'wix-data';
import { currentUser } from 'wix-users-backend';

export function getDetails() {
    /* we're checking if the logged in user has already filled the form, and
    if he/she did, return their data. Reject the promise if the user is not
    logged in */
    
    if (currentUser.loggedIn) {
        return wixData.query('UserDetails').eq('_owner', currentUser.id).find()
        .then((results) => {
            if (results.length > 0) {
                let item = results.items[0]; // The details of the current user
                
                /* You can return the whole user details, or you can return
                specific fields, like name, data of birth, picture and so on */
                let details = {
                    _id: item._id, // We need this when updating the form
                    name: item.name,
                    dateOfBirth: item.dateOfBirth,
                    picture: item.profilePic
                }
                
                // Prepare the response
                let response = {
                    type: 'success',
                    details: details
                }
                
                // Return the response.
                return Promise.resolve(response);
            } else {
                let response = {
                    type: 'not found',
                    code: 404,
                    message: "User hasn't filled the form yet!"
                }
                return Promise.resolve(response);
            }
        })
    } else {
        // Reject if the user is not logged in
        return Promise.reject({type: 'error', message: "You're not logged in", code: 500})
    }
}

// This the form page
// Import our function "getDetails" from a backend web module "form.jsw".
import { getDetails } from 'backend/form.jsw';

let details; // A global variable to store the visitor's details

$w.onReady(async() => {
    await getDetails().then((result) => {
        if (result.type === 'success') {
            // Get the details from the backend
            details = result.details;
            
            // populate the fields
            $w('#name').value = details.name || '';
            $w('#datePicker').value = details.dateOfBirth || null;
            if (details.picture) { $w('#profile').src = details.picture }
        } else if (result.type === 'not found') {
            // Prompt the member to enter his/her data
            $w('#message').text = "Please enter your details here";
        }
    }).catch(err => {
        // Display the error message returned from the backend
        $w('#errorMessage').text = err.message;
    })
})

Now to code the save button, we need to add another function to the backend module to save or update the data

// A backend module called form.jsw;

export function saveData(details) {
    /* Check if the details are new or not by checking if they have a valid "_id"
    If the details are new, just insert them in the database, if not we need to get
    the old details and update them */
    
    if (typeof details.id === 'string') {
        // The details have an ID;
        return wixData.query('UserDetails').eq('_id', details.id).find()
        .then(results => {
            // If there are no matching results, that mean the _id is invalid
            if (results.length > 0) {
                let item = results.items[0];
                
                // Updating the specific fields
                item.name = details.name;
                item.dateOfBirth = details.dateOfBirth;
                item.picture = details.picture;
                
                // Update the details in the database.
                return wixData.update('UserDetails', item).then(() => {
                    let response = {
                        type: 'success',
                        operation: 'update'                        
                    }
                    
                    // Resolve the promise
                    return Promise.resolve(response);
                }).catch(err => {
                    let response = {
                        type: 'error',
                        operation: 'update',
                        level: 'wixData update',
                        error: {
                            raw: err,
                            message: `An error occurred! - Error: ${err}`;
                        }
                    }
                    return Promise.reject(response);
                })
            }
        }).catch(err => {
            if (err.type === 'error') {
                return Promise.reject(err);
            } else {
                let response = {
                    type: 'error',
                    operation: 'update',
                    level: 'wixData query',
                    error: {
                        raw: err,
                        message: `An error occurred! - Error: ${err}`;
                    }
                }
                return Promise.reject(response);
            }            
        })
    } else {
        return wixData.insert('UserDetails', details).then((item) => {
            let response = {
                type: 'success',
                operation: 'new',
                id: item._id, // We need to return the ID to update the form
            }
            return Promise.resolve(response);
        }).catch(err => {
            let response = {
                type: 'error',
                operation: 'insert',
                level: 'wixData insert',
                error: {
                    raw: err,
                    message: `An error occurred! - Error: ${err}`;
                }
            }
            return Promise.reject(response);
        })
    }
}

Now on the frontend page where you have your form, we need to import the new function, and call it when we want to save the form data.

// This the form page
// Import our function "saveData" from a backend web module "form.jsw".
import { saveData } from 'backend/form.jsw';

// Inside the page's onReady function, add the following event handler
$w('#saveButton').onClick(() => {
    // Disable the button until the operation is done.
    $w('#saveButton').disable().then(async() => {
        // Declare a variable for the new details
        let newDetails = {
            name: $w('#name').value,
            dateOfBirth: $w('#datePicker').value,
            picture: $w('#picture').src,
        }
        
        // Check to see if the values are new or not, and add the ID if it's not
        if(details) {
            if (typeof details._id === 'string') { newDetails.id = details._id }
        }        
        
        // Call the backend function
        await saveData(newDetails).then((result) => {
            // Update all fields
            details = newDetails
            
            if (result.operation === 'update') {
                // Update the details with the ID returned from the backend
                details._id = result.id;
            }
            
            // Enable the button again
            $w('#saveButton').enable().then(() => {
                // Show a success message
                $w('#successMessage').text = "Data was successfully saved!";
                $w('#successMessage').show();
                
                // Hide the message after a short time
                setTimeout(() => {
                    $w('#successMessage').hide();
                }, 3500)
            })
        }).catch(err => {
            // Show an error message
            $w('#errorMessage').text = err.error.message;
            
            // log the error to the console
            console.error(err);
        })
    })
})

Hope this helps~!
Ahmad

Ok, i hope the post-opener will ever understand all these code :grin:.
And you told me, don’t be scaryfied! :joy: What the hell …:joy:

Ahmad, try to keep it more simple, when you explain someone who perhaps do even not see difference between dataset and data-collections+code.

Thanks Ahmad for your detailed answer. There is a lot to digest but I will try it out tomorrow. From what I can decipher, I think it looks like what I need and will give me insert and update functionality in the same form which would ben truly great.
There is a lot to learn here though and especially the syntax with Promises is very new to me.

@russian-dima I lost myself here :joy: My apologies, I just didn’t find any simpler way to explain it, if you have a simpler one, please, I would love to use a simpler method.

I also hope so :joy: I put into account that others might see the post, and can benefit from, so, I complete professional code - like the one I’d do as a job - was needed :ok_hand:

@lars76madsen Sorry if it seems too complicated, but honestly, this is the easiest way, I tried to put comments to explain everything as much as I could to make it easier to understand, don’t worry though, we’re here to help :wink: So feel free to tag me whenever you want.

@ahmadnasriya Yeah, i do not understand how you could write that code, in such a short time (RESPECT!) and it has good description and tips, too. But this one is sure nothing for beginner.

Question: Why do you use BACK-END? (What is it for? Security? Something else?)

Edit: And no, i can’t create a more simple one, surely not in that short time :joy: NEVER!

@russian-dima Okay, okay … I admit it, it’s not for beginners, but it’s the only answer I have.

You’re right, the main reason is, you guessed it, security, imagine that the query was on the frontend, that means anyone can use any email to access the account details of that email, since the query will return all the details, while as in my example, the only thing you can get from the backend is whether the account is the public details (you don’t want to return sensitive data like phone number, address, credit card numbers, passwords, files) or really anything that is not needed. ALWAYS , only return the needed fields, and avoid exposing unnecessary fields to the frontend as much as possible. And yes, datasets return all the fields, so if you mean business, do NOT use them.

The second reason is, performance, most devices are slow, especially mobile devices, so when you move the work to the backend, you reduce the overhead load on the client CPU, increasing performance.

Also, using the backend will always return the up to date data, for example, products reviews, instead of updating the reviews average ratings on the frontend, only pass the new rating to the backend, add it there, calculate the new average at THAT specific moment and return the updated results. Why? Let’s assume that you opened the page 2 hours ago, in this period, some users might have left reviews, and obviously the rating has changed, so if you used the rating in the frontend to calculate the new rating, it’ll not be accurate, you’ll be missing other reviews, that’s why always use the backend, only run the functions on frontend that are not important, and doesn’t require sensitive data. Remember, frontend code can be exploited by hackers to steel data or harm your website.

@ahmadnasriya I had a chance to try out the code, and I think I have most of it in place but I don’t understand what “_id” is in the UserDetails table. Is that a new field like name, dateOfBirth and picture? Or is it some way of referencing the automatically created field called ID?

This part I don’t get. Don’t I somehow have to create the “id”-field for my newDetails or am I missing something obvious?

I tried to add a record manually trough the collection editor, and it turns out that the code works fine if it needs to update an existing record, but it fails if there is no record present.

It is very likely that I have made a mistake though. I am trying to read up on the save() funtion but I have a lot to learn.

The “_id” field is an auto-generated field when you insert a new item into a collection, and all items have this fields, regardless of the collection.

The " newDetails " object is used to store the input fields’ values (as normal), and the last line is used to check if the global variable " details " has an " _id " value or not, and if it is, add that ID to the " newDetails " object, because without it, a new entry will be created instead of updating it.

What did you change? Can you tell which line the error occurred on? Maybe some screenshots or a link to your site might help.

Link is here https://lars76madsen.wixsite.com/crossroads

On line 53 , I changed this section from this

// Check to see if the values are new or not, and add the ID if it's not
if (typeof details._id === 'string') { newDetails.id = details._id }

To this:

// Check to see if the values are new or not, and add the ID if it's not
if(details) {
    if (typeof details._id === 'string') { newDetails.id = details._id }
}  

Also, only use one $w.onReady( ) function instead of two, move the body of one of them to the other one’s body.

Oh! So if there is no record in the DB for the current user, then there is also no details object?! Is that correct? I thought it would just be created with empty values.

The form is working now!

Well the value of the datepicker is getting saved to the database but not loaded if i go from the editor into preview mode. Thats a bit strange, but the concept of creating and updating the same database table from just 1 form is working and that’s really great @ahmadnasriya

Yes, there is no details object, but once the user submit their data, the details object will be updated with the newDetails and with the " _id " returned from the backend when the entry was saved.

Regarding the date picker value, I had a typo on the backend module (line 21).

let details = {
    _id: item._id, // We need this when updating the form
    name: item.name,
    dateOfBirth: item.dateOfBirth, // Line 21
    picture: item.profilePic
}

I wrote it dataOfBirth instead of dateOfBirth when I was typing quickly. Correct the property name and the date picker will work again.

Let me know how it goes.

@ahmadnasriya you are my Wix-hero!!

It works now. I didn’t catch that. I changed it and it is working now.

I will use this example to experiment further. There is so much good stuff in here to learn from. How to call backend-functions. Initialize input fields. Updating database. Creating objects etc.

This has been such a great learning experience Ahmad, so thank you so much for taking the time to help me out!