I want to load an exteranal svg file in SAPUI5. I was thinking of using D3 JS library to do this
Is it possible and advisable ?
If yes ,then are there any other framework based on D3 library which can be utilized to do the integration as SAP UI5 is a object oriented framework and writing D3 code inside the view is not working?
It's possible to load an external SVG with D3, but it's not built-in. Here's an example (the footballs are external SVG icons):
http://bl.ocks.org/emeeks/a347eed5c50a7f1cf08a
However, this is not what D3 is built for. Rather, D3 is built for creating complex SVG graphics based on data. You're better off either using native Javascript (which is mostly what is done in that example, anyway, loading the element on a documentFragment and then cloning the node) or another library. You might want to look at snap.svg, since it's more focused on traditional SVG manipulation.
find my solution below. The idea with the onAfterRendering is taken from SCN.
Alternatively you may consider to use a sap.m.Image. According to CSS Tricks, an <img> tag should be sufficient to display SVG from an external source. However, in my case it didn't work; I suspect the reason was that my service URL doesn't return an adequate MIME type (is: application/xml, should: image/svg+xml???)
return Control.extend("com.example.SvgDisplay", {
metadata : {
properties: {
svgID: "string",
mySvg: "object"
}
},
setSvgID: function(sSvgID) {
var that = this,
sParams;
this.setProperty("svgID", sSvgID, true);
if (sSvgID) {
sParams = $.sap.encodeURLParameters({ id: sSvgID });
d3.xml("/my/svg/service?" + sParams, function (oError, oDocument) {
var oSvgNode;
if (oError) {
// Do error handling - for example evaluate oError.responseText
} else if (oDocument) {
oSvgNode = oDocument.lastChild;
}
that.setMySvg(oSvgNode); // may still be undefined; force re-rendering
});
}
},
// "static" function, renders the basic <div> tag. The rest will be done by method onAfterRendering
renderer : function(oRm, oControl) {
var sId = oControl.getId(),
oMySvg = oControl.getMySvg();
oRm.write("<div"); // Control - DIV
oRm.writeControlData(oControl); // writes the Control ID and enables event handling - important!
oRm.writeClasses();
oRm.write('><div id="' + sId + '-svgContainer"></div>');
oRm.write("</div>"); // end Control - DIV
},
onAfterRendering: function(){
var oMySvg = this.getMySvg(), sDomID, oVis;
if (oMySvg) {
sDomID = this.getId();
oVis = d3.select("#" + sDomID + "-svgContainer").node();
oVis.appendChild(oMySvg);
}
}
});
Related
I extend a Control to create a new custom control in UI5 and this control renders a tree as UL items nicely. Now I need to implement a collapse/expand within that tree. Hence my renderer writes a tag like
<a class="json-toggle" onclick="_ontoggle"></a>
and within that _ontoggle function I will handle the collapse/expand logic.
No matter where I place the _ontoggle function in the control, I get the error "Uncaught ReferenceError: _ontoggle is not defined"
I am missing something obvious but I can't find what it is.
At the moment I have placed a function inside the
return Control.extend("mycontrol",
{_onToggle: function(event) {},
...
Please note that this event is not one the control should expose as new event. It is purely for the internals of how the control reacts to a click event.
I read things about bind and the such but nothing that made sense for this use case.
Took me a few days to crack that, hence would like to provide you with a few pointers.
There are obviously many ways to do that, but I wanted to make that as standard as possible.
The best suggestion I found was to use the ui5 Dialog control as sample. It consists of internal buttons and hence is similar to my requirement: Render something that does something on click.
https://github.com/SAP/openui5/blob/master/src/sap.ui.commons/src/sap/ui/commons/Dialog.js
In short, the solution is
1) The
<a class="json-toggle" href></a>
should not have an onclick. Neither in the tag nor by adding such via jQuery.
2) The control's javascript code should look like:
sap.ui.define(
[ 'sap/ui/core/Control' ],
function(Control) {
var control = Control.extend(
"com.controlname",
{
metadata : {
...
},
renderer : function(oRm, oControl) {
...
},
init : function() {
var libraryPath = jQuery.sap.getModulePath("mylib");
jQuery.sap.includeStyleSheet(libraryPath + "/MyControl.css");
},
onAfterRendering : function(arguments) {
if (sap.ui.core.Control.prototype.onAfterRendering) {
sap.ui.core.Control.prototype.onAfterRendering.apply(this, arguments);
}
},
});
control.prototype.onclick = function (oEvent) {
var target = oEvent.target;
return false;
};
return control;
});
Nothing in the init(), nothing in the onAfterRendering(), renderer() outputs the html. So far there is nothing special.
The only thing related with the onClick is the control.prototype.onclick. The variable "target" is the html tag that was clicked.
I'm facing shieldUI wicket integration and I'm trying to get base 64 image dfom a shieldUi chart using this tutorial: https://www.shieldui.com/documentation/javascript.chart/exporting
I tried to run the code below:
function render_image_box(chart_id) {
var result = false;
var svg_chart = $("#" + chart_id);
if (svg_chart) {
var chart = svg_chart.swidget();
if (chart != null) {
chart.exportToImage();
result = true;
}
}
// setTimeout(find_image_source, 100) // wait before continuing
return chart;
}
in both the $(document).ready(..) and $(window).load(...) functions and the load event raised from the library (https://www.shieldui.com/documentation/javascript.chart/events/load) as well.
In none of these function chart is rendered yet, so the svg_chart.swidget() returns null.
Is there any other event to use to accomplish my goal or any other way to get the image source?
Thanks in advance,
Laura
You can access the chart instance using .swidget() only after you have initialized it with the .shieldChart() constructor.
To make your code work, you should also turn off animation for the chart, because right after you initialize it, the rendering will not be over and there will be no image contents.
Here is a complete JSBin to get you started...
As far as I can tell, Backbone.js view represents DOM element. I take it from existing DOM or create it on the fly in el attribute.
In my case, I want to take it from server with AJAX request because I'm using Django templates and don't want to rewrite everything to JavaScript templates.
So I define el function that performs AJAX request.
el: function() {
model.fetch().success(function(response) {
return response.template
})
}
Of course, it does NOT work because AJAX request is executed asynchronous.
This means that I don't have el attribute and events does NOT work neither. Can I fix it?
Maybe the Backbone.js framework isn't the right tool for my needs? The reason I want to use that was to have some structure for the code.
P.S. I'm new to Backbone.js.
Do your ajax request from another view, or directly after the page load using jquery directly, and after you've downloaded your template, THEN instantiate your backbone view class with the proper id/el or whatever (depending on where you stored your ajax fetched template). Depending on your use-case, this may or may not be a sensible approach.
Another, perhaps more typical approach, would be to set up your view with some placeholder element (saying "loading" or whatever), then fire off the ajax, and after the updated template has been retrieved, then update your view accordingly (replace the placeholder with the actual template you requested).
When/if you update your view with new/other DOM elements, you need to call the view's delegateEvents method to rebind your events to the new elements, see:
http://backbonejs.org/#View-delegateEvents
I came across a similar requirement. In my instance, I was running asp.net and wanted to pull my templates from user controls. The first thing I would recommend is looking into Marionette because it will save you from writing a lot of boiler plate code in Backbone. The next step is to override how your templates are loaded. In this case I created a function that uses Ajax to retrieve the HTML from the server. I found an example of this function where they were using it to pull down html pages so I did a little modification so I can make MVC type requests. I can't remember where I found the idea from; otherwise, I would give the link here.
function JackTemplateLoader(params) {
if (typeof params === 'undefined') params = {};
var TEMPLATE_DIR = params.dir || '';
var file_cache = {};
function get_filename(name) {
if (name.indexOf('-') > -1) name = name.substring(0, name.indexOf('-'));
return TEMPLATE_DIR + name;
}
this.get_template = function (name) {
var template;
var file = get_filename(name);
var file_content;
var result;
if (!(file_content = file_cache[name])) {
$.ajax({
url: file,
async: false,
success: function (data) {
file_content = data; // wrap top-level templates for selection
file_cache[name] = file_content;
}
});
}
//return file_content.find('#' + name).html();
return file_content;
}
this.clear_cache = function () {
template_cache = {};
};
}
The third step would be to override Marionette's method to load templates. I did this in the app.addInitializer method. Here I am initializing my template loader and setting it's directory to a route handler. So when I want to load a template, I just set the template: "templatename" in my view and Backbone will load the template from api/ApplicationScreens/templatename. I am also overriding my template compiling to use Handlebars because ASP.net is not impressed with the <%= %> syntax.
app.JackTemplateLoader = new JackTemplateLoader({ dir: "/api/ApplicationScreens/", ext: '' });
Backbone.Marionette.TemplateCache.prototype.loadTemplate = function (name) {
if (name == undefined) {
return "";
} else {
var template = app.JackTemplateLoader.get_template(name);
return template;
}
};
// compiling
Backbone.Marionette.TemplateCache.prototype.compileTemplate = function (rawTemplate) {
var compiled = Handlebars.compile(rawTemplate);
return compiled;
};
// rendering
Backbone.Marionette.Renderer.render = function (template, data) {
var template = Marionette.TemplateCache.get(template);
return template(data);
}
Hopefully this helps. I've been working on a large dynamic website and it is coming along very nicely. I am constantly being surprised by the overall functionality and flow of using Marionette and Backbone.js.
EDIT - URL to see the issue http://syndex.me
I am dynamically resizing images bigger than the browser to equal the size of the browser.
This was no easy feat as we had to wait for the images to load first in order to check first if the image was bigger than the window.
We got to this stage (which works):
var maxxxHeight = $(window).height();
$(".theImage").children('img').each(function() {
$(this).load( function() { // only if images can be loaded dynamically
handleImageLoad(this);
});
handleImageLoad(this);
});
function handleImageLoad(img)
{
var $img = $(img), // declare local and cache jQuery for the argument
myHeight = $img.height();
if ( myHeight > maxxxHeight ){
$img.height(maxxxHeight);
$img.next().text("Browser " + maxxxHeight + " image height " + myHeight);
};
}
The thing is, the page is an infinite scroll (I'm using this)
I know that you are not able to attach 'live' to 'each' as 'live' deals with events, and 'each' is not an event.
I've looked at things like the livequery plugin and using the ajaxComplete function.
With livequery i changed
$(".theImage").children('img').each(function() {
to
$(".theImage").children('img').livequery(function(){
But that didnt work.
ajaxComplete seemed to do nothing so i'm guessing the inifinte scroll i'm using is not ajax based. (surely it is though?)
Thanks
Use delegate:
$(".theImage").delegate('img', function() {
$(this).load( function() { // only if images can be loaded dynamically
handleImageLoad(this);
});
handleImageLoad(this);
});
The problem is that your infinite scroll plugin does not provide the callback functionality. Once your pictures are loaded there is no way to affect them.
I have tried to modify your plugin, so that it will serve your needs, please see http://jsfiddle.net/R8yLZ/
Scroll down the JS section till you see a bunch of comments.
This looks really complicated, and I probably don't get it at all, but I'll try anyway :-)
$("img", ".theImage").bind("load", function() {
var winH = $(window).height();
var imgH = $(this).height();
if (winH < imgH) {
$(this).height(winH);
$(this).next().text("Browser " + winH + " image height " + imgH);
}
});
I am new to prototype and finding it a lot more difficult than jquery. All i want to do is get the inner html of various classes.
$$('.book').each(function() {
var msg = this.down(".information");
alert(msg.innerHTML);
//new Tip(this, msg.innerHTML, {stem: 'topLeft', hook: { tip: 'topLeft', mouse: true }, offset: { x: 14, y: 14 }});
});
I'm trying to create tooltips for multiple items, but I'm not even getting the alert.
I think you can probably prevent the extra dom work of down() like this:
$$('.book .information').each(function(book) {
alert(book.innerHTML);
});
remember you also have the ability to use advanced CSS2 and CSS3 selectors in prototype like this for example:
$$('.book a[rel]').each(function(el) {
alert(el.rel);
});
see the bottom of this page for more examples http://www.prototypejs.org/api/utility/dollar-dollar
The this variable is not pointing to the element you're iterating over in Prototype, you have to explicitly use a parameter:
$$('.book').each(function(book) {
var msg = book.down(".information");
alert(msg.innerHTML);
});