Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Embed into object tag #26

Closed
maxwellito opened this issue Feb 24, 2015 · 38 comments
Closed

Embed into object tag #26

maxwellito opened this issue Feb 24, 2015 · 38 comments

Comments

@maxwellito
Copy link
Owner

Hey @jolic
I continue the topic here.
I wanted to implement your idea but after looking at your code, can you tell me why you need this:

var _checkElm = function(elm){
    if (elm.constructor === SVGSVGElement) {
        return true;
    }
    if ( typeof elm === 'object' && typeof elm.nodeName !== 'undefined' && elm.nodeName.toLowerCase() === 'svg') {
        return true;
    }
    return false;
};

Because when I look at the way you implement it, you still provide an SVG object to Vivus. I tried your fork with the unpatched version and it works fine.

Can you give me more details about it please?
Thanks :)

@jolic
Copy link

jolic commented Feb 25, 2015

without this vivus won't work on an <object>.

see
http://jolic.github.io/#vivus

@maxwellito
Copy link
Owner Author

Yes, I launched it, and I've put two console, one in the first condition, one in the second. It always show the first, never the second.

Try it

var _checkElm = function(elm){
    if (elm.constructor === SVGSVGElement) {
        console.warn('_checkElm passed on SVGSVGElement');
        return true;
    }
    if ( typeof elm === 'object' && typeof elm.nodeName !== 'undefined' && elm.nodeName.toLowerCase() === 'svg') {
        console.warn('_checkElm passed on ObjectElement');
        return true;
    }
    return false;
};

@jolic
Copy link

jolic commented Feb 25, 2015

The first condition is for a svg element which is placed directly in the html.
The second for an object where the svg is loaded and the onload event is fired...

@maxwellito
Copy link
Owner Author

I've made a copy of your code to show you. I'm probably wrong but I wanted to be sure because I'm missing something :-)

http://maxwellito.github.io/drafts/vivus-embedded/

@jolic
Copy link

jolic commented Feb 26, 2015

Ah, now I unterstand where the problem is.... ;)

Ok, the patch is for everybody who is loading vivus in the html file! To clarify:
If vivus is loaded inside the svg you don't need the patch.

But, if you load vivus in the html and you want to show the svg within an <object>vivus won't work without the patch. This is because you wrote vivus to look for an id of the DOM and then vivus checks if this is a SVG element. Your checking function returns false as the id or element is an <object>!

With my patch you can do:

A) Load vivus inside the html and add a graphic with <svg>
B) Load vivus inside the SVG and show it inside the html with <svg>
C) Load vivus inside the html and add a graphic with an external SVG with a placed <object>

C won't work without the patch.

If you look at the example I wrote at http://jolic.github.io you'll see that I used example C.

See the source of
http://jolic.github.io/assets/svg/lens-with-external-vivus.svg

My intention was that I wanted vivus to work with a SVG which is loaded as external source.
This works with an <object>and the great benefit of this is that I can put the logic to control the graphic inside the SVG.

The JS source of the vivus example which you can see @ http://jolic.github.io is an example how you can call methods or control vivus which is loaded from the SVG inside the calling html from outside.

HTH

@jolic
Copy link

jolic commented Feb 26, 2015

BTW I've added some methods inside the patch. For example

Vivus.prototype.setType

With this method you can do for exmaple

svgDOM.Vivus.setType('oneByOne').play(-2)

means: when the animation is finished I can change the type of the animation.

Vivus.prototype.setStrokeColoris an example.

Maybe this is helpful too...

svgDOM.Vivus.setStrokeColor('#fa0')

@maxwellito
Copy link
Owner Author

I'm really sorry but I can't see the difference between the example from your _PATCH folder and the one on your GitHub page.

I set a console in vivus-patched.js and it never goes in the object case.

http://maxwellito.github.io/drafts/vivus-embedded-2/

Are you sure to use a C example in your GitHub page?

@jolic
Copy link

jolic commented Feb 27, 2015

Ok.

I've made a lot of changes to the vivus js class especially on the constructor that the svg is working with an <object> element as external graphic.

The SVG https://github.com/jolic/vivus/blob/master/_PATCH/svg/hi-there.svg is the original SVG from your source as a external file.

