אדר ב'

חוויות מפיתוח כפתור ל-TinyMCE, ו-Shortcake

בפוסט הזה אראה איך ליצור כפתור בעורך TinyMCE שמאפשר למשתמש ליצור shortcode, ואיך התוסף Shortcake מאפשר גם לערוך אותו.

אחד הפרוייקטים שלי הוא עבור עמותה המקיימת פעילויות עבור חבריה. כשהפעילות כרוכה בתשלום – למשל טיול – אחת מדרכי התשלום היא באמצעות אתר פלא-פיי. כדי לחסוך לנרשמים את הצורך למלא את סכום התשלום בעצמם, יצרו מתכנתי האתר שקדמו לי, טופס שמופיע בפוסט של ההרשמה לפעילות. בטופס הזה, הנרשמים בוחרים את הסכום לתשלום (עפ”י היותם חברי העמותה או לא), ולוחצים על כפתור התשלום. כך הם מועברים לאתר פלא-פיי, יחד עם הסכום לתשלום אותו בחרו.

איך מוכנס הטופס לפוסט? טוב ששאלתם.

טופס התשלום

אפשרות אחת היא שמנהל האתר יעשה זאת. אבל מה יעשה מנהל אתר שאין לו מושג בקוד? הוא לא ישוש להכניס את הטופס בעצמו (גם אם מדובר רק ב-copy + paste מפוסט אחר), כי דברים עלולים להשבר בקלות (מספיק שתגית אחת לא נסגרת כראוי, וכל הפוסט מתחרבש). ואם, בנוסף, מדובר על שינוי שדות בטופס (למשל אם המחיר השתנה) אז על אחת כמה וכמה.

לכן לפעמים הוא יעדיף אפשרות שנייה: לבקש ממתכנת לעשות זאת. גם זו לא כזאת אפשרות נפלאה, הן מצד מנהל האתר, שנהיה תלוי באדם נוסף; והן מצד המתכנת, שההתעסקות הזאת היא מיותרת מבחינתו.
וזה עוד בלי להתייחס לעובדה שקוד בתוך פוסט מוּעַד לפורענות – וורדפרס אוהבת למחוק קוד HTML מפוסטים…

Slide3
תראו כמה מקומות צריך לשנות כשמשתנה המחיר

אז אף אחת משתי האפשרויות אינה מלהיבה. צריך למצוא אפשרות שתתן למנהל האתר להיות עצמאי, בלי שפעולותיו תסכנה את הפוסט.

הפתרון – shortcode.

במקום להכניס לכל פוסט את קוד ה-HTML של הטופס, נכניס shortcode שיתורגם לקוד. כך ההעתקה וההדבקה הם פחות מסוכנים – גם מפני שזה קצר יותר, וגם מפני שאפילו אם ההעתקה משתבשת, זה לא יהרוס את ה-HTML. חוץ מזה שזה פותר את בעיית מחיקת הקודים מהפוסטים שוורדפרס כה חובבת.
חשבתי, ועשיתי.
יצרתי shortcode בשם pelepay_form שמקבל מאפיינים של סכומים לתשלום, מלל לכל סכום, ומספר תשלומים (נשמר בשדה נסתר). כעת מספיק שמתכנת ייצור את ה-shortcode פעם אחת, ובפעמים הבאות, מנהל האתר יעתיק אותו לפוסט הרלוונטי. אפילו שינוי הסכומים לתשלום הוא קל יותר, כי אין הרבה קוד HTML שעוטף את המקומות שצריך לשנות.

השורטקוד

אבל עדיין לא הייתי מרוצה. אמנם הצלחתי לחסוך מעורך האתר מגע עם קוד אמתי, אבל עדיין, הוא צריך לגעת ב-shortcode ולשנות בו ערכים. אפשרי, אבל ממש לא אידיאלי.

הרעיון הבא היה – ממשק גרפי. למה לא ליצור חלונית עם שדות טקסט שמנהל האתר ימלא, וזה ייצור את ה-shortcode? כך הוא אף פעם לא יצטרך לגעת בשום קוד, ואפילו לא להעתיק ולהדביק אותו.

ואיך יופעל הממשק הגרפי? ע”י כפתור בעורך הטקסט שיוגדר במיוחד לשם כך:

הכפתור שפותח את החלון שיוצר את השורטקוד

