├── web ├── step-01 │ ├── images │ │ ├── img-1.png │ │ └── img-2.png │ └── README.md ├── step-02 │ ├── images │ │ ├── img-1.png │ │ ├── img-2.png │ │ ├── img-3.png │ │ ├── img-4.png │ │ ├── img-5.png │ │ ├── img-6.png │ │ ├── img-7.png │ │ ├── img-8.png │ │ ├── img-9.png │ │ ├── counter.gif │ │ ├── img-10.png │ │ ├── img-11.png │ │ ├── img-12.png │ │ └── css-declaration-1.png │ ├── src │ │ ├── counter.js │ │ ├── counter.html │ │ └── counter.css │ └── README.md ├── step-03 │ ├── images │ │ ├── img-1.png │ │ ├── img-2.png │ │ ├── img-3.png │ │ ├── img-4.png │ │ ├── img-5.png │ │ ├── img-6.png │ │ ├── img-7.png │ │ ├── img-8.png │ │ ├── img-9.png │ │ ├── counter.gif │ │ ├── img-10.png │ │ ├── img-11.png │ │ └── img-12.png │ ├── src │ │ ├── SimpleBlazorCounter │ │ │ ├── Pages │ │ │ │ ├── Counter.razor.cs │ │ │ │ ├── Counter.razor │ │ │ │ └── Counter.razor.css │ │ │ ├── App.razor │ │ │ ├── _Imports.razor │ │ │ ├── wwwroot │ │ │ │ └── index.html │ │ │ ├── Properties │ │ │ │ └── launchSettings.json │ │ │ ├── SimpleBlazorCounter.csproj │ │ │ └── Program.cs │ │ └── SimpleBlazorCounter.sln │ └── README.md ├── step-04 │ ├── images │ │ ├── img-1.png │ │ ├── img-2.png │ │ ├── img-3.png │ │ ├── img-4.png │ │ ├── img-5.png │ │ ├── img-6.png │ │ ├── img-7.png │ │ ├── img-9.png │ │ ├── img-11.png │ │ └── calculator.gif │ ├── src │ │ ├── SimpleBlazorCalculator │ │ │ ├── App.razor │ │ │ ├── _Imports.razor │ │ │ ├── wwwroot │ │ │ │ └── index.html │ │ │ ├── Properties │ │ │ │ └── launchSettings.json │ │ │ ├── SimpleBlazorCalculator.csproj │ │ │ ├── Program.cs │ │ │ └── Pages │ │ │ │ ├── Calculator.razor │ │ │ │ ├── Calculator.razor.cs │ │ │ │ └── Calculator.razor.scss │ │ └── SimpleBlazorCalculator.sln │ └── README.md ├── step-05 │ ├── images │ │ ├── img-4.png │ │ ├── img-5.png │ │ ├── img-6.png │ │ ├── img-7.png │ │ ├── img-9.png │ │ ├── img-1-1.png │ │ ├── img-10.png │ │ ├── img-12.png │ │ ├── img-13-1.png │ │ ├── img-13.png │ │ ├── img-14.png │ │ ├── img-15-2.png │ │ ├── img-2-2.png │ │ ├── img-3-1.png │ │ ├── img-3-3.png │ │ ├── img-5-1.png │ │ ├── img-8-2.png │ │ └── responsive.png │ ├── src │ │ ├── ToDoApp │ │ │ ├── wwwroot │ │ │ │ ├── images │ │ │ │ │ ├── logo.png │ │ │ │ │ ├── logo-small.png │ │ │ │ │ └── background-image.png │ │ │ │ ├── main.css │ │ │ │ └── index.html │ │ │ ├── App.razor │ │ │ ├── _Imports.razor │ │ │ ├── Pages │ │ │ │ ├── Login.razor.cs │ │ │ │ ├── Login.razor │ │ │ │ └── Login.razor.scss │ │ │ ├── Properties │ │ │ │ └── launchSettings.json │ │ │ ├── ToDoApp.csproj │ │ │ └── Program.cs │ │ └── ToDoApp.sln │ └── README.md └── step-06 │ ├── images │ ├── img-2.png │ ├── img-3.png │ ├── img-4.png │ ├── img-5.png │ ├── img-6.png │ ├── img-7.png │ ├── img-8.png │ ├── img-9.png │ ├── img-1-1.png │ └── img-10.png │ ├── src │ ├── ToDoApp │ │ ├── wwwroot │ │ │ ├── images │ │ │ │ ├── logo.png │ │ │ │ ├── logo-small.png │ │ │ │ └── background-image.png │ │ │ ├── main.css │ │ │ └── index.html │ │ ├── compilerconfig.json │ │ ├── TodoItem.cs │ │ ├── App.razor │ │ ├── _Imports.razor │ │ ├── Pages │ │ │ ├── Login.razor.cs │ │ │ ├── Login.razor │ │ │ ├── TodoPage.razor.scss │ │ │ ├── Login.razor.scss │ │ │ ├── TodoPage.razor │ │ │ └── TodoPage.razor.cs │ │ ├── Properties │ │ │ └── launchSettings.json │ │ ├── Program.cs │ │ ├── ToDoApp.csproj │ │ └── compilerconfig.json.defaults │ └── ToDoApp.sln │ └── README.md ├── markdown-styles.css ├── README.md ├── LICENSE └── .gitignore /web/step-01/images/img-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-01/images/img-1.png -------------------------------------------------------------------------------- /web/step-01/images/img-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-01/images/img-2.png -------------------------------------------------------------------------------- /web/step-02/images/img-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-02/images/img-1.png -------------------------------------------------------------------------------- /web/step-02/images/img-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-02/images/img-2.png -------------------------------------------------------------------------------- /web/step-02/images/img-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-02/images/img-3.png -------------------------------------------------------------------------------- /web/step-02/images/img-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-02/images/img-4.png -------------------------------------------------------------------------------- /web/step-02/images/img-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-02/images/img-5.png -------------------------------------------------------------------------------- /web/step-02/images/img-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-02/images/img-6.png -------------------------------------------------------------------------------- /web/step-02/images/img-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-02/images/img-7.png -------------------------------------------------------------------------------- /web/step-02/images/img-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-02/images/img-8.png -------------------------------------------------------------------------------- /web/step-02/images/img-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-02/images/img-9.png -------------------------------------------------------------------------------- /web/step-03/images/img-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-03/images/img-1.png -------------------------------------------------------------------------------- /web/step-03/images/img-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-03/images/img-2.png -------------------------------------------------------------------------------- /web/step-03/images/img-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-03/images/img-3.png -------------------------------------------------------------------------------- /web/step-03/images/img-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-03/images/img-4.png -------------------------------------------------------------------------------- /web/step-03/images/img-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-03/images/img-5.png -------------------------------------------------------------------------------- /web/step-03/images/img-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-03/images/img-6.png -------------------------------------------------------------------------------- /web/step-03/images/img-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-03/images/img-7.png -------------------------------------------------------------------------------- /web/step-03/images/img-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-03/images/img-8.png -------------------------------------------------------------------------------- /web/step-03/images/img-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-03/images/img-9.png -------------------------------------------------------------------------------- /web/step-04/images/img-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-04/images/img-1.png -------------------------------------------------------------------------------- /web/step-04/images/img-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-04/images/img-2.png -------------------------------------------------------------------------------- /web/step-04/images/img-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-04/images/img-3.png -------------------------------------------------------------------------------- /web/step-04/images/img-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-04/images/img-4.png -------------------------------------------------------------------------------- /web/step-04/images/img-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-04/images/img-5.png -------------------------------------------------------------------------------- /web/step-04/images/img-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-04/images/img-6.png -------------------------------------------------------------------------------- /web/step-04/images/img-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-04/images/img-7.png -------------------------------------------------------------------------------- /web/step-04/images/img-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-04/images/img-9.png -------------------------------------------------------------------------------- /web/step-05/images/img-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-05/images/img-4.png -------------------------------------------------------------------------------- /web/step-05/images/img-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-05/images/img-5.png -------------------------------------------------------------------------------- /web/step-05/images/img-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-05/images/img-6.png -------------------------------------------------------------------------------- /web/step-05/images/img-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-05/images/img-7.png -------------------------------------------------------------------------------- /web/step-05/images/img-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-05/images/img-9.png -------------------------------------------------------------------------------- /web/step-06/images/img-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-06/images/img-2.png -------------------------------------------------------------------------------- /web/step-06/images/img-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-06/images/img-3.png -------------------------------------------------------------------------------- /web/step-06/images/img-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-06/images/img-4.png -------------------------------------------------------------------------------- /web/step-06/images/img-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-06/images/img-5.png -------------------------------------------------------------------------------- /web/step-06/images/img-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-06/images/img-6.png -------------------------------------------------------------------------------- /web/step-06/images/img-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-06/images/img-7.png -------------------------------------------------------------------------------- /web/step-06/images/img-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-06/images/img-8.png -------------------------------------------------------------------------------- /web/step-06/images/img-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-06/images/img-9.png -------------------------------------------------------------------------------- /web/step-02/images/counter.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-02/images/counter.gif -------------------------------------------------------------------------------- /web/step-02/images/img-10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-02/images/img-10.png -------------------------------------------------------------------------------- /web/step-02/images/img-11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-02/images/img-11.png -------------------------------------------------------------------------------- /web/step-02/images/img-12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-02/images/img-12.png -------------------------------------------------------------------------------- /web/step-03/images/counter.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-03/images/counter.gif -------------------------------------------------------------------------------- /web/step-03/images/img-10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-03/images/img-10.png -------------------------------------------------------------------------------- /web/step-03/images/img-11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-03/images/img-11.png -------------------------------------------------------------------------------- /web/step-03/images/img-12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-03/images/img-12.png -------------------------------------------------------------------------------- /web/step-04/images/img-11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-04/images/img-11.png -------------------------------------------------------------------------------- /web/step-05/images/img-1-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-05/images/img-1-1.png -------------------------------------------------------------------------------- /web/step-05/images/img-10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-05/images/img-10.png -------------------------------------------------------------------------------- /web/step-05/images/img-12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-05/images/img-12.png -------------------------------------------------------------------------------- /web/step-05/images/img-13-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-05/images/img-13-1.png -------------------------------------------------------------------------------- /web/step-05/images/img-13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-05/images/img-13.png -------------------------------------------------------------------------------- /web/step-05/images/img-14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-05/images/img-14.png -------------------------------------------------------------------------------- /web/step-05/images/img-15-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-05/images/img-15-2.png -------------------------------------------------------------------------------- /web/step-05/images/img-2-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-05/images/img-2-2.png -------------------------------------------------------------------------------- /web/step-05/images/img-3-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-05/images/img-3-1.png -------------------------------------------------------------------------------- /web/step-05/images/img-3-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-05/images/img-3-3.png -------------------------------------------------------------------------------- /web/step-05/images/img-5-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-05/images/img-5-1.png -------------------------------------------------------------------------------- /web/step-05/images/img-8-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-05/images/img-8-2.png -------------------------------------------------------------------------------- /web/step-06/images/img-1-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-06/images/img-1-1.png -------------------------------------------------------------------------------- /web/step-06/images/img-10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-06/images/img-10.png -------------------------------------------------------------------------------- /web/step-04/images/calculator.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-04/images/calculator.gif -------------------------------------------------------------------------------- /web/step-05/images/responsive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-05/images/responsive.png -------------------------------------------------------------------------------- /web/step-02/images/css-declaration-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-02/images/css-declaration-1.png -------------------------------------------------------------------------------- /web/step-05/src/ToDoApp/wwwroot/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-05/src/ToDoApp/wwwroot/images/logo.png -------------------------------------------------------------------------------- /web/step-06/src/ToDoApp/wwwroot/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-06/src/ToDoApp/wwwroot/images/logo.png -------------------------------------------------------------------------------- /web/step-05/src/ToDoApp/wwwroot/images/logo-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-05/src/ToDoApp/wwwroot/images/logo-small.png -------------------------------------------------------------------------------- /web/step-06/src/ToDoApp/compilerconfig.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "outputFile": "Pages/TodoPage.razor.css", 4 | "inputFile": "Pages/TodoPage.razor.scss" 5 | } 6 | ] -------------------------------------------------------------------------------- /web/step-06/src/ToDoApp/wwwroot/images/logo-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-06/src/ToDoApp/wwwroot/images/logo-small.png -------------------------------------------------------------------------------- /web/step-05/src/ToDoApp/wwwroot/images/background-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-05/src/ToDoApp/wwwroot/images/background-image.png -------------------------------------------------------------------------------- /web/step-06/src/ToDoApp/wwwroot/images/background-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfoundation/bit-academy/HEAD/web/step-06/src/ToDoApp/wwwroot/images/background-image.png -------------------------------------------------------------------------------- /web/step-02/src/counter.js: -------------------------------------------------------------------------------- 1 | let counter = document.getElementById("count"); 2 | 3 | function increase() { 4 | counter.textContent++; 5 | } 6 | 7 | function decrease() { 8 | counter.textContent--; 9 | } 10 | -------------------------------------------------------------------------------- /markdown-styles.css: -------------------------------------------------------------------------------- 1 | .markdown-body { 2 | font-family: "Segoe UI"; 3 | } 4 | 5 | .markdown-body p { 6 | font-size: 16px; 7 | line-height: 1.5; 8 | text-align: justify; 9 | text-align-last: right; 10 | } 11 | -------------------------------------------------------------------------------- /web/step-06/src/ToDoApp/TodoItem.cs: -------------------------------------------------------------------------------- 1 | namespace ToDoApp; 2 | 3 | public class TodoItem 4 | { 5 | public int Id { get; set; } 6 | 7 | public string Title { get; set; } 8 | 9 | public bool IsDone { get; set; } 10 | 11 | public bool IsEdit { get; set; } = false; 12 | } 13 | 14 | -------------------------------------------------------------------------------- /web/step-05/src/ToDoApp/wwwroot/main.css: -------------------------------------------------------------------------------- 1 | * { 2 | padding: 0; 3 | margin: 0; 4 | } 5 | 6 | body { 7 | font-family: "Segoe UI", "Segoe UI Web (West European)", "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, "Helvetica Neue", sans-serif; 8 | } 9 | 10 | .text-field { 11 | width: 300px; 12 | margin-bottom: 10px; 13 | } 14 | -------------------------------------------------------------------------------- /web/step-03/src/SimpleBlazorCounter/Pages/Counter.razor.cs: -------------------------------------------------------------------------------- 1 | namespace SimpleBlazorCounter.Pages; 2 | 3 | public partial class Counter 4 | { 5 | public int CurrentCount { get; set; } 6 | 7 | public void Increase() 8 | { 9 | CurrentCount++; 10 | } 11 | 12 | public void Decrease() 13 | { 14 | CurrentCount--; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /web/step-05/src/ToDoApp/App.razor: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

Sorry, there's nothing at this address.

8 |
9 |
10 |
11 | -------------------------------------------------------------------------------- /web/step-06/src/ToDoApp/App.razor: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

Sorry, there's nothing at this address.

8 |
9 |
10 |
11 | -------------------------------------------------------------------------------- /web/step-03/src/SimpleBlazorCounter/App.razor: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

Sorry, there's nothing at this address.

8 |
9 |
10 |
11 | -------------------------------------------------------------------------------- /web/step-04/src/SimpleBlazorCalculator/App.razor: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

Sorry, there's nothing at this address.

8 |
9 |
10 |
11 | -------------------------------------------------------------------------------- /web/step-05/src/ToDoApp/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using System.Net.Http.Json 3 | @using Microsoft.AspNetCore.Components.Forms 4 | @using Microsoft.AspNetCore.Components.Routing 5 | @using Microsoft.AspNetCore.Components.Web 6 | @using Microsoft.AspNetCore.Components.Web.Virtualization 7 | @using Microsoft.AspNetCore.Components.WebAssembly.Http 8 | @using Microsoft.JSInterop 9 | @using ToDoApp 10 | @using Bit.BlazorUI -------------------------------------------------------------------------------- /web/step-06/src/ToDoApp/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using System.Net.Http.Json 3 | @using Microsoft.AspNetCore.Components.Forms 4 | @using Microsoft.AspNetCore.Components.Routing 5 | @using Microsoft.AspNetCore.Components.Web 6 | @using Microsoft.AspNetCore.Components.Web.Virtualization 7 | @using Microsoft.AspNetCore.Components.WebAssembly.Http 8 | @using Microsoft.JSInterop 9 | @using ToDoApp 10 | @using Bit.BlazorUI -------------------------------------------------------------------------------- /web/step-03/src/SimpleBlazorCounter/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using System.Net.Http.Json 3 | @using Microsoft.AspNetCore.Components.Forms 4 | @using Microsoft.AspNetCore.Components.Routing 5 | @using Microsoft.AspNetCore.Components.Web 6 | @using Microsoft.AspNetCore.Components.Web.Virtualization 7 | @using Microsoft.AspNetCore.Components.WebAssembly.Http 8 | @using Microsoft.JSInterop 9 | @using SimpleBlazorCounter 10 | 11 | -------------------------------------------------------------------------------- /web/step-04/src/SimpleBlazorCalculator/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using System.Net.Http.Json 3 | @using Microsoft.AspNetCore.Components.Forms 4 | @using Microsoft.AspNetCore.Components.Routing 5 | @using Microsoft.AspNetCore.Components.Web 6 | @using Microsoft.AspNetCore.Components.Web.Virtualization 7 | @using Microsoft.AspNetCore.Components.WebAssembly.Http 8 | @using Microsoft.JSInterop 9 | @using SimpleBlazorCalculator 10 | -------------------------------------------------------------------------------- /web/step-03/src/SimpleBlazorCounter/Pages/Counter.razor: -------------------------------------------------------------------------------- 1 | @page "/" 2 | 3 |
4 |
5 | @CurrentCount 6 |
7 |
8 | 11 | 14 |
15 |
16 | -------------------------------------------------------------------------------- /web/step-03/src/SimpleBlazorCounter/wwwroot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | SimpleBlazorCounter 8 | 9 | 10 | 11 | 12 |
Loading...
13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /web/step-04/src/SimpleBlazorCalculator/wwwroot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | SimpleBlazorCalculator 8 | 9 | 10 | 11 | 12 |
Loading...
13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /web/step-05/src/ToDoApp/Pages/Login.razor.cs: -------------------------------------------------------------------------------- 1 | namespace ToDoApp.Pages; 2 | 3 | public partial class Login 4 | { 5 | public string UserName { get; set; } 6 | public string Password { get; set; } 7 | 8 | public bool ShowLoginErrorMessage = false; 9 | 10 | public void Signin() 11 | { 12 | if (UserName != "test" || Password != "test") 13 | { 14 | ShowLoginErrorMessage = true; 15 | } 16 | else 17 | { 18 | ShowLoginErrorMessage = false; 19 | } 20 | } 21 | } 22 | 23 | -------------------------------------------------------------------------------- /web/step-05/src/ToDoApp/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "ToDoApp": { 4 | "launchUrl": "login", 5 | "commandName": "Project", 6 | "dotnetRunMessages": "true", 7 | "launchBrowser": true, 8 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 9 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 10 | "environmentVariables": { 11 | "ASPNETCORE_ENVIRONMENT": "Development" 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /web/step-06/src/ToDoApp/Pages/Login.razor.cs: -------------------------------------------------------------------------------- 1 | namespace ToDoApp.Pages; 2 | 3 | public partial class Login 4 | { 5 | public string UserName { get; set; } 6 | public string Password { get; set; } 7 | 8 | public bool ShowLoginErrorMessage = false; 9 | 10 | public void Signin() 11 | { 12 | if (UserName != "test" || Password != "test") 13 | { 14 | ShowLoginErrorMessage = true; 15 | } 16 | else 17 | { 18 | NavigationManager.NavigateTo("/Todo"); 19 | } 20 | } 21 | } 22 | 23 | -------------------------------------------------------------------------------- /web/step-06/src/ToDoApp/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "ToDoApp": { 4 | "commandName": "Project", 5 | "dotnetRunMessages": "true", 6 | "launchBrowser": true, 7 | "launchUrl": "login", 8 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 9 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 10 | "environmentVariables": { 11 | "ASPNETCORE_ENVIRONMENT": "Development" 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /web/step-03/src/SimpleBlazorCounter/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "SimpleBlazorCounter": { 4 | "commandName": "Project", 5 | "dotnetRunMessages": "true", 6 | "launchBrowser": true, 7 | "launchUrl": "", 8 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 9 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 10 | "environmentVariables": { 11 | "ASPNETCORE_ENVIRONMENT": "Development" 12 | } 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /web/step-03/src/SimpleBlazorCounter/SimpleBlazorCounter.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /web/step-04/src/SimpleBlazorCalculator/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "SimpleBlazorCalculator": { 4 | "commandName": "Project", 5 | "dotnetRunMessages": "true", 6 | "launchUrl": "calculator", 7 | "launchBrowser": true, 8 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 9 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 10 | "environmentVariables": { 11 | "ASPNETCORE_ENVIRONMENT": "Development" 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /web/step-06/src/ToDoApp/wwwroot/main.css: -------------------------------------------------------------------------------- 1 | * { 2 | padding: 0; 3 | margin: 0; 4 | } 5 | 6 | body { 7 | font-family: "Segoe UI", "Segoe UI Web (West European)", "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, "Helvetica Neue", sans-serif; 8 | } 9 | 10 | .text-field { 11 | width: 300px; 12 | margin-bottom: 10px; 13 | } 14 | 15 | .bit-bsc-lst { 16 | height: auto; 17 | } 18 | 19 | .todo-list { 20 | height: auto; 21 | } 22 | 23 | .completed, 24 | .accept { 25 | color: #2ecc71; 26 | } 27 | 28 | 29 | .edit { 30 | color: #f1c40f; 31 | } 32 | 33 | .delete, 34 | .cancel { 35 | color: #d35400; 36 | } -------------------------------------------------------------------------------- /web/step-04/src/SimpleBlazorCalculator/SimpleBlazorCalculator.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /web/step-06/src/ToDoApp/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components.WebAssembly.Hosting; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using System; 4 | using System.Net.Http; 5 | using System.Threading.Tasks; 6 | 7 | namespace ToDoApp; 8 | 9 | public class Program 10 | { 11 | public static async Task Main(string[] args) 12 | { 13 | var builder = WebAssemblyHostBuilder.CreateDefault(args); 14 | builder.RootComponents.Add("#app"); 15 | 16 | builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); 17 | 18 | await builder.Build().RunAsync(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /web/step-05/src/ToDoApp/ToDoApp.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | # ‫آکادمی مجازی ‌Bit 4 | در اینجا می‌توانید هر آن‌چه که برای شروع توسعه‌ نرم‌افزارهای وب، موبایل و‫ Backend لازم دارید را فرا بگیرید. 5 | 6 | برای دریافت آموزش‌ها به [این آدرس](https://github.com/bitfoundation/bit-academy/tree/develop/web) بروید و به هر مشکلی که خوردید با خیال راحت یک گزارش در [اینجا](https://github.com/bitfoundation/bit-academy/issues/new) قرار دهید! 7 | 8 | کد نهایی هر قسمت در کنار توضیحات آن قرار دارد، میتوانید از آن هم برای درک بهتر توضیحات استفاده کنید. برای دانلود تمامی کدها از [این لینک](https://github.com/bitfoundation/bit-academy/archive/refs/heads/develop.zip) استفاده کنید. 9 | 10 | براتون آرزوی موفقیت داریم 11 | 12 |
13 | -------------------------------------------------------------------------------- /web/step-02/src/counter.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Counter 5 | 6 | 7 | 8 |
9 |
10 | 0 11 |
12 |
13 | 16 | 19 |
20 |
21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /web/step-05/src/ToDoApp/wwwroot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | ToDoApp 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
Loading...
16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /web/step-06/src/ToDoApp/wwwroot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | ToDoApp 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
Loading...
18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /web/step-05/src/ToDoApp/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components.WebAssembly.Hosting; 2 | using Microsoft.Extensions.Configuration; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Microsoft.Extensions.Logging; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Net.Http; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace ToDoApp 12 | { 13 | public class Program 14 | { 15 | public static async Task Main(string[] args) 16 | { 17 | var builder = WebAssemblyHostBuilder.CreateDefault(args); 18 | builder.RootComponents.Add("#app"); 19 | 20 | builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); 21 | 22 | await builder.Build().RunAsync(); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /web/step-03/src/SimpleBlazorCounter/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components.WebAssembly.Hosting; 2 | using Microsoft.Extensions.Configuration; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Microsoft.Extensions.Logging; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Net.Http; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace SimpleBlazorCounter 12 | { 13 | public class Program 14 | { 15 | public static async Task Main(string[] args) 16 | { 17 | var builder = WebAssemblyHostBuilder.CreateDefault(args); 18 | builder.RootComponents.Add("#app"); 19 | 20 | builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); 21 | 22 | await builder.Build().RunAsync(); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /web/step-04/src/SimpleBlazorCalculator/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components.WebAssembly.Hosting; 2 | using Microsoft.Extensions.Configuration; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Microsoft.Extensions.Logging; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Net.Http; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace SimpleBlazorCalculator 12 | { 13 | public class Program 14 | { 15 | public static async Task Main(string[] args) 16 | { 17 | var builder = WebAssemblyHostBuilder.CreateDefault(args); 18 | builder.RootComponents.Add("#app"); 19 | 20 | builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); 21 | 22 | await builder.Build().RunAsync(); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /web/step-04/src/SimpleBlazorCalculator/Pages/Calculator.razor: -------------------------------------------------------------------------------- 1 | @page "/calculator" 2 | 3 | 4 |
5 |
6 |
7 | 8 |
9 | 10 |
11 | 12 |
13 | 14 |
15 | 16 | 17 | 18 | 19 |
20 | 21 |
22 | 23 |
24 |
25 |
26 | 27 | -------------------------------------------------------------------------------- /web/step-06/src/ToDoApp/ToDoApp.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /web/step-04/src/SimpleBlazorCalculator/Pages/Calculator.razor.cs: -------------------------------------------------------------------------------- 1 | namespace SimpleBlazorCalculator.Pages; 2 | 3 | public partial class Calculator 4 | { 5 | public decimal Num1 { get; set; } 6 | 7 | public decimal Num2 { get; set; } 8 | 9 | public string FinalResult { get; set; } 10 | 11 | public void AddNumbers() 12 | { 13 | FinalResult = (Num1 + Num2).ToString("0.##"); 14 | } 15 | 16 | public void SubtractNumbers() 17 | { 18 | FinalResult = (Num1 - Num2).ToString("0.##"); 19 | } 20 | 21 | public void MultiplyNumbers() 22 | { 23 | FinalResult = (Num1 * Num2).ToString("0.##"); 24 | } 25 | 26 | public void DivideNumbers() 27 | { 28 | if (Num2 != 0) 29 | { 30 | FinalResult = (Num1 / Num2).ToString("0.##"); 31 | } 32 | else 33 | { 34 | FinalResult = "Cannot Divide by Zero"; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /web/step-02/src/counter.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #fafafa; 3 | } 4 | 5 | .card { 6 | padding: 20px; 7 | margin: 200px auto; 8 | width: 400px; 9 | height: 400px; 10 | background-color: lightskyblue; 11 | box-shadow: 1px 2px 10px 0 #808080; 12 | } 13 | 14 | .counter { 15 | margin: 50px auto; 16 | width: 150px; 17 | height: 150px; 18 | line-height: 150px; 19 | background-color: #eee; 20 | border-radius: 50%; 21 | border: 10px solid #2196f3; 22 | text-align: center; 23 | font-size: 60px; 24 | font-weight: bold; 25 | color: #1077c2; 26 | } 27 | 28 | .action { 29 | text-align: center; 30 | } 31 | 32 | button { 33 | padding: 20px 30px; 34 | margin: 5px; 35 | font-size: 24px; 36 | font-weight: bold; 37 | border-radius: 5px; 38 | cursor: pointer; 39 | color: white 40 | } 41 | 42 | .increase { 43 | background-color: #18cd73; 44 | border: 3px solid #19a35d; 45 | } 46 | 47 | .decrease { 48 | background-color: #ef7694; 49 | border: 3px solid #b74b66; 50 | } 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Bit Foundation 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /web/step-04/src/SimpleBlazorCalculator/Pages/Calculator.razor.scss: -------------------------------------------------------------------------------- 1 | // Variables 2 | // 3 | // Color system 4 | $light-blue: cornflowerblue !default; 5 | $light-gray: #dcdcdc !default; 6 | 7 | // Font 8 | $font-family: "Segoe UI", Roboto, Arial, sans-serif !default; 9 | 10 | .card { 11 | margin: 120px auto; 12 | padding: 20px; 13 | width: 400px; 14 | height: 400px; 15 | background-color: $light-blue; 16 | border-radius: 5px; 17 | box-shadow: 1px 2px 10px 0 $light-gray; 18 | } 19 | 20 | .field { 21 | margin: 15px 22 | } 23 | 24 | input { 25 | padding: 10px; 26 | width: 350px; 27 | height: 30px; 28 | color: $light-blue; 29 | border: 2px solid $light-gray; 30 | text-align: center; 31 | font-family: $font-family; 32 | font-size: 30px; 33 | } 34 | 35 | .action { 36 | margin: 30px 0; 37 | text-align: center; 38 | } 39 | 40 | .result { 41 | margin-top: 75px; 42 | } 43 | 44 | .btn { 45 | margin: 5px; 46 | width: 80px; 47 | height: 80px; 48 | border-radius: 5px; 49 | border: 2px solid $light-gray; 50 | line-height: 80px; 51 | color: $light-blue; 52 | font-size: 35px; 53 | cursor: pointer; 54 | } 55 | 56 | 57 | -------------------------------------------------------------------------------- /web/step-06/src/ToDoApp.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.31424.327 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ToDoApp", "ToDoApp\ToDoApp.csproj", "{5794712F-0D76-4B1B-A1AF-5049F8CAD5A9}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {5794712F-0D76-4B1B-A1AF-5049F8CAD5A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {5794712F-0D76-4B1B-A1AF-5049F8CAD5A9}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {5794712F-0D76-4B1B-A1AF-5049F8CAD5A9}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {5794712F-0D76-4B1B-A1AF-5049F8CAD5A9}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {379CD958-51AB-43CF-95F1-FFCA14766F8E} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /web/step-01/README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | ## جلسه اول- آموزش نصب Visual Studio 4 | 5 | در این جلسه قصد داریم روش نصب Visual Studio را یاد بگیریم، تا از آن در آموزش‌های جلسات بعدی استفاده کنیم 6 | 7 | ## لیست موضوعات 8 | 1. [آموزش نصب Visual Studio](#InstallVisualStudio) 9 | 10 | 11 | ## آموزش نصب Visual Studio 12 | 13 | در صورتی که Visual studio را نصب دارید میتوانید از این مرحله عبور کنید، ولی حتما از آپدیت بودن آن مطمئن شوید. برای این کار از منوی Help، گزینه Check updates را بزنید و در نهایت باید عبارت Visual Studio is up to date را مشاهده کنید. 14 | 15 | در ابتدا به آدرس https://visualstudio.microsoft.com/downloads می‌رویم و گزینه Visual Studio Community را انتخاب می‌کنیم که رایگان است. 16 | 17 | 18 | 19 | فایل دانلود شده را باز و سپس از بین Workloadهای مختلف که برای نوشتن بازی، برنامه های Android / iOS و... است، مطابق عکس فقط تیک ASP.NET and web development را می‌زنیم و از بین گزینه‌های سمت راست، تیک همه موارد را حذف می‌کنیم و سپس Install را می‌زنیم. این پروسه نیاز به دانلود حدودا یک گیگ فایل دارد. 20 | 21 | 22 | 23 | در پایان کار، Visual Studio شما آماده است. 24 | 25 |
26 | -------------------------------------------------------------------------------- /web/step-03/src/SimpleBlazorCounter.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.31321.278 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimpleBlazorCounter", "SimpleBlazorCounter\SimpleBlazorCounter.csproj", "{CA97419B-81FB-4D22-AF63-656CC68B6BF3}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {CA97419B-81FB-4D22-AF63-656CC68B6BF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {CA97419B-81FB-4D22-AF63-656CC68B6BF3}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {CA97419B-81FB-4D22-AF63-656CC68B6BF3}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {CA97419B-81FB-4D22-AF63-656CC68B6BF3}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {73137E9C-90C2-4B42-AF7C-EF77408F3C64} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /web/step-04/src/SimpleBlazorCalculator.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.31321.278 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimpleBlazorCalculator", "SimpleBlazorCalculator\SimpleBlazorCalculator.csproj", "{88A4A06B-EFFF-4C55-8BF7-C64D3F1C2A36}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {88A4A06B-EFFF-4C55-8BF7-C64D3F1C2A36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {88A4A06B-EFFF-4C55-8BF7-C64D3F1C2A36}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {88A4A06B-EFFF-4C55-8BF7-C64D3F1C2A36}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {88A4A06B-EFFF-4C55-8BF7-C64D3F1C2A36}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {17692F02-75B2-4FCF-BDB3-C8A93B8356E6} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /web/step-05/src/ToDoApp/Pages/Login.razor: -------------------------------------------------------------------------------- 1 | @page "/login" 2 | 3 |
4 |
5 | 6 |

7 | The ToDoApp lets you write, organize, and prioritize your tasks. 8 | This way you can be more productive by registering your tasks in the ToDo App 9 |

10 |
11 | 30 |
31 | 32 | -------------------------------------------------------------------------------- /web/step-05/src/ToDoApp.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31606.5 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ToDoApp", "ToDoApp\ToDoApp.csproj", "{5794712F-0D76-4B1B-A1AF-5049F8CAD5A9}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F7853060-B4F5-4CE1-828A-59C9CCAEDDF4}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {5794712F-0D76-4B1B-A1AF-5049F8CAD5A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {5794712F-0D76-4B1B-A1AF-5049F8CAD5A9}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {5794712F-0D76-4B1B-A1AF-5049F8CAD5A9}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {5794712F-0D76-4B1B-A1AF-5049F8CAD5A9}.Release|Any CPU.Build.0 = Release|Any CPU 20 | EndGlobalSection 21 | GlobalSection(SolutionProperties) = preSolution 22 | HideSolutionNode = FALSE 23 | EndGlobalSection 24 | GlobalSection(ExtensibilityGlobals) = postSolution 25 | SolutionGuid = {379CD958-51AB-43CF-95F1-FFCA14766F8E} 26 | EndGlobalSection 27 | EndGlobal 28 | -------------------------------------------------------------------------------- /web/step-06/src/ToDoApp/Pages/Login.razor: -------------------------------------------------------------------------------- 1 | @page "/login"; 2 | @inject NavigationManager NavigationManager; 3 | 4 |
5 |
6 | 7 |

8 | The ToDoApp lets you write, organize, and prioritize your tasks. 9 | This way you can be more productive by registering your tasks in the ToDo App 10 |

11 |
12 | 32 |
33 | 34 | -------------------------------------------------------------------------------- /web/step-06/src/ToDoApp/compilerconfig.json.defaults: -------------------------------------------------------------------------------- 1 | { 2 | "compilers": { 3 | "less": { 4 | "autoPrefix": "", 5 | "cssComb": "none", 6 | "ieCompat": true, 7 | "strictMath": false, 8 | "strictUnits": false, 9 | "relativeUrls": true, 10 | "rootPath": "", 11 | "sourceMapRoot": "", 12 | "sourceMapBasePath": "", 13 | "sourceMap": false 14 | }, 15 | "sass": { 16 | "autoPrefix": "", 17 | "includePath": "", 18 | "indentType": "space", 19 | "indentWidth": 2, 20 | "outputStyle": "nested", 21 | "Precision": 5, 22 | "relativeUrls": true, 23 | "sourceMapRoot": "", 24 | "lineFeed": "", 25 | "sourceMap": false 26 | }, 27 | "stylus": { 28 | "sourceMap": false 29 | }, 30 | "babel": { 31 | "sourceMap": false 32 | }, 33 | "coffeescript": { 34 | "bare": false, 35 | "runtimeMode": "node", 36 | "sourceMap": false 37 | }, 38 | "handlebars": { 39 | "root": "", 40 | "noBOM": false, 41 | "name": "", 42 | "namespace": "", 43 | "knownHelpersOnly": false, 44 | "forcePartial": false, 45 | "knownHelpers": [], 46 | "commonjs": "", 47 | "amd": false, 48 | "sourceMap": false 49 | } 50 | }, 51 | "minifiers": { 52 | "css": { 53 | "enabled": true, 54 | "termSemicolons": true, 55 | "gzip": false 56 | }, 57 | "javascript": { 58 | "enabled": true, 59 | "termSemicolons": true, 60 | "gzip": false 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /web/step-06/src/ToDoApp/Pages/TodoPage.razor.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | margin: 40px auto; 3 | max-width: 500px; 4 | 5 | .todo-count { 6 | margin-bottom: 20px; 7 | font-size: 80px; 8 | font-weight: 100; 9 | text-align: center; 10 | color: rgba(175, 47, 47, 0.15); 11 | text-rendering: optimizeLegibility; 12 | } 13 | 14 | input { 15 | width: 100%; 16 | padding: 10px; 17 | } 18 | 19 | .searchbox { 20 | margin: 10px 0; 21 | } 22 | 23 | .todo-add { 24 | display: flex; 25 | padding: 10px; 26 | border: none; 27 | background: rgba(0, 0, 0, 0.003); 28 | box-shadow: inset 0 -2px 1px #cecece; 29 | font-size: 27px; 30 | } 31 | 32 | .todo-app { 33 | background: #fff; 34 | margin: 20px 0 40px 0; 35 | position: relative; 36 | box-shadow: 0 2px 4px 0 #ccc; 37 | } 38 | 39 | .todo-item { 40 | display: flex; 41 | flex-direction: row; 42 | align-items: center; 43 | padding: 15px; 44 | border-bottom: 1px solid #e6e6e6; 45 | margin: 10px 0; 46 | } 47 | 48 | .todo-title { 49 | display: flex; 50 | align-items: center; 51 | flex: auto; 52 | } 53 | 54 | .filters { 55 | list-style: none; 56 | display: flex; 57 | } 58 | 59 | .todo-action { 60 | display: flex; 61 | width: 120px; 62 | justify-content: flex-end; 63 | } 64 | 65 | .footer { 66 | display: flex; 67 | flex-direction: row; 68 | align-items: center; 69 | color: #777; 70 | font-size: 14px; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /web/step-03/src/SimpleBlazorCounter/Pages/Counter.razor.css: -------------------------------------------------------------------------------- 1 | body { 2 | Background-color: #fafafa; 3 | } 4 | 5 | 6 | .card { 7 | Padding: 20px; /* .برای تعیین فاصله المنت از محتوای درون المنت استفاده می‌شود */ 8 | Margin: 200px auto; /* .برای تعیین فاصله المنت از محتوای بیرون از المنت استفاده می‌شود */ 9 | Width: 400px; /* .برای تعیین عرض المنت استفاده می‌شود */ 10 | Height: 400px; /* .برای تعین ارتفاع المنت استفاده می‌شود */ 11 | Background-color: lightskyblue; /* .برای تعیین رنگ پس زمینه المنت استفاده می‌شود */ 12 | Box-shadow: 1px 2px 10px 0 #808080; /* .برای افزودن سایه به المنت مورد نظر استفاده می‌شود */ 13 | Border-radius: 5px; /* .باافزودن شعاع به گوشه‌های یک المنت که در نهایت موجب خمیدگی آن گوشه می‌شود */ 14 | } 15 | 16 | 17 | .counter { 18 | margin: 50px auto; 19 | width: 150px; 20 | height: 150px; 21 | line-height: 150px; /* .متن داخل المنت به اندازه مقداری که داده شده از خط قبل و بعد فاصله می‌گیرد */ 22 | background-color: #eee; 23 | border-radius: 50%; /* .با دادن این مقدار به این پراپرتی المنت دایره‌ای شکل می‌شود */ 24 | border: 10px solid #2196f3; /* .استایل، عرض و رنگ حاشیه المنت را مشخص می‌کند */ 25 | text-align: center; /* .نحوه چیدمان متن داخل المنت را مشخص می‌کند */ 26 | font-size: 60px; /* .سایز فونت متن داخل المنت را مشخص می‌کند */ 27 | Font-weight: bold; /* .ضخامت متن داخل المنت را مشخص می‌کند */ 28 | color: #107732; /* .رنگ متن داخل المنت را مشخص می‌کند */ 29 | } 30 | 31 | .action { 32 | text-align: center; 33 | } 34 | 35 | button { 36 | padding: 20px 30px; 37 | margin: 5px; 38 | font-size: 24px; 39 | font-weight: bold; 40 | border-radius: 5px; 41 | cursor: pointer; 42 | color: white; /* .زمانی که با موس روی دکمه‌ها می‌رویم اشاره گر ماوس به شکل دست در می‌یاید */ 43 | } 44 | 45 | .increase { 46 | background-color: #18cd73; 47 | border: 3px solid #19a35d; 48 | } 49 | 50 | .decrease { 51 | background-color: #ef7694; 52 | border: 3px solid #b74b66; 53 | } 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /web/step-05/src/ToDoApp/Pages/Login.razor.scss: -------------------------------------------------------------------------------- 1 | // Variables 2 | // 3 | // Color system 4 | $white: #fff !default; 5 | $purple: #6264A7 !default; 6 | $light-gray: #aaa !default; 7 | $dark-gray: #333 !default; 8 | 9 | .container { 10 | display: flex; 11 | flex-direction: row; 12 | justify-content: center; 13 | align-items: center; 14 | width: 100vw; 15 | height: 100vh; 16 | background-image: url("./images/background-image.png"); 17 | background-size: cover; 18 | background-repeat: no-repeat; 19 | } 20 | 21 | .card { 22 | display: flex; 23 | flex-direction: column; 24 | padding: 0 40px 100px 40px; 25 | box-shadow: 1px 2px 10px 0 $light-gray; 26 | width: 400px; 27 | height: 500px; 28 | 29 | &.product-description { 30 | justify-content: center; 31 | align-items: center; 32 | background-color: $purple; 33 | color: $white; 34 | text-align: center; 35 | border-radius: 5px 0 0 5px; 36 | } 37 | 38 | &.login-form { 39 | justify-content: center; 40 | align-items: flex-start; 41 | background-color: $white; 42 | border-radius: 0 5px 5px 0; 43 | } 44 | 45 | .logo { 46 | margin-bottom: 30px; 47 | } 48 | } 49 | 50 | .login-form { 51 | h1 { 52 | margin-bottom: 30px; 53 | color: $dark-gray; 54 | } 55 | } 56 | 57 | .logo-small { 58 | display: none; 59 | } 60 | 61 | 62 | /* Media Query for low-resolution Tablets and Mobiles */ 63 | @media (max-width: 768px) { 64 | .container { 65 | flex-direction: column; 66 | } 67 | 68 | .hidden-desktop { 69 | display: block; 70 | } 71 | 72 | .card { 73 | width: auto; 74 | height: auto; 75 | 76 | &.product-description { 77 | display: none; 78 | } 79 | 80 | &.login-form { 81 | background-color: transparent; 82 | box-shadow: none; 83 | 84 | .logo-small { 85 | display: block; 86 | } 87 | } 88 | } 89 | } 90 | 91 | -------------------------------------------------------------------------------- /web/step-06/src/ToDoApp/Pages/Login.razor.scss: -------------------------------------------------------------------------------- 1 | // Variables 2 | // 3 | // Color system 4 | $white: #fff !default; 5 | $purple: #6264A7 !default; 6 | $light-gray: #aaa !default; 7 | $dark-gray: #333 !default; 8 | 9 | .container { 10 | display: flex; 11 | flex-direction: row; 12 | justify-content: center; 13 | align-items: center; 14 | width: 100vw; 15 | height: 100vh; 16 | background-image: url("./images/background-image.png"); 17 | background-size: cover; 18 | background-repeat: no-repeat; 19 | } 20 | 21 | .card { 22 | display: flex; 23 | flex-direction: column; 24 | padding: 0 40px 100px 40px; 25 | box-shadow: 1px 2px 10px 0 $light-gray; 26 | width: 400px; 27 | height: 500px; 28 | 29 | &.product-description { 30 | justify-content: center; 31 | align-items: center; 32 | background-color: $purple; 33 | color: $white; 34 | text-align: center; 35 | border-radius: 5px 0 0 5px; 36 | } 37 | 38 | &.login-form { 39 | justify-content: center; 40 | align-items: flex-start; 41 | background-color: $white; 42 | border-radius: 0 5px 5px 0; 43 | } 44 | 45 | .logo { 46 | margin-bottom: 30px; 47 | } 48 | } 49 | 50 | .login-form { 51 | h1 { 52 | margin-bottom: 30px; 53 | color: $dark-gray; 54 | } 55 | } 56 | 57 | .logo-small { 58 | display: none; 59 | } 60 | 61 | .m-b-20 { 62 | margin-bottom: 20px; 63 | } 64 | 65 | 66 | /* Media Query for low-resolution Tablets and Mobile Devices*/ 67 | @media (max-width: 768px) { 68 | .container { 69 | flex-direction: column; 70 | } 71 | 72 | .hidden-desktop { 73 | display: block; 74 | } 75 | 76 | .card { 77 | width: auto; 78 | height: auto; 79 | 80 | &.product-description { 81 | display: none; 82 | } 83 | 84 | &.login-form { 85 | background-color: transparent; 86 | box-shadow: none; 87 | 88 | .logo-small { 89 | display: block; 90 | } 91 | } 92 | } 93 | } 94 | 95 | -------------------------------------------------------------------------------- /web/step-06/src/ToDoApp/Pages/TodoPage.razor: -------------------------------------------------------------------------------- 1 | @page "/todo"; 2 | 3 |
4 |
5 | Todos(@TodoList.Count(todo => !todo.IsDone)) 6 |
7 | 8 | 11 | 12 |
13 |
14 | 15 |
16 | 17 | @if (FilteredTodoList.Count > 0) 18 | { 19 | 20 | 21 |
22 |
23 | 24 | @if (TodoItem.IsEdit) 25 | { 26 | 27 | 28 | 29 | } 30 | else 31 | { 32 | 33 | @TodoItem.Title 34 | 35 | } 36 | 37 |
38 | 39 |
40 | 41 | 42 |
43 |
44 | 45 |
46 |
47 | } 48 |
49 | 56 |
57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /web/step-06/src/ToDoApp/Pages/TodoPage.razor.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace ToDoApp.Pages; 6 | 7 | public partial class TodoPage 8 | { 9 | private List TodoList = new(); 10 | private List FilteredTodoList = new(); 11 | private string TodoName { get; set; } 12 | public string NewName { get; set; } 13 | private string SearchTerm; 14 | private string FilterValue; 15 | 16 | public void AddTodo() 17 | { 18 | if (!string.IsNullOrWhiteSpace(TodoName)) 19 | { 20 | var newTask = new TodoItem() 21 | { 22 | Id = TodoList.Count() + 1, 23 | Title = TodoName, 24 | 25 | IsDone = false 26 | }; 27 | 28 | TodoList.Add(newTask); 29 | Filter(); 30 | TodoName = null; 31 | } 32 | } 33 | 34 | public void DeleteTodoItem(TodoItem todo) 35 | { 36 | TodoList.Remove(todo); 37 | Filter(); 38 | } 39 | 40 | public void EditTodoItem(TodoItem todo) 41 | { 42 | todo.IsEdit = true; 43 | } 44 | 45 | public void EditTodo(TodoItem todo) 46 | { 47 | if (!string.IsNullOrWhiteSpace(NewName)) 48 | { 49 | todo.IsEdit = false; 50 | todo.Title = NewName; 51 | NewName = null; 52 | } 53 | } 54 | 55 | public void CancelEditTodo(TodoItem todo) 56 | { 57 | todo.IsEdit = false; 58 | NewName = null; 59 | } 60 | 61 | private void HandleTodoChange(TodoItem todo) 62 | { 63 | todo.IsDone = !todo.IsDone; 64 | Filter(); 65 | } 66 | 67 | private void HandleClear() 68 | { 69 | HandleSearch(""); 70 | } 71 | 72 | private void HandleSearch(string searchTerm) 73 | { 74 | SearchTerm = searchTerm; 75 | Filter(); 76 | } 77 | 78 | private void HandleFilterChange(ChangeEventArgs e) 79 | { 80 | FilterValue = (string)e.Value; 81 | Filter(); 82 | } 83 | 84 | private void Filter() 85 | { 86 | FilteredTodoList = TodoList.Where(item => 87 | { 88 | var result = string.IsNullOrWhiteSpace(SearchTerm) || item.Title.ToLower().Contains(SearchTerm.ToLower()); 89 | if (result is false) return false; 90 | if (string.IsNullOrWhiteSpace(FilterValue) is false) 91 | { 92 | switch (FilterValue) 93 | { 94 | case "Active": 95 | return !item.IsDone; 96 | case "Completed": 97 | return item.IsDone; 98 | } 99 | 100 | } 101 | return true; 102 | }).ToList(); 103 | 104 | } 105 | } 106 | 107 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Aa][Rr][Mm]/ 27 | [Aa][Rr][Mm]64/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Ll]og/ 32 | [Ll]ogs/ 33 | 34 | # Visual Studio 2015/2017 cache/options directory 35 | .vs/ 36 | # Uncomment if you have tasks that create the project's static files in wwwroot 37 | #wwwroot/ 38 | 39 | # Visual Studio 2017 auto generated files 40 | Generated\ Files/ 41 | 42 | # MSTest test Results 43 | [Tt]est[Rr]esult*/ 44 | [Bb]uild[Ll]og.* 45 | 46 | # NUnit 47 | *.VisualState.xml 48 | TestResult.xml 49 | nunit-*.xml 50 | 51 | # Build Results of an ATL Project 52 | [Dd]ebugPS/ 53 | [Rr]eleasePS/ 54 | dlldata.c 55 | 56 | # Benchmark Results 57 | BenchmarkDotNet.Artifacts/ 58 | 59 | # .NET Core 60 | project.lock.json 61 | project.fragment.lock.json 62 | artifacts/ 63 | 64 | # StyleCop 65 | StyleCopReport.xml 66 | 67 | # Files built by Visual Studio 68 | *_i.c 69 | *_p.c 70 | *_h.h 71 | *.ilk 72 | *.meta 73 | *.obj 74 | *.iobj 75 | *.pch 76 | *.pdb 77 | *.ipdb 78 | *.pgc 79 | *.pgd 80 | *.rsp 81 | *.sbr 82 | *.tlb 83 | *.tli 84 | *.tlh 85 | *.tmp 86 | *.tmp_proj 87 | *_wpftmp.csproj 88 | *.log 89 | *.vspscc 90 | *.vssscc 91 | .builds 92 | *.pidb 93 | *.svclog 94 | *.scc 95 | 96 | # Chutzpah Test files 97 | _Chutzpah* 98 | 99 | # Visual C++ cache files 100 | ipch/ 101 | *.aps 102 | *.ncb 103 | *.opendb 104 | *.opensdf 105 | *.sdf 106 | *.cachefile 107 | *.VC.db 108 | *.VC.VC.opendb 109 | 110 | # Visual Studio profiler 111 | *.psess 112 | *.vsp 113 | *.vspx 114 | *.sap 115 | 116 | # Visual Studio Trace Files 117 | *.e2e 118 | 119 | # TFS 2012 Local Workspace 120 | $tf/ 121 | 122 | # Guidance Automation Toolkit 123 | *.gpState 124 | 125 | # ReSharper is a .NET coding add-in 126 | _ReSharper*/ 127 | *.[Rr]e[Ss]harper 128 | *.DotSettings.user 129 | 130 | # TeamCity is a build add-in 131 | _TeamCity* 132 | 133 | # DotCover is a Code Coverage Tool 134 | *.dotCover 135 | 136 | # AxoCover is a Code Coverage Tool 137 | .axoCover/* 138 | !.axoCover/settings.json 139 | 140 | # Visual Studio code coverage results 141 | *.coverage 142 | *.coveragexml 143 | 144 | # NCrunch 145 | _NCrunch_* 146 | .*crunch*.local.xml 147 | nCrunchTemp_* 148 | 149 | # MightyMoose 150 | *.mm.* 151 | AutoTest.Net/ 152 | 153 | # Web workbench (sass) 154 | .sass-cache/ 155 | 156 | # Installshield output folder 157 | [Ee]xpress/ 158 | 159 | # DocProject is a documentation generator add-in 160 | DocProject/buildhelp/ 161 | DocProject/Help/*.HxT 162 | DocProject/Help/*.HxC 163 | DocProject/Help/*.hhc 164 | DocProject/Help/*.hhk 165 | DocProject/Help/*.hhp 166 | DocProject/Help/Html2 167 | DocProject/Help/html 168 | 169 | # Click-Once directory 170 | publish/ 171 | 172 | # Publish Web Output 173 | *.[Pp]ublish.xml 174 | *.azurePubxml 175 | # Note: Comment the next line if you want to checkin your web deploy settings, 176 | # but database connection strings (with potential passwords) will be unencrypted 177 | *.pubxml 178 | *.publishproj 179 | 180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 181 | # checkin your Azure Web App publish settings, but sensitive information contained 182 | # in these scripts will be unencrypted 183 | PublishScripts/ 184 | 185 | # NuGet Packages 186 | *.nupkg 187 | # NuGet Symbol Packages 188 | *.snupkg 189 | # The packages folder can be ignored because of Package Restore 190 | **/[Pp]ackages/* 191 | # except build/, which is used as an MSBuild target. 192 | !**/[Pp]ackages/build/ 193 | # Uncomment if necessary however generally it will be regenerated when needed 194 | #!**/[Pp]ackages/repositories.config 195 | # NuGet v3's project.json files produces more ignorable files 196 | *.nuget.props 197 | *.nuget.targets 198 | 199 | # Microsoft Azure Build Output 200 | csx/ 201 | *.build.csdef 202 | 203 | # Microsoft Azure Emulator 204 | ecf/ 205 | rcf/ 206 | 207 | # Windows Store app package directories and files 208 | AppPackages/ 209 | BundleArtifacts/ 210 | Package.StoreAssociation.xml 211 | _pkginfo.txt 212 | *.appx 213 | *.appxbundle 214 | *.appxupload 215 | 216 | # Visual Studio cache files 217 | # files ending in .cache can be ignored 218 | *.[Cc]ache 219 | # but keep track of directories ending in .cache 220 | !?*.[Cc]ache/ 221 | 222 | # Others 223 | ClientBin/ 224 | ~$* 225 | *~ 226 | *.dbmdl 227 | *.dbproj.schemaview 228 | *.jfm 229 | *.pfx 230 | *.publishsettings 231 | orleans.codegen.cs 232 | 233 | # Including strong name files can present a security risk 234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 235 | #*.snk 236 | 237 | # Since there are multiple workflows, uncomment next line to ignore bower_components 238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 239 | #bower_components/ 240 | 241 | # RIA/Silverlight projects 242 | Generated_Code/ 243 | 244 | # Backup & report files from converting an old project file 245 | # to a newer Visual Studio version. Backup files are not needed, 246 | # because we have git ;-) 247 | _UpgradeReport_Files/ 248 | Backup*/ 249 | UpgradeLog*.XML 250 | UpgradeLog*.htm 251 | ServiceFabricBackup/ 252 | *.rptproj.bak 253 | 254 | # SQL Server files 255 | *.mdf 256 | *.ldf 257 | *.ndf 258 | 259 | # Business Intelligence projects 260 | *.rdl.data 261 | *.bim.layout 262 | *.bim_*.settings 263 | *.rptproj.rsuser 264 | *- [Bb]ackup.rdl 265 | *- [Bb]ackup ([0-9]).rdl 266 | *- [Bb]ackup ([0-9][0-9]).rdl 267 | 268 | # Microsoft Fakes 269 | FakesAssemblies/ 270 | 271 | # GhostDoc plugin setting file 272 | *.GhostDoc.xml 273 | 274 | # Node.js Tools for Visual Studio 275 | .ntvs_analysis.dat 276 | node_modules/ 277 | 278 | # Visual Studio 6 build log 279 | *.plg 280 | 281 | # Visual Studio 6 workspace options file 282 | *.opt 283 | 284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 285 | *.vbw 286 | 287 | # Visual Studio LightSwitch build output 288 | **/*.HTMLClient/GeneratedArtifacts 289 | **/*.DesktopClient/GeneratedArtifacts 290 | **/*.DesktopClient/ModelManifest.xml 291 | **/*.Server/GeneratedArtifacts 292 | **/*.Server/ModelManifest.xml 293 | _Pvt_Extensions 294 | 295 | # Paket dependency manager 296 | .paket/paket.exe 297 | paket-files/ 298 | 299 | # FAKE - F# Make 300 | .fake/ 301 | 302 | # CodeRush personal settings 303 | .cr/personal 304 | 305 | # Python Tools for Visual Studio (PTVS) 306 | __pycache__/ 307 | *.pyc 308 | 309 | # Cake - Uncomment if you are using it 310 | # tools/** 311 | # !tools/packages.config 312 | 313 | # Tabs Studio 314 | *.tss 315 | 316 | # Telerik's JustMock configuration file 317 | *.jmconfig 318 | 319 | # BizTalk build output 320 | *.btp.cs 321 | *.btm.cs 322 | *.odx.cs 323 | *.xsd.cs 324 | 325 | # OpenCover UI analysis results 326 | OpenCover/ 327 | 328 | # Azure Stream Analytics local run output 329 | ASALocalRun/ 330 | 331 | # MSBuild Binary and Structured Log 332 | *.binlog 333 | 334 | # NVidia Nsight GPU debugger configuration file 335 | *.nvuser 336 | 337 | # MFractors (Xamarin productivity tool) working folder 338 | .mfractor/ 339 | 340 | # Files built by macOS 341 | *.DS_Store 342 | 343 | # Local History for Visual Studio 344 | .localhistory/ 345 | 346 | # BeatPulse healthcheck temp database 347 | healthchecksdb 348 | 349 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 350 | MigrationBackup/ 351 | 352 | # Ionide (cross platform F# VS Code tools) working folder 353 | .ionide/ 354 | 355 | /web/step-04/src/SimpleBlazorCalculator/Pages/Calculator.razor.css 356 | /web/step-05/src/ToDoApp/Pages/Login.razor.css 357 | /web/step-06/src/ToDoApp/Pages/Login.razor.css 358 | /web/step-06/src/ToDoApp/Pages/TodoPage.razor.css 359 | -------------------------------------------------------------------------------- /web/step-03/README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | ## جلسه سوم- پیاده سازی شمارنده با استفاده از Blazor 4 | 5 | در این جلسه قصد داریم شمارنده جلسه پیش را با استفاده از Blazor پیاده سازی کنیم. Blazor به شما اجازه می‌دهد که با همان HTML/CSS کار کنید، ولی به جای JavaScript، از #C استفاده کنید. 6 | 7 | ## لیست موضوعات 8 | 1. [آماده سازی پروژه Blazor در Visual Studio](#CreateProject) 9 | 2. [افزودن کد های HTML](#AddHtml) 10 | 3. [افزودن کد های CSS](#AddCSS) 11 | 4. [افزودن رفتار تعاملی با استفاده از #C](#AddCSharp) 12 | 13 | ## آماده سازی پروژه Blazor در Visual Studio 14 | وارد محیط Visual Studio شده و از قسمت Get started روی گزینه Create a new project به منظور ایجاد پروژه جدید کلیک می‌کنیم. 15 | 16 | 17 | 18 | 19 | در مرحله بعد مطابق تصویر زیر از لیست ارایه شده ‌Blazor WebAssembly App را انتخاب و با کلیک بر روی دکمه Next به مرحله بعدی می‌رویم. 20 | 21 | 22 | 23 | همان طور که در تصویر زیر می‌بینید در قسمت Project name می‌توانیم اسم پروژه را تعیین کنیم (در این قسمت اسم پروژه را SimpleBlazorCounter می‌گذاریم) و در قسمت Location محل قرار گیری پروژه را مشخص کنیم. مجددا برای رفتن به مرحله بعد بر روی دکمه Next کلیک می‌کنیم. 24 | 25 | 26 | 27 | مطابق تصویر زیر، در این مرحله دقت کنید که Framework حتما روی **NET 7.0** قرار گرفته باشد. 28 | 29 | 30 | 31 | با کلیک بر روی دکمه Create پروژه SimpleBlazorCounter ایجاد می‌شود. 32 | 33 | همان طور که می بینید در سمت راست محیط Visual Studio ما بخش Solution Explorer را داریم. با توجه با این نکته که ما Blazor WebAssembly App را انتخاب کردیم ساختار فایلی که اینجا می‌بینیم مربوط به ساختار پیش فرض Blazor می‌باشد. در ابتدا ما توضیحی کوتاه و مختصر در مورد این ساختار داده و سپس بنا بر نیاز تمرین فعلی این ساختار را تغییر می‌دهیم. 34 | 35 | 36 | 37 | 38 | همانطور که در تصویر بالا می‌بینید ما داخل پروژه SimpleBlazorCounter به ترتیب پوشه‌های زیر را داریم. 39 | 40 | پوشه Properties، داخل این پوشه یک فایل به نام launchSettings.json قرار گرفته که تنظیمات مربوط به راه اندازی برنامه در آن است، برای مثال در برنامه ای که چندین صفحه دارد، کدام صفحه پیش‌فرض باز شود. 41 | 42 | 43 | پوشه wwwroot، در این پوشه فایل های استاتیک مانند فایل های css ,font ,image و… قرار دارند. 44 | 45 | پوشه Pages، در این پوشه صفحات پروژه با پسوند razor قرار گرفته‌اند. فایل razor، شبیه به فایل html قسمت قبل است، با این تفاوت که امکاناتی از #C به آن اضافه شده است. 46 | 47 | پوشه Shared، با این پوشه، فعلا کاری نداریم، بنابراین روی آن راست کلیک کرده و با گزینه Delete آن را حذف می‌کنیم. در آینده به طور کامل در مورد این پوشه توضیح می‌دهیم. 48 | 49 | توضیحات در مورد ساختار فایل‌های پروژه برای این جلسه کافی است. همان طور که گفتیم طبق نیاز تمرین این جلسه ساختار فعلی را تغییر می دهیم. 50 | 51 | هر زمان خواستید برنامه را اجرا کنید از ترکیب Ctrl + F5 استفاده کنید 52 | 53 | سپس از پوشه wwwroot پوشه های css ،sample-data و فایل favicon.ico رو حذف کرده به این صورت که روی نام فایل کلیک راست و از منوی باز شده گزینه Delete را انتخاب می‌کنیم. 54 | 55 | در ادامه کدهای مربوط به فایل index.html رو به صورت زیر تغییر می‌دهیم. 56 | 57 | 58 |
59 | 60 | ```html 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | SimpleBlazorCounter 69 | 70 | 71 | 72 | 73 |
Loading...
74 | 75 | 76 | 77 | ``` 78 |
79 | 80 | از پوشه pages هم فایل های FetchData.razor و Index.razor را هم حذف می‌کنیم. 81 | و همین طور پوشه Shared را هم حذف نمایید. 82 | در ادامه در فایل Imports.razor_ ، به دلیل اینکه پوشه Shared را از قبل حذف کرده ایم، خط زیر را هم حذف می‌کنیم. 83 | 84 |
85 | 86 | ```razor 87 | @using SimpleBlazorCounter.Shared 88 | ``` 89 |
90 | 91 | کدهای داخل فایل App.razor را هم به کد زیر تغییر می دهیم. 92 | 93 |
94 | 95 | ```razor 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 |

Sorry, there's nothing at this address.

104 |
105 |
106 |
107 | ``` 108 |
109 | 110 | 111 | برای مطمئن شدن از درستی تغییر ساختار با استفاده " ctrl+f5 " پروژه را اجرا می‌کنیم. 112 | به دلیل این که از قبل و به صورت پیش فرض ما یک سری کد داخل فایل Counter.razor داشتیم خروجی که در مرورگر می‌بینیم مطابق تصویر زیر است. 113 | 114 | 115 | 116 | 117 | 118 | ## افزودن کد های HTML 119 | برای ایجاد شمارنده‌‌ای مطابق جلسه پیش، اما این بار با استفاده از ‌Blazor به این صورت عمل می‌کنیم. 120 | 121 | ابتدا از پوشه Pages فایل Counter.razor را انتخاب می‌کنیم. ساختار کلی Blazor به این صورت است که کدهای Blazor‌ با @ مشخص می‌شوند. و بلاک های کد با code@ نوشته می‌شوند. 122 | 123 | کدهای HTML جلسه گذشته را به صورت زیر، جایگزین کدهای HTML این فایل می‌کنیم. 124 | 125 | 126 |
127 | 128 | ```razor 129 | 130 | @page "/" 131 | 132 |
133 |
134 | 0 135 |
136 |
137 | 140 | 143 |
144 |
145 | 146 | @code { 147 | private int currentCount = 0; 148 | 149 | private void IncrementCount() 150 | { 151 | currentCount++; 152 | } 153 | } 154 | 155 | ``` 156 |
157 | 158 | دقت کنید که خط اول این فایل که مسیر این فایل را در مرورگر مشخص می کند همچنان بدون تغییر باقی می‌ماند. 159 | 160 | 161 | 162 | 163 | ## افزودن کد های CSS 164 | 165 | برای افزودن کدهای css، روی پوشه pages کلیک راست کرده، گزینه Add را انتخاب و سپس گزینه NewItem را انتخاب می‌نماییم. 166 | 167 | 168 | 169 | مطابق تصویر زیر از لیست ارائه شده Style Sheet را انتخاب و در قسمت Name نامی مشابه نام Component که در اینجا Counter.razor نام دارد با پسوند css. انتخاب می کنیم. 170 | اسم فایل Counter.razor.css می‌باشد. سپس روی دکمه Add کلیک نمایید. 171 | 172 | 173 | 174 | فایل Counter.razor.css مشابه تصویر زیر به پروژه اضافه می‌شود. 175 | 176 | 177 | 178 | کدهای CSS را از فایل CSS جلسه گذشته کپی و در فایل Counter.razor.css وارد می‌کنیم. 179 | 180 | 181 |
182 | 183 | ```css 184 | .card { 185 | padding: 20px; 186 | margin: 200px auto; 187 | width: 400px; 188 | height: 400px; 189 | background-color: lightskyblue; 190 | box-shadow: 1px 2px 10px 0 #808080; 191 | } 192 | 193 | .counter { 194 | margin: 50px auto; 195 | width: 150px; 196 | height: 150px; 197 | line-height: 150px; 198 | background-color: #eee; 199 | border-radius: 50%; 200 | border: 10px solid #2196f3; 201 | text-align: center; 202 | font-size: 60px; 203 | font-weight: bold; 204 | color: #1077c2; 205 | } 206 | 207 | .action { 208 | text-align: center; 209 | } 210 | 211 | button { 212 | padding: 20px 30px; 213 | margin: 5px; 214 | font-size: 24px; 215 | font-weight: bold; 216 | border-radius: 5px; 217 | cursor: pointer; 218 | color: white; 219 | } 220 | 221 | .increase { 222 | background-color: #18cd73; 223 | border: 3px solid #19a35d; 224 | } 225 | 226 | .decrease { 227 | background-color: #ef7694; 228 | border: 3px solid #b74b66; 229 | } 230 | 231 | ``` 232 |
233 | 234 | خروجی تغییرات در مرورگر مشابه تصویر زیر می‌باشد. 235 | 236 | 237 | 238 | 239 | 240 | ## افزودن رفتار تعاملی با استفاده از #C 241 | 242 | در ادامه به جای افزودن کدهای Javascript برای تعیین عملکرد دکمه‌ها، از کدهای #C استفاده می‌کنیم. 243 | کدهای #C را می‌توانیم داخل بلاک code@، فایل Counter.razor بنویسیم. اما بهتر است بلاک code@ را از این فایل حذف کرده و یک فایل جدید به نام Counter.razor.cs از طریق Add > New Item < راست کلیک روی پوشه Pages، ایجاد و کدهای زیر را وارد این فایل کنیم. 244 | 245 |
246 | 247 | ```c# 248 | namespace SimpleBlazorCounter.Pages; 249 | 250 | public partial class Counter 251 | { 252 | public int CurrentCount { get; set; } 253 | 254 | public void Increase() 255 | { 256 | CurrentCount++; 257 | } 258 | 259 | public void Decrease() 260 | { 261 | CurrentCount--; 262 | } 263 | } 264 | 265 | ``` 266 |
267 | 268 | همان طور که می بینید در کلاسی به نام Counter یک پارامتر به نام CurrentCount از نوع int که نوع عددی می باشد را تعیین کرده‌ایم. همچنین دو متد به نام‌های Increase و Decrease که همانند جلسه گذشته، زمانی که فراخوانی می‌شوند، یک واحد به عدد ما افزوده و یا کم می‌کنند. 269 | 270 | سپس مجددا وارد فایل Counter.razor شده و نحوه فراخوانی متدها را تغییر می‌دهیم چرا که باید از سینتکس ‌‌Blazor که با @ مشخص میشوند استفاده کنیم. 271 | 272 | همچنین به جای عدد صفر از نام متغیر CurrntCount استفاده می کنیم تا هر تغییر که روی عدد جاری اعمال شد مستقیما در این قسمت نمایش داده شود. 273 | 274 | نحوه فراخوانی هر تابع روی رویداد onclick دکمه مربوطه و استفاده از نام متغیر به جای عدد مطابق کد زیر تغییر می‌کند. 275 | 276 |
277 | 278 | ```html 279 |
280 |
281 | @CurrentCount 282 |
283 |
284 | 287 | 290 |
291 |
292 | 293 | ``` 294 |
295 | 296 | نتیجه نهایی تغییرات به صورت زیر می‌باشد. 297 | 298 | 299 |
300 | -------------------------------------------------------------------------------- /web/step-04/README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | ## جلسه چهارم - پیاده سازی ماشین حساب در Blazor 4 | 5 | در این جلسه قصد داریم یک ماشین حساب ساده مطابق تصویر زیر با استفاده از Blazor پیاده سازی کنیم. 6 | 7 | 8 | 9 | ## لیست موضوعات 10 | 1. [ایجاد پروژه در Visual Studio](#CreateProject) 11 | 2. [ایجاد ساختار اصلی](#CreateMainStructure) 12 | 3. [افزودن استایل بااستفاده از SCSS](#AddSCSS) 13 | 4. [افزودن رفتار تعاملی با استفاده از #C](#AddFunctionality) 14 | 15 | ## ایجاد پروژه در Visual Studio 16 | همانند جلسه گذشته پروژه جدیدی به نام SimpleBlazorCalculator ایجاد کرده و مجددا فایل‌ها و پوشه‌های اضافی را مطابق جلسه گذشته حذف کنید. با دو تفاوت: 17 | همه صفحات موجود در پوشه Pages را پاک کنید. 18 | اگر از کدهای جلسه قبل استفاده می‌کنید، SimpleBlazorCounter را در هر جایی که کد را کپی کردید، با SimpleBlazorCalculator جایگزین کنید. 19 | 20 | 21 | داخل پوشه Pages راست کلیک نموده و از منوی Add، گزینه New Item و نام آن را 22 | 23 | `Calculator.razor, Calculator.razor.cs, Calculator.razor.scss` 24 | 25 | بگذارید، با این کار در یک حرکت هر سه فایل لازم برای کار شما به پروژه اضافه میشود. 26 | 27 | 28 | وارد فایل Calculator.razor شده و در ابتدای این فایل کد زیر را برای تعیین مسیر این صفحه در مرورگر وارد کنید و در launchSettings.json نیز مقدار launchUrl را برابر با calculator قرار دهید. 29 | 30 |
31 | 32 | ```razor 33 | 34 | @page "/calculator" 35 | 36 | ``` 37 |
38 | 39 | 40 | 41 | 42 | ## ایجاد ساختار اصلی 43 | برای ایجاد ساختار اولیه ماشین حساب کد زیر را در فایل Calculator.razor وارد کنید. 44 | 45 |
46 | 47 | ```html 48 | 49 | 50 |
51 |
52 |
53 | 54 |
55 |
56 | 57 |
58 |
59 | 60 | 61 | 62 | 63 |
64 |
65 | 66 |
67 |
68 |
69 | 70 | 71 | 72 | ``` 73 |
74 | 75 | از تگ‌های Input برای گرفتن دیتا از کاربر در صفحات وب استفاده می‌شود. به دلیل گستردگی در انواع داده‌ها، انواع متفاوتی از تگ‌های input وجود دارد که نوع آنها با استفاده ویژگی type این تگ، مانند موارد زیر مشخص می‌شود. 76 | 77 |
78 | 79 | ```html 80 | 81 | 82 | 83 | 84 | (default value) 85 | 86 | 87 | ``` 88 |
89 | 90 | ویژگی placeholder در input می‌تواند شامل یک متن خیلی کوتاه باشد که اشاره به مقدار مورد انتظار یک المنت دارد. 91 | 92 | در این تمرین همانطور که مشاهده می‌کنید ازدو تگ input برای گرفتن دو عدد از کاربر استفاده می‌کنیم. 93 | و همچنین از یک تگ input با ویژگی readonly به معنای این که محتوای این تگ قابل تغییر نیست و فقط قابل خواندن است، برای نتیجه نهایی استفاده می‌کنیم. 94 | 95 | 96 | 97 | 98 | ## افزودن استایل بااستفاده از SCSS 99 | از این جلسه به بعد برای افزودن استایل‌ها از SCSS به جای CSS استفاده می‌کنیم. SCSS علاوه بر داشتن تمامی ویژگی های موجود در CSS شامل تعدادی ویژگی‌ قدرتمندتر نیز نسبت به CSS می‌باشد، که با استفاده از این ویژگی‌ها نوشتن استایل‌ها آسان‌تر، سریعتر و خواناتر می‌شود. 100 | 101 | سعی ما بر این است که به مرور زمان از این جلسه تا جلسه آخر این دوره، به توضیح کامل این ویژگی‌ها با مثال‌های متعدد بپردازیم. 102 | 103 | زمانی که از فایل‌ها با پسوند SCSS استفاده می‌کنیم باید آنها را به فایل با پسوند CSS تبدیل کنیم تا برای مرورگر قابل فهم باشد. در واقع زمان استفاده از SCSS احتیاج به یک کامپایلر داریم تا فایل‌های SCSS پروژه را به CSS کامپایل کند. برای انجام این کار، ابزار و روش‌های متفاوتی وجود دارد که در ادامه به بررسی یکی از این ابزارها می‌پردازیم. 104 | 105 | در ابتدا مطابق تصویر زیر از بخش Solution Explorer بر روی Dependencies کلیک راست کرده و از منوی باز شده گزینه Manage NuGet Packages را انتخاب می‌کنیم. 106 | 107 | 108 | 109 | در فایل باز شده تب ‌‌Browse را انتخاب و در قسمت سرچ باکس `DartSassBuilder` را جستجو می‌کنیم. 110 | 111 | 112 | 113 | و سپس از لیست ارائه شده `DartSassBuilder` را انتخاب و از سمت راست بر روی دکمه Install کلیک می‌کنیم. 114 | 115 | 116 | در نهایت بر روی دکمه Ok کلیک می‌کنیم تا این Package نصب شود. 117 | 118 | 119 | با هر بار اجرای برنامه، فایل Calculator.razor.css از روی Calculator.razor.css ساخته میشود. 120 | 121 | 122 | 123 | 124 | 125 | هدف ما از بخش استایل‌ها در این جلسه، آشنایی شما با ویژگی Variable در SCSS می‌باشد. 126 | 127 | فرض نمایید در فایل مربوط به استایل‌ها ما چندین بار از رنگ آبی استفاده کردیم، بعد از مدتی تصمیم میگیریم این رنگ آبی، به عنوان مثال به رنگ آبی روشن تر تغییر کند. اگر از Variableها استفاده نکنیم مجبوریم تمامی رنگ‌های آبی موجود در فایل‌ها را به صورت دستی تغییر دهیم اما با استفاده از Variable ها ما تنها مقدار متغیر مربوط به رنگ آبی را تغییر می‌دهیم، بدین ترتیب تمامی پراپرتی‌ها که متغیر مربوط به رنگ آبی را به عنوان مقدار داشته‌اند، رنگ جدیدی را که به متغیر نسبت داده ایم به خود می‌گیرند. 128 | 129 | به منظور تعریف متغیرها در SCSS از کاراکتر $ قبل از نام متغیر استفاده می‌کنیم. بدیهی است که نام متغیر می‌تواند هر آنچه که شما می‌خواهید باشد. 130 | 131 | کد زیر را copy و در فایل paste ،Calculator.razor.scss نمایید. 132 | 133 | 134 |
135 | 136 | ```scss 137 | 138 | // Variables 139 | // 140 | // Color system 141 | $light-blue: cornflowerblue !default; 142 | $light-gray: #dcdcdc !default; 143 | 144 | // Font 145 | $font-family: "Segoe UI", Roboto, Arial, sans-serif !default; 146 | 147 | .card { 148 | margin: 120px auto; 149 | padding: 20px; 150 | width: 400px; 151 | height: 400px; 152 | background-color: $light-blue; 153 | border-radius: 5px; 154 | box-shadow: 1px 2px 10px 0 $light-gray; 155 | } 156 | 157 | .field { 158 | margin: 15px 159 | } 160 | 161 | input { 162 | padding: 10px; 163 | width: 350px; 164 | height: 30px; 165 | color: $light-blue; 166 | border: 2px solid $light-gray; 167 | text-align: center; 168 | font-family: $font-family; 169 | font-size: 30px; 170 | } 171 | 172 | .action { 173 | margin: 30px 0; 174 | text-align: center; 175 | } 176 | 177 | .result { 178 | margin-top: 75px; 179 | } 180 | 181 | .btn { 182 | margin: 5px; 183 | width: 80px; 184 | height: 80px; 185 | border-radius: 5px; 186 | background-color: $light-gray; 187 | line-height: 80px; 188 | color: $light-blue; 189 | font-size: 35px; 190 | cursor: pointer; 191 | } 192 | 193 | ``` 194 |
195 | 196 | خروجی تمرین، تا این مرحله به صورت تصویر زیر می باشد. 197 | 198 | 199 | 200 | 201 | 202 | ## افزودن رفتار تعاملی با استفاده از #C 203 | سپس فایل جدیدی به نام Calculator.razor.cs به منظور نوشتن متدها، برای چهار عمل اصلی (جمع، تفریق، ضرب، تقسیم) ایجاد می‌کنیم و کدهای زیر را وارد این فایل می‌کنیم. 204 | 205 |
206 | 207 | ```c# 208 | 209 | namespace SimpleBlazorCalculator.Pages; 210 | 211 | public partial class Calculator 212 | { 213 | public decimal Num1 { get; set; } 214 | 215 | public decimal Num2 { get; set; } 216 | 217 | public string FinalResult { get; set; } 218 | 219 | 220 | public void AddNumbers() 221 | { 222 | FinalResult = (Num1 + Num2).ToString("0.##"); 223 | } 224 | 225 | public void SubtractNumbers() 226 | { 227 | FinalResult = (Num1 - Num2).ToString("0.##"); 228 | } 229 | 230 | public void MultiplyNumbers() 231 | { 232 | FinalResult = (Num1 * Num2).ToString("0.##"); 233 | } 234 | 235 | public void DivideNumbers() 236 | { 237 | if (Num2 != 0) 238 | { 239 | FinalResult = (Num1 / Num2).ToString("0.##"); 240 | } 241 | else 242 | { 243 | FinalResult = "Cannot Divide by Zero"; 244 | } 245 | } 246 | } 247 | 248 | 249 | 250 | ``` 251 |
252 | 253 | همانطور که در کد بالا می‌بینید داخل کلاس Calculator، از دو پارامتر از نوع decimal و یک پارامتر از نوع string استفاده کرده ایم که پارامترهای Num1 و Num2 قرار است دو عددی که از کاربر دریافت می‌کنیم را داخل خود نگه دارند و پارامتر FinalResult نتیجه نهایی را در خود نگه می‌دارد. 254 | 255 | نوع decimal و یا ده‌دهی زمانی مناسب است که درجه دقت مورد نیاز، توسط تعداد ارقام سمت راست نقطه اعشاری تعیین شود. این اعداد معمولاً در برنامه های مالی ، برای مبالغ ارزی (به عنوان مثال ، 1.00 دلار) ، نرخ بهره (به عنوان مثال ، 2.625٪) و غیره استفاده می شود. 256 | 257 | چهار متد هم به نام‌های `AddNumbers`، `SubtractNumbers`، `MultiplyNumbers`، `DivideNumbers` برای چهار عمل اصلی داریم. 258 | 259 | در داخل هر متد بعد از اعمال عملگر بر روی دو عدد، از متد ()ToStrin برای تبدیل نتیجه که به صورت decimal می‌باشد به string استفاده می کنیم و در نهایت داخل پارامتر FinalResult قرار می‌دهیم. 260 | 261 | 262 | برای نمایش اعشار تا دو رقم از ToString("0.##") استفاده می‌کنیم. 263 | 264 | در متد DivideNumbers از یک شرط استفاده می کنیم چرا که در تقسیم اعداد، تقسیم یک عدد بر صفر تعریف نشده است. به همین منظور می‌توانیم با قرار دادن یک شرط، به این صورت که اگر عدد دوم مخالف صفر بود عمل تقسیم انجام و در غیر این صورت پیغام مناسب را به کاربر نمایش دهد، خروجی درست را به کاربر نمایش دهیم. 265 | 266 | الگوی کلی دستورات شرطی if به صورت زیر ‌می‌باشد. 267 | 268 | 269 | 270 | به منظور استفاده از ساختار شرطی از کلمه کلیدی if استفاده می‌کنیم. در داخل پرانتز شرط مورد نظر را نوشته، در صورت صحت شرط تعیین شده، مجموعه‌ای از دستورات در بلاک کد اول، اجرا و در غیر این صورت بعد از کلمه کلیدی else مجموعه دستورات در بلاک کد دوم، اجرا می‌شوند. 271 | 272 | در مرحله بعد باید هر متد را بر روی رویداد onclick دکمه مربوطه فراخوانی کنیم. 273 | بدین ترتیب مجددا وارد فایل Calculator.razor شده و تغییرات زیر را بر روی کد اعمال می‌کنیم. 274 | 275 |
276 | 277 | ```html 278 | 279 |
280 |
281 |
282 | 283 |
284 | 285 |
286 | 287 |
288 | 289 |
290 | 291 | 292 | 293 | 294 |
295 | 296 |
297 | 298 |
299 |
300 |
301 | 302 | ``` 303 |
304 | 305 | با استفاده از ویژگی bind@ در ‌Blazor می‌توانیم مقادیر متغیرهای (Num1, Num2, FinalResult) مربوط به هر input را به آنها نسبت دهیم. 306 | 307 | در واقع ما یک اتصال داده بین متغیرها در کلاس calculator و این input ها ایجاد می‌کنیم. بدین ترتیب زمانی که کاربر اعداد را وارد می‌کند، مقادیر داخل متغیرهای Num1 و Num2 نشسته و در مقابل مقدار متغیر FinalResult را به input سوم نسبت می‌دهیم و با هر تغییری در این متغیر مقدار input بروزرسانی می‌شود. 308 | 309 | برای اعمال ویژگی bind@ کد را به صورت زیر تغییر دهید. 310 | 311 |
312 | 313 | ```html 314 | 315 |
316 |
317 |
318 | 319 |
320 | 321 |
322 | 323 |
324 | 325 |
326 | 327 | 328 | 329 | 330 |
331 | 332 |
333 | 334 |
335 |
336 |
337 | 338 | ``` 339 |
340 | 341 | خروجی نهایی به صورت زیر می‌باشد. 342 | 343 | 344 | 345 |
346 | 347 | بعد از هر تغییر در کد میتوانید با Ctrl + F5 تاثیر آن را ببینید. -------------------------------------------------------------------------------- /web/step-02/README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | ## جلسه دوم- پیاده سازی شمارنده با استفاده از HTML, CSS, JavaScript 4 | 5 | در این جلسه قصد داریم شمارنده ای مشابه تصویر زیر پیاده سازی کنیم. هدف ما در این جلسه این است که با انجام یک تمرین ساده شما را با کاربرد HTML, CSS, JavaScript در صفحات وب آشنا کنیم. 6 | 7 | 8 | 9 | ## لیست موضوعات 10 | 1. [ایجاد پروژه در Visual Studio](#CreateProject) 11 | 2. [ایجاد ساختار اصلی با HTML](#CreateMainStructure) 12 | 3. [افزودن استایل با استفاده از CSS](#AddStyle) 13 | 4. [افزودن رفتار تعاملی با استفاده از JavaScript](#AddFunctionality) 14 | 15 | 16 | ## ایجاد پروژه در Visual Studio 17 | 18 | در ابتدا پوشه ای (یا همان فولدر) به نام SimpleCounter در مسیر دلخواه ایجاد می‌کنیم. از همین ابتدا به اسامی دقت کنید، بزرگ و کوچکی حروف، داشتن یا نداشتن Space و... تاثیرگذار است. 19 | 20 | سپس وارد Visual Studio شده و از قسمت Get Started مطابق تصویر زیر روی گزینه Open a local folder کلیک کرده و فولدر مورد نظر را انتخاب می کنیم. 21 | 22 | 23 | 24 | 25 | مطابق تصویر زیر بعد از وارد شدن به محیط اصلی Visual Studio در قسمت Solution Explorer روی اسم فولدر کلیک راست و روی گزینه Add کلیک کرده و در ادامه گزینه NewFile را انتخاب می‌کنیم تا یک فایل جدید ایجاد شود سپس نام فایل را به counter.html تغییر می دهیم. 26 | 27 | 28 | 29 | ## ایجاد ساختار اصلی با HTML 30 | 31 | فایل های HTML معمولا با پسوند html و یا htm مشخص می شوند. با استفاده از کدهای HTML ساختار اصلی صفحات وب را ایجاد می‌کنیم. صفحات HTML شامل انواع element ها یا عناصر هستند به عنوان مثال یک صفحه HTML می تواند شامل element های text, image, table و…. باشد. 32 | هر عنصر HTML توسط یک تگ شروع و یک تگ پایان به صورت زیر تعریف می شود. 33 | 34 |
35 | 36 | ```html 37 | 38 | Element content 39 | 40 | ``` 41 |
42 | 43 | با استفاده از کدهای HTML زیر، ساختار شمارنده را ایجاد می‌کنیم. در ادامه به توضیح کوتاهی از هر کدام از تگ های زیر می پردازیم. 44 | 45 |
46 | 47 | ```html 48 | 49 | 50 | 51 | 52 | Counter 53 | 54 | 55 | 56 | 57 |
58 |
59 | 0 60 |
61 |
62 | 63 | 64 |
65 |
66 | 67 | 68 | 69 | ``` 70 |
71 | 72 | تگ html عنصر اصلی یک صفحه HTML است و مشخص کننده شروع و پایان یک فایل HTML می باشد. 73 | 74 | تگ head می تواند شامل تگ title برای مشخص کردن عنوان صفحه، یا تگ های link و یا تگ‌های meta و … باشد. 75 | 76 | تگ body تمامی مواردی که جنبه نمایش دارند و قرار است به کاربر نشان داده شوند داخل این تگ تعریف می‌شوند. 77 | 78 | تگ div (مخفف division) برای بخش بندی قسمت‌های مختلف صفحه و یا گروه بندی عناصر استفاده می‌شود. 79 | 80 | تگ span معمولا برای مشخص کردن قسمتی از متن استفاده می‌شود. 81 | 82 | تگ button برای ایجاد دکمه ای که قابلیت کلیک شدن داشته باشه استفاده می‌شود. 83 | 84 | به منظور دیدن خروجی کدها با استفاده از Ctrl+S تغییرات را ذخیره و در پوشه محل قرارگیری فایل html، روی آن دو بار کلیک کنید تا در مرورگر باز شود. 85 | 86 | از این پس هر تغییری که در فایل ها ایجاد کردید ابتدا تغییرات را ذخیره و سپس برا دیدن نتیجه تغییرات، صفحه مرورگر را refresh نمایید. 87 | 88 | 89 | ## افزودن استایل با استفاده از CSS 90 | فایل‌های CSS با پسوند CSS مشخص می‌شوند. با استفاده از کدهای CSS، نحوه نمایش عناصر HTML را بر روی صفحه تعیین می‌کنیم. 91 | برای افزودن کدهای CSS به این تمرین ابتدا فایل جدیدی به نام counter.css ایجاد می‌کنیم. 92 | 93 | و سپس فایل counter.css رو با استفاده از تگ link داخل تگ head و با استفاده از ویژگی href این تگ، به فایل counter.html اضافه کنیم. 94 | 95 | 96 |
97 | 98 | ```html 99 | 100 | 101 | 102 | Counter 103 | 104 | 105 | 106 | 107 | ``` 108 | 109 |
110 | 111 | الگوی کلی دستورات CSS به صورت زیر می‌باشد. 112 | 113 | 114 | 115 | 116 | همانطور که مشاهده می‌کنید دستورات CSS از سه بخش Selector، Property، Property value تشکیل شده است. 117 | 118 | Selector: به منظور اضافه نمودن استایل به یک element باید آن element را انتخاب کنیم. یکی از روش های انتخاب element در CSS، استفاده از نام خود element می‌باشد. برای مثال در تصویر بالا ما از نام تگ button به عنوان سلکتور استفاده کرده‌ایم، به این معنا که می‌خواهیم استایلی را برای تمامی button های موجود در صفحه تعیین کنیم. 119 | 120 | Property: با استفاده از Property ها، ویژگی‌های نمایش یک element در HTML را تعیین می‌کنیم. به عنوان مثال اگر بنویسیم background-color: red به این معناست که رنگ پس زمینه تمامی button های موجود در صفحه قرمز باشد. 121 | 122 | Property value: برای هر Property یا ویژگی در CSS باید یک مقدار در نظر گرفته شود. به عنوان مثال در این تصویر، رنگ قرمز را برای پراپرتی background-color element یا تگ button در نظر گرفتیم. 123 | 124 | برای نوشتن کدهای CSS وارد فایل counter.css می‌شویم. 125 | 126 | در ابتدا برای تعیین رنگ پس زمینه صفحه به صورت زیر عمل می‌کنیم. 127 | 128 |
129 | 130 | ```css 131 | 132 | body { 133 | background-color: #fafafa; 134 | } 135 | 136 | ``` 137 | 138 |
139 | 140 | در ادامه برای افزودن استایل به element‌های دیگر می خواهیم از روش انتخاب element با استفاده از ویژگی class هر element استفاده کنیم. بدین ترتیب به فایل counter.html رفته و برای هر کدام از element ها، ویژگی class را با مقدار دلخواه تعیین می کنیم. 141 | 142 | 143 |
144 | 145 | ```html 146 | 147 | 148 |
149 |
150 | 0 151 |
152 |
153 | 154 | 155 |
156 |
157 | 158 | 159 | ``` 160 | 161 |
162 | 163 | اگر ما همچنان از نام خود element برای انتخاب element استفاده می‌کردیم، پراپرتی‌هایی که به آن element اختصاص می‌دادیم تمامی element‌ها با آن نام را در صفحه تحت تاثیر قرار می‌داد. به عنوان مثال در این تمرین ما دو دکمه داریم که یکسری از ویژگی‌‌های ظاهری بین آنها مشترک هستند مانند اندازه دکمه‌ها که ما می توانیم همچنان از نام خود element (button) برای انتخاب element استفاده کنیم اما یک سری از ویژگی‌ها مانند رنگ پس‌زمینه هر دکمه با دکمه بعدی متفاوت می‌باشد که در اینجا باید از یک Selector یا انتخاب‌گر دیگر مانند class برای انتخاب element مورد نظر استفاده کنیم. 164 | 165 | برای انتخاب element با استفاده از نام class از " . " قبل از نوشتن نام selector استفاده می‌کنیم. 166 | 167 |
168 | 169 | ```css 170 | 171 | .card { 172 | Padding: 20px; /* .برای تعیین فاصله المنت از محتوای درون المنت استفاده می‌شود */ 173 | Margin: 200px auto; /* .برای تعیین فاصله المنت از محتوای بیرون از المنت استفاده می‌شود */ 174 | Width: 400px; /* .برای تعیین عرض المنت استفاده می‌شود */ 175 | Height: 400px; /* .برای تعین ارتفاع المنت استفاده می‌شود */ 176 | Background-color: lightskyblue; /* .برای تعیین رنگ پس زمینه المنت استفاده می‌شود */ 177 | Box-shadow: 1px 2px 10px 0 #808080; /* .برای افزودن سایه به المنت مورد نظر استفاده می‌شود */ 178 | Border-radius: 5px; /* .باافزودن شعاع به گوشه‌های یک المنت که در نهایت موجب خمیدگی آن گوشه می‌شود */ 179 | } 180 | 181 | 182 | ``` 183 | 184 |
185 | 186 | به علت کامنت‌های فارسی داخل این CSS Style، ممکن است موقع ذخیره سازی، با دیالوگ زیر مواجه شوید که گزینه Yes را انتخاب کنید. 187 | 188 | 189 | 190 | هر کدام از پراپرتی‌های padding و margin حداکثر چهار عدد را می‌توانند به عنوان مقدار بگیرند که این چهار عدد به ترتیب نشان دهنده فاصله از بالا، راست، پایین و چپ می‌باشند. اگر فقط یک مقدار به هر کدام از این property ها اختصاص دهیم به این معنی است که هر چهار طرف فاصله‌ای یکسان را نسبت به محتوای بیرون از element و یا محتوای درون element را دارند و اگر دو مقدار اختصاص دهیم به این معنی است که عدد اول مربوط به فاصله از بالا و پایین element، و عدد دوم مربوط به فاصله از چپ و راست element می‌باشد. 191 | اختصاص دادن مقدار auto به جای عدد دوم به پراپرتی margin به این معنی است که element ما از سمت چپ و راست در قسمت وسط قرار می‌گیرد. 192 | 193 | پراپرتی box-shaow در این قسمت از استایل‌ها، همان طور که می‌بینید دارای پنج مقدار است که به ترتیب، عدد اول، فاصله سایه با باکس در راستای محور افقی، عدد دوم، فاصله سایه با باکس در راستای محور عمودی، عدد سوم، میزان مات شدن یا محو شدن سایه که هر چه این مقدار بیشتر شود لبه سایه مات تر و محوتر می‌شود. عدد چهارم اندازه سایه را مشخص می‌کند و آخرین مقدار در این جا رنگ سایه را تعیین می‌کند. 194 | 195 | 196 | 197 |
198 | 199 | ```css 200 | 201 | .counter { 202 | margin: 50px auto; 203 | width: 150px; 204 | height: 150px; 205 | line-height: 150px; /* فاصله بین خطوط یا سطرهای یک متن را مشخص می‌کند. */ 206 | background-color: #eee; 207 | border-radius: 50%; /* .با دادن این مقدار به این پراپرتی المنت دایره‌ای شکل می‌شود */ 208 | border: 10px solid #2196f3; /* .استایل، عرض و رنگ حاشیه المنت را مشخص می‌کند */ 209 | text-align: center; /* .نحوه چیدمان متن داخل المنت را مشخص می‌کند */ 210 | font-size: 60px; /* .سایز فونت متن داخل المنت را مشخص می‌کند */ 211 | Font-weight: bold; /* .ضخامت متن داخل المنت را مشخص می‌کند */ 212 | color: #107732; /* .رنگ متن داخل المنت را مشخص می‌کند */ 213 | } 214 | 215 | 216 | ``` 217 | 218 |
219 | 220 | 221 | 222 |
223 | 224 | ```css 225 | 226 | .action { 227 | text-align: center; 228 | } 229 | 230 | 231 | ``` 232 | 233 |
234 | 235 | 236 | 237 | 238 |
239 | 240 | ```css 241 | 242 | button { 243 | padding: 20px 30px; 244 | margin: 5px; 245 | font-size: 24px; 246 | font-weight: bold; 247 | border-radius: 5px; 248 | cursor: pointer; /* .زمانی که با موس روی دکمه‌ها می‌رویم اشاره گر ماوس به شکل دست در می‌یاید */ 249 | color: white; 250 | } 251 | 252 | .increase { 253 | background-color: #18cd73; 254 | border: 3px solid #19a35d; 255 | } 256 | 257 | .decrease { 258 | background-color: #ef7694; 259 | border: 3px solid #b74b66; 260 | } 261 | 262 | ``` 263 | 264 |
265 | 266 | 267 | 268 | 269 | ## افزودن رفتار تعاملی با استفاده از JavaScript 270 | 271 | به منظور این که به صفحات وب عملکردی را اضافه کنیم، مثلا در این تمرین، زمانی که روی دکمه increase کلیک کردیم یه واحد به عدد اضافه و زمانی که روی دکمه decrease کلیک کردیم یک واحد از عدد کم شود، از کدهای JavaScript استفاده می‌کنیم. 272 | بدین ترتیب فایل جدیدی به نام counter.js ایجاد می‌کنیم. 273 | در ادامه ابتدا باید فایل counter.js را به فایل counter.html اضافه کنیم. به فایل counter.html رفته و در انتهای تگ body یک تگ script اضافه می کنیم. با استفاده از ویژگی src این تگ آدرس فایل counter.js را مشخص می‌کنیم. 274 | 275 | قبل از ورود به فایل counter.js در همین فایل counter.html برای عنصری که می‌خواهیم محتوای درونش با هر کلیک کاربر، بر روی دکمه‌ها آپدیت شود، یک شناسه منحصر به فرد یا همان ویژگی id را به صورت کد زیر تعیین می‌کنیم. 276 | 277 | 278 |
279 | 280 | ```html 281 | 282 |
283 | 0 284 |
285 | 286 | ``` 287 | 288 |
289 | 290 | 291 | ویژگی id، یک شناسه را برای یک element مشخص می کند. مقداری که به ویژگی id اختصاص می‌دهیم باید در فایل HTML منحصر به فرد باشد. 292 | 293 | از ویژگی id بیشتر برای تغییر دادن element ‌ها و یا محتوای داخل element ‌ها توسط JavaScript در زمان اجرا استفاده می‌کنیم. 294 | 295 | 296 |
297 | 298 | ```html 299 | 300 | 301 |
302 |
303 | 0 304 |
305 |
306 | 309 | 312 |
313 |
314 | 315 | 316 | 317 | ``` 318 | 319 |
320 | 321 | برای نوشتن کدهای Javascript به فایل counter.js رفته و در ابتدا یک متغیر به نام counter به صورت زیر تعریف می‌کنیم. 322 | 323 |
324 | 325 | ```js 326 | 327 | let counter = document.getElementById("count"); 328 | 329 | ``` 330 | 331 |
332 | 333 | 334 | در کد بالا let یک کلمه کلیدی برای تعریف کردن متغیر در Javascript می‌باشد. هر متغیر می‌تواند حاوی هر مقداری باشد. در اینجا قصد ما داشتن متغیری است که به element با id و یا شناسه count اشاره می‌کند. با استفاده از متد getElementById بر روی document یا سند(فایل) HTML می‌توانیم به element با id و یا شناسه‌ای که به این متد پاس می‌دهیم دسترسی داشته باشیم. 335 | 336 | 337 | نکته: هر متد (یا function)، یک بلاک کد از پیش تعریف شده است که به منظور انجام وظیفه ای خاص مانند تبدیل یک مقدار به یک رشته و یا انتخاب یک element و … تعیین شده است. 338 | 339 | 340 | صفحه مرورگر را refresh کرده و روی صفحه کلیک راست و گزینه‌ی inspect را انتخاب کنید. محیطی که با انتخاب این گزینه برای شما باز می‌شود محیط developer tools نام دارد. سپس وارد تب console شوید. در تب console می‌توانید دستوراتی را نوشته و خروجی آنها را ببینید. برای مثال با نوشتن 2 + 2 و زدن دکمه Enter، می‌توانید جواب را که برابر 4 هست ببینید. 341 | به علاوه، خطاهایی که ممکن است در هنگام کار با برنامه رخ دهند را نیز در اینجا می‌توانید مشاهده کنید. 342 | 343 | 344 | 345 | صفحه مرورگر خود را در همین حالت نگه داشته و مجددا به فایل counter.js رفته و خط زیر را به این فایل اضافه نمایید. 346 | 347 | 348 |
349 | 350 | ```js 351 | 352 | console.log (counter); 353 | 354 | ``` 355 | 356 |
357 | 358 | مجددا صفحه مرورگر را refresh نمایید همان طور که می‌بینید مقداری که داخل متغیر counter قرار گرفته در قسمت console چاپ شده است. 359 | 360 | 361 | 362 | 363 | از (console.log (counter صرفا برای متوجه شدن مقدار داخل متغیر counter استفاده کردیم و در ادامه کار احتیاجی به آن نداریم بنابراین این خط از کد را پاک می‌کنیم. 364 | 365 | در ادامه باید دو تابع یا function برای اضافه کردن و کم کردن از عدد بنویسیم. 366 | 367 |
368 | 369 | ```js 370 | 371 | function increase() { 372 | counter.textContent++ ; 373 | } 374 | 375 | ``` 376 | 377 |
378 | 379 | از متد textContent برای دسترسی به عدد داخل element استفاده می‌کنیم. با استفاده از عملگر " ++ " یک واحد به این عدد افزوده و با استفاده از عملگر " -- " یک واحد از این عدد کم می‌کنیم. 380 | 381 |
382 | 383 | ```js 384 | 385 | function decrease() { 386 | counter.textContent-- ; 387 | } 388 | 389 | ``` 390 | 391 |
392 | 393 | در نهایت هر function باید یک جایی فراخوانی شود در این تمرین ما در نظر داشتیم با کلیک بر روی هر دکمه (افزایش یا کاهش عدد) انجام شود برای این منظور به فایل counter.html برمی گردیم و روی رویداد onclick هر کدام از button ها function مربوطه را به صورت زیر فراخوانی می‌کنیم. 394 | 395 |
396 | 397 | ```html 398 | 399 |
400 | 401 | 402 |
403 | 404 | ``` 405 | 406 |
407 | 408 | تغییرات را ذخیره و به مرورگر برمی‌گردیم. خروجی نهایی به صورت زیر می‌باشد. 409 | 410 | 411 | 412 |
413 | 414 | 415 | -------------------------------------------------------------------------------- /web/step-05/README.md: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | ## جلسه پنجم- پیاده سازی صفحه Login 5 | 6 | در این جلسه قصد داریم صفحه Login پروژه اصلی این دوره (TodoApp) را به صورت Responsive پیاده سازی کنیم. 7 | 8 | منظور از پیاده‌سازی به صورت Responsive، این است که ما در پیاده‌سازی صفحه وب به گونه‌ای عمل کنیم که المنت‌‌های موجود در صفحه، در تمامی دستگاه‌هایی که مد نظر ما هستند اعم از لپ‌تاپ، تبلت و موبایل به درستی نمایش داده شوند. به عنوان مثال اگر برای باز کردن صفحه‌ای از وب به جای مرورگر لپ‌تاپ، از مرورگر موبایل استفاده کردیم برای خواندن متن موجود در صفحه احتیاج به زوم کردن متن نداشته باشیم و یا برای دیدن کامل محتوای صفحه، مجبور به اسکرول کردن صفحه به صورت افقی نباشیم. 9 | 10 | 11 | 12 | ## لیست موضوعات 13 | 1. [آشنایی با Figma](#Figma) 14 | 2. [استفاده از فریم ورک Bit برای ایجاد المنت ها](#BitComponents) 15 | 3. [نمایش صحیح المنت ها در سایز تبلت و موبایل](#TabletAndMobile) 16 | 4. [پردازش ورودی ها و نمایش پیغام مناسب](#ProcessInput) 17 | 18 | ## آشنایی با Figma 19 | 20 | قبل از شروع به پیاده سازی صفحه Login می‌خواهیم به بررسی طرح این صفحه، که از قبل، توسط تیم طراحی به وسیله ابزاری به نام Figma ایجاد شده است بپردازیم. 21 | 22 | همان‌طور که در تصویر زیر می‌بینید، این صفحه در سه سایز Tablet ،Desktop و Mobile طراحی شده است. 23 | 24 | در سایز دسکتاپ، بخش مربوط به Login کاربر، از دو بخش تشکیل شده است، که در یک بخش، فرم Login قرار گرفته و در بخش بعدی، لوگوی محصول به اضافه توضیح کوتاهی در مورد محصول قرار گرفته است. 25 | 26 | در سایز تبلت و موبایل به دلیل کمبود فضا بخش اصلی، که همان فرم Login است نگه داشته شده است و همچنین لوگوی مربوط به پروژه هم به قسمت بالای فرم Login با رنگی متفاوت از لوگو، در سایز دسکتاپ قرار گرفته است. 27 | 28 | زمانی که طراح، طرح صفحه موجود را داخل Figma به شما می‌دهد، شما می‌توانید با کلیک بر روی هر المنت و یا کلیک بر روی لایه المنت مورد نظر، به مشخصات المنت، در سمت راست صفحه، که شامل رنگ، سایز و موقعیت المنت در صفحه است، دسترسی داشته باشید. 29 | 30 | لازم به ذکر است بیشترین استفاده ما از این مشخصات مربوط به سایز المنت، رنگ، فونت و فاصله المنت مورد نظر از بقیه اجزای صفحه می‌باشد. لذا، لطفا از کپی کردن کدهای CSS موجود در Figma اجتناب کرده و فقط برای راهنمایی گرفتن و بالا بردن دقت در پیاده‌سازی طرح، از این کدها استفاده کنید. 31 | 32 |

33 | 34 |

35 | 36 | ٖهمچنین شما می توانید مطابق تصویر زیر، تب Export را انتخاب و آیکن‌ها، لوگو و تمامی تصاویر موجود در طرح مربوط به پروژه را با هر نام و فرمتی که می‌خواهید دانلود نمایید. 37 | 38 | 39 | 40 | 41 | از طریق لینک زیر می‌توانید به طرح بالا در فیگما دسترسی داشته باشید. 42 | 43 | https://www.figma.com/file/q0ldEdrilGhZMs7Nj2vZe3/ToDoApp 44 | 45 | ## استفاده از Bit BlazorUI Components برای ایجاد المنت ها 46 | 47 | در ادامه پروژه‌ای به نام TodoApp ایجاد کرده و همانند [جلسات گذشته](https://github.com/bitfoundation/virtual-academy/tree/main/web/step-03) پوشه‌ها، فایل‌ها و کدهای اضافی را حذف می‌کنیم. 48 | 49 | از این جلسه به بعد می‌خواهیم از کامپوننت‌های ‌Bit برای ایجاد سریع‌تر و آسان‌تر المنت‌ها استفاده کنیم. 50 | 51 | برای نصب Bit، همانند [نصب DartSassBuilder در جلسه گذشته](https://github.com/bitfoundation/virtual-academy/tree/main/web/step-04#%D8%A7%D9%81%D8%B2%D9%88%D8%AF%D9%86-%D8%A7%D8%B3%D8%AA%D8%A7%DB%8C%D9%84-%D8%A8%D8%A7%D8%A7%D8%B3%D8%AA%D9%81%D8%A7%D8%AF%D9%87-%D8%A7%D8%B2-scss-) از بخش Solution Explorer بر روی Dependencies کلیک راست کرده و از منوی باز شده گزینه Manage NuGet Packages را انتخاب و در تب ‌Browse در قسمت سرچ باکس Bit.BlazorUI را جستجو و نصب کنید. 52 | 53 | 54 | 55 | سپس خط زیر را که مربوط به کدهای CSS این کامپوننت‌ها می‌باشد به تگ head داخل فایل index.html در پوشه wwwroot اضافه می‌کنیم. 56 | 57 |
58 | 59 | ```razor 60 | 61 | 62 | 63 | ``` 64 |
65 | 66 | و خط زیر را هم که مربوط به کدهای JavaScript این کامپوننت‌ها می‌باشد را به انتهای تگ body در این فایل اضافه می‌کنیم. 67 | 68 |
69 | 70 | ```razor 71 | 72 | 73 | 74 | ``` 75 |
76 | 77 | کدهای داخل فایل index.html بعد از افزودن دو خط بالا به صورت زیر می‌باشد. 78 | 79 |
80 | 81 | ```razor 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | TodoApp 90 | 91 | 92 | 93 | 94 | 95 | 96 |
Loading...
97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | ``` 105 |
106 | 107 | و همچنین خط زیر را هم که مربوط به فضای نام Bit BlazorUI است به فایل Imports.razor_ اضافه میکنیم 108 | 109 |
110 | 111 | ```razor 112 | 113 | @using Bit.BlazorUI 114 | 115 | ``` 116 |
117 | 118 | ​بعد از اتمام مراحل نصب، به ساختار پروژه برمی‌گردیم، ابتدا پوشه جدیدی به نام images در فولدر wwwroot ایجاد کنید، در ادامه image هایی که از فیگما export کردین را با drag & drop داخل این پوشه بگذارید تا در ادامه از این image ها استفاده کنیم. 119 | 120 | 121 | سپس کد زیر را به منظور ایجاد ساختار صفحه Login پروژه در این فایل وارد نمایید. 122 | 123 | 124 |
125 | 126 | ```razor 127 | @page "/login" 128 | 129 |
130 |
131 | 132 |

133 | The TodoApp lets you write, organize, and prioritize your tasks. 134 | This way you can be more productive by registering your tasks in the Todo App 135 |

136 |
137 | 154 |
155 | 156 | ``` 157 |
158 | 159 | برای توضیح بهتر کد بالا، بگذارید از تصویر طرح صفحه Login، در سایز دسکتاپ، استفاده کنیم. 160 | 161 | 162 |

163 | 164 | تگ div با کلاس container مربوط به پس زمینه رنگی می‌باشد که در پشت همه المنت‌های داخل صفحه قرار دارد. 165 | 166 | از تگ div با دو کلاس card, product-description برای ایجاد بخشی که به رنگ بنفش است استفاده کرده‌ایم. 167 | 168 | داخل همین div از تگ img برای نمایش لوگو مربوط به سایز دسکتاپ استفاده کرده‌ایم. دقت داشته باشید که ویژگی alt مربوط به تگ img را همیشه مقداردهی کنید، چرا که اگر کاربر به دلایلی نتواند تصویر را مشاهده کند (اتصال کند، خطا در ویژگی src و … ) ، مقداری که به ویژگی alt اختصاص داده‌اید به جای آن تصویر نمایش داده می‌شود. 169 | 170 | از تگ p در HTML برای تعریف یک پاراگراف استفاده می‌کنیم. در این جا هم ما توضیحات مربوط به این پروژه را داخل این تگ قرار داده‌ایم. 171 | 172 | از تگ div با کلاس card, login-form برای ایجاد قسمت سفید رنگ مربوط به فرم لاگین استفاده می‌کنیم. 173 | 174 | از تگ‌های h1 تا h6 برای تعریف عناوین در HTML استفاده می‌کنیم. تگ h1 مهمترین عنوان، و تگ h6، عنوان با کمترین درجه اهمیت در صفحه را تعریف می‌کند. 175 | 176 | تگ form، یک فرم را در HTML تعریف می‌کند. تگ form می‌تواند شامل تگ‌های input برای گرفتن اطلاعات از کاربر مانند Username, Password باشد که در نهایت کاربر را قادر می‌سازد اطلاعاتی را که از طریق این ورودی‌ها وارد کرده است را، به یک وب سرور ارسال کند. 177 | 178 | در کد بالا مربوط به صفحه Login، داخل تگ form، ما از دو کامپوننت‌ BitTextField فریم‌ورک Bit برای ایجاد دو ورودی مربوط به Username و Password و از کامپوننت BitButton برای ایجاد دکمه Sign in استفاده کرده‌ایم. 179 | 180 | هر کدام از این کامپوننت‌ها یک جز از صفحه وب را تشکیل می‌دهند. این کامپوننت‌ها تشکیل شده از کدهای HTML ،CSS و #C می‌باشند. 181 | 182 | برای درک بهتر این کامپوننت‌ها پروژه را اجرا و وارد محیط Developer Tools می‌شویم. در تب Elements شما می‌توانید نتیجه کامپایل کامپوننت‌های ‌Bit به کدهای HTML را مشاهده کنید. همانطور که در تصویر زیر می‌بینید کامپوننت BitTextField تشکیل شده از یک تگ div است که در میان آن یک تگ input قرار گرفته است. همچنین این تگ، شامل تعداد کلاس از جمله کلاس bit-txt-fluent می‌باشد که اگر دقت کنید در سمت راست محیط Developer Tools بخش مربوط به استایل‌ها، با استفاده از این کلاس‌ها، استایل‌هایی به این کامپوننت‌ها اختصاص داده شده است. 183 |

184 | 185 |

186 | هر کدام از این کامپوننت‌ها شامل تعدادی Property هستند. 187 | مثلا در کامپوننت ‌BitTextField، ما پراپرتی‌های bind-Value ,Type, Label, Class@ را داریم که مانند attribute ها در کدهای HTML، استفاده می‌شوند. مقادیری را که به این پراپرتی‌ها اختصاص ‌می‌دهیم به کامپوننت پاس داده شده و در جای مناسب خود قرار می‌گیرند. 188 | 189 | تصویر زیر محل قرار گرفتن پراپرتی های پاس داده شده به کامپوننت ‌BitTextField را در کد کامپایل شده این کامپوننت نشان می‌دهد. 190 | 191 | 192 | 193 | تا این جای کار ما ساختار صفحه Login را ایجاد کرده‌ایم. 194 | 195 | در ادامه و قبل از شروع به نوشتن کدهای CSS میخواهیم یک CSS RESET به پروژه اضافه کنیم. 196 | 197 | قضیه از این قرار است که مرورگرها مجموعه‌ای از استایل‌های پیش‌فرض را برای تگ‌های HTML در نظر میگیرند، این استایل‌های پیش فرض می‌توانند با استایل‌هایی که ما می‌نویسم تداخل پیدا کنند به همین دلیل بهتر است قبل از شروع به کار بر روی کدهای CSS، استایل‌های پیش‌فرض را پاک کنیم و با یک صفحه خالی شروع کنیم. 198 | 199 | به این منظور یک فایل SCSS جدید به نام main.scss خارج از سطح کامپوننت‌ها و داخل پوشه wwwroot ایجاد می‌کنیم و آن را به عنوان فایل SCSS اصلی در نظر می‌گیریم، این که کدهای مربوط به CSS RESET را داخل این فایل و خارج از سطح کامپوننت‌ها قرار می‌دهیم به این دلیل است که می‌خواهیم این کدها نه تنها بر روی یک کامپوننت، بلکه بر روی تمامی المنت‌ها در صفحه تاثیر بگذارد. 200 | 201 | 202 | 203 | در این مرحله، صفر کردن padding ها و margin های مربوط به تمامی المنت‌ها برای ما کافی‌ست. 204 | 205 |
206 | 207 | ```css 208 | * { 209 | padding: 0; 210 | margin: 0; 211 | } 212 | ``` 213 | 214 |
215 | 216 | سلکتور * در CSS به معنای انتخاب تمامی المنت‌ها موجود در صفحه می‌باشد. 217 | 218 | در ادامه باید فایل main.css را به فایل index.html اضافه کنیم. 219 | 220 |
221 | 222 | ```razor 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | TodoApp 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 |
Loading...
239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | ``` 247 |
248 | 249 | سپس برای افزودن استایل‌ها فایل جدیدی به نام Login.razor.scss ایجاد می کنیم. 250 | 251 | در ابتدای این فایل، متغیرهای رنگ را، بر اساس کد رنگ‌های استفاده شده در طراحی این صفحه داخل Figma، تعریف می‌کنیم. 252 | 253 |
254 | 255 | ```scss 256 | // Variables 257 | // 258 | // Color system 259 | $white: #fff !default; 260 | $purple: #6264A7 !default; 261 | $light-gray: #aaa !default; 262 | $dark-gray: #333 !default; 263 | 264 | ``` 265 | 266 |
267 | 268 | قبل از اختصاص پراپرتی‌ها به کلاس container، لازم است بدانید، از بین روش‌های متفاوتی که برای چیدمان المنت‌ها (Layout) صفحه وجود دارد، ما روش Flexbox را برای Layout صفحه انتخاب کرده‌ایم. 269 | 270 | در ادامه به div با کلاس container پراپرتی‌های زیر را اختصاص می‌دهیم. 271 | 272 |
273 | 274 | ```scss 275 | .container { 276 | display: flex; 277 | flex-direction: row; 278 | justify-content: center; 279 | align-items: center; 280 | width: 100vw; 281 | height: 100vh; 282 | background-image: url("./images/background-image.png"); 283 | background-size: cover; 284 | background-repeat: no-repeat; 285 | } 286 | 287 | ``` 288 |
289 | 290 | با استفاده از ویژگی display نحوه نمایش یک element را در صفحه مشخص می‌کنیم. این مشخصه مقادیر متفاوتی را می‌تواند به خود بگیرد. در اینجا برای پیاده سازی طرح صفحه براساس مدل Flexbox احتیاج داریم یک المنت والد را به عنوان container در نظر بگیریم و به پراپرتی display مقدار flex را اختصاص دهیم. 291 | 292 | Flexbox پراپرتی به نام flex-direction دارد که مشخص می کند المنت‌های فرزند در چه جهتی قرار بگیرند. به عنوان مثال اگر مقدار row را به این پراپرتی اختصاص دهیم المنت‌های فرزند در یک سطر قرار می‌گیرند. 293 | 294 | تصویر زیر چهار مقدار اصلی را که این پراپرتی می‌تواند داشته باشد را با چهار شکل بیان می‌کند. 295 | 296 | 297 | 298 | با استفاده از پراپرتی justify-content در FlexBox محل قرار گرفتن المنت‌های فرزند در محور اصلی یا طول المنت والد، مشخص می کنیم. 299 | 300 | با استفاده از پراپرتی align-items در FlexBox محل قرار گرفتن المنت‌های فرزند در محور عمودی یا عرض المنت والد، مشخص می کنیم. 301 | 302 | برای درک بهتر، تصویر زیر تفاوت بین پراپرتی justify-content و align-items را در FlexBox نشان می‌دهد. 303 | 304 | 305 | 306 | بدیهی است که چون ما هر دو این پراپرتی‌ها را، center مقداردهی کرده‌ایم، المنت‌های فرزند در مرکز المنت والد قرار میگیرند. 307 | 308 | همانطور که در کد بالا می‌بینید ما برای تعیین عرض این المنت با کلاس container از واحد vw و برای تعیین ارتفاع از واحد vh استفاده کرده‌ایم. به این واحدها، واحدهای Viewport می‌گویند. واحدهای Viewport به نسبت اندازه نمایشگر کار می‌کنند. 309 | وقتی می‌گوییم 100vw یعنی عرض المنت به اندازه 100% عرض نمایشگر و وقتی می گوییم 100vh یعنی ارتفاع المنت به اندازه 100% ارتفاع صفحه نمایش باشد. 310 | 311 | از پراپرتی background-image برای تعیین تصویر پس زمینه المنت استفاده می‌کنیم. 312 | 313 | پراپرتی background-size با مقدار cover باعث می‌شود، تصویر پس‌زمینه به اندازه‌ای که تمام پس زمینه المنت را بتواند بپوشاند تغییر اندازه دهد. 314 | 315 | پراپرتی background-repeat با مقدار no-repeat از تکرار شدن تصویر پس زمینه، زمانی که سایز تصویر کوچکتر از سایز المنت هست جلوگیری می‌کند. 316 | 317 | قبل از نوشتن ادامه استایل‌ها، می خواهیم به معرفی ویژگی پرکاربرد Nesting در Sass بپردازیم و در ادامه از این ویژگی استفاده کنیم. 318 | 319 | با استفاده از این ویژگی شما می توانید به جای تکرار مجدد همان سلکتورها، یک سلکتور را داخل سلکتور دیگر، به صورت تو در تو بنویسید. 320 | 321 | برای درک بهتر این ویژگی به مقایسه دو کد، در تصویر زیر توجه نمایید. 322 |

323 | 324 | 325 | 326 |

327 | همانطور که در تصویر بالا می‌بینید، در CSS اگر بخواهیم المنتی را انتخاب کنیم که حتما هر دو کلاس را داشته باشد به صورت زیر هر دو کلاس را بدون هیچ فاصله ای پشت هم می نویسیم. 328 | 329 | 330 | 331 | با استفاده از" & " می توانید این انتخاب را در ویژگی Nesting مربوط به Sass ، به صورت زیر انجام دهید. 332 | 333 | 334 | 335 | در ادامه، کدهای زیر را به انتهای کدهای قبلی اضافه کنید. 336 | 337 |
338 | 339 | ```scss 340 | .card { 341 | display: flex; 342 | flex-direction: column; 343 | padding: 0 40px 100px 40px; 344 | box-shadow: 1px 2px 10px 0 $light-gray; 345 | width: 400px; 346 | height: 500px; 347 | 348 | &.product-description { 349 | justify-content: center; 350 | align-items: center; 351 | background-color: $purple; 352 | color: $white; 353 | text-align: center; 354 | border-radius: 5px 0 0 5px; 355 | } 356 | 357 | &.login-form { 358 | justify-content: center; 359 | align-items: flex-start; 360 | background-color: $white; 361 | border-radius: 0 5px 5px 0; 362 | 363 | h1 { 364 | margin-bottom: 30px; 365 | color: $dark-gray; 366 | } 367 | 368 | form { 369 | width: 300px; 370 | } 371 | } 372 | 373 | .logo { 374 | margin-bottom: 30px; 375 | } 376 | } 377 | 378 | .logo-small { 379 | display: none; 380 | } 381 | 382 | .mb-10 { 383 | margin-bottom: 10px; 384 | } 385 | ``` 386 |
387 | 388 | در کلاس logo-small در کد بالا، از پراپرتی display با مقدار none استفاده کرده ایم. از این مقدار در پراپرتی display به منظور پنهان کردن یک المنت می‌توانیم استفاده کنیم. به گونه‌ای که انگار این المنت از ابتدا وجود نداشته است. 389 | 390 | 391 | ## نمایش صحیح المنت ها در سایز تبلت و موبایل 392 | از این قسمت به بعد، استایل‌هایی که مینویسم مربوط به نمایش صحیح المنت‌ها در سایز تبلت و موبایل می‌باشد. 393 | 394 |
395 | 396 | ```scss 397 | /* Media Query for low-resolution Tablets and Mobile Devices*/ 398 | @media (max-width: 768px) { 399 | .container { 400 | flex-direction: column; 401 | } 402 | 403 | .hidden-desktop { 404 | display: block; 405 | } 406 | 407 | .card { 408 | width: auto; 409 | height: auto; 410 | 411 | &.product-description { 412 | display: none; 413 | } 414 | 415 | &.login-form { 416 | background-color: transparent; 417 | box-shadow: none; 418 | 419 | .logo-small { 420 | display: block; 421 | } 422 | } 423 | } 424 | } 425 | 426 | ``` 427 |
428 | 429 | همانطور که در کد بالا می ‌بینید از media queries برای اعمال استایل‌ها بر روی المنت‌ها متناسب با صفحه نمایش‌های تبلت‌ و موبایل‌ استفاده کرده‌ایم. 430 | 431 | با استفاده از بررسی عرض صفحه نمایش در media queries می‌توانیم استایل‌های متناسب با هر اندازه از صفحه نمایش را، به المنت‌ها اضافه، تغییر و یا از آنها حذف نماییم. 432 | 433 | به منظور مطمئن شدن از صحیح بودن نمایش المنت‌ها در دستگاه‌ها و یا صفحه نمایش‌های متفاوت، می‌توانید مطابق تصویر زیر، از محیط developer tools، بر روی Toggle device toolbar کلیک کرده و از بالای صفحه داخل مرورگر، سایز صفحه نمایش و یا نوع device خود را انتخاب نمایید. 434 |

435 | 436 |

437 | 438 | ## پردازش ورودی ها و نمایش پیغام مناسب 439 | در ادامه فایل جدیدی به نام Login.razor.cs ایجاد می‌کنیم. 440 | 441 | در این مرحله مطابق فلوچارتی که در تصویر زیر آمده، دو مقداری را که توسط ورودی‌ها به عنوان UserName و Password از کاربر گرفته‌ایم را با یک مقدار فرضی مثلا "test" مقایسه می‌کنیم. در صورت درست بودن نتیجه مقایسه، متغیر Show Login Error Message که از نوع boolean می‌باشد و به صورت پیش فرض با مقدار false مقدار دهی شده را، با مقدار true مقداردهی کرده و در غیر این صورت با مقدار false مقداردهی می‌کنیم. در نهایت هم با بررسی مقدار داده شده به متغیر Show Login Error Message، در صورت true بودن پیغامی مبنی بر معتبر نبودن UserName و Password به کار نمایش می‌دهیم. 442 | 443 |

444 | 445 |

446 | 447 |
448 | 449 | ```c# 450 | namespace TodoApp.Pages; 451 | 452 | public partial class Login 453 | { 454 | public string UserName { get; set; } 455 | public string Password { get; set; } 456 | 457 | public bool ShowLoginErrorMessage = false; 458 | 459 | public void Signin() 460 | { 461 | if (UserName != "test" || Password != "test") 462 | { 463 | ShowLoginErrorMessage = true; 464 | } 465 | else 466 | { 467 | ShowLoginErrorMessage = false; 468 | } 469 | } 470 | } 471 | 472 | ``` 473 |
474 | 475 | در #C یک متغیر از نوع boolean با کلمه کلیدی bool تعریف می‌شود و فقط می تواند شامل دو مقدار true و false باشد. 476 | 477 | عملگر منطقی || یا OR به این شکل عمل می‌کند که اگر تنها یکی از عبارات یا شرط ها درست باشد مقدار true را برمی گرداند. 478 | 479 | در کد بالا داخل بلاک کد if ، ما می‌گوییم اگر نام کاربری " یا " رمز عبور مخالف مقدار test بود متغیر ShowLoginErrorMessage با true مقداردهی شود. 480 | 481 | در مرحله آخر می‌خواهیم از پارامترها و متد تعریف شده در این فایل، در فایل Login.razor استفاده کنیم. 482 | 483 |
484 | 485 | ```razor 486 | 487 | @page "/login"; 488 | 489 |
490 |
491 | 492 |

493 | The TodoApp lets you write, organize, and prioritize your tasks. 494 | This way you can be more productive by registering your tasks in the ToDo App 495 |

496 |
497 | 521 |
522 | 523 | ``` 524 |
525 | 526 | همان طور که در کد بالا می‌بینید از متغیر ShowLoginErrorMessage برای نشان دادن پیغامی مبنی بر نادرست بودن UserName و Password استفاده کرده‌ایم. بدین صورت که در صورت true بودن ShowLoginErrorMessage با استفاده از کامپوننت BitMessageBar در فریم ورک ‌Bit این پیغام را نمایش می‌دهیم. 527 | 528 | کامپوننت BitMessageBar دارای پراپرتی MessageBarType می باشد که اگر با BitMessageBarType.Error مقدار دهی شود، پیغام ما مانند تصویر زیر، استایل یک پیغامی که جنبه‌ی خطا دارد را، به خود می‌گیرد. 529 | 530 | 531 |
532 | -------------------------------------------------------------------------------- /web/step-06/README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | ## جلسه ششم- پیاده‌سازی TodoList 4 | 5 | در این جلسه می خواهیم به پیاده‌سازی یک TodoList ساده همانند تصویر زیر بپردازیم. 6 | 7 | 8 | 9 | ابتدا فایل جدیدی به نام TodoPage.razor, TodoPage.razor.scss, TodoPage.razor.cs در پوشه pages ایجاد کرده و کد زیر را به منظور تعیین آدرس این صفحه در ابتدای فایل TodoPage.razor قرار می دهیم. 10 | 11 |
12 | 13 | ```razor 14 | 15 | @page "/todo" 16 | 17 | ``` 18 |
19 | 20 | سپس برای فایل Todo.razor.scss داریم: 21 | 22 |
23 | 24 | ```css 25 | 26 | .container { 27 | margin: 40px auto; 28 | max-width: 500px; 29 | 30 | .todo-count { 31 | margin-bottom: 20px; 32 | font-size: 80px; 33 | font-weight: 100; 34 | text-align: center; 35 | color: rgba(175, 47, 47, 0.15); 36 | text-rendering: optimizeLegibility; 37 | } 38 | 39 | input { 40 | width: 100%; 41 | padding: 10px; 42 | } 43 | 44 | .searchbox { 45 | margin: 10px 0; 46 | } 47 | 48 | .todo-add { 49 | display: flex; 50 | padding: 10px; 51 | border: none; 52 | background: rgba(0, 0, 0, 0.003); 53 | box-shadow: inset 0 -2px 1px #cecece; 54 | font-size: 27px; 55 | } 56 | 57 | .todo-app { 58 | background: #fff; 59 | margin: 20px 0 40px 0; 60 | position: relative; 61 | box-shadow: 0 2px 4px 0 #ccc; 62 | } 63 | 64 | .todo-item { 65 | display: flex; 66 | flex-direction: row; 67 | align-items: center; 68 | padding: 15px; 69 | border-bottom: 1px solid #e6e6e6; 70 | margin: 10px 0; 71 | } 72 | 73 | .todo-title { 74 | display: flex; 75 | align-items: center; 76 | flex: auto; 77 | } 78 | 79 | .filters { 80 | list-style: none; 81 | display: flex; 82 | } 83 | 84 | .todo-action { 85 | display: flex; 86 | width: 120px; 87 | justify-content: flex-end; 88 | } 89 | 90 | .footer { 91 | display: flex; 92 | flex-direction: row; 93 | align-items: center; 94 | color: #777; 95 | font-size: 14px; 96 | } 97 | } 98 | 99 | 100 | ``` 101 |
102 | 103 | در ادامه فایلی با نام TodoItem.cs به ریشه پروژه اضافه کنید. 104 | 105 | 106 | 107 | در این فایل قرار است کلاسی به نام TodoItem داشته باشیم که این کلاس شامل فیلدهای مربوط به یک مورد todo می‌باشد. 108 | 109 |
110 | 111 | ```c# 112 | 113 | public class TodoItem 114 | { 115 | public int Id { get; set; } 116 | 117 | public string Title { get; set; } 118 | 119 | public bool IsDone { get; set; } 120 | 121 | public bool IsEdit { get; set; } = false; 122 | } 123 | 124 | ``` 125 |
126 | 127 | همانطور که در کد بالا می بینید قرار است هر کدام از Todo ها، شامل چهار فیلد Id, Title, IsDone و IsEdit با مقدار پیشفرض false باشد. 128 | 129 | سپس فایل جدیدی به نام TodoPage.razor.cs ایجاد می‌کنیم. 130 | 131 | برای نگهداری Todo ها باید از یک لیست استفاده کنیم. یک لیست مجموعه‌ای از object های قابل دسترسی است که قابلیت جستجو، مرتب کردن و تغییر دادن اعضای داخل آن را برای ما فراهم می کند. 132 | 133 | در این فایل، ابتدا یک متغیر به نام TodoList ازنوع List برای TodoItem ها به صورت زیر ایجاد می‌کنیم. در ادامه قرار است در فایل TodoPage.razor از این متغیر به منظور نگهداری وضعیت لیست Todo ها استفاده کنیم. 134 | 135 |
136 | 137 | ```c# 138 | 139 | using Microsoft.AspNetCore.Components; 140 | using System.Collections.Generic; 141 | using System.Linq; 142 | 143 | namespace ToDoApp.Pages; 144 | 145 | public partial class TodoPage 146 | { 147 | public List TodoList = new(); 148 | } 149 | 150 | ``` 151 |
152 | 153 | بدین‌ترتیب یک لیست از روی کلاس TodoItem که از قبل در فایل TodoItem.cs ایجاده کرده بوده‌ایم، می‌سازیم. 154 | 155 | به فایل TodoPage.razor برمیگردیم. 156 | 157 | در این مرحله، باید بتوانیم اعضای داخل لیستی را که تعریف کرده‌ایم، نمایش دهیم. البته در حالت اولیه هیچ آیتمی داخل لیست ما موجود نیست، اما ابتدا این بخش از کد را می‌نویسیم و در مرحله بعد کد مربوط به افزودن یک Todo جدید به لیست را، اضافه می‌کنیم که بتوانیم به تعداد دلخواه به لیست موجود آیتم اضافه کنیم. 158 | 159 | در HTML برای نمایش یک List از تگ ul استفاده می کنیم. ul مخفف Unordered List و به معنای لیستی که در آن ترتیب اعضا مهم نیست می‌باشد و برای نمایش آیتم‌های داخل لیست از تگ li استفاده می‌کنیم. 160 | 161 | تصویر زیر نحوه استفاده از تگ ul و تگ li را، برای نمایش یک لیست فرضی نشان می دهد. 162 | 163 | 164 | 165 | در مثالی که در تصویر بالا آمده ، یک لیستی را داریم که اعضای آن ثابت و یا به اصطلاح static است. به این معنا که ما از قبل تعداد اعضای داخل لیست و محتوای آن را در نظر گرفته‌ایم، اما در TodoList ، ما اطلاعی از این که کاربر چه تعداد آیتم می‌خواهد به لیستمان (TodoList) اضافه و یا از لیست کم کند و یا این که اصلا محتوای آیتم‌های آن چه باشد نداریم، پس در واقع ما یک لیستی داریم که به صورت پویا و به اصطلاح dynamic تعداد اعضای آن اضافه و کم می‌شود و محتوای آیتم های آن تغییر می‌کند. 166 | 167 | در این جور مواقع ما باید از دستوری استفاده کنیم که لیست موجود را گرفته و شروع کند آیتم به آیتم اعضای لیست را پیمایش کردن، حالا در حین این پیمایش ما می‌توانیم دستوری اضافه کنیم که اگر به فلان آیتم رسیدی به عنوان مثال نامش را تغییر بده و یا اصلا آیتم را حذف کن و یا اینکه به هر آیتمی که رسیدی محتوای آن آیتم را به کاربر نشان بده. 168 | 169 | ما می‌توانیم از دستور foreach برای پیمایش اعضای یک لیست به صورت زیراستفاده کنیم. 170 | 171 | 172 | 173 |
174 | 175 | ```razor 176 | @page "/todo"; 177 | 178 |
179 |
    180 | @foreach (TodoItem todo in TodoList) 181 | { 182 |
  • @todo.Title
  • 183 | } 184 |
185 |
186 | 187 | ``` 188 | 189 |
190 | 191 | در کد بالا، از یک دستور foreach برای پیمایش لیست TodoList استفاده کرده‌ایم. همانطور که در کد بالا می‌بینید هر کدام از اعضای این لیست را یک todo از نوع کلاس TodoItem که قبلا ایجاد کرده بودیم نامگذاری کرده‌ایم. سپس در تگ li عنوان مربوط به هر todo را نمایش داده‌ایم. در واقع با این دستور ما یک حلقه ایجاد کرده‌ایم که به ازای هر کدام از اعضای لیست، یک تگ li ایجاد می کند و عنوان مربوط به همان عضو را داخل li میگذارد. این روال تا آخرین عضو مربوط به این لیست ادامه دارد. 192 | 193 | در کامپوننت‌های ‌Bit ما کامپوننتی به نام BitBasicList برای نمایش لیست داریم. در این کامپوننت علاوه بر نمایش اعضای لیست، موارد دیگر مانند صفحه بندی یا Pagination و مواردی که باعث کارایی بهتر نسبت به دیگر دستورات می‌شود را داریم. 194 | 195 | بنابراین کامپوننت BitBasicList را به صورت زیر جایگزین دستور foreach می‌کنیم. 196 | 197 |
198 | 199 | ```razor 200 | 201 |
202 | 203 | 204 | 205 |
206 |
207 | 208 | @TodoItem.Title 209 | 210 |
211 |
212 |
213 |
214 | 215 |
216 | 217 | ``` 218 | 219 |
220 | 221 | 222 | در کامپوننت BitBasicList پراپرتی Items این کامپوننت را، با متغیری که از قبل برای لیست Todoها (TodoList) ایجاد کرده‌ایم مقداردهی می کنیم. 223 | همچنین RowTemplate داخل این کامپوننت مربوط به هر عضو از TodoList که از نوع TodoItem بوده می‌باشد، همانطور که می‌بینید شما می‌توانید هر آنچه را که از TodoItem می‌خواهید به کاربر نمایش دهید را داخل RowTemplate بگذارید. 224 | 225 | در همین مرحله همچنین می‌خواهیم وضعیت تکمیل شدن یا نشدن یک Todo را هم به کاربر نمایش دهیم. برای این منظور می توانیم از تگ input با "type="checkbox به صورتی که در تصویر زیر آمده استفاده کنیم. 226 | 227 | 228 | 229 | هر input با "type="checkbox تنها می‌تواند شامل دو مقدار checked و یا unchecked از نوع boolean باشد. 230 | 231 | همانطور که ‌می‌بینید کنار تگ input از یک تگ label هم استفاده شده است. با وجود تگ label شما می ‌توانید برای checkbox مربوطه عنوانی را تعیین نمایید، همچین منطقه بیشتری برای checked کردن یا unchecked کردن checkbox دارید چرا که حتی اگر روی مقداری که برای label در نظر گرفته‌اید هم کلیک کنید، checkbox شما تغییر وضعیت می‌دهد. 232 | 233 | توجه فرمایید که در مثال بالا مقداری که برای ویژگی id تگ input در نظر گرفته شده است با مقداری که برای ویژگی for تگ label در نظر گرفته شده یکی می‌باشد. بدین صورت ما می توانیم مشخص کنیم که کدام Label مربوط به کدام input است. 234 | 235 | ما در کامپوننت‌های Bit برای input با تایپ checkbox هم کامپوننتی به نام BitCheckbox را داریم که می توانیم از آن به صورت زیر در کد استفاده کنیم. 236 | 237 |
238 | 239 | ```razor 240 | 241 |
242 | 243 | 244 |
245 |
246 | 247 | 248 | @TodoItem.Title 249 | 250 |
251 |
252 |
253 |
254 |
255 | 256 | ``` 257 | 258 |
259 | 260 | در ادامه می خواهیم کد مربوط به افزودن یک Todo جدید را بنویسیم. 261 | 262 | بدین ترتیب ابتدا یک متغیر جدید به نام TodoName در فایل TodoPage.razor.cs اضافه می کنیم. 263 | 264 |
265 | 266 | ```c# 267 | 268 | using System.Collections.Generic; 269 | using System.Linq; 270 | 271 | namespace ToDoApp.Pages; 272 | 273 | public partial class TodoPage 274 | { 275 | public List TodoList = new(); 276 | public string TodoName { get; set; } 277 | } 278 | 279 | ``` 280 |
281 | 282 | سپس در فایل TodoPage.razor یک input قرار داده و متغیر TodoName را به آن bind می‌کنیم و می خواهیم روی رویداد onkeyup این input یک متد به نام AddTodo را فراخوانی کنیم. 283 | 284 | رویداد onkeyup زمانی رخ می دهد که کاربر یک کلید (روی صفحه کلید) را رها می کند. 285 | 286 | کد تغییر یافته این فایل به صورت زیر می‌باشد. 287 | 288 |
289 | 290 | ```razor 291 | 292 |
293 |
294 | 295 |
296 | 297 | 298 | 299 |
300 |
301 | 302 | 303 | @TodoItem.Title 304 | 305 |
306 |
307 |
308 |
309 | 310 |
311 | 312 | ``` 313 |
314 | 315 | در ادامه متد AddTodo را به کلاس TodoPage به صورت زیر اضافه می‌کنیم. 316 | 317 | ٖ
318 | 319 | ```c# 320 | 321 | using System.Collections.Generic; 322 | using System.Linq; 323 | 324 | namespace ToDoApp.Pages; 325 | 326 | public partial class TodoPage 327 | { 328 | public List TodoList = new(); 329 | public string TodoName { get; set; } 330 | private void AddTodo() 331 | { 332 | if (!string.IsNullOrWhiteSpace(TodoName)) 333 | { 334 | var newTask = new TodoItem() 335 | { 336 | Id = TodoList.Count() + 1, 337 | Title = TodoName, 338 | IsDone = false 339 | }; 340 | 341 | TodoList.Add(newTask); 342 | TodoName = null; 343 | } 344 | } 345 | } 346 | 347 | 348 | ``` 349 |
350 | 351 | متد IsNullOrWhiteSpace در #C برای بررسی این که رشته مشخص شده خالی یا فقط شامل کاراکترهای space است استفاده می شود. اگر به یک رشته مقداری اختصاص داده نشده باشد یا صراحتاً مقدار null به آن اختصاص داده شده باشد،آن رشته null خواهد بود. 352 | 353 | 354 | در متد AddTodo در کد بالا، تعیین کرده‌ایم که اگر مقدار TodoName مخالف null بود، یک object جدید از کلاس TodoItem ساخته و مقادیر تعیین شده به فیلدهای todo جدید اختصاص داده شود. 355 | 356 | برای ایجاد یک object جدید از یک کلاس از کلمه کلیدی new قبل از نام کلاس استفاده می کنیم. 357 | 358 | بدین صورت که برای مقدار Id تعداد todo های موجود در لیست را با استفاده از متد از پیش تعریف شده Count گرفته و یک واحد به آن اضافه کرده و به Id نسبت می‌دهیم. 359 | 360 | مقدار TodoName یا همان نامی که کاربر داخل input به عنوان نام todo جدید وارد کرده را به Title نسبت می‌دهیم. 361 | 362 | و در نهایت مقدار false را هم به IsDone نسبت می‌دهیم چرا که Todo جدید است و هنوز انجام نشده. 363 | 364 | بعد از ایجاد و مقدار دهی Todo جدید، Todo جدید را باید با استفاده از متد ()Add لیست‌ها به انتهای TodoList اضافه کنیم. 365 | 366 | بعد از اضافه کردن TodoName را با مقدار null مقداردهی می کنیم. 367 | 368 | در این مرحله می‌توانیم یک شرط برای نمایش لیست Todo ها قرار دهیم به این صورت که اگر تعداد آیتم‌های لیست بیشتر از صفر بود، لیست را نمایش دهد. 369 | 370 |
371 | 372 | ```razor 373 | 374 |
375 |
376 | 377 |
378 | @if (TodoList.Count > 0) 379 | { 380 | 381 | 382 |
383 |
384 | 385 | 386 | @TodoItem.Title 387 | 388 |
389 |
390 |
391 |
392 | } 393 |
394 | 395 | ``` 396 | 397 |
398 | 399 | در ادامه می خواهیم هر Todo داخل لیست امکان ویرایش و حذف را نیز داشته باشد. 400 | 401 | بدین منظور از دو کامپوننت BitIconButton برای فراخوانی دو متد EditTodoItem و DeleteTodoItem داخل لیست استفاده می‌کنیم. 402 | 403 | برای این که خود آیکون‌ها نمایش داده شوند، پکیج Bit.BlazorUI.Icons را نصب کرده و style زیر را در index.html بعد از bit.blazorui.css قرار دهید: 404 | 405 | `` 406 | 407 | کد بالا به صورت زیر تغییر می‌کند. 408 | 409 |
410 | 411 | ```razor 412 | 413 |
414 |
415 | 416 |
417 | @if (TodoList.Count > 0) 418 | { 419 | 420 | 421 |
422 |
423 | 424 | 425 | @TodoItem.Title 426 | 427 |
428 | 429 |
430 | 431 | 432 |
433 |
434 |
435 |
436 | } 437 |
438 | 439 | ``` 440 | 441 |
442 | 443 | کدهای HTML مربوط به رندر کامپوننت BitIconButton به صورت تصویر زیر می‌باشد. 444 | 445 | 446 | 447 | همانطور که در تصویر بالا می‌بینید کامپوننت BitIconButton تشکیل شده است از یک تگ button که داخل آن یک تگ span قرار گرفته و داخل تگ span هم یک تگ i برای نمایش icon مورد نظر قرار گرفته است که به هر کدام از آنها یک فونت آیکن می‌گویند. فونت آیکون‌ها فونت هایی هستند که به جای حروف یا اعداد ، دارای نمادها و آیکن‌ها هستند. با فونت آیکن‌ها می‌توانید مانند متن معمولی رفتار کنید به این صورت که هر پراپرتی CSS که به متن اختصاص می‌دهید مثل font-size، color می‌توانید به آنها هم اختصاص دهید. 448 | 449 | شما می‌توانید لیست کامل این فونت آیکن‌ها را در لینک زیر ببینید. 450 | 451 | https://components.bitplatform.dev/iconography 452 | 453 | اگر نام هر کدام از فونت آیکن‌های موجود در این لیست را به پارامتر IconName کامپوننت BitIconButton اختصاص دهید، button شما شکل آن آیکن را به خود می‌گیرد. 454 | 455 | برخی از این فونت آیکن‌ها در تصویر زیر آمده است. 456 | 457 | 458 | 459 | پارامتر بعدی که در استفاده از کامپوننت BitIconButton به کار بردیم پارامتر OnClick می‌باشد به این معنا که وقتی روی این دکمه کلیک کردیم یک اتفاقی بیفتد. مقداری که به این پارامتر در BitIconButton اول اختصاص داده‌ایم عبارت (e => EditTodoItem(TodoItem)) می‌باشد. به این عبارت در #Lambda expression ،C می‌گوییم. از یک عبارت lambda برای ایجاد یک anonymous function استفاده می کنیم. از عملگر <= در Lambda برای جدا کردن لیست پارامترهای lambda، از بدنه آن استفاده می‌کنیم. 460 | 461 | برای ایجاد عبارت lambda، پارامترهای ورودی را در صورت وجود در سمت چپ عملگر lambda و یک عبارت یا یک بلاک دستور را در طرف دیگر مشخص می کنیم. 462 | 463 | با استفاده از این عبارت با هر بار کلیک بر روی این دکمه متد EditTodoItem فراخوانی شده و مقادیر TodoItem جاری به آن اختصاص داده می‌شود. 464 | 465 | در مرحله بعد می‌خواهیم متد مربوط به حذف شدن یک Todo از لیست را بنویسیم. بدین منظور به فایل TodoPage.razor.cs رفته و کد مربوط به این متد را به صورت زیر به این فایل اضافه می‌کنیم. 466 | 467 |
468 | 469 | ```c# 470 | 471 | using System.Collections.Generic; 472 | using System.Linq; 473 | 474 | namespace ToDoApp.Pages; 475 | 476 | public partial class TodoPage 477 | { 478 | public List TodoList = new(); 479 | public string TodoName { get; set; } 480 | } 481 | 482 | private void AddTodo() 483 | { 484 | if (!string.IsNullOrWhiteSpace(TodoName)) 485 | { 486 | var newTask = new TodoItem() 487 | { 488 | Id = TodoList.Count() + 1, 489 | Title = TodoName, 490 | IsDone = false 491 | }; 492 | 493 | TodoList.Add(newTask); 494 | TodoName = null; 495 | } 496 | 497 | private void DeleteTodoItem(TodoItem todo) 498 | { 499 | TodoList.Remove(todo); 500 | } 501 | } 502 | 503 | ``` 504 |
505 | 506 | همانطور که می‌بینید ورودی این متد یک todo از نوع TodoItem می‌باشد. همان مقداری که زمان فراخوانی این متد روی رویداد OnClick مربوط به دکمه Delete به آن اختصاص دادیم. دستوری که داخل این متد نوشته شده است به این معناست که روی لیست TodoList، متد از پیش تعریف شده Remove را فراخوانی کن و todo ای را که به عنوان پارامتر ورودی داده شده است را حذف کن. 507 | 508 | مرحله بعدی مربوط به افزودن متد EditTodoItem است که مربوط به ویرایش یک todo می‌باشد. کد این متد به صورت زیر به فایل TodoPage.razor.cs اضافه می‌شود. 509 | 510 |
511 | 512 | ```c# 513 | 514 | private void EditTodoItem(TodoItem todo) 515 | { 516 | todo.IsEdit = true; 517 | } 518 | 519 | ``` 520 |
521 | 522 | در این متد ما مقدار IsEdit مربوط به todo جاری که قصد ویرایشش را داریم را برابر با true قرار می‌دهیم. 523 | 524 | در ادامه به فایل TodoPage.razor می‌رویم و کد مربوط به این صفحه را به صورت زیر تغییر می‌دهیم. 525 | 526 |
527 | 528 | ```razor 529 | 530 |
531 |
532 | 533 |
534 | @if (TodoList.Count > 0) 535 | { 536 | 537 | 538 |
539 |
540 | 541 | @if (TodoItem.IsEdit) 542 | { 543 | 544 | 545 | 546 | } 547 | else 548 | { 549 | 550 | @TodoItem.Title 551 | 552 | } 553 |
554 |
555 | 556 | 557 |
558 |
559 |
560 |
561 | } 562 |
563 | 564 | ``` 565 | 566 |
567 | 568 | همانطور که در کد بالا می‌بینید در div با کلاس todo-title تغییری ایجاد کرده‌ایم. به این صورت که اگر مقدار IsEdit مربوط به TodoItem برابر با true بود، یعنی عنوان مربوط به todo باید ویرایش شود پس به جای نمایش عنوان مربوط به todo، یک input را نمایش می‌دهیم که متغیر NewName را که در مرحله بعد تعریف کنیم، به آن bind می‌کنیم و همچنین دو button داریم که یکی از آنها قرار است متدی به نام EditTodo را فراخوانی کرده و آن یکی متد CancelEditTodo را فراخوانی می‌کند. 569 | 570 | در ادامه برای تعریف متغیر و متدهای مربوط به ویرایش یک todo، به فایل TodoPage.razor.cs برمی‌گردیم و کد مربوط به این فایل را به صورت زیر تغییر می دهیم. 571 | 572 |
573 | 574 | ```c# 575 | 576 | using System.Collections.Generic; 577 | using System.Linq; 578 | 579 | namespace ToDoApp.Pages; 580 | 581 | public partial class TodoPage 582 | { 583 | public List TodoList = new(); 584 | public string TodoName { get; set; } 585 | public string NewName { get; set; } 586 | 587 | private void AddTodo() 588 | { 589 | if (!string.IsNullOrWhiteSpace(TodoName)) 590 | { 591 | var newTask = new TodoItem() 592 | { 593 | Id = TodoList.Count() + 1, 594 | Title = TodoName, 595 | IsDone = false 596 | }; 597 | 598 | TodoList.Add(newTask); 599 | TodoName = null; 600 | } 601 | } 602 | 603 | private void DeleteTodoItem(TodoItem todo) 604 | { 605 | TodoList.Remove(todo); 606 | } 607 | 608 | private void EditTodoItem(TodoItem todo) 609 | { 610 | todo.IsEdit = true; 611 | } 612 | 613 | private void EditTodo(TodoItem todo) 614 | { 615 | if (!string.IsNullOrWhiteSpace(NewName)) 616 | { 617 | todo.IsEdit = false; 618 | todo.Title = NewName; 619 | NewName = null; 620 | } 621 | } 622 | 623 | private void CancelEditTodo(TodoItem todo) 624 | { 625 | todo.IsEdit = false; 626 | NewName = null; 627 | } 628 | } 629 | 630 | ``` 631 |
632 | 633 | در کد بالا ابتدا پارامتری به نام NewName و از نوع string را در بخش مربوط به تعریف متغیرها، تعریف کرده‌ایم. 634 | 635 | سپس در ادامه متدها، متد EditTodo را اضافه کرده‌ایم. شرح مربوط به دستورات داخل این متد به این صورت است که بررسی می‌کنیم که اگر مقدار NewName مخالف null بود، ابتدا مقدار IsEdit مربوط به todo را برابر با false قرار داده و سپس مقدار NewName را به Title مربوط به todo اختصاص می‌دهیم و در نهایت مقدار null را به NewName اختصاص می‌دهیم. 636 | 637 | در ادامه متد CancelEditTodo را داریم، این متد زمانی فراخوانی می‌شود که ما از تغییر دادن نام todo منصرف شده‌ایم، بنابراین در این متد بعد از اختصاص دادن مقدار false به متغیر IsEdit مربوط به todo مقدار NewName را برابر با null قرار می‌دهیم. 638 | 639 | در ادامه می خواهیم متد مربوط به تکمیل شدن todo ها را بنویسیم به این صورت که اگر checkbox مربوط به هر todo را کاربر به حالت checked تغییر داد، یعنی todo مربوط تکمیل شده است. 640 | 641 | به این منظور ابتدا متد HandleTodoChange را به پارامتر OnChange کامپوننت BitCheckbox موجود در فایل TodoPage، به صورت زیر اختصاص می‌دهیم. 642 | 643 |
644 | 645 | ```razor 646 | 647 | 648 | 649 | ``` 650 |
651 | 652 | برای اضافه کردن متد HandleTodoChange به فایل TodoPage.razor.cs رفته و کد زیر را به این فایل اضافه می‌کنیم. 653 | 654 | 655 |
656 | 657 | ```c# 658 | 659 | private void HandleTodoChange(TodoItem todo) 660 | { 661 | todo.IsDone = !todo.IsDone; 662 | } 663 | 664 | ``` 665 |
666 | 667 | مجموع این دو قطعه کد به این صورت عمل کند که چون ما متد HandleTodoChange را به پارامتر OnChange اختصاص داده‌ایم با هر تغییر در وضعیت چک باکس (checked شدن و یا unchecked شدن) بلافاصله متد HandleTodoChange فراخوانی شده و وضعیت IsDone هر todo را مخالف وضعیت جاری قرار می‌دهد. 668 | 669 | 670 | در ادامه می‌خواهیم امکان جستجو کردن یک todoItem را به TodoList مان اضافه کنیم. 671 | 672 | 673 | بدین منظور ابتدا کامپوننت BitSearchBox را به صورت زیر به فایل TodoPage.razor اضافه می‌کنیم. 674 | 675 |
676 | 677 | ```razor 678 | 679 |
680 | 683 |
684 | 685 |
686 | @if (TodoList.Count > 0) 687 | { 688 | 689 | 690 |
691 |
692 | 693 | @if (TodoItem.IsEdit) 694 | { 695 | 696 | 697 | 698 | } 699 | else 700 | { 701 | 702 | @TodoItem.Title 703 | 704 | } 705 |
706 |
707 | 708 | 709 |
710 |
711 |
712 |
713 | } 714 |
715 | 716 | ``` 717 |
718 | 719 | در ادامه به فایل TodoPage.razor.cs رفته و مجددا یک لیست جدید از روی کلاس TodoItem به نام FilteredTodoList به منظور نگه داشتن نتیجه سرچی که روی لیست اصلی انجام شده ایجاد می‌کنیم. 720 | 721 | همچنین متغیر جدیدی به نام SearchTerm را هم به منظور نگه داشتن نام Todo سرچ شده به این قسمت اضافه می کنیم. 722 | 723 | بدین ترتیب بخش اول کد این فایل که مربوط به تعریف پارامترها و متغیرها است به صورت زیر تغییر می‌کند. 724 | 725 |
726 | 727 | ```c# 728 | 729 | private List TodoList = new(); 730 | private List FilteredTodoList = new(); 731 | private string TodoName { get; set; } 732 | public string NewName { get; set; } 733 | private string SearchTerm; 734 | 735 | ``` 736 |
737 | 738 | به فایل Todopage.razor برمی‌گردیم، در کامپوننت BitSearchBox پارامتری به نام OnSearch تعریف شده است که ما می توانیم با استفاده از این پارامتر تعیین کنیم، که زمانی که کاربر شروع به سرچ کردن می‌کند چه اتفاقی بیفتد. 739 | 740 | پس پارامتر BitSearchBox را به این کامپوننت اضافه و متدی به نام HandleSearch را برای آن در نظر می‌گیریم. 741 | 742 |
743 | 744 | ```razor 745 | 746 | 749 | 750 | ``` 751 |
752 | 753 | در ادامه می خواهیم متد HandleSearch را تعریف و به متدهای موجود اضافه کنیم. 754 | 755 | در متد HandleSearch به صورت زیر، ابتدا واژه سرچ شده را به متغیر SearchTerm اختصاص داده و سپس متد Filter که در ادامه تعریف می کنیم را، فراخوانی می کنیم. 756 | 757 |
758 | 759 | ```c# 760 | 761 | private void HandleSearch(string searchTerm) 762 | { 763 | SearchTerm = searchTerm; 764 | Filter(); 765 | } 766 | 767 | ``` 768 |
769 | 770 | متد فیلتر را فعلا به صورت زیر تعریف می‌کنیم. 771 | 772 |
773 | 774 | ```c# 775 | 776 | private void Filter() 777 | { 778 | FilteredTodoList = TodoList.Where(item => 779 | { 780 | var result = string.IsNullOrWhiteSpace(SearchTerm) || item.Title.ToLower().Contains(SearchTerm.ToLower()); 781 | if (result is false) return false; 782 | return true; 783 | }).ToList(); 784 | } 785 | 786 | ``` 787 |
788 | 789 | سپس باید در BitBasicList تغییری ایجاد کنیم که به جای این که آیتم‌ها را از روی TodoList بخواند آنها را از روی FilteredTodoList خوانده و لیست را بسازد در واقع FilteredTodoList همان لیست قبلی است با این تفاوت که اگر سرچی هم انجام شده نتیجه سرچ را هم داخل خودش دارد. 790 | 791 | این قسمت از کد قبلی به صورت زیر تغییر می‌کند. 792 | 793 | 794 |
795 | 796 | ```razor 797 | @if (FilteredTodoList.Count > 0) 798 | { 799 | 800 | 801 |
802 |
803 | 804 | @if (TodoItem.IsEdit) 805 | { 806 | 807 | 808 | 809 | } 810 | else 811 | { 812 | 813 | @TodoItem.Title 814 | 815 | } 816 | 817 |
818 | 819 |
820 | 821 | 822 |
823 |
824 | 825 |
826 |
827 | } 828 | 829 | ``` 830 |
831 | 832 | 833 | با استفاده از پارامتر OnClear این کامپوننت می‌توانیم متدی را برای زمانی که کاربر بر روی آیکن ( close ( X این کامپوننت کلیک کرد اختصاص دهیم. 834 | 835 | پس ابتدا، پارامتر OnClear را به این کامپوننت مطابق زیر اضافه کرده و سپس متد HandleClear را تعریف و به متدهای قبلی اضافه می‌کنیم. 836 | 837 |
838 | 839 | ```razor 840 | 841 | 844 | 845 | ``` 846 |
847 | 848 | 849 |
850 | 851 | ```c# 852 | 853 | private void HandleClear() 854 | { 855 | HandleSearch(""); 856 | } 857 | 858 | ``` 859 |
860 | 861 | همانطور که در کد بالا می‌بینید داخل متد HandleClear ما متد HandleSearch را صدا زده‌ایم و یک رشته خالی را به آن اختصاص داده‌ایم این رشته خالی داخل SearchTerm قرار گرفته و عملا چون هیچ واژه برای سرچ وجود ندارد، در کل سرچی انجام نمی‌شود. 862 | 863 | در ادامه می خواهیم علاوه بر سرچی که نوشتیم آیتم‌ها را براساس فعال بودن(هنوز تکمیل نشده) و تکمیل شده فیلتر کنیم. 864 | 865 | بدین منظور از کامپوننت BitChoiceGroup و BitChoiceOption زیر استفاده کرده و کد قبلی را به صورت زیر می‌دهیم. 866 | 867 |
868 | 869 | ```razor 870 |
871 |
872 | Todos(@TodoList.Count(todo => !todo.IsDone)) 873 |
874 | 875 | 878 | 879 |
880 |
881 | 882 |
883 | 884 | @if (FilteredTodoList.Count > 0) 885 | { 886 | 887 | 888 |
889 |
890 | 891 | @if (TodoItem.IsEdit) 892 | { 893 | 894 | 895 | 896 | } 897 | else 898 | { 899 | 900 | @TodoItem.Title 901 | 902 | } 903 | 904 |
905 | 906 |
907 | 908 | 909 |
910 |
911 | 912 |
913 |
914 | } 915 |
916 | 923 |
924 | 925 | ``` 926 |
927 | 928 | اضافه کردن این قسمت از کد باعث می‌شود خروجی به صورت زیر تغییر کند. 929 | 930 | 931 | 932 | طبق روال گذشته می توانیم در قسمت DeveloperTools کد کامپایل شده این کامپوننت را ببینیم. 933 | 934 | 935 | 936 | زمانی که می‌خواهیم مجموعه‌ای از گزینه‌های مرتبط را کنار هم داشته باشیم، می‌توانیم از input با نوع radio و یا checkbox استفاده کنیم با این تفاوت که زمانی که شما از نوع checkbox برای input استفاده می‌کنید می‌توانید به صورت همزمان چندین گزینه را انتخاب کنید اما زمانی که از نوع radio برای input استفاده می‌کنید در هر لحظه فقط یه گزینه می تواند انتخاب شود. 937 | 938 | در مورد label هم در درس‌های گذشته به صورت کامل توضیح داده‌ایم. 939 | 940 | کامپوننت BitChoiceGroup پارامتری به نام OnValueChange دارد که با تغییر انتخاب بین گزینه‌ها، متدی که به آن اختصاص داده شده را فرا می‌خواند. 941 | 942 | پس در ادامه نیاز داریم این پارامتر ار به این کامپوننت اضافه و متدی را به آن اختصاص دهیم. 943 | 944 |
945 | 946 | ```razor 947 | 948 | 949 | 950 | 951 | 952 | 953 | 954 | ``` 955 |
956 | 957 | در ادامه برای تعریف متد HandleFilterChange به فایل TodoPage.razor.cs رفته و در ابتدا متغیری به نام FilterValue را در قسمت مربوط به تعریف پارامترها و متغیر تعریف می‌کنیم. 958 | 959 |
960 | 961 | ```c# 962 | 963 | private List TodoList = new(); 964 | private List FilteredTodoList = new(); 965 | private string TodoName { get; set; } 966 | public string NewName { get; set; } 967 | private string SearchTerm; 968 | private string FilterValue; 969 | 970 | ``` 971 |
972 | 973 | همانطور که در کد بالا می‌بینید ما value حاصل از انتخاب گزینه ها را به FilterValue اختصاص داده و مجدد متد فیلتر را صدا می‌زنیم. 974 | 975 |
976 | 977 | ```c# 978 | 979 | private void HandleFilterChange(string filter) 980 | { 981 | FilterValue = filter; 982 | Filter(); 983 | } 984 | 985 | ``` 986 |
987 | 988 | در ادامه متد Filter را به صورت زیر تغییر می دهیم. 989 | 990 |
991 | 992 | ```c# 993 | 994 | private void Filter() 995 | { 996 | FilteredTodoList = TodoList.Where(item => 997 | { 998 | var result = string.IsNullOrWhiteSpace(SearchTerm) || item.Title.ToLower().Contains(SearchTerm.ToLower()); 999 | if (result is false) return false; 1000 | if (string.IsNullOrWhiteSpace(FilterValue) is false) 1001 | { 1002 | switch (FilterValue) 1003 | { 1004 | case "Active": 1005 | return !item.IsDone; 1006 | case "Completed": 1007 | return item.IsDone; 1008 | } 1009 | 1010 | } 1011 | return true; 1012 | }).ToList(); 1013 | } 1014 | 1015 | ``` 1016 |
1017 | 1018 | در کد اضافه شده به متد Filter گفته‌ایم که اگر مقدار FilterValue مخالف null و یا WhiteSpace بود، در کل FilterValue دارای مقدار بود در ادامه وارد بلاک مربوط به switch شود. 1019 | 1020 | از دستور switch case می توان به جای دستور if..else استفاده کرد، زمانی که بیش‌تر از یک گزینه برای انتخاب وجود دارد. 1021 | 1022 | دستور switch به طور کلی به صورت زیر تعریف می‌شود. 1023 | 1024 | 1025 | 1026 | 1027 | این دستور به این صورت کار می کند که ابتدا switch expression یکبار ارزیابی می‌شود و سپس مقدار expression با مقدار هر case مقایسه و در صورت مطابقت بلاک کد مرتبط اجرا می‌شود. پس از اجرا اگر دستور break وجود داشته باشد اجرای کد در داخل بلاک متوقف شده و کار همین جا تمام می‌شود. اگر پیش‌بینی می‌کنیم که ممکن است مقدار expression با هیچ کدام از مقادیر case ها مطابقت نداشته باشد می‌توانیم از دستور default استفاده کنیم، در این صورت (عدم مطابقت مقادیر با هم) بلاک کد مربوط به دستور default اجرا می‌شود. 1028 | 1029 | در کد بالا ما گفته‌ایم که اگر FilterValue دارای مقدار Active بود item هایی را برگردان که IsDone آنها false هست یعنی هنوز تکمیل نشده‌اند و فعال هستند و اگر FilterValue دارای مقدار Completed بود item هایی را برگردان که IsDone آنها ture هست به این معنی که این item ها تکمیل شده‌اند. 1030 | 1031 | در نهایت هر جایی که تغییری در تعداد آیتم‌های موجود در لیست می‌دهیم مانند حذف کردن و یا اضافه شدن آیتم‌ها، باید متد Filter را هم صدا بزنیم. 1032 | 1033 | کدهای موجود در این فایل به صورت زیر تغییر می‌کند. 1034 | 1035 | 1036 |
1037 | 1038 | ```c# 1039 | 1040 | using System.Collections.Generic; 1041 | using System.Linq; 1042 | 1043 | namespace ToDoApp.Pages; 1044 | 1045 | public partial class TodoPage 1046 | { 1047 | private List TodoList = new(); 1048 | private List FilteredTodoList = new(); 1049 | private string TodoName { get; set; } 1050 | private string NewName { get; set; } 1051 | private string SearchTerm; 1052 | private string FilterValue; 1053 | 1054 | private void AddTodo() 1055 | { 1056 | if (!string.IsNullOrWhiteSpace(TodoName)) 1057 | { 1058 | var newTask = new TodoItem() 1059 | { 1060 | Id = TodoList.Count() + 1, 1061 | Title = TodoName, 1062 | 1063 | IsDone = false 1064 | }; 1065 | 1066 | TodoList.Add(newTask); 1067 | Filter(); 1068 | TodoName = null; 1069 | } 1070 | } 1071 | 1072 | private void DeleteTodoItem(TodoItem todo) 1073 | { 1074 | TodoList.Remove(todo); 1075 | Filter(); 1076 | } 1077 | 1078 | private void EditTodoItem(TodoItem todo) 1079 | { 1080 | todo.IsEdit = true; 1081 | } 1082 | 1083 | private void EditTodo(TodoItem todo) 1084 | { 1085 | if (!string.IsNullOrWhiteSpace(NewName)) 1086 | { 1087 | todo.IsEdit = false; 1088 | todo.Title = NewName; 1089 | NewName = null; 1090 | } 1091 | } 1092 | 1093 | private void CancelEditTodo(TodoItem todo) 1094 | { 1095 | todo.IsEdit = false; 1096 | NewName = null; 1097 | } 1098 | 1099 | private void HandleTodoChange(TodoItem todo) 1100 | { 1101 | todo.IsDone = !todo.IsDone; 1102 | Filter(); 1103 | } 1104 | 1105 | private void HandleClear() 1106 | { 1107 | HandleSearch(""); 1108 | } 1109 | 1110 | private void HandleSearch(string searchTerm) 1111 | { 1112 | SearchTerm = searchTerm; 1113 | Filter(); 1114 | } 1115 | 1116 | private void HandleFilterChange(string filter) 1117 | { 1118 | FilterValue = filter; 1119 | Filter(); 1120 | } 1121 | 1122 | private void Filter() 1123 | { 1124 | FilteredTodoList = TodoList.Where(item => 1125 | { 1126 | var result = string.IsNullOrWhiteSpace(SearchTerm) || item.Title.ToLower().Contains(SearchTerm.ToLower()); 1127 | if (result is false) return false; 1128 | if (string.IsNullOrWhiteSpace(FilterValue) is false) 1129 | { 1130 | switch (FilterValue) 1131 | { 1132 | case "Active": 1133 | return !item.IsDone; 1134 | case "Completed": 1135 | return item.IsDone; 1136 | } 1137 | 1138 | } 1139 | return true; 1140 | }).ToList(); 1141 | } 1142 | } 1143 | 1144 | ``` 1145 |
1146 | 1147 |
1148 | 1149 | 1150 | 1151 | --------------------------------------------------------------------------------