So, now it's possible to call the vivus as you want:

<html>
<svg id="mySVG">...</svg>
...
a = new Vivus("mySVG",...);
...
</html>

or

<html>
<object id="mySVG" data="pathToMySVGFile">...</object>
...
a = new Vivus("mySVG",...);
...
</html>

see

https://github.com/jolic/vivus/blob/master/_PATCH/js/vivus-patched2.js

and the example

https://github.com/jolic/vivus/blob/master/_PATCH/patch-test2.html

To include the vivus file inside the SVG file is working as i described in the previous example.

Hope that explains it...

;)

@jolic
Copy link

jolic commented Feb 27, 2015

Oh, btw... it would be better to do:

this.el = this.setElement(element);
...use this.el

than

this.setElement(element);
...use this.el

@jolic
Copy link

jolic commented Feb 27, 2015

If you use a external svg file you must set
data-async=""
and not
data-async

The browser throws an error if an empty attribute is omitted.

See svg specs.

Fyi.

@maxwellito
Copy link
Owner Author

Thank you so much for this example.

This is CRAZY!!
Sorry it took me so long to get it, the example was necessary. Thanks for your patience.
I just love it! It's seamless for the user, no change to do in his SVG file, nothing to change in the way to use Vivus, it's brilliant!

A shame you didn't use Gulp to generate the patched file, but don't worry I will take care of that.

I just have a simple question, how could we manage to make it working if the object is already loaded when the Vivus instance is created?

@jolic
Copy link

jolic commented Mar 1, 2015

Thank you. ;)

According to http://en.wikipedia.org/wiki/DOM_events the load event"...fires when the target element and all of its content has finished loading".

The only problem i see in the current state of the concstructor is that in case of var a = new Vivus(...) a could be undefined (or anything else, depends on how ais defined) when the SVG isn't loaded yet when you call `a``. (could be when the internet connection is slow, the html and js was processed but the SVG not).

Maybe it would be better to add a queue like mechanism in the public methods to call the methods when Vivus is ready to use them. Consider following example:

<html>
<object id="mySVG" data="pathToMySVGFile">...</object>
...
a = new Vivus("mySVG",...);
a.play();
...
</html>

a.play() could fail because the SVG isn't loaded yet (maybe).

However, I would recommend to implement the object stuff so that everybody could use it.

BTW: The patch i wrote was a fast example, here is a better one:

var isReady = false;

function Vivus (element, options, callback) {
    var self = this;
    this.setElement(element);
    var _init = function(){
        // Setup
        self.setOptions(options);
        self.setCallback(callback);
        // Set object variables
        self.frameLength = 0;
        self.currentFrame = 0;
        self.map = [];
        new Pathformer(element);
        self.mapping();
        self.starter();
        isReady = true;
        if ( typeof options.onReady !== 'undefined' && typeof options.onReady === 'function'){
            options.onReady(this);
        }
    };
    if ( this.el ) {
        switch ( this.el.nodeName.toLowerCase() ){
            case 'object':
                this.el.addEventListener('load', function(e){
                    element = e.target.contentDocument;
                    var svgElements = element.getElementsByTagName('svg');
                    if ( 0 === svgElements.length ) {
                          throw new Error('Vivus: there is no <svg> element in this <object>.');
                    }
                    else {
                        element = self.el = svgElements[0];
                        _init();
                    }
                });
                break;
            case 'svg':
                _init();
                break;
            default: // no supported element
                throw new Error('Vivus can\'t work on this type of element.');
        }
    }
}

Vivus.prototype.isReady = function(){
    return isReady;
}

So, if somebody want to use an <object>and want to be sure his function is called when the SVG is loaded he could do:

var myCallbackOnReady = function(_vivus){
    // use _vivus or a and do some stuff
};
var a = new Vivus( ...,{ onReady: myCallbackOnReady },... );

A better callback and/or queue management system inside Vivus would be great, for example:

var onVivusReady = function(_vivus){
    _vivus
        .start()
        .catch(function(err) {
            alert('Uhh oh.. Vivus error: '+err.message);
        })
        .then(function(){
            //_vivus.rewind()...
        })
        .then(function(){
            //do other stuff
        });
};
var a = new Vivus(...., { onReady: onVivusReady } ,....);

