آموزش کامل Alpine.js: ساخت Modal، Dropdown و کامپوننت‌های تعاملی | OnePower

آموزش کامل Alpine.js: ساخت Modal، Dropdown و کامپوننت‌های تعاملی | OnePower

امیر بصیر 1 ماه پیش 96 بازدید مدت زمان مطالعه 3 دقیقه

مشخصات وبلاگ

آیدینویسندهتاریخ ایجاد
#65امیر بصیر یکشنبه ، 11 آبان 1404
معرفی Alpine.js: جاوااسکریپت سبک‌وزن برای توسعه‌دهندگان
Alpine.js یک فریمورک جاوااسکریپت سبک‌وزن است که برای افزودن تعاملات ساده و پویا به markupهای HTML طراحی شده است. اگر با فریمورک‌های بزرگی مانند Vue یا React آشنا هستید، Alpine.js را می‌توان به عنوان "همتای سبک‌وزن" آن‌ها در نظر گرفت که برای کارهای کوچک و متمرکز ایده‌آل است.

چرا Alpine.js؟

- سبک‌وزن: تنها ~7KB (فشرده شده)
- ساده برای یادگیری: سینتکس شبیه به Vue
- بدون نیاز به Build Process: مستقیماً در HTML استفاده می‌شود
- یکپارچگی عالی: با هر کتابخانه یا فریمورک دیگری کار می‌کند


فصل اول: شروع کار با Alpine.js

نصب و راه‌اندازی

روش ۱: استفاده از CDN
<!DOCTYPE html>
<html>
<head>
    <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
</head>
<body>
    <!-- محتوای شما -->
</body>
</html>
روش ۲: نصب با NPM
npm install alpinejs
import Alpine from 'alpinejs'
window.Alpine = Alpine
Alpine.start()

ساختار پایه

<div x-data="{ open: false }">
    <button @click="open = !open">Toggle</button>
    
    <div x-show="open">
        این محتوا قابل نمایش/پنهان شدن است!
    </div>
</div>


فصل دوم: دستورات اصلی Alpine.js

x-data - تعریف State

<div x-data="{ 
    count: 0,
    name: 'Alpine.js',
    isActive: true 
}">
    <span x-text="count"></span>
    <span x-text="name"></span>
    <span x-text="isActive ? 'Active' : 'Inactive'"></span>
</div>

x-show و x-if - نمایش شرطی

<div x-data="{ isVisible: true }">
    <!-- x-show (change display) -->
    <div x-show="isVisible">
        It is displayed by changing the display.
    </div>
    
    <!-- x-if (remove from DOM) -->
    <template x-if="isVisible">
        <div>
           Removed from the DOM when the condition is false
        </div>
    </template>
    
    <button @click="isVisible = !isVisible">
        تغییر وضعیت
    </button>
</div>

x-bind - binding ویژگی‌ها

<div x-data="{ 
    url: 'https://example.com',
    isDisabled: false,
    className: 'btn-primary'
}">
    <a x-bind:href="url">Example link</a>
    
    <button x-bind:disabled="isDisabled" 
            x-bind:class="className">
        دکمه
    </button>
    
    <!-- سینتکس مختصر -->
    <input :type="isDisabled ? 'password' : 'text'"
           :placeholder="isDisabled ? 'Disabled' : 'Enter text'">
</div>

x-on - مدیریت رویدادها

<div x-data="{ message: 'Hello World!' }">
    <button x-on:click="message = 'Clicked!'">
        کلیک کنید
    </button>
    
    <!-- سینتکس مختصر -->
    <button @click="message = 'Click with short syntax'">
        کلیک مختصر
    </button>
    
    <!-- رویدادهای کیبورد -->
    <input @keyup.enter="message = 'Enter button pressed'"
           @keyup.escape="message = 'Skipped'">
    
    <p x-text="message"></p>
</div>

x-model - دوطرفه binding داده‌ها

<div x-data="{ username: '', rememberMe: false }">
    <input type="text" x-model="username" 
           placeholder="Enter username.">
    
    <label>
        <input type="checkbox" x-model="rememberMe">
        مرا به خاطر بسپار
    </label>
    
    <p>Username: <span x-text="username"></span></p>
    <p>Reminder status: <span x-text="rememberMe"></span></p>
