Skip to content

Commit

Permalink
special case handling for plain text attributes, including static cla…
Browse files Browse the repository at this point in the history
…ss- and style- directive aggregation
  • Loading branch information
evs-chris committed Apr 12, 2018
1 parent f9effaf commit 1b58fe5
Show file tree
Hide file tree
Showing 5 changed files with 188 additions and 56 deletions.
71 changes: 70 additions & 1 deletion src/parse/converters/readElement.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { ANCHOR, DOCTYPE, ELEMENT } from 'config/types';
import { ANCHOR, DOCTYPE, ELEMENT, ATTRIBUTE } from 'config/types';
import { voidElements } from 'utils/html';
import { READERS, PARTIAL_READERS } from '../_parse';
import cleanup from 'parse/utils/cleanup';
import readMustache from './readMustache';
import readClosingTag from './element/readClosingTag';
import readClosing from './mustache/section/readClosing';
import { create } from 'utils/object';
import { isString } from 'utils/is';
import hyphenateCamel from 'utils/hyphenateCamel';

const tagNamePattern = /^[a-zA-Z]{1,}:?[a-zA-Z0-9\-]*/;
const anchorPattern = /^[a-zA-Z_$][-a-zA-Z0-9_$]*/;
Expand Down Expand Up @@ -246,6 +248,73 @@ function readElement(parser) {
return exclude;
}

if (
element.m &&
lowerCaseName !== 'input' &&
lowerCaseName !== 'select' &&
lowerCaseName !== 'textarea' &&
lowerCaseName !== 'option'
) {
const attrs = element.m;
let classes, styles, cls, style;
let i = 0;
let a;
while (i < attrs.length) {
a = attrs[i];

if (a.t !== ATTRIBUTE) {
i++;
continue;
}

if (a.n.indexOf('class-') === 0 && !a.f) {
// static class directives
(classes || (classes = [])).push(a.n.slice(6));
attrs.splice(i, 1);
} else if (a.n.indexOf('style-') === 0 && isString(a.f)) {
// static style directives
(styles || (styles = [])).push(`${hyphenateCamel(a.n.slice(6))}: ${a.f};`);
attrs.splice(i, 1);
} else if (a.n === 'class' && isString(a.f)) {
// static class attrs
(classes || (classes = [])).push(a.f);
attrs.splice(i, 1);
} else if (a.n === 'style' && isString(a.f)) {
// static style attrs
(styles || (styles = [])).push(`${a.f};`);
attrs.splice(i, 1);
} else if (a.n === 'class') {
cls = a;
i++;
} else if (a.n === 'style') {
style = a;
i++;
} else if (
!~a.n.indexOf(':') &&
a.n !== 'value' &&
a.n !== 'contenteditable' &&
isString(a.f)
) {
a.g = 1;
i++;
} else {
i++;
}
}

if (classes) {
if (!cls || !isString(cls.f))
attrs.unshift({ t: ATTRIBUTE, n: 'class', f: classes.join(' '), g: 1 });
else cls.f += ' ' + classes.join(' ');
} else if (cls && isString(cls.f)) cls.g = 1;

if (styles) {
if (!style || !isString(style.f))
attrs.unshift({ t: ATTRIBUTE, n: 'style', f: styles.join(' '), g: 1 });
else style.f += '; ' + styles.join(' ');
} else if (style && isString(style.f)) style.g = 1;
}

return element;
}

Expand Down
95 changes: 54 additions & 41 deletions src/view/items/Element.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import ConditionalAttribute from './element/ConditionalAttribute';
import createItem from './createItem';
import findElement from './shared/findElement';
import selectBinding from './element/binding/selectBinding';
import { assign, create, defineProperty } from 'utils/object';
import { assign, create, defineProperty, keys } from 'utils/object';
import { isString } from 'utils/is';

const endsWithSemi = /;\s*$/;