Here is a nice article about the technique:
http://www.html5rocks.com/en/tutorials/es6/promises/

The promise stuff is a neat thing but not necessary as this would blow up Vivus and is not in focus for the functionality.

Hope this helps

@jolic
Copy link

jolic commented Mar 1, 2015

Oh...one thing:

You could write

...
        if ( typeof options.onReady !== 'undefined' && typeof options.onReady === 'function'){
            options.onReady.call(this);
        }
...

in the html js code thisrefers to the Vivus instance:

var myCallbackOnReady = function(){
    // "this" is the Vivus instance!
   this.play(); // for example
};
var a = new Vivus( ...,{ onReady: myCallbackOnReady },... );

Maybe this is better...

@maxwellito
Copy link
Owner Author

Actually I was wondering how to access the document when the content is already loaded, simply via contentDocument on the element. Gosh Sunday doesn't help on my brain.

The loaded thing is not really a problem, we can implement a onReady callback and a state variable like isReady. Personally, I love promises. Unfortunately, I think it's not accessible enough. The thing I like about Vivus is it doesn't require a big JavaScript knowledge to use it. Plus it would make the library heavier, I would love to avoid it.

I have to admit this is quite tricky, because until here I didn't have to manage any asynchronous logic. I quite like the onReady solution: simple, accessible and won't make Vivus quite heavy. Maybe later, in the future, when ES6 will be mostly available, it will be time for an update :)

The only thing, we have to specify this tricks only works when the SVG is on the same domain. But that shouldn't be a problem.

@timelyportfolio
Copy link

this is really nice. I've been planning to build a R htmlwidget wrapping Vivus, and this would be a killer feature. I wondered what would be the timeline for including this? Thanks so much for all of your work on Vivus. I love it.

@jolic
Copy link

jolic commented Mar 2, 2015

Oh there was an error:

options.onReady.call(this);

have to be

options.onReady.call(self);

now "this" refers in the callback function to the Vivus instance.


And I've added another feature. ;)

Now you can refer to an element where no <object> or <svg> is placed inside the html.

With this we can use Vivus as we want - we have 3 possibilities to use Vivus:

A) a placed <svg> inside the html
B) a placed <object> inside the html
C) any DOM element with an id

here is an example how we can call Vivus in case of C):

...
    <div id="showMySVGInsideThisElement"></div>
...

    var myCallbackOnReady = function(){
        console.log('**** myCallbackOnReady on created <object> ****');
        this.play(); // "this" is the Vivus instance
    };
    externSVGviaJS = new Vivus(
        'showMySVGInsideThisElement'
        ,{
             type   :'delayed'
            ,start  :'manual'
            ,duration: 200
            ,file   :'svg/hi-there.svg'
            ,onReady: myCallbackOnReady
        }
        ,myCallback3
    );

In this case Vivus checks if options.file is set and if so Vivus creates an <object> and adds this to the element with the DOM id.

Please have a look at the source (especially the constructor):

https://github.com/jolic/vivus/blob/master/_PATCH/js/vivus-patched3.js

and the example

https://github.com/jolic/vivus/blob/master/_PATCH/patch-test3.html

;)

@timelyportfolio
htmlwidgets for R looks great. Do you have any links with some tutorials on this?

@jolic
Copy link

jolic commented Mar 2, 2015

@maxwellito

Don't worry about the same domain policy. Everbody who wants to use this have to know about it.

My intention to include the logic to support <object> is that i can use javascript inside the <svg>.

That's all.

And on the other side I don't like to blow the html with graphics like <svg>....</svg>.
Better is to refer to an external graphic. If I need to change the graphic I don't need to touch the html.

Simple as that.

;)

HTH

@maxwellito
Copy link
Owner Author

@jolic I like all your efforts on this topic :-) Everytime you write a big response with code examples, that's appreciable. I will implement the feature on the dev branch tonight, I would be happy if you could review it. I'll mention you.

@timelyportfolio Thanks :-) I was thinking to turn it into a WebComponent (or Polymer). Even if at the moment I'm more into React. I would be happy to see nice components. Now I focus on the next release which should be soon (v0.2 : animation function, npm, object tag compatible). But feel free to open a ticket and start the conversation. The most important to me is to make it accessible and interoperable, like WebComponents.

