سه‌شنبه, 14 مهر 1405

ایده ساخت یک Page Builder

...

” تا حالا به این فکر کردید چطور میشه ساختار یک صفحه رو طوری طراحی کرد که کاربر خودش بتونه بخش‌های مختلفش رو جابه‌جا، حذف یا ویرایش کنه؟ توی این مقاله ایده ساخت یک Page Builder رو بررسی می‌کنیم. “

تا حالا چشمتون به محصولی خورده که این قابلیت رو داشته باشه که خودتون صفحه‌ای که می‌خواید رو بسازید؟

مثلاً وارد پنل مدیریت بشید، یک صفحه جدید ایجاد کنید، چند بخش مختلف بهش اضافه کنید، داخل هر بخش محتوا قرار بدید، جای بخش‌ها رو تغییر بدید و در نهایت همون صفحه‌ای که ساختید در سایت نمایش داده بشه.

بنظرم داریم به سمتی میریم که این قابلیت برای خیلی از وب‌سایت‌ها دیگه چیز خاصی نباشه و حتی ممکنه کم‌کم تبدیل به یکی از قابلیت‌های معمول یک محصول بشه :)

البته Page Builder چیز جدیدی نیست. من با اینکه خودم خیلی با وردپرس کار نکردم ولی اسم Elementor رو زیاد شنیدم و نمونه‌های مشابهش رو هم در بعضی اسکریپت‌های اختصاصی چه به صورت پیشرفته و چه به صورت خیلی بیسیک و ساده دیدم.

چیزی که باعث شده دوباره به این موضوع فکر کنم، سرعت پیشرفت AI و ابزارهاییه که این روزها داریم می‌بینیم. بعضی قابلیت‌هایی که قبلاً برای ساختنشون باید زمان زیادی صرف می‌کردیم، الان خیلی راحت‌تر قابل پیاده‌سازی هستن. به همین خاطر ممکنه بعضی امکاناتی که قبلاً خاص به حساب می‌اومدن، کم‌کم تبدیل به چیزهایی بشن که کاربر از یک محصول انتظار داره.

Page Builder هم می‌تونه یکی از همین قابلیت‌ها باشه.

برای همین توی این مقاله می‌خوام ایده پیاده‌سازی چنین سیستمی رو با هم بررسی کنیم.

نه قراره وارد کدنویسی بشیم و نه می‌خوام بگم این تنها روش درست برای ساخت چنین سیستمیه :)

طبیعتاً میشه یک Page Builder رو به شکل‌های مختلفی طراحی کرد و حتی ممکنه یک تیم تصمیم بگیره ساختار داده کاملاً متفاوتی داشته باشه.

هدف این مقاله فقط اینه که اگه یک روز خواستیم چنین قابلیتی رو برای یک محصول بسازیم، بدونیم مسئله از چه بخش‌هایی تشکیل شده و پشت صحنه چه اتفاقی باید بیفته.

اول از همه یک Page داریم

وقتی کاربر میگه می‌خوام یک صفحه جدید بسازم، اولین چیزی که باید داشته باشیم خود Page هست.

مثلاً کاربر می‌خواد صفحه‌ای به اسم About Us بسازه.

در ساده‌ترین حالت، برای این صفحه اطلاعاتی مثل عنوان، آدرس صفحه و وضعیت انتشار رو نگه می‌داریم.

مثلاً:

id: 10
title: About Us
slug: about-us
status: published

تا اینجا اتفاق خاصی نیفتاده.

ما فقط می‌دونیم یک صفحه به اسم About Us داریم.

اما هنوز نمی‌دونیم این صفحه چه شکلیه یا شامل چه محتوایی میشه.

برای اینکه این رو مشخص کنیم، باید محتوای صفحه رو هم ذخیره کنیم.

اینجاست که کم‌کم ساختار Page Builder شکل می‌گیره.

صفحه از چه چیزهایی ساخته شده؟

