|
@@ -9,6 +9,7 @@ import CKEditorError from '../utils/ckeditorerror.js';
|
|
|
import mix from '../utils/mix.js';
|
|
import mix from '../utils/mix.js';
|
|
|
import EmitterMixin from '../utils/emittermixin.js';
|
|
import EmitterMixin from '../utils/emittermixin.js';
|
|
|
import cloneDeepWith from '../utils/lib/lodash/cloneDeepWith.js';
|
|
import cloneDeepWith from '../utils/lib/lodash/cloneDeepWith.js';
|
|
|
|
|
+import isObject from '../utils/lib/lodash/isObject.js';
|
|
|
|
|
|
|
|
const bindToSymbol = Symbol( 'bindTo' );
|
|
const bindToSymbol = Symbol( 'bindTo' );
|
|
|
const bindIfSymbol = Symbol( 'bindIf' );
|
|
const bindIfSymbol = Symbol( 'bindIf' );
|
|
@@ -401,17 +402,20 @@ export default class Template {
|
|
|
|
|
|
|
|
for ( attrName in attributes ) {
|
|
for ( attrName in attributes ) {
|
|
|
attrValue = attributes[ attrName ];
|
|
attrValue = attributes[ attrName ];
|
|
|
- attrNs = attrValue[ 0 ].ns || null;
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // Detect custom namespace:
|
|
|
|
|
+ // { class: { ns: 'abc', value: Template.bind( ... ).to( ... ) } }
|
|
|
|
|
+ attrNs = isObject( attrValue[ 0 ] ) && attrValue[ 0 ].ns ? attrValue[ 0 ].ns : null;
|
|
|
|
|
|
|
|
// Activate binding if one is found. Cases:
|
|
// Activate binding if one is found. Cases:
|
|
|
// { class: [ Template.bind( ... ).to( ... ) ] }
|
|
// { class: [ Template.bind( ... ).to( ... ) ] }
|
|
|
// { class: [ 'bar', Template.bind( ... ).to( ... ), 'baz' ] }
|
|
// { class: [ 'bar', Template.bind( ... ).to( ... ), 'baz' ] }
|
|
|
// { class: { ns: 'abc', value: Template.bind( ... ).to( ... ) } }
|
|
// { class: { ns: 'abc', value: Template.bind( ... ).to( ... ) } }
|
|
|
if ( hasBinding( attrValue ) ) {
|
|
if ( hasBinding( attrValue ) ) {
|
|
|
- // Normalize attributes with additional data like namespace:
|
|
|
|
|
- // { class: { ns: 'abc', value: [ ... ] } }
|
|
|
|
|
this._bindToObservable(
|
|
this._bindToObservable(
|
|
|
- attrValue[ 0 ].value || attrValue,
|
|
|
|
|
|
|
+ // Normalize attributes with additional data like namespace:
|
|
|
|
|
+ // { class: { ns: 'abc', value: [ ... ] } }
|
|
|
|
|
+ attrNs ? attrValue[ 0 ].value : attrValue,
|
|
|
el,
|
|
el,
|
|
|
getAttributeUpdater( el, attrName, attrNs )
|
|
getAttributeUpdater( el, attrName, attrNs )
|
|
|
);
|
|
);
|
|
@@ -437,9 +441,11 @@ export default class Template {
|
|
|
// Flatten the array.
|
|
// Flatten the array.
|
|
|
.reduce( ( p, n ) => p.concat( n ), [] )
|
|
.reduce( ( p, n ) => p.concat( n ), [] )
|
|
|
// Convert into string.
|
|
// Convert into string.
|
|
|
- .reduce( arrayValueReducer );
|
|
|
|
|
|
|
+ .reduce( arrayValueReducer, '' );
|
|
|
|
|
|
|
|
- el.setAttributeNS( attrNs, attrName, attrValue );
|
|
|
|
|
|
|
+ if ( !isFalsy( attrValue ) ) {
|
|
|
|
|
+ el.setAttributeNS( attrNs, attrName, attrValue );
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -553,9 +559,11 @@ export default class Template {
|
|
|
*/
|
|
*/
|
|
|
_bindToObservable( valueSchema ) {
|
|
_bindToObservable( valueSchema ) {
|
|
|
valueSchema
|
|
valueSchema
|
|
|
- // Filter inactive bindings from schema, like static strings, etc.
|
|
|
|
|
|
|
+ // Filter "falsy" (false, undefined, null, '') value schema components out.
|
|
|
|
|
+ .filter( item => !isFalsy( item ) )
|
|
|
|
|
+ // Filter inactive bindings from schema, like static strings ('foo'), numbers (42), etc.
|
|
|
.filter( item => item.observable )
|
|
.filter( item => item.observable )
|
|
|
- // Let the emitter listen to observable change:attribute event.
|
|
|
|
|
|
|
+ // Once only the actual binding are left, let the emitter listen to observable change:attribute event.
|
|
|
// TODO: Reduce the number of listeners attached as many bindings may listen
|
|
// TODO: Reduce the number of listeners attached as many bindings may listen
|
|
|
// to the same observable attribute.
|
|
// to the same observable attribute.
|
|
|
.forEach( ( { emitter, observable, attribute } ) => {
|
|
.forEach( ( { emitter, observable, attribute } ) => {
|
|
@@ -605,9 +613,9 @@ function hasBinding( valueSchema ) {
|
|
|
// @return {Array}
|
|
// @return {Array}
|
|
|
function getBindingValue( valueSchema, domNode ) {
|
|
function getBindingValue( valueSchema, domNode ) {
|
|
|
return valueSchema.map( schemaItem => {
|
|
return valueSchema.map( schemaItem => {
|
|
|
- let { observable, callback, type } = schemaItem;
|
|
|
|
|
-
|
|
|
|
|
- if ( observable ) {
|
|
|
|
|
|
|
+ // Process {@link ui.TemplateBinding} bindings.
|
|
|
|
|
+ if ( isObject( schemaItem ) ) {
|
|
|
|
|
+ let { observable, callback, type } = schemaItem;
|
|
|
let modelValue = observable[ schemaItem.attribute ];
|
|
let modelValue = observable[ schemaItem.attribute ];
|
|
|
|
|
|
|
|
// Process the value with the callback.
|
|
// Process the value with the callback.
|
|
@@ -616,13 +624,14 @@ function getBindingValue( valueSchema, domNode ) {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if ( type === bindIfSymbol ) {
|
|
if ( type === bindIfSymbol ) {
|
|
|
- return !!modelValue ? schemaItem.valueIfTrue || true : '';
|
|
|
|
|
|
|
+ return isFalsy( modelValue ) ? false : ( schemaItem.valueIfTrue || true );
|
|
|
} else {
|
|
} else {
|
|
|
return modelValue;
|
|
return modelValue;
|
|
|
}
|
|
}
|
|
|
- } else {
|
|
|
|
|
- return schemaItem;
|
|
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ // All static values like strings, numbers, and "falsy" values (false, null, undefined, '', etc.) just pass.
|
|
|
|
|
+ return schemaItem;
|
|
|
} );
|
|
} );
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -634,26 +643,19 @@ function getBindingValue( valueSchema, domNode ) {
|
|
|
// @param {Function} domUpdater A function which updates DOM (like attribute or text).
|
|
// @param {Function} domUpdater A function which updates DOM (like attribute or text).
|
|
|
function syncBinding( valueSchema, domNode, domUpdater ) {
|
|
function syncBinding( valueSchema, domNode, domUpdater ) {
|
|
|
let value = getBindingValue( valueSchema, domNode );
|
|
let value = getBindingValue( valueSchema, domNode );
|
|
|
- let shouldSet;
|
|
|
|
|
|
|
|
|
|
// Check if valueSchema is a single Template.bind.if, like:
|
|
// Check if valueSchema is a single Template.bind.if, like:
|
|
|
// { class: Template.bind.if( 'foo' ) }
|
|
// { class: Template.bind.if( 'foo' ) }
|
|
|
if ( valueSchema.length == 1 && valueSchema[ 0 ].type == bindIfSymbol ) {
|
|
if ( valueSchema.length == 1 && valueSchema[ 0 ].type == bindIfSymbol ) {
|
|
|
value = value[ 0 ];
|
|
value = value[ 0 ];
|
|
|
- shouldSet = value !== '';
|
|
|
|
|
-
|
|
|
|
|
- if ( shouldSet ) {
|
|
|
|
|
- value = value === true ? '' : value;
|
|
|
|
|
- }
|
|
|
|
|
} else {
|
|
} else {
|
|
|
value = value.reduce( arrayValueReducer, '' );
|
|
value = value.reduce( arrayValueReducer, '' );
|
|
|
- shouldSet = value;
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if ( shouldSet ) {
|
|
|
|
|
- domUpdater.set( value );
|
|
|
|
|
- } else {
|
|
|
|
|
|
|
+ if ( isFalsy( value ) ) {
|
|
|
domUpdater.remove();
|
|
domUpdater.remove();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ domUpdater.set( value );
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -889,10 +891,13 @@ function arrayify( obj, key ) {
|
|
|
// @param {String} cur
|
|
// @param {String} cur
|
|
|
// @returns {String}
|
|
// @returns {String}
|
|
|
function arrayValueReducer( prev, cur ) {
|
|
function arrayValueReducer( prev, cur ) {
|
|
|
- return prev === '' ?
|
|
|
|
|
- `${cur}`
|
|
|
|
|
- :
|
|
|
|
|
- cur === '' ? `${prev}` : `${prev} ${cur}`;
|
|
|
|
|
|
|
+ if ( isFalsy( cur ) ) {
|
|
|
|
|
+ return prev;
|
|
|
|
|
+ } else if ( isFalsy( prev ) ) {
|
|
|
|
|
+ return cur;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return `${prev} ${cur}`;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Extends one object defined in the following format:
|
|
// Extends one object defined in the following format:
|
|
@@ -961,6 +966,11 @@ function extendTemplateDefinition( def, extDef ) {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+function isFalsy( value ) {
|
|
|
|
|
+ // Note 0 is not falsy in this context.
|
|
|
|
|
+ return !value && value !== 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* A definition of {@link ui.Template}.
|
|
* A definition of {@link ui.Template}.
|
|
|
* See: {@link ui.TemplateValueSchema}.
|
|
* See: {@link ui.TemplateValueSchema}.
|