jolic added a commit to jolic/vivus that referenced this issue Mar 2, 2015
@jolic
Copy link

jolic commented Mar 2, 2015

Thank you. ;)

Now here is my last patch [ for today? ;) ]:

I've added a 4th possibility to use Vivus:

Use Vivus on an element that is created via js directly:

...
    var myElement = document.createElement('span');

    var appendMyElementToThisElm = document.getElementById('showMySVGInsideThisElement2');

    appendMyElementToThisElm.appendChild( myElement );

/* myElement is a created DOM element with js */

    externSVGviaJS2 = new Vivus(
        myElement
        ,{
             type   :'delayed'
            ,start  :'manual'
            ,duration: 200
            ,file   :'svg/hi-there.svg'
            ,onReady: myCallbackOnReady2
        }
        ,myCallback4
    );
...

With this you could use Vivus without any DOM element inside the html.

see
https://github.com/jolic/vivus/blob/master/_PATCH/js/vivus-patched4.js

and example D at
https://github.com/jolic/vivus/blob/master/_PATCH/patch-test4.html

I've just changed the _checkElm function and added.

...
    if ( elm instanceof HTMLElement ) {
console.warn('_checkElm passed on HTMLElement');
        return true;
    }
...

I've created the _checkElm function because Vivus and Pathformer are looking for a valid element.

I've also added a reference to the Pathformer instance:

...
... in constructor:
        self._PathformerInstance = new Pathformer(element);
...

Vivus.prototype._PathformerInstance = null;
...

Please let me know when you've merged the code that i can see it.

In fact you need to take my test page https://github.com/jolic/vivus/blob/master/_PATCH/patch-test4.html and import your Vivus js file.

There are all current available possibilities shown to use Vivus.

@jolic
Copy link

jolic commented Mar 2, 2015

and please use https://github.com/jolic/vivus/blob/master/_PATCH/svg/hi-there.svg as I've replaced

data-async

with

data-async=""

@jolic
Copy link

jolic commented Mar 2, 2015

here's the current code of the constructor, fyi:

...
var isReady = false;

function Vivus (element, options, callback) {
    var self = this;
    this.setElement(element);
    var _init = function(){
        // Setup
        self.setOptions(options);
        self.setCallback(callback);
        // Set object variables
        self.frameLength = 0;
        self.currentFrame = 0;
        self.map = [];
        self._PathformerInstance = new Pathformer(element);
        self.mapping();
        self.starter();
        isReady = true;
        if ( typeof options.onReady !== 'undefined' && typeof options.onReady === 'function'){
            options.onReady.call(self);
        }
    };
    var addLoadEventOnElement = function(){
        self.el.addEventListener('load', function(e){
            element = e.target.contentDocument;
            var svgElements = element.getElementsByTagName('svg');
            if ( 0 === svgElements.length ) {
                  throw new Error('Vivus: there is no <svg> element in this <object>.');
            }
            else {
                element = self.el = svgElements[0];
                _init();
            }
        });
    };
    if ( this.el ) {
        switch ( this.el.nodeName.toLowerCase() ){
            case 'object':
                addLoadEventOnElement();
                break;
            case 'svg':
                _init();
                break;
            default: // no supported element or create one
                if ( typeof options.file !== 'undefined' ) {
                    var objElm = document.createElement('object');
                    objElm.setAttribute('type', 'image/svg+xml');
                    objElm.setAttribute('data', options.file);
                    this.el.appendChild(objElm);
                    this.el = objElm;
                    addLoadEventOnElement();
                } 
                else {
                    throw new Error('Vivus can\'t work on this type of element.');
                }
        }
    }
};

Vivus.prototype.isReady = function(){
    return isReady;
};
Vivus.prototype._PathformerInstance = null;
...

and the _checkElm function:

...
var _checkElm = function(elm){
    if (elm.constructor === SVGSVGElement) {
        return true;
    }
    if ( typeof elm === 'object' && typeof elm.nodeName !== 'undefined' && elm.nodeName.toLowerCase() === 'svg') {
        return true;
    }
    if ( elm instanceof HTMLElement ) {
        return true;
    }
    return false;
};
...