Expand Down Expand Up @@ -45,36 +46,42 @@ export default class Element extends ContainerItem {

for (let i = 0; i < len; i++) {
template = m[i];
switch (template.t) {
case ATTRIBUTE:
case BINDING_FLAG:
case DECORATOR:
case EVENT:
case TRANSITION:
attr = createItem({
owner: this,
up: this.up,
template
});

n = template.n;

attrs = attrs || (attrs = this.attributes = []);

if (n === 'value') val = attr;
else if (n === 'name') name = attr;
else if (n === 'class') cls = attr;
else attrs.push(attr);

break;

case DELEGATE_FLAG:
this.delegate = false;
break;

default:
(leftovers || (leftovers = [])).push(template);
break;
if (template.g) {
(this.statics || (this.statics = {}))[template.n] = isString(template.f)
? template.f
: template.n;
} else {
switch (template.t) {
case ATTRIBUTE:
case BINDING_FLAG:
case DECORATOR:
case EVENT:
case TRANSITION:
attr = createItem({
owner: this,
up: this.up,
template
});

n = template.n;

attrs = attrs || (attrs = this.attributes = []);

if (n === 'value') val = attr;
else if (n === 'name') name = attr;
else if (n === 'class') cls = attr;
else attrs.push(attr);

break;

case DELEGATE_FLAG:
this.delegate = false;
break;

default:
(leftovers || (leftovers = [])).push(template);
break;
}
}
}

Expand Down Expand Up @@ -135,14 +142,6 @@ export default class Element extends ContainerItem {

destroyed() {
if (this.attributes) this.attributes.forEach(destroyed);

if (!this.up.delegate && this.listeners) {
const ls = this.listeners;
for (const k in ls) {
if (ls[k] && ls[k].length) this.node.removeEventListener(k, handler);
}
}

if (this.fragment) this.fragment.destroyed();
}

Expand Down Expand Up @@ -181,6 +180,7 @@ export default class Element extends ContainerItem {
}

getAttribute(name) {
if (this.statics && name in this.statics) return this.statics[name];
const attribute = this.attributeByName[name];
return attribute ? attribute.getValue() : undefined;
}
Expand Down Expand Up @@ -307,6 +307,12 @@ export default class Element extends ContainerItem {
this.node = node;
}

if (this.statics) {
keys(this.statics).forEach(k => {
node.setAttribute(k, this.statics[k]);
});
}

// tie the node to this vdom element
defineProperty(node, '_ractive', {
value: {
Expand Down Expand Up @@ -343,7 +349,8 @@ export default class Element extends ContainerItem {
let i = node.attributes.length;
while (i--) {
const name = node.attributes[i].name;
if (!(name in this.attributeByName)) node.removeAttribute(name);
if (!(name in this.attributeByName) && (!this.statics || !(name in this.statics)))
node.removeAttribute(name);
}
}

Expand Down Expand Up @@ -378,6 +385,11 @@ export default class Element extends ContainerItem {

let attrs = (this.attributes && this.attributes.map(stringifyAttribute).join('')) || '';

if (this.statics)
keys(this.statics).forEach(
k => k !== 'class' && k !== 'style' && (attrs = ` ${k}="${this.statics[k]}"` + attrs)
);

// Special case - selected options
if (this.name === 'option' && this.isSelected()) {
attrs += ' selected';
Expand All @@ -389,7 +401,8 @@ export default class Element extends ContainerItem {
}

// Special case style and class attributes and directives
let style, cls;
let style = this.statics ? this.statics.style : undefined;
let cls = this.statics ? this.statics.class : undefined;
this.attributes &&
this.attributes.forEach(attr => {
if (attr.name === 'class') {
Expand Down
2 changes: 1 addition & 1 deletion src/view/items/element/attribute/getUpdateDelegate.js
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ function updateClassName(reset) {
cls = cls.baseVal !== undefined ? cls.baseVal : cls;

const attr = readClass(cls);
const prev = this.previous || attr.slice(0);
const prev = this.previous || [];

const className = value.concat(attr.filter(c => !~prev.indexOf(c))).join(' ');

Expand Down
50 changes: 50 additions & 0 deletions tests/browser/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -395,4 +395,54 @@ export default function() {
components: { cmp }
});
});

test(`static, dynamic, and directive classes play nicely`, t => {
const r = new Ractive({
target: fixture,
template:
'<div class="foo {{foo}}" class="bar" class-sure /><div class-foo class="bar {{foo}}" /><div class-foo="bar" class="bar yep" /><div class-yep=bar class-foo class-bar />',
data: {
foo: 'yep',
bar: 1
}
});

t.htmlEqual(
fixture.innerHTML,
'<div class="foo yep bar sure"></div><div class="bar yep foo"></div><div class="bar yep foo"></div><div class="foo bar yep"></div>'
);

r.set('bar', 0);
r.set('foo', 'yes');

t.htmlEqual(
fixture.innerHTML,
'<div class="foo yes bar sure"></div><div class="bar yes foo"></div><div class="bar yep"></div><div class="foo bar"></div>'
);
});

test(`static, dynamic, and directive styles play nicely`, t => {
const r = new Ractive({
target: fixture,
template:
'<div style="color: {{foo}};" style="display: block;" style-width="1em" /><div style-width="1em" style="color: {{foo}};" /><div style-display="{{bar}}" style="color: green;" />',
data: {
foo: 'red',
bar: 'block'
}
});

t.htmlEqual(
fixture.innerHTML,
'<div style="display: block; width: 1em; color: red;"></div><div style="width: 1em; color: red;"></div><div style="color: green; display: block;"></div>'
);

r.set('bar', 'inline');
r.set('foo', 'pink');

t.htmlEqual(
fixture.innerHTML,
'<div style="display: block; width: 1em; color: pink;"></div><div style="width: 1em; color: pink;"></div><div style="color: green; display: inline;"></div>'
);
});
}
Loading

0 comments on commit 1b58fe5

Please sign in to comment.