فرض کنید کاربر می‌خواد یک صفحه ساده بسازه.

بالای صفحه یک Hero داشته باشه، بعد چند بخش برای معرفی ویژگی‌های محصول (Feature)، بعد بخش نظرات مشتری‌ها (Testimonials) و در انتها هم یک فرم تماس.

اگه بخوایم این صفحه رو بدون Page Builder بسازیم، باید خودمون بریم و بخش‌های مختلف صفحه رو یکی‌یکی پیاده‌سازی کنیم؛ مثلاً ساختار صفحه، بخش معرفی، ویژگی‌ها، فرم تماس و بقیه قسمت‌ها رو از قبل مشخص کنیم و با کد بسازیم.

در Page Builder هم تقریباً همین اتفاق می‌افته، با این تفاوت که این بار باید این ساختار رو به شکلی ذخیره کنیم که کاربر بعداً بتونه خودش تغییرش بده.

پس به یک مفهوم بین Page و محتوای داخل صفحه نیاز داریم.

اینجا می‌تونیم از چیزی مثل Section یا Layout استفاده کنیم.

من در این مثال از Layout استفاده می‌کنم.

Layout در Page Builder دقیقاً چیست؟

Layout قرار نیست خودش محتوای صفحه باشه.

Layout بیشتر مشخص می‌کنه فضایی که برای قرار گرفتن محتوا داریم چه شکلی باشه.

مثلاً یک Layout می‌تونه یک ستون تمام‌عرض داشته باشه.

یک Layout دیگه می‌تونه دو ستون داشته باشه که ستون اول ۴۰ درصد و ستون دوم ۶۰ درصد عرض رو بگیره.

یا مثلاً سه ستون مساوی داشته باشیم.

یا یک Layout داشته باشیم که سمت چپ محتوا و سمت راست Sidebar قرار گرفته باشه.

در واقع Layout درباره چیدمان صحبت می‌کنه، نه درباره محتوایی که داخل اون چیدمان قرار می‌گیره.

مثلاً اگه بخوایم یک بخش از صفحه این شکلی باشه:

┌───────────────────────────────┐
│                               │
│          Hero                 │
│                               │
└───────────────────────────────┘

یک Layout تمام‌عرض داریم.

حالا اگر بخوایم این شکلی باشه:

┌──────────────────┬─────────────┐
│                  │             │
│     Content      │   Sidebar   │
│                  │             │
└──────────────────┴─────────────┘

یک Layout دو ستونه داریم.

حالا محتوای داخل این ستون‌ها می‌تونه چیزهای مختلفی باشه.

ممکنه در ستون اصلی یک Text قرار بگیره، یا Image، یا یک Hero.

پس Layout میگه کجا و با چه ساختاری چیزی قرار بگیره، اما اینکه خود اون «چیز» چی باشه، به Widget مربوط میشه.

این تفکیک خیلی کمک می‌کنه که سیستم بعداً قابل توسعه بمونه.

Widget چیست؟

Widget رو می‌تونیم کوچک‌ترین بخش قابل استفاده از محتوای صفحه در نظر بگیریم.

مثلاً:

  • Heading

  • Text

  • Image

  • Button

  • Hero

  • Feature

  • Pricing

  • FAQ

  • Testimonials

  • Contact Form

این‌ها همون چیزهایی هستن که کاربر از داخل Page Builder انتخاب می‌کنه و به صفحه اضافه می‌کنه.

مثلاً کاربر یک Layout دو ستونه ساخته.

داخل ستون اول یک Heading قرار میده و داخل ستون دوم یک Image.

اینجا Layout فقط مسئول دو ستونه بودن این قسمت هست و Widgetها محتوای واقعی اون رو تشکیل میدن.

این موضوع باعث می‌شه Layout و Widget وظایف متفاوتی داشته باشن.

اگه فردا بخوایم یک Layout سه ستونه جدید اضافه کنیم، لازم نیست Widget جدیدی بسازیم.

