I've implemented a login dialog for an application. When the "login" button is clicked, the UI is disabled and a spinner is shown while the login happens. Also, if the user has previously logged in, the app can use a saved token to automatically log in again.
To implement this, I have created two commands. One command for the user-initiated login, and one command for the automatic login. This is so I can observe the IsExecuting Observable for both commands, something like this
_isExecuting = this.WhenAnyObservable(
x => x.CmdLogin.IsExecuting,
x => x.CmdAutoLogin.IsExecuting)
.ToProperty(this, x => x.IsExecuting);
The IsExecuting viewmodel property is then bound to the Isnabled property of the view. This is working and the UI behaves perfectly, but it feels very unclean having two commands. Also, I am triggering the automatic login in the viewmodel like so:
this.WhenActivated((Action<IDisposable> disposer) =>
{
(CmdAutoLogin as System.Windows.Input.ICommand).Execute(null);
});
My question is, what is a cleaner way to do this? Can I do this without having two commands? Cheers.
You don't need to cast to ICommand.
Import this namespace:
using System.Reactive.Linq;
Then just await a command:
await CmdAutoLogin.Execute();
Or use the Adrian Romero way:
Observable.Return(Unit.Default).InvokeCommand();
I think your approach its ok, I am on a similiar situation and I have AutoLogin and Login on separate commands, If you think about it, auto login and login are different things, at least to me. The only thing I'd do different would be to put
CmdAutoLogin execution on the view and not in de view model:
this.WhenActivated(disposables =>
{
Observable.Return(Unit.Default).InvokeCommand(ViewModel.CmdAutoLogin);
});
Related
When I try to, for example, click on a button using Cypress, the get command will get the button before it is actionable (still invisible for example). The click command later will fail because the subject passed to it is not actionable. How to avoid this behavior?
Try adding a visibility assertion
cy.get('#button')
.should('be.visible')
.click();
You can add visibility check as well as make sure the button is enabled and then perform a click().
cy.get('selector').should('be.visible').and('be.enabled').click()
I won't suggest you to overwrite an existing cypress command, instead create a custom command under cypress/support/commands.js like this:
Cypress.Commands.add('waitAndClick', (selector) => {
cy.get(selector).should('be.visible').and('be.enabled').click()
})
And in your test you can add:
cy.waitAndClick('button')
Background:
I'm writing test automation for a React web application, using Cypress. In the application I have a dialog box in which there are elements I need to click. When I try to click any of these elements normally, Cypress gives me an error that the element in not visible because its content is being clipped by one of its parent elements, which has a CSS property of overflow: 'hidden', 'scroll' or 'auto'. Because these DOM elements are generated by some 3rd party React components, I cannot change this, and the only way I can work-around it is to use {force:true} in the click command.
The problem:
Because I have few of these elements and in order to keep the DRY principle, I wanted to create a custom child command named forceClick that simply wraps subject.click({force:true}). However, for some reason, when I do that, Cypress does not perform the click command at all!
Note: For debugging purposes I added a cy.log command to the custom command as well, and strangely enough, I see that this log command is executed and only the click command doesn't.
Here's the code:
Cypress.Commands.add('forceClick', {prevSubject:'element'}, subject => {
cy.log('forceClick was called!');
subject.click({force:true})});
And inside my test I have the following line:
cy.get("[data-test='panel-VALUES']").forceClick();
Note that if I change it to the following line, it works as expected:
cy.get("[data-test='panel-VALUES']").click({force:true});
Any idea why the click command isn't executed by the forceClick custom command?
You are almost there, you just missed that you have to wrap the subject if you want to work with it.
Cypress.Commands.add('forceClick', {prevSubject: 'element'}, (subject, options) => {
// wrap the existing subject and do something with it
cy.wrap(subject).click({force:true})
})
I never saw a solution with subject.click({force:true}), I'm not saying it won't work, but I just never saw it before. What works anyway is this:
Custom command:
Cypress.Commands.add('forceClick', {prevSubject:'element'}, subject => {
cy.log('forceClick was called!');
cy.get(subject)
.click({force:true})});
}
Test step:
cy.forceClick('[data-test="panel-VALUES"]');
If you only use the forceClick you could even shorten it further to this:
Custom command:
Cypress.Commands.add('forceClick', {prevSubject:'element'}, subject => {
cy.log('forceClick was called!');
cy.get(`[data-test=${subject}]`)
.click({force:true})});
}
Test step:
cy.forceClick('panel-VALUES');
I am doing web development in Yii framework and PHP. I am writing a login page. On this page, there are two forms. I used the attributeLabels() function to set the labels for these two forms. One is User Name. One is Password. We can call this kind of labels as hints or placeholder text.
After I logged in my system on safari and clicked the button of "saving password", then the system stored my user name and password. The hint and the saved password overlapped when I came to the login page again.
I have no idea how to fix this bug. I searched the Internet but did not find any useful suggestions.
Thank you in advance.
public function attributeLabels()
{
return array(
'userName' => 'Player/User Name',
'password' => 'Password',
);
}
I am not familiar with this particular framework, but it sounds like you mean the placeholder text that displays in the password field, and the saved password dots or stars that appear when your browser inputs the password for you are overlapping.
This will happen when you are using javascript to place and remove the placeholder text, usually done for browser compatibility with the standard placeholder attribute. it is probably being triggered on focus or key press in that field, but since the browser doesn't trigger those events when its placing your password in the field it does not remove the placeholder text.
Changing this to a trigger that repeatedly checks that there is something in the value attribute, or something like that will make sure that it will remove that text when there is a value to the field.
I am using the following directive to create a ckEditor view. There are other lines to the directive to save the data but these are not included as saving always works for me.
app.directive('ckEditor', [function () {
return {
require: '?ngModel',
link: function ($scope, elm, attr, ngModel) {
var ck = ck = CKEDITOR.replace(elm[0]);
ngModel.$render = function (value) {
ck.setData(ngModel.$modelValue);
setTimeout(function () {
ck.setData(ngModel.$modelValue);
}, 1000);
}; }
};
}])
The window appears but almost always the first time around it is empty. Then after clicking the [SOURCE] button to show the source and clicking it again the window is populated with data.
I'm very sure that the ck.setData works as I tried a ck.getData and then logged the output to the console. However it seems like ck.setData does not make the data visible at the start.
Is there some way to force the view window contents to appear?
You can call render on the model at any time and it will simply do whatever you've told it to do. In your case, calling ngModel.$render() will grab the $modelValue and pass it to ck.setData(). Angular will automatically call $render whenever it needs to during its digest cycle (i.e. whenever it notices that the model has been updated). However, I have noticed that there are times when Angular doesn't update properly, especially in instances where the $modelValue is set prior to the directive being compiled.
So, you can simply call ngModel.$render() when your modal object is set. The only problem with that is you have to have access to the ngModel object to do that, which you don't have in your controller. My suggestion would be to do the following:
In your controller:
$scope.editRow = function (row, entityType) {
$scope.modal.data = row;
$scope.modal.visible = true;
...
...
// trigger event after $scope.modal is set
$scope.$emit('modalObjectSet', $scope.modal); //passing $scope.modal is optional
}
In your directive:
ngModel.$render = function (value) {
ck.setData(ngModel.$modelValue);
};
scope.$on('modalObjectSet', function(e, modalData){
// force a call to render
ngModel.$render();
});
Its not a particularly clean solution, but it should allow you to call $render whenever you need to. I hope that helps.
UPDATE: (after your update)
I wasn't aware that your controllers were nested. This can get really icky in Angular, but I'll try to provide a few possible solutions (given that I'm not able to see all your code and project layout). Scope events (as noted here) are specific to the nesting of the scope and only emit events to child scopes. Because of that, I would suggest trying one of the three following solutions (listed in order of my personal preference):
1) Reorganize your code to have a cleaner layout (less nesting of controllers) so that your scopes are direct decendants (rather than sibling controllers).
2) I'm going to assume that 1) wasn't possible. Next I would try to use the $scope.$broadcast() function. The specs for that are listed here as well. The difference between $emit and $broadcast is that $emit only sends event to child $scopes, while $broadcast will send events to both parent and child scopes.
3) Forget using $scope events in angular and just use generic javascript events (using a framework such as jQuery or even just roll your own as in the example here)
There's a fairly simple answer to the question. I checked the DOM and found out the data was getting loaded in fact all of the time. However it was not displaying in the Chrome browser. So the problem is more of a display issue with ckEditor. Strange solution seems to be to do a resize of the ckEditor window which then makes the text visible.
This is a strange issue with ckeditor when your ckeditor is hidden by default. Trying to show the editor has a 30% chance of the editor being uneditable and the editor data is cleared. If you are trying to hide/show your editor, use a css trick like position:absolute;left-9999px; to hide the editor and just return it back by css. This way, the ckeditor is not being removed in the DOM but is just positioned elsewhere.
Use this java script code that is very simple and effective.Note editor1 is my textarea id
<script>
$(function () {
CKEDITOR.timestamp= new Date();
CKEDITOR.replace('editor1');
});
</script>
Second way In controller ,when your query is fetch data from database then use th
is code after .success(function().
$http.get(url).success(function(){
CKEDITOR.replace('editor1');
});
I know, that this thread is dead for a year, but I got the same problem and I found another (still ugly) solution to this problem:
instance.setData(html, function(){
instance.setData(html);
});
In grails, I use the mechanism below in order to implement what I'd call a conditional server-side-triggered dialog: When a form is submitted, data must first be processed by a controller. Based on the outcome, there must either be a) a modal Yes/No confirmation in front of the "old" screen or b) a redirect to a new controller/view replacing the "old" screen (no confirmation required).
So here's my current approach:
In the originating view, I have a <g:formRemote name="requestForm" url="[controller:'test', action:'testRequest']", update:"dummyRegion"> and a
<span id="dummyRegion"> which is hidden by CSS
When submitting the form, the test controller checks if a confirmation is necessary and if so, renders a template with a yui-based dialog including Yes No buttons in front of the old screen (which works fine because the dialog "comes from" the dummyRegion, not overwriting the page). When Yes is pressed, the right other controller & action is called and the old screen is replaced, if No is pressed, the dialog is cancelled and the "old" screen is shown again without the dialog. Works well until here.
When submitting the form and test controller sees that NO confirmation is necessary, I would usually directly redirect to the right other controller & action. But the problem is that the corresponding view of that controller does not appear because it is rendered in the invisble dummyRegion as well. So I currently use a GSP template including a javascript redirect which I render instead. However a javascript redirect is often not allowed by the browser and I think it's not a clean solution.
So (finally ;-) my question is: How do I get a controller redirect to cause the corresponding view to "break out" of my AJAX dummyRegion, replacing the whole screen again?
Or: Do you have a better approach for what I have in mind? But please note that I cannot check on the client side whether the confirmation is necessary, there needs to be a server call! Also I'd like to avoid that the whole page has to be refreshed just for the confirmation dialog to pop up (which would also be possible without AJAX).
Thanks for any hints!
I know, it's not an "integrated" solution, but have you considered to do this "manually" with some JS library of your choice (my personal choice would be jQuery, but any other of the established libraries should do the trick)? This way you wouldn't depend on any update "region", but could do whatever you want (such as updating any DOM element) in the response handler of the AJAX request.
Just a thought. My personal experience is that the "built-in" AJAX/JS stuff in Grails often lacks some flexibility and I've always been better off just doing everything in plain jQuery.
This sounds like a good use-case for using web flows. If you want to show Form A, do some kind of check, and then either move onto NextScreen or show a Dialog that later redirects to NextScreen, then you could accomplish this with a flow:
def shoppingCartFlow = {
showFormA {
on("submit") {
if(needToShowDialog())return
}.to "showNextScreen"
on("return").to "showDialog"
}
showDialog {
on("submit").to "showNextScreen"
}
showNextScreen {
redirect(controller:"nextController", action:"nextAction")
}
}
Then you create a showDialog.gsp that pops up the dialog.
--EDIT--
But, you want an Ajax response to the first form submit, which WebFlow does not support. This tutorial, though, will teach you how to Ajaxify your web flow.