Durandal 2.0: transition not triggered while staying on same view with new data - durandal

Here's my shell:
<div>
<div id="content" class="container-fluid page-host main">
<!--ko router: { transition:'entrance', alwaysTriggerAttach: 'true', cacheViews: 'true' }--><!--/ko-->
</div>
</div>
I always navigate to views using:
router.navigate(route);
Now, when being on a certain view (item), and calling the navigate function for another id (/#item/id), the data changes correctly, but the transition does not happen.
Any advice?
Thanks
Nicolas

Should transition be triggered is defined in this function of durandal/js/composition.js:
function shouldTransition(context) {
if (system.isString(context.transition)) {
if (context.activeView) {
if (context.activeView == context.child) {
return false;
}
if (!context.child) {
return true;
}
if (context.skipTransitionOnSameViewId) {
var currentViewId = context.activeView.getAttribute('data-view');
var newViewId = context.child.getAttribute('data-view');
return currentViewId != newViewId;
}
}
return true;
}
return false;
If you want to trigger transition then navigating within the same view, you can comment this if statement:
if (context.activeView == context.child) {
return false;

Related

How to trigger a function on route-change from a specific route?

I have login component which has the following method:
login() {
this.$v.loginValidationGroup.$touch();
if (this.$v.loginValidationGroup.$error) {
return;
}
this.setLogsInfo();
userService.login(this.email, this.password, this.twoFactorAuthCode, this.rememberMe, this.userOs, this.userIp, this.userAgent, this.browserName)
.then(authenticationToken => {
if(authenticationToken === "2FactorAuthRequired") {
this.is2FAuthEnabled = true;
}
else {
this.$store.dispatch('login', authenticationToken);
this.$router.push('/Home');
}
})
.catch(error => {
if (error instanceof AuthenticationError && error.errorType === AuthErrorType.WRONG_CREDENTIALS) {
this.loginError = 'wrongLoginCredentials';
} else if (error instanceof ValidationError) {
this.loginError = 'invalidLoginCredentials';
} else {
this.loginError = 'unknownLoginError';
}
this.$v.$reset();
});
},
After login the user is redirected to the Home component.
On my Home component I have made a modal that contains a welcome message:
<template>
<div class="modal v-model="visible">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Welcome</h5>
</div>
<div class="modal-body">
some text....
</div>
</div>
</div>
</div>
</template>
Is there any way I can tell the application to set the v-model "visible" to true when routing from the Login component to the Home component?
Mind you I ONLY want the v-model be set to true when entering the page from the Login compoment,not any other component.
Basically, you can make use of in-component navigation guard called "beforeRouteEnter". In it, you have access to the route which the router navigated from. You check if the from route is the login route, then set the visible variable through vm component instance provided by next function (remember in this navigation guard you don't have access to this component instance)
Home component:
data(){
return {
visible: false
}
},
beforeRouteEnter(to, from, next) {
next((vm) => {
if (from.path === "/login") {
vm.visible = true;
}
});
},
In your Home.vue component, you can set a router watcher which would have to and from params to check the previous and current routes.
Here is an example-
Home.vue
watch: {
$route(to, from) {
if(from.name == "Login") {
this.visible = true;
}
}
}

Blazor server component isn't rerendered

I have a Blazor Server (.NETv5) application with a search page.
On this page I have a form to search by name.
On the form submit event I call the search method of my child component.
That component is doing the actual search. This is working fine.
Because the search might take a few seconds I want to show a spinner when the search starts and hide it when the search is done.
Also when I do a second search I want to hide the previous search results.
Hiding the spinner and showing the search results is working, but showing the spinner before the search doesn't work. The variable is set correctly but the page is not rerendered (I think).
My page:
<div class="container pt-2 mb-3">
<RadzenTemplateForm Data="#searchTerms" Submit="#((SearchTerms args) => { Submit(args); })">
<div class="row">
<div class="mb-2 col-6 pl-0">
<RadzenLabel Text="Name" />
<RadzenTextBox class="col-12" Name="Name" #bind-Value="searchTerms.Name"/>
</div>
</div>
<div class="row">
<div class="col-md-12 mt-3">
<RadzenButton ButtonType="ButtonType.Submit" Icon="search" Text="Search" Disabled="#isSearching" />
<RadzenButton ButtonStyle="ButtonStyle.Light" Icon="cancel" Text="Cancel" Click="#Cancel" class="ml-2"/>
</div>
</div>
</RadzenTemplateForm>
</div>
<SearchResultsComponent #ref="searchResultsComponent" />
protected SearchTerms searchTerms = new();
protected SearchResultsComponent searchResultsComponent;
protected bool isSearching;
protected void Submit(SearchTerms args)
{
if (string.IsNullOrEmpty(args.Name)) return;
// Disable submit button ==> NOT WORKING:
isSearching = true;
// Call search method on child component
searchResultsComponent.Search(args);
// Enable submit button again:
isSearching = false;
}
protected void Cancel()
{
// Reset form:
searchTerms = new SearchTerms();
}
My child component:
<div class="container">
#if (isSearching)
{
<div class="spinner-border text-primary mr-2" role="status">
<span class="sr-only">Searching...</span>
</div>
<b>Searching ...</b>
}
#if (noResults)
{
<div class="alert alert-warning" role="alert">
No results.
</div>
}
#if (getSearchResults != null && getSearchResults.Any())
{
<RadzenHeading Size="H2" Text=#($"Results({getSearchResults.Count})")></RadzenHeading>
<div class="row">
#foreach (var searchResult in getSearchResults)
{
<RadzenCard>
<b>#searchResult.Name</b>
</RadzenCard>
}
</div>
}
</div>
private IList<MultiShardSearchResultsWerknemer> _searchResults;
private bool _isSearching = true;
private bool _noResults;
protected bool noResults
{
get => _noResults;
set
{
if (Equals(_noResults, value)) return;
_noResults = value;
InvokeAsync(() => StateHasChanged());
}
}
protected bool isSearching
{
get => _isSearching;
set
{
if (Equals(_isSearching, value)) return;
_isSearching = value;
InvokeAsync(() => StateHasChanged());
}
}
protected IList<MultiShardSearchResultsWerknemer> getSearchResults
{
get => _searchResults;
set
{
if (Equals(_searchResults, value)) return;
_searchResults = value;
InvokeAsync(() => StateHasChanged());
}
}
public void Search(SearchTerms args)
{
Helpers.ConsoleLog(args);
if (string.IsNullOrEmpty(args.Name)) return;
// Reset ==> NOT WORKING:
isSearching = true;
noResults = false;
getSearchResults = null;
InvokeAsync(() => StateHasChanged());
getSearchResults = ShardingService.SearchForAllEmployees(args.Name, null).GetAwaiter().GetResult();
Helpers.ConsoleLog("Found results: " + getSearchResults.Count);
isSearching = false;
noResults = !getSearchResults.Any();
}
For debugging purposes, I've set _isSearching = true which shows me the spinner. The spinner is also hidden when the search is done, so that is working. But I can't get the spinner to show when I start searching.
I've tried all options I could find, without success.
I must be missing something. Please advice.
Have a look at your search handel method
protected void Submit(SearchTerms args)
{
if (string.IsNullOrEmpty(args.Name)) return;
// Disable submit button ==> NOT WORKING:
isSearching = true;
// Call search method on child component
searchResultsComponent.Search(args);
// Enable submit button again:
isSearching = false;
}
Keep in mind, that rendering will occur once the method has finished. So, before the call isSearching is false and after it is also false. That's why you don't see the spinner.
Blazor offers a method to kick off a new render cycle: StateHasChanged().
So, you could modify your submit method like
protected void Submit(SearchTerms args)
{
if (string.IsNullOrEmpty(args.Name)) return;
isSearching = true;
StateHasChanged()
// Call search method on child component
searchResultsComponent.Search(args);
isSearching = false;
StateHasChanged()
}
So, you click the search/submit button on this method is executed.
Or if you like, create a property instead
#code
{
private Boolean isSearching = false;
public Boolean IsSearching
{
get => isSearching;
private set
{
isSearching = value;
StateHasChanged();
}
}
protected void Submit(SearchTerms args)
{
if (string.IsNullOrEmpty(args.Name)) return;
IsSearching = true;
// Call search method on child component
searchResultsComponent.Search(args);
IsSearching = false;
}
}
I haven't tested it but faced a similar problems once.

ag-grid in angular5 row inline edit

I want to know the best way I can give user inline edit in ag-grid on button click.
see the image below.
As per my requirement, if user clicks on edit icon, then ag-grid row goes in fullrow edit mode (able to do from documentation provided onag-grid.com) and at the same time, icons in 'Action' column changes to save and cancel icons. So, want to know how this can be done in Angular5. I need idea of dynamically changing this last column.
There's quite a few steps here that you'll need to implement.
Step 1: Create a custom renderer component
#Component({
selector: 'some-selector',
template: `
<span *ngIf="!this.isEditing">
<button (click)="doEdit()">Edit</button>
</span>
<span *ngIf=this.isEditing">
<button (click)="doSave()">Save</button>
<button (click)="doCancel()">Cancel</button>
</span>
`
})
export class MyRenderer implements ICellRendererAngularComp {
isEditing = false;
params: any;
agInit(params: any): void {
this.params = params;
}
doEdit() {
// we need to loop thru all rows, find the column with the renderer, and 'cancel the edit mode'.
// otherwise, we will end up with rows that has SAVE buttons appearing but not in edit mode
const renderersInOtherRows = this.params.api.getCellRendererInstances(this.params);
if( renderersInOtherRows && renderersInOtherRows.length > 0 ) {
for ( let i=0; i<renderersInOtherRows.length; i++) {
const wrapper = renderersInOtherRows[i];
if ( wrapper.getFrameworkComponentInstance() instanceof MyRenderer ) {
const foundRenderer = wrapper.getFrameworkComponentInstance() as MyRenderer;
if( foundRenderer.isEditing ) {
foundRenderer.doCancel();
}
}
}
}
this.isEditing = true;
this.params.api.startEditingCell( { rowIndex: this.params.node.rowIndex, colKey: 'some-col-field-name'});
}
doCancel() {
this.isEditing = false;
// restore previous data or reload
}
doSave() {
this.isEditing = false;
// do your saving logic
}
}
Step 2: Load the component
#NgModule({
imports:[
AgGridModule.withComponents([MyRenderer]),
// ...
],
declarations: [MyRenderer],
})
export class MyModule();
Step 3: Use the component
SuppressClickEdit = true, will prevent double/single click edit mode
#Component({
selector: 'my-grid',
template: `
<ag-grid-angular #grid
style="width: 100%; height: 500px;"
class="ag-theme-balham"
[rowData]="this.rowData"
[columnDefs]="this.columnDefs"
[editType]="'fullRow'"
[suppressClickEdit]="true"></ag-grid-angular>
`
})
export class MyGridComponent implements OnInit {
columnDefs = [
// ... other cols
{ headerName: '', cellRendererFramework: MyRenderer }
];
}
I was just looking for something similiar and so I thought I would share what I did to get this working. I am new to Angular so this may not be the best way.
This is in my component.html
<button (click)="onEdit()">edit button</button>
<button (click)="onSaveEdit()" *ngIf="!editClicked">save button</button>
<button (click)="onCancelEdit()" *ngIf="!editClicked">cancel</button>
This is in my component.ts
public params: any;
private editClicked;
constructor() {
this.editClicked = true;
}
agInit(params: any): void{
this.params = params;
}
onEdit() {
this.editClicked = !this.editClicked;
this.params.api.setFocusedCell(this.params.node.rowIndex, 'action');
this.params.api.startEditingCell({
rowIndex: this.params.node.rowIndex,
colKey: 'action'
});
}
onSaveEdit() {
this.params.api.stopEditing();
this.editClicked = !this.editClicked;
}
onCancelEdit() {
this.params.api.stopEditing(true);
this.editClicked = !this.editClicked;
}
Hope this helps steer you in the right direction.