و اگه بخوایم یک Widget جدید مثل Video اضافه کنیم، لازم نیست ساختار Layoutها رو تغییر بدیم.

هر کدوم مسئول بخش خودشونه.

Widgetهایی که کاربر می‌تواند انتخاب کند

یک نکته مهم این وسط وجود داره که شاید در نگاه اول مشخص نباشه.

قرار نیست کاربر خودش Widget جدید طراحی کنه.

ما قبل از اینکه Page Builder رو در اختیار کاربر قرار بدیم، مجموعه‌ای از Widgetها رو از قبل توسعه دادیم. مثلاً ممکنه سیستم در حال حاضر از Hero، Heading، Image، Button، Feature، Pricing، FAQ و چند مورد دیگه پشتیبانی کنه.

حالا وقتی کاربر می‌خواد یک Page جدید بسازه، Editor همین مواردی که سیستم در حال حاضر پشتیبانی می‌کنه رو بهش نمایش میده.

مثلاً کاربر وارد بخش اضافه کردن Widget میشه و می‌بینه:

Hero
Heading
Text
Image
Button
Feature
Pricing
FAQ

کاربر یکی از این موارد رو انتخاب می‌کنه و اون Widget به صفحه اضافه میشه.

پس Page Builder قرار نیست در لحظه برای کاربر یک Widget جدید بسازه. Widgetها از قبل توسط خودمون توسعه داده شدن و Page Builder فقط امکان استفاده، تنظیم و جابه‌جایی اون‌ها رو در اختیار کاربر قرار میده.

همین موضوع برای Layoutها هم وجود داره.

ما می‌تونیم از قبل چند Layout مختلف داشته باشیم؛ مثلاً Layout تمام‌عرض، دو ستونه، سه ستونه یا Layoutهایی با Sidebar و..

وقتی کاربر می‌خواد یک بخش جدید به صفحه اضافه کنه، لیست Layoutهای موجود رو می‌بینه و یکی از اون‌ها رو انتخاب می‌کنه.

بنابراین چیزی که کاربر در Page Builder انجام میده، بیشتر شبیه کنار هم قرار دادن و تنظیم کردن قطعاتیه که ما از قبل برای سیستم آماده کردیم.

مثلاً ممکنه یک صفحه رو با یک Layout تمام‌عرض شروع کنه و داخل اون یک Hero قرار بده. بعد یک Layout دو ستونه اضافه کنه و در ستون اول Text و در ستون دوم Image قرار بده.

در نتیجه آزادی کاربر از اینجا میاد که می‌تونه اجزای آماده رو با ترکیب‌های مختلف کنار هم قرار بده، نه اینکه برای هر صفحه مجبور باشیم Widget یا Layout جدیدی توسعه بدیم.

این موضوع از نظر معماری هم نکته مهمیه.

چون ما یک مجموعه Widget و Layout قابل استفاده داریم و Page Builder فقط مشخص می‌کنه در هر Page کدوم مورد استفاده شده و تنظیماتش چی هست.

اگه چند ماه بعد تصمیم بگیریم یک Widget جدید به سیستم اضافه کنیم، مثلاً Video، اون رو یک بار توسعه میدیم و به لیست Widgetهای قابل استفاده اضافه می‌کنیم. از اون به بعد کاربر می‌تونه Video رو هم مثل بقیه Widgetها در Pageهای خودش استفاده کنه.

پس Page Builder بیشتر از اینکه یک ابزار برای ساخت Widget باشه، ابزاری برای ساخت Page با استفاده از Widgetها و Layoutهای از قبل آماده‌شده هست.

حالا اطلاعات Layout را چطور ذخیره کنیم؟

اینجا هم چند راه مختلف داریم.

مثلاً می‌تونیم Layout رو به صورت یک رکورد جداگانه ذخیره کنیم و مشخص کنیم مربوط به کدوم Page هست، چه نوعی داره و ترتیبش در صفحه چطوره.

