التوجيه في مكتبة مـنصة_ويب

التوجيه (Routing) من العناصر المهمة في أي مكتبة ويب، وهو أيضًا واحد من الأمور التي تدعهما لغة الأسس بشكل متميز. التوجيه في مـنصة_ويب يوفر المزايا التالية:

  • تحميل الصفحة المطابقة للعنوان تلقائيا.
  • التنقل بين الصفحات دون إعادة التحميل.
  • تحديث شريط العنوان عند التنقل بين الصفحات.
  • التجاوب مع زر الرجوع في المتصفح دون إعادة تحميل الموقع.
  • تطبيق المؤثرات الحركية عند الانتقال بين الصفحات وتمكين المستخدم من إنشاء مؤثراته الخاصة.
  • توفير نمطين من التنقل بين الصفحات، نمط الانتقال عبر استبدال صفحة بأخرى، ونمط الانتقال المؤقت عبر تحميل صفحة فوق الصفحة الحالية بهدف إزالتها لاحقا والعودة إلى الصفحة السابقة.

النقطة الأخيرة هي ما نرغب بالوقوف عنده في هذا المقال لتوضيحه، فمكتبة مـنصة_ويب تدعم هذه الخاصيات بشكل فريد لم أره في أي مكتبة ويب أخرى.

المبدال والمكداس

مـنصة_ويب تدعم عمليات التوجيه عبر توفير مركبين (component) أساسيين مخصصين لهذه العملية: مـبدال (Switcher) و مـكداس (Stack). يتيح المبدال التبديل بين الصفحات عبر إزالة الصفحة الحالية من الذاكرة وإنشاء صفحة مختلفة، بينما المكداس يمكن المستخدم من تحميل الصفحات فوق بعض لإتاحة العودة لاحقًا إلى الصفحات السابقة دون خسارة بياناتها وحالتها.

يمكن للمستخدم جعل هذين المركبين يملآن الشاشة كاملة أو جزءًا منها، كما يمكنه إنشاء نسخ متعددة منها وتركيبها في تطبيقه بأي تشكيلة يريد، ما يتيح مرونة عالية لإنشاء أي خريطة لموقعه. على سبيل المثال يمكن للمستخدم إنشاء مكداس وبداخله مبدال، فيكون المبدال مسؤولا عن التبديل بين صفحات الموقع، بينما يستخدم المكداس لعرض نوافذ منبثقة أو صفحات مؤقتة تظهر فوق الموقع (أي فوق المبدال) أيا كانت الصفحة التي يقف عليها المستخدم.

وكذلك يمكن للمستخدم فعل العكس، أي إنشاء مبدال وتضمين مكداس ضمن أحد مشاهده. فيستخدم المكداس في هذه الحالة لعرض نافذة مؤقتة تظهر فوق تلك الصفحة من الموقع فقط وليس كامل الموقع، أي أن الانتقال إلى صفحة أخرى يزيل الصفحة الحالية مع الصفحة المنبثقة التابعة لها.

يمكن أيضًا إنشاء مبدالات أو مكداسات متعددة حسب رغبة المستخدم وبأي تشكيل يريد. على سبيل المثال، يمكن إنشاء مبدال رئيسي لصفحات الموقع الرئيسية، ثم في أحد الصفحات تضمين مبدال آخر ليتولى مسؤولية الانتقال بين صفحات فرعية ضمن الصفحة الرئيسية.

التوجيه

الصنفان الأساسيان مـبدال (Switcher) و مـكداس (Stack) مسؤولان فقط عن توفير عملية التبديل بين عدة مشاهد، وهي العملية الأساسية التي يُبنى عليها التوجيه، لكنهما لا يوفران عملية توجيه حقيقة تربط العنوان بالصفحة المطلوبة، فهذه العملية مناطة بالصنفين مـبدال_توجيه (RoutingSwitcher) و مـكداس_توجيه (RoutingStack) الذين يعتمدان على الصنفين السابقين لتوفير عملية الانتقال بين المشاهد ويربطانها بعنوان الصفحة في المتصفح.

