البته یک راه حل برای آن در اینجا مطرح شده. رابطه دیگر many-to-many نیست. دو رابطه one-to-many تشکیل شده به جدول واسط.
البته یک راه حل برای آن در اینجا مطرح شده. رابطه دیگر many-to-many نیست. دو رابطه one-to-many تشکیل شده به جدول واسط.
حتما تا به حال در وب سایتهای زیادی قسمت هایی را دیده اید که چیدمان عناصر آن به شکل زیر است:
این گونه چیدمان را حتما در منوی Start ویندوز 8 بارها دیدهاید! عناصر تشکیل دهندهی این شکل از چیدمان، میتوانند یک سری عکس باشند که تشکیل یک گالری عکس را دادهاند و یا یک سری div که محتوای پستهای یک وبلاگ را در خود جای دادهاند. چیزی که این شکل از چیدمان عناصر را نسبت به چیدمانهای معمول متمایز میکند این است که طول و عرض هر یک از این عناصر با یکدیگر متفاوت است و هدف از این گونه چیدمان آن است که این عناصر در فضایی که به آنها اختصاص داده شده است، به صورت بهینه قرار گیرند تا کمترین فضا هدر رود.
برای اعمال این شکل از چیدمان در دنیای وب افزونههای زیادی بر فراز کتاب خانهی jQuery تدارک دیده شده است که از جمله مطرحترین آنها میتوان به افزونه های Isotope ، Masonry و Gridster اشاره کرد.
افزونهی Isotope مزایایی را برای من در پی داشت و این افزونه را برای انجام کارهای خود، مناسب دیدم. نکتهی مهم اینجا است که هدف من بررسی Isotope نیست، چرا که اگر به وب سایت آن مراجعه کنید، با کوهی از مستندات مواجه میشوید که چگونه از آن در وب سایتهای معمولی استفاده کنید.
در این مقاله قصد من این است که نشان دهم چگونه از افزونهی Isotope در AngularJS استفاده کنیم؛ چگونه چیدمان آن را راست به چپ کنیم و چگونه آن را با محیطهای واکنش گرا (Responsive) سازگار کنیم.
فرض کنید در یک وب سایت قصد داریم اطلاعات یک سری مطلب خبری را از سرور، به فرمت JSON دریافت کرده و نمایش دهیم. در AngularJS شیوهی کار بدین صورت است که اطلاعاتی که به فرمت JSON هستند را با استفاده از directive ایی به نام ng-repeat پیمایش کرده و آنها را نمایش دهیم. حال اگر بخواهیم چیدمان مطالب را با استفاده از Isotope تغییر دهیم، میبینیم که هیچ چیزی نمایش داده نمیشود. دلیل آن بر میگردد به مراحل کامپایل کردن AngularJS و نامشخص بودن زمان اعمال چیدمان Isotope به عناصر است.
در AngularJS هنگامیکه با دستکاری DOM سر و کار پیدا میکنیم، معمولا باید به سراغ Directiveها رفت و یک Directive سفارشی برای کار با Isotope تعریف کرد تا با مکانیزمهای Angular سازگار باشد. خوشبختانه Directive Isotope برای Angular موجود میباشد. نکتهی مهم این است که این Directive برای نگارش 1 افزونهی Isotope نوشته شده است. البته با نگارش 2 هم کار میکند که من برای انجام کار خود نسخهی 1 را ترجیح دادم استفاده کنم.
نکتهی بعدی که باید رعایت شود این است که چیدمان عناصر باید از راست به چپ شوند. خوشبختانه این کار در نسخهی 1 Isotope با تغییر کوچکی در سورس Isotope و تغییر یک تابع انجام میشود. گویا نسخهی دوم امکان پیش فرضی را برای این کار دارد، اما نتوانستم آن را به خوبی پیاده سازی کنم و به همین دلیل ترجیح دادم از همان نسخهی اول استفاده کنم.
برای اینکه در هنگام جابه جا شدن عناصر، انیمیشنها نیز از راست به چپ انجام شوند، باید cssهای زیر را نیز اعمال نمود:
.isotope .isotope-item { -webkit-transition-property: right, top, -webkit-transform, opacity; -moz-transition-property: right, top, -moz-transform, opacity; -ms-transition-property: right, top, -ms-transform, opacity; -o-transition-property: right, top, -o-transform, opacity; transition-property: right, top, transform, opacity; }
Responsive بودن این عناصر مسئلهی دیگری است که باید حل گردد. امروزه اکثر فریم ورکهای مطرح css، واکنشگرا نیز هستند و برای پشتیبانی از سایزهای متفاوت صفحه نمایش، تدابیری در نظر گرفتهاند. اساس کار واکنش گرا بودن این فریم ورکها در تعیین ابعاد عناصر، بیان ابعاد به صورت درصدی است. مثلا فلان عرض div برابر 50% باشد بدین معناست که همیشه عرض این div نصف عرض عنصر والد آن باشد.
متاسفانه Isotope میانهی چندانی با این ابعاد درصدی ندارد و باید عرض عناصر به صورت دقیق و بر حسب پیکسل بیان شود. البته نسخهی جدید آن و یا حتی پلاگین هایی برای کار با ابعاد درصدی نیز تدارک دیده شده است که به شخصه به نتیجهی با کیفیتی نرسیدم.
@media (min-width: 768px) and (max-width: 980px) { .card { width: 320px; } } @media (min-width: 980px) and (max-width: 1200px) { .card { width: 260px; } } @media (min-width: 1200px) { .card { width: 340px; } }
app.directive('imageOnload', function () { return { restrict: 'A', link: function (scope, element, attrs) { element.bind('load', function () { scope.$emit('iso-method', { name: 'reLayout', params: null }); // call reLayout isotope methode prevent overlaaping the items }); } }; });
$(window).resize(function () { $timeout(function myfunction() { $scope.$broadcast('iso-method', { name: 'reLayout', params: null }); // call reLayout isotope methode prevent overlaaping the items },1000); });
@using BlazorWasm.Client.Pages.Authentication
@using System.Security.Claims @inject NavigationManager NavigationManager if(AuthState is not null) { <div class="alert alert-danger"> <p>You [@AuthState.User.Identity.Name] do not have access to the requested page</p> <div> Your roles: <ul> @foreach (var claim in AuthState.User.Claims.Where(c => c.Type == ClaimTypes.Role)) { <li>@claim.Value</li> } </ul> </div> </div> } @code { [CascadingParameter] private Task<AuthenticationState> AuthenticationState {set; get;} AuthenticationState AuthState; protected override async Task OnInitializedAsync() { AuthState = await AuthenticationState; if (!IsAuthenticated(AuthState)) { var returnUrl = NavigationManager.ToBaseRelativePath(NavigationManager.Uri); if (string.IsNullOrEmpty(returnUrl)) { NavigationManager.NavigateTo("login"); } else { NavigationManager.NavigateTo($"login?returnUrl={Uri.EscapeDataString(returnUrl)}"); } } } private bool IsAuthenticated(AuthenticationState authState) => authState?.User?.Identity is not null && authState.User.Identity.IsAuthenticated; }
در ادامه برای استفاده از این کامپوننت، به کامپوننت ریشهای BlazorWasm.Client\App.razor مراجعه کرده و قسمت NotAuthorized آنرا به صورت زیر، با معرفی کامپوننت RedirectToLogin، جایگزین میکنیم:
<NotAuthorized> <RedirectToLogin></RedirectToLogin> </NotAuthorized>
using System; using BlazorServer.Common; using Microsoft.AspNetCore.Authorization; namespace BlazorWasm.Client.Utils { [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] public class RolesAttribute : AuthorizeAttribute { public RolesAttribute(params string[] roles) { Roles = $"{ConstantRoles.Admin},{string.Join(",", roles)}"; } } }
@attribute [Roles(ConstantRoles.Customer, ConstantRoles.Employee)]
namespace BlazorWasm.WebApi.Controllers { [Route("api/[controller]")] [Authorize(Roles = "Editor")] public class MyProtectedEditorsApiController : Controller { [HttpGet] public IActionResult Get() { return Ok(new ProtectedEditorsApiDTO { Id = 1, Title = "Hello from My Protected Editors Controller!", Username = this.User.Identity.Name }); } } }
namespace BlazorWasm.Client.Services { public class ClientHttpInterceptorService : DelegatingHandler { private readonly NavigationManager _navigationManager; private readonly ILocalStorageService _localStorage; private readonly IJSRuntime _jsRuntime; public ClientHttpInterceptorService( NavigationManager navigationManager, ILocalStorageService localStorage, IJSRuntime JsRuntime) { _navigationManager = navigationManager ?? throw new ArgumentNullException(nameof(navigationManager)); _localStorage = localStorage ?? throw new ArgumentNullException(nameof(localStorage)); _jsRuntime = JsRuntime ?? throw new ArgumentNullException(nameof(JsRuntime)); } protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { // How to add a JWT to all of the requests var token = await _localStorage.GetItemAsync<string>(ConstantKeys.LocalToken); if (token is not null) { request.Headers.Authorization = new AuthenticationHeaderValue("bearer", token); } var response = await base.SendAsync(request, cancellationToken); if (!response.IsSuccessStatusCode) { await _jsRuntime.ToastrError($"Failed to call `{request.RequestUri}`. StatusCode: {response.StatusCode}."); switch (response.StatusCode) { case HttpStatusCode.NotFound: _navigationManager.NavigateTo("/404"); break; case HttpStatusCode.Forbidden: // 403 case HttpStatusCode.Unauthorized: // 401 _navigationManager.NavigateTo("/unauthorized"); break; default: _navigationManager.NavigateTo("/500"); break; } } return response; } } }
@page "/unauthorized" <div class="alert alert-danger mt-3"> <p>You don't have access to the requested resource.</p> </div>
dotnet add package Microsoft.Extensions.Http
namespace BlazorWasm.Client { public class Program { public static async Task Main(string[] args) { var builder = WebAssemblyHostBuilder.CreateDefault(args); //... // builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); /*builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.Configuration.GetValue<string>("BaseAPIUrl")) });*/ // dotnet add package Microsoft.Extensions.Http builder.Services.AddHttpClient( name: "ServerAPI", configureClient: client => { client.BaseAddress = new Uri(builder.Configuration.GetValue<string>("BaseAPIUrl")); client.DefaultRequestHeaders.Add("User-Agent", "BlazorWasm.Client 1.0"); } ) .AddHttpMessageHandler<ClientHttpInterceptorService>(); builder.Services.AddScoped<ClientHttpInterceptorService>(); builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("ServerAPI")); //... } } }
.row-cols
classes
for quickly specifying the number of columns across breakpoints. This
one is huge for those of you who have asked for responsive card decks. A jQuery plugin that makes images truly responsive, without sacrificing anyone's face. Demo