@jolic
Copy link

jolic commented Mar 2, 2015

So, now we are able to call Vivus in the following ways:

A) placed svg inside the html (with <svg id="mysvg">...</svg>):
        new Vivus( 'mysvg', ...);
B) placed extern svg as <object id="externsvg" type="image/svg+xml" data="svg/hi-there.svg"></object>:
        new Vivus( 'externsvg', ...);
C) placed extern svg as created <object> inside an html element (with for example: <span id="putSVGhere"></span>):
        new Vivus( 'putSVGhere', {...file:'svg/hi-there.svg'...});
D) placed extern svg as created <object> inside an with js created html element:
        var myElement = document.createElement('span');
        /// append myElement to any element in DOM
        new Vivus( myElement, {...file:'svg/hi-there.svg'...});
E) import Vivus directly inside the svg and use it there
        (inside SVG file, for example):
        <svg onload="onLoad(evt);" ....>
            <script type="text/javascript" xlink:href="path/to/vivus.js"/>
            <g id="group">
                ...
            </g>
            <script type="text/javascript">
            <![CDATA[
                var myVivus = null;
                var onLoad=function(evt) {
                    myVivus = new Vivus('group',...);
                };
            ]]>
            </script>

        </svg>
F) import Vivus directly inside the svg and set a reference to the created Vivus instance for the parent object (document):
        (inside SVG file, for example):
        // same code as E) but with following addition
        document._SVG={
             getElement     : function(){ return evt.target; }
            ,Vivus          : myVivus
            ...
        };

        ---
        (inside the html):
        <object id="svg_C" type="image/svg+xml" data="svg/hi-there.svg"></object>
        ...
        document.getElementById('svg_C').addEventListener('load', function(e){
            var doc = e.target.contentDocument;
            // do stuff with doc._SVG.Vivus (<- instance of Vivus which is created inside the SVG)
        }, false);
        ...

For every usage of Vivus I would recommend to use the onReady option as this is called definitly when Vivus is ready to be used in any circumstances.

In example E) and F) the Vivus js file is loaded from the SVG file.

@maxwellito
Copy link
Owner Author

I pushed my code on dev. I like the way you want to improve the library but I think you're overthinking. The object tag creation is too much. It's adding too much code for less freedom. For example if the user want to add a class to the element, it will be 'hacky'. This must remain the dev personal problem.

But for this kind of case I would love a WebComponent.

At the moment I just quickly implemented the original object feature. I have to update the tests.

@jolic
Copy link

jolic commented Mar 3, 2015

Why not?

...
<span id="putSVGhere"></span>
...
new Vivus( 'putSVGhere', {...file:'svg/hi-there.svg'...});
...

The code to handle this is a snap and if you need to style the object that's easy:

#putSVGhere object{
    background:red;
}

If you patched the file with the listener for the <object> then there's only the dom creation left:

         var objElm = document.createElement('object');
         objElm.setAttribute('type', 'image/svg+xml');
         objElm.setAttribute('data', options.file);
         this.el.appendChild(objElm);
         this.el = objElm;
         addLoadEventOnElement();

And you'll give the developers the freedom to decide how they want to use Vivus...

My 2 cents.

@maxwellito
Copy link
Owner Author

I understand your point of view, I'm just scared about how it will end. For now it looks great until people want to implement an option to add a class name, then one to set the object content (for fallback)...

Let me try that tomorrow night.

@jolic
Copy link

jolic commented Mar 4, 2015

Nothing to be afraid of. ;)