في كلا الصنفين يحتاج المستخدم لتحديد مجموعة من المسارات مع دالة مغلفة لكل مسار تُنشئ المشهد المطلوب لذلك المسار. كلا الصنفين يعتمد على التعبيرات النمطية (regular expressions) لتحديد المشهد الواجب عرضه، مع اختلاف في الخوارزمية المتبعة كما موضح أدناه.

لكن، قبل الخوض في التفاصيل يجب التنويه إلى أن كل هذا يعتمد على أنك تضع الموقع كله في منفذ مرئي واحد، أي أنك بدل الاعتماد على عدة دالات معلمة بالمبدل @منفذ_مرئي (@uiEndpoint) كل واحدة مخصصة لمسار، فإنك بدلا من ذلك تعتمد على دالة واحدة تشمل كل المسارات التي سيغطيها مركبا التوجيه المذكوران، ويتم ذلك باستخدام علامة * في مسار المنفذ. على سبيل المثال:

@منفذ_مرئي["/*"]
@عنوان["مثال منصة الويب"]
دالة رئيسي {  
    ...  
}
@uiEndpoint["/*"]
@title["WebPlatform Example"]
func main {
}

في المثال أعلاه نخبر مـنصة_ويب أن توجه كل مسار يبدأ ب / إلى نفس الدالة، وبالتالي ننقل عملية التوجيه من الخادم إلى المتصفح بالكامل. بالطبع يمكنك حصر المجال في مسار أصغر، كأن تستخدم مسارا محددا يليه *، مثلا استخدام /ui/* لإعلام مـنصة_ويب أن أي مسار يبدأ ب /ui/ يجب توجيهه لهذه الدالة.

جدير بالذكر أن مـنصة_ويب تعتمد أولويات في تحديد المسارات فتُعطي الأولوية لمسارات المنافذ البيانية ثم لمسارات الموارد قبل مسارات المنافذ المرئية، وبالتالي فإن تحديد مسار المنفذ المرئي ك /* لن يمنع الوصول إلى المنفذ البياني /api/login على سبيل المثال.

مـبدال_توجيه (RoutingSwitcher)

الخوارزمية التي يتبعها مـبدال_توجيه بسيطة جدا، فهو يدور على التعريفات المتوفرة بالتسلسل مطابقا تعبيرها النمطي مع العنوان الحالي كاملا، فإن تطابق يعرض ذلك المشهد ويتوقف وإن لم يتطابق يستمر للتعريف التالي وهكذا.

مـبدال_توجيه().{
    مسار("^/one$") = مغلفة(حـمولة_مسار): سـندنا[ضـبيطة] {
        أرجع مـشهد1()؛
    }؛
    مسار("^/two$") = مغلفة(حـمولة_مسار): سـندنا[ضـبيطة] {
        أرجع مـشهد2()؛
    }؛
    مسار("^/three$") = مغلفة(حـمولة_مسار): سـندنا[ضـبيطة] {
        أرجع مـشهد3()؛
    }؛
}؛
RoutingSwitcher().{
    route("^/one$") = closure(RoutePayload): SrdRef[Widget] {
        return View1();
    };
    route("^/two$") = closure(RoutePayload): SrdRef[Widget] {
        return View2();
    };
    route("^/three$") = closure(RoutePayload): SrdRef[Widget] {
        return View3();
    };
}

لكن، ماذا لو أردنا تضمين مـبدال_توجيه ضمن آخر، كأن يكون في المركب مـشهد3 مبدال آخر للتنقل بين صفحات داخلية؟ لنفترض أن المسارات الفرعية هي:

  • /three/subview1
  • /three/subview2

في هذه الحالة علينا أن نحسب حساب هذه المسارات في التعبيرات النمطية لكل من المبدال الخارجي والداخلي. فنغير مسار المبدال الخارجي من ^/three$ إلى ^/three، أي بإزالة $ لنسمح بإنشاء المشهد 3 عند مطابقة المسارات الفرعية، وإلا فإن المبدال الداخلي لن يُنشأ أساس ولن نتمكن من عرض الصفحات الداخلية. كذلك فإن مسارات المبدال الداخلي يجب أن تحسب حساب وجود الجزء three في العنوان، فإما أن تكتب العنوان كاملا أو أن تكتب العنوان الفرعي دون استخدام علامة ^.

مـكداس_توجيه (RoutingStack)

مكداس التوجيه يعمل بطريقة مختلفة، فهو بدل أن يدور على كل المشاهد بحثا عن مشهد واحد مطابق، يفعل العكس بأن يدور على كل المشاهد بحثا عن أول مشهد غير مطابق، فيستمر بإنشاء المشاهد واحدا فوق الآخر حتى يصل مشهدا مساره غير مطابق ويتوقف عندها. لذلك يحتاج المستخدم لضمان أن كل مشهد يعرف مسارا يطابق المشاهد التي قبله كلها وإلا فإنه لن يُعرض أبدا. على سبيل المثال:

مـكداس_توجيه().{
    مسار(نـص("^/one")) = مغلفة(حـمولة_مسار): سـندنا[ضـبيطة] {
        أرجع مـشهد1()؛
    }؛
    مسار(نـص("^/one/edit")) = مغلفة(حـمولة_مسار): سـندنا[ضـبيطة] {
        أرجع مـحرر()
    }؛
}؛
RoutingStack().{
    route(String("^/one")) = closure(RoutePayload): SrdRef[Widget] {
        return View1();
    };
    route(String("^/one/edit")) = closure(RoutePayload): SrdRef[Widget] {
        return Editor();
    };
};

فإن كان المسار الحالي /one فسيُعرض مـشهد1، أما إن كان المسار one/edit فسيعرض مـشهد1 وفوقه مـحرر.

ماذا لو أن لك أكثر من مشهد فرعي تود عرضها فوق المشهد الحالي؟ كأن يكون لك one/edit و one/share؟ لتلبية هذا الطلب يمكنك جعل المشهد الفرعي مبدالا يقرر أي المشهدين يحمل. مثلا:

مـكداس_توجيه().{
    مسار(نـص("^/one")) = مغلفة(حـمولة_مسار): سـندنا[ضـبيطة] {
        أرجع مـشهد1()؛
    }؛
    مسار(نـص("^/one/(edit|share)")) = مغلفة(حـمولة_مسار): سـندنا[ضـبيطة] {
        أرجع مـبدال_توجيه().{
            مسار("^/one/edit$") = مغلفة(حـمولة_مسار): سـندنا[ضـبيطة] {
                أرجع مـحرر()؛
            }؛
            مسار("^/one/share$") = مغلفة(حـمولة_مسار): سـندنا[ضـبيطة] {
                أرجع مـشارك()؛
            }؛
        }؛
    }؛
}؛
RoutingStack().{
    route(String("^/one")) = closure(RoutePayload): SrdRef[Widget] {
        return View1();
    };
    route(String("^/one/(edit|share)")) = closure(RoutePayload): SrdRef[Widget] {
        return RoutingSwitcher().{
            route("^/one/edit$") = closure(RoutePayload): SrdRef[Widget] {
                return Editor();
            };
            route("^/one/share$") = closure(RoutePayload): SrdRef[Widget] {
                return Share();
            };
        };
    };
};

التنقل دون إعادة التحميل

الاعتماد على الارتباطات التشعبية (hyperlinks) التقليدية سيمكنك من التنقل بين الصفحات، لكن النقر على ارتباط تشعبي تقليدي سيؤدي لإعادة تحميل الصفحة، ولتجنب إعادة التحميل نستخدم دالة ادفع_مسارا (pushLocation) ضمن الصنف نـافذة (Window). على سبيل المثال:

نـافذة.النموذج.ادفع_مسارا("/one/edit")؛
Window.instance.pushLocation("/one/edit");

سيؤدي هذا الأمر إلى تحديث العنوان في المتصفح ثم الانتقال إلى المشهد المطلوب. وستتكفل مـنصة_ويب في عرض المشهد الجديد مع تطبيق المؤثرات الحركية المطلوبة. كذلك فإن الكبس بعد ذلك على زر الرجوع في المتصفح سيؤدي إلى العودة إلى الصفحة السابقة مع تطيبق المؤثرات الحركية دون إعادة تحميل الصفحة.

لكن هذا لا يعني أن نتجنب استعمال الارتباطات التشعبية، فللارتباطات التشعبية فائدة مهمة، وهي إعلام محركات البحث بالصفحات التي يمكن الانتقال إليها. في حال كانت الصفحة خاصة وغير معنية بمحركات البحث فيمكنك تجاهل الارتباطات التشعبية، أما إن كانت صفحة عامة نرغب لمحركات البحث بفهرستها فالأفضل الاعتماد على الارتباطات التشعبية مع الاعتماد على دالة ادفع_مسارا، كما في هذا المثال:

ارتـباط_تشعبي().{
    العنوان = نـص("/about")؛
    حدد_الفرع(كـتابة(نـص("عن الموقع")))؛
    عند_الضغط.المبدئي_مكبوح = 1؛
    عند_الضغط.اربط(مغلفة (سند[ودجـة]، سند[صـحيح]) {
        نـافذة.النموذج.ادفع_مسارا("/about")؛
    })؛
}؛
Hyperlink().{
    url = String("/about");
    setChild(Text(String("About")));
    onClick.defaultPrevented = 1;
    onClick.connect(closure (ref[Widget], ref[Int]) {
        Window.instance.pushLocation("/about");
    });
};

في المثال أعلاه نستخدم ارتباطا تشعبيا لكننا نغير تصرفه عند الضغط عليه لنجعله يستخدم دالة ادفع_مسارا بدل الاعتماد على التصرف المبدئي الذي يعيد تحميل الصفحة، وبالتالي نكسب الأمرين، تنقل مع مؤثرات بلا إعادة تحميل، وروابط في الموقع تقرأها محركات البحث.

المؤثرات

المبدال والمكداس يدعمان المؤثرات الحركية عند الانتقال من مشهد لآخر ويمكننان المستخدم من تحديد هذه المؤثرات، إما بتعريف أسلوب التحريك أو الاختيار من بين مجموعة من المؤثرات المعرفة مسبقا. يتم تحديد المؤثرات الحركية باستخدام الدالة حدد_الانتقال (setTransition) المتوفرة في كل من مـبدال_توجيه (RoutingSwitcher) و مـكداس_توجيه (RoutingStack) والتي تستلم معطيين، الأول يحدد المؤثر عند الانتقال من مشهد أدنى إلى مشهد أعلى (حسب تسلسلها في قائمة المشاهد) والآخر لتحديد الانتقال بالعكس. على سبيل المثال، لتحديد الانتقال في المبدال يمكن استخدام الأمر التالي:

حدد_الانتقال(
    أنشئ_انتقال_انزلاق_مبدال(0.5, 0، 1),
    أنشئ_انتقال_انزلاق_مبدال(0.5, 0، 0)
)؛
setTransition(
    createSlideSwitcherTransition(0.5, 0, 0),
    createSlideSwitcherTransition(0.5, 0, 1)
);

الدالة أعلاه تستخدم تأثير الانزلاق، أي أنزلاق المشهد الجديد من وإلى خارج الشاشة. المعطى الأول يحدد المدة الزمنية للانزلاق بالثواني، والثاني يحدد إن كان الانزلاق عموديا (1) أو أفقيا (0)، أما المعطى الثالث فيحدد اتجاه الحركة دخولًا أو خروجًا.

تتوفر الدالات التالية لإنشاء مؤثرات المبدال:

  • أنشئ_انتقال_انزلاق_مبدال (createSlideSwitcherTransition)
  • أنشئ_انتقال_تلاشي_مبدال (createFadeSwitcherTransition)

والدالات التالية لإنشاء مؤثرات المكداس:

  • أنشئ_انتقال_انزلاق_مكداس (createSlideStackTransition)
  • أنشئ_انتقال_تلاشي_مكداس (createFadeStackTransition)
  • أنشئ_انتقال_تلاشي_مع_تحجيم_مكداس (createFadeWithScaleStackTransition)

إنشاء مؤثراتك الخاصة أمر سهل أيضًا ويمكنك الاطلاع على تعريفات هذه الدالات في مكتبة مـنصة_ويب للتعرف على طريقة إنشاء هذه المؤثرات.

مثال كامل

المثال التالي في مـنصة ويب يوضح التوجيه باستخدام هذين المركبين مع المؤثرات الحركية، ويتوفر بالهجتين العربية والإنجليزية:

إعجاب واحد (1)