Skip to content
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

Expose public API for refreshing widget mask and parts #3857

Merged
merged 23 commits into from
Feb 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
f17cfb0
Expose method for refreshing widget mask.
Dumluregn Feb 5, 2020
e5e4549
Expose 'refreshParts()' method.
Dumluregn Feb 11, 2020
cf43bb7
Add manual test.
Dumluregn Feb 12, 2020
2e51c56
Add unit test.
Dumluregn Feb 12, 2020
6e0b398
Update existing test according to new spec.
Dumluregn Feb 12, 2020
d0a59f1
Fix naming.
Dumluregn Feb 12, 2020
9886698
Unignore manual test on mobiles.
Dumluregn Feb 12, 2020
63cf47e
Add API docs reference about refreshMask() and refreshParts().
Dumluregn Feb 12, 2020
0fbd9ad
Use single quotes instead of single ones.
Dumluregn Feb 14, 2020
78eb17d
Introduce partSelectors to reduce impact on other parts of code.
Dumluregn Feb 15, 2020
b68ad47
Rename and adjust manual tests.
Dumluregn Feb 15, 2020
eb9ac60
Revert unit test to previous form.
Dumluregn Feb 15, 2020
6bce733
Rename unit test and adjust expected behavior for widget parts.
Dumluregn Feb 15, 2020
390198c
Add docs and fix spelling typo.
Dumluregn Feb 15, 2020
5005b92
Add reference about refreshing manually created widget parts.
Dumluregn Feb 15, 2020
bf00974
Change default value for 'refresgInitialized' flag.
Dumluregn Feb 17, 2020
9cf4de0
Minor rewording. [skip ci]
f1ames Feb 18, 2020
a53f57c
Tests: Manual scenario minor rewording. [skip ci]
f1ames Feb 18, 2020
e04e65f
Change REAL default value for flag.
Dumluregn Feb 18, 2020
c890c35
Add unit test for refreshParts() method.
Dumluregn Feb 18, 2020
ef3b3f0
Add ticket references to unit tests.
Dumluregn Feb 18, 2020
6f8abf6
Rename existing test and create new as a replacement.
Dumluregn Feb 18, 2020
16577ac
Changelog entry.
f1ames Feb 18, 2020
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 CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ API Changes:
* [#3727](https://github.com/ckeditor/ckeditor4/issues/3727): Added new commands `textColor` and `bgColor` which applies the selected color choosen by the [Color Button](https://ckeditor.com/cke4/addon/colorbutton) plugin.
* [#3728](https://github.com/ckeditor/ckeditor4/issues/3728): Added new commands `font` and `fontSize` which applies the selected font style choosen by the [Font](https://ckeditor.com/cke4/addon/colorbutton) plugin.
* [#3842](https://github.com/ckeditor/ckeditor4/issues/3842): Added [`editor.getSelectedRanges`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_editor.html#method-getSelectedRanges) alias.
* [#3775](https://github.com/ckeditor/ckeditor4/issues/3775): Widget [mask](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_plugins_widget.html#property-mask) and [parts](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_plugins_widget.html#property-parts) can now be refreshed dynamically via API calls.

## CKEditor 4.13.1

Expand Down
57 changes: 51 additions & 6 deletions plugins/widget/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -986,11 +986,23 @@
*
* For every `partName => selector` pair in {@link CKEDITOR.plugins.widget.definition#parts},
* one `partName => element` pair is added to this object during the widget initialization.
* Parts can be reinitialized with {@link #refreshParts} method.
*
* @readonly
* @property {Object} parts
*/

/**
* An object containing definitions of widget parts (`part name => CSS selector`).
*
* Unlike {@link #parts} object, it stays unchanged throughout the widget lifecycle
* and is used in {@link #refreshParts} method.
*
* @readonly
* @property {Object} partSelectors
* @since 4.14.0
*/

/**
* The template which will be used to create a new widget element (when the widget's command is executed).
* It will be populated with {@link #defaults default values}.
Expand Down Expand Up @@ -1420,6 +1432,31 @@
this.editor.focus();
},

/**
* Refreshes widget's mask. Can be used together with {@link #refreshParts} method to reinitialize mask
* for dynamically created widgets.
*
* @since 4.14.0
*/
refreshMask: function() {
setupMask( this );
},

/**
* Reinitializes widget's {@link #parts}.
*
* This method can be used to link new DOM elements to widget parts, for example in case when widget's HTML is created
* asynchronously or modified during widget lifecycle. Note that it uses {@link #partSelectors} object, so it doesn't
* refresh parts that were created manually.
*
* @since 4.14.0
* @param {Boolean} [refreshInitialized=true] Whether parts that are already initialized should be reinitialized.
*/
refreshParts: function( refreshInitialized ) {
refreshInitialized = typeof refreshInitialized !== 'undefined' ? refreshInitialized : true;
setupParts( this, refreshInitialized );
},

/**
* Removes a class from the widget element. This method is used by
* the {@link #removeStyle} method and should be overriden by widgets
Expand Down Expand Up @@ -3598,15 +3635,23 @@
// partName => selector pairs
// with:
// partName => element pairs
function setupParts( widget ) {
function setupParts( widget, refreshInitialized ) {
if ( !widget.partSelectors ) {
widget.partSelectors = widget.parts;
}

if ( widget.parts ) {
var parts = {},
el,
partName;

for ( partName in widget.parts ) {
el = widget.wrapper.findOne( widget.parts[ partName ] );
parts[ partName ] = el;
for ( partName in widget.partSelectors ) {
if ( refreshInitialized || !widget.parts[ partName ] || typeof widget.parts[ partName ] == 'string' ) {
el = widget.wrapper.findOne( widget.partSelectors[ partName ] );
parts[ partName ] = el;
} else {
parts[ partName ] = widget.parts[ partName ];
}
}
widget.parts = parts;
}
Expand Down Expand Up @@ -3727,8 +3772,8 @@
var part = this.parts[ this.maskPart ],
mask;

// If requested part is invalid, don't create mask.
if ( !part ) {
// If requested part is invalid or wasn't fetched yet (#3775), don't create mask.
if ( !part || typeof part == 'string' ) {
return;
}

Expand Down
62 changes: 62 additions & 0 deletions tests/plugins/widget/definition.js
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,68 @@
} );
},

// (#3775)
'test parts refresh': function() {
var editor = this.editor,
bot = this.editorBot;

var widgetDef = {
parts: {
first: '.first',
second: '.second',
third: '.third'
}
};

editor.widgets.add( 'testparts2', widgetDef );

bot.setData( '<div data-widget="testparts2" id="w"><p class="first">Hello</p><p class="third">!</p></div>', function() {
var missingPart = CKEDITOR.dom.element.createFromHtml( '<p class="second">World</p>' ),
widget = getWidgetById( editor, 'w' );

assert.areEqual( 2, widget.element.getChildCount(), 'Middle part of widget should be missing.' );
assert.areEqual( widget.parts.second, null, 'Middle part of widget should be missing.' );

missingPart.insertAfter( widget.wrapper.findOne( '.first' ) );

widget.refreshParts();

assert.areEqual( 3, widget.element.getChildCount(), 'Middle part of widget should be inserted.' );
assert.areEqual( widget.parts.second, missingPart, 'Middle part of widget should be inserted.' );
} );
},

// (#3775)
'test parts refresh flag': function() {
var editor = this.editor,
bot = this.editorBot;

var widgetDef = {
parts: {
first: '.first',
second: '.second'
}
};

editor.widgets.add( 'testparts2', widgetDef );

bot.setData( '<div data-widget="testparts2" id="w"><p class="first">Hello</p><p class="second">World!</p></div>', function() {
var widget = getWidgetById( editor, 'w' ),
partToRefresh = CKEDITOR.dom.element.createFromHtml( '<p class="second">World!</p>' );

widget.wrapper.findOne( '.second' ).remove();
partToRefresh.insertAfter( widget.element.findOne( '.first' ) );

widget.refreshParts( false );

assert.areEqual( widget.parts.second.getParent(), null, 'Deleted widget part should not be reinitialized.' );

widget.refreshParts();

assert.areEqual( widget.parts.second.getParent(), widget.element, 'Deleted widget part should be reinitialized.' );
} );
},

'test defined dialog name': function() {
var editor = this.editor;

Expand Down
87 changes: 87 additions & 0 deletions tests/plugins/widget/manual/refreshmaskapi.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<div id="editor">
<p>bar</p>
<div id="widget" class="partiallyMasked"><div class="notmasked">You can type here...</div></div>
</div>
<div>
<button id="add-content">Add missing widget HTML</button>
</div>
<div>
<button id="refresh-parts">Refresh widget parts</button>
</div>
<div>
<button id="refresh-mask">Refresh widget mask</button>
</div>

<script>
CKEDITOR.addCss( '.left { float: left; width: 200px; } .right { float: right; width: 200px; } .cke_widget_partial_mask { border: 1px solid red };' );

CKEDITOR.stylesSet.add( 'default', [
{ name: 'None', type: 'widget', widget: 'partiallyMasked', attributes: { 'class': '' }, group: 'alignment' },
{ name: 'Left', type: 'widget', widget: 'partiallyMasked', attributes: { 'class': 'left' }, group: 'alignment' },
{ name: 'Right', type: 'widget', widget: 'partiallyMasked', attributes: { 'class': 'right' }, group: 'alignment' }
] );

CKEDITOR.plugins.add( 'partiallyMasked', {
requires: 'widget',
init: function( editor ) {
editor.widgets.add( 'partiallyMasked', {
button: 'Partial mask widget',
pathName: 'test-widget',

template:
'<div class="partiallyMasked">' +
'<div class="notmasked"></div>' +
'<div class="content"></div>' +
'</div>',

editables: {
nomask: {
selector: '.notmasked',
allowedContent: 'br'
},
content: {
selector: '.content',
allowedContent: 'br'
}
},

parts: {
nomask: '.notmasked',
content: '.content'
},

allowedContent: 'div(partiallyMasked,content,notmasked,left,right)',
requiredContent: 'div(partiallyMasked)',

mask: 'content',

upcast: function( element ) {
return element.name == 'div' && element.hasClass( 'partiallyMasked' );
}
} );
}
} );

var editor = CKEDITOR.replace( 'editor', {
extraPlugins: 'partiallyMasked'
} );

CKEDITOR.document.findOne( '#add-content' ).on( 'click', function() {
var div = new CKEDITOR.dom.element( 'div' ),
widget = editor.widgets.instances[ 0 ];

if ( !widget.element.findOne( '.content' ) ) {
div.setText( '...but not here' );
div.addClass( 'content' );

widget.element.append( div );
}
} );
CKEDITOR.document.findOne( '#refresh-parts' ).on( 'click', function() {
editor.widgets.instances[ 0 ].refreshParts();
} );
CKEDITOR.document.findOne( '#refresh-mask' ).on( 'click', function() {
editor.widgets.instances[ 0 ].refreshMask();
} );

</script>
68 changes: 68 additions & 0 deletions tests/plugins/widget/manual/refreshmaskapi.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
@bender-ui: collapsed
@bender-tags: 4.14.0, feature, 3775
@bender-ckeditor-plugins: wysiwygarea,toolbar,clipboard,image2,sourcearea,list,undo,stylescombo

**Note:** Mask has a red border so it's positioning is visible without inspecting mask element,
but still keep the console opened and watch out for errors.

1. Click `Refresh widget mask` button below editor.

**Expected:**

Nothing happened.

**Unexpected:**

Error was thrown.

1. Click `Add missing widget HTML` button below editor.

**Expected:**

Widget expanded by the second row. It's not clickable, but there is no mask (red border).

**Unexpected:**

Second row didn't appear.

1. Click `Refresh widget mask` button below editor.

**Expected:**

Nothing happened.

**Unexpected:**

Error was thrown.

1. Click `Refresh widget parts` button below editor.

1. Click `Refresh widget mask` button below editor.

**Expected:**

Mask appeared over the second row.

**Unexpected:**

Mask didn't appear or error was thrown.

1. Add a new row in the widget's editable.

**Expected:**

Mask moved and still covers the right part of widget.

**Unexpected:**

Mask didn't move.

1. Switch to source mode and back.

**Expected:**

Mask is still visible and in the right place.

**Unexpected:**

Mask disappeared.
Loading