Vue.js check if scroller scrolled to the element

I have an element in my html code
<div class="my_div">
</div>
Now i want to alert when user scrolled to this element.How can i do that using Vue considering that my_div is in the center of the page?
You should use the vue $ref.
HTML
<div id="PAGE-MAIN-PARENT-DIV" v-scroll="handleScroll">
<div ref="my_div" > </div>
</div>
JS
Vue.directive('scroll', {
inserted: function (el, binding) {
let f = function (evt) {
if (binding.value(evt, el)) {
window.removeEventListener('scroll', f)
}
}
window.addEventListener('scroll', f)
}
})
Inside Vue methods:
methods: {
handleScroll (evt, el) {
if (!this.$refs.my_div) {
return false
}
const emissionYOfsset = this.$refs.my_div.$el.offsetTop
// Change this condition in order to fit your requirements
if (window.scrollY + window.innerHeight >= emissionYOfsset &&
window.scrollY <= emissionYOfsset) {
console.log('currently in screen')
// Do you want to continue with the event?
return false
}
}
}

Cannot get deactivate function to fire in durandal with deeplinking

I am new to durandal and single page apps, I am having issues getting the deactivate and canDeactivate function to fire. I am using some custom code to achieve deep linking, which is probably what is causing my issue.
I followed the example here: https://github.com/evanlarsen/DurandalDeepLinkingExample
please also see Durandal Subrouting (Hottowel)
Any help would be most appreciated.
Here is the viewmodel code I am calling:
define([''], function () {
var vm = {
activate: function () {
alert('In activate!');
},
deactivate: function () {
alert('In deactivate!');
},
canDeactivate: function () {
alert('In candeactivate!');
}
}
return vm;
});
Here is the viewhtml
<div class="container-fixed">
<div>
<header>
<!-- ko compose: {view: 'Main/Users/UsersNav'} -->
<!-- /ko-->
</header>
<section id="content" class="in-users">
<!--ko compose: {
model: inUsers, afterCompose: router.afterCompose,
transition: 'entrance',
activate: true
} -->
<!--/ko-->
</section>
</div>
</div>
Here is the calling code:
define(['durandal/system', 'durandal/viewModel', 'durandal/plugins/router'],
function (system, viewModel, router) {
var App = {
router: router,
activate: activate,
showPage: showPage,
isPageActive: isPageActive,
inUsers: viewModel.activator(),
};
return App;
var defaultPage = '';
function activate(activationData) {
defaultPage = 'ManageUsers';
App.inUsers(convertSplatToModuleId(activationData.splat));
router.activeItem.settings.areSameItem = function (currentItem, newItem, data) {
if (currentItem != newItem) {
return false;
}
else {
App.inUsers(convertSplatToModuleId(data.splat));
return true;
}
};
}
function showPage(name) {
return function () {
router.activate('#/Users/' + name);
//router.navigateTo('#/Users/' + name);
App.inUsers(convertNameToModuleId(name));
};
}
function isPageActive(name) {
var moduleName = convertNameToModuleId(name);
return ko.computed(function () {
return App.inUsers() === moduleName;
});
}
// Internal methods
function convertNameToModuleId(name) {
return 'Main/Users/' + name + '/' + name;
}
function convertSplatToModuleId(splat) {
if (splat && splat.length > 0) {
return convertNameToModuleId(splat[0]);
}
return convertNameToModuleId(defaultPage);
}
});
EDIT: (Main master page)
function activate() {
// my convention
router.autoConvertRouteToModuleId = function (url) {
return 'Main/' + url + '/index';
};
return router.activate('Home');
}
Nav HTML for master:
<div class="btn-group">
HOME
RESOURCES
USERS
</div>
Main master:
<div class="container-fixed">
<div>
<header>
<!-- ko compose: {view: 'Main/masterNav'} -->
<!-- /ko-->
</header>
<section id="content" class="main">
<!--ko compose: {model: router.activeItem,
afterCompose: router.afterCompose,
transition: 'entrance'} -->
<!--/ko-->
</section>
<footer>
<!--ko compose: {view: 'Main/masterFooter'} --><!--/ko-->
</footer>
</div>
</div>
The issue you are running into about not being able to deactivate your sub-routed views is because the viewmodel.activator() observable, that is returned from that method, enforces the activator pattern in durandal. That observable is expecting a amd module and not a string.
Even though the string works fine because the compose binding knows how to load modules based off of the string.. the viewmodel activator doesn't know how to load modules from strings.
So, you need to pass the actually module to the observable.
The example I created before just used a string so it will compose the view.. but the activator pattern doesnt work if there is just a string. So, instead you will have to require all your sub-route amd modules into your calling code and then instead of using the
convertSplatToModuleId method.. create a new method that returns the correct module.
So, something like this:
define(['durandal/system', 'durandal/viewModel', 'durandal/plugins/router'],
function (system, viewModel, router) {
var App = {
router: router,
activate: activate,
showPage: showPage,
isPageActive: isPageActive,
inUsers: viewModel.activator(),
};
return App;
var defaultPage = '';
function activate(activationData) {
defaultPage = 'ManageUsers';
convertSplatToModuleId(activationData.splat).then(function(activeModule){
App.inUsers(activeModule);
})
router.activeItem.settings.areSameItem = function (currentItem, newItem, data) {
if (currentItem != newItem) {
return false;
}
else {
convertSplatToModuleId(data.splat).then(function (module) {
App.inUsers(module);
});
return true;
}
};
}
function showPage(name) {
return function () {
router.activate('#/Users/' + name);
//router.navigateTo('#/Users/' + name);
convertNameToModuleId(name).then(function(module){
App.inUsers(module);
});
};
}
// Internal methods
function convertNameToModuleId(name) {
return system.acquire('Main/Users/' + name + '/' + name);
}
function convertSplatToModuleId(splat) {
if (splat && splat.length > 0) {
return convertNameToModuleId(splat[0]);
}
return convertNameToModuleId(defaultPage);
}
});