Converting Create/Edit Modal to Page
In this document we will explain how to convert AspNet Zero's tenant create & edit modals to regular mvc pages.
Create Tenant
Create Action Result
Create action result named Create. Copy CreateModal partial view result into that and change necessary parts as seen below
[AbpMvcAuthorize(AppPermissions.Pages_Tenants_Create)]
public async Task<ActionResult> Create()
{
var editionItems = await _editionAppService.GetEditionComboboxItems();
var defaultEditionName = _commonLookupAppService.GetDefaultEditionName().Name;
var defaultEditionItem = editionItems.FirstOrDefault(e => e.DisplayText == defaultEditionName);
if (defaultEditionItem != null)
{
defaultEditionItem.IsSelected = true;
}
var viewModel = new CreateTenantViewModel(editionItems)
{
PasswordComplexitySetting = await _passwordComplexitySettingStore.GetSettingsAsync()
};
return View(viewModel);
}
Create View File
Then create view named Create in Areas/[AppAreaName]/Views/Tenants
folder.
Copy _CreateModel.cshtml into your new Create.cshtml .
Remove modal header, footer partialviews and modal body tag. Add a portlet that surrounds the form tag.
The result will be like seen below:
@using Abp.Json
@using MyCompanyName.AbpZeroTemplate.MultiTenancy
@using MyCompanyName.AbpZeroTemplate.Web.Areas.AppAreaName.Models.Tenants
@model CreateTenantViewModel
@section Scripts
{
<script src="~/view-resources/Areas/AppAreaName/Views/Tenants/Create.js"></script>
<script>
window.passwordComplexitySetting = @Html.Raw(Model.PasswordComplexitySetting.ToJsonString(indented: true));
</script>
}
<div class="kt-content kt-grid__item kt-grid__item--fluid kt-grid kt-grid--hor">
<div class="kt-subheader kt-grid__item">
<div class="@(await GetContainerClass())">
<div class="kt-subheader__main">
<h3 class="kt-subheader__title">
<span>@L("CreateNewTenant")</span>
</h3>
</div>
</div>
</div>
<div class="@(await GetContainerClass()) kt-grid__item kt-grid__item--fluid">
<div class="kt-portlet">
<div class="kt-portlet__body">
<form name="TenantInformationsForm">
<div class="form-group">
<label for="TenancyName">@L("TenancyName")</label>
<input id="TenancyName" class="form-control" type="text" name="TenancyName" required maxlength="@Tenant.MaxTenancyNameLength" regex="@Tenant.TenancyNameRegex">
</div>
<div class="form-group no-hint">
<label for="Name">@L("Name")</label>
<input id="Name" type="text" name="Name" class="form-control" required maxlength="@Tenant.MaxNameLength">
</div>
<div class="kt-checkbox-list">
<label class="kt-checkbox">
<input id="CreateTenant_UseHostDb" type="checkbox" name="UseHostDb" value="true" checked="checked">
@L("UseHostDatabase")
<span></span>
</label>
</div>
<div class="form-group no-hint" style="display: none">
<label for="ConnectionString">@L("DatabaseConnectionString")</label>
<input id="ConnectionString" type="text" name="ConnectionString" class="form-control" required maxlength="@Tenant.MaxConnectionStringLength">
</div>
<div class="form-group">
<label for="AdminEmailAddress">@L("AdminEmailAddress")</label>
<input id="AdminEmailAddress" type="email" name="AdminEmailAddress" class="form-control" required maxlength="@MyCompanyName.AbpZeroTemplate.Authorization.Users.User.MaxEmailAddressLength">
</div>
<div class="kt-checkbox-list">
<label class="kt-checkbox">
<input id="CreateTenant_SetRandomPassword" type="checkbox" name="SetRandomPassword" value="true" checked="checked" />
@L("SetRandomPassword")
<span></span>
</label>
</div>
<div class="form-group no-hint tenant-admin-password" style="display: none">
<label for="CreateTenant_AdminPassword">@L("Password")</label>
<input id="CreateTenant_AdminPassword" type="password" name="AdminPassword" class="form-control" maxlength="@MyCompanyName.AbpZeroTemplate.Authorization.Users.User.MaxPlainPasswordLength" autocomplete="new-password">
</div>
<div class="form-group tenant-admin-password" style="display: none">
<label for="AdminPasswordRepeat">@L("PasswordRepeat")</label>
<input id="AdminPasswordRepeat" type="password" name="AdminPasswordRepeat" class="form-control" maxlength="@MyCompanyName.AbpZeroTemplate.Authorization.Users.User.MaxPlainPasswordLength" equalto="#CreateTenant_AdminPassword" autocomplete="new-password">
</div>
<div class="form-group no-hint">
<label for="EditionId">@L("Edition")</label>
<select class="form-control" id="EditionId" name="EditionId">
@foreach (var edition in Model.EditionItems)
{
<option value="@edition.Value" data-isfree="@edition.IsFree">@edition.DisplayText</option>
}
</select>
</div>
<div class="kt-checkbox-list subscription-component">
<label for="CreateTenant_IsUnlimited" class="kt-checkbox">
<input id="CreateTenant_IsUnlimited" type="checkbox" name="IsUnlimited" />
@L("UnlimitedTimeSubscription")
<span></span>
</label>
</div>
<div class="form-group subscription-component">
<label for="SubscriptionEndDateUtc">@L("SubscriptionEndDateUtc")</label>
<input id="SubscriptionEndDateUtc" type="datetime" name="SubscriptionEndDateUtc" class="form-control date-time-picker" required>
</div>
<div class="kt-checkbox-list subscription-component">
<label for="CreateTenant_IsInTrialPeriod" class="kt-checkbox">
<input id="CreateTenant_IsInTrialPeriod" class="md-check" type="checkbox" name="IsInTrialPeriod" value="true" />
@L("IsInTrialPeriod")
<span></span>
</label>
</div>
<div class="kt-checkbox-list">
<label for="CreateTenant_ShouldChangePasswordOnNextLogin" class="kt-checkbox">
<input id="CreateTenant_ShouldChangePasswordOnNextLogin" type="checkbox" name="ShouldChangePasswordOnNextLogin" value="true" checked="checked">
@L("ShouldChangePasswordOnNextLogin")
<span></span>
</label>
<label for="CreateTenant_SendActivationEmail" class="kt-checkbox">
<input id="CreateTenant_SendActivationEmail" type="checkbox" name="SendActivationEmail" value="true" checked="checked">
@L("SendActivationEmail")
<span></span>
</label>
<label for="CreateTenant_IsActive" class="kt-checkbox">
<input id="CreateTenant_IsActive" type="checkbox" name="IsActive" value="true" checked="checked">
@L("Active")
<span></span>
</label>
</div>
</form>
</div>
<div class="kt-portlet__foot">
<div class="row align-items-center">
<div class="col-md-12 kt-align-right pr-5">
[Cancel](/en/aspnet-core-mvc/v9.1.0//AppAreaName/Tenants)
<span class="kt-margin-left-10">or</span> <button id="btnCreateTenant" class="btn btn-brand">Submit</button>
</div>
</div>
</div>
</div>
</div>
</div>
Create Javascript File
Create javascript file named Create.js.
Copy _CreateModal.js into Create.js.
Search modal.find
and replace all with $
.
Remove modal function and take their content.
The result will be like seen below:
(function () {
$(function () {
//Custom validation type for tenancy name
$.validator.addMethod("tenancyNameRegex", function (value, element, regexpr) {
return regexpr.test(value);
}, app.localize('TenantName_Regex_Description'));
var _tenantService = abp.services.app.tenant;
var _$tenantInformationForm = null;
var _passwordComplexityHelper = new app.PasswordComplexityHelper();
_$tenantInformationForm = $('form[name=TenantInformationsForm]');
_$tenantInformationForm.validate({
rules: {
TenancyName: {
tenancyNameRegex: new RegExp(_$tenantInformationForm.find('input[name=TenancyName]').attr('regex'))
}
}
});
//Show/Hide password inputs when "random password" checkbox is changed.
var passwordInputs = $('input[name=AdminPassword],input[name=AdminPasswordRepeat]');
var passwordInputGroups = passwordInputs.closest('.form-group');
_passwordComplexityHelper.setPasswordComplexityRules(passwordInputs, window.passwordComplexitySetting);
$('#CreateTenant_SetRandomPassword').change(function () {
if ($(this).is(':checked')) {
passwordInputGroups.slideUp('fast');
passwordInputs.removeAttr('required');
} else {
passwordInputGroups.slideDown('fast');
passwordInputs.attr('required', 'required');
}
});
//Show/Hide connection string input when "use host db" checkbox is changed.
var connStringInput = $('input[name=ConnectionString]');
var connStringInputGroup = connStringInput.closest('.form-group');
$('#CreateTenant_UseHostDb').change(function () {
if ($(this).is(':checked')) {
connStringInputGroup.slideUp('fast');
connStringInput.removeAttr('required');
} else {
connStringInputGroup.slideDown('fast');
connStringInput.attr('required', 'required');
}
});
$('.date-time-picker').datetimepicker({
locale: abp.localization.currentLanguage.name,
format: 'L'
});
var $subscriptionEndDateDiv = $('input[name=SubscriptionEndDateUtc]').parent('div');
var $isUnlimitedInput = $('#CreateTenant_IsUnlimited');
var subscriptionEndDateUtcInput = $('input[name=SubscriptionEndDateUtc]');
function toggleSubscriptionEndDateDiv() {
if ($isUnlimitedInput.is(':checked')) {
$subscriptionEndDateDiv.slideUp('fast');
subscriptionEndDateUtcInput.removeAttr('required');
} else {
$subscriptionEndDateDiv.slideDown('fast');
subscriptionEndDateUtcInput.attr('required', 'required');
}
}
$isUnlimitedInput.change(function () {
toggleSubscriptionEndDateDiv();
});
var $editionCombobox = $('#EditionId');
$editionCombobox.change(function () {
var isFree = $('option:selected', this).attr('data-isfree') === "True";
var selectedValue = $('option:selected', this).val();
if (selectedValue === "" || isFree) {
$('.subscription-component').slideUp('fast');
if (isFree) {
$isUnlimitedInput.prop("checked", true);
} else {
$isUnlimitedInput.prop("checked", false);
}
} else {
$('.subscription-component').slideDown('fast');
}
});
toggleSubscriptionEndDateDiv();
$editionCombobox.trigger('change');
function save() {
if (!_$tenantInformationForm.valid()) {
return;
}
var tenant = _$tenantInformationForm.serializeFormToObject();
//take selected date as UTC
if ($('#CreateTenant_IsUnlimited').is(':visible') && !$('#CreateTenant_IsUnlimited').is(':checked')) {
tenant.SubscriptionEndDateUtc = $('.date-time-picker').data("DateTimePicker").date().format("YYYY-MM-DDTHH:mm:ss") + 'Z';
} else {
tenant.SubscriptionEndDateUtc = null;
}
if (tenant.SetRandomPassword) {
tenant.Password = null;
tenant.AdminPassword = null;
}
if (tenant.UseHostDb) {
tenant.ConnectionString = null;
}
abp.ui.setBusy();
_tenantService.createTenant(
tenant
).done(function () {
abp.notify.info(app.localize('SavedSuccessfully'));
window.location.href = "/AppAreaName/Tenants";
}).always(function () {
abp.ui.clearBusy();
});
};
$("#btnCreateTenant").click(save);//Now there is no modal button. Thats why you need to call save function with another button
});
})();
And finally go Index.cshtml and change Create new tenant button as seen below
<a href="/[AppAreaName]/Tenants/Create" class="btn btn-primary">
<i class="fa fa-plus"></i> @L("CreateNewTenant")
</a>
Result
Edit Tenant
Converting edit modal to edit page has same step with converting create modal to create page. The results will be as seen below.
Create Action Result
TenantsController.cs
[AbpMvcAuthorize(AppPermissions.Pages_Tenants_Edit)]
public async Task<ActionResult> Edit(int id)
{
var tenantEditDto = await _tenantAppService.GetTenantForEdit(new EntityDto(id));
var editionItems = await _editionAppService.GetEditionComboboxItems(tenantEditDto.EditionId);
var viewModel = new EditTenantViewModel(tenantEditDto, editionItems);
return View(viewModel);
}
Create View File
Edit.cshtml
@using Abp.Extensions
@using MyCompanyName.AbpZeroTemplate.MultiTenancy
@using MyCompanyName.AbpZeroTemplate.Web.Areas.AppAreaName.Models.Common.Modals
@using MyCompanyName.AbpZeroTemplate.Web.Areas.AppAreaName.Models.Tenants
@model EditTenantViewModel
@section Scripts
{
<script src="~/view-resources/Areas/AppAreaName/Views/Tenants/Edit.js"></script>
}
<div class="kt-content kt-grid__item kt-grid__item--fluid kt-grid kt-grid--hor">
<div class="kt-subheader kt-grid__item">
<div class="@(await GetContainerClass())">
<div class="kt-subheader__main">
<h3 class="kt-subheader__title">
<span>@L("EditTenant")</span>
</h3>
<span class="kt-subheader__separator kt-subheader__separator--v"></span>
<span class="kt-subheader__desc">
@Model.Tenant.TenancyName
</span>
</div>
</div>
</div>
<div class="@(await GetContainerClass()) kt-grid__item kt-grid__item--fluid">
<div class="kt-portlet">
<div class="kt-portlet__body">
<form name="TenantInformationsForm">
<input type="hidden" name="Id" value="@Model.Tenant.Id" />
<input type="hidden" name="TenancyName" value="@Model.Tenant.TenancyName" />
<div class="form-group">
<label for="Name">@L("Name")</label>
<input id="Name" type="text" name="Name" value="@Model.Tenant.Name" class="form-control edited" required maxlength="@Tenant.MaxNameLength">
</div>
@if (!Model.Tenant.ConnectionString.IsNullOrEmpty())
{
<div class="form-group">
<label for="ConnectionString">@L("DatabaseConnectionString")</label>
<input id="ConnectionString" type="text" name="ConnectionString" class="form-control edited" value="@Model.Tenant.ConnectionString" required maxlength="@Tenant.MaxConnectionStringLength">
</div>
<div>
<span class="help-block text-warning">@L("TenantDatabaseConnectionStringChangeWarningMessage")</span>
</div>
}
<div class="form-group">
<label for="EditionId">@L("Edition")</label>
<select class="form-control" id="EditionId" name="EditionId">
@foreach (var edition in Model.EditionItems)
{
<option value="@edition.Value" data-isfree="@edition.IsFree" selected="@edition.IsSelected">@edition.DisplayText</option>
}
</select>
</div>
<div class="kt-checkbox-list subscription-component">
<label class="kt-checkbox">
<input id="CreateTenant_IsUnlimited" type="checkbox" name="IsUnlimited" @(!Model.Tenant.SubscriptionEndDateUtc.HasValue ? "checked=\"checked\"" : "") />
@L("UnlimitedTimeSubscription")
<span></span>
</label>
</div>
<div class="form-group position-relative">
<label for="SubscriptionEndDateUtc">@L("SubscriptionEndDateUtc")</label>
<input id="SubscriptionEndDateUtc" type="datetime" name="SubscriptionEndDateUtc" value="@(Model.Tenant.SubscriptionEndDateUtc)" class="form-control edited date-time-picker" @(!Model.Tenant.SubscriptionEndDateUtc.HasValue ? "required" : "")>
</div>
<div class="kt-checkbox-list subscription-component">
<label class="kt-checkbox">
<input id="EditTenant_IsInTrialPeriod" type="checkbox" name="IsInTrialPeriod" value="true" @(Model.Tenant.IsInTrialPeriod ? "checked=\"checked\"" : "") />
@L("IsInTrialPeriod")
<span></span>
</label>
</div>
<div class="kt-checkbox-list">
<label class="kt-checkbox">
<input id="EditTenant_IsActive" type="checkbox" name="IsActive" value="true" @Html.Raw(Model.Tenant.IsActive ? "checked=\"checked\"" : "")>
@L("Active")
<span></span>
</label>
</div>
</form>
</div>
<div class="kt-portlet__foot">
<div class="row align-items-center">
<div class="col-md-12 kt-align-right pr-5">
[Cancel](/en/aspnet-core-mvc/v9.1.0//AppAreaName/Tenants)
<span class="kt-margin-left-10">or</span> <button id="btnEditTenant" class="btn btn-brand">Submit</button>
</div>
</div>
</div>
</div>
</div>
</div>
Create Javascript File
Edit.js
(function () {
$(function () {
var _tenantService = abp.services.app.tenant;
var _$tenantInformationForm = null;
_$tenantInformationForm = $('form[name=TenantInformationsForm]');
_$tenantInformationForm.validate();
$('.date-time-picker').datetimepicker({
locale: abp.localization.currentLanguage.name,
format: 'L'
});
var $subscriptionEndDateDiv = $('input[name=SubscriptionEndDateUtc]').parent('div');
var isUnlimitedInput = $('#CreateTenant_IsUnlimited');
function toggleSubscriptionEndDateDiv() {
if (isUnlimitedInput.is(':checked')) {
$subscriptionEndDateDiv.slideUp('fast');
} else {
$subscriptionEndDateDiv.slideDown('fast');
}
}
isUnlimitedInput.change(function () {
toggleSubscriptionEndDateDiv();
});
var $editionCombobox = $('#EditionId');
var $isInTrialCheckbox = $('#EditTenant_IsInTrialPeriod');
$editionCombobox.change(function () {
var isFree = $('option:selected', this).attr('data-isfree') === "True";
var selectedValue = $('option:selected', this).val();
if (isFree) {
$isInTrialCheckbox.closest('div').slideUp('fast');
} else {
$isInTrialCheckbox.closest('div').slideDown('fast');
}
if (selectedValue <= 0) {
$('.subscription-component').slideUp('fast');
if (!isUnlimitedInput.is(':checked')) {
$subscriptionEndDateDiv.slideDown('fast');
}
} else {
$('.subscription-component').slideDown('fast');
}
});
$editionCombobox.trigger('change');
toggleSubscriptionEndDateDiv();
function edit() {
if (!_$tenantInformationForm.valid()) {
return;
}
var tenant = _$tenantInformationForm.serializeFormToObject();
//take selected date as UTC
if ($('#CreateTenant_IsUnlimited').is(':visible') && !$('#CreateTenant_IsUnlimited').is(':checked')) {
tenant.SubscriptionEndDateUtc = $('.date-time-picker').data('DateTimePicker').date().format("YYYY-MM-DDTHH:mm:ss") + 'Z';
} else {
tenant.SubscriptionEndDateUtc = null;
}
abp.ui.setBusy();
_tenantService.updateTenant(
tenant
).done(function () {
abp.notify.info(app.localize('SavedSuccessfully'));
location.href="/AppAreaName/Tenants";
}).always(function () {
abp.ui.clearBusy();
});
};
$("#btnEditTenant").click(edit);
});
})();
Then go index.js and change edit button on data table.
{
text: app.localize('Edit'),
visible: function () {
return _permissions.edit;
},
action: function (data) {
location.href = "/AppAreaName/Tenants/Edit?id=" + data.record.id;//change that
}
},