مثلاً:

id: 15
page_id: 10
type: two-columns
sort: 2

اینجا type میگه این Layout چه ساختاری داره.

مثلاً:

full-width
two-columns
three-columns
sidebar

البته این اسم‌ها فقط برای مثال هستن و در یک پروژه واقعی ممکنه ساختار کاملاً متفاوتی داشته باشیم.

نکته مهم اینه که Layout باید اطلاعاتی داشته باشه که فرانت بتونه از روی اون بفهمه چطور باید این بخش رو نمایش بده.

اما هنوز یک سؤال باقی مونده.

اگه Layout دو ستون داشته باشه، Widgetها دقیقاً داخل کدوم ستون قرار می‌گیرن؟

فقط ترتیب Widgetها کافی نیست

فرض کنیم یک Layout دو ستونه داریم.

کاربر می‌خواد Heading و Text در ستون اول باشن و Image در ستون دوم.

پس ما فقط به ترتیب Widgetها نیاز نداریم؛ باید محل قرار گرفتن Widget رو هم بدونیم.

برای همین یک مدل ساده می‌تونه چیزی شبیه این باشه:

Layout
    columns
        column 1
            Heading
            Text

        column 2
            Image

حالا این ساختار می‌تونه در دیتابیس به شکل‌های مختلف ذخیره بشه.

مثلاً ممکنه برای هر Widget یک column_id داشته باشیم.

یا ممکنه خود Layout یک ساختار داده داشته باشه که ستون‌ها و Widgetهای داخل اون رو مشخص کنه.

حتی ممکنه یک تیم اصلاً Layout رو به عنوان جدول جداگانه ذخیره نکنه و همه این اطلاعات رو در یک ساختار دیگه نگه داره.

این همون جاییه که باید یادمون باشه این مقاله قرار نیست یک نسخه قطعی برای ساخت Page Builder ارائه بده.

ما فقط داریم یک روش قابل فهم برای فکر کردن به مسئله پیدا می‌کنیم :)

اطلاعات خود Widgetها چه می‌شود؟

حالا فرض کنیم یک Hero به صفحه اضافه کردیم.

خود Widget باید بدونه چه نوعی داره. مثلاً:

type: hero

اما Hero فقط type نیست. محتوای اون هم باید جایی ذخیره بشه.

مثلاً یک Hero می‌تونه این اطلاعات رو داشته باشه:

title: Build better products
description: Create and launch your next idea faster.
button_text: Get Started
button_url: /signup

یک Image ممکنه اطلاعات متفاوتی داشته باشه:

image
alt
caption

و یک Widget مثل Pricing ممکنه اطلاعات خیلی بیشتری داشته باشه.

برای همین معمولاً در جدول Widgetها یک ستون به اسم data داریم که نوعش JSON هست. اطلاعات و تنظیمات مربوط به همون Widget داخل این ستون ذخیره میشه.

مثلاً رکورد یک Hero می‌تونه چیزی شبیه این باشه:

{
  "type": "hero",
  "data": {
    "title": "Build better products",
    "description": "Create and launch your next idea faster.",
    "button_text": "Get Started",
    "button_url": "/signup"
  }
}

مزیت این روش اینه که لازم نیست برای هر نوع Widget چندین ستون جداگانه به جدول اضافه کنیم.

هر Widget می‌تونه ساختار داده خودش رو داخل data داشته باشه.

پس در نهایت، هر Widget دو چیز مهم داره: یک type که مشخص می‌کنه چه Widgetی هست و یک data که محتوا و تنظیمات همون Widget رو نگه می‌داره.

چرا Type اینقدر مهم است؟

فرض کنید در دیتابیس فقط اطلاعات محتوا رو ذخیره کنیم.

مثلاً:

title: Build better products
description: ...

فرانت از کجا باید بفهمه این اطلاعات مربوط به Hero هست؟

اینجاست که type اهمیت پیدا می‌کنه.

مثلاً:

type: hero

یعنی این داده مربوط به Widget نوع Hero هست.

حالا یک Widget دیگه می‌تونه داشته باشه:

type: feature

و یکی دیگه:

type: pricing

در نتیجه داده‌ای که ذخیره می‌کنیم فقط شامل محتوا نیست؛ نوع چیزی که باید با اون محتوا نمایش داده بشه رو هم مشخص می‌کنه.

این موضوع بعداً در فرانت خیلی مهم میشه.

یک Page در نهایت چه اطلاعاتی دارد؟

حالا اگر همه این‌ها رو کنار هم بذاریم، یک Page می‌تونه اطلاعاتی شبیه این داشته باشه:

Page
    title
    slug
    status

    Layouts
        Layout
            type
            sort

            Columns
                Column
                    Widgets
                        type
                        data
                        sort

این ساختار رو لازم نیست دقیقاً با همین جداول و همین اسم‌ها پیاده کنیم.

ممکنه کسی Layout و Column رو در یک جدول ذخیره کنه.

ممکنه شخص دیگه‌ای ساختار صفحه رو به صورت یک JSON کامل ذخیره کنه.

ممکنه حتی اصلاً مفهوم Column رو جدا نکنه و Layout رو به شکل دیگه‌ای تعریف کنه.

مهم اینه که در نهایت سیستم بتونه به این چند سؤال جواب بده:

این صفحه چیست؟

چه بخش‌هایی دارد؟

هر بخش چه چیدمانی دارد؟

هر Widget کجا قرار گرفته؟

Widget چه نوعی است؟

و اطلاعات آن چیست؟

اگه این اطلاعات رو داشته باشیم، تقریباً هر چیزی که برای ساخت صفحه لازم داریم قابل ساختن هست.

حالا برسیم به خود Editor

تا اینجا بیشتر درباره داده صحبت کردیم.

ولی کاربر قرار نیست خودش این اطلاعات رو در دیتابیس وارد کنه :)

ما به یک Editor نیاز داریم.

مثلاً کاربر وارد صفحه ساخت یا ویرایش Page میشه.

یک قسمت از صفحه برای نمایش خود Page داریم و یک بخش هم برای انتخاب Widgetها.

کاربر می‌تونه یک Layout انتخاب کنه.

مثلاً Layout دو ستونه.

بعد سیستم دو فضای قابل استفاده در اختیارش قرار میده.

حالا می‌تونه Widgetها رو داخل این فضاها قرار بده.

مثلاً Heading رو در ستون اول و Image رو در ستون دوم.

اگه بعداً تصمیم گرفت Image رو جابه‌جا کنه، Editor باید اطلاعات مربوط به محل Widget رو تغییر بده.

اگه Widget رو حذف کرد، باید از ساختار صفحه حذف بشه.

اگه Widget جدیدی اضافه کرد، یک رکورد یا داده جدید برای اون ایجاد میشه.

در نتیجه Editor در اصل داره یک ساختار داده رو برای کاربر قابل ویرایش می‌کنه.

ظاهرش ممکنه خیلی پیچیده باشه، اما اتفاقی که پشت صحنه می‌افته در نهایت تغییر همین داده‌هاست.

Drag & Drop هم در اصل همین است

یکی از جذاب‌ترین قسمت‌های Page Builder معمولاً Drag & Drop هست.

مثلاً کاربر یک Widget رو می‌گیره و از ستون اول به ستون دوم می‌بره.

از دید کاربر اتفاق خیلی ساده‌ای افتاده.

اما سیستم باید بفهمه که محل Widget تغییر کرده.

یا کاربر یک Layout رو بالاتر از Layout دیگه‌ای قرار داده.

باز هم باید ترتیب ذخیره‌شده تغییر کنه.

پس Drag & Drop خودش بخش اصلی معماری Page Builder نیست.

Drag & Drop فقط یک روش راحت برای تغییر ساختار صفحه است.

