What problems do you have with custom tags? And on which browsers?
I checked that CKEditor saves this tag, but wraps it with all the contents. To avoid this, you need to edit CKEDITOR.dtd , namely:
CKEDITOR.dtd.$empty[ 'r:product_listing' ] = 1;
But this may still not be enough. In order to get better support, you will need to make more changes to this object - it is especially important to determine what its parents can be and what is the built-in tag. For instance:
CKEDITOR.dtd.p[ 'r:product_listing' ] = 1; // it is allowed in <p> tag CKEDITOR.dtd.$inline[ 'r:product_listing' ] = 1;
This may not be enough, for example, you most likely will not have copy and paste support.
So, if you need more reliable support, I would try a little differently. Using CKEDITOR.dataProcessor , you can convert this tag to some ordinary one, when the data is loaded into the editor and when the data is retrieved, convert it back to this tag.
Example solution:
// We still need this, because this tag has to be parsed correctly. CKEDITOR.dtd.p[ 'r:product_listing' ] = 1; CKEDITOR.dtd.$inline[ 'r:product_listing' ] = 1; CKEDITOR.dtd.$empty[ 'r:product_listing' ] = 1; CKEDITOR.replace( 'editor1', { on: { instanceReady: function( evt ) { var editor = evt.editor; // Add filter for html->data transformation. editor.dataProcessor.dataFilter.addRules( { elements: { 'r:product_listing': function( element ) { // Span isn't self closing element - change that. element.isEmpty = false; // Save original element name in data-saved-name attribute. element.attributes[ 'data-saved-name' ] = element.name; // Change name to span. element.name = 'span'; // Push zero width space, because empty span would be removed. element.children.push( new CKEDITOR.htmlParser.text( '\u200b' ) ); } } } ); // Add filter for data->html transformation. editor.dataProcessor.htmlFilter.addRules( { elements: { span: function( element ) { // Restore everything. if ( element.attributes[ 'data-saved-name' ] ) { element.isEmpty = true; element.children = []; element.name = element.attributes[ 'data-saved-name' ]; delete element.attributes[ 'data-saved-name' ] } } } } ); } } } );
Now the r:product_listing element will be converted to a span with zero-width space inside. There will be a normal range inside the editor, but in the source mode and in the data received by the editor#getData() method, you will see the original r:product_listing tag.
I think this solution should be the safest. For instance. copy and paste.