diff --git a/addon/components/async-svg.js b/addon/components/async-svg.js new file mode 100644 index 0000000..9aea23c --- /dev/null +++ b/addon/components/async-svg.js @@ -0,0 +1,23 @@ +import Ember from 'ember'; +import Aria from '../mixins/aria'; +import Common from '../mixins/common'; +const PREFIX = ''; + +const async = Ember.Component.extend(Aria, Common, { + _svg: Ember.observer('src', function() { + const src = this.get('src').replace(/\.svg$/, ''); + Ember.$.ajax({ + method: 'GET', + url: `${PREFIX}/${src}.svg` + }).done(data => { + this.setSVG(data); + }); + return true; + }) +}); + +async.reopenClass({ + positionalParams: ['src'] +}); +async[Ember.NAME_KEY] = 'async-svg'; +export default async; diff --git a/addon/components/inline-svg.js b/addon/components/inline-svg.js new file mode 100644 index 0000000..9fe4e64 --- /dev/null +++ b/addon/components/inline-svg.js @@ -0,0 +1,22 @@ +import Ember from 'ember'; +import Aria from '../mixins/aria'; +import Common from '../mixins/common'; +import SVGs from 'svgs'; + +const inline = Ember.Component.extend(Aria, Common, { + _svg: Ember.computed('src', function() { + const src = this.get('src') || ''; + const path = src.replace(/\.svg$/, '').replace(/\//g, '.'); + const svg = Ember.get(SVGs, path); + + Ember.assert(`No SVG found for ${path}`, svg); + + this.setSVG(svg); + }) +}); + +inline.reopenClass({ + positionalParams: ['src'] +}); +inline[Ember.NAME_KEY] = 'inline-svg'; +export default inline; diff --git a/addon/mixins/aria.js b/addon/mixins/aria.js new file mode 100644 index 0000000..b0df230 --- /dev/null +++ b/addon/mixins/aria.js @@ -0,0 +1,10 @@ +import Ember from 'ember'; + +export default Ember.Mixin.create({ + labelledBy: Ember.computed('title', 'desc', function() { + const title = this.get('title') ? 'title' : ''; + const desc = this.get('desc') ? ' desc' : ''; + return `${title}${desc}`; + }), + role: 'img' +}); diff --git a/addon/mixins/common.js b/addon/mixins/common.js new file mode 100644 index 0000000..91bada1 --- /dev/null +++ b/addon/mixins/common.js @@ -0,0 +1,60 @@ +import Ember from 'ember'; +import layout from '../templates/components/svg'; +const htmlSafe = Ember.String.htmlSafe; + +export default Ember.Mixin.create({ + layout: layout, + tagName: '', + + /** + * Initialize SVG on load + */ + init() { + this._super(...arguments); + Ember.run.schedule('afterRender', () => { + this.notifyPropertyChange('src'); + }); + }, + preserveAspectRatio: null, + setSVG(data) { + const {width, height, x, y} = this.getProperties('width', 'height', 'x', 'y'); + this.set('svg', htmlSafe(Ember.$(data).find('svg').html())); + + /** + * viewBox + * + * Not required but often included; + */ + const viewBox = Ember.$(data).find('svg').attr('viewBox'); + if (Ember.typeOf(this.get('viewBox')) === 'undefined' && viewBox) { this.set('viewBox', viewBox); } + + /** + * viewport (width, height) + * + * The visible portion of the SVG. Firefox may object if not defined but isn't + * strictly required and you run the risk of cutoff. + * + * By default if not set on component or in the SVG then a width of 100% will be + * used; this is typically the desired behaviour. + */ + const viewWidth = Ember.$(data).find('svg').attr('width'); + const viewHeight = Ember.$(data).find('svg').attr('height'); + if (!width) { this.set('width', viewWidth || '100%'); } + if (!height && !width && viewHeight) { this.set('height', viewHeight); } + + /** + * Aspect Ratio (preserveAspectRatio) + * + * SVG has a boolean flag to ensure that viewBox and viewport are the same aspect + * ratio. If its stated in the file we'll proxy it through but default its not set. + */ + const preserveAspectRatio = Ember.$(data).find('svg').attr('preserveAspectRatio'); + if (preserveAspectRatio) { this.set('preserveAspectRatio', preserveAspectRatio); } + + const defaultX = Ember.$(data).find('svg').attr('x'); + const defaultY = Ember.$(data).find('svg').attr('y'); + if (!x && defaultX) { this.set('x', Ember.$(data).find('svg').attr('x')); } + if (!y && defaultY) { this.set('y', Ember.$(data).find('svg').attr('y')); } + } + +}); diff --git a/addon/templates/components/svg.hbs b/addon/templates/components/svg.hbs new file mode 100644 index 0000000..5100e5d --- /dev/null +++ b/addon/templates/components/svg.hbs @@ -0,0 +1,20 @@ + + {{#if title}} + {{title}} + {{/if}} + {{#if desc}} + {{desc}} + {{/if}} + {{svg}} + {{yield}} + diff --git a/addon/utils/general.js b/addon/utils/general.js deleted file mode 100644 index e486741..0000000 --- a/addon/utils/general.js +++ /dev/null @@ -1,14 +0,0 @@ -// converts slash paths to dot paths so nested hash values can be fetched with Ember.get -// foo/bar/baz -> foo.bar.baz -export function dottify(path) { - return (path || '').replace(/\//g, '.'); -} - -// maybe this should be a component with tagName: 'svg' and strip the outer tag -// so we can use standard component class stuff? -export function applyClass(svg, klass) { - if (!klass) { return svg; } - - // now we have 2 problems... - return svg.replace(' - Dummy + Inline SVG diff --git a/tests/dummy/app/templates/application.hbs b/tests/dummy/app/templates/application.hbs index ecd4780..71f6bb0 100644 --- a/tests/dummy/app/templates/application.hbs +++ b/tests/dummy/app/templates/application.hbs @@ -1,5 +1,18 @@

ember-inline-svg

-{{inline-svg 'kiwi'}} +

Compile Time:

+{{inline-svg 'kiwi' + title='kiwi' + desc='demonstration of compile-time inlining' + width='400px' +}} + +

Run Time:

+ +{{async-svg 'kiwi' + title='kiwi' + desc='demonstration of run-time inlining' + width='400px' +}} {{outlet}} diff --git a/tests/integration/components/async-svg-test.js b/tests/integration/components/async-svg-test.js new file mode 100644 index 0000000..b95f834 --- /dev/null +++ b/tests/integration/components/async-svg-test.js @@ -0,0 +1,25 @@ +import { moduleForComponent, test } from 'ember-qunit'; +import hbs from 'htmlbars-inline-precompile'; + +moduleForComponent('async-svg', 'Integration | Component | async svg', { + integration: true +}); + +test('it renders', function(assert) { + + // Set any properties with this.set('myProperty', 'value'); + // Handle any actions with this.on('myAction', function(val) { ... });" + EOL + EOL + + + this.render(hbs`{{async-svg}}`); + + assert.equal(this.$().text().trim(), ''); + + // Template block usage:" + EOL + + this.render(hbs` + {{#async-svg}} + template block text + {{/async-svg}} + `); + + assert.equal(this.$().text().trim(), 'template block text'); +}); diff --git a/tests/integration/components/inline-svg-test.js b/tests/integration/components/inline-svg-test.js new file mode 100644 index 0000000..ebb45ed --- /dev/null +++ b/tests/integration/components/inline-svg-test.js @@ -0,0 +1,25 @@ +import { moduleForComponent, test } from 'ember-qunit'; +import hbs from 'htmlbars-inline-precompile'; + +moduleForComponent('inline-svg', 'Integration | Component | inline svg', { + integration: true +}); + +test('it renders', function(assert) { + + // Set any properties with this.set('myProperty', 'value'); + // Handle any actions with this.on('myAction', function(val) { ... });" + EOL + EOL + + + this.render(hbs`{{inline-svg}}`); + + assert.equal(this.$().text().trim(), ''); + + // Template block usage:" + EOL + + this.render(hbs` + {{#inline-svg}} + template block text + {{/inline-svg}} + `); + + assert.equal(this.$().text().trim(), 'template block text'); +}); diff --git a/tests/unit/mixins/aria-test.js b/tests/unit/mixins/aria-test.js new file mode 100644 index 0000000..bc46d78 --- /dev/null +++ b/tests/unit/mixins/aria-test.js @@ -0,0 +1,12 @@ +import Ember from 'ember'; +import AriaMixin from '../../../mixins/aria'; +import { module, test } from 'qunit'; + +module('Unit | Mixin | aria'); + +// Replace this with your real tests. +test('it works', function(assert) { + let AriaObject = Ember.Object.extend(AriaMixin); + let subject = AriaObject.create(); + assert.ok(subject); +}); diff --git a/tests/unit/mixins/common-test.js b/tests/unit/mixins/common-test.js new file mode 100644 index 0000000..19df351 --- /dev/null +++ b/tests/unit/mixins/common-test.js @@ -0,0 +1,12 @@ +import Ember from 'ember'; +import CommonMixin from '../../../mixins/common'; +import { module, test } from 'qunit'; + +module('Unit | Mixin | common'); + +// Replace this with your real tests. +test('it works', function(assert) { + let CommonObject = Ember.Object.extend(CommonMixin); + let subject = CommonObject.create(); + assert.ok(subject); +});