همین نکته باعث میشه اگه بعداً بخوایم روش دیگری برای ویرایش صفحه داشته باشیم، مثلاً یک پنل تنظیمات یا حتی تولید صفحه با AI، مجبور نباشیم کل سیستم رو از اول بسازیم.

هر روشی که استفاده کنیم، در نهایت باید همان ساختار داده‌ای را تولید یا تغییر بده.

حالا اطلاعات را به فرانت می‌فرستیم

فرض کنیم کاربر صفحه رو ذخیره کرده و حالا یک بازدیدکننده می‌خواد اون رو ببینه.

فرانت باید اطلاعات صفحه رو دریافت کنه.

مثلاً داده‌ای شبیه این:

{
    "title": "About Us",
    "slug": "about-us",
    "layouts": [
        {
            "type": "two-columns",
            "columns": [
                {
                    "widgets": [
                        {
                            "type": "heading",
                            "data": {
                                "text": "About our company"
                            }
                        },
                        {
                            "type": "text",
                            "data": {
                                "content": "..."
                            }
                        }
                    ]
                },
                {
                    "widgets": [
                        {
                            "type": "image",
                            "data": {
                                "src": "/images/about.jpg",
                                "alt": "Our team"
                            }
                        }
                    ]
                }
            ]
        }
    ]
}

این مثال فقط برای درک ساختاره.

ممکنه API واقعی ما خیلی متفاوت باشه.

اما چیزی که مهمه اینه که فرانت اطلاعات لازم برای ساخت صفحه رو داشته باشه.

حالا یک مسئله مهم داریم:

فرانت با دیدن type: "heading" از کجا بفهمه باید چه چیزی نمایش بده؟

Mapping وارد می‌شود

اینجا می‌رسیم به Mapping.

فرض کنید در فرانت چند کامپوننت مختلف داریم:

heading
text
image
hero
pricing
faq

می‌تونیم یک Mapping داشته باشیم که هر نوع Widget رو به کامپوننت مربوط به خودش وصل کنه.

مثلاً:

heading → Heading
text → Text
image → Image
hero → Hero
pricing → Pricing
faq → FAQ

حالا وقتی فرانت داده‌ای با type: "hero" دریافت می‌کنه، می‌دونه باید کامپوننت Hero رو نمایش بده و data مربوط به اون رو هم بهش بده.

این همون ایده Mapping هست که قبلاً در مقاله پیاده‌سازی فیلتر داینامیک با Next.js درباره‌ش صحبت کردیم.

اونجا هم یک مقدار مشخص داشتیم و بر اساس اون مقدار تصمیم می‌گرفتیم چه چیزی باید نمایش داده بشه.

اینجا هم تقریباً همین اتفاق می‌افته.

به جای اینکه برای هر Widget کلی شرط مختلف بنویسیم، نوع Widget رو می‌خونیم و از روی Mapping کامپوننت مناسب رو پیدا می‌کنیم.

Layoutها هم می‌توانند Mapping داشته باشند

همین کار رو می‌تونیم برای Layoutها هم انجام بدیم.

مثلاً اگه اطلاعات صفحه بگه:

type: two-columns

فرانت می‌فهمه باید Layout مربوط به دو ستون رو نمایش بده.

اگر:

type: three-columns

باشه، Layout سه ستونه نمایش داده می‌شه.

پس Layout هم مثل Widget یک نوع مشخص داره و فرانت می‌تونه بر اساس اون نوع تصمیم بگیره چه ساختاری رو رندر کنه.

بعد داخل اون Layout، Widgetهای مربوط به هر قسمت نمایش داده می‌شن.

اینجا دیگه ارتباط بین داده و UI خیلی ساده میشه.

چرا این مدل قابل توسعه است؟

فرض کنید چند ماه بعد تصمیم گرفتیم یک Widget جدید به اسم Video اضافه کنیم.

اگه ساختارمون درست طراحی شده باشه، لازم نیست سیستم Page Builder رو از اول تغییر بدیم.

