Browse Source

Merge 0cec4b1b25 into 6370546f43

pull/6688/merge
Kris 3 weeks ago
committed by GitHub
parent
commit
2d02fdf56c
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 21
      docs/api/datasources.md
  2. 2
      packages/core/src/trait_manager/config/config.ts
  3. 5
      packages/core/src/trait_manager/view/TraitView.ts
  4. 70
      packages/core/src/trait_manager/view/TraitsView.ts
  5. 42
      packages/core/test/specs/trait_manager/view/TraitsView.ts

21
docs/api/datasources.md

@ -113,6 +113,18 @@ const ds = dsm.get('my_data_source_id');
Returns **[DataSource]** Data source.
## getAll
Return all data sources.
### Examples
```javascript
const ds = dsm.getAll();
```
Returns **[Array][8]<[DataSource]>**&#x20;
## getValue
Get value from data sources by path.
@ -121,6 +133,7 @@ Get value from data sources by path.
* `path` **[String][7]** Path to value.
* `defValue` **any** Default value if the path is not found.
* `opts` **{context: Record<[string][7], any>?}?**&#x20;
Returns **any** const value = dsm.getValue('ds\_id.record\_id.propName', 'defaultValue');
@ -139,7 +152,7 @@ Set value in data sources by path.
dsm.setValue('ds_id.record_id.propName', 'new value');
```
Returns **[Boolean][8]** Returns true if the value was set successfully
Returns **[Boolean][9]** Returns true if the value was set successfully
## remove
@ -183,7 +196,7 @@ data record, and optional property path.
Store data sources to a JSON object.
Returns **[Array][9]** Stored data sources.
Returns **[Array][8]** Stored data sources.
## load
@ -209,6 +222,6 @@ Returns **[Object][6]** Loaded data sources.
[7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[8]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
[8]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
[9]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
[9]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean

2
packages/core/src/trait_manager/config/config.ts

@ -20,6 +20,8 @@ export interface TraitManagerConfig {
custom?: boolean;
optionsTarget?: Record<string, any>[];
customTraitValidationRegex?: RegExp;
}
const config: () => TraitManagerConfig = () => ({

5
packages/core/src/trait_manager/view/TraitView.ts

@ -261,7 +261,7 @@ export default class TraitView extends View<Trait> {
}
render() {
const { $el, pfx, ppfx, model } = this;
const { $el, pfx, ppfx, model, config } = this;
const { type, id } = model.attributes;
const hasLabel = this.hasLabel && this.hasLabel();
const cls = `${pfx}trait`;
@ -277,7 +277,8 @@ export default class TraitView extends View<Trait> {
: ''
}
</div>
</div>`;
</div>
`;
$el.empty().append(tmpl);
hasLabel && this.renderLabel();
this.renderField();

70
packages/core/src/trait_manager/view/TraitsView.ts

@ -1,5 +1,6 @@
import TraitManager from '..';
import CategoryView from '../../abstract/ModuleCategoryView';
import Component from '../../dom_components/model/Component';
import DomainViews from '../../domain_abstract/view/DomainViews';
import EditorModel from '../../editor/model/Editor';
import Trait from '../model/Trait';
@ -16,6 +17,7 @@ interface TraitsViewProps {
const ATTR_CATEGORIES = 'data-categories';
const ATTR_NO_CATEGORIES = 'data-no-categories';
const CUSTOM_TRAIT_VALIDATION_REGEX_DEFAULT = new RegExp('^data-[a-z][a-z0-9-]*$');
export default class TraitsView extends DomainViews {
reuseView = true;
@ -33,6 +35,55 @@ export default class TraitsView extends DomainViews {
itemsView: TraitManager['types'];
collection: Traits;
events() {
return {
'click [data-add-trait]': 'handleAddTrait',
'keyup [data-add-trait-input]': 'handleInputKeyup',
};
}
handleAddTrait() {
const input = this.el.querySelector('[data-add-trait-input]') as HTMLInputElement;
const name = input.value.trim();
if (name) {
const component = this.validateTraitName(name);
if (!component) {
return;
}
const attributes = component.getAttributes();
if (attributes[name]) {
return;
}
component.addTrait([name]);
component.addAttributes({ [name]: '' });
input.value = '';
}
}
handleInputKeyup(e: KeyboardEvent) {
if (e.key === 'Enter') {
this.handleAddTrait();
}
}
validateTraitName(name: string): Component | undefined {
const component = this.em.getSelected();
if (component) {
const attributes = component.getAttributes();
if (attributes[name]) {
return undefined;
}
if (this.config.customTraitValidationRegex) {
return this.config.customTraitValidationRegex.test(name) ? component : undefined;
}
return CUSTOM_TRAIT_VALIDATION_REGEX_DEFAULT.test(name) ? component : undefined;
}
return undefined;
}
constructor(props: TraitsViewProps, itemsView: TraitManager['types']) {
super(props);
this.itemsView = itemsView;
@ -61,7 +112,14 @@ export default class TraitsView extends DomainViews {
const { ppfx, em } = this;
const comp = em.getSelected();
this.el.className = `${this.traitContClass}s ${ppfx}one-bg ${ppfx}two-color`;
if (this.collection) {
this.stopListening(this.collection);
}
this.collection = comp?.traits || new Traits([], { em });
const collection = this.collection;
this.listenTo(collection, 'add', (model) => this.add(model));
this.listenTo(collection, 'reset', this.render);
this.listenTo(collection, 'remove', this.render);
this.render();
}
@ -125,14 +183,24 @@ export default class TraitsView extends DomainViews {
}
render() {
const { el, ppfx, catsClass, traitContClass, classNoCat } = this;
const { el, ppfx, catsClass, traitContClass, classNoCat, config } = this;
const frag = document.createDocumentFragment();
delete this.catsEl;
delete this.traitsEl;
this.renderedCategories = new Map();
const iconAdd = (config as any).iconAdd || '+';
el.innerHTML = `
<div class="${catsClass}" ${ATTR_CATEGORIES}></div>
<div class="${classNoCat} ${traitContClass}" ${ATTR_NO_CATEGORIES}></div>
<div class="${ppfx}traits-footer" style="padding: 10px; display: flex; align-items: center; gap: 5px;">
<div class="${ppfx}field">
<input placeholder="Attribute name" data-add-trait-input style="flex-grow: 1;"/>
</div>
<button class="${ppfx}btn-prim" data-add-trait>
${iconAdd}
</button>
</div>
`;
this.collection.forEach((model) => this.add(model, frag));

42
packages/core/test/specs/trait_manager/view/TraitsView.ts

@ -1,5 +1,6 @@
import Trait from '../../../../src/trait_manager/model/Trait';
import TraitView from '../../../../src/trait_manager/view/TraitView';
import TraitsView from '../../../../src/trait_manager/view/TraitsView';
import Component from '../../../../src/dom_components/model/Component';
import EditorModel from '../../../../src/editor/model/Editor';
import Editor from '../../../../src/editor';
@ -70,3 +71,44 @@ describe('TraitView', () => {
expect(target2.get('attributes')).toEqual(eq2);
});
});
describe('TraitsView', () => {
let em: EditorModel;
let traitsView: TraitsView;
let component: Component;
beforeEach(() => {
em = new Editor().getModel();
component = new Component({}, { em, config: em.Components.config });
em.setSelected(component);
});
test('validateTraitName with default regex', () => {
traitsView = new TraitsView({
collection: [],
editor: em,
config: em.Traits.getConfig(),
}, {});
expect(traitsView.validateTraitName('data-test')).toBeTruthy();
expect(traitsView.validateTraitName('invalid name')).toBeFalsy();
});
test('validateTraitName with custom regex', () => {
const config = { ...em.Traits.getConfig(), customTraitValidationRegex: /^[a-z]+$/ };
traitsView = new TraitsView({
collection: [],
editor: em,
config,
}, {});
expect(traitsView.validateTraitName('abc')).toBeTruthy();
expect(traitsView.validateTraitName('abc1')).toBeFalsy();
});
test('validateTraitName returns undefined if attribute exists', () => {
component.addAttributes({ 'data-test': 'value' });
traitsView = new TraitsView({ collection: [], editor: em, config: em.Traits.getConfig() }, {});
expect(traitsView.validateTraitName('data-test')).toBeUndefined();
});
});

Loading…
Cancel
Save