Skip to content

Commit

Permalink
Fixes #17
Browse files Browse the repository at this point in the history
- Move utility functions to `util.js`
- Replace DOMParser approach with create empty element and `innerHTML` approach
- Update test
  • Loading branch information
Jaeho Lee committed Feb 27, 2017
1 parent 4eb7203 commit a6bac3a
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 67 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ You can change element where svg included using `element` prop, default is `<i /

### prop `raw` : bool (experimental!)

This prop allows your svg file to be rendered directly, without a container element wraps it. This is an experimental feature. Since this feature needs `DOMParser` svg+xml support, it's only avaialble on >= IE10 and other modern browsers (it checks support before use, so errors won't be thrown though). Also, the prop will be ignored on server side rendering environment.
This prop allows your svg file to be rendered directly, without a container element wraps it. This is an experimental feature. Also, the prop will be ignored on server side rendering environment.

## Notes

Expand Down
62 changes: 6 additions & 56 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -1,73 +1,23 @@
import React from 'react';

import { switchSVGAttrToReactProp, getSVGFromSource, extractSVGProps, stripSVG } from './util';

const DOMParser = typeof window !== 'undefined' && window.DOMParser;
const process = process || {};
process.env = process.env || {};
const parserAvailable = typeof DOMParser !== 'undefined' &&
DOMParser.prototype != null &&
DOMParser.prototype.parseFromString != null;

function isParsable(src) {
// kinda naive but meh, ain't gonna use full-blown parser for this
return parserAvailable &&
typeof src === 'string' &&
src.trim().substr(0, 4) === '<svg';
}

// parse SVG string using `DOMParser`
function parseFromSVGString(src) {
const parser = new DOMParser();
return parser.parseFromString(src, "image/svg+xml");
}

// Transform DOM prop/attr names applicable to `<svg>` element but react-limited
function switchSVGAttrToReactProp(propName) {
switch (propName) {
case 'class': return 'className';
default: return propName;
}
}
const process = process || { env: {} };

export default class InlineSVG extends React.Component {
static defaultProps = {
element: 'i',
raw: false,
src: ''
};

static propTypes = {
src: React.PropTypes.string.isRequired,
element: React.PropTypes.string,
raw: React.PropTypes.bool
};

constructor(props) {
super(props);
this._extractSVGProps = this._extractSVGProps.bind(this);
}

// Serialize `Attr` objects in `NamedNodeMap`
_serializeAttrs(map) {
const ret = {};
let prop;
for (let i = 0; i < map.length; i++) {
prop = switchSVGAttrToReactProp(map[i].name);
ret[prop] = map[i].value;
}
return ret;
}

// get <svg /> element props
_extractSVGProps(src) {
const map = parseFromSVGString(src).documentElement.attributes;
return (map.length > 0) ? this._serializeAttrs(map) : null;
}

// get content inside <svg> element.
_stripSVG(src) {
return parseFromSVGString(src).documentElement.innerHTML;
}

componentWillReceiveProps({ children }) {
if ("production" !== process.env.NODE_ENV && children != null) {
console.info('<InlineSVG />: `children` prop will be ignored.');
Expand All @@ -78,10 +28,10 @@ export default class InlineSVG extends React.Component {
let Element, __html, svgProps;
const { element, raw, src, ...otherProps } = this.props;

if (raw === true && isParsable(src)) {
if (raw === true) {
Element = 'svg';
svgProps = this._extractSVGProps(src);
__html = this._stripSVG(src);
svgProps = extractSVGProps(src);
__html = getSVGFromSource(src).innerHTML;
}
__html = __html || src;
Element = Element || element;
Expand Down
37 changes: 37 additions & 0 deletions lib/util.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Transform DOM prop/attr names applicable to `<svg>` element but react-limited

export function convertReactSVGDOMProperty(str) {
return str.replace(/[-|:]([a-z])/g, function (g) { return g[1].toUpperCase(); })
}

export function startsWith(str, substring) {
return str.indexOf(substring) === 0;
}

const DataPropPrefix = 'data-';
// Serialize `Attr` objects in `NamedNodeMap`
export function serializeAttrs(map) {
const ret = {};
for (let prop, i = 0; i < map.length; i++) {
const key = map[i].name;
if (!startsWith(key, DataPropPrefix)) {
prop = convertReactSVGDOMProperty(key);
}
ret[prop] = map[i].value;
}
return ret;
}

export function getSVGFromSource(src) {
const svgContainer = document.createElement('div');
svgContainer.innerHTML = src;
const svg = svgContainer.firstElementChild;
svg.remove(); // deref from parent element
return svg;
}

// get <svg /> element props
export function extractSVGProps(src) {
const map = getSVGFromSource(src).attributes;
return (map.length > 0) ? serializeAttrs(map) : null;
}
25 changes: 15 additions & 10 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import InlineSVG from '../lib';


describe('<InlineSVG />', function () {
var src = require('./fixture/1.svg');
var target;
let src = require('./fixture/1.svg');
let target;

before(() => {
target = document.createElement('div');
Expand All @@ -17,14 +17,14 @@ describe('<InlineSVG />', function () {
it('should render component correctly with expected output', () => {
const rendered = ReactDOM.render(<InlineSVG src={src} />, target);
const r = ReactDOM.findDOMNode(rendered);
const svg = r.children[0];
const g = svg.children[0];

// TODO: automated 1:1 element prop check
assert.equal(r.tagName, 'I');
assert.equal(svg.tagName, 'svg');
assert.equal(g.tagName, 'g');
assert.equal(g.id, 'artboard-1');
const svg = r.firstElementChild;
const g = svg.firstElementChild;
[
[r.tagName, 'I'],
[svg.tagName, 'svg'],
[g.tagName, 'g'],
[g.id, 'artboard-1']
].forEach(([a, b]) => { assert.equal(a, b) });
});

it('should be able to change container element', function () {
Expand All @@ -37,5 +37,10 @@ describe('<InlineSVG />', function () {
const rendered = ReactDOM.render(<InlineSVG src={src} raw={true} />, target);
const svg = ReactDOM.findDOMNode(rendered);
assert.equal(svg.tagName, 'svg');

// node removed data-reactroot
const svg2 = svg.cloneNode(true);
svg2.removeAttribute('data-reactroot');
assert.equal(src, svg2.outerHTML);
});
});

0 comments on commit a6bac3a

Please sign in to comment.