טופס בפאנל הניהול הוא אובייקט AJAXForm שאתם ממלאים בשדות (FormInput_*) ומחזירים מהמתודה של הפאנל. בבקשה הראשונה הוא מחזיר את ה-HTML, ובשליחה הוא שומר לטבלה ומחזיר הודעה. בדף הזה מוסבר המחזור המלא: הגדרה, מצב עדכון, שמירה, ולידציה, רב-לשוניות, ואיך הרשימה (panel_table) והטופס מדברים זה עם זה. קטלוג השדות נמצא ב-שדות טופס.
{admin} הוא CONFIG::$admin_url, ו-system/ הוא תיקיית הליבה (api/core בריפו). הקוד ב-system/libraries/forms/ (Form.php, Form_AJAXForm.php, FormInputs*.php).
הדפוס הבסיסי#
public function insert()
{
$frm = new AJAXForm("frmArticles"); // שם הטופס: ייחודי בעמוד
$frm->table = "articles"; // הטבלה שאליה שומרים
$frm->successFunc = "adminMoveToLastPanel()";
if (isset($_GET["id"])) {
$frm->update_mode((int)$_GET["id"]); // עריכה של שורה קיימת
}
$title = new FormInput_Text("title");
$title->title = "כותרת";
$title->Must(); // לפני push
$frm->push($title);
$body = new FormInput_Textarea("body");
$body->title = "תוכן";
$frm->push($body);
if (!$frm->IsFormSent()) {
return $frm->get_html(); // הצגה
}
return $frm->save(); // שמירה
}
הסדר חשוב:
update_mode($id)לפני שמירה והצגה: היא טוענת את השורה (db_row) ומציבה אתdb_id. בלעדיה כל שמירה היאINSERT.Must()על השדה לפניpush().push()רושם את בדיקת השדה החובה רק אםmustכבר נקבע.IsFormSent()בודקתisset($_POST["isFormSent_<formName>"]). השדה הנסתר הזה מתווסף לטופס אוטומטית.
מאפיינים של הטופס#
| מאפיין | ברירת מחדל | משמעות |
|---|---|---|
table | הטבלה לשמירה (בלי CRM_) | |
successFunc | "" | מחרוזת JavaScript שמורצת אחרי שמירה מוצלחת. למשל adminMoveToLastPanel() (חזרה לרשימה) |
tpl | "frmUL" | תבנית הפריסה. "groups" מציגה קבוצות לפי groups; ערך אחר הוא נתיב לקובץ Smarty (FILES::html_template) שמקבל את form, inputs, את כל המשתנים של tpl_vars ואת כל שדה לפי שמו |
groups | [] | קבוצות שדות: [["key" => "", "name" => "פרטים"], ...]. שדה משויך לקבוצה בעזרת $input->group = "key" |
className, style | מחלקת CSS וסגנון לעטיפה | |
msgAddedStr, msgNotAddedStr | הודעות ברירת מחדל | מה חוזר אחרי שמירה (הצלחה / כישלון) |
sendStr | "שלח" | טקסט כפתור השמירה |
submit_class | "" | מחלקה לכפתור השמירה |
hide_save_button | false | הסתרת כפתור השמירה |
general_buttons | [] | כפתורים נוספים (ראו להלן) |
autosave | false | מספר שניות בין שמירות אוטומטיות (למשל 30) |
ReloadAfterSubmit | true | התשובה מחליפה את תוכן הטופס; כשהוא false, מורץ successFunc |
allowMultyLang | true בפאנל | הצגת בורר שפות עריכה |
copyToAllLangs | false | העתקת הערכים לכל השפות בשמירה |
contentTableName, queryKey | "parentId" | טבלת תוכן רב-לשוני מותאמת (ChangeContentTable) |
mustChar | כוכבית | הסימון שמוצג ליד שדה חובה |
realtime_enabled | true | עריכה משותפת בזמן אמת, כשהשירות wizzonow פעיל |
ai_automations | [] | אוטומציות AI שמוצגות בטופס |
מתודות#
| מתודה | משמעות |
|---|---|
new AJAXForm($name) | יוצרת טופס (Form היא האב ללא AJAX; בפאנל משתמשים ב-AJAXForm) |
push($input) | מוסיפה שדה. מדלגת על שדה שה-perms שלו לא עובר ADMIN::has_perms, ועל שדות conf כשעורכים שפה משנית |
RemoveInput($name) | מסירה שדה |
GetInputByName($name) | מחזירה את אובייקט השדה |
update_mode($id) | טוענת שורה לעריכה (כולל השפה הנוכחית) |
IsFormSent() | האם הטופס נשלח |
get_html() | HTML מלא, כולל JavaScript של שליחה בלי רענון |
save() | שומרת ומחזירה הודעה |
MakeSave() | השמירה עצמה, מחזירה bool; save() עוטפת אותה בהודעות |
GetNewID() | מזהה השורה שנוצרה (אחרי save) |
ChangeContentTable($tbl, $queryKey) | טבלת תוכן רב-לשוני ידנית |
UnSend() | מבטלת את דגל השליחה, כדי להציג את הטופס שוב |
מה קורה בשמירה#
save() קוראת ל-MakeSave(), ש:
- יוצרת
DB::update($table). - לכל שדה שנדחף: קוראת ל-
get_results($new_id). התוצאה"ERROR"עוצרת הכול (הטופס חוזר עם הודעה). תוצאה שהיא מערך ([שם_עמודה => ערך]) מועברת ל-set_varשל השאילתה, לטבלתconfאוcontentלפי שדהtableשל השדה. - עושה
INSERT(כשאיןdb_id) אוUPDATE. - שדות עם
requireID = true(גלריות,SeoUrlועוד) רצים אחרי ה-INSERT, כשהמזהה כבר ידוע. אם אחד מהם נכשל, השורה החדשה נמחקת.
רק שדות שנדחפו לטופס נשמרים, ולכן שולח לא יכול לכתוב עמודה שלא הגדרתם (מניעת mass assignment). הערך המחרוזתי "NULL" נשמר כ-SQL NULL, ו-"NOW()" כביטוי.
בהצלחה save() שולחת את successFunc כ-<script> ומחזירה את msgAddedStr. בכישלון היא מחזירה את msgNotAddedStr. אחרי שמירה מוצלחת מופעלים גם סוכני AI שהוגדרו לאירוע form_save:<table> (ראו סוכני AI).
אחרי השמירה: להריץ קוד משלכם#
$result = $frm->save();
if ($frm->GetNewID()) {
cache_engine::remove("articles"); // למשל ניקוי מטמון
}
return $result;
מזהה השורה אחרי INSERT זמין ב-$frm->GetNewID(), ואחרי עדכון הוא ה-db_id.
ולידציה#
- שדה חובה:
$input->Must()(אוMust(true)כדי לאפשר "להתעלם"). הדפדפן בודק שהשדה לא ריק ומציג הודעה, והשרת מחזיר"ERROR"מ-get_resultsכשהשדה ריק. - בדיקה בצד הלקוח:
$input->add_validate_function("myJsFunc")מוסיפה שם של פונקציית JavaScript (שחייבת להחזירtrueאוfalse) ל-submitFormCheakErrors<formName>(). הפונקציות מורצות לפני השליחה.validate_urlמאפשר בדיקה מול כתובת (AJAX סינכרוני). - בדיקה בשרת: בפועל הליבה בודקת בשרת רק את
must. כל בדיקה אחרת (פורמט, ייחודיות, טווח) כתבו בעצמכם לפניsave(), והציגו שגיאה במקום לשמור:
if ($frm->IsFormSent()) {
$slug = DB::escape($_POST["slug"] ?? "");
$dup = DB::get_row("SELECT id FROM CRM_articles WHERE slug = '" . $slug . "'");
if ($dup && (int)$dup["id"] !== (int)($_GET["id"] ?? 0)) {
$frm->UnSend(); // מבטל את דגל השליחה: הטופס יוצג שוב
return "<div class='error'>הכתובת כבר בשימוש</div>" . $frm->get_html();
}
}
בדיקות JavaScript נעקפות בקלות. כל כלל שחשוב לשלמות הנתונים (ייחודיות, טווחים, הרשאות) צריך להיבדק גם ב-PHP לפני save().
טפסי הניהול ופעולות הטבלה לא נושאים טוקן CSRF משלהם: ההגנה היא עוגיית הסשן (SameSite=Lax). פעולה מסוכנת במיוחד (מחיקה, שליחה) שווה טוקן נוסף כמו בפאנל file_manager (ראו ממשק JSON).
כפתורים נוספים: general_buttons#
$frm->general_buttons = [
[
"title" => "שכפל",
"icon" => '<i class="fas fa-copy"></i>',
"class" => "btn",
"save" => true, // true: לבדוק ולשמור לפני; "force": לשמור בלי בדיקת ולידציה
"alert" => "לשכפל את המאמר?",
"perms" => "edit",
"func" => function ($db_row) {
// רץ בשרת, מקבל את שורת הטופס
$up = DB::update("articles");
$up->set_var("title", $db_row["title"] . " (עותק)");
$up->insert();
},
],
];
מפתחות נוספים: link (במקום func: ניווט), target. הפעולה נשלחת כ-POST form_action=<token>, והשרת מריץ רק כפתורים שהוגדרו (הטוקן הוא MISC::encode של ההגדרה). בכל טופס מתווסף אוטומטית כפתור "ביטול" שחוזר לפאנל הקודם.
טפסים רב-לשוניים#
בפאנל הטופס רב-לשוני כברירת מחדל. הנתונים נשמרים בשתי טבלאות: articles (שדות משותפים, table = "conf" בשדה, ברירת המחדל) ו-articles_content (שדות לפי שפה, parentId + langId), כשהשדה מוגדר $input->table = "content":
$title = new FormInput_Text("title");
$title->table = "content"; // נשמר בטבלת התוכן, לפי שפה
$title->title = "כותרת";
$frm->push($title);
מעבר שפה נעשה עם ?editLangID=<מזהה פלטפורמה>; update_mode טוענת את השורה בשפה זו, ושדות conf מוסתרים כשעורכים שפה משנית. ראו רב-לשוניות ו-פלטפורמות.
הטופס והרשימה#
הקשר בין panel_table לטופס הוא כתובת בלבד:
btn_add_urlב-panel_tableמצביע על מתודת הטופס ({admin}/articles/insert).- פעולת
"link" => "EDIT"מוסיפה את מזהה השורה:{admin}/articles/insert/12, ו-$_GET["id"]מגיע ל-update_mode. - אחרי שמירה
successFuncמחזיר לרשימה:adminMoveToLastPanel()בעמוד מלא, או סגירת חלון קופץ ורענון רשימה כשהטופס נפתח עםbtn_add_url_target = "popup"(ראו דוגמתredirectionsב-בניית פאנל חדש).
גילוי לסוכני AI#
בקשה עם הכותרת AI-Form-Discovery למתודת הטופס מחזירה תיאור JSON של הטופס (שדות, סוגים, חובה, אפשרויות, פעולות) במקום HTML. בקשת שמירה עם AI-Form-Insert מנרמלת ערכים דרך השדות, ובכישלון מחזירה את הכותרת AI-Form-Error עם שגיאות לפי שדה. פרטים ב-AI בטפסים.
ראו גם#
- שדות טופס ו-רפרנס שדות טופס
- panel_table
- בניית פאנל חדש
- ממשק JSON
- הרשאות:
permsעל שדה