Hello all,
I am currently building an Umbraco package for a YouTube property editor.
One of the PreValues is a custom and allows the user to enter a username along with a button to query the username/channel on YouTube.
Currently if the username does not exist I use the Umbraco AngularJS NotificationsService to show an error message that the user could not be found.
However it is still possible to save the DataType with this custom PreValue in.
What I would like to do is to prevent the datatype from being saved and giving the user a validation message that they must set a username.
A simple textbox and a required attribute is simply not enough for me, as I am not saving the value of the textbox as the prevalue data in $scope.model.value but instead a JSON object including the channel username/title, channel description, channel thumbnail image & statistics.
Here is my AngularJS Controller for my PreValue for reference:
angular.module("umbraco").controller("YouTube.prevalue.channel.controller", function($scope, YouTubeResource, notificationsService) {
//Set to be default empty object or value saved if we have it
$scope.model.value = $scope.model.value ? $scope.model.value : null;
if ($scope.model.value) {
//Have a value - so lets assume our JSON object is all good
//Debug message
console.log("Scope Model Value on init", $scope.model.value);
//As we have JSON value on init
//Let's set the textbox to the value of the querried username
$scope.username = $scope.model.value.querriedUsername;
}
$scope.queryChannel = function(username) {
//Debug info
console.log("Query Channel Click", username);
//Query this via our resource
YouTubeResource.queryUsernameForChannel(username).then(function(response) {
//Debug info
console.log("Value back from query API", response);
console.log("Items length", response.data.items.length);
//Only do this is we have a result back from the API
if (response.data.items.length > 0) {
//Data we are interested is in
//response.data.items[0]
var channel = response.data.items[0];
//Create new JSON object as we don't need full object from Google's API response
var newChannelObject = {
"querriedUsername": username,
"channelId": channel.id,
"title": channel.snippet.title,
"description": channel.snippet.description,
"thumbnails": channel.snippet.thumbnails,
"statistics": channel.statistics
};
//Set the value to be our new JSON object
$scope.model.value = newChannelObject;
} else {
//Fire a notification - saying user can not be found
notificationsService.error("YouTube User Lookup", "The channel/user '" + username + "' could not be found on YouTube");
//Set the value to be empty
$scope.model.value = null;
}
});
};
});
If anyone can offer any pointers or advice on how best to do this, that would be fantastic.
Not 100% but I think you can add a event handler to the submit of the form do you custom validation if successful the form submit would continue as normal.
Hiya.
Thanks for the replies. Kasper do you have an example with a directive & custom validation please or how you would go about approaching my problem.
Matthew, do you have an example of this event I can listen to with an .On() event hook function, to do what I need to?
So go with a custom directive is the consensus?
I thought if the Umbraco guys are using a form I can just set the validity or am I totally wrong with this?!
You don't have to validation via a directive, rather hooking into the formatters/parsers to do validation is the idea. Set the validity and the rest should just work.
I'm all for directives, they work great on client side validation and you can write them super generic to reuse them across your views. However, I think you'll find yourself in a pickle trying to do what you're trying to do with a directive. Why? Because formatters, parsers, watches and the like all fire when the value changes. You don't want that, because then you'll end up querying the YouTube resource for each letter typed in your text input. I'm guessing that's also why you have your "validate" button.
I think you'll have to roll with some controller logic for this one. I'd like to be proven wrong on this point (please, someone?), but generally when I have to validate multiple fields against each other or do some server side validation with $http, I almost always find that directives either fail me or plain simply just over-complicates the code without giving me the benefit of reuse-ability, and I have to revert back to using controller logic.
As for preventing the users from saving an invalid username/channel configuration, if everything else fails you can always use the angularHelper to invalidate the entire form like this:
@Kevin yuss you could do that, but wouldn't you still end up sending X requests to YouTube before you hit a positive validation result?
In this particular case I guess one could also add an onBlur directive to the mix and use the blur event from the input field to perform the validation in a controller method (ng-blur doesn't work in the version of Angular used in the Umbraco backend, but I have a directive implementation of onBlur and onFocus somewhere, if anyone wants it).
Kenn I have considered the onblur of the field to do the YouTube lookup & validation at the same time. I am not 100% keen on this and like the action of the user confirming they want to check YouTube for that username.
How about a non-bound textbox that takes a username. Click a button (which fires a youtube request). Then if successful, save a boolean to the model a value that says the the youtube was valid. Use parsers/formatters to check boolean to validate. Save username as well to the model if ya like.
@Kevin that does sound a good solution, but as you just have replied I have a solution that is working and I am keen to see what everyone thinks of this please.
angular.module("umbraco").controller("YouTube.prevalue.channel.controller", function ($scope, YouTubeResource, notificationsService, angularHelper) {
//Set to be default empty object or value saved if we have it
$scope.model.value = $scope.model.value ? $scope.model.value : null;
if($scope.model.value){
//Have a value - so lets assume our JSON object is all good
//Debug message
console.log("Scope Model Value on init", $scope.model.value);
//As we have JSON value on init
//Let's set the textbox to the value of the querried username
$scope.username = $scope.model.value.querriedUsername;
}
$scope.queryChannel = function(username) {
//Debug info
console.log("Query Channel Click", username);
//Default flag for validity
var isThisValid = false;
//Query this via our resource
YouTubeResource.queryUsernameForChannel(username).then(function(response) {
//Debug info
console.log("Value back from query API", response);
console.log("Items length", response.data.items.length);
//Only do this is we have a result back from the API
if(response.data.items.length > 0){
//Data we are interested is in
//response.data.items[0]
var channel = response.data.items[0];
//Create new JSON object as we don't need full object from Google's API response
var newChannelObject = {
"querriedUsername": username,
"channelId": channel.id,
"title": channel.snippet.title,
"description": channel.snippet.description,
"thumbnails": channel.snippet.thumbnails,
"statistics": channel.statistics
};
//Set the value to be our new JSON object
$scope.model.value = newChannelObject;
//Set our flag to true
isThisValid = true;
}
else {
//Fire a notification - saying user can not be found
notificationsService.error("YouTube User Lookup","The channel/user '" + username + "' could not be found on YouTube");
//Set the value to be empty
$scope.model.value = null;
//Ensure flag is set to false
isThisValid = false;
}
//Get the form with Umbraco's helper of this $scope
//The form is wrapped just around this single prevalue editor
var form = angularHelper.getCurrentForm($scope);
//Inside the form we have our input field with the name/id of username
//Set this field to be valid or invalid based on our flag
form.username.$setValidity('YouTubeChannel', isThisValid);
//Debug
console.log("Form", form);
console.log("Form Username", form.username);
console.log("Is this Valid?", isThisValid);
});
};
});
Your solution will surely work. What bugs me a little bit about it is the line form.username.$setValidity('YouTubeChannel', isThisValid);. It implies that your controller knows about the structure/implementation of your form/markup.
Then again, the alternative is a fair bit of extra code just to be rid of this binding, it might not really be worth it. But for arguments sake, here's an example of an alternative solution, where the directive performs validation of the form element it's bound to.
Controller:
angular.module("umbraco").controller("Something.Config.Controller",
function ($scope, angularHelper) {
// set the model for the input field
$scope.model.somethingToValidate = $scope.model.value;
$scope.validate = function () {
// validate the input field value
var isValid = $scope.model.somethingToValidate != null
&& $scope.model.somethingToValidate != ""
&& $scope.model.somethingToValidate != "invalid";
// update the model value according to the validation result
if (isValid) {
$scope.model.value = $scope.model.somethingToValidate
}
else {
$scope.model.value = null;
}
// debug info
console.log("Controller says", isValid, $scope.model.value);
}
});
Directive:
angular.module("umbraco").directive("somethingValidate",
function () {
return {
restrict: "A",
require: "ngModel",
link: function (scope, element, attr, ctrl) {
ctrl.$parsers.unshift(function (viewValue) {
return validateSomething(viewValue);
});
ctrl.$formatters.unshift(function (viewValue) {
return validateSomething(viewValue);
});
scope.$watch("model.value", function (v) {
// need to register a watch on model value to re-validate once the validated value changes on scope
validateSomething(v);
});
function validateSomething(viewValue) {
// it's valid if the view has a value and that value is equal to the currently validated value on scope
var isValid = viewValue != null && scope.model.value == viewValue;
// set the control validity accordingly
ctrl.$setValidity("something", isValid);
// debug info
console.log("Directive says", isValid, scope.model.value);
return viewValue;
}
}
};
});
An added value (or annoyance?) of this is that the field invalidates when the user changes the field value, and remains invalid until the controller has explicitly validated the field value.
Hi Kenn,
I see your point with this and may refactor to do this at some point.
I followed the same pattern that the core guys have done for validating min & max for the content picker, that has hidden input fields in the view and sets their validity in the same way based on the form name and input name.
I need to do something similar for the main editor to ensure a user can select a min or max or both YouTube video items. Would it be best to replicate this or is the best idea to go with directives for everything for this?
From a purist point of view I'm sure you should use directives in an effort to eliminate the controller's knowledge of the view implementation. From a practical point of view, I'm not so sure. Both solutions have obvious drawbacks. Both solutions work. That being said, the code you've shown above is really the same code twice with a tiny twist (greater than versus less than), and could easily have been turned into a directive that would eliminate the need for maintaining the same code two times over.
Come to think of it, I'm pretty sure that code should have been turned into a directive, but I'd have to read it in context to be sure.
In the end I think you should go with the solution that makes you feel most comfortable and that you feel best equipped for supporting. At least when you make a choice of one over the other, you're making a kind of informed choice now :)
I won't be able to follow this very actively for a little while, so I hope you'll find the solution that works best for you somewhere in all of this. Best of luck to you!
Hello all,
To add more to the confusion & choice of options my colleague Ale, at work come across serverValidationManager.
So it is possible to give a better contextual error message than firing the notificationsService.
This is the line I have added to give it a better contextual error message rather than this 'Property has errors'
//Property Alias, Field name (ID/name of text box), Error Message
serverValidationManager.addPropertyError($scope.model.alias, "username", "The user '" + username + "' could not be found on YouTube");
Again I have stumbled across another approach with the help of my colleague, I really would love someone from the Umbraco core team to validate & go through the different options for validation, with when & where to use approach. For anyone following along with this thread, here is my updated funtion in my controller for the query of the username when the styled button is clicked.
V7 Custom PreValue with custom Validation
Hello all,
I am currently building an Umbraco package for a YouTube property editor.
One of the PreValues is a custom and allows the user to enter a username along with a button to query the username/channel on YouTube.
Currently if the username does not exist I use the Umbraco AngularJS NotificationsService to show an error message that the user could not be found.
However it is still possible to save the DataType with this custom PreValue in.
What I would like to do is to prevent the datatype from being saved and giving the user a validation message that they must set a username.
A simple textbox and a required attribute is simply not enough for me, as I am not saving the value of the textbox as the prevalue data in $scope.model.value but instead a JSON object including the channel username/title, channel description, channel thumbnail image & statistics.
Here is my AngularJS Controller for my PreValue for reference:
If anyone can offer any pointers or advice on how best to do this, that would be fantastic.
Cheers,
Warren
Not 100% but I think you can add a event handler to the submit of the form do you custom validation if successful the form submit would continue as normal.
Hi warren
have you look into a custom Directives, thats how i ended up making my custom validation ? :)
https://docs.angularjs.org/guide/directive
Hiya.
Thanks for the replies. Kasper do you have an example with a directive & custom validation please or how you would go about approaching my problem.
Matthew, do you have an example of this event I can listen to with an .On() event hook function, to do what I need to?
Thanks,
Warren
Yes i do, this is added on my controller, and it basicly just checks that if there is adeed any items do the value :)
.directive('daysAdded', function () {
Comment author was deleted
+1 to ==^ answer
So go with a custom directive is the consensus? I thought if the Umbraco guys are using a form I can just set the validity or am I totally wrong with this?!
Cheers,
Warren
Comment author was deleted
Just to add on to Twitter comments...
You don't have to validation via a directive, rather hooking into the formatters/parsers to do validation is the idea. Set the validity and the rest should just work.
https://github.com/imulus/Archetype/blob/master/app/directives/archetypeproperty.js#L153
Hiya all,
I'm all for directives, they work great on client side validation and you can write them super generic to reuse them across your views. However, I think you'll find yourself in a pickle trying to do what you're trying to do with a directive. Why? Because formatters, parsers, watches and the like all fire when the value changes. You don't want that, because then you'll end up querying the YouTube resource for each letter typed in your text input. I'm guessing that's also why you have your "validate" button.
I think you'll have to roll with some controller logic for this one. I'd like to be proven wrong on this point (please, someone?), but generally when I have to validate multiple fields against each other or do some server side validation with $http, I almost always find that directives either fail me or plain simply just over-complicates the code without giving me the benefit of reuse-ability, and I have to revert back to using controller logic.
As for preventing the users from saving an invalid username/channel configuration, if everything else fails you can always use the angularHelper to invalidate the entire form like this:
It's hacky, but it'll work if you can't get to the input control(s) that are invalid.
-Kenn
Comment author was deleted
@Kenn,
I don't disagree with your reasoning, but couldn't you just simply check a flag before sending a request to YouTube?
i.e.
function validate(){
if(!alreadyValidated){
//check YouTube
alreadyValidated = true;
}
}
@Kevin yuss you could do that, but wouldn't you still end up sending X requests to YouTube before you hit a positive validation result?
In this particular case I guess one could also add an onBlur directive to the mix and use the blur event from the input field to perform the validation in a controller method (ng-blur doesn't work in the version of Angular used in the Umbraco backend, but I have a directive implementation of onBlur and onFocus somewhere, if anyone wants it).
Kenn I have considered the onblur of the field to do the YouTube lookup & validation at the same time. I am not 100% keen on this and like the action of the user confirming they want to check YouTube for that username.
So the entire form invalidation may be the best, or would it be best to do a directive in replace of ng-click on the I have, that does the same for me instead?
Cheers,
Warren :)
Comment author was deleted
@Kenn good observation.
@Warren
How about a non-bound textbox that takes a username. Click a button (which fires a youtube request). Then if successful, save a boolean to the model a value that says the the youtube was valid. Use parsers/formatters to check boolean to validate. Save username as well to the model if ya like.
@Kevin that does sound a good solution, but as you just have replied I have a solution that is working and I am keen to see what everyone thinks of this please.
Hi Warren.
Your solution will surely work. What bugs me a little bit about it is the line
form.username.$setValidity('YouTubeChannel', isThisValid);
. It implies that your controller knows about the structure/implementation of your form/markup.Then again, the alternative is a fair bit of extra code just to be rid of this binding, it might not really be worth it. But for arguments sake, here's an example of an alternative solution, where the directive performs validation of the form element it's bound to.
Controller:
Directive:
Usage:
An added value (or annoyance?) of this is that the field invalidates when the user changes the field value, and remains invalid until the controller has explicitly validated the field value.
Hi Kenn, I see your point with this and may refactor to do this at some point.
I followed the same pattern that the core guys have done for validating min & max for the content picker, that has hidden input fields in the view and sets their validity in the same way based on the form name and input name.
https://github.com/umbraco/Umbraco-CMS/blob/7.1.5/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js#L98 https://github.com/umbraco/Umbraco-CMS/blob/7.1.5/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html#L30
I need to do something similar for the main editor to ensure a user can select a min or max or both YouTube video items. Would it be best to replicate this or is the best idea to go with directives for everything for this?
Thanks,
Warren
Uh... blimey, I dunno :)
From a purist point of view I'm sure you should use directives in an effort to eliminate the controller's knowledge of the view implementation. From a practical point of view, I'm not so sure. Both solutions have obvious drawbacks. Both solutions work. That being said, the code you've shown above is really the same code twice with a tiny twist (greater than versus less than), and could easily have been turned into a directive that would eliminate the need for maintaining the same code two times over.
Come to think of it, I'm pretty sure that code should have been turned into a directive, but I'd have to read it in context to be sure.
In the end I think you should go with the solution that makes you feel most comfortable and that you feel best equipped for supporting. At least when you make a choice of one over the other, you're making a kind of informed choice now :)
I won't be able to follow this very actively for a little while, so I hope you'll find the solution that works best for you somewhere in all of this. Best of luck to you!
-Kenn
Hello all,
To add more to the confusion & choice of options my colleague Ale, at work come across serverValidationManager.
So it is possible to give a better contextual error message than firing the notificationsService.
This is the line I have added to give it a better contextual error message rather than this 'Property has errors'
Again I have stumbled across another approach with the help of my colleague, I really would love someone from the Umbraco core team to validate & go through the different options for validation, with when & where to use approach. For anyone following along with this thread, here is my updated funtion in my controller for the query of the username when the styled button is clicked.
is working on a reply...