I wouldn't go into this so deep.
Means: If you implement the above and the possibility to start Vivus with an created DOM elm (see example D @ https://github.com/jolic/vivus/blob/master/_PATCH/patch-test4.html and the js @ https://github.com/jolic/vivus/blob/master/_PATCH/js/vivus-patched4.js) we have all options to change everything we want. It's simple: just add in _checkElm

    if ( elm instanceof HTMLElement ) {
        return true;
    }

Thats all.

With this we could do

    var myElement = document.createElement('div');
    // ...code to append myElement anywhere you like

    var v = new Vivus( myElement ,{ file:'svg/hi-there.svg' } );
    // "<object>" is appended to myElement which loads the svg

And now every developer could add and do whatever he likes.

@jolic
Copy link

jolic commented Mar 4, 2015

All I'm writing about the code is for the view to be as flexible as we can without to blow up the code.
I mean the patch i wrote has a big benefit that Vivus works on the <object> element too.

The adiitions I wrote gives all of us the flexibility to use Vivus in any circumstances.

A Webdesigner would prefer to add the graphic as a <svg>...</svg> directly into the html and call Vivus with a simple line in js.
Another would prefer to use an <object> as Javascript inside the SVG is processed by the browser. Have a look at some projects which are creating icons in SVG and the icons resizes automatically and look different. (In my opinion: this technique will be the future as we don't have to create 5000 different SIZE versions of the graphics).
A pure fontend and/or backend developer who is creating a dashboard would like that the logic of his code has the control to add SVG element but he likes to use Vivus without "getElementById".

These examples are only a couple of possible usage scenarios.

But all this is possible without to write spaghetti code or a workaround to use Vivus in their environment.

@jolic
Copy link

jolic commented Mar 4, 2015

And for the fallback thing, there are simple solutions for this:

A) add a {...content:'<img src="imageToShowIfNotSupported.jpg"/>'} and set objElm.innerHTML = options.content;

or

B) don't add A) and do in css

div.insideThisVivusAddsAnObject {
    background: url( path/to/imageToShowIfNotSupported.jpg );
}
...

that's all

@jolic
Copy link

jolic commented Mar 4, 2015

I've made a patch on 0.2.0 that everything is working with the features of above:

Example:
https://github.com/jolic/vivus/blob/master/_PATCH/patch-test-DEV-0.2.0.html

js files:
https://github.com/jolic/vivus/tree/master/_PATCH/js/dev

patched version 0.2.0:
https://github.com/jolic/vivus/blob/master/_PATCH/js/dev/vivus-0.2.0_PATCH.js

diff:
https://github.com/jolic/vivus/blob/master/_PATCH/js/dev/diff.txt

Hope, that's ok... please have a look...

@jolic
Copy link

jolic commented Mar 4, 2015

Better view of the diff:
jolic@278b41e#diff-cfc0807b305cf24b09d85b957362847f

@maxwellito
Copy link
Owner Author

Hey @jolic

Sorry I never closed this issue even if now Vivus accepts dynamically loaded SVGs. I wanted to give you a big and massive thanks because without you this would have never happen (I didn't know that was possible).

However, there's two cases I haven't mentioned in the documentation which are the point E and F of your previous comment. Because these use-cases are rares and are more a 'hacky' way of doing things. But it remain very interesting. This is why I created the hacks.md page, a place to list the different hacks for the library.
I would like if you could make a pull request to add your hacks. If you don't no worries :)

Anyway, thanks again for your contribution, you're star!

@jolic
Copy link

jolic commented Oct 2, 2015

Thanks! ;)

Will look at the hacks to show a way where we could use a js created DOM elm to append a SVG when I have some time.

...would be nice to mention all the guys who helped to get Vivus better on https://github.com/maxwellito/vivus under a section "Thanks" at the bottom. ;)

@maxwellito
Copy link
Owner Author

Yes definitely!
I started to have a look to all the issues and there's a lot of people. But I think you remain the most important one :)

@jolic
Copy link

jolic commented Oct 2, 2015

;)

I would like to add a patch where we can use Vivus also without getElementById().
Therefore D is nice to be as flexible as we can without to blow the code...

Will make it when I have some time...

cu

@maxwellito
Copy link
Owner Author

No problem :)
Just make the PR to dev branch

For now https://github.com/maxwellito/vivus/tree/dev#special-thanks

@jolic
Copy link

jolic commented Oct 2, 2015

Very nice. Thanks!

@jolic
Copy link

jolic commented Oct 3, 2015

On the main page @ section "Debug" you write "...If you're using Google Chrome, I recommend you use console.table...".

I would write "...If you're using a modern browser, I..." as on Safari and Firefox console.table is available too.

@maxwellito
Copy link
Owner Author

Oh finally!!! Hooray!
I'll commit that

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants