2016-04-12 09:40:37 -07:00
|
|
|
import {
|
|
|
|
Directive,
|
|
|
|
DoCheck,
|
|
|
|
ElementRef,
|
|
|
|
EventEmitter,
|
|
|
|
Inject,
|
2016-01-06 14:13:44 -08:00
|
|
|
OnInit,
|
2016-04-12 09:40:37 -07:00
|
|
|
OnChanges,
|
|
|
|
SimpleChange,
|
2016-05-09 15:45:04 -07:00
|
|
|
SimpleChanges,
|
2016-04-12 09:40:37 -07:00
|
|
|
Type
|
2016-04-28 17:50:03 -07:00
|
|
|
} from '@angular/core';
|
2016-04-12 09:40:37 -07:00
|
|
|
import {
|
|
|
|
NG1_COMPILE,
|
|
|
|
NG1_SCOPE,
|
|
|
|
NG1_HTTP_BACKEND,
|
|
|
|
NG1_TEMPLATE_CACHE,
|
|
|
|
NG1_CONTROLLER
|
|
|
|
} from './constants';
|
2015-10-11 11:18:11 -07:00
|
|
|
import {controllerKey} from './util';
|
2015-10-26 20:17:46 -07:00
|
|
|
import * as angular from './angular_js';
|
2015-10-04 09:33:20 -07:00
|
|
|
|
|
|
|
const CAMEL_CASE = /([A-Z])/g;
|
|
|
|
const INITIAL_VALUE = {
|
|
|
|
__UNINITIALIZED__: true
|
|
|
|
};
|
2015-10-11 11:18:11 -07:00
|
|
|
const NOT_SUPPORTED: any = 'NOT_SUPPORTED';
|
2015-10-04 09:33:20 -07:00
|
|
|
|
|
|
|
|
2015-10-12 21:32:41 -07:00
|
|
|
export class UpgradeNg1ComponentAdapterBuilder {
|
2015-10-04 09:33:20 -07:00
|
|
|
type: Type;
|
|
|
|
inputs: string[] = [];
|
|
|
|
inputsRename: string[] = [];
|
|
|
|
outputs: string[] = [];
|
|
|
|
outputsRename: string[] = [];
|
|
|
|
propertyOutputs: string[] = [];
|
|
|
|
checkProperties: string[] = [];
|
|
|
|
propertyMap: {[name: string]: string} = {};
|
2015-10-11 11:18:11 -07:00
|
|
|
linkFn: angular.ILinkFn = null;
|
|
|
|
directive: angular.IDirective = null;
|
|
|
|
$controller: angular.IControllerService = null;
|
2015-10-04 09:33:20 -07:00
|
|
|
|
|
|
|
constructor(public name: string) {
|
2016-06-08 15:45:15 -07:00
|
|
|
var selector = name.replace(CAMEL_CASE, (all: any /** TODO #9100 */, next: string) => '-' + next.toLowerCase());
|
2015-10-04 09:33:20 -07:00
|
|
|
var self = this;
|
|
|
|
this.type =
|
|
|
|
Directive({selector: selector, inputs: this.inputsRename, outputs: this.outputsRename})
|
|
|
|
.Class({
|
|
|
|
constructor: [
|
2016-04-12 09:40:37 -07:00
|
|
|
new Inject(NG1_SCOPE),
|
|
|
|
ElementRef,
|
2015-10-11 11:18:11 -07:00
|
|
|
function(scope: angular.IScope, elementRef: ElementRef) {
|
|
|
|
return new UpgradeNg1ComponentAdapter(
|
|
|
|
self.linkFn, scope, self.directive, elementRef, self.$controller, self.inputs,
|
|
|
|
self.outputs, self.propertyOutputs, self.checkProperties, self.propertyMap);
|
2015-10-04 09:33:20 -07:00
|
|
|
}
|
|
|
|
],
|
2016-03-14 07:51:04 +01:00
|
|
|
ngOnInit: function() { /* needs to be here for ng2 to properly detect it */ },
|
refactor(lifecycle): prefix lifecycle methods with "ng"
BREAKING CHANGE:
Previously, components that would implement lifecycle interfaces would include methods
like "onChanges" or "afterViewInit." Given that components were at risk of using such
names without realizing that Angular would call the methods at different points of
the component lifecycle. This change adds an "ng" prefix to all lifecycle hook methods,
far reducing the risk of an accidental name collision.
To fix, just rename these methods:
* onInit
* onDestroy
* doCheck
* onChanges
* afterContentInit
* afterContentChecked
* afterViewInit
* afterViewChecked
* _Router Hooks_
* onActivate
* onReuse
* onDeactivate
* canReuse
* canDeactivate
To:
* ngOnInit,
* ngOnDestroy,
* ngDoCheck,
* ngOnChanges,
* ngAfterContentInit,
* ngAfterContentChecked,
* ngAfterViewInit,
* ngAfterViewChecked
* _Router Hooks_
* routerOnActivate
* routerOnReuse
* routerOnDeactivate
* routerCanReuse
* routerCanDeactivate
The names of lifecycle interfaces and enums have not changed, though interfaces
have been updated to reflect the new method names.
Closes #5036
2015-11-16 17:04:36 -08:00
|
|
|
ngOnChanges: function() { /* needs to be here for ng2 to properly detect it */ },
|
|
|
|
ngDoCheck: function() { /* needs to be here for ng2 to properly detect it */ }
|
2015-10-04 09:33:20 -07:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2015-10-26 20:17:46 -07:00
|
|
|
extractDirective(injector: angular.IInjectorService): angular.IDirective {
|
2015-10-04 09:33:20 -07:00
|
|
|
var directives: angular.IDirective[] = injector.get(this.name + 'Directive');
|
|
|
|
if (directives.length > 1) {
|
|
|
|
throw new Error('Only support single directive definition for: ' + this.name);
|
|
|
|
}
|
|
|
|
var directive = directives[0];
|
2015-10-11 11:18:11 -07:00
|
|
|
if (directive.replace) this.notSupported('replace');
|
|
|
|
if (directive.terminal) this.notSupported('terminal');
|
|
|
|
var link = directive.link;
|
|
|
|
if (typeof link == 'object') {
|
|
|
|
if ((<angular.IDirectivePrePost>link).post) this.notSupported('link.post');
|
|
|
|
}
|
|
|
|
return directive;
|
|
|
|
}
|
|
|
|
|
|
|
|
private notSupported(feature: string) {
|
|
|
|
throw new Error(`Upgraded directive '${this.name}' does not support '${feature}'.`);
|
|
|
|
}
|
|
|
|
|
|
|
|
extractBindings() {
|
2015-11-13 18:55:40 +01:00
|
|
|
var btcIsObject = typeof this.directive.bindToController === 'object';
|
|
|
|
if (btcIsObject && Object.keys(this.directive.scope).length) {
|
|
|
|
throw new Error(
|
|
|
|
`Binding definitions on scope and controller at the same time are not supported.`);
|
|
|
|
}
|
|
|
|
|
|
|
|
var context = (btcIsObject) ? this.directive.bindToController : this.directive.scope;
|
|
|
|
|
|
|
|
if (typeof context == 'object') {
|
|
|
|
for (var name in context) {
|
|
|
|
if ((<any>context).hasOwnProperty(name)) {
|
|
|
|
var localName = context[name];
|
2015-10-04 09:33:20 -07:00
|
|
|
var type = localName.charAt(0);
|
|
|
|
localName = localName.substr(1) || name;
|
|
|
|
var outputName = 'output_' + name;
|
|
|
|
var outputNameRename = outputName + ': ' + name;
|
2015-10-10 19:56:22 -07:00
|
|
|
var outputNameRenameChange = outputName + ': ' + name + 'Change';
|
2015-10-04 09:33:20 -07:00
|
|
|
var inputName = 'input_' + name;
|
|
|
|
var inputNameRename = inputName + ': ' + name;
|
|
|
|
switch (type) {
|
|
|
|
case '=':
|
|
|
|
this.propertyOutputs.push(outputName);
|
|
|
|
this.checkProperties.push(localName);
|
|
|
|
this.outputs.push(outputName);
|
2015-10-10 19:56:22 -07:00
|
|
|
this.outputsRename.push(outputNameRenameChange);
|
2015-10-04 09:33:20 -07:00
|
|
|
this.propertyMap[outputName] = localName;
|
|
|
|
// don't break; let it fall through to '@'
|
|
|
|
case '@':
|
2016-03-14 07:51:04 +01:00
|
|
|
// handle the '<' binding of angular 1.5 components
|
|
|
|
case '<':
|
2015-10-04 09:33:20 -07:00
|
|
|
this.inputs.push(inputName);
|
|
|
|
this.inputsRename.push(inputNameRename);
|
|
|
|
this.propertyMap[inputName] = localName;
|
|
|
|
break;
|
|
|
|
case '&':
|
|
|
|
this.outputs.push(outputName);
|
|
|
|
this.outputsRename.push(outputNameRename);
|
|
|
|
this.propertyMap[outputName] = localName;
|
|
|
|
break;
|
|
|
|
default:
|
2015-11-13 18:55:40 +01:00
|
|
|
var json = JSON.stringify(context);
|
2015-10-04 09:33:20 -07:00
|
|
|
throw new Error(
|
|
|
|
`Unexpected mapping '${type}' in '${json}' in '${this.name}' directive.`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-12 09:40:37 -07:00
|
|
|
compileTemplate(compile: angular.ICompileService, templateCache: angular.ITemplateCacheService,
|
|
|
|
httpBackend: angular.IHttpBackendService): Promise<any> {
|
2015-12-10 13:23:47 +01:00
|
|
|
if (this.directive.template !== undefined) {
|
2016-06-04 19:53:51 -07:00
|
|
|
this.linkFn = compileHtml(typeof this.directive.template === 'function' ? this.directive.template() : this.directive.template);
|
2015-10-11 11:18:11 -07:00
|
|
|
} else if (this.directive.templateUrl) {
|
2016-06-04 19:53:51 -07:00
|
|
|
var url = typeof this.directive.templateUrl === 'function' ? this.directive.templateUrl() : this.directive.templateUrl;
|
2015-10-11 11:18:11 -07:00
|
|
|
var html = templateCache.get(url);
|
|
|
|
if (html !== undefined) {
|
|
|
|
this.linkFn = compileHtml(html);
|
|
|
|
} else {
|
|
|
|
return new Promise((resolve, err) => {
|
2016-06-08 15:45:15 -07:00
|
|
|
httpBackend('GET', url, null, (status: any /** TODO #9100 */, response: any /** TODO #9100 */) => {
|
2015-10-11 11:18:11 -07:00
|
|
|
if (status == 200) {
|
|
|
|
resolve(this.linkFn = compileHtml(templateCache.put(url, response)));
|
|
|
|
} else {
|
|
|
|
err(`GET ${url} returned ${status}: ${response}`);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
throw new Error(`Directive '${this.name}' is not a component, it is missing template.`);
|
|
|
|
}
|
|
|
|
return null;
|
2016-06-08 15:45:15 -07:00
|
|
|
function compileHtml(html: any /** TODO #9100 */): angular.ILinkFn {
|
2015-10-11 11:18:11 -07:00
|
|
|
var div = document.createElement('div');
|
|
|
|
div.innerHTML = html;
|
|
|
|
return compile(div.childNodes);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-12 09:40:37 -07:00
|
|
|
static resolve(exportedComponents: {[name: string]: UpgradeNg1ComponentAdapterBuilder},
|
|
|
|
injector: angular.IInjectorService): Promise<any> {
|
2016-06-08 15:45:15 -07:00
|
|
|
var promises: any[] /** TODO #9100 */ = [];
|
2015-10-11 11:18:11 -07:00
|
|
|
var compile: angular.ICompileService = injector.get(NG1_COMPILE);
|
|
|
|
var templateCache: angular.ITemplateCacheService = injector.get(NG1_TEMPLATE_CACHE);
|
|
|
|
var httpBackend: angular.IHttpBackendService = injector.get(NG1_HTTP_BACKEND);
|
|
|
|
var $controller: angular.IControllerService = injector.get(NG1_CONTROLLER);
|
2015-10-04 09:33:20 -07:00
|
|
|
for (var name in exportedComponents) {
|
2015-10-09 21:19:00 -07:00
|
|
|
if ((<any>exportedComponents).hasOwnProperty(name)) {
|
2015-10-04 09:33:20 -07:00
|
|
|
var exportedComponent = exportedComponents[name];
|
2015-10-11 11:18:11 -07:00
|
|
|
exportedComponent.directive = exportedComponent.extractDirective(injector);
|
|
|
|
exportedComponent.$controller = $controller;
|
|
|
|
exportedComponent.extractBindings();
|
|
|
|
var promise = exportedComponent.compileTemplate(compile, templateCache, httpBackend);
|
2015-10-26 20:17:46 -07:00
|
|
|
if (promise) promises.push(promise);
|
2015-10-04 09:33:20 -07:00
|
|
|
}
|
|
|
|
}
|
2015-10-11 11:18:11 -07:00
|
|
|
return Promise.all(promises);
|
2015-10-04 09:33:20 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-01-06 14:13:44 -08:00
|
|
|
class UpgradeNg1ComponentAdapter implements OnInit, OnChanges, DoCheck {
|
2015-10-11 11:18:11 -07:00
|
|
|
destinationObj: any = null;
|
2015-10-04 09:33:20 -07:00
|
|
|
checkLastValues: any[] = [];
|
2016-01-06 14:13:44 -08:00
|
|
|
componentScope: angular.IScope;
|
|
|
|
element: Element;
|
2016-05-17 14:53:59 -07:00
|
|
|
$element: any = null;
|
2015-10-04 09:33:20 -07:00
|
|
|
|
2016-01-06 14:13:44 -08:00
|
|
|
constructor(private linkFn: angular.ILinkFn, scope: angular.IScope,
|
|
|
|
private directive: angular.IDirective, elementRef: ElementRef,
|
2016-05-17 14:53:59 -07:00
|
|
|
private $controller: angular.IControllerService, private inputs: string[],
|
2016-01-06 14:13:44 -08:00
|
|
|
private outputs: string[], private propOuts: string[],
|
2016-04-12 09:40:37 -07:00
|
|
|
private checkProperties: string[], private propertyMap: {[key: string]: string}) {
|
2016-01-06 14:13:44 -08:00
|
|
|
this.element = elementRef.nativeElement;
|
|
|
|
this.componentScope = scope.$new(!!directive.scope);
|
2016-05-17 14:53:59 -07:00
|
|
|
this.$element = angular.element(this.element);
|
2015-10-11 11:18:11 -07:00
|
|
|
var controllerType = directive.controller;
|
2016-05-17 14:53:59 -07:00
|
|
|
if (directive.bindToController && controllerType) {
|
|
|
|
this.destinationObj = this.buildController(controllerType);
|
|
|
|
} else {
|
|
|
|
this.destinationObj = this.componentScope;
|
2015-10-11 11:18:11 -07:00
|
|
|
}
|
2015-10-04 09:33:20 -07:00
|
|
|
|
|
|
|
for (var i = 0; i < inputs.length; i++) {
|
2016-06-08 15:45:15 -07:00
|
|
|
(this as any /** TODO #9100 */)[inputs[i]] = null;
|
2015-10-04 09:33:20 -07:00
|
|
|
}
|
|
|
|
for (var j = 0; j < outputs.length; j++) {
|
2016-06-08 15:45:15 -07:00
|
|
|
var emitter = (this as any /** TODO #9100 */)[outputs[j]] = new EventEmitter();
|
|
|
|
this.setComponentProperty(outputs[j], ((emitter: any /** TODO #9100 */) => (value: any /** TODO #9100 */) => emitter.emit(value))(emitter));
|
2015-10-04 09:33:20 -07:00
|
|
|
}
|
|
|
|
for (var k = 0; k < propOuts.length; k++) {
|
2016-06-08 15:45:15 -07:00
|
|
|
(this as any /** TODO #9100 */)[propOuts[k]] = new EventEmitter();
|
2015-10-04 09:33:20 -07:00
|
|
|
this.checkLastValues.push(INITIAL_VALUE);
|
|
|
|
}
|
2016-05-17 14:53:59 -07:00
|
|
|
|
2015-10-04 09:33:20 -07:00
|
|
|
}
|
|
|
|
|
2016-03-14 07:51:04 +01:00
|
|
|
ngOnInit() {
|
2016-05-17 14:53:59 -07:00
|
|
|
|
|
|
|
if (!this.directive.bindToController && this.directive.controller) {
|
2016-05-26 13:33:53 -07:00
|
|
|
this.buildController(this.directive.controller);
|
2016-05-17 14:53:59 -07:00
|
|
|
}
|
|
|
|
var link = this.directive.link;
|
|
|
|
if (typeof link == 'object') link = (<angular.IDirectivePrePost>link).pre;
|
|
|
|
if (link) {
|
|
|
|
var attrs: angular.IAttributes = NOT_SUPPORTED;
|
|
|
|
var transcludeFn: angular.ITranscludeFunction = NOT_SUPPORTED;
|
|
|
|
var linkController = this.resolveRequired(this.$element, this.directive.require);
|
|
|
|
(<angular.IDirectiveLinkFn>this.directive.link)(this.componentScope, this.$element, attrs,
|
|
|
|
linkController, transcludeFn);
|
|
|
|
}
|
|
|
|
|
2016-01-06 14:13:44 -08:00
|
|
|
var childNodes: Node[] = [];
|
2016-06-08 15:45:15 -07:00
|
|
|
var childNode: any /** TODO #9100 */;
|
2016-01-06 14:13:44 -08:00
|
|
|
while (childNode = this.element.firstChild) {
|
|
|
|
this.element.removeChild(childNode);
|
|
|
|
childNodes.push(childNode);
|
|
|
|
}
|
|
|
|
this.linkFn(this.componentScope, (clonedElement: Node[], scope: angular.IScope) => {
|
|
|
|
for (var i = 0, ii = clonedElement.length; i < ii; i++) {
|
|
|
|
this.element.appendChild(clonedElement[i]);
|
|
|
|
}
|
2016-06-08 15:45:15 -07:00
|
|
|
}, {parentBoundTranscludeFn: (scope: any /** TODO #9100 */, cloneAttach: any /** TODO #9100 */) => { cloneAttach(childNodes); }});
|
2016-03-14 07:51:04 +01:00
|
|
|
if (this.destinationObj.$onInit) {
|
|
|
|
this.destinationObj.$onInit();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-09 15:45:04 -07:00
|
|
|
ngOnChanges(changes: SimpleChanges) {
|
2015-10-04 09:33:20 -07:00
|
|
|
for (var name in changes) {
|
2015-10-26 20:17:46 -07:00
|
|
|
if ((<Object>changes).hasOwnProperty(name)) {
|
2015-10-04 09:33:20 -07:00
|
|
|
var change: SimpleChange = changes[name];
|
|
|
|
this.setComponentProperty(name, change.currentValue);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
refactor(lifecycle): prefix lifecycle methods with "ng"
BREAKING CHANGE:
Previously, components that would implement lifecycle interfaces would include methods
like "onChanges" or "afterViewInit." Given that components were at risk of using such
names without realizing that Angular would call the methods at different points of
the component lifecycle. This change adds an "ng" prefix to all lifecycle hook methods,
far reducing the risk of an accidental name collision.
To fix, just rename these methods:
* onInit
* onDestroy
* doCheck
* onChanges
* afterContentInit
* afterContentChecked
* afterViewInit
* afterViewChecked
* _Router Hooks_
* onActivate
* onReuse
* onDeactivate
* canReuse
* canDeactivate
To:
* ngOnInit,
* ngOnDestroy,
* ngDoCheck,
* ngOnChanges,
* ngAfterContentInit,
* ngAfterContentChecked,
* ngAfterViewInit,
* ngAfterViewChecked
* _Router Hooks_
* routerOnActivate
* routerOnReuse
* routerOnDeactivate
* routerCanReuse
* routerCanDeactivate
The names of lifecycle interfaces and enums have not changed, though interfaces
have been updated to reflect the new method names.
Closes #5036
2015-11-16 17:04:36 -08:00
|
|
|
ngDoCheck(): number {
|
2015-10-04 09:33:20 -07:00
|
|
|
var count = 0;
|
2015-10-11 11:18:11 -07:00
|
|
|
var destinationObj = this.destinationObj;
|
2015-10-04 09:33:20 -07:00
|
|
|
var lastValues = this.checkLastValues;
|
|
|
|
var checkProperties = this.checkProperties;
|
|
|
|
for (var i = 0; i < checkProperties.length; i++) {
|
2015-10-11 11:18:11 -07:00
|
|
|
var value = destinationObj[checkProperties[i]];
|
2015-10-04 09:33:20 -07:00
|
|
|
var last = lastValues[i];
|
|
|
|
if (value !== last) {
|
|
|
|
if (typeof value == 'number' && isNaN(value) && typeof last == 'number' && isNaN(last)) {
|
|
|
|
// ignore because NaN != NaN
|
|
|
|
} else {
|
2016-06-08 15:45:15 -07:00
|
|
|
var eventEmitter: EventEmitter<any> = (this as any /** TODO #9100 */)[this.propOuts[i]];
|
2015-11-15 23:58:59 -08:00
|
|
|
eventEmitter.emit(lastValues[i] = value);
|
2015-10-04 09:33:20 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
setComponentProperty(name: string, value: any) {
|
2015-10-11 11:18:11 -07:00
|
|
|
this.destinationObj[this.propertyMap[name]] = value;
|
|
|
|
}
|
|
|
|
|
2016-06-08 15:45:15 -07:00
|
|
|
private buildController(controllerType: any /** TODO #9100 */) {
|
2016-05-17 14:53:59 -07:00
|
|
|
var locals = { $scope: this.componentScope, $element: this.$element };
|
|
|
|
var controller:any = this.$controller(controllerType, locals, null, this.directive.controllerAs);
|
|
|
|
this.$element.data(controllerKey(this.directive.name), controller);
|
|
|
|
return controller;
|
|
|
|
}
|
|
|
|
|
2016-04-12 09:40:37 -07:00
|
|
|
private resolveRequired($element: angular.IAugmentedJQuery, require: string | string[]): any {
|
2015-10-11 11:18:11 -07:00
|
|
|
if (!require) {
|
|
|
|
return undefined;
|
|
|
|
} else if (typeof require == 'string') {
|
|
|
|
var name: string = <string>require;
|
|
|
|
var isOptional = false;
|
|
|
|
var startParent = false;
|
|
|
|
var searchParents = false;
|
|
|
|
var ch: string;
|
|
|
|
if (name.charAt(0) == '?') {
|
|
|
|
isOptional = true;
|
|
|
|
name = name.substr(1);
|
|
|
|
}
|
|
|
|
if (name.charAt(0) == '^') {
|
|
|
|
searchParents = true;
|
|
|
|
name = name.substr(1);
|
|
|
|
}
|
|
|
|
if (name.charAt(0) == '^') {
|
|
|
|
startParent = true;
|
|
|
|
name = name.substr(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
var key = controllerKey(name);
|
|
|
|
if (startParent) $element = $element.parent();
|
|
|
|
var dep = searchParents ? $element.inheritedData(key) : $element.data(key);
|
|
|
|
if (!dep && !isOptional) {
|
|
|
|
throw new Error(`Can not locate '${require}' in '${this.directive.name}'.`);
|
|
|
|
}
|
|
|
|
return dep;
|
|
|
|
} else if (require instanceof Array) {
|
2016-06-08 15:45:15 -07:00
|
|
|
var deps: any[] /** TODO #9100 */ = [];
|
2015-10-11 11:18:11 -07:00
|
|
|
for (var i = 0; i < require.length; i++) {
|
|
|
|
deps.push(this.resolveRequired($element, require[i]));
|
|
|
|
}
|
|
|
|
return deps;
|
|
|
|
}
|
|
|
|
throw new Error(
|
|
|
|
`Directive '${this.directive.name}' require syntax unrecognized: ${this.directive.require}`);
|
2015-10-04 09:33:20 -07:00
|
|
|
}
|
|
|
|
}
|