Skip to content
Merged
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
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,26 @@ Then include the entry in your template:

Adjust the import path to your application structure.

### JavaScript Namespace

The library registers its constraints and its view transformers in a single
global object named after the `Svaroh\JsFormValidatorBundle` PHP namespace:

```js
Svaroh.constraints // constraints, keyed by class name without the separators
Svaroh.transformers // view transformers, keyed the same way
```

Register your own classes there, as shown in
[custom constraints](src/Resources/doc/3_7.md) and
[custom data transformers](src/Resources/doc/3_8.md). The object is created by
the library, so run your registrations after its script is loaded.

Every bundled constraint and transformer is still exported under its own global
name, such as `window.SymfonyComponentValidatorConstraintsNotBlank`, and a class
your application defines as a global is still picked up. Both are deprecated and
will be dropped in a future release.

### Render Bundle Config And Form Models

After the scripts are loaded, render the generated config and queued form models:
Expand Down
21 changes: 18 additions & 3 deletions src/Resources/doc/3_7.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
### 3.7 Custom constraints

If you have your own Symfony constraint, implement the matching constraint on
the JavaScript side too. The JavaScript constructor name should match the full
PHP class name with namespace separators removed.
the JavaScript side too. Register it in `Svaroh.constraints` under the full PHP
class name with the namespace separators removed.

For example, a PHP constraint:

Expand Down Expand Up @@ -51,7 +51,7 @@ requires this JavaScript constraint:

```html
<script type="text/javascript">
function AppValidatorConstraintsContainsAlphanumeric() {
Svaroh.constraints.AppValidatorConstraintsContainsAlphanumeric = function() {
/**
* This value will be filled with the real message received from the PHP constraint.
*/
Expand All @@ -75,6 +75,21 @@ requires this JavaScript constraint:
this.onCreate = function() {
// Put setup logic here if the constraint needs it.
};
};
</script>
```

`Svaroh` is the namespace of the library, as described in the README.
Constraints declared as a global function of the same name still work:

```html
<script type="text/javascript">
function AppValidatorConstraintsContainsAlphanumeric() {
// ...
}
</script>
```

This form is deprecated and support for it will be dropped in a future release,
so prefer the namespace for new code. Note that a global cannot replace a
constraint the bundle ships: the namespace is looked up first.
11 changes: 8 additions & 3 deletions src/Resources/doc/3_8.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ If you use a custom view transformer, implement the matching transformer on the
JavaScript side so validation receives the same normalized value.

For a PHP view transformer such as `App\Form\DataTransformer\MyTransformer`,
create a JavaScript constructor with the namespace separators removed:
register a JavaScript constructor in `Svaroh.transformers` under the class name
with the namespace separators removed:

```html
<script type="text/javascript">
function AppFormDataTransformerMyTransformer() {
Svaroh.transformers.AppFormDataTransformerMyTransformer = function() {
/**
* Extra options from the PHP transformer can be copied into this object.
*/
Expand All @@ -22,6 +23,10 @@ create a JavaScript constructor with the namespace separators removed:
// Compose the real value here.
return value;
};
}
};
</script>
```

`Svaroh` is the namespace of the library, as described in the README.
Transformers declared as a global function of the same name still work, but that
form is deprecated and support for it will be dropped in a future release.
34 changes: 30 additions & 4 deletions src/Resources/public/js/SvarohJsFormValidator.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import Svaroh from './namespace.js';
import './constraints';
import './transformers';

Expand Down Expand Up @@ -1278,17 +1279,41 @@ var SvarohJsFormValidator = new function () {
return value;
};

/**
* Resolves the constraint or the transformer the PHP side refers to by its
* class name with the namespace separators removed.
*
* The classes shipped by the bundle are registered in the Svaroh namespace.
* The ones an application defines for its own constraints and transformers
* are looked up in the global scope as before, see doc/3_7.md and
* doc/3_8.md, so that a global keeps working when the namespace holds no
* class of that name.
*
* @param {Object} registry
* @param {String} className
*
* @returns {Function|undefined}
*/
this.resolveClass = function (registry, className) {
if (Object.prototype.hasOwnProperty.call(registry, className)) {
return registry[className];
}

return window[className];
};

/**
* @param {Object} list
*/
this.parseConstraints = function (list) {
var constraints = [];
for (var name in list) {
var className = name.replace(/\\/g, '');
if (undefined !== window[className]) {
var constraintClass = this.resolveClass(Svaroh.constraints, className);
if (undefined !== constraintClass) {
var i = list[name].length;
while (i--) {
var constraint = new window[className]();
var constraint = new constraintClass();
for (var param in list[name][i]) {
constraint[param] = list[name][i][param];
}
Expand All @@ -1314,8 +1339,9 @@ var SvarohJsFormValidator = new function () {
var i = list.length;
while (i--) {
var className = String(list[i]['name']).replace(/\\/g, '');
if (undefined !== window[className]) {
var transformer = new window[className]();
var transformerClass = this.resolveClass(Svaroh.transformers, className);
if (undefined !== transformerClass) {
var transformer = new transformerClass();
for (var propName in list[i]) {
transformer[propName] = list[i][propName];
}
Expand Down
17 changes: 14 additions & 3 deletions src/Resources/public/js/constraints/globals.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,22 @@ const constraintGlobals = [
'SymfonyComponentValidatorConstraintsValid',
];

// The validator instantiates constraints as window[className], so a constraint
// that is not exported to the global scope is silently skipped at runtime.
// The validator instantiates constraints as Svaroh.constraints[className], so a
// constraint that is not registered in the namespace is silently skipped at
// runtime.
test.each(constraintGlobals)(
'window.%s is registered',
'Svaroh.constraints.%s is registered',
(name) => {
expect(typeof window.Svaroh.constraints[name]).toBe('function');
},
);

// The global name of every constraint is kept as a deprecated alias: user code
// may define custom constraints and Callback handlers against it.
test.each(constraintGlobals)(
'window.%s is a deprecated alias of the namespaced constraint',
(name) => {
expect(typeof window[name]).toBe('function');
expect(window[name]).toBe(window.Svaroh.constraints[name]);
},
);
107 changes: 76 additions & 31 deletions src/Resources/public/js/constraints/index.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,76 @@
import './Blank.js';
import './Callback.js';
import './Choice.js';
import './Count.js';
import './Date.js';
import './DateTime.js';
import './Email.js';
import './EqualTo.js';
import './File.js';
import './GreaterThan.js';
import './GreaterThanOrEqual.js';
import './IdenticalTo.js';
import './Ip.js';
import './IsFalse.js';
import './IsNull.js';
import './IsTrue.js';
import './Length.js';
import './LessThan.js';
import './LessThanOrEqual.js';
import './Luhn.js';
import './NotBlank.js';
import './NotEqualTo.js';
import './NotIdenticalTo.js';
import './NotNull.js';
import './Range.js';
import './Regex.js';
import './Time.js';
import './Type.js';
import './UniqueEntity.js';
import './Url.js';
import './Valid.js';
import Svaroh from '../namespace.js';

import SymfonyComponentValidatorConstraintsBlank from './Blank.js';
import SymfonyComponentValidatorConstraintsCallback from './Callback.js';
import SymfonyComponentValidatorConstraintsChoice from './Choice.js';
import SymfonyComponentValidatorConstraintsCount from './Count.js';
import SymfonyComponentValidatorConstraintsDate from './Date.js';
import SymfonyComponentValidatorConstraintsDateTime from './DateTime.js';
import SymfonyComponentValidatorConstraintsEmail from './Email.js';
import SymfonyComponentValidatorConstraintsEqualTo from './EqualTo.js';
import SymfonyComponentValidatorConstraintsFile from './File.js';
import SymfonyComponentValidatorConstraintsGreaterThan from './GreaterThan.js';
import SymfonyComponentValidatorConstraintsGreaterThanOrEqual from './GreaterThanOrEqual.js';
import SymfonyComponentValidatorConstraintsIdenticalTo from './IdenticalTo.js';
import SymfonyComponentValidatorConstraintsIp from './Ip.js';
import SymfonyComponentValidatorConstraintsIsFalse from './IsFalse.js';
import SymfonyComponentValidatorConstraintsIsNull from './IsNull.js';
import SymfonyComponentValidatorConstraintsIsTrue from './IsTrue.js';
import SymfonyComponentValidatorConstraintsLength from './Length.js';
import SymfonyComponentValidatorConstraintsLessThan from './LessThan.js';
import SymfonyComponentValidatorConstraintsLessThanOrEqual from './LessThanOrEqual.js';
import SymfonyComponentValidatorConstraintsLuhn from './Luhn.js';
import SymfonyComponentValidatorConstraintsNotBlank from './NotBlank.js';
import SymfonyComponentValidatorConstraintsNotEqualTo from './NotEqualTo.js';
import SymfonyComponentValidatorConstraintsNotIdenticalTo from './NotIdenticalTo.js';
import SymfonyComponentValidatorConstraintsNotNull from './NotNull.js';
import SymfonyComponentValidatorConstraintsRange from './Range.js';
import SymfonyComponentValidatorConstraintsRegex from './Regex.js';
import SymfonyComponentValidatorConstraintsTime from './Time.js';
import SymfonyComponentValidatorConstraintsType from './Type.js';
import SvarohJsFormValidatorBundleFormConstraintUniqueEntity from './UniqueEntity.js';
import SymfonyComponentValidatorConstraintsUrl from './Url.js';
import SymfonyComponentValidatorConstraintsValid from './Valid.js';

// The constraints are keyed by the name the PHP side serialises them under:
// their class name with the namespace separators removed. Every module also
// still assigns that name on "window" as a deprecated alias.
Object.assign(Svaroh.constraints, {
SymfonyComponentValidatorConstraintsBlank,
SymfonyComponentValidatorConstraintsCallback,
SymfonyComponentValidatorConstraintsChoice,
SymfonyComponentValidatorConstraintsCount,
SymfonyComponentValidatorConstraintsDate,
SymfonyComponentValidatorConstraintsDateTime,
SymfonyComponentValidatorConstraintsEmail,
SymfonyComponentValidatorConstraintsEqualTo,
SymfonyComponentValidatorConstraintsFile,
SymfonyComponentValidatorConstraintsGreaterThan,
SymfonyComponentValidatorConstraintsGreaterThanOrEqual,
SymfonyComponentValidatorConstraintsIdenticalTo,
SymfonyComponentValidatorConstraintsIp,
SymfonyComponentValidatorConstraintsIsFalse,
SymfonyComponentValidatorConstraintsIsNull,
SymfonyComponentValidatorConstraintsIsTrue,
SymfonyComponentValidatorConstraintsLength,
SymfonyComponentValidatorConstraintsLessThan,
SymfonyComponentValidatorConstraintsLessThanOrEqual,
SymfonyComponentValidatorConstraintsLuhn,
SymfonyComponentValidatorConstraintsNotBlank,
SymfonyComponentValidatorConstraintsNotEqualTo,
SymfonyComponentValidatorConstraintsNotIdenticalTo,
SymfonyComponentValidatorConstraintsNotNull,
SymfonyComponentValidatorConstraintsRange,
SymfonyComponentValidatorConstraintsRegex,
SymfonyComponentValidatorConstraintsTime,
SymfonyComponentValidatorConstraintsType,
SvarohJsFormValidatorBundleFormConstraintUniqueEntity,
SymfonyComponentValidatorConstraintsUrl,
SymfonyComponentValidatorConstraintsValid,

// The names the False, Null and True constraints had before Symfony 2.7
// renamed them, they are still reachable as class aliases in PHP
SymfonyComponentValidatorConstraintsFalse: SymfonyComponentValidatorConstraintsIsFalse,
SymfonyComponentValidatorConstraintsNull: SymfonyComponentValidatorConstraintsIsNull,
SymfonyComponentValidatorConstraintsTrue: SymfonyComponentValidatorConstraintsIsTrue,
});
32 changes: 32 additions & 0 deletions src/Resources/public/js/namespace.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* The namespace the library registers its constraints and transformers in.
*
* The PHP side serialises the class name of every constraint and of every view
* transformer into the form model, and the browser side instantiates them by
* that name with the namespace separators removed. Each of those names used to
* be a separate property of "window", which is what the "Add namespace in JS
* lib" issue objects to.
*
* The name mirrors the "Svaroh\JsFormValidatorBundle" PHP namespace and the
* "Svaroh" prefix the library already uses for its own globals. The object is
* merged into whatever is on "window.Svaroh" already, so an application that
* owns a "Svaroh" object of its own keeps it.
*
* An application registers its own constraint like this:
*
* Svaroh.constraints.AppValidatorConstraintsContainsAlphanumeric = function () {
* // ...
* };
*
* Constraints and transformers that are only defined as globals keep working:
* the lookup falls back to the global scope, see
* SvarohJsFormValidator.resolveClass().
*/
var Svaroh = window.Svaroh || {};

Svaroh.constraints = Svaroh.constraints || {};
Svaroh.transformers = Svaroh.transformers || {};

window.Svaroh = Svaroh;

export default Svaroh;
Loading
Loading