איך מפתחים כפתור כזה? לא ידעתי.
אז חיפשתי מדריך (tutorial) שיסביר לי איך לעשות את זה. מאחר שלא חסרים מדריכים בנושא, הייתי זקוקה לקריטריון שיעזור לי להחליט מיהו tutorial טוב. הימים היו ימי וורדפרס 3.9, שבה השתנה הייצור של כפתורי ה-TinyMCE: במקום תמונות, עברו לשימוש ב- font icon. החלטתי שרק מדריך שיסביר איך ליצור כפתור בדרך החדשה, עם font-icon, יתקבל.
כך הגעתי למאמר מפורט ויסודי ביותר על אופן הוספת כפתורים ושאר מרעין בישין ל-TinyMCE בוורדפרס (לזכותו ייאמר שהוא הכיל הסברים הן על הדרך הישנה והן על הדרך החדשה!).

מאחר שכל הקוד נמצא ב-tutorial, אכתוב כאן את השלבים הנצרכים כדי לבנות כזו חלונית, ואקשר לדוגמאות הקוד של ה-tutorial:

  • יצירת ה-Shortcode
  • הוספת הכפתור לעורך
    • הגדרת פונקציה שיושבת על admin_head ומקשרת שתי פונקציות לשני פילטרים: mce_external_plugins, mce_buttons. הפונקציה הראשונה, מגדירה את הנתיב של קובץ ה-JS כפלגאין של TinyMCE; הפונקציה השניה מוסיפה את הכפתור למערך הכפתורים של TinyMCE
    • יצירת אייקון – המדריך מסביר איך להוסיף איקון מסדרת ה-Dashicons, שאינו מופיע בעורך. זה כולל מתן Class לאייקון בקובץ ה-JS, יצירת קובץ CSS והגדרת ה-Class שנתנו כך שיראה את האיקון המבוקש, והכללה של קובץ ה-CSS (enqueue).
    • יצירת החלונית – כתיבת קוד בקובץ ה-JS (הקובץ שהוגדר בשלב 2.1) הפותח חלונית כשמקליקים על הכפתור שיצרנו. האפשרות הכי פשוטה היא חלונית עם שדה טקסט, וכפתורי אישור וביטול. יש גם אפשרות לפקדים מתקדמים יותר.
    • תרגום – שמות ה-label-ים בחלונית צריכים להיות בעברית, כדי להקל על המשתמש. הפתעה נעימה הייתה שהמדריך התייחס לנושא התרגום (דבר שנזנח פעמים רבות ב-tutorial-ים הנכתבים ע”י דוברי אנגלית).
      שימוש ב- load_textdomain או load_plugin_textdomain כמובן לא רלוונטי, מפני שהם נועדו לתרגום ב-PHP. אבל גם wp_localize_script לא יועיל, מפני ש-TinyMCE לא יודע להתייחס לזה.  הפתרון הוא הפילטר mce_external_languages, שהפונקציה המחוברת אליו מוסיפה אלמנט נוסף לטבלת ה-$locals, המציין קובץ תרגום נוסף בשם translations.php. צריך ליצור את הקובץ הזה ולהכניס בו את התרגומים לפי ההסבר במדריך, ולאחר מכן, להוסיף את הקריאה לתרגומים בקובץ ה-JS, ע”י שימוש ב-editor.getLang.

וזו התוצאה:

בעיקרון, לפי חוק הפרדת הרשויות – שלפיו מה שקשור לעיצוב שייך בתבנית ומה שקשור בפונקציונליות שייך בתוסף – את כל הקוד בפוסט הזה כדאי להכניס לתוסף ייעודי. אם, משום מה, לא בא לכם (אבל למה? זה קלי קלות ליצור תוסף, וזה עוד יותר קלי קלות עם התוסף שיוצר תוספים. מי שראה את הסרט Inception יבין את שם התוסף 🙂 ), אז את הקוד אפשר להכניס ל-functions.php של התבנית, או, עדיף, לקובץ נפרד שנקרא מתוך functions.php ע”י require_once. כשיש מקומות שבהם צריך לכתוב את הקוד בקבצים אחרים, זה מפורט במדויק תוך כדי ההדרכה.

**

לתקופה מסויימת, הכל היה נפלא וזרם על מי מנוחות.
ברם אולם, אחרי זמן מה, עלה עלה הצורך לשנות משהו במלל. אמנם את הסכומים קל היה לשנות ב-shortcode עצמו, אבל עריכה של עברית (הטקסט שאמור להופיע בטופס) משולבת באנגלית (שמות המאפיינים של ה-shortcode) היא כבר יותר מאתגרת.

הפתרון – יכולת לערוך את ה-shortcode בממשק גרפי.

השורטקוד

לשמחתי, שמעתי על תוסף בשם Shortcake, שזו עיקר מעלתו: לאפשר עריכת shortcode-ים. ובונוס: במקום לראות בפוסט את הסוגריים המרובעים, אפשר לראות את התוצר עצמו:

השורטקוד מתורגם ל-HTML, וניתן לעריכה

כדי לאפשר עריכה כזאת, צריך “לרשום” את ה-shortcode שלנו באופן כזה שהתוסף ייצר חלונית עריכה, שבה יהיו אותם שדות שיש בחלונית היצירה, רק שהערכים שכבר הוכנסו ב-shortcode יופיעו בהם. ה”רישום” הוא ממש פשוט: יש ליצור פונקציה שיושבת על על hook של register_shortcode_ui, וממפה את השדות והשמות שלהם. הקוד הזה דומה להפליא לקוד שכתבנו בקובץ ה-JS כשיצרנו את הכפתור, רק שהקוד ההוא היה ב-JS, והקוד הנוכחי הוא ב-PHP.

זה ה-JS (חלק מתוך הקוד שיוצר את החלונית שהכפתור מפעיל):

...
body: [
                        {
                            type: 'textbox',
                            name: 'price_list',
                            label: editor.getLang('pelepay_tc_button.price_list')
                        },
                        {
                            type: 'textbox',
                            name: 'price_text',
                            label: editor.getLang('pelepay_tc_button.price_text')
                        },
                        {
                            type: 'textbox',
                            name: 'payment_for',
                            label: editor.getLang('pelepay_tc_button.payment_for')
                        },                        {
                            type: 'textbox',
                            name: 'payments',
                            label: editor.getLang('pelepay_tc_button.payments_text')
                        }
                    ],
...

וזה ה-PHP (מתוך הקוד ש”רושם” את ה-shortcode שלנו ב-shortcake):

...
'attrs' => array (
                array (
                    'label' => esc_html__( 'Comma separated price list', $this->plugin_name ),
                    'attr' => 'price_list',
                    'type' => 'text',
                ),
                array (
                    'label' => esc_html__( 'Comma separated price text list', $this->plugin_name ),
                    'attr' => 'price_text',
                    'type' => 'text',
                ),
                array (
                    'label' => esc_html__( 'Payment for...', $this->plugin_name ),
                    'attr' => 'payment_for',
                    'type' => 'text',
                ),
                array (
                    'label' => esc_html__( 'Max number of payments', $this->plugin_name ),
                    'attr' => 'payments',
                    'type' => 'text',
                ),
            ),
...

כמו שאתם רואים, הקוד מאד פשוט. במקרה שלי זה כך גם מפני שיש לי רק שדות טקסט בחלונית. עקרונית התוסף מאפשר הרבה יותר סוגים של שדות. כמו-כן, אני השתמשתי רק ב-3 מאפיינים לכל שדה: type, attr, label. יש עוד כמה מאפיינים שמוסברים בקוד הזה בגיטהאב, ובאחד מהם עוד אשתמש בהמשך הפוסט.

וזה כל מה שצריך לעשות – ליצור הפונקציה הזאת (שכאמור היא גרסת PHP לפונקציה שבונה את החלון מהכפתור), ולשייך אותה ל-hook.

מסך עדכון ה-shortcode

אך זה אינו סוף הסיפור. כמו בסיפורים רבים, גם כאן יש טוויסט יהודי.

שמחה וטובת לב הודעתי לעורך האתר שיש באפשרותו לערוך את שדות הטופס. בתגובה קיבלתי את הדואל הבא:

ניסיתי להוסיף את זה לשורת “בעבור”: טיול חוה”מ פסח תשע”ו
שמרתי, ו…. לא נשמר.
והנה התגלה לו נ’ שלי: שכחתי לטפל בגרשיים.
כשיצרתי את הכפתור, דווקא כן טיפלתי בגרשיים (אם כי גם אז, זה קרה אחרי פנייה של עורך האתר). שם היה צריך לטפל בהם הן ב-JS (בשלב שבו אוספים את המידע מהחלונית ומכניסים אותו לפוסט), והן בשלב ה-PHP (כשמתרגמים את ה-shortcode ל-HTML). הקידוד צריך להעשות בשלב ה-JS כי אחרת הגרשיים גרמו לחלוקה לא נכונה של ה-attribute-ים, וכשזה הגיע ל-PHP זה היה כבר מאוחר מדי. אבל לקודד shortcode בשלב ה-JS זה לא אפשרי (באג מלפני 4 שנים שאף אחד לא מתלונן עליו, אז החליטו לא לתקן אותו). לכן עטפתי את ערכי ה-attribute-ים בגרש בודד, מה שאִפשר להכניס גרשיים בערכים.  כמובן שזה ישתבש אם יהיה שימוש בגרש אחד… כשהתייצעתי על זה עם מתכנת שעבד איתי על הפרוייקט, הוא המליץ בפעם הבאה לאפשר דווקא גרש אחד ולא גרשיים, מפני שמגרש אחד אפשר ליצור גרשיים ע”י שני גרשים, אבל להפך – לא…
אותו מתכנת גם המליץ לי על פונקציית htmlspecialchars לשימוש ב-PHP בעת תרגום ה-shortcode. כאמור, כל זה נשכח ממני בעת ההתאמה לעריכה.
למרבה השמחה, אנשי ה-shortcake חשבו על, וטיפלו ב, בעיות של תווים מיוחדים, ועשו זאת כך שנותרה לי מעט מאד עבודה: כדי שקלט משדה מסויים יקודד, יש להוסיף מאפיין encode עם ערך של true:
array (
 'label' => esc_html__( 'Payment for...', $this->plugin_name ),
 'attr' => 'payment_for',
 'type' => 'text',
 'encode' => true,
 ),