</div>

x-text و x-html - نمایش محتوا

<div x-data="{ 
    plainText: 'Plain text',
    htmlContent: '<strong>HTML text</strong>' 
}">
    <!-- Show plain text -->
    <div x-text="plainText"></div>
    
    <!-- HTML display -->
    <div x-html="htmlContent"></div>
</div>


فصل سوم: کامپوننت‌های عملی و کاربردی

۱. Modal (پنجره محاوره‌ای)

<div x-data="{ isOpen: false }">
    <!-- Modal Open Button -->
    <button @click="isOpen = true" 
            class="bg-blue-500 text-white px-4 py-2 rounded">
        باز کردن Modal
    </button>

    <!-- Modal -->
    <div x-show="isOpen" 
         x-transition:enter="transition ease-out duration-300"
         x-transition:enter-start="opacity-0"
         x-transition:enter-end="opacity-100"
         x-transition:leave="transition ease-in duration-200"
         x-transition:leave-start="opacity-100"
         x-transition:leave-end="opacity-0"
         class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
        
        <div class="bg-white rounded-lg p-6 max-w-md w-full mx-4">
            <h2 class="text-xl font-bold mb-4">Modal title</h2>
            <p class="mb-4">This is a modal built with Alpine.js.</p>
            
            <div class="flex justify-end space-x-2">
                <button @click="isOpen = false" 
                        class="bg-gray-300 px-4 py-2 rounded">
                    بستن
                </button>
                <button class="bg-blue-500 text-white px-4 py-2 rounded">
                    تایید
                </button>
            </div>
        </div>
    </div>
</div>

۲. Dropdown (منوی آبشاری)

<div x-data="{ open: false }" class="relative">
    <!-- دکمه باز کننده -->
    <button @click="open = !open" 
            class="bg-gray-200 px-4 py-2 rounded flex items-center">
        منو
        <svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
        </svg>
    </button>

    <!-- Dropdown content -->
    <div x-show="open" 
         @click.away="open = false"
         x-transition:enter="transition ease-out duration-300"
         x-transition:enter-start="opacity-0 transform scale-95"
         x-transition:enter-end="opacity-100 transform scale-100"
         x-transition:leave="transition ease-in duration-200"
         x-transition:leave-start="opacity-100 transform scale-100"
         x-transition:leave-end="opacity-0 transform scale-95"
         class="absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg py-1 z-50">
        
        <a href="#" class="block px-4 py-2 hover:bg-gray-100">Profile</a>
        <a href="#" class="block px-4 py-2 hover:bg-gray-100">Settings</a>
        <a href="#" class="block px-4 py-2 hover:bg-gray-100">Exit</a>
    </div>
</div>

۳. Tab (زبانه‌ها)

<div x-data="{ activeTab: 'tab1' }">
    <!-- Tab Header -->
    <div class="border-b">
        <button @click="activeTab = 'tab1'" 
                :class="activeTab === 'tab1' ? 'border-blue-500 text-blue-600' : 'border-transparent'"
                class="inline-block px-4 py-2 border-b-2">
            تب اول
        </button>
        <button @click="activeTab = 'tab2'"
                :class="activeTab === 'tab2' ? 'border-blue-500 text-blue-600' : 'border-transparent'"
                class="inline-block px-4 py-2 border-b-2">
            تب دوم
        </button>
        <button @click="activeTab = 'tab3'"
                :class="activeTab === 'tab3' ? 'border-blue-500 text-blue-600' : 'border-transparent'"
                class="inline-block px-4 py-2 border-b-2">
            تب سوم
        </button>
    </div>

    <!-- Tab content -->
    <div class="p-4">
        <div x-show="activeTab === 'tab1'">
            <h3>Contents of the first tab</h3>
            <p>This content is related to the first tab.</p>
        </div>
        
        <div x-show="activeTab === 'tab2'">
            <h3>Contents of the second tab</h3>
            <p>This content is related to the second tab.</p>
        </div>
        
        <div x-show="activeTab === 'tab3'">
            <h3>Contents of the third tab</h3>
            <p>اThis content is related to the third tab.</p>
        </div>
    </div>
