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
3 changes: 2 additions & 1 deletion packages/joint-core/src/mvc/View.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export const View = ViewBase.extend({

options: {},
theme: null,
classNamePrefix: config.classNamePrefix,
themeClassNamePrefix: util.addClassNamePrefix('theme-'),
requireSetThemeOverride: false,
defaultTheme: config.defaultTheme,
Expand Down Expand Up @@ -134,7 +135,7 @@ export const View = ViewBase.extend({
_ensureElClassName: function() {
var className = util.result(this, 'className');
if (!className) return;
var prefixedClassName = util.addClassNamePrefix(className);
var prefixedClassName = util.addClassNamePrefix(className, this.classNamePrefix);
// Note: className removal here kept for backwards compatibility only
if (this.svgElement) {
this.vel.removeClass(className).addClass(prefixedClassName);
Expand Down
16 changes: 8 additions & 8 deletions packages/joint-core/src/util/util.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -43,29 +43,29 @@ import {
merge
} from './utilHelpers.mjs';

export const addClassNamePrefix = function(className) {
export const addClassNamePrefix = function(className, prefix = config.classNamePrefix) {

if (!className) return className;
if (!className || !prefix) return className;

return className.toString().split(' ').map(function(_className) {

if (_className.substr(0, config.classNamePrefix.length) !== config.classNamePrefix) {
_className = config.classNamePrefix + _className;
if (_className.substr(0, prefix.length) !== prefix) {
_className = prefix + _className;
}

return _className;

}).join(' ');
};

export const removeClassNamePrefix = function(className) {
export const removeClassNamePrefix = function(className, prefix = config.classNamePrefix) {

if (!className) return className;
if (!className || !prefix) return className;

return className.toString().split(' ').map(function(_className) {

if (_className.substr(0, config.classNamePrefix.length) === config.classNamePrefix) {
_className = _className.substr(config.classNamePrefix.length);
if (_className.substr(0, prefix.length) === prefix) {
_className = _className.substr(prefix.length);
}

return _className;
Expand Down
52 changes: 52 additions & 0 deletions packages/joint-core/test/jointjs/core/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -882,6 +882,31 @@ QUnit.module('util', function(hooks) {

assert.equal(joint.util.addClassNamePrefix('some-class some-other-class'), joint.config.classNamePrefix + 'some-class ' + joint.config.classNamePrefix + 'some-other-class');
});

QUnit.test('custom prefix provided', function(assert) {

assert.equal(joint.util.addClassNamePrefix('some-class', 'custom-'), 'custom-some-class');
});

QUnit.test('multiple class names with custom prefix', function(assert) {

assert.equal(joint.util.addClassNamePrefix('a b', 'x-'), 'x-a x-b');
});

QUnit.test('empty string prefix is a no-op', function(assert) {

assert.equal(joint.util.addClassNamePrefix('some-class', ''), 'some-class');
});

QUnit.test('null prefix is a no-op', function(assert) {

assert.equal(joint.util.addClassNamePrefix('some-class', null), 'some-class');
});

QUnit.test('idempotent with custom prefix', function(assert) {

assert.equal(joint.util.addClassNamePrefix('custom-some-class', 'custom-'), 'custom-some-class');
});
});

QUnit.module('removeClassNamePrefix', function(hooks) {
Expand Down Expand Up @@ -919,6 +944,33 @@ QUnit.module('util', function(hooks) {

assert.equal(joint.util.removeClassNamePrefix(joint.config.classNamePrefix + 'some-class without-prefix'), 'some-class without-prefix');
});

QUnit.test('custom prefix provided', function(assert) {

assert.equal(joint.util.removeClassNamePrefix('custom-some-class', 'custom-'), 'some-class');
});

QUnit.test('multiple prefixed class names with custom prefix', function(assert) {

assert.equal(joint.util.removeClassNamePrefix('x-a x-b', 'x-'), 'a b');
});

QUnit.test('empty string prefix is a no-op', function(assert) {

var input = joint.config.classNamePrefix + 'some-class';
assert.equal(joint.util.removeClassNamePrefix(input, ''), input);
});

QUnit.test('null prefix is a no-op', function(assert) {

var input = joint.config.classNamePrefix + 'some-class';
assert.equal(joint.util.removeClassNamePrefix(input, null), input);
});

QUnit.test('custom prefix ignores joint- prefixed names', function(assert) {

assert.equal(joint.util.removeClassNamePrefix(joint.config.classNamePrefix + 'some-class', 'custom-'), joint.config.classNamePrefix + 'some-class');
});
});

QUnit.module('wrapWith', function(hooks) {
Expand Down
91 changes: 91 additions & 0 deletions packages/joint-core/test/jointjs/mvc.view.js
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,97 @@ QUnit.module('joint.mvc.View', function(hooks) {
assert.equal(view.$el.attr('class'), joint.util.addClassNamePrefix(className) + ' ' + themeClassName);
});

QUnit.module('classNamePrefix override', function() {

QUnit.test('default prefix unchanged', function(assert) {

var SomeView = joint.mvc.View.extend({
className: 'foo',
defaultTheme: null
});
var view = new SomeView();
assert.equal(view.el.className, 'joint-foo');
view.remove();
});

QUnit.test('custom prefix on prototype', function(assert) {

var SomeView = joint.mvc.View.extend({
className: 'foo',
classNamePrefix: 'custom-',
defaultTheme: null
});
var view = new SomeView();
assert.equal(view.el.className, 'custom-foo');
view.remove();
});

QUnit.test('empty prefix opts out of prefixing', function(assert) {

var SomeView = joint.mvc.View.extend({
className: 'foo',
classNamePrefix: '',
defaultTheme: null
});
var view = new SomeView();
assert.equal(view.el.className, 'foo');
view.remove();
});

QUnit.test('multiple class names with custom prefix', function(assert) {

var SomeView = joint.mvc.View.extend({
className: 'foo bar',
classNamePrefix: 'x-',
defaultTheme: null
});
var view = new SomeView();
assert.equal(view.el.className, 'x-foo x-bar');
view.remove();
});

QUnit.test('idempotent — already-prefixed class names are not double-prefixed', function(assert) {

var SomeView = joint.mvc.View.extend({
className: 'x-foo',
classNamePrefix: 'x-',
defaultTheme: null
});
var view = new SomeView();
assert.equal(view.el.className, 'x-foo');
view.remove();
});

QUnit.test('SVG element with custom prefix', function(assert) {

var SomeView = joint.mvc.View.extend({
svgElement: true,
tagName: 'g',
className: 'foo',
classNamePrefix: 'custom-',
defaultTheme: null
});
var view = new SomeView();
assert.equal(view.el.className.baseVal, 'custom-foo');
view.remove();
});

QUnit.test('theme class still uses joint- prefix', function(assert) {

var SomeView = joint.mvc.View.extend({
className: 'foo',
classNamePrefix: 'custom-'
});
var view = new SomeView();
var defaultTheme = joint.mvc.View.prototype.defaultTheme;
var themeClassName = SomeView.prototype.themeClassNamePrefix + defaultTheme;
assert.ok(view.$el.hasClass('custom-foo'), 'custom prefix applied to className');
assert.ok(view.$el.hasClass(themeClassName), 'theme class still uses joint- prefix');
assert.equal(view.$el.attr('class'), 'custom-foo ' + themeClassName);
view.remove();
});
});

QUnit.test('mvc.View.extend does not modify prototype or static properties objects', function(assert) {

var protoProps = {};
Expand Down
2 changes: 2 additions & 0 deletions packages/joint-core/types/mvc.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,8 @@ export class View<T extends (Model | undefined), E extends DOMElement = HTMLElem

options: ViewOptions<T, E>;

classNamePrefix: string;

theme: string;

themeClassNamePrefix: string;
Expand Down
7 changes: 2 additions & 5 deletions packages/joint-react/src/presets/paper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ const linkView = (
};

export const Paper = dia.Paper.extend({
className: 'jj-paper joint-paper',
classNamePrefix: '',
options: {
...dia.Paper.prototype.options,
// Required for React integration features:
Expand All @@ -84,9 +86,4 @@ export const Paper = dia.Paper.extend({
measureNode,
linkView,
},

_ensureElClassName() {
// Note: the `className` property is ignored here.
this.el.classList.add('jj-paper', 'joint-paper');
},
}) as typeof dia.Paper;