Skip to content

add showConditional expressions and conditional disabling #180

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
node_modules
coverage
bower_components
./idea
2 changes: 1 addition & 1 deletion dist/bootstrap-decorator.min.js

Large diffs are not rendered by default.

70 changes: 68 additions & 2 deletions dist/schema-form.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,11 @@ angular.module('schemaForm').provider('schemaFormDecorators',
return directive.mappings['default'];
};


var lookupForKey = function (key) {
return 'model' + (key[0] !== '[' ? '.' : '') + key;
};

var createDirective = function(name) {
$compileProvider.directive(name, ['$parse', '$compile', '$http', '$templateCache',
function($parse, $compile, $http, $templateCache) {
Expand Down Expand Up @@ -226,6 +231,67 @@ angular.module('schemaForm').provider('schemaFormDecorators',
}
};

var evalExpression = function (expression) {
angular.forEach(scope.form.dependencies, function (key) {
expression = expression.replace(key, lookupForKey(key));
});

return scope.$eval(expression);
};


var resetModelValue = function () {
if (scope.ngModel) {
scope.ngModel.$setViewValue(undefined);
scope.ngModel.$render();
scope.ngModel.$setPristine();
}
};

scope.showCondition = function () {
var expressionString = scope.form.expression;
if (angular.isUndefined(expressionString)) {
return true;
}

var show = evalExpression(expressionString);

if (angular.isDefined(scope.form.required)) {
scope.form.required = show;
scope.form.schema.required = show;
}


if (scope.form.key && !show) {
resetModelValue();
}

return show;

};


scope.disabledElement = function () {
var expressionString = scope.form.disabledExpression;

if (angular.isUndefined(expressionString)) {
return false;
}

var disabled = evalExpression(expressionString);

if (angular.isDefined(scope.form.required)) {
scope.form.required = !disabled;
scope.form.schema.required = !disabled;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing the actual schema is not something we can do, either it is required or it's not.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I fill awkward for this part. But maybe you can give me an advice, how to handle better situation when field is required only if it is visible. Don't you?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is kind of a restriction we have by using JSON Schema. It becomes a bit complicated when your validation scenario is hard to describe with a JSON Schema.

What you can do is to re-redraw the form by changing the schema or the form definition, but change the entire schema/form instance (i.e. use angular.copy), otherwise it won't trigger a redraw of the form.

A bit more correct solution would be to use oneOf, anyOf etc and that way you might be able to describe the validation you need from within the schema, but sadly we lack support for those.

Could you give some more specifics on what such a situation might be? There might be some other way to solve it.

}

if (disabled) {
resetModelValue();
}
return disabled;
};


/**
* Evaluate an expression, i.e. scope.$eval
* but do it in sfSchemas parent scope sf-schema directive is used
Expand Down Expand Up @@ -1024,8 +1090,8 @@ angular.module('schemaForm').directive('sfArray', ['sfSelect', 'schemaForm', 'sf
var len = list.length;
var copy = scope.copyWithIndex(len);
schemaForm.traverseForm(copy, function(part) {
if (part.key && angular.isDefined(part['default'])) {
sfSelect(part.key, scope.model, part['default']);
if (part.key && part.schema && angular.isDefined(part.schema['default'])) {
sfSelect(part.key, scope.model, part.schema['default']);
}
});

Expand Down
2 changes: 1 addition & 1 deletion dist/schema-form.min.js

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion examples/data/simple.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@
{
"key": "comment",
"type": "textarea",
"placeholder": "Make a comment"
"placeholder": "Make a comment",
"dependencies": ["name", "email"],
"expression": "name === 'secret'",
"disabledExpression": "email === '[email protected]'"
},
{
"type": "submit",
Expand Down
27 changes: 27 additions & 0 deletions gulp/tasks/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
var gulp = require('gulp'),
karma = require('gulp-karma');

gulp.task('test', function() {
return gulp.src([
'bower_components/jquery/dist/jquery.min.js',
'test/lib/angular.js',
'test/lib/angular-mocks.js',
'bower_components/tv4/tv4.js',
'bower_components/objectpath/lib/ObjectPath.js',
'src/module.js',
'src/sfPath.js',
'src/services/*.js',
'src/directives/*.js',
'src/directives/decorators/bootstrap/*.js',
'src/**/*.html',
'test/services/schema-form-test.js',
'test/services/decorators-test.js',
'test/directives/schema-form-test.js'])
.pipe(karma({
configFile: 'karma.conf.js',
action: 'run'
}))
.on('error', function(err) {
throw err;
});
});
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"gulp-angular-templatecache": "^1.2.1",
"gulp-concat": "^2.2.0",
"gulp-jscs": "^1.1.0",
"gulp-karma": "0.0.4",
"gulp-minify-html": "^0.1.1",
"gulp-uglify": "^0.2.1",
"karma": "^0.12.0",
Expand Down
4 changes: 2 additions & 2 deletions src/directives/decorators/bootstrap/actions.html
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
<div class="btn-group">
<div class="btn-group" ng-show="showCondition()">
<input ng-repeat-start="item in form.items"
type="submit"
class="btn {{ item.style || 'btn-primary' }}"
value="{{item.title}}"
ng-if="item.type === 'submit'">
<button ng-repeat-end class="btn {{ item.style || 'btn-default' }}"
type="button"
ng-disabled="form.readonly"
ng-disabled="form.readonly || disabledElement()"
ng-if="item.type !== 'submit'"
ng-click="buttonClick($event,item)">{{item.title}}</button>
</div>
2 changes: 1 addition & 1 deletion src/directives/decorators/bootstrap/checkbox.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<div class="checkbox" ng-class="{'has-error': hasError(), 'has-success': hasSuccess()}">
<div class="checkbox" ng-class="{'has-error': hasError(), 'has-success': hasSuccess()}" ng-show="showCondition()">
<label>
<input type="checkbox"
sf-changed="form"
Expand Down
4 changes: 2 additions & 2 deletions src/directives/decorators/bootstrap/default.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<div class="form-group" ng-class="{'has-error': hasError(), 'has-success': hasSuccess(), 'has-feedback': form.feedback !== false }">
<div class="form-group" ng-class="{'has-error': hasError(), 'has-success': hasSuccess(), 'has-feedback': form.feedback !== false }" ng-show="showCondition()">
<label class="control-label" ng-show="showTitle()">{{form.title}}</label>

<input ng-show="form.key"
Expand All @@ -9,7 +9,7 @@
class="form-control"
ng-model-options="form.ngModelOptions"
ng-model="$$value$$"
ng-disabled="form.readonly"
ng-disabled="form.readonly || disabledElement()"
schema-validate="form">
<span ng-if="form.feedback !== false"
class="form-control-feedback"
Expand Down
2 changes: 1 addition & 1 deletion src/directives/decorators/bootstrap/fieldset.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<fieldset ng-disabled="form.readonly">
<fieldset ng-disabled="form.readonly || disabledElement()" ng-show="showCondition()">
<legend ng-show="form.title">{{ form.title }}</legend>
<div class="help-block" ng-show="form.description" ng-bind-html="form.description"></div>
<sf-decorator ng-repeat="item in form.items" form="item"></sf-decorator>
Expand Down
2 changes: 1 addition & 1 deletion src/directives/decorators/bootstrap/help.html
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<div class="helpvalue" ng-bind-html="form.helpvalue"></div>
<div class="helpvalue" ng-bind-html="form.helpvalue" ng-show="showCondition()"></div>
4 changes: 2 additions & 2 deletions src/directives/decorators/bootstrap/radio-buttons.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<div class="form-group" ng-class="{'has-error': hasError(), 'has-success': hasSuccess()}">
<div class="form-group" ng-class="{'has-error': hasError(), 'has-success': hasSuccess()}" ng-show="showCondition()">
<div>
<label class="control-label" ng-show="showTitle()">{{form.title}}</label>
</div>
Expand All @@ -9,7 +9,7 @@
<input type="radio"
sf-changed="form"
style="display: none;"
ng-disabled="form.readonly"
ng-disabled="form.readonly || disabledElement()"
ng-model="$$value$$"
ng-model-options="form.ngModelOptions"
ng-value="item.value">
Expand Down
4 changes: 2 additions & 2 deletions src/directives/decorators/bootstrap/radios-inline.html
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<div class="form-group" ng-class="{'has-error': hasError(), 'has-success': hasSuccess()}">
<div class="form-group" ng-class="{'has-error': hasError(), 'has-success': hasSuccess()}" ng-show="showCondition()">
<label class="control-label" ng-show="showTitle()">{{form.title}}</label>
<div>
<label class="radio-inline" ng-repeat="item in form.titleMap" >
<input type="radio"
sf-changed="form"
ng-disabled="form.readonly"
ng-disabled="form.readonly || disabledElement()"
ng-model="$$value$$"
ng-value="item.value">
<span ng-bind-html="item.name"></span>
Expand Down
4 changes: 2 additions & 2 deletions src/directives/decorators/bootstrap/radios.html
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<div class="form-group" ng-class="{'has-error': hasError(), 'has-success': hasSuccess()}">
<div class="form-group" ng-class="{'has-error': hasError(), 'has-success': hasSuccess()}" ng-show="showCondition()">
<label class="control-label" ng-show="showTitle()">{{form.title}}</label>
<div class="radio" ng-repeat="item in form.titleMap" >
<label>
<input type="radio"
sf-changed="form"
ng-disabled="form.readonly"
ng-disabled="form.readonly || disabledElement()"
ng-model="$$value$$"
ng-model-options="form.ngModelOptions"
ng-value="item.value">
Expand Down
2 changes: 1 addition & 1 deletion src/directives/decorators/bootstrap/readonly.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<div class="form-group">
<div class="form-group" ng-show="showCondition()">
<label ng-show="showTitle()">{{form.title}}</label>
<input ng-if="form.type !== 'textarea'" type="text" disabled class="form-control" value="{{$$value$$}}">
<textarea ng-if="form.type === 'textarea'" disabled class="form-control">{{$$value$$}}</textarea>
Expand Down
4 changes: 2 additions & 2 deletions src/directives/decorators/bootstrap/select.html
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<div class="form-group" ng-class="{'has-error': hasError(), 'has-success': hasSuccess(), 'has-feedback': form.feedback !== false}">
<div class="form-group" ng-class="{'has-error': hasError(), 'has-success': hasSuccess(), 'has-feedback': form.feedback !== false}" ng-show="showCondition()">
<label class="control-label" ng-show="showTitle()">
{{form.title}}
</label>
<select ng-model="$$value$$"
ng-model-options="form.ngModelOptions"
ng-disabled="form.readonly"
ng-disabled="form.readonly || disabledElement()"
sf-changed="form"
class="form-control"
schema-validate="form"
Expand Down
4 changes: 2 additions & 2 deletions src/directives/decorators/bootstrap/submit.html
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<div class="form-group">
<div class="form-group" ng-show="showCondition()">
<input type="submit"
class="btn {{ form.style || 'btn-primary' }}"
value="{{form.title}}"
ng-disabled="form.readonly"
ng-disabled="form.readonly || disabledElement()"
ng-if="form.type === 'submit'">
<button class="btn {{ form.style || 'btn-default' }}"
type="button"
Expand Down
6 changes: 3 additions & 3 deletions src/directives/decorators/bootstrap/tabs.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<div ng-init="selected = { tab: 0 }">
<div ng-init="selected = { tab: 0 }" ng-show="showCondition()">
<ul class="nav nav-tabs" style="margin-bottom: 15px">
<li ng-repeat="tab in form.tabs"
ng-disabled="form.readonly"
ng-disabled="form.readonly || disabledElement()"
ng-click="$event.preventDefault() || (selected.tab = $index)"
ng-class="{active: selected.tab === $index}">
<a href="#">{{ tab.title }}</a>
Expand All @@ -10,7 +10,7 @@

<div class="tab-content">
<div class="tab-pane"
ng-disabled="form.readonly"
ng-disabled="form.readonly || disabledElement()"
ng-repeat="tab in form.tabs"
ng-show="selected.tab === $index"
ng-class="{active: selected.tab === $index}">
Expand Down
4 changes: 2 additions & 2 deletions src/directives/decorators/bootstrap/textarea.html
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<div class="form-group has-feedback" ng-class="{'has-error': hasError(), 'has-success': hasSuccess()}">
<div class="form-group has-feedback" ng-class="{'has-error': hasError(), 'has-success': hasSuccess()}" ng-show="showCondition()">
<label ng-show="showTitle()">{{form.title}}</label>
<textarea class="form-control"
sf-changed="form"
placeholder="{{form.placeholder}}"
ng-disabled="form.readonly"
ng-disabled="form.readonly || disabledElement()"
ng-model="$$value$$"
ng-model-options="form.ngModelOptions"
schema-validate="form"></textarea>
Expand Down
66 changes: 66 additions & 0 deletions src/services/decorators.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ angular.module('schemaForm').provider('schemaFormDecorators',
return directive.mappings['default'];
};


var lookupForKey = function (key) {
return 'model' + (key[0] !== '[' ? '.' : '') + key;
};

var createDirective = function(name) {
$compileProvider.directive(name, ['$parse', '$compile', '$http', '$templateCache',
function($parse, $compile, $http, $templateCache) {
Expand Down Expand Up @@ -100,6 +105,67 @@ angular.module('schemaForm').provider('schemaFormDecorators',
}
};

var evalExpression = function (expression) {
angular.forEach(scope.form.dependencies, function (key) {
expression = expression.replace(key, lookupForKey(key));
});

return scope.$eval(expression);
};


var resetModelValue = function () {
if (scope.ngModel) {
scope.ngModel.$setViewValue(undefined);
scope.ngModel.$render();
scope.ngModel.$setPristine();
}
};

scope.showCondition = function () {
var expressionString = scope.form.expression;
if (angular.isUndefined(expressionString)) {
return true;
}

var show = evalExpression(expressionString);

if (angular.isDefined(scope.form.required)) {
scope.form.required = show;
scope.form.schema.required = show;
}


if (scope.form.key && !show) {
resetModelValue();
}

return show;

};


scope.disabledElement = function () {
var expressionString = scope.form.disabledExpression;

if (angular.isUndefined(expressionString)) {
return false;
}

var disabled = evalExpression(expressionString);

if (angular.isDefined(scope.form.required)) {
scope.form.required = !disabled;
scope.form.schema.required = !disabled;
}

if (disabled) {
resetModelValue();
}
return disabled;
};


/**
* Evaluate an expression, i.e. scope.$eval
* but do it in sfSchemas parent scope sf-schema directive is used
Expand Down
Loading