</div>

۴. Accordion (آکاردئون)

<div x-data="{ openIndex: null }">
    <!-- آیتم ۱ -->
    <div class="border rounded mb-2">
        <button @click="openIndex = openIndex === 0 ? null : 0" 
                class="w-full text-right px-4 py-3 bg-gray-100 hover:bg-gray-200 flex justify-between items-center">
            <span>What is the first question?</span>
            <svg :class="openIndex === 0 ? 'rotate-180' : ''" 
                 class="w-4 h-4 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
            </svg>
        </button>
        <div x-show="openIndex === 0" x-collapse class="px-4 py-3">
            <p>This is the answer to the first question. The detailed content is displayed here.</p>
        </div>
    </div>

    <!-- آیتم ۲ -->
    <div class="border rounded mb-2">
        <button @click="openIndex = openIndex === 1 ? null : 1" 
                class="w-full text-right px-4 py-3 bg-gray-100 hover:bg-gray-200 flex justify-between items-center">
            <span>What is the second question?</span>
            <svg :class="openIndex === 1 ? 'rotate-180' : ''" 
                 class="w-4 h-4 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
            </svg>
        </button>
        <div x-show="openIndex === 1" x-collapse class="px-4 py-3">
            <p>This is the answer to the second question. The detailed content is displayed here.</p>
        </div>
    </div>
</div>


فصل چهارم: کامپوننت‌های پیشرفته

۱. Shopping Cart (سبد خرید)

<div x-data="{
    cart: [],
    products: [
        { id: 1, name: 'Laptop', price: 15000000 },
        { id: 2, name: 'Mouse', price: 300000 },
        { id: 3, name: 'Keyboard', price: 800000 }
    ],
    
    addToCart(product) {
        const existingItem = this.cart.find(item => item.id === product.id);
        if (existingItem) {
            existingItem.quantity  ;
        } else {
            this.cart.push({ ...product, quantity: 1 });
        }
    },
    
    removeFromCart(productId) {
        this.cart = this.cart.filter(item => item.id !== productId);
    },
    
    get cartTotal() {
        return this.cart.reduce((total, item) => total   (item.price * item.quantity), 0);
    },
    
    get cartItemsCount() {
        return this.cart.reduce((count, item) => count   item.quantity, 0);
    }
}">
    
    <!-- هدر سبد خرید -->
    <div class="bg-gray-100 p-4 mb-4">
        <span x-text="cartItemsCount"></span> Items in cart - 
        Total: <span x-text="new Intl.NumberFormat('fa-IR').format(cartTotal)"></span> Toman
    </div>

    <!-- لیست محصولات -->
    <div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
        <template x-for="product in products" :key="product.id">
            <div class="border p-4 rounded">
                <h3 x-text="product.name" class="font-bold"></h3>
                <p x-text="new Intl.NumberFormat('fa-IR').format(product.price)   ' Toman'"></p>
                <button @click="addToCart(product)" 
                        class="bg-green-500 text-white px-3 py-1 rounded mt-2">
                    افزودن به سبد
                </button>
            </div>
        </template>
    </div>

    <!-- سبد خرید -->
    <div x-show="cart.length > 0" class="border-t pt-4">
        <h3 class="font-bold mb-2">Shopping Cart</h3>
        <template x-for="item in cart" :key="item.id">
            <div class="flex justify-between items-center border-b py-2">
                <span x-text="item.name"></span>
                <div class="flex items-center space-x-2">
                    <span x-text="item.quantity"></span>
                    <span>x</span>
                    <span x-text="new Intl.NumberFormat('fa-IR').format(item.price)"></span>
                    <button @click="removeFromCart(item.id)" 
                            class="bg-red-500 text-white px-2 py-1 rounded text-sm">
                        حذف
                    </button>
                </div>
            </div>
        </template>
    </div>
</div>

۲. Image Gallery (گالری تصاویر)

