1
votes

I will try to be as detailed as possible, it is a complicated question.

This part works perfect but please allow me to explain. I have a template which I use for many controllers:

<script type='text/x-handlebars' data-template-name='main-content'> 
... bunch of components ...
<!-- background 100% x 100% -->
<div id="content">
    <div id="svg_cont" style="position:relative; top:0; left:0; width:100%;height:100%;">
        <svg id="svg" class="svg-main" width="100%" height="100%" {{bind-attr viewBox="pageViewbox"}} preserveAspectRatio="xMidYMid meet">
        <g id="svg-view" class="svg-view">
            <path {{bind-attr d="pageBoundaryPath"}} class="svg-drawing-boundary" />
                {{#each}}
                    {{#if this.isShape}}
                    {{view App.ShapeView content=this id=this.guid}}
                    {{/if}}
                {{/each}}
        </g>
        </svg>
    </div>
</div>
... couple other components ...
</script>

Please note the template contains inline SVG in the HTML and this is where my fixtures are being rendered inside of the #each loop. I pre-loaded 4-5 fixtures when I made the model shown below - when the page initially loads this all works and looks perfect:

App.Shape = App.Asset.extend({

   ... many properties but irrelevant .....

    shapeTag: DS.attr('string', {defaultValue: "path"}),    
    d: DS.attr('string', {defaultValue: ""}),           
    svgAttributes: DS.attr(),

    init: function() {
        this._super();
    },

    didLoad: function() {
        this._super();
    }

});

which is based off of a base model:

App.Asset = DS.Model.extend({

    guid: DS.attr('string'),    // REQUIRED, unique id
    type: DS.attr('string'),    // REQUIRED,  shape | stencil

    isShape: function(){
        return this.get("type") === "shape";
    }.property("type"),

    rootTagId: DS.attr('string', {defaultValue: "svg-view"}), //?? necessary??

    // what to surround the true object with
    parentTag: DS.attr('string', {defaultValue: "g"}),

});

and pre-loaded with some shape objects:

App.Shape.FIXTURES = [{
    "id": "line124",
    "guid": "line124",
    "type": "shape",
    "d": "M57,57L510,57L510,510L57,510,L57,57",
    "svgAttributes": {"stroke-width":"1", "stroke":"#ff9900", "fill":"none"},
    "css": "idp-asset,idp-line"
},
......
];

When the page loads ALL the shapes pre-loaded in the fixtures render and look great. The backend View that is used to render them is below, please note the init method which builds a dynamic template compiled on the fly:

App.ShapeView = Ember.View.extend({

    id: null,
    tagName: null,
    content: null,
    classNames: [],
    attributeBindings: ["trans:transform"],

    template: function(){
        var tmp = this.get("dynamicTemplate");
        return Ember.Handlebars.compile(tmp);
    }.property(),

    didInsertElement: function(){
        this._super();
    },

    dynamicTemplate: "",

    init: function() {
        this._super();

        this.set("id", this.get("content.guid"));
        this.set("tagName", this.get("content.parentTag"));
        this.set("classNames", this.get("content.cssClasses"));

        // iterate all attrs
        var tmp = '';
        tmp += '<' + this.get("content.shapeTag") + ' ';
        tmp += ' {{bind-attr d=view.content.d}} ';

        /*
        this writes out dynamic 'bind-attr' template code
        to bind all the SVGs element attributes to the
        model.svgAttributes. object and it's properties,
        they are a 1 to 1 relationship with valid svg attributes
        and valid values, such as stroke-width: 1.4 OR stroke:"red"
        */
        var svgAttrs = this.get("content.svgAttributes");
        for (var k in svgAttrs) {
            tmp += ' {{bind-attr ' + k + '=view.content.svgAttributes.' + k + '}} ';
        }
        tmp += '></' + this.get("content.shapeTag") + '>';
        this.set("dynamicTemplate", tmp);    

    }
});

NOW TO THE PROBLEM! whew... this all works perfect, items are rendered from the fixture data, the svg objects are inserted into the DOM and they appear on screen in the browser window. I can update model properties in the store and the changes are propagated to the screen as expected. It works awesome. The problem is when I run this command in code from a component (OR ANYWHERE):

var that = this;

var newline = {
    "id": "line112233",
    "guid": "line112233",
    "type": "shape",
    "d": "",
    "shapeTag": "circle",
    "svgAttributes": {
        "cx": "200",
        "cy": "200",
        "r": "200",
        "fill": "red"
    },
    "css": "idp-asset, idp-line"
};

d3.select("#svg").on("click", function(e, i){
    var s = that.store.createRecord('shape', newobj).save().then(function(obj){

         console.log(obj);

    });
});

Everything works perfectly as expected except the item never appears on screen. Please note, the object appears in the DOM as expected but is not displayed on screen. When viewing the site in chrome inspector I SEE THE NEW ELEMENT fine, it appears like this below:

<g id="line112233" class="idp-asset idp-line" transform="translate(0,0)scale(1)rotate(0)">
    <circle d="" data-bindattr-44="44" cx="200" data-bindattr-45="45" cy="200" data-bindattr-46="46" r="200" data-bindattr-47="47" fill="red" data-`bindattr-48="48">
    </circle>
</g>

It ALSO appears under the ember inspector as an added item in the data store, it appears exactly like the other fixtures that were pre-loaded do. This problem occurs if I used .push or .createRecord when adding the new item to the store. So to recap, I add the newly created Shape, and it appears in ember with correct data, it appears in the ember view in the ember inspector, it appears in the DOM when viewing it in Chrome inspector but it DOES NOT ever render on screen.

I have done everything and googled everything I can think of. I loaded the most recent code to http://v4-staging.idplans.com:8181/rpm.htm if you want to see it in action or view source. Go to menu in upper right, select "design mode", open slider on right hand side, and choose the line button, and just click in the center somewhere and you will have a new object in the store, view, and DOM but NOT ON screen.

I really appreciate any help and you reading this long :)

EDIT/UPDATED I thought this was a solution:

In the VIEW (NOT TEMPLATE) for the main outlet (in my case the base controller that my other controllers all extend) I tried this:

App.PageBaseView = Ember.View.extend({

    ........

    // THIS IS WHAT I ADDed, BY WATCHING MY CONTENT OF THE CONTROLLER
    // AND FORCING A RE-RENDER ON CURRENT (THIS) VIEW THE OBJECT IS NOW DISPLAYED
    // ON SCREEN
    contentChanged: function() {
        this.rerender();
    }.observes('controller.content.@each')

});

Because the object started appearing but because the page re-rendered I lost all event listeners and page state and properties went back to initialized values.... Still looking for answer

1
Try using this.rerender() in your view after the the operation is complete. You'll probably need some type of debounce tho.MilkyWayJoe
Anywhere I attempt to use that it creates an endless loop which I think is what you meant about debounce. Not sure how I would implement that without some time delay which I want to avoid because this is a drawing tool so I need it more responsive. Any ideas?Nick Licata
Not really. I just thought this could be something a rerender could fix.MilkyWayJoe

1 Answers

0
votes

UPDATED SOLVED - NOT EMBER RELATED!

Leaving all code identical to original post (without adding or using any rerender() methods, the code can be made to work 100% (and very cool I might add) by surrounding any added elements from the store to the document in SVG root tags, so this seems to be a SVG DOM problem or its by design but it doesn't make sense (to me) if so.

In other words, if you look at the above code where the g element is added

<g id="line112233" class="idp-asset idp-line" transform="translate(0,0)scale(1)rotate(0)">
    <circle d="" data-bindattr-44="44" cx="200" data-bindattr-45="45" cy="200" data-bindattr-46="46" r="200" data-bindattr-47="47" fill="red" data-`bindattr-48="48">
    </circle>
</g>

All I had to do to get the element to appear on screen was begin outputting the code above with surrounding svg tags, with no attributes (I added an id and classes but that's unrelated). So the new code would look like:

<svg>
    <g id="line112233" class="idp-asset idp-line" transform="translate(0,0)scale(1)rotate(0)">
        <circle d="" data-bindattr-44="44" cx="200" data-bindattr-45="45" cy="200" data-bindattr-46="46" r="200" data-bindattr-47="47" fill="red" data-`bindattr-48="48">
        </circle>
    </g>
</svg>

And now it appears.... One important note to anyone this very specific problem would apply to, you cannot perform transforms in a svg tag, only g tags and element tags. But if you leave the nested svg tag with no viewbox, height, or width attributes it will just take the shape of it's child elements bounding box which works out perfect.