یک Widget جدید داریم.

داده‌هایی که Video نیاز داره رو مشخص می‌کنیم.

کامپوننت مربوط به Video رو در فرانت می‌سازیم.

Mapping مربوط به video رو اضافه می‌کنیم.

حالا Editor می‌تونه Video رو به عنوان یک Widget جدید به کاربر پیشنهاد بده و فرانت هم می‌دونه وقتی type: "video" دریافت کرد، باید چه چیزی نمایش بده.

برای Layout هم همین اتفاق می‌افته.

اگه یک Layout جدید با Sidebar اضافه کنیم، فقط ساختار و Mapping مربوط به اون رو اضافه می‌کنیم.

این یکی از مزیت‌های اصلی جدا کردن Page، Layout و Widget از یکدیگه هست.

یک تصمیم مهم: HTML را ذخیره کنیم یا داده را؟

اینجا احتمالاً یک سؤال مهم پیش میاد.

چرا وقتی کاربر صفحه رو ساخت، کل HTML نهایی رو ذخیره نکنیم؟

در نگاه اول شاید ساده‌تر به نظر برسه.

کاربر صفحه رو می‌سازه، ما HTML نهایی رو تولید می‌کنیم و همون رو ذخیره می‌کنیم.

فرض کنید بعداً ظاهر Widget مربوط به Pricing رو تغییر بدیم.

اگه اطلاعات اصلی صفحه فقط HTML باشه، سیستم خیلی سخت‌تر می‌تونه بفهمه کدوم قسمت مربوط به Pricing بوده.

یا اگه بخوایم کاربر بتونه یک Widget رو دوباره ویرایش کنه، باید HTML رو بررسی کنیم تا بفهمیم چه اطلاعاتی داخلش قرار گرفته.

در مقابل، اگه داده رو ذخیره کرده باشیم، سیستم از قبل می‌دونه:

این یک Widget از نوع Pricing هست.

این هم اطلاعات مربوط به اون.

پس معمولاً منطقی‌تره که ساختار و داده صفحه رو ذخیره کنیم و HTML نهایی رو در زمان نمایش تولید کنیم.

اینجوری بگم که به جای اینکه خروجی رو ذخیره کنیم، دستور ساخت خروجی رو ذخیره می‌کنیم.

Preview چطور کار می‌کند؟

Preview هم با همین منطق قابل پیاده‌سازی هست.

Editor در حال تغییر دادن داده صفحه‌ست.

وقتی کاربر Preview رو باز می‌کنه، همون داده‌ها رو می‌گیریم و با همون Mappingها صفحه رو نمایش میدیم.

فقط تفاوت اینه که در حالت Editor ابزارهایی مثل Drag & Drop، حذف Widget، تنظیمات و انتخاب Layout رو داریم.

در Preview این ابزارها وجود ندارن.

بنابراین اگه از ابتدا Widgetها و Layoutها رو مستقل از Editor طراحی کرده باشیم، پیاده‌سازی Preview کار خیلی پیچیده‌ای نیست.

حالا اگر بخواهیم یک Page Builder بزرگ‌تر بسازیم چه؟

تا اینجا یک مدل نسبتاً ساده داشتیم.

اما وقتی بخوایم یک Page Builder جدی‌تر بسازیم، امکانات بیشتری وارد داستان می‌شن.

مثلاً ممکنه بخوایم برای هر Widget تنظیمات Responsive داشته باشیم.

کاربر مشخص کنه یک Widget در موبایل نمایش داده بشه یا نه.

یا اندازه فونت در موبایل و دسکتاپ متفاوت باشه.

یا فاصله‌های مختلفی برای اندازه‌های مختلف صفحه تعریف بشه.

ممکنه بخوایم Widgetها قابل تکرار باشن.

ممکنه بخوایم یک Layout داخل Layout دیگه‌ای قرار بگیره.

