Is there a way to create a custom fieldtype in angular schema form that is able to sum other fields on the form? I've looked at directives/decorators but am not sure how to reference the fields to be summed?
The easiest way is probably to let the user supply the keys of the fields you'd like to sum up the and watch for changes with a $watchGroup
So an example of how a form definition could look:
{
type: "sum",
sumFields: [
"key.to.field1",
"key.to.field2",
"key.to.field3"
]
}
And then you need a directive in your field type sum to actually sum things up. (WARNING, untested code)
<div class="form-control-group">
<input disabled sum-it-up="form.sumFields" type="text">
</div>
angular.module('myModule').directive('sumItUp',function(){
return {
scope: true,
link: function(scope, element, attr) {
var keys = scope.$eval(attrs.sumItUp);.map(function(key){
// Whatever you put in model is always available on scope
// as model. Skipped ObjectPath for simplification.
return 'model.'+key
})
scope.$watchGroup(keys, function(values) {
element.value(values.reduce(function(sum, value){
return sum+value
},0))
})
}
}
})
You could also do a fieldset type of thing and loop through each item in the items property of the form and go from there. The sfSelect service might also be useful. Hope that helps!
Related
I'm using ng-admin.
I have two dropdown lists in a page, one for Provinces and the other for cities.
I would like to populate choice of cities according to the value of the selected Province field. I can't load the whole list because it is humungous.
What's the best way to do it?
Here is my code:
dipendenti.creationView()
.title('Aggiungi dipendente')
.fields([
nga.field('Nome')
.validation({required: true })
.cssClasses('col-sm-4'),
nga.field('Cognome')
.validation({required: true })
.cssClasses('col-sm-4'),
nga.field('anagrafica.idprovincia','reference')
.label('Provincia')
.targetEntity(admin.getEntity('province'))
.targetField(nga.field('id'))
.targetField(nga.field('nome'))
.validation({required: true })
.cssClasses('col-sm-4'),
nga.field('anagrafica.idcitta','reference')
.label('Città ')
.targetEntity(admin.getEntity('comuni'))
.targetField(nga.field('id'))
.targetField(nga.field('nome'))
.validation({required: true })
.permanentFilters({
provincia: 5}) //<- here shoud be retrieved automatically
// .remoteComplete(true, {
// refreshDelay: 300,
//// populate choices from the response of GET /posts?q=XXX
// searchQuery: function(search) { return { provincia: search }; } //<- here with autocomplete
// })
.cssClasses('col-sm-4')
I wonder if there is a way to inject a scope on the reference or the best practice for ng-admin...otherwise I think I shoud write an Angular directive...which is not exactly MY piece of cake...!
I've looked at the code generated and it is a bit complicated ( I think I should replicate it as the directive's template ) so I'd prefer to find an elegant way to do the job.
Thank you very much...
I have an angularjs application and have to do form validation with custom business rules.
The problem is that my validation rules for a particular input field is dependent on other fields, and I don't know how to trigger the validation other than when the actual modelvalue changes.
The case is a dynamic list of employees each with a dynamic list of times of day to be entered. One rule is that these times must not overlap, which means one value can be invalid due to another value being changed and vice-versa.
I also have to show an error message for each field.
The form content is generated from the datamodel with a few layers of nested repeaters.
I have made a custom directive that contains the different validation rules and it triggers nicely when that field changes.
I'm using ngMessages to show the appropriate errormessage based on what business rule is violated.
The question is, how do I trigger validation on all other fields, when one particular field is changed? Preferably I should just trigger validation of all fields for the employee, whos value is being changed, since the values for one employee doesn't affect validation of other employees.
The fiddle here has a simplified version of my case, where the "overlap" rule just checks if two numbers are the same.
The html:
<form name="demoForm">
<div ng-repeat="employee in list">
<div ng-bind="employee.name"></div>
<div ng-repeat="day in employee.days" ng-form="employeeForm">
<input ng-model="day.hours" name="hours" custom-validate="{day: day, days: employee.days}" ng-model-options="{allowInvalid:true}" />
<span ng-messages="employeeForm.hours.$error">
<span ng-message="number">Should be a number.</span>
<span ng-message="businessHours">The number is outside business hours.</span>
<span ng-message="max">The number is too large.</span>
<span ng-message="overlap">The number must be unique for each employee.</span>
</span>
</div>
<br/>
</div>
</form>
The validation directive:
angular.module('app').directive('customValidate', [validator]);
function validator() {
return {
restrict: 'A',
require: 'ngModel',
scope: {
data: '=customValidate'
},
link: linkFunc,
};
function linkFunc(scope, element, attrs, ctrl) {
ctrl.$validators.number = function(value) {
return value === "" || Number.isInteger(+value);
}
ctrl.$validators.businessHours = function(value) {
// imagine other validation data here
return value === "" || (value >= 1 && value <= 10);
}
ctrl.$validators.overlap = function(value) {
if (value === "") {
return true;
}
// find all other entries with identical value excluding self
var identical = scope.data.days.filter(function(x) {
return x !== scope.data.day && +x.hours === +value;
});
return identical.length === 0;
};
}
}
Fiddle here:
http://jsfiddle.net/maxrawhawk/dvpjdjbv/
The answer:
This little piece of code in the end of the directives link function:
scope.$watch('data', function(){
ctrl.$validate();
}, true);
Watch the data related to validation given from the markup with the most important detail 'true' as third parameter, making $watch check for object equality.
Updated fiddle:
http://jsfiddle.net/maxrawhawk/dvpjdjbv/12/
I have lots of input, textarea and select on some pages (angular templates).
I want to redefine "input" directive such that It will take a value like ViewMode = true from either localStorage and convert all inputs as label. If I change the ViewMode then on page refresh input should behave properly.
But I do not want to edit any input tag on any angular template.
Means I want to override input, textarea and select as my own angular directive.
I am not able to start. Where from should I start? (I have experience of custom directive with new name, but not with any exciting HTML tag name)
Note: I do not want to use readonly (with proper style) since it requires editing all input tag. Not only that I have custom directives with isolated scope, so I need to pass the ViewMode value to all custom directives. More over if user press CTRL+A content readonly field is not being selected.
I am looking for a solution kind of as follows
ViewButtonClickEvent () {
set localStorage.viewMode = true;
callExistingEditMethod();
}
EditButtonClickEvent () {
set localStorage.viewMode = false;
callExistingEditMethod();
}
editPagesModule.directive('input', {
if(localStorage.viewMode != true)
//Keep all existing functionality with ng-model
}
else {
//replace input with span or label.
}
})
You could create directives called input, select and textarea, which would automatically be compiled without having to change your existing markup.
Working examples: JSFiddle & Plunker
It would look something like this:
angular.module('myApp', [])
.directive('input', inputDirective)
.directive('select', inputDirective)
.directive('textarea', inputDirective)
.factory('$editMode', editModeFactory)
;
inputDirective.$inject = ['$editMode'];
function inputDirective($editMode) {
return {
link: postLink
};
function postLink(scope, iElement, iAttr) {
scope.$watch($editMode.get, function(edit) {
if (iElement[0].nodeName === 'SELECT') {
if (edit === 'true') iElement.removeAttr('disabled');
else iElement.attr('disabled', true);
}
else {
if (edit === 'true') iElement.removeAttr('readonly');
else iElement.attr('readonly', true);
}
});
}
}
editModeFactory.$inject = ['$window'];
function editModeFactory($window) {
return {
get: function() {
return $window.localStorage.getItem('editMode');
},
set: function(value) {
$window.localStorage.setItem('editMode', value);
}
};
}
I did use the readonly attribute (disabled for select), because the only other option I can think of would be to replace the entire input element with something like a div. You would also have to cache the original element, so you can restore it later...and doing that sort of thing would break your bindings so you'd have to recompile every input, every time. That just seems like a horrible idea.
I'm creating a site in which I have objects that all have a 'tags' property, which is a list of strings.
I'm creating a search functionality that filters all elements in a list. If the user enters '#something here', then I want to ONLY match the user input to the tags of each property. If the user just enters a string in the search box, then I want to search all object properties.
I have a form defined like so:
<form class="navbar-form navbar-left" role="search">
<div class="form-group">
<input data-ng-model="$root.searchText" type="text" class="form-control" placeholder="#hashtag or just a string">
</div>
</form>
I know that the default filter can be used in the way I want it to if I define the data-ng-model with the field I want. So if I wanted to only search tags, I'd do data-ng-model='$root.searchText.tags', and user input will only match that. If I want to search all, then I'd do data-ng-model='$root.searchText.$'.
But how can I make the model switch based on whether or not a user types in a string with '#' at the beginning or not?
I've tried creating a custom filter, but that got confusing. There should be some kind of conditional statement that either sets the model to be $root.searchText.tags or $root.searchText.$, but that's more difficult that I thought. Does anyone know how to structure that conditional statement?
I have a solution for you, although it might not be the best workaround.
You watch the search filter and update the lists based on your logic:
1.filter by tags if searchText starts with '#'
2.fitler by properties values if searchText do not starts with '#'
$scope.$watch('model.search', function(search){
var tagFilter,results;
if(search.startsWith('#')) { //handle tag filter logic
tagFilters = search.split('#');
console.log(tagFilters);
results = _.filter($scope.model.lists, function(obj){
return _.intersection(obj.tags, tagFilters).length > 0;
});
} else { //handle property filter logic
results = _.filter($scope.model.lists, function (obj) {
return _.values(obj).some(function (el) {
return el.indexOf(search) > -1;
});
});
}
console.log(results);
$scope.model.displayItems = results;
}, true);
plnkr
I need to validate a form with a bunch of inputs in it. And, if an input is invalid, indicate visually in the form that a particular attribute is invalid. For this I need to validate each form element individually.
I have one model & one view representing the entire form. Now when I update an attribute:
this.model.set('name', this.$name.val())
the validate method on the model will be called.
But, in that method I am validating all the attributes, so when setting the attribute above, all others are also validated, and if any one is invalid, an error is returned. This means that even if my 'name' attribute is valid, I get errors for others.
So, how do I validate just one attribute?
I think that it is not possible to just validate one attribute via the validate() method. One solution is to not use the validate method, and instead validate every attribute on 'change' event. But then this would make a lot of change handlers. Is it the correct approach? What else can I do?
I also think that this points to a bigger issue in backbone:
Whenever you use model.set() to set an attribute on the model, your validation method is run and all attributes are validated. This seems counterintuitive as you just want that single attribute to be validated.
Validate is used to keep your model in a valid state, it won't let you set an invalid value unless you pass a silent:true option.
You could either set all your attributes in one go:
var M=Backbone.Model.extend({
defaults:{
name:"",
count:0
},
validate: function(attrs) {
var invalid=[];
if (attrs.name==="") invalid.push("name");
if (attrs.count===0) invalid.push("count");
if (invalid.length>0) return invalid;
}
});
var obj=new M();
obj.on("error",function(model,err) {
console.log(err);
});
obj.set({
name:"name",
count:1
});
or validate them one by one before setting them
var M=Backbone.Model.extend({
defaults:{
name:"",
count:0
},
validate: function(attrs) {
var invalid=[];
if ( (_.has(attrs,"name"))&&(attrs.name==="") )
invalid.push("name");
if ( (_.has(attrs,"count"))&&(attrs.count===0) )
invalid.push("count");
if (invalid.length>0) return invalid;
}
});
var obj=new M();
obj.on("error",function(model,err) {
console.log(err);
});
if (!obj.validate({name:"name"}))
obj.set({name:"name"},{silent:true});
I recently created a small Backbone.js plugin, Backbone.validateAll, that will allow you to validate only the Model attributes that are currently being saved/set by passing a validateAll option.
https://github.com/gfranko/Backbone.validateAll
That is not the issue of Backbone, it doesn't force you to write validation in some way. There is no point in validation of all attributes persisted in the model, cause normally your model doesn't contain invalid attributes, cause set() doesn't change the model if validation fails, unless you pass silent option, but that is another story. However if you choose this way, validation just always pass for not changed attributes because of the point mentioned above.
You may freely choose another way: validate only attributes that are to be set (passed as an argument to validate()).
You can also overload your model's set function with your own custom function to pass silent: true to avoid triggering validation.
set: function (key, value, options) {
options || (options = {});
options = _.extend(options, { silent: true });
return Backbone.Model.prototype.set.call(this, key, value, options);
}
This basically passes {silent:true} in options and calls the Backbone.Model set function with {silent: true}.
In this way, you won't have to pass {silent: true} as options everywhere, where you call
this.model.set('propertyName',val, {silent:true})
For validations you can also use the Backbone.Validation plugin
https://github.com/thedersen/backbone.validation
I had to make a modification to the backbone.validation.js file, but it made this task much easier for me. I added the snippet below to the validate function.
validate: function(attrs, setOptions){
var model = this,
opt = _.extend({}, options, setOptions);
if(!attrs){
return model.validate.call(model, _.extend(getValidatedAttrs(model), model.toJSON()));
}
///////////BEGIN NEW CODE SNIPPET/////////////
if (typeof attrs === 'string') {
var attrHolder = attrs;
attrs = [];
attrs[attrHolder] = model.get(attrHolder);
}
///////////END NEW CODE SNIPPET///////////////
var result = validateObject(view, model, model.validation, attrs, opt);
model._isValid = result.isValid;
_.defer(function() {
model.trigger('validated', model._isValid, model, result.invalidAttrs);
model.trigger('validated:' + (model._isValid ? 'valid' : 'invalid'), model, result.invalidAttrs);
});
if (!opt.forceUpdate && result.errorMessages.length > 0) {
return result.errorMessages;
}
}
I could then call validation on a single attribute like so
this.model.set(attributeName, attributeValue, { silent: true });
this.model.validate(attributeName);