لوحة المتصدرين
المحتوى الأكثر حصولًا على سمعة جيدة
المحتوى الأعلى تقييمًا في 07/05/21 في كل الموقع
-
لدي مصفوفة Numpy كبيرة تحتوي على عمودين فقط كالتالي: a = np.array([ [1, 2,], [3, 4] # ... ]) كيف يمكنني إضافة عمود لهذه المصفوفة بقيم عشوائية باستخدام Numpy للحصول على أسرع نتيجة؟2 نقاط
-
أعتقد أنه لديك مُشكلة في الإستعلام الذي يتم تنفيذه يُمكنك عرض الأخطاء في حالة حدوثها بإستخدام: mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT); في ملف الإتصال، حيث إذا أضفته و جربت إرسال البيانات سيعرض لك رسالة تُخبرك بوجود خطأ ما في الإستعلام. سبب الخطأ هو وجود الكلمة group في الإستعلام حيث أن هذه الكلمة تُعتبر كلمة محجوزة في لغة sql لذلك في هذه الحالة يجب إحاطة الكلمة ب علامتي إقتباس مائلة `group` كما يُمكنك إحاطة كل الحقول بهذه العلامات ليُصبح الإستعلام بهذا الشكل: $query = "INSERT INTO `messages_groups` (`sender_id`, `sender_name`, `picture`, `group`, `message`) VALUES ('".$sender_id."', '".$sender_name."','".$picture."','".$group."','".$message."')";2 نقاط
-
2 نقاط
-
كيف يمكنني إضافة مستخدم وأجعله مديراً ثم يضيف (يسجل) المدير المستخدمين لقاعدة البيانات وبعدها كل مستخدم يستطيع تسجيل الدخول لصفحته بالعملومات التي انشئها المدير. بإختصار اي لا احعل عملية التسجيل عند المستخدم بل عند الإدارة. بإستخدام laravel2 نقاط
-
حسناً أنت محظوظ فهذا النوع من البرمجة كان اختصاصي خلال مشاركاتي في المسابقات البرمجية. عندما تتحدث عن ال memoization فهذا يعني أنك دخلت حقل البرمجة الديناميكية "Dynamic Programming" كبداية سأعرفك بالبرمجة الديناميكية: البرمجة الديناميكية هي أسلوب أو نهج في حل المسائل لتحسين التعقيد الزمني للمسائل التي تحوي "العودية". الفكرة هي ببساطة تخزين نتائج المشكلات الفرعية "Subproblems"، حتى لا نضطر إلى إعادة حسابها عند الحاجة لها لاحقاً. وهذا مايؤدي إلى تقليل التعقيد الزمني للعديد من المسائل من تعقيد أسي إلى تعقيد خطي polynomail. على سبيل المثال، إذا كتبنا حلًا عودياً بسيطًا لأرقام فيبوناتشي، فإننا نحصل على تعقيد زمني أسي، وإذا قمنا بتحسينه عن طريق تخزين حلول المشكلات الفرعية، فإن تعقيد الوقت ينخفض إلى خطي. # تايع لحساب أعداد فيبوناتشي بالطريقة العودية def Fib(n): if n<0: print("error") elif n==0: return 0 elif n==1: return 1 else: return Fib(n-1)+Fib(n-2) print(Fib(5)) # هذا الكود تعقيده أسي وهذا سيئ وغير فعال fib(5) / \ fib(4) fib(3) / \ / \ fib(3) fib(2) fib(2) fib(1) / \ / \ / \ fib(2) fib(1) fib(1) fib(0) fib(1) fib(0) / \ fib(1) fib(0) #تستدعى مرتين fib 3 يمكن مثلاً ملاحظة أنّ الدالة #وإن تسنّى لنا تخزين القيمة الناتجة عن استدعاء هذه الدالة، فستنتفي الحاجة إلى إعادة حساب هذه القيمة وسيكون بالإمكان استخدام القيمة المخزّنة سابقًا عوضًا عن ذلك. أما عند حله باستخدام مفهوم البرمجة الديناميكية: # Dynamic Programming def Fib(n): dp = [0, 1] for i in range(2, n+1): dp.append(dp[i-1] + dp[i-2]) return dp[n] print(Fib(5)) لكن يجب أن تعلم أن ليس كل مسألة عودية أو أي مسألة تحوي استدعاءات متكررة يمكن أن يكون لها حل باستخدام البرمجة الديناميكية، هناك شرطين يجب أن تحققهما أي مسألة قبل أن نحاول حلها باستخدام هذا المفهوم، فإذا تحققا هذا يعني أنه يمكن حلها في ال DP، وإلا فلا تحاول، وهما: مسائل فرعية متداخلة "Overlapping Subproblems": أي هل يمكن تقسيم المسألة إلى مسائل فرعية أصغر، أو إلى مهام أصغر، أو بمعنى آخر هل يمكننا تقسيم المسألة إلى مسائل أصغر بحيث تجتمع هذه المسائل لحل المسألة الأكبر. حيث أن البرمجة الديناميكية تشبه نموذج فرّق تسد في أنها تدمج حلول المسائل الفرعية بعضها ببعض. تستخدم البرمجة الديناميكية عمومًا عندما تظهر الحاجة إلى استخدام حلول مجموعة معينة من المسائل الفرعية مرة بعد أخرى. تخزّن حلول المسائل الفرعية في البرمجة الديناميكية في مصفوفة وذلك لتجنّب حسابها مرة أخرى. هذا يعني أنّ البرمجة الديناميكية غير مفيدة عندما لا تكون هناك مسائل فرعية متداخلة؛ إذ لا فائدة من تخزين الحلول إن لم تكن الخوارزمية بحاجة إليها مرة أخرى. قمثلاً تتضمن عملية حساب أعداد فيبوناتشي بطريقة تعاودية العديد من المسائل الفرعية التي يجري حلّها مرة تلو الأخرى مثل fib3 كما رأينا. بنية فرعية مثالية "Optimal Substructure":نقول أنّ مسألة معيّنة تمتلك بنية فرعية مثالية إذا كان بالإمكان الحصول على الحل المثالي للمسألة المعطاة بجمع الحلول المثالية للمسائل المتفرّعة عن المسألة الرئيسية. الآن سأنتقل لك لمفهوم ال Memoization.. نحن قلنا أنه سيتم تخزين الحلول، حسناً كيف سيتم تخزينها؟ هناك طريقتين للقيام بذلك: التحفيظ Memoization (من الأعلى إلى الأسفل): تشبه طريقة التحفيظ في حل مسألة معينة الطريقة التعاودية ولكن مع تعديل بسيط، وهو أنّ هذه الطريقة تبحث في جدول البحث lookup table معين قبل حساب الحلول. تبدأ العملية بتهيئة مصفوفة بحث تحمل القيمة NIL كقيمة أولية. وفي كل مرّة نحتاج فيها إلى إيجاد حلٍّ لمسألة فرعية، نبدأ بالبحث في جدول البحث، فإن كانت القيمة محسوبة مسبقًا موجودة فيه فسنعيد حينئذٍ تلك القيمة، وإلا سنحسب القيمة ونضع النتيجة في جدول البحث ليتسنى لنا إعادة استخدامها في وقت لاحق. مثال لاستخدام هذه الطريقة مع المسألة السابقة: # الدالة المسؤولة عن حساب العدد ذي الترتيب المعطى في متتالية فيبوناتشي def fib(n, lookup): # الحالة الأساس if n == 0 or n == 1 : lookup[n] = n # إن لم تكن القيمة محسوبة مسبقًا فسنحسبها الآن if lookup[n] is None: lookup[n] = fib(n-1 , lookup) + fib(n-2 , lookup) # n تعيد الدالة القيمة المرتبطة بالقيمة المعطاة return lookup[n] # اختبار الدالة السابقة def main(): n = 34 # إنشاء جدول البحث # يستوعب الجدول 100 عنصر lookup = [None]*(101) print "Fibonacci Number is ", fib(n, lookup) if __name__=="__main__": main() 2.الجدولة Tabulation (من الأسفل إلى الأعلى): تبني هذه الطريقة جدولًا من الأسفل إلى الأعلى وتعيد آخر قيمة من الجدول عند الحاجة. فعلى سبيل المثال، تحسب دالة فيبوناتشي الأعداد في المتتالية حسب التسلسل fib(0) ثم fib(1) ثم fib(2) ثم fib(3) وهكذا. وهذا يعني أنّنا نبني جدول الحلول للمسائل الفرعية من الأسفل إلى الأعلى حرفياً. وهي ذات الطريقة التي استخدماتها لتخزين الحلول للمسألة في البداية. مثال آخر للتحفيظ: لنفترض أن لدينا الأعداد {1, 3, 5} والمطلوب هو إيجاد عدد الطرق التي يمكن استخدام هذه الأرقام فيها لحساب رقم معيّن (ليكن N) وذلك بإجراء عملية الجمع مع السماح بتكرار الأعداد وتغيير ترتيبها. لكي نتمكّن من حلّ هذه المسألة ديناميكيًا يجب في البداية اتخاذ قرار بشأن الحالة الخاصة بالمسألة المعطاة، وسنستخدم معاملًا (ليكن n) لاتخاذ القرار بشأن الحالة الراهنة وذلك لإمكانية استخدام هذه المعامل في تشخيص أي مسألة فرعية بطريقة فريدة. وبهذا تكون الحالة في هذه المسألة هي state(n)، والتي تمثّل هنا العدد الكلي للترتيبات التي يمكن استخدامها لتكوين العدد n باستخدام العناصر {1, 3, 5}. ولمّا كان بالإمكان استخدام الأعداد 1 و 3 و 5 فقط لتكوين العدد المعطى، سنفترض أنّنا نعرف نتائج الأعداد n = 1, 2, 3, 4 , 5, 6 مسبقًا، وبمعنى آخر فإنّنا نعرف نتائج كلّ من state (n = 1), state (n = 2), state (n = 3) ……… state (n = 6). // تعيد الدالة عدد الترتيبات لتكوين العدد المعطى int solve(int n) { // الحالة الأساس if (n < 0) return 0; if (n == 0) return 1; return solve(n-1) + solve(n-3) + solve(n-5); } تعمل الشيفرة السابقة بتعقيد زمني أسّي وتحسب الحالة نفسها مرارًا وتكرارًا؛ ولهذا يجب إضافة عملية التحفيظ memoization. إضافة عملية التحفيظ إليها:: // تهيئة القيمة لتكون -1 int dp[MAXN]; // تعيد هذه الدالة عدد الترتيبات لتكوين العدد `n` int solve(int n) { // الحالة الأساس if (n < 0) return 0; if (n == 0) return 1; // التحقق من أنّ الحالة الراهنة محسوبة مسبقًا if (dp[n]!=-1) return dp[n]; // تخزين النتيجة وإعادتها return dp[n] = solve(n-1) + solve(n-3) + solve(n-5); }2 نقاط
-
أريد دالة نمطية من numpy يمكنها العثور على القيمة العظمى/الصغرى المحلية local maxima/minima في مصفوفة عددية أحادية الأبعاد، هل توجد دالة / طريقة تقوم بهذا الأمر من خلال مكتبة Numpy فقط؟1 نقطة
-
1 نقطة
-
مكتبة numpy مفيدة للغاية في التعامل مع المصفوفات والأرقام، يمكنها التعامل مع مصفوفات كبيرة للغاية قد تصل إلى 1000 × 1000 بدون ادنى مشكلة، لكن يبدو أني أواجهة مشكلة عند التعامل مصفوفات أكبر من هذا (5000 × 5000 على سبيل المثال) بالتأكيد بسبب حجم الذاكرة العشوائية الصغير لدي، هل توجد طريقة أفضل لإنشاء مصفوفات غاية في الضخامة (مليون صف × مليون عمود على سبيل المثال)، دون الحاجة إلى مساحة ذاكرة عشوائية ضخمة؟1 نقطة
-
السلام عليكم اخواني الاعزاء لدي مشكلة لا اعرفها وهي ان البيانات لا يتم ارسالها للقاعدة الجدول CREATE TABLE `messages_groups` ( `id` int(11) NOT NULL AUTO_INCREMENT, `sender_id` varchar(256) NOT NULL, `sender_name` varchar(256) NOT NULL, `picture` varchar(256) NOT NULL, `group` varchar(256) NOT NULL, `message` text NOT NULL, PRIMARY KEY ( `id` ) )ENGINE=MyISAM DEFAULT CHARSET=utf8; connect_file.php <?php $DATABASE_HOST = 'localhost'; $DATABASE_USER = 'root'; $DATABASE_PASS = ''; $DATABASE_NAME = 'test'; $db = mysqli_connect($DATABASE_HOST, $DATABASE_USER, $DATABASE_PASS, $DATABASE_NAME); // Check connection if($db === false){ die("ERROR: Could not connect. " . mysqli_connect_error()); } ?> index.php <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js"></script> <script type="text/javascript"> $(document).ready(function() { //##### Add record when Add Record Button is click ######### $("#messages_form_groups").submit(function(e) { e.preventDefault(); //build a post data structure var sender_id = $("#my_sender_id").val(), sender_name = $("#my_sender_name").val(), picture = $("#my_picture").val(), group = $("#my_group").val(), message = $("#my_message").val(); if (sender_id == '' || sender_name == '' || picture == '' || group == '' || message == '') { alert("All fields are required"); return false; } jQuery.ajax({ type: "POST", // Post / Get method url: "response.php", //Where form data is sent on submission dataType: "text", // Data type, HTML, json etc. data: { sender_id: sender_id, sender_name: sender_name, picture: picture, group: group, message: message }, //Form variables success: function(response) { $("#norec").hide(); $("#responds").append(response); $('#messages_form_groups').trigger('reset'); }, error: function(xhr, ajaxOptions, thrownError) { alert(thrownError); } }); }); }); </script> <form id="messages_form_groups" > <input name="sender_id" id="my_sender_id" value="test"><br> <input name="sender_name" id="my_sender_name" value="test"><br> <input name="picture" id="my_picture" value="test"><br> <input name="group" id="my_group" value="test"><br> <textarea name="message" id="my_message"></textarea> <br> <INPUT type="submit" id="FormSubmit" > </form> <?php include_once("connect_file.php"); $Result = mysqli_query($db, "SELECT * FROM messages_groups"); $count = mysqli_num_rows($Result); ?> <table id="responds" border="1"> <tr> <th>#</th> <th>sender_id</th> <th>sender_name</th> <th>picture</th> <th>group</th> <th>Message</th> </tr> <?php if ($count > 0) : ?> <?php while ($row = mysqli_fetch_array($Result)) : ?> <tr> <td><?= $row['id'] ?></td> <td><?= $row['sender_id'] ?></td> <td><?= $row['sender_name'] ?></td> <td><?= $row['picture'] ?></td> <td><?= $row['group'] ?></td> <td><?= $row['Message'] ?></td> </tr> <?php endwhile; ?> <?php else : ?> <tr id="norec"> <td colspan="4">No Records</td> </tr> <?php endif; ?> </table> response.php <?php //include db configuration file include_once("connect_file.php"); if(isset($_POST["message"]) && strlen($_POST["message"])>0) { $sender_id = mysqli_real_escape_string($db, $_POST["sender_id"]); $sender_name = mysqli_real_escape_string($db, $_POST["sender_name"]); $picture = mysqli_real_escape_string($db, $_POST["picture"]); $group = mysqli_real_escape_string($db, $_POST["group"]); $message = mysqli_real_escape_string($db, $_POST["message"]); $query = "INSERT INTO messages_groups(sender_id, sender_name, picture, group, message) VALUES ('".$sender_id."', '".$sender_name."','".$picture."','".$group."','".$message."')"; if(mysqli_query($db, $query)) { $id = mysqli_insert_id($db); $result = "<tr> <td>$id</td> <td>$sender_id</td> <td>$sender_name</td> <td>$picture</td> <td>$group</td> <td>$message</td> </tr>"; echo $result; } } ?>1 نقطة
-
1 نقطة
-
يمكنك استخدام Widgets SizedBox() , Expanded() بحيث تقوم بإضافة الزر داخل إحدى هذه Widgets بهذا الشكل Expanded( child: TextButton( child: Text("Lang"), onPressed: (){} ), ) أو SizedBox( child: TextButton( child: Text("Lang"), onPressed: (){} ), )1 نقطة
-
يمكنك ايضاً إستخدام الدالة append لإضافة المسار في حال كنت في نفس المجلد الموجود فيه الملف الذي تريد الوصول إليه: import sys, os, inspect currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) parentdir = os.path.dirname(currentdir) sys.path.insert(0, parentdir) import myfile as f يبدأ البرنامج أولا بإستيراد الدوال اللازمة، و من ثم لإستيراد file يجب أن يكون مقروء في PYTHONPATH وهي عبارة عن متغير البيئة الذي يقوم بتحميل كل المكتبات و الملفات التي سيتم تحميلها في بايثون لإتمام عملية التنفيذ وهي ايضاً الملفات التي تكون متوفرة في sys.path يتم التحميل كما هو موضح في السطر الثاني، لاحظ أن الفرق بين append و insert هو أن append تقوم بالإضافة في نهاية sys.path مباشرة لكن insert تقوم بإدراجه في مؤشر محدد كما في حالتنا 0.1 نقطة
-
لا أعتقد انه يوجد كلاس جاهز لذلك يمكنك توسيط العنصر بالعديد من الطرق وتختلف حسب الكود الذي تستخدمه حيث يمكنك استخدام ال flexbox .flex-container { display: flex; align-items: center; justify-content: center; flex-direction: column; }1 نقطة
-
ماذا تقصد؟ إذا كنت تقصد عمل ذلك من خلال css أو التصميم فقط فيمكنك تنفيذ ذلك من خلال ال border يمكنك تصميم الأسهم كالتالي <div id="triangle-up"></div> ثم إضافة التنسيقات #triangle-up { width: 0; height: 0; border-left: 50px solid transparent; border-right: 50px solid transparent; border-bottom: 100px solid red; } والكود السابق يجعل السهم لأعلى ولعمل السهم لأسفل يمكنك استخدام #triangle-down { width: 0; height: 0; border-left: 50px solid transparent; border-right: 50px solid transparent; border-top: 100px solid red; /* bottom بدلاً من top لاحظ */ } وبالنسبة للعداد أو الرقم الوجود بين الأسهم فيمكنك استخدام الجافاسكربت لتنفيذ ذلك كالتالي <h1 class="counter-display">(..)</h1> <button class="counter-minus">-</button> <button class="counter-plus">+</button> <script> let counterDisplayElem = document.querySelector('.counter-display'); let counterMinusElem = document.querySelector('.counter-minus'); let counterPlusElem = document.querySelector('.counter-plus'); let count = 0; updateDisplay(); counterPlusElem.addEventListener("click",()=>{ count++; updateDisplay(); }) ; counterMinusElem.addEventListener("click",()=>{ count--; updateDisplay(); }); function updateDisplay(){ counterDisplayElem.innerHTML = count; }; </script>1 نقطة
-
يمكنك إنشاء حقل باسم isAdmin و قيمته تكون أما 0 أي عضو , 1 أدمن , مدير, في جدول users $table->integer('isAdmin')->default(0); ومن ثم إنشاء Seeder لإضافة يوزر افتراضي للموقع عن طريق تنفيذ الأمر التالي php artisan make:seeder UserSeeder ثم تقوم بفتح هذا الملف من خلال المسار التالي database\seeds ومن ثم تقوم بإضافة بيانات اختبارية أو اليوزر الذي تود إنشائه عن تثبيت المشروع الخاص بك داخل ملف UserSeeder $user = \App\User::create([ 'name' => 'user', 'email' => 'user@user.com', 'password' => Hash::make('123123123'), 'phone' => '', 'isAdmin' => 1, ]); وتعطيه قيمة حقل isAdmin = 1 , ومن ثم تقوم بإنشاء ملف Controller لإضافة مستخدمين عن طريق الأمر التالي php artisan make:controller Admin\UsersController --resource ومن ثم بداخل هذا الملف نقوم بكتابة أكواد إضافة مستخدمين و تعديل مستخدم و حذف مستخدم فيمكن إضافة مستحدمين عن طريق الكود التالي /** * Show the form for creating a new resource. * * @return \Illuminate\Http\Response */ public function create() { $countries = Country::all(); return view('admin.users.create', compact('countries')); } /** * Store a newly created resource in storage. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function store(Request $request) { $request->validate([ 'name' => ['required', 'string', 'max:255'], 'email' => ['required', 'string', 'email', 'max:255', 'unique:users'], 'password' => ['required', 'string', 'min:8', 'confirmed'], ]); $users = new User(); $users->name = $request->name; $users->email = $request->email; $users->phone = $request->phone; $users->password = \Hash::make($request->password); if($request->hasFile('image')){ $img = time() . '.' . $request->file('image')->getClientOriginalExtension(); $users->image = $img; $request->image->move(public_path('assets/users_img'), $img); } $users->save(); \Session::flash('alert-success', trans('lang.success_add')); return redirect()->route('users.index'); } وكامل الملف يكون <?php namespace App\Http\Controllers\Admin; use App\Http\Controllers\Controller; use Illuminate\Http\Request; use App\User; use Illuminate\Support\Facades\Hash; use Str; class UsersController extends Controller { /** * Create a new controller instance. * * @return void */ public function __construct() { } /** * Display a listing of the resource. * * @return \Illuminate\Http\Response */ public function index(UsersDataTable $users) { return view('admin.users.index'); } /** * Show the form for creating a new resource. * * @return \Illuminate\Http\Response */ public function create() { return view('admin.users.create'); } /** * Store a newly created resource in storage. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function store(Request $request) { $request->validate([ 'name' => ['required', 'string', 'max:255'], 'email' => ['required', 'string', 'email', 'max:255', 'unique:users'], 'password' => ['required', 'string', 'min:8', 'confirmed'], ]); $users = new User(); $users->name = $request->name; $users->email = $request->email; $users->phone = $request->phone; $users->password = \Hash::make($request->password); if($request->hasFile('image')){ $img = time() . '.' . $request->file('image')->getClientOriginalExtension(); $users->image = $img; $request->image->move(public_path('assets/users_img'), $img); } $users->save(); \Session::flash('alert-success', trans('lang.success_add')); return redirect()->route('users.index'); } /** * Display the specified resource. * * @param int $id * @return \Illuminate\Http\Response */ public function show(Request $request, $id) { $user = User::find($id); if(! $request->ajax()){ return view('admin.users.show', compact('user')); }else{ return view('admin.users.modal.show', compact('user')); } } /** * Show the form for editing the specified resource. * * @param int $id * @return \Illuminate\Http\Response */ public function edit($id) { $user = User::find($id); $users = User::all(); return view('admin.users.edit', compact('user', 'users')); } /** * Update the specified resource in storage. * * @param \Illuminate\Http\Request $request * @param int $id * @return \Illuminate\Http\Response */ public function update(Request $request, $id) { $request->validate([ 'name' => ['required', 'string', 'max:255'], 'email' => ['required', 'string', 'email', 'max:255', 'unique:users,email,' . $id], ]); $users = User::find($id); $users->name = $request->name; $users->email = $request->email; $users->phone = $request->phone; if($request->hasFile('image')){ $img = time() . '.' . $request->file('image')->getClientOriginalExtension(); $users->image = $img; $request->image->move(public_path('assets/users_img'), $img); } $users->save(); \Session::flash('alert-success', trans('lang.success_update')); return redirect()->route('users.index'); } /** * Remove the specified resource from storage. * * @param int $id * @return \Illuminate\Http\Response */ public function destroy($id) { $user = User::find($id)->delete(); \Session::flash('alert-success', trans('lang.success_delete')); return redirect()->route('users.index'); } } ثم تقوم بإنشاء middleware للتحكم بالأعضاء للسماح لهم بالدخول إلى الملف أو لا عن طريق الأمر التالي php artisan make:middleware IsAdmin ثم نتوجه إلى المسار التالي لفتح الملف IsAdmin.php app\Http\Middleware ونقوم بوضع شرط معين إذا كان العضو الذي يريد الدخول إلى الصفحة قيمة الحقل isAdmin تساوي 1 يمكنه الدخول إلى هذه الصفحة public function handle($request, Closure $next) { if (auth()->guard('web')->user()->isAdmin == 1) { return $next($request); } else { return redirect('/404'); } //return $next($request); } فيكون كامل الملف <?php namespace App\Http\Middleware; use Closure; class IsAdmin { /** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * @return mixed */ public function handle($request, Closure $next) { if (auth()->guard('web')->user()->isAdmin == 1) { return $next($request); } else { return redirect('/404'); } //return $next($request); } } ثم نقوم بإضافة هذا Middleware الذي قمنا بإنشائه إلى ملف Kernel.php داخل المسار التالي app\Http إلى مصفوفة $routeMiddleware = []; بهذه الطريقة 'admin' => \App\Http\Middleware\IsAdmin::class, ثم نقوم بفتح ملف web.php و إضافة المسار إلى هذا الملف ويكون المسار Route::group(['middleware' => ['auth:admins'], 'prefix' => 'admin'], function(){ Route::resource('users', 'Admin\UsersController'); });1 نقطة
-
بداية نحتاج الى الغاء عملية التسجيل والرابط الموجه لصفحة التسجيل, اذا كنت تستخدم حزمة laravel/ui يمكنك فعل ذلك من خلال الامر التالي في ملف web.php Auth::routes(['register' => false]); أما اذا كنت تستخدم jetstream يمكنك الغاء عملية التسجيل من خلال الذهاب الى ملف fortify.php الموجود ضمن المسار الآتي config/fortify.php ثم تقوم بعمل تعليق لهذا السطر Features::registration( بعد ذلك يمكننا انشاء حساب في قاعدة البيانات بشكل يدوي ونجعله مديرا اما باستخدام المساعد Tinker الذي يأتي بشكل تلقائي مع لارافيل أو يمكننا ذلك من خلال قاعدة البيانات بشكل يدوي, والآن يحتاج المدير الى لوحة تحكم يمكنه من خلالها التحكم في كافة أمور الموقع مثل اضافة وتعديل وحذف المستخدمين وغيرها من الأمور حيث يجب علينا تصميمها وبرمجتها , الآن اذا اراد مستخدم جديد التسجيل في الموقع يجب عليه أن يتواصل مع ادارة الموقع لكي توفر له حساب يتسطيع الدخول من خلاله, بعد ان يتم التواصل وانشاء الحساب سوف ترسل له ادارة الموقع معلومات الحساب ويستطيع الدخول من خلالها1 نقطة
-
أشوف أن الأفضل أن تقوم بتطبيق مهاراتك اللي ستتعلمها وتكتسب خبرات أو تتقدم على مشاريع بسيطة في مواقع العمل الحر ( مستقل، بعيد، خمسات..الخ) اكتسب خبرات أثناء تعلمك وقدامك مستقبل باهر شغفك وطموحك مطلوبة في سنك لكن استمر و واصل في مجالك وإن شاء الله ستكون فخور بنفسك بعد فترة وجيزة1 نقطة
-
يبدو أنك أخطأت في شيء ما عند جلب البيانات من القاعدة سأرفق لك الملفين يعملان بشكل سليم: index.php response.php أضفت جدول في أسفل الإستمارة سيحتوي على كافة البيانات و عند إضافة سجل جديد سيظهر صف آخر في الجدول دون الحاجة لتحديث الصفحة.1 نقطة
-
يمكنك ذلك عبر مايسمى بالوسطاء الموضعية أو الاختيارية *args. def echo(*args): for i in args: print(i) myLits = [1, 2, 3, 4] echo(myLits) حيث يمكن تعريف دالة قادرة على أخذ عدد اختياري من الوسطاء عن طريق وسيط واحد مع إضافة الرمز * له، وهذا مفيد عندما لاتعلم عدد الوسطاء التي سوف يتم تمريرها إلى الدالة، وبهذه الطريقة ستكون الدالة قادرة على تلقي مجموعة من الوسطاء، مع إمكانية الوصول إلى أي منها، حيث يفهم مترجم بايثون أن هذا المتحول هو مجموعة من الوسطاء، وبالتالي مهما وضعنا وسطاء سوف يقبلها. def func(*args): # argswill be a tuple containing all values that are passed in for i in args: print(i) func(1, 2, 3) # Calling it with 3 arguments # Out: 1 # 2 # 3 list_of_arg_values = [1, 2, 3] func(*list_of_arg_values) # Calling it with list of values, * expands the list # Out: 1 # 2 # 3 func() # Calling it without arguments # No Output للتنويه: لايمكنك تمرير عدد افتراضي من الوسطاء، فمثلاً: (func(*args=[1, 2, 3]) # خطأ ولايمكن التمرير بالاسم عند استدعاء الدالة: (func(*args=[1, 2, 3]) ولكن إذا كان لديك بالفعل وسطاء كمصفوفة (أو أي نوع آخر)، تستطيع عندها استدعاء الدالة بالشكل: func(*my_stuff) ويمكن الوصول إلى هذه الوسطاء (*args) عن طريق الفهرس، فمثلاُ: args[0] # ترجع قيمة العنص الأول يمكنك تمرير مجموعة من الوسطاء العادية والاختيارية لكن يجب التقيد في التعريف، أي الوسطاء العادية أولاً ثم الاختيارية: def func(p1, p2=10 , *args): #p1 يجب أن نمرر أولاً قيمة ل pass1 نقطة
-
تقوم دالة sort() بفرز عناصر المصفوفة في مكانها وإرجاع المصفوفة التي تم فرزها. ترتيب الفرز الافتراضي تصاعدي ، مبني على تحويل العناصر إلى سلاسل ، ثم مقارنة تسلسل قيم وحدات رمز UTF-16. حيث أي عنصر في المصفوفة يتم تحويله الى نص ، على سبيل المثال إذا كان العنصر رقم فيتم تحويله الى سلسلة نصية string ومن ثم يتم مقارنة السلاسل النصية وفقاً لقيمها في نظام UTF-16 . هذه هي الحالة الإفتراضية لدالة sort أي إذا لم تقم بتمرير دالة مقارنة إليها ، على سبيل المثال let arr = [4,30,100,1] arr.sort() // [1, 100, 30, 4] /* * تم ترتيب العناصر على أنها سلاسل نصية ووفقاً لقيمها في نظام * UTF-16 */ أما إذا قمت بتمرير دالة مقارنة إليها فيتم ترتيب العناصر بناءً على دالة المقارنة ولايتم الترتيب الافتراضي بتحويل العناصر الى سلاسل نصية string ، كما في الكود الخاص بك let numbers =[2, 14, 1, 2, 5]; /* دالة المقارنة تقوم بمقارنة عنصرين متجاورين حيث المتغير * x * هو المتغير الأول والمتغير * y * هو المتغير التالي المجاور للمتغير * x * ويتم إرجاع المتغير الأكبر */ number.sort((x,y) => y - x);1 نقطة
-
من خلال منصات العمل الحر مثل مستقل فبالتأكيد يمكنك العمل بدون مشاكل ولا يشكل العمر عائق في العمل حيث في منصات العمل الحر تنفيذ لعمل على أكمل وجه هو الأولوية أما بالنسبة للشركات فمن الصعب أن تجد وظيفة في هذا العمر ولكن ربما توجد بعض الشركات التي تقبل العمل في هذا العمر وأنا أجد أن محاولة العمل في هذا العمر خطوة جيدة لتكتسب الخبرة مبكراً وما يجب عليك عمله هو التعلم بشكل جيد وكثرة التطبيق1 نقطة
-
كما شرح عبدالمجيد فإنه يمكنك إضافة المسار الذي تريده من خلال ال sys.path ويمكنك إضافة المسار بداخله ولكن إذا أردت أن يكون البرنامج أن يجد المسار الأب تلقائياً يمكنك استخدام ال os.path.abspath() التي تقوم بإرجاع المسار الأب import os, sys sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) import helpers1 نقطة
-
تسمح لك الدالة ()sort بترتيب عناصر المصفوفة في مكانها. بالإضافة إلى إعادة المصفوفة التي تم ترتيبها ، الدالة ()sort أيضا تغير مواضع العناصر في المصفوفة الأصلية. بشكل افتراضي ، تقوم الدالة ()sort بفرز عناصر المصفوفة بترتيب تصاعدي من الأصغر إلى الأكبر. في حالة إستعمال الدالة بشكل إفتراضي: let numbers = [0, 1 , 2, 3, 10, 20, 30 ]; numbers.sort(); console.log(numbers); // النتيجة // [ 0, 1, 10, 2, 20, 3, 30 ] في حالة تم تمرير دالة المقارنة الخاصة بطريقة الفرز التي تقبل عاملين وترجع قيمة تحدد ترتيب الفرز. فالنتيجة تكون على حسب طريقة الفرز في الدالة الممررة. يوضح ما يلي كيفية إستعمال دالة المقارنة: let numbers = [0, 1 , 2, 3, 10, 20, 30 ]; numbers.sort( function( a , b){ if(a > b) return 1; // نرجع عدد موجب if(a < b) return -1; // نرجع عدد سالب return 0; // نرجع 0 }); عند كل دورة تقوم الدالة ()sort بمقارنة زوجين من المصفوفة 0 و 1 ثم 1 و 2 ثم 2 و 3 و هذا ... في حالة تم إسترجاع عدد موجب من دالة المقارنة تضع العدد b في رتبة أقل من a وفي حالة تم إسترجاع عدد سالب فإن دالة المقارنة تضع العدد b في رتبة أكبر من a أما في حالة إسترجاع 0 فتعتبر a يساوي b وتترك مواقعها دون تغيير. بإختصار عند كل دورة تأخد الدالة عددين من المصفوفة, العدد الأول مع العدد اللذي بجانيه وتقارن بينهما ثم تغير مركزهما حسب الأمر الموجود في الدالة.1 نقطة
-
الدالة sort نستطيع من خلالها تمرير function لديها معاملات واجراء بعض الشروط على المعاملات وعلى أساس ذلك يمكن ترتيب العناصر, الكود الذي في الأعلى هو اختصارا لهذا الكود numbers =[2, 14, 1, 2, 5]; numbers.sort( function( a , b){ if(a < b) return 1; if(a > b) return -1; return 0; }); اذا كان المعامل الاول أصغر من المعامل الثاني قم بارجاع القيمة 1 وهي تعني ان قم بعملية الترتيب والعكس صحيح, يمكنك تجربة الكود الكود المرفق مع الكود الذي ارفقته أنت لترى النتائج1 نقطة
-
إذا كان لديك دالة غير معلوم أو غير محدد عدد المُدخلات فيمكنك إستخدام رمز * لقبول عدد متغير من المُدخلات الى الدالة وايضاً يمكنك تمرير قائمة من المدخلات مرةً واحدة بإستخدام رمز * مجدداً وستحصل في داخل الدالة على قائمة من المُدخلات وهذا الكود يبين كيفية ذلك # تعريف دالة بعدد مُدخلات غير معلوم def func(*nums): # حلقة تكرار حول قائمة المتغيرات for num in nums: print(num) # تعريف قائمة من المُدخلات lst = [1,2,3,4,5] # تمرير قائمة المُدخلات الى الدالة func(*lst)1 نقطة
-
يُسمى هذا العامل عامل التفكيك أو عامل الإنتشار ( Spread Operator ) و هذه بعض الأمثلة التي يُستخدم فيها في جافاسكربت و كيف نستخدمه في بايثون: عوامل دالة: ( Function Arguments ) جافاسكربت: function multiply(a, b) { return a * b; } const numbers = [2, 5]; console.log(multiply(...numbers)); // 10 بايثون: def multiply(a, b): return a * b numbers = [2, 5] print(multiply(*numbers)) # 10 تفكيك عناصر مصفوفة في مصفوفة أخرى: ( Array Literals ) جافاسكربت: const numbers = [1, 2, 3]; const newNumbers = [0, ...numbers, 4] console.log(newNumbers); // [ 0, 1, 2, 3, 4 ] بايثون: numbers = [1, 2, 3] new_numbers = [0, *numbers, 4] print(new_numbers) # [0, 1, 2, 3, 4] تفكيك كائن في كائن آخر: ( Object Literals ) جافاسكربت: const testObj = { foo: 'bar' }; console.log({ ...testObj, foo2: 'bar2' }); // { foo: 'bar', foo2: 'bar2' } بايثون: test_obj = { 'foo': 'bar' } print({ **test_obj, 'foo2': 'bar2' }) # {'foo': 'bar', 'foo2': 'bar2'} أسلوب rest parameters الذي يسمح بتمرير عدد غير محدد من العوامل لدالة ما: جافاسكربت: function sum(...numbers) { let t = 0; for(num of numbers) t += num; return t; } console.log(sum(1, 2, 3, 4, 5)) // 15 بايثون: def sum(*numbers): t = 0; for num in numbers: t += num; return t; print(sum(1, 2, 3, 4, 5, 6)) # 211 نقطة
-
Memoization هي طريقة تستخدم لتخزين نتائج استدعاءات الدوال functions السابقة لتسريع العمليات الحسابية المستقبلية. إذا تم إجراء استدعاءات دالة متكررة باستخدام نفس المُدخلات ، فيمكننا تخزين القيم السابقة بدلاً من تكرار العمليات الحسابية غير الضرورية. يمكنك التفكير به ك cache لنتائج الدوال ولهذا فائدة كبيرة جداً حيث ينتج عن هذا تسريع كبير في العمليات الحسابية. وهناك عدة حالات لإستخدام Memoization فعلى سبيل المثال لتسريع دالة المعامل كما هو مبين في الكود factorial_memo = {} def factorial(k): if k < 2: return 1 if k not in factorial_memo: factorial_memo[k] = k * factorial(k-1) return factorial_memo[k]1 نقطة
-
يمكن استعمال المعامل '*' فهو يقوم بفرد المصفوفة/نشرها وتحويلها لعدد مساوِ من العناصر: def echo(a, b, c, d): print(a, b, c, d) myLits = [1, 2, 3, 4] echo(*myLits) # echo(1, 2, 3, 4) # ^^^^^ # output # 1 2 3 41 نقطة
-
هناك عدة طرق لإستدعاء ملف من المجلد الأب ، فيمكنك إضافة المسار الأب الى المسارات التي بايثون سيقوم بالبحث فيها بإستخدام دالة path كالآتي import sys sys.path.append("..") import helpers كما يمكنك إضافة المسار الأب أو أي مسار تريد إستيراد الوحدات modules منه كالآتي # إستدعاء دالة sys import sys # إضافة المسار الى الرقم 1 لأن 0 هو المسار الحالي sys.path.insert(1, '/path/to/application/app/folder') # إستيراد الوحدة من المسار المختلف import file1 نقطة
-
هل تمضي وقتًا في استخدام وسائل التواصل الاجتماعي أيًّا كانت مُدته؟ لم لا تستغل الفُرص المُجدية التي بين يديك طالما أنك مثل ملايين البشر حول العالم ممن يستخدمون وسائل التواصل الاجتماعي لأسبابٍ عدة؟ قد لا يتبادر إلى ذهنك وجود مهارات تؤهلك للعمل كمُدير لوسائل التواصل الاجتماعي بعائد مادي ودون أن يذهب وقتك في استخدام وسائل التواصل الاجتماعي دون فائدة. يظن الكثيرون أن مُهمة مدير وسائل التواصل الاجتماعي سهلة وبسيطة؛ لكنها في الواقع تتطلب إتقان بعض المهارات الهامة التي من شأنها أن تُمهد الطريق لبداية موفقة في هذا المجال؛ وحتى تتضح الصورة أكثر، وضعنا عدة نقاط تُرشدك للمسار الصحيح في هذا المضمار. كيف يمكن أن تصبح مديرا لوسائل التواصل الاجتماعي؟ كل ما عليك فعله هو اتباع الخطوات الست التالية للبدء في مُهمتك بصفتك مُديرًا التواصل الاجتماعي والتي من مهامها استقطاب باكورة روادها من العملاء. حدد منصات وسائل التواصل الاجتماعي التي ترغب التخصص بإدارتها تُعَد تحديد المنصات التي ترغب بإدارتها هي أولى الخطوات في مسارك لتُصبح مُديرًا لوسائل التواصل الاجتماعي؛ ويرجع السبب الكامن وراء ذلك هو تفرّد كل منصة من منصات التواصل الاجتماعي عن الأخرى من ناحية مُرتاديها واستخداماتها وخصائصها. يُفضل لمدير مواقع التواصل الاجتماعي الإلمام بالمِنصات العريقة كافةً، والتي تستخدمها الشركات للتسويق، وليس من المُجدي استيعابها كافةً في بداياتك المهنية على الأقل لما قد يتسبب في تشتتك والإخفاق في إتقانها، وتكون عبئًا ثقيلاً عليك. فيما يلي لمحة عن شبكات التواصل الاجتماعي الأكثر استخدامًا من قبل مختلف الشركات: فيسبوك Facebook: يُعد موقع الفيسبوك عميد مِنصات التواصل الاجتماعي، ويمتلك 2.5 مليار مُستخدم نشط، وأصبح الفيس بوك أداة من أدوات التسويق عبر وسائل التواصل الاجتماعي بالنسبة للشركات، فالصفحات التجارية والاجتماعية (المجموعات) من أهم السُبل التي تعتمدها الشركات للوصول للشريحة المُستهدفة على هذه المِنصة. لينكد إن LinkedIn: يُعرَّف موقع لينكد إن بقناة التواصل الاجتماعي لأرباب المِهن، حيث يقوم الموقع برمته على أساس الأعمال والوظائف؛ والربط الشبكي والمحتوى القيّم، مثل: الفيديوهات، ومنشورات المُدونات، ومواد الريادة الفكرية، والنشرات الصوتية (البودكاست). تُعَد غالبية رواد هذا الموقع من فئة البالغين ممن تتراوح أعمارهم بين سن 45 عامًا فما فوق؛ أما النسبة الكُبرى من مستخدمي هذا الموقع هم أصحاب الشركات والموظفين التنفيذيين للأعمال. إنستغرام Instagram: تقوم فكرة عمل موقع الإنستغرام على شبكة تواصل اجتماعية ترتكز على الصور ومقاطع الفيديو القصيرة. يُتابع حوالي نصف مُستخدمي مِنصة الإنستغرام شركة واحدة على الأقل؛ وأصبح الموقع وجهةً للتعرف على العلامات التجارية والمُنتجات. توجه الإنستغرام حديثًا نحو تسهيل الشراء المُباشر من منِصته بسلاسة فائقة للمُتسوقين. بنترست Pinterset: هي أيضًا مِنصة اجتماعية قائمة على الصور، كما أنها مُحرك بحث. يقصدها المُستخدمون للتعرف على الاهتمامات والهوايات والمُنتجات ولشراء السلع أيضًا؛ وأغلب مُرتادي المِنصة من النساء من فئة الدخل المُرتفع، وتتضاعف نسبة المبيعات على مِنصة بنترست أربع مرات عن غيرها من المِنصات. تويتر Twitter: لم تبلغ شبكة التواصل الاجتماعي توتير درجة الرواج التي بلغها موقع الفيسبوك والإنستغرام عند بداية ظهوره للمُستخدمين، لكنه أصبح مُنافسًا لها عندما توجه نحو الأعمال التجارية والتسويق، وأغلب مُستخدمي تويتر من جيل الألفية الأثرياء، وهم أكبر فئة من المُستهلكين، وهو مِنصة من المِنصات التي يقصدها المُستخدمون للتعرف على العلامات التجارية والشركات؛ وبمتوسط مُتابعة المُستخدم لخمس شركات على الأقل. يوتيوب YouTube: لم يذع صيت موقع اليوتيوب في بداية ظهوره مثل شبكة تواصلٍ اجتماعية؛ لكنه أصبح فيما بعد من أقوى شبكات التواصل وثاني أكبر مُحرك بحث (بعد مُحرك البحث جوجل)؛ يضم ملايين المُستخدمين لمنصته يوميًا لتعلم الأمور جوالهوايات والاهتمامات كافةً…إلخ؛ أما من يُغذيه بغالبية محتواه فهم الشركات والأعمال التجارية والمُسوقون. اختر قناتين إلى ثلاث قنوات للتواصل الاجتماعي للبدء في تعلمها، وتعرف على مكنونها من منظور الأعمال التجارية والتسويق. تختلف المِنصات الأخرى في ملاءمتها لعملائها بناءً على مجال نشاطهم التجاري وأهدافهم. أنشىء موقعك الإلكتروني الخاص بإدارة وسائل التواصل الاجتماعي إذا عقدت العزم للعمل مثل مُدير لوسائل التواصل الاجتماعي، فإن إنشاء موقع إلكتروني للترويج لخدماتك الإدارية لوسائل التواصل الاجتماعي يُعَد جُزءًا من هذه العملية. فعملك مُدير لوسائل التواصل الاجتماعي يُحتّم عليك التواصل مع المجتمع عبر الإنترنت؛ فالموقع الإلكتروني أساسي لعرض مهاراتك. كما يُعَد الموقع الإلكتروني الخاص بعملك يجعله قانونيًا ويؤسس قاعدةً من العملاء المُحتملين ويسوق خدماتك الموثوقة لهم. اصنع لعملك تواجدا في وسائل التواصل الاجتماعي بعد أن تُحدد منصات التواصل الاجتماعي التي ستختص بإدارتها؛ يتعين عليك صنع تواجد خاص بك على شبكة الإنترنت أو تحسينه إذا كنت تمتلكه من ذي قبل، لأجل مجال عملك الجديد الذي وقع عليه اختيارك، فالتواجد القوي عبر الإنترنت من أفضل الطُرق للعثور على العملاء بالنسبة لمدير وسائل التواصل الاجتماعي؛ حيث يتسنى لك عرض مهاراتك بوسائل التواصل الاجتماعي عبر استخدامك لحساباتك عليها. أما فيما يخص تواجدك عبر وسائل التواصل الاجتماعي؛ فمن المُهم جدًا عمل صفحات خاصة بعملك في إدارة هذه الوسائل؛ ومن المحتمل أنها المكان الأوّل للتواصل عبرها واستقطاب العملاء المُحتملين، وقد يُحدد انطباعهم الأولي عنك -بناءً على صفحاتك- نجاح أو فشل علاقتك المُستقبلية بهم؛ لذا من المُهم أن تهتم بكل ما تنشره على صفحاتك، ويُفضل مُراجعة صفحاتك الشخصية ومسح كل ما يُقلل من مهنيّتك أو موثوقيّت، كما يُفضّل أن تفصل بين عملك وحياتك الشخصية؛ والمُحصلة أنه في عالم إدارة وسائل التواصل الاجتماعي تخضع مناحي ظهورك كافةً عبر الإنترنت لأنظار الجمهور، وسيبحث عنك العُملاء المُحتملون؛ فكن مُستعدًا لذلك. حدد عروض خدمتك وأجرك بصفتك مديرا لوسائل التواصل الاجتماعي الخطوة التالية في مسيرتك في مجال إدارة وسائل التواصل الاجتماعي هي تحديد الخدمات التي ستُقدمها وتكلفتها. ومن أفضل مُميزات العمل كمُدير لوسائل التواصل الاجتماعي هي أنها غنية جدًا بالكثير من الفرص للتخصص بها؛ وأهم ما يعود بالنفع من ميزة التخصص هذه، هو طلبك لأجور مُرتفعة بالمُقابل. ستكتشف بمرور الوقت في أثناء عملك مُديرًا لوسائل التواصل الاجتماعي وجود خدمات مُمتعة تُقدمها وتُتقنها وعليها الكثير من الطلب، ويستغرق الوصول لهذه المرحلة بعضًا من الوقت، ومن الأفضل التركيز على تعلم بعض الخدمات وإتقانها بسرعة وتقديمها بثقة للعملاء وسيكون خيارًا موفقًا -حينئذٍ- أن تقدّم لعملائك مزيدًا من الخدْمات على قدرٍ كافٍ من العلم والإتقان. يُساعدك اختيار بعض الخدمات للبدء بتقديمها على التركيز، ويُسهل عليك مُهمة العثور على العُملاء؛ فعملية اختيار منصتين أو ثلاث مِنصات للتواصل الاجتماعي للتركيز عليها؛ ستزيد من ثقتك في عملك على نقيض من يدعي مقدرته تقديم كل الخدمات، كما ستتضح لك فئة العملاء التي تستهدفها لترويج خدماتك. ثماني خدمات رائجة في وسائل التواصل الاجتماعي إنشاء وتحسين الصفحة الشخصية. ترتيب المُحتوى. التصميم الجرافيكي. بحث الوسم (الهاشتاق). الحملات والإعلانات الدعائية المدفوعة. إدارة المجموعة / المُجتمع. تجديد المحتوى. خدمة العملاء. اختر ثلاثةً من هذه الخدمات لبدء التركيز عليها وتعلمها؛ وليس هناك إشكالية في إلمامك بالمفاهيم الأساسية لكل خدمة منها، ولكن حدد القليل منها والتي تسترعي اهتمامك. تعرف الآن على الخدمات التي ستُقدمها؛ إذ حان الوقت لتحديد أجرك مُقابل خدماتك. قد تكون هذه الجُزئية محيرةً لمُديري وسائل التواصل الاجتماعي، لكن لا عليك، بعد أن تكتسب بعض الخبرة في المجال ويُصبح لديك بعض العُملاء، ستحتاج للانتقال من مرحلة التسعير بالساعة إلى مرحلة تكلفة أجور مجموعة الخدمات، وسيُسهل ذلك الأمور عليك وعلى العميل؛ كما ستوفر الوقت ويزيد دخلك من ممارستك لنفس المهام التي زادت خبرتك بها، كما سيُثني عليك عملاؤك عند معرفتهم الخدمات التي يدفعون مُقابلها كل شهر. ننصحك بالبدء بثلاث مجموعات للخدمات؛ بدايةً من الدرجة الأقل، ثم المتوسطة، ثم الأعلى. حيث سيشعر العملاء بتوفر الخيارات لديهم بالطريقة التي يرغبون من خلالها بالتعامل معك وما هو الأنسب لاحتياجاتهم في وسائل التواصل الاجتماعي. ابدأ باستهداف العملاء لخدماتك لوسائل التواصل الاجتماعي بعد أن تعرفت على أساسيات إدارة وسائل التواصل الاجتماعي وتواجدك عبر الإنترنت والخدمات والأجور؛ حان وقت العثور على العملاء في ذلك المضمار، فقد قضيت وقتًا في تعلم كيفية ظهورك وتواجدك في وسائل التواصل الاجتماعي؛ وعليك أن تُمضي مزيدًا من الوقت في مجال عملك الجديد لاستهداف العملاء وهو بمثابة عصب حياة بالنسبة لعملك، وإذا لم تُنجز هذه المُهمة على الوجه الأكمل فلن تحصل على أي عميل. توجد الكثير من الأماكن للبحث عن عُملاء وسائل التواصل الاجتماعي المُحتملين؛ فغالبية الشركات والأعمال التجارية تُسوق عبر وسائل التواصل الاجتماعي (وإذا لم تكن تسوق من خلال وسائل التواصل الاجتماعي، فيتعين عليها التوجه لها والتسويق عبرها)؛ مع ذلك لا يرغب أكثر أصحاب الأعمال التجارية بإدارة صفحاته على وسائل التواصل الاجتماعي؛ مما يوفر لك الكثير من الفرص لتقديم خدماتك على نطاقٍ واسع. فيما يلي بعض الأماكن لبدء البحث عن عُملاء: مواقع التواصل الاجتماعي: أنشىء شبكتك الخاصة للتواصل الاجتماعي وضع على صفحاتها منشوراتك بين الحين والآخر لتعرض فيها خدماتك؛ وانضم للمجموعات والمُجتمعات وامضِ وقتًا تُنمي فيه علاقاتك بنشاط. شبكة تواصلك المُباشرة: تواصل مع أرباب الأعمال الذين تعرفهم واستعلم منهم عن حاجتهم لخدماتك أو إذا ما كانوا يعرفون من هم بحاجةٍ لها، وأطلع أصدقاءك وعائلتك على نشاطك وعلى بحثك عن عُملاء واطلب منهم تذكّرك عند معرفتهم لمن هم بحاجة لخدماتك. أجرِ البحث: اقضِ بعض الوقت للبحث عبر الإنترنت أو حتى في مُجتمعك المحلي أو في الشركات التي بحاجة لتحسين ظهورها عبر وسائل التواصل الاجتماعي أو أنّ ظُهورها غير مُنتظم أو لا من ليس لها ظهور على الإطلاق. أعد ملفا خاصا بدراسات الحالة والمقاييس أنشىء ملفًا بمُجرد حصولك على أوّل عميل أو ثلاثة عُملاء لتُطلع عليه فيما بعد العُملاء الجُدد؛ فإدارة وسائل التواصل الاجتماعي عبارة عن "أطلعني على خدماتك المُقدمة"، فكما أن ظهورك على وسائل التواصل أمر جوهري ونقطة انطلاقك؛ فإن عرض الخدمات التي أنجزتها للعملاء من أقوى الوسائل لتكوين عملك. إذا رغبت في نقلةٍ كُبرى لإعداد ملف دراسات الحالة قُبيل العثور على عملاء، فقدّم خدمتك لصديق من الأصدقاء أو لعمل خيري محلي مثل الأعمال غير الربحية، فهذه من أفضل الطُرق لاكتساب الخبرة، ولا ننصحك بالعمل دون مقابل لمُدة طويلة. كيف تزيد من فرص نجاحك؟ هناك أمور يتعين عليك وضعها في الحسبان طالما أنك قررت خوض عالم إدارة وسائل التواصل الاجتماعي المطلوب جدًا ومُحتمل الربحية؛ وهي كالتالي: لماذا أنت ماضٍ في هذا العمل، وما أسباب نجاحك فيه؟ إلى أي مدى أنت مُستعد لتحسين مهارات نمط حياتك الحالي أو مهارات تنظيم وقتك لتنجح؟ هل توجد لديك أمور أنت بحاجة لتغييرها أو إيقافها لتوفر الوقت لعملك؟ هل أنت قادر على ذلك؟ كيف يبدو النجاح بالنسبة لك؟ إلى ماذا تتطلع أن تكون عليه بعد عام أو ثلاثة أعوام أو خمسة أعوام من الآن؟ ستنجح بالتأكيد كمُدير لوسائل التواصل الاجتماعي نتيجة المعلومات الصحيحة والتركيز، وإذا أخذت عملك على محمل الجد؛ فمثلًا إذا كنت جديًا في عملك وعلى قدر المسؤولية، فسيكون عائده المادي عليك مثل العمل بدوامٍ كامل، وعلى النقيض من ذلك إذا استهنت بعملك فلن يزيد العائد المادي بالنسبة لك عن نطاق مُمارسته مثل هواية. إليك بعض الخطوات لاتباعها لتأخذ عملك على محمل الجد وتبذل كل ما في وسعك كمُدير لوسائل التواصل الاجتماعي. 1. لا تبرح التواصل وشبكات التواصل هل تبادر إلى مسامعك أن شبكة اتصالك هي رأس مالك؟ وهي الحقيقة؛ اقضِ وقتًا لإقامة العلاقات وتوثيقها عبر وسائل التواصل الاجتماعي وعلى جميع الأصعدة المتوفرة لديك مثل مُناسبات التواصل شخصيًا، وتواصل مع هذه العلاقات برغبة صادقة لأجل التواصل والتعلم على عكس بقائك في دوامة العمل؛ وستُبهرك قيمة العلاقة التي ستعود بالكثير من النفع على حياتك وعملك؛ قد لا يتحول التواصل المُباشر إلى علاقة عمل ولكن قد يعود منها بالنفع في مجالات أُخرى، مثل: الإحالة،أو اكتساب بعض المهارات، أو تعلم بعض الأشياء الجديدة التي تُثري شخصيتك. امضِ وقتًا في مجموعات وسائل التواصل الاجتماعي التي تُركز على سوقك المُستهدف مثل المجال الذي خصصت العمل فيه، وأجب على التعليقات بالمعلومات النافعة وبث الحماسة، أو تفاعل بأي شكل من الأشكال بالمُشاركة الفعلية، وبادر بالحوار واطلب من الأعضاء مراسلتك عبر الرسائل الخاصة للتعرف أكثر عليهم وكن دائمًا مُستعدًا لتقديم المُساعدة. إن الرغبة الصادقة لمُساعدة الآخرين مُثمرة جدًا بشكل لا يوصف. 2. لا تتوقف عن التعلم أغدق على نفسك بالتعلم المُستمر قدر المُستطاع؛ بحيث يتمحور التعلم حول بعض الأمور المُتعلقة بوسائل التواصل الاجتماعي، مما سيدفعك لأخذ عملك على محمل الجد؛ فأنت بذلك تستثمر في نفسك ومهارتك. استمر في التدرب فيما يخص المِنصات ومجالات الخدمة التي وقع اختيارك عليها لتُصبح مُديرًا لوسائل التواصل الاجتماعي ومُنافسًا حقيقيًا في هذا المضمار. تتوافر الكثير من المصادر المجانية على شبكة الإنترنت؛ وهناك العديد من الدورات الخاصة بالمِنصات والخدمات المُتعلقة بها؛ وننصح بالبحث عن دورة وسائل التواصل الاجتماعي للمُساعدين الافتراضين؛ للبدء في تعلم ما يخص مجال مُدير وسائل التواصل الاجتماعي بأساس متين للمُضي قُدمًا فيه ولتقديم العون للشركات والأعمال التجارية. تُعَدّ وسائل التواصل الاجتماعي في تغير مُستمر، لذا لا ترضى بأقل من الصدارة كمُدير بارع لوسائل التواصل الاجتماعي، وكن علامةً فارقةً فيها بمنأى عن الجميع وتعلم أدوات جديدة، وواكب التغيرات التي تطرأ على المِنصات التي تخصصت للعمل فيها. 3. استوعب أدوات العمل على وسائل التواصل الاجتماعي إنَّ فهم واستيعاب الأدوات الخاصة بوسائل التواصل الاجتماعي من أهم الأمور للعمل فيها والتي ستُساعدك على تنفيذ استراتيجية محتواها بفعالية، وأدواتها لا عدّ لها ولا حصر، بما فيها الأدوات الأصلية لمِنصات وسائل التواصل الاجتماعي، وتطبيقات الطرف الثالث. تتضمن بعض تطبيقات الطرف الثالث أدوات للجدولة، والتشغيل الآلي، والتحليل المنطقي، والتصميم الجرافيكي، وتخطيط المحتوى على سبيل المثال لا الحصر. وابدأ تعلم الأدوات الأصلية للمِنصات التي اخترت التخصص فيها وإتقانها تمامًا؛ فكلما اكتسبت خبرة فيها مثل مُدير للتواصل الاجتماعي، ستكتشف بعضًا من أدوات تطبيقات الطرف الثالث الكثيرة والموجودة في تلك المِنصات، وستُعينك هذه الأدوات التي ستحتفظ بها في مجموعة الأدوات الافتراضية، لتكون الأفضل في مجال عملك؛ فعندما يتوافر لديك فهم عميق لبعض الأدوات الأكثر شيوعًا وفعالية ستزيد مصداقيتك كمُدير حقيقي لوسائل التواصل الاجتماعي. لا تُرهق نفسك في تعلم جميع الأدوات فهناك ما لا يحصى منها شبيهة بمِنصات التواصل الاجتماعي وخدماتها؛ فقط ركز على القليل منها واصقل مهارتك في استخدامها. الخطوات التالية في طريق نجاحك كمدير لوسائل التواصل الاجتماعي لا يوجد هناك أفضل توقيت لتُزاول فيه عملك مثل مُدير لوسائل التواصل الاجتماعي؛ فقد تطورت وسائل التواصل الاجتماعي وتعدد نطاق استخدام المُراهقين وطُلاب الجامعات لها، وامتدت لتُصبح من الطُرق الرئيسة لتُسوق الشركات والأعمال التجارية لنفسها. لا يتوفر لدى أرباب الشركات والأعمال التجارية الوقت الكافي، ولا الرغبة أو المعرفة لوضع استراتيجية فعّالة لوسائل التواصل الاجتماعي، كما لا يتوفر لديهم من يقوم بالمهمة من فريق عملهم؛ وقد أصبح من المُعتاد التعاقد مع أطراف خارج الشركة لتقديم الخدمات وهي فُرصة ذهبية إذا أُتيحت لك لتُصبح مُديرًا لوسائل تواصل اجتماعي. تتعدد أوجه وسائل التواصل الاجتماعي وتتنوع وتُتيح لك مزاولة عملك عبر الأمور التي تُحبها وتستمتع بأدائها وتتقنها، سواءً كنت تُجيد التصميم الجرافيكي، أو خدمة العملاء، أو أي مجال من المجالات الأخرى ذات التخصص؛ وتُركز على إدارة وسائل التواصل الاجتماعي وتصنع منها عملًا ناجحًا ومُستدامًا يعود عليك بالدخل الوفير. انظر إلى عملك بجدية من خلال تخصيص الوقت لصقل مهاراتك وبدء ظهور جيد عبر الإنترنت وبناء شبكة تواصل من المهنيين مُتقاربي التفكير، وإذا ظللت ثابتًا ودؤوبًا في مجهوداتك لبناء عملك في مجال إدارة وسائل التواصل الاجتماعي فحتمًا ستجني ثمارها وتحقق نجاحًا هائلًا. ترجمة وبتصرّف للمقال How to Become a Social Media Manager لصاحبته Laura Nicholls. اقرأ أيضًا تأثير شخصيّتك في وسائل التواصل على عملك مجموعة أفكار لجذب العملاء وتوسيع قائمة البريد الإلكتروني. دليل الإعلانات المدفوعة على فيسبوك.1 نقطة
-
ملاحظة مهمة: هذه إضافة حديثة للغة، لذا قد تحتاج المتصفحات القديمة لترقيع هذا النقص. التسلسل الاختياري .? هو طريقة مقاومة للأخطاء للوصول إلى خصائص الكائن المتداخلة، حتى إذا كانت الخاصية الوسيطة غير موجودة. المشكلة إذا كنت قد بدأت للتو في قراءة هذه السلسلة التعليمية وتتعلم جافاسكربت، فلربما لم تواجه هذه المشكلة بعد، لكنها شائعة جدًا. فمثلًا، يمتلك بعض من المستخدمين لدينا يمتلكون عناوين، لكن هنالك قليل منهم لم يقدمها. لذا لا يمكننا قراءة user.address.street بأمان. هكذا: let user = {}; // تحدث للمستخدم user في حالة كان ليس لديه عنوان alert(user.address.street); // خطأ! أو عند تطويرنا لموقع وِب، ونرغب في الحصول على معلومات حول عنصر ما في الصفحة، لكنه قد لا يكون موجودًا: // إذا كان نتيجة querySelector(...) فارغًا let html = document.querySelector('.my-element').innerHTML; قبل ظهور "?." في اللغة، كان يستخدم المعامل && للتغلب على المشكلة. فمثلًا: let user = {}; // إذا كان user لا يملك عنوان alert( user && user.address && user.address.street ); // undefined (أي ليس خطأً) وكونها تتطلب كتابة طويلة للتأكد من وجود جميع المكونات، لذلك كان استخدامها مرهقًا. تسلسل اختياري التسلسل الاختياري "?." يوقف التقييم ويعيد غير معرّف undefined إذا كان الجزء قبل "?." غير معرّف undefined أو فارغ null. **للإيجاز سنفترض في هذه المقالة أن شيئًا ما "موجود" إذا لم تكن القيمة "فارغة" null أو غير معرّفة undefined. ** إليك الطريقة الآمنة للوصول إلى user.address.street: let user = {}; // إذا كان user لا يملك عنوان alert( user?.address?.street ); // undefined (ليس خطأً) إن قراءة العنوان باستخدام هذه الطريقة user?.address ستعمل حتى ولو كان الكائن user غير موجود: let user = null; alert( user?.address ); // undefined alert( user?.address.street ); // undefined الرجاء ملاحظة أن: صياغة جملة .? تجعل القيمة الموجودة قبلها اختيارية، ولكن ليس القيمة الّتي تأتي بعدها. في المثال أعلاه، تسمح التعليمة "user?." للكائن user فقط بأن يكون غير معرف أو فارغ "null/undefined". من ناحية أخرى، إذا كان الكائن user موجودًا، فيجب أن يحتوي على خاصية user.address، وإلا فإن user?.address.street ستُعطي خطأ في النقطة الثانية. ملاحظة: لا تفرط في استخدام التسلسل الاختياري. يجب أن نستخدم .? فقط في حالة عدم وجود شيئ ما. فمثلًا، بحسب منطق الشيفرة خاصتنا يجب أن يكون الكائن user موجودًا، ولكن الخاصية address اختيارية، بهذه الحالة سيكون user?.address.street أفضل. لذلك، إذا حدث أن كان الكائن user غير معرف بسبب خطأ ما، فسنُعرف عنه ونصلحه. خلاف ذلك، يمكن إسكات أخطاء الترميز عندما لا يكون ذلك مناسبًا، ويصبح تصحيحها أكثر صعوبة. ملاحظة: يجب التصريح عن المتغير الموجود قبل .? إذا لم يكن هناك متغير user على الإطلاق، فإن التعليمة user?.anything ستؤدي حتمًا إلى حدوث خطأ: // ReferenceError: إن المستخدم user غير معرًف user?.address; يجب أن يكون هناك تعريف واضح للمتغير let / const / var user. لأن التسلسل الاختياري يعمل فقط للمتغيرات المصرح عنها. اختيار الطريق الأقصر كما قلنا سابقًا، ستوقف .? فورًا (أي سيحدث قصر في الدارة) إذا لم يكن الجزء الأيسر موجودًا. لذلك، إذا كان هناك أي استدعاءات دوالّ أخرى أو آثار جانبية، فلن تحدث: let user = null; let x = 0; user?.sayHi(x++); // لا يحدث شيئ alert(x); // 0, لم تزداد القيمة حالات أخرى ()?. و []?. إن التسلسل الاختياري .? ليس مُعامل، ولكنه طريقة معينة لصياغة تعليمة، يعمل أيضًا مع الدوالّ والأقواس المربعة. فمثلًا، تُستخدم ().? لاستدعاء دالّة قد لا تكون موجودة. نلاحظ في الشيفرة أدناه، أنه لدى بعض مستخدمينا التابع admin والبعض ليس لديه: let user1 = { admin() { alert("I am admin"); } } let user2 = {}; user1.admin?.(); // I am admin user2.admin?.(); هنا، في كلا السطرين، نستخدم النقطة . أولًا للحصول على خاصية admin، لأن كائن المستخدم user يجب أن يكون موجودًا، لذا فهو آمن للقراءة منه. ثم يتحقق ().? من الجزء الأيسر: إذا كانت دالّة المسؤول موجودة، فستنفذ (على الكائن user1). وبخلاف ذلك (بالنسبة للكائن user2) يتوقف التقييم الشيفرة بدون أخطاء. تعمل الصياغة [].? أيضًا، إذا أردنا استخدام الأقواس [] للوصول إلى الخصائص بدلًا من النقطة .. على غرار الحالات السابقة، فإنه يسمح بقراءة خاصية بأمان من كائن قد لا يكون موجودًا. let user1 = { firstName: "John" }; let user2 = null; // Imagine, we couldn't authorize the user let key = "firstName"; alert( user1?.[key] ); // John alert( user2?.[key] ); // undefined alert( user1?.[key]?.something?.not?.existing); // undefined كما يمكننا استخدام .? مع delete: delete user?.name; // احذف المستخدم user.name إذا كان موجودًا ملاحظة: يمكننا استخدام .? للقراءة الآمنة والحذف، ولكن ليس الكتابة التسلسل الاختياري .? ليس له أي فائدة في الجانب الأيسر من المهمة: // فكرة الشيفرة أدناه أن تكتب قيمة user.name إذا لم تكن موجودة user?.name = "John"; // خطأ لن تعمل الشيفرة // لأن الإسناد بين undefined = "John" الخلاصة يتكون بناء جملة التسلسل الاختياري .? من ثلاثة أشكال: obj?.prop - ستُعيد obj.prop إذا كان الكائن obj موجودًا، وإلا ستعيد القيمة undefined. obj?.[prop] - ستُعيد obj[prop] إذا كان الكائن obj موجودًا، وإلا ستُعيد undefined. obj?.method() - تستدعي obj.method() إذا كان الكائن obj موجودًا، وإلا ستُعيد undefined. كما رأينا كل الطرق واضحة وسهلة الاستخدام. تتحقق .? من الجزء الأيسر بحثًا عن قيمة فارغة أو غير معرفة null/undefined ويسمح للتقييم بالمتابعة إذا لم يكن كذلك. تسمح سلسلة .? بالوصول الآمن إلى الخصائص المتداخلة. ومع ذلك، يجب أن نطبق .? بحذر، وفقط في الحالات الّتي يكون فيها الجزء الأيسر غير موجود، حتى لا نخفي عن أنفسنا الأخطاء البرمجية إذا حدثت. ترجمة -وبتصرف- للفصل Optional chaining '?.' من كتاب The JavaScript language1 نقطة
-
ملاحظة ابتدائية: لتوضيح طريقة استخدام ردود النداء callbacks والوعود promises والمفاهيم المجردة سنستخدم بعض توابِع المتصفح، تحديدًا سكربتات التحميل loading scripts وأدوات التلاعب بالمستندات البسيطة. إن لم تكُ على دراية بهذه الطرق، وكان استخدامها في الأمثلة مربكًا نوعًا ما، أو حتى إن رغبتَ فقط في فهمها فهمًا أفضل، فيمكنك قراءة بعض الفصول من الجزء التالي من الدورة التعليمية. وبالرغم من ذلك سنحاول توضيح الأمور بكل الأحوال ولن يكون هنالك شيء معقد في المتصفح. أغلب الإجراءات في جافاسكربت هي إجراءات غير متزامنة، أي أنّنا نشغّلها الآن ولكنّها تنتهي في وقت لاحق. مثال على ذلك هو حين نُجدول تلك الإجراءات باستعمال setTimeout. يُعدّ تحميل السكربتات والوحدات (سنشرحها في الفصول القادمة) أمثلة فعلية عن الإجراءات غير المتزامنة. لاحظ مثلًا الدالة loadScript(src) أسفله، إذ تُحمّل سكربتًا من العنوان src الممرّر: function loadScript(src) { // أنشئ وسم <script> وأضفه للصفحة // فسيؤدي ذلك إلى بدء تحميل السكربت ذي الخاصية src ثم تنفيذه عند الاكتمال let script = document.createElement('script'); script.src = src; document.head.append(script); } تُضيف الدالة إلى المستند الوسم <script src="…"> الجديد الذي وُلّد ديناميكيًا. حين يعمل المتصفّح ينفّذ الدالة. يمكننا استعمال هذه الدالة هكذا: // حمّل ونفّذ السكربت في هذا المكان loadScript('/my/script.js'); يُنفّذ هذا السكربت ”بلا تزامن“ إذ تحميله يبدأ الآن أمّا تشغيله فيكون لاحقًا متى انتهى الدالة. ولو كانت هناك شيفرة أسفل الدالة loadScript(…) فلن ننتظر انتهاء تحميل السكربت. loadScript('/my/script.js'); // الشيفرة أسفل loadScript // لا تنتظر انتهاء تحميل السكربت // ... فنقل بأنّنا نريد استعمال السكربت متى انتهى تحميله (فهو مثلًا يُصرّح عن دوال جديدة ونريد تشغيلها). ولكن لو حدث ذلك مباشرةً بعد استدعاء loadScript(…)، فلن تعمل الشيفرة: // في السكربت الدالة "function newFunction() {…}" loadScript('/my/script.js'); newFunction(); // ما من دالة بهذا الاسم! الطبيعي هو أنّ ليس على المتصفّح انتظار مدّة من الزمن حتّى ينتهي تحميل السكربت. كما نرى فحاليًا لا تقدّم لنا الدالة loadScript أيّ طريقة لنعرف وقت اكتمال التحميل، بل يُحمّل السكربت ويُشغّل متى اكتمل، وفقط. ولكن ما نريده هو معرفة وقت ذلك كي نستعمل الدوال والمتغيرات الجديدة فيه. لنُضيف دالة ردّ نداء callback لتكون الوسيط الثاني لدالة loadScript، كي تُنفّذ متى انتهى تحميل السكربت: function loadScript(src, callback) { let script = document.createElement('script'); script.src = src; script.onload = () => callback(script); document.head.append(script); } الآن متى أردنا استدعاء الدوال الجديدة في السكربت، نكتبها في دالة ردّ النداء تلك: loadScript('/my/script.js', function() { // يعمل ردّ النداء بعد تحميل السكربت newFunction(); // الآن تعمل ... }); هذه الفكرة تمامًا: يُعدّ الوسيط الثاني دالةً (عادةً ما تكون مجهولة) تعمل متى اكتمل الإجراء. وإليك مثالًا حقيقيًا قابل للتشغيل: function loadScript(src, callback) { let script = document.createElement('script'); script.src = src; script.onload = () => callback(script); document.head.append(script); } *!* loadScript('https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.2.0/lodash.js', script => { alert(`Cool, the script ${script.src} is loaded`); alert( _ ); // التابع معرف في السكربت المُحمّل }); */!* يُسمّى هذا الأسلوب في البرمجة غير المتزامنة ”الأسلوب المبني على ردود النداء“ (callback-based)، إذ على الدوال التي تنفّذ أمور غير متزامنة تقديمَ وسيط ردّ نداء callback نضع فيه الدالة التي ستُشغّل متى اكتملت تلك الأمور. ولقد فعلناها في loadScript ولكن بكلّ الأحوال هذا نهج عام. رد النداء داخل رد النداء وكيف نُحمّل سكربتين واحدًا تلو الآخر: يعمل الأول وبعدها حين ينتهي يعمل الثاني؟ الحلّ الطبيعي الذي سيفكّر به الجميع هو وضع استدعاء loadScript الثاني داخل ردّ النداء، هكذا: loadScript('/my/script.js', function(script) { // جميل، اكتمل تحميل كذا، هيًا نُحمّل الآخر alert(`Cool, the ${script.src} is loaded, let's load one more`); loadScript('/my/script2.js', function(script) { // جميل، اكتمل تحميل السكربت الثاني أيضًا alert(`Cool, the second script is loaded`); }); }); وبعدما تكتمل الدالة الخارجية loadScript، يُشغّل ردّ النداء الدالة الداخلية. ولكن ماذا لو أردنا سكربتًا آخر، أيضًا…؟ loadScript('/my/script.js', function(script) { loadScript('/my/script2.js', function(script) { loadScript('/my/script3.js', function(script) { // ...نواصل متى اكتملت كلّ السكربتات }); }) }); هكذا نضع كلّ إجراء جديد داخل ردّ نداء. لو كانت الإجراءات قليلة فليست مشكلة، ولكن لو زادت فستصير كارثية. سنرى ذلك قريبًا. التعامل مع الأخطاء لم نضع الأخطاء في الحسبان في هذه الأمثلة. ماذا لو فشل تحميل السكربت؟ يفترض أن تتفاعل دالة ردّ النداء بناءً على الخطأ. إليك نسخة محسّنة من الدالة loadScript تتعقّب أخطاء التحميل: function loadScript(src, callback) { let script = document.createElement('script'); script.src = src; // هنا script.onload = () => callback(null, script); // خطأ في تحميل السكربت كذا script.onerror = () => callback(new Error(`Script load error for ${src}`)); document.head.append(script); } هنا نستدعي callback(null, script) لو اكتمل التحميل، ونستدعي callback(error) لو لم يكتمل. طريقة الاستعمال: loadScript('/my/script.js', function(error, script) { if (error) { // نتعامل مع الخطأ } else { // تحمّل السكربت بنجاح } }); نُعيد، ما استعملناه هنا للدالة loadScript هي طريقة مشهورة جدًا، تُدعى بأسلوب ”ردّ نداء الأخطاء أولًا“. إليك ما اصطُلح عليه: يُحجز الوسيط الأوّل من دالة callback للخطأ إن حدث، ويُستدعى callback(err). يكون الوسيط الثاني (وغيرها إن دعت الحاجة) للنتيجة الصحيحة، هكذا نستعمل الدالة callback فقط للأمرين: الإبلاغ عن الأخطاء وتمرير النتيجة. هرم العذابات (Pyramid of Doom) يمكن أن نرى أنّ البرمجة بنمط اللاتزامن مفيد جدًا، وهذا جليّ من النظرة الأولى. فحين نكون أمام استدعاء أو استدعاءين فقط، فأمورنا ممتازة. ولكن لو تتابعت الإجراءات غير المتزامنة واحدة تلو الأخرى، سنرى هذا: loadScript('1.js', function(error, script) { if (error) { handleError(error); } else { // ... loadScript('2.js', function(error, script) { if (error) { handleError(error); } else { // ... loadScript('3.js', function(error, script) { if (error) { handleError(error); } else { // ...نُواصل بعد اكتمال تحميل كل السكربتات (*) } }); } }) } }); إليك ما في الشيفرة أعلاه: نُحمّل 1.js ونرى لو لم تحدث أخطاء. نُحمّل 2.js ونرى لو لم تحدث أخطاء. نُحمّل 3.js، ولو لم تحدث أخطاء نفّذنا شيئًا (*). فكلّما تداخل الاستدعاءات أكثر أصبحت الشيفرة متشعّبة جدًا وأصعب في الإدارة كثيرًا، هذا خصوصًا لو كانت هناك شيفرة فعلية لا ... (مثل الحلقات والعبارات الشرطية وغيرها). يُسمّون هذا أحيانًا ”بجحيم ردود النداء“ (callback hell) أو ”هرم العذابات“. بزيادة الإجراءات غير المتزامنة واحدًا بعد آخر، يتشعّب ”هرم“ الاستدعاءات المتداخلة إلى اليمين أكثر فأكثر، ولن يمضي من الوقت الكثير حتى دخلتْ في دوّامة حلزونية محال تنظيمها. بهذا تُعدّ طريقة البرمجة هذه سيّئة. يمكننا في محاولة يائسة لتقليل وقع المشكلة تحويل كلّ إجراء إلى دالة منفردة، هكذا: loadScript('1.js', step1); function step1(error, script) { if (error) { handleError(error); } else { // ... loadScript('2.js', step2); } } function step2(error, script) { if (error) { handleError(error); } else { // ... loadScript('3.js', step3); } } function step3(error, script) { if (error) { handleError(error); } else { // ...نواصل بعد اكتمال تحميل كل السكربتات (*) } }; رأيت الفكرة؟ مبدأ الشيفرة واحد وليس هناك تداخلات متشعّبة إذ حوّلنا كلّ إجراء إلى دالة لا مشكلة في الشيفرة إذ هي تعمل، ولكنّها فعليًا مقطّعة إربًا إربًا، ويصعُب على الشخص قراءتها وغالبًا تنتقل بنظرك بين أجزاء الشيفرة وأنت تقرأها. ليس هذا بالأمر الجيد خصوصًا لو لم يكن قارئ الشيفرة يعلم بما تفعله أو إلى أين ينقل نظره. كما وأنّ الدوال بأسماء الخطوات step* هي لاستعمال واحد ولم نصنعها إلّا لتفادي ”هرم العذابات“. لن يأتي أحد لاحقًا ويُعيد استعمالها في أيّ شيء عدا سلسلة الإجراءات هذه. بهذا ”نلوّث“ فضاء الأسماء، إن جاز التعبير. نريد بالطبع ما هو أفضل من هذا الحل. ولحسن حظنا (كالعادة)، فهناك طرق أخرى نتجنّب بها الأهرام هذه، أفضلها هي استعمال ”الوعود“ وسنشرحها في الفصل القادم. تمارين دائرة تتحرك ولها رد نداء نرى في التمرين Animated circle دائرة يكبُر مقاسها في حركة جميلة. لنقل بأنّا لا نريد الدائرة فقط، بل أيضًا عرض رسالة فيها. يجب أن تظهر الرسالة بعدما ينتهي التحريك (أي تكون الدائرة بمقاسها الكبير الكامل)، وإلّا بدا النص قبيح المظهر. في حلّ ذاك التمرين، نرى الدالة showCircle(cx, cy, radius) ترسم الدائرة ولكن لا تُقدّم لنا أيّ طريقة نعرف بها انتهاء الرسم. أضِف وسيط ردّ نداء showCircle(cx, cy, radius, callback) يُستدعى متى اكتمل التحريك. على المُعامل callback استلام كائن <div> للدائرة وسيطًا له. إليك مثالًا: showCircle(150, 150, 100, div => { div.classList.add('message-ball'); div.append("Hello, world!"); }); تجربة حية: يمكنك اعتماد حل هذا التمرين منطلقًا للحل. الحل يمكنك معاينة الحل من خلال المثال الحي. ترجمة -وبتصرف- للفصل Introduction: callbacks من كتاب The JavaScript language1 نقطة
-
كما هو عليه الحال كل بضع عقود، تطفو إلى السطح لغة برمجة ما ويعدنا المتعصبون لها بأنها ستفعل لنا كل شيء، بدءًا من تطبيقات الحاسوب مرورًا بالهواتف الذكية وليس انتهاءً بالتعامل مع الجمادات من حولنا بطرق رائعة كالتحكم بطائرة بلا طيّار باستخدام قبضة Xbox 360! لم يكن هذا الحماس يومًا أشدّ منه مع JavaScript، والأمر يعود لعدّة أسباب: كونها تعمل في المتصفح جعلها عابرة للمنصات، فكل حاسوب وكلّ هاتف ذكي (أو حتى متوسّط الذكاء!) اليوم يأتي مزوّدًا بمتصفّح قادر على تشغيل JavaScript،وبما أنّها لغة الويب الوحيدة التي يمكن استعمالها لبرمجة المواقع من جهة المتصفّح، فلك أن تتخيّل عدد مطوّري الويب الذي يتقنونها حول العالم!كذلك كون JavaScript بطبيعتها لغة فائقة المرونة لدرجة أن كل شيء فيها هو في الحقيقة كائن Object حتى الدّوال (functions)! ولا يوجد شيء اسمه أصناف (classes) بالمعنى التّقليديّ، وإنما توجد وراثة أنموذجية (Prototypal inheritance) فكل كائن يستطيع أن يرث أي كائن آخر، ولا أنواع محدّدة للكائنات، فما تفرضه في البداية كسلسلة نصيّة يمكنك أن تغيّره فيما بعد ليصبح رقمًا، وبإمكانك توسعة الأنماط البدئية. هذه المرونة التي يألفها من يبتدئ البرمجة بـJavaScript تجعله يُصاب بصدمة عندما ينتقل إلى لغة أخرى تفرض عليه قيودًا في التصريح عن الأنواع ووراثة الأصناف...الأمر الثالث الذي يجعل JavaScript متفوّقة هو التحسّن الممتاز في أدائها الذي لا يبدو أنه سيتوقّف عند حدّ ما قريبًا، منذ بضع سنوات عندما ظهر Google Chrome مع محرّك JavaScript الجديد V8 والذي اعتمد على JIT compilation بدأت ثورة في عالم التطوير للويب جعلت JavaScript موضع اهتمام وأخذ المطوّرون ينظرون في إمكانيّة استعمالها في تطوير "تطبيقات ويب" بدل "مواقع ويب"، ثم توسّع الأمر مع ظهور Node.js التي قامت على محرّك V8 ذاته لتصبح JavaScript مواطنًا من الدّرجة الأولى على الخواديم مثلها مثل Ruby on Rails وPHP. الأمور ليست ورديّة تمامًا لكنك تستطيع استيعاب سرعة التطوّر الذي تشهده JavaScript وخاصّة أنّه أصبح لدينا أنظمة تشغيل ليست سوى متصفّح ويب في حقيقتها (Chrome OS وFirefox OS). بإمكانك استضافة تطبيقات Node.js مجّانًا على منصّة Heroku أو OpenShift من RedHat مثلها مثل تطبيقات Java وPython وRuby.باختصار، تحوّلت JavaScript إلى لغة عامّة الأغراض (general-purpose) بعد أن كانت تستخدم بشكل بسيط لإضفاء القليل من التأثيرات السخيفة (Blink! Blink!) على صفحات الويب. الآن أريد أن أوضّح شيئًا، في الحقيقة أنا لست مُبرمجًا مُختصًّا، وJavaScript هي اللغة الوحيدة التي أزعم أنّني متوسّط إلى خبير بها، ومنذ عام أو أكثر لم أكتب تقريبًا أي شيء بلغة أخرى (بالطّبع CoffeeScript لا تُعتبر لغة مستقلّة، هي فقط لغة تُحوّل إلى JavaScript ومهمّتها تبسيط الكتابة)، مع أنّني بدأت تعلّم البرمجة مع PHP، إلا أنّني كرهت كل إشارات الدولار تلك ($variable) وعدم انسجام الواجهات البرمجيّة فيها. لا شيء يمنعك من استخدام PHP، وفي الحقيقة إطار العمل Laravel ممتاز ومنسّق بشكل جيّد، لكنّ ما يعيبها هو أنها تحاول أن تفعل كلّ شيء من الوصول لنظام الملفّات إلى دوال للتعامل مع مُعاملات طلبات HTTP إلخ... وهذا بالضّبط ما تحاول بيئات البرمجة الجديدة أن تتجنّبه، ففي Node.js، وعلى الرّغم من أنّه باستطاعتك أن تصل إلى نظام الملفّات وأن تُنشئ خادمًا يستمع إلى الطلبات على أحد المنافذ؛ إلّا أنّ كلّ شيء مُنظّم في وحدات (modules) مستقلّة وعليك أن تُصرح علانيًّة برغبتك باستعمال وحدة نظام الملفّات مثلاً، وكذلك الأمر بالنسبة للوحدات التي يكتبها مبرمجون آخرون. نظام الوحدات هذا والتصريح عنها ضمن ملفّ وعدم تلويث نطاق الأسماء العامّ (Global scope) هي بعضٌ من الأشياء التي نفّذها مُطوّرو Node.js على وجه صحيح، وأزعم أنه واحد من الأشياء التي جعلت JavaScript تُؤخذ على محمل الجدّ. هناك الكثير من المحاولات لتقليد هذا النظام بعد أن أثبت تفوّقه، انظر مثلاً إلى Composer بالنسبة لـPHP، وPip مع virtualenv في Python؛ لكنّ محاولة إدخال هذه الأنظمة على لغات ناضجة لا تبدو موفّقة كثيرًا، وأما في لغة Go فتعتبر الحُزم شيئًا من أساس اللغة. أيًّا يكن، لقد وصلنا إلى مرحلة يمكن بها إنجاز أيّة تطبيق بأيّة لغة، ويبقى الفارق هو التنظيم والسرعة والأمان وأنماط التّصميم المُتّبعة، وأهمّ من ذلك كلّه المجتمع الّذي يوفّر الدّعم والمساعدة للمبتدئين (في النّقطة الأخيرة لا شكّ أن JavaScript متفوّقة على كلّ اللّغات). لكن دعونا لا نُهين قدرتنا العقلية ونتجاهل وجود لغات برمجة أخرى فقط لأنّنا ألِفنا لغة برمجة واحدة، مهما كانت محبوبة! هناك أشياء في JavaScript لا يمكن التّغاضي عنها ولا يُمكن في أحسن الأحوال أن نعتبرها مزايا: فهي أوّلاً بطيئة رغم تحسّن أدائها بأضعاف ما كانت عليه منذ سنوات، وما تزال أبطأ بكثير من لغات أخرى. هناك من يجادل (وأنا أؤيّد هذا الرأي) أن السّرعة ليست كلّ شيء، فيكفي لتطبيقات الويب أن تكون سريعة بما يكفي، وليس عليها أن تكون خارقة السرعة، الفارق بين كتابة تطبيق بسيط يؤدي مهمّة محدّدة بـJavaScript وتوزيعه ليعمل على كلّ منصّات الهواتف الذكية أمر يستحق التضحية بالقليل (والقليل فقط، أي إلى حدّ معقول) من السّرعة في مقابل كتابة تطبيق منفصل بـJava (لن تتخيّل عدد السّطور المُرعب الذي تحتاجه!) وآخر بـObjective C (أو Swift) وآخر بلغة ما لـWindows Phone (إن كان هناك من يُطوّر لهذا النظام! ونعم أنا جاهل به لدرجة أنّني لا أعرف شيئًا عن اللّغة التي تُستخدم لتطوير تطبيقاته!). لكن بعد تجاوز هذا الحد المعقول من التضحية، يجدر بك أن تُعيد النظر في صلاحية هذه اللّغة إذا ما أردت تطبيقًا ينفّذ مهمّة تتطلّب سرعة فائقة، دعك من أنّ هناك تطبيقات تحتاج إلى التعامل مع النظام بطريقة لا توفّرها بيئة تطبيقات الويب على هذه الأجهزة.وثانيًا JavaScript هي عالم من الفوضى إن تغاضينا عن التنظيم الممتاز في Node.js ونظرنا إلى بيئة المتصفّحات... كم مرّة عانى مطوّرو الويب من عدم التوافق... المتصفّح الفلاني يوفّر الميزة الفلانية... رائع! هذا بالضبط ما أحتاجه! لكن للأسف المتصفح الآخر لا يوفّرها، وستحتاج إلى مكتبة بديلة (polyfill) لسدّ هذا الفراغ، حسنًا سأضيف هذا الـpolyfill وسيمكنني التطوير لكل المتصفّحات... نعم، باستثناء أنّ هذا الـpolyfill يسدّ الفراغ بنسخة قديمة من معيار هذه الميزة التي تحتاجها - فكما تعرف أعضاء منظّمة W3C لا يتوقّفون عن تغيير الواجهات البرمجيّة للأشياء التجريبية في المتصفّحات... ألم نُحذّرك من استخدام هذه الميزة غير المُستقرّة؟ كان يجدر بك أن تبحث عن حلّ بديل!... وهذا هو بالضّبط السّبب الذي يجعلني أكره التطوير للواجهات (front-end development) ولهذا قرّرت الاعتزال في عالم Node.js والتطوير للنهاية الخلفيّة (backend)! لا شيء من أحلامك الورديّة يتحقّق بسهولة في عالم المتصفّحات! أعرف أن الأمور في تحسّن دائم، وهناك الكثير من الأشياء الرائعة القادمة... مثل applicationCache وindexedDb وObject.observe وWeb Components وHTML Imports... لكنّ المشكلة أنها جميعها ليست مستقرّة أو غير مُتبنّاة في كلّ المتصفّحات بعد؛ ويبدو أن هذه الفوضى لن تنتهي يومًا، ولا حلّ لها سوى المزيد من فوضى المكتبات البديلة، هل قلت يومًا إنّ الويب هو مستقبل التطوير الموحّد لكلّ المنصّات؟ لقد كنت ساذجًا!هناك الكثير من عيوب التصميم في JavaScript، بعضها يعود لكونها لغة صُمّمت لإنجاز مهام بسيطة وعلى عجَلَ (في الحقيقة Brendan Eich صمّمها خلال 10 أيام لمتصفّح Netscape)، فمثلاً لا يُمكنك إنشاء خيوط (threads) لأنّها تعمل ضمن خيط واحد (single-threaded)، هناك الكثير من الحلول الالتفافيّة (workarounds) في Node.js والمتصفّحات تعدنا بحل محدود الفعاليّة (Web Workers) لكنّ إيّاك أن تُخبر مُبرمج Java بأنّه لا يُمكنك إنشاء threads في JavaScript ثمّ تدعي أنّها لغة قويّة! أيضًا لا تستغرب إن قضيت ساعات تشرح لمطوّري اللّغات الأخرى عن حلقة الأحداث (event loop) وكيف تعمل ولماذا عليك تمرير استدعاءات راجعة (callbacks) عندما تُنفّذ طلبات XMLHttpRequest وما هو جحيم الاستدعاءات (callback hell) ولماذا ظهرت بدائل عنها مثل الوعود (Promise) التي تحوّل جحيم الاستدعاءات إلى جحيم then!asynctask1(function(err1, data1) { if (err1) { throw err1; return; } asynctask2(data1, function(err2, data2) { if (err2) { throw err2; return; } asynctask3(data2, function(err3, data3) { // ... WELCOME TO JAVASCRIPT! }) }) });أخيرًا أرجوك لا تنسى بديهيّة أن JavaScript بحدّ ذاتها لغة تُفسّر بمحرّك مكتوب بـC++ أو لغة أخرى أعرق وأقوى أداءً، النّواة Linux مكتوبة بخليط من C وC++ وبرامج تشغيل الأجهزة (drivers) غالبًا تُكتب بـAssembly. نعم، صدّقني JavaScript ليست اللّغة الوحيدة ضمن المجموعة الشّمسيّة!ضحكت كثيرًا عندما شاهدت هذه الصورة منذ بضعة أسابيع، التي تُعبّر بالضّبط عن موضوع هذه التدوينة: تسخر هذه الصّورة بشدّة من تعصّب بعض مبرمجي JavaScript الذي يدعوهم إلى الظنّ بأنّ على كل تطبيق جديد في الكرة الأرضية أن يستخدم JavaScript من اليوم فصاعدًا، وأنّ لغات أخرى ستصبح طيّ النسيان ولا يستخدمها إلّا المبرمجون القدامى الذين عفى عليهم الدهر؛ الأمر ليس مقتصرًا على متعصّبي JavaScript وحدهم، فلكلّ لغة برمجة أنصارها ومتعصّبوها، لكن JavaScript بالذّات هي أكثر اللغات التي تترافق بهذه الظاهرة، للأسباب التي ذكرتها سابقًا. ما خُلاصة هذا الحديث؟ما أريد قوله من هذه التدوينة السّريعة هو أن أنصح المتعصّبين للغة برمجية أيًّا كانت أن يتوقّفوا عن إهانة قدراتهم العقليّة على التّعلّم وتقبّل الكتابة بلغة أخرى يرون أنّها للفاشلين فقط أو للقادمين من العصر الحجري... لم أتعلّم هذا إلا بالطّريقة الصّعبة، وأعتقد أنّ السّبب الّذي جعلني أتقبّل هذه الحقيقة هو كوني غير مختصّ، تعلّمي للبرمجة غير مرهون بعملٍ أو بربح مادّيٍّ، أنا فقط أُبرمج على سبيل التّسلية، وكلّ مشاريعي التي كتبتها (هذه المدوّنة بنيتها من الصّفر، وتطبيق aQuran، وتطبيق جديد أكتبه لتوليد خلاصات RSS...) كلّها كانت مجرّد تجربة ومحاولة لاستكشاف أنماط التصميم (design patterns) ومفاهيم برمجيّة أخرى. نعم تعلّمت الكثير عن البرمجة عمومًا من خلال JavaScript، لكنّها لن تكون اللّغة الوحيدة التي أكتب بها لبقيّة حياتي بالطّبع! يبدو أن هدفي التالي سيكون لغة Go حديثة العهد. البرمجة أوسع من صياغة اللّغة (syntax) لذا فلا يعتبر انتقالي لتعلّم لغة أخرى خسارة، والكثير من المفاهيم البرمجية كالبرمجة المُقادة بالاختبارات (test-driven development) والتّعامل مع الاستثناءات (exception handling) ومكوّنات اللغة كالأصناف (classes) والواجهات (interfaces) والدّوالّ (functions) هي أشياء توجد بعضها أو كلّها في كلّ اللّغات.1 نقطة