ممکنه بخوایم کاربر بتونه یک بخش رو Duplicate یا کپی کنه.

یا Undo و Redo داشته باشیم.

حتی ممکنه بخوایم چند نسخه مختلف از یک Page رو نگه داریم تا اگه کاربر تغییری داد، بتونه به نسخه قبلی برگرده.

بعد از مدتی خود Editor هم تبدیل به یک پروژه نسبتاً بزرگ میشه.

اما نکته اینجاست که این قابلیت‌ها نباید باعث بشن مدل اصلی رو فراموش کنیم.

ما همچنان داریم یک ساختار قابل تغییر برای یک Page نگه می‌داریم.

فقط این ساختار به مرور اطلاعات بیشتری پیدا می‌کنه.

در نهایت چه چیزی ساختیم؟

اگه بخوام کل ایده رو ساده کنم، ما یک سیستم داریم که اجازه میده یک Page از بخش‌های مختلف تشکیل بشه.

هر بخش یک Layout مشخص داره که چیدمان اون قسمت رو تعیین می‌کنه.

داخل Layoutها Widget قرار می‌گیره و هر Widget نوع و داده خودش رو داره.

این اطلاعات ذخیره میشن و زمانی که صفحه درخواست میشه، به فرانت ارسال میشن.

فرانت هم با توجه به نوع Layout و Widget، کامپوننت مناسب رو پیدا می‌کنه و اطلاعات مربوط به اون رو در اختیارش قرار میده.

در نتیجه صفحه‌ای که کاربر در Editor ساخته،توی فرانت ساخته و نمایش داده بشه.

و بنظرم خوبه که اینجوری فکر کنیم:

Page Builder در اصل یک ابزار برای ساختن و ویرایش یک ساختار داده هست.

چیزی که کاربر می‌بینه یک Editor جذاب با Drag & Drop و کلی ابزار مختلفه، اما پشت صحنه ما داریم اطلاعاتی رو مدیریت می‌کنیم که مشخص می‌کنن یک Page چه بخش‌هایی داره، هر بخش چه Layoutی داره، Widgetها کجا قرار گرفتن و هر Widget چه اطلاعاتی داره.

بعد فرانت همین اطلاعات رو می‌گیره و از روی اون‌ها صفحه رو می‌سازه.

البته همونطور که اول مقاله هم گفتم، این تنها راه پیاده‌سازی چنین سیستمی نیست.

حتی ممکنه برای یک پروژه خاص، ذخیره کردن کل ساختار صفحه به شکل دیگه‌ای منطقی‌تر باشه یا اصلاً مفهوم Layout و Widget رو متفاوت تعریف کنیم.

فقط خواستم اگه یک روز تصمیم گرفتیم برای یک محصول Page Builder بسازیم، بدونیم مسئله از کجا شروع میشه و پشت صحنه این قابلیت‌هایی که از بیرون خیلی ساده به نظر می‌رسن، چه ساختاری دارن.

چون در نهایت چیزی که کاربر می‌بینه فقط یک صفحه هست.

ولی برای اینکه اون صفحه رو خودش ساخته باشه، ما باید راهی داشته باشیم که ساختار اون صفحه رو ذخیره کنیم، تغییر بدیم و دوباره از روی همون داده‌ها نمایش بدیم.

خب تا همینجا کافیه :)

نکته،پیشنهاد یا هر موردی بود توی دیدگاه‌ها بنویسید..

فعلا تا بعد 👋

ارسال دیدگاه

دیدگاه و یا پرسش خود را برای ما ارسال کنید.

وارد شوید

برای ارسال دیدگاه یا پرسش خود ابتدا وارد سایت شوید

ورود یا ثبت نام

دیدگاه کاربران

هنوز دیدگاه یا پرسشی ایجاد نشده است :/

تازه‌ترین نوشته‌ها

دیدن همه

تجربه‌ها، دیدگاه‌ها و نکات الهام‌بخشی که با شما به اشتراک می‌گذاریم.