<div x-data="{
    images: [
        'image1.jpg',
        'image2.jpg', 
        'image3.jpg',
        'image4.jpg'
    ],
    currentIndex: 0,
    
    nextImage() {
        this.currentIndex = (this.currentIndex   1) % this.images.length;
    },
    
    prevImage() {
        this.currentIndex = (this.currentIndex - 1   this.images.length) % this.images.length;
    },
    
    selectImage(index) {
        this.currentIndex = index;
    }
}">
    
    <!-- تصویر اصلی -->
    <div class="relative">
        <img :src="images[currentIndex]" 
             :alt="'Image '   (currentIndex   1)"
             class="w-full h-64 object-cover rounded">
        
        <!-- دکمه‌های کنترل -->
        <button @click="prevImage" 
                class="absolute right-0 top-1/2 transform -translate-y-1/2 bg-black bg-opacity-50 text-white p-2">
            ‹
        </button>
        <button @click="nextImage" 
                class="absolute left-0 top-1/2 transform -translate-y-1/2 bg-black bg-opacity-50 text-white p-2">
            ›
        </button>
    </div>

    <!-- تصاویر کوچک -->
    <div class="flex space-x-2 mt-4">
        <template x-for="(image, index) in images" :key="index">
            <img :src="image" 
                 @click="selectImage(index)"
                 :class="currentIndex === index ? 'border-2 border-blue-500' : 'border border-gray-300'"
                 class="w-16 h-16 object-cover cursor-pointer rounded">
        </template>
    </div>
</div>


فصل پنجم: ترفندها و بهترین روش‌ها

۱. استفاده از $el و $refs

<div x-data="{
    scrollToTop() {
        window.scrollTo({ top: 0, behavior: 'smooth' });
    },
    
    focusInput() {
        this.$refs.myInput.focus();
    }
}">
    <button @click="scrollToTop" class="bg-blue-500 text-white px-4 py-2 rounded">
        بازگشت به بالا
    </button>
    
    <input x-ref="myInput" type="text" placeholder="این input فوکوس می‌شود">
    <button @click="focusInput" class="bg-green-500 text-white px-4 py-2 rounded">
        Focus on input
    </button>
</div>

۲. استفاده از $watch

<div x-data="{
    searchQuery: '',
    results: [],
    
    init() {
        this.$watch('searchQuery', (value) => {
            if (value.length > 2) {
                // شبیه‌سازی جستجو
                this.results = ['Result 1', 'Result 2', 'Result 3'];
            } else {
                this.results = [];
            }
        });
    }
}">
    <input type="text" x-model="searchQuery" placeholder="Search...">
    
    <div x-show="results.length > 0">
        <template x-for="result in results" :key="result">
            <div x-text="result" class="p-2 border-b"></div>
        </template>
    </div>
</div>

۳. کامپوننت‌های قابل استفاده مجدد

<!-- Counter component -->
<div x-data="counter(0)">
    <button @click="increment()" class="bg-blue-500 text-white px-3 py-1 rounded">
        افزایش
    </button>
    <span x-text="count" class="mx-2"></span>
    <button @click="decrement()" class="bg-red-500 text-white px-3 py-1 rounded">
        کاهش
    </button>
</div>

<script>
function counter(initialValue = 0) {
    return {
        count: initialValue,
        increment() {
            this.count  ;
        },
        decrement() {
            this.count--;
        },
        reset() {
            this.count = initialValue;
        }
    }
}
</script>


نتیجه‌گیری
Alpine.js ابزاری فوق‌العاده برای افزودن تعاملات پویا به پروژه‌های وب است. با یادگیری این کتابخانه سبک‌وزن، می‌توانید بدون نیاز به فریمورک‌های پیچیده، کامپوننت‌های تعاملی زیبا و کاربردی ایجاد کنید.


نکات کلیدی:
· برای پروژه‌های کوچک تا متوسط ایده‌آل است
· یادگیری سریع و پیاده‌سازی آسان
· یکپارچگی عالی با Tailwind CSS
· بدون نیاز به Build Process
شروع کنید و اولین کامپوننت Alpine.js خود را امروز بسازید!
Alpinejsآموزش Alpineجاوااسکریپتفریمورک سبکModalDropdownTabکامپوننت تعاملی

نظرات

برای ارسال نظر باید وارد شوید. ورود یا ثبت نام
هنوز نظری ثبت نشده است.
بازدید روزانه: 11
بازدید هفتگی: 134
بازدید ماهانه: 134
بازدید سالانه: 8053
0%