זה כבר פותר את אי השמירה של טקסט בעל גרשיים.
לגבי תצוגת הטקסט באתר ללא קידוד – כל שנדרש ממני הוא לעשות decode בעת התצוגה.
חשבתי שמאחר שגם ככה הרצתי את הפונקציה htmlspecialchars כל שדות הטקסט בפונקציית ה-callback של ה-shortcode, זה יספיק, אך התברר שלא. גוגל שלח אותי לקרוא את הקוד שמממש את ה-shortcake, וראיתי איך הוא מתמודד שם כשהמאפיין encode דלוק: הוא משתמש ב-rawurldecode. הנוסחה המנצחת היא קודם  rawurldecode (קודם להמיר את ה-decode בחזרה לתווים רגילים) ואח”כhtmlspecialchars  (שממירה תווים מיוחדים לישויות HTML-יות):
htmlspecialchars(rawurldecode ($atts['payment_for']));

ועכשיו נראה שבא לציון גואל 🙂 .

מבחינה מסויימת, כל עניין בניית הכפתור והחלונית הם קצת מיותרים כשישנו התוסף הזה, כי בעצם, התוסף מאפשר להכניס shortcode בלי להצטרך להגדיר כפתור בעורך וליצור את החלונית הנפתחת בעצמנו. הוא משתמש בחלון הוספת המדיה, ומוסיף בתפריט אפשרות נוספת, בשם “הכנס אלמנט לפוסט”, שלחיצה עליו מראה את כל ה-shortcode-ים הרשומים באתר בצורת ריבועים עם איקון עליהם (“הרשומים” פירושו, אלה שנכתבה עבורם פונקציה כדלעיל), ואז לחיצה על הריבוע הרלוונטי פותחת חלון שבו ניתן להכניס ערכים למאפיינים של ה-shortcode.

יצירת shortcode באמצעות shortcake

אבל אני בכל זאת שמחה על הכפתור – בעיניי זה לא כ”כ אינטואיטיבי להשתמש בהוספת המדיה לשם יצירת שורטקוד.

אם תוסף ה-shortcake מעניין אתכם, יש דיבורים על הכנסתו ל-core. אפשר לעקוב אחרי הדיון כאן, כרגע הכיוון הוא בגרסה 4.6.

 

9 תגובות על “חוויות מפיתוח כפתור ל-TinyMCE, ו-Shortcake

    1. מה, המשתמש אשם? 😉
      אבל איך עושים גרשיים אמתיים? הגרשיים שיש בעברית הם לא נכונים?

      1. המשתמש לא אשם והמתכנת צריך לחשוב על כל המצבים.
        לגרשיים יש תו מיוחד ביוניקוד (U+05F4) אבל כיוון שהוא לא הופיע בעבר על מקלדות רגילות רגילים להשתמש במקומו במירכאות לועזיות (u+0022).

        לפי הערך בוויקיפדיה (שלא לגמרי מדויק) הסימן הוסף למקלדת בווינדוס 8 כחלק מתקן המקלדת החדש.

        אם מעניין אותך תוכלי למצוא עוד מידע בוויקי של עמותת המקור.

        1. איזה כיף שהתגובות שלך כאלה מלמדות. אתה יודע, רק המתגובות שלך אתה יכול לכתוב פוסטים מעניינים!

          לבושתי, לא שמתי לב עד עכשיו שיש הבדל בין גרשיים למרכאות. מגניב.

    2. ותודה שוב על הגרשיים האלה.
      הם הצילו אותי בפוטושופ
      (הגרשיים הרגילים של המקלדת יושרו כל הזמן לצד הלא נכון…)

כתבו תגובה

כתובת הדוא"ל שלכם לא תוצג.