אין כמעט אתר שאינו מכיל טפסים. טפסים משתמשים אותנו למגוון צרכים – איסוף לידים, הרשמה לרשימת תפוצה, תמיכה טכנית, יצירת קשר, הרשמה לאירועים, ועוד. שליחת אימייל היא הדבר הבסיסי והמובן מאליו שכל טופס מבצע, אך מה אם נרצה שפעולות נוספות יקרו בעת שליחת הטופס?
בעבר כתבתי על חיבור טופס אלמנטור למערכת הדיוור הפופולרית Smoove, ובמאמר הזה אסקור, לראשונה בעברית, אפשרות נוחה לחיבור טופסי אלמנטור לגיליון שניצור ב Google Sheets (נוחה יחסית. תידרשו לבצע "העתק-הדבק" לקוד). לאלמנטור פרו יש וידג'ט טופס, אשר בשנים האחרונות תופס בהדרגה מקום מרכזי מאוד באתרי אינטרנט. הטופס של אלמנטור גמיש, נוח לעיצוב ומגיע "מהקופסה" עם שלל אפשרויות חזקות מאוד להתממשקות.
עם זאת, טפסי אלמנטור לא כוללים התממשקות מובנית ל Google Sheets, למרות שבעצם זה די הגיוני שנרצה לאסוף יחד פניות רבות ולא רק לטפל בהן באימייל שלנו. השימוש ב-Google Sheets מאפשר לנו לנתח, לטפל ולסנן פניות רבות בקלות, כאשר כל אחת מהן היא שורה בטבלה. יתרון נוסף הוא שמירה של נתוני הטופס גם במקרה בו הטופס לא נשלח אל כתובת המייל הרצוייה עקב תקלה.
פעמים רבות מטופל הצורך הנפוץ הזה באמצעות שירות התממשקויות כגון Zapier או Integromat, אך במאמר זה אני מעוניין להציע דרך שאינה כרוכה בשירות צד שלישי לצורך החיבור הזה, וניתן לעשות אותה בקלות יחסית דרך הממשקים של טפסי אלמנטור פרו ושל Google Sheets. ההתממשקות בהדרכה זו מתבצעת באמצעות קוד שכתב והעלה לגיטהאב שלו אוהד רז, מתכנת מהצוות של אלמנטור ואחד האנשים המוכשרים שפגשתי.
מהם היתרונות בחיבור ללא הסתמכות על גורם שלישי?
- אין עלויות. זה לגמרי חינם ואין מגבלה על כמות השליחות היומית או החודשית.
- פחות גורמים מעורבים – פחות סיכוי לתקלות ושיבושים, אי זמינות של שירות וכו'.
יוצאים לדרך – מחברים את הטופס לגליון Google Sheets
- יוצרים גיליון חדש ב Google Sheets.
- יוצרים בגיליון Web App שיאפשר את משיכת הנתונים מהטפסים באתר:
- בתפריט של הגיליון לוחצים על תוספים > Apps Script.
- נפתח לנו חלון חדש ובו אזור עריכת קוד עם פונקציה ריקה. נמחק את הפונקציה הקיימת ונדביק את הקוד הבא:
/** * Google app-script to utilise Elementor Pro From webhook. * * In order to enable this script, follow these steps: * * From your Google Sheet, from the "Exstensions" menu select "App Script"… * Paste the script from this gist into the script code editor and hit Save. * From the "Deploy" menu, select Deploy as web app… * Choose to execute the app as yourself, and allow Anyone, even anonymous to execute the script. (Note: depending on your Google Apps instance, this option may not be available. You will need to contact your Google Apps administrator, or else use a Gmail account.) * Now click Deploy. You may be asked to review permissions now. * The URL that you get will be the webhook that you can use in your elementor form, You can test this webhook in your browser first by pasting it. * It will say "Yepp this is the webhook URL, request received". * Last all you have to do is set up and Elementor Pro Form with a form name and Webhook action pointing to the URL from above. * * Update: 09/06/2022 * - Name the sheet: you can now add a field (could be hidden) to your form labeled "e_gs_SheetName" and set the defualt value to the name of the sheet you want to use. * - Set the Order: you can now add a form field (hidden) labeled "e_gs_order" and set its defualt value to the names of the columns you want to seperated by comma in the order you want, any other colum not in that list will be added after. * - Exclude Columns: you can now add a field (could be hidden) labeled "e_gs_exclude" and set its value to the names of the columns you want to exclude seperated by comma. */ // Change to true to enable email notifications let emailNotification = false; let emailAddress = "Change_to_your_Email"; // DO NOT EDIT THESE NEXT PARAMS let isNewSheet = false; let postedData = []; const EXCLUDE_PROPERTY = 'e_gs_exclude'; const ORDER_PROPERTY = 'e_gs_order'; const SHEET_NAME_PROPERTY = 'e_gs_SheetName'; /** * this is a function that fires when the webapp receives a GET request * Not used but required. */ function doGet( e ) { return HtmlService.createHtmlOutput( "Yepp this is the webhook URL, request received" ); } // Webhook Receiver - triggered with form webhook to published App URL. function doPost( e ) { let params = JSON.stringify( e.parameter ); params = JSON.parse( params ); postedData = params; insertToSheet( params ); // HTTP Response return HtmlService.createHtmlOutput( "post request received" ); } /** * flattenObject * Flattens a nested object for easier use with a spreadsheet * @param ob * @returns {{}} */ const flattenObject = ( ob ) => { let toReturn = {}; for ( let i in ob ) { if ( ! ob.hasOwnProperty( i ) ) { continue; } if ( ( typeof ob[ i ] ) !== 'object' ) { toReturn[ i ] = ob[ i ]; continue; } let flatObject = flattenObject( ob[ i ] ); for ( let x in flatObject ) { if ( ! flatObject.hasOwnProperty( x ) ) { continue; } toReturn[ i + '.' + x ] = flatObject[ x ]; } } return toReturn; } /** * getHeaders * normalize headers * @param formSheet * @param keys * @returns {*[]} */ const getHeaders = ( formSheet, keys ) => { let headers = []; // retrieve existing headers if ( ! isNewSheet ) { headers = formSheet.getRange( 1, 1, 1, formSheet.getLastColumn() ).getValues()[0]; } const newHeaders = keys.filter( h => ! headers.includes( h ) ); headers = [ ...headers, ...newHeaders ]; // maybe set order headers = getColumnsOrder( headers ); // maybe exclude headers headers = excludeColumns( headers ); // filter out control columns headers = headers.filter( header => ! [ EXCLUDE_PROPERTY, ORDER_PROPERTY, SHEET_NAME_PROPERTY ].includes( header ) ); return headers; }; /** * getValues * normalize values * @param headers * @param flat * @returns {*[]} */ const getValues = ( headers, flat ) => { const values = []; // push values based on headers headers.forEach( ( h ) => values.push( flat[ h ] ) ); return values; } /** * insertRowData * inserts values to a given sheet at a given row * @param sheet * @param row * @param values * @param bold */ const insertRowData = ( sheet, row, values, bold = false ) => { const currentRow = sheet.getRange( row, 1, 1, values.length ); currentRow.setValues( [ values ] ) .setFontWeight( bold ? "bold" : "normal" ) .setHorizontalAlignment( "center" ); } /** * setHeaders * Insert headers * @param sheet * @param values */ const setHeaders = ( sheet, values ) => insertRowData( sheet, 1, values, true ); /** * setValues * Insert Data into Sheet * @param sheet * @param values */ const setValues = ( sheet, values ) => { const lastRow = Math.max( sheet.getLastRow(), 1 ); sheet.insertRowAfter( lastRow ); insertRowData( sheet, lastRow + 1, values ); } /** * getFormSheet * Find or create sheet for form * @param sheetName * @returns Sheet */ const getFormSheet = ( sheetName ) => { const activeSheet = SpreadsheetApp.getActiveSpreadsheet(); // create sheet if needed if ( activeSheet.getSheetByName( sheetName ) == null ) { const formSheet = activeSheet.insertSheet(); formSheet.setName( sheetName ); isNewSheet = true; } return activeSheet.getSheetByName( sheetName ); } /** * insertToSheet * magic function where it all happens * @param data */ const insertToSheet = ( data ) => { const flat = flattenObject( data ), keys = Object.keys( flat ), formSheet = getFormSheet( getSheetName( data ) ), headers = getHeaders( formSheet, keys ), values = getValues( headers, flat ); setHeaders( formSheet, headers ); setValues( formSheet, values ); if ( emailNotification ) { sendNotification( data, getSheetURL() ); } } /** * getSheetName * get sheet name based on form field named "e_gs_SheetName" if exists or used form name * @param data * @returns string */ const getSheetName = ( data ) => data[SHEET_NAME_PROPERTY] || data["form_name"]; /** * getSheetURL * get sheet url as string * @returns string */ const getSheetURL = () => SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getUrl(); /** * stringToArray * split and trim comma seperated string to array * @param str * @returns {*} */ const stringToArray = ( str ) => str.split( "," ).map( el => el.trim() ); /** * getColumnsOrder * used to set the columns order, set this by adding a form field (hidden) named "e_gs_order" * and set its value to the names of the columns you want to seperated by comma in the order you want, * any other colum not in that field will be added after * @param data * @param headers * @returns {*} */ const getColumnsOrder = ( headers ) => { if ( ! postedData[ORDER_PROPERTY] ) { return headers; } let sortingArr = stringToArray( postedData[ORDER_PROPERTY] ); // filter out non existing headers sortingArr = sortingArr.filter( h => headers.includes( h ) ); // filterout sorted headers headers = headers.filter( h => ! sortingArr.includes( h ) ); return [ ...sortingArr, ...headers ]; } /** * excludeColumns * used to exclude columns, set this by adding a form field (hidden) named "e_gs_exclude" * and set its value to the names of the columns you want to exclude seperated by comma * @param data * @param headers * @returns {*} */ const excludeColumns = ( headers ) => { if ( ! postedData[EXCLUDE_PROPERTY] ) { return headers; } const columnsToExclude = stringToArray( postedData[EXCLUDE_PROPERTY] ); return headers.filter( header => ! columnsToExclude.includes( header ) ); } /** * sendNotification * send email notification if enabled * @param data * @param url */ const sendNotification = ( data, url ) => { MailApp.sendEmail( emailAddress, "A new Elementor Pro Forms submission has been inserted to your sheet", // mail subject `A new submission has been received via ${data['form_name']} form and inserted into your Google sheet at: ${url}`, //mail body { name: 'Automatic Emailer Script' } ); };
- אם נרצה לקבל דיווח על כל שורה שנוספה לגיליון שלנו (בלי קשר לאימייל שנוכל להגדיר שישלח דרך הטופס באלמנטור) נערוך את שורה 18 – נחליף את
false
ב-true
. נערוך את שורה 19 – נחליף את Change_to_your_Email בכתובת האימייל הרלוונטית. מלבד זה אין מה לערוך את הקוד. - נשמור את האפליקציה באמצעות לחיצה על סמל השמירה.
- כעת נפרסם את האפליקצייה שלנו: נלחץ על הכפתור פריסה, ואז באפשרות פריסה חדשה. (באנגלית: Deploy > New deployment.)
- נלחץ על גלגל השיניים ונבחר ב"אפליקציית אינטרנט".
- נבחר שם שיעזור לנו להבין את תפקיד הקוד: "Add new entries from Elementor forms" או כל שם אחר שיעזור לכם לזכור מה הקוד בא לבצע.
את האפשרות "למי יש גישה" נגדיר כ"כולם" ונלחץ על כפתור "לפריסה".
- אם המערכת מבקשת מכם אישור לחצו על כפתור "הרשאת גישה" ואז בחרו בחשבון Google שלכם.
אם תופיע לכם הודעה "Google hasn’t verified this app" (ברור, אתם יצרתם אותה, הרגע). אל תלצחו על הכפתור אלא על "Advanced" ואז לחצו על הקישור בתחתית – "Go to <שם הגליון שלכם> (unsafe)".
אני מדגיש שוב: האזהרה היא כי מבחינת Google אתם נותנים לאפליקציה לא מוכרת גישה לחשבון Google שלכם. הקטע הוא שזו אפליקציה שאתם יצרתם ולכן זה ממש בסדר. - זהו! סיימנו את השלב הזה וקיבלנו קישור. נעתיק אותו כדי להשתמש בו בשלב הבא, ונלחץ על כפתור "סיום".
- בתפריט של הגיליון לוחצים על תוספים > Apps Script.
- כעת נבדוק את הקישור שלנו: נדביק אותו בחלון חדש בדפדפן ונלחץ על Enter. אם הכל בוצע כמו שצריך תופיע לנו הודעה Yepp this is the webhook URL, request received.
- נחבר לטופס את האפליקצייה שיצרנו ב Google Sheets:
נפתח את האתר וניצור טופס אלמנטור חדש / נערוך טופס אלמנטור קיים:- נבחר לו שם שמסביר את מטרת הטופס (Contact form / Subscribe to newsletter / Leads form / Submit feedback form…):
(אם האתר כבר פעיל והטופס מבצע פעולות נוספות, במיוחד בקוד, יש לבדוק שאף נזק לא נגרם משינוי השם) - פעולות אחרי שליחה > נוסיף Webhook:
- אל תוך שדה ה-Webhook נזין את הכתובת שנוצרה עבור האפליקציה שיצרנו ב-Google Sheets (שלב 2ח'):
- נשמור את עמוד האלמנטור.
- נבחר לו שם שמסביר את מטרת הטופס (Contact form / Subscribe to newsletter / Leads form / Submit feedback form…):
- כעת הכל צריך לעבוד:
נפתח את הדף עם הטופס (לא במצב עריכה באלמנטור) וננסה לשלוח את הטופס. אם הכל תקין יתווסף גיליון חדש בגיליון האלקטרוני שלנו, ותופיע בו שורה אחת עם הנתונים שהוזנו בטופס.
שימו לב שהשורה נוספה בגיליון חדש שהאפליקציה שפרסמנו יצרה עבורינו, ולא בגיליון ברירת המחדל. - כברירת מחדל, כל גיליון מקבל את שם הטופס ששלח אותו, ולכן אם יש לכם באתר טפסים שונים, (אשר מכילים שדות שונים ונועדו לצרכים שונים), הם צריכים להיות בעלי שמות שונים.
הגדרות מתקדמות (רשות)
- אם תרצו לבחור את שם הגליון בעצמכם, ללא קשר לשם הטופס באלמנטור, תוכלו לעשות זאת ע"י הוספת שדה מוסתר עם התוית
e_gs_SheetName
. הזינו את שם הגליון שתרצו כערך ברירת המחדל של השדה. - אם הטופס שלכם מוטמע בתבנית ומשותף לפוסטים רבים (CPT), ואתם רוצים שלכל פוסט יהיה גליון לידים נפרד, תוכלו להשתמש בשדה מוסתר עם התוית
e_gs_SheetName
גם למטרה זו – פשוט בחרו לשדה ערך ברירת מחדל דינאמי – כותרת פוסט. - כדי לשלוט בסדר העמודות בגליון תוכלו להוסיף שדה מוסתר עם התוית
e_gs_order
. כערך ברירת מחדל הזינו את שמות העמודות לפי הסדר שתרצו, מופרדות בפסיקים. - יש שדות בטופס שלא אמורים לעבור לגליון? הוסיפו שדה מוסתר עם התוית
e_gs_exclude
וכערך ברירת מחדל הזינו את העמודות שברצונכם שלא יופיעו בגליון (מופרדות בפסיקים).
סיכום – חיבור טופס אלמנטור לGoogle Sheets
במאמר זה הראיתי כיצד ניתן לשמור ב Google Sheets את כל הפניות שמגיעות מטפסי אלמנטור. ניתן להשתמש בכלי הזה במגוון דרכים ולמטרות שונות. ככל שאנחנו מיישמים יותר פתרונות ללא שימוש בשירותים חיצוניים ובתוספים אנחנו מאפשרים לאתר לעבוד בקלות ובהירות ומייתרים את הצורך בתשלומים מיותרים.
אם מצאתם שקוד זה אינו מעודכן ואינו פועל יותר אנא ספרו לי על זה.
על מנת למנוע אבדן של מידע יקר, דאגו שתהיה יותר מדרך אחת של שמירה של המידע באתר שלכם. כך, אם עקב שינוי בגרסה של אלמנטור או שינוי בGoogle Sheets הקוד יפסיק לעבוד, תוכלו לשחזר את המידע. לטפסי אלמנטור יש יכולת של שמירת המידע מהטפסים שנשלחו, גישה אליו מלוח הבקרה והורדה שלו כקובץ CSV. ודאו כי אפשרות זו מופעלת בהגדרות הטופס: פעולות אחרי שליחה > שמור לידים.
אצלינו בסטודיו מבצעים מגוון אינטגרציות מורכבות ומפתחים אתרים משלב הרעיון ועד שהאתר מתפרסם ומביא תוצאות. נשמח לבצע עבורכם עבודות וורדפרס מתקדמות הן בתחום הפיתוח והן בתחום העיצוב. אתם מוזמנים להציץ בתיק העבודות שלנו, ואם נראה לכם שזה יכול לעבוד, נשמח להכיר אתכם.
לקריאה נוספת
דיון ב-GitHub: Forms Webhook + Google Sheet integration via App Script