... <script type="text/javascript" src=".../jquery.js"></script> <script type="text/javascript" src=".../underscore.js"></script> <script type="text/javascript" src=".../backbone.js"></script> <script type="text/javascript" src=".../jquery.rdfquery.js"></script> <script type="text/javascript" src=".../vie-2.0.0.js"></script> <script type="text/javascript" src=".../jquery-ui.js"></script> <!-- the current VIE widget --> <script type="text/javascript" src="./vie.widget.image_search.js"></script> ...
Now, we can read-in the embedded RDFa annotations from the webpage:
<script type="text/javascript"> $(function() { // initialize a global VIE object var myVIE = window.myVIE = new VIE(); myVIE.loadSchema("http://schema.rdfs.org/all.json", { baseNS : "http://schema.org/", success : function () { // read RDFa from the trext in the HTML myVIE.use(new myVIE.RdfaService); myVIE.load({element: $('[about]')}).using('rdfa').execute(); finishSetUp(); }, error : function (e) { //.... } }); function finishSetUp () { // set-up of the Image-widget $('#image_container') .vieImageSearch({ // your local VIE instance vie : myVIE, // the maximal number of elements to query each service for max_objects : 8, // setup of each service services : { flickr : { api_key : "<your_FLICKR_API_KEY_HERE>", // whether to use it or not use: true, // some weight for the relevance sorting algorithm weight: 1.0 }, gimage : { use: true, weight: 1.0 }, ookaboo : { use: true, weight: 1.0 } } }); } });
And finally, registering the annotated RDFa elements with a click-event handler to trigger the semantic image search:
$('[about]') .click(function () { $('#image_container').vieImageSearch({ entity: $(this).attr('about') }); });
User: "Great! But this searches only for Persons and Places... How can I extend it?"
Me: "Glad that you ask! Let me show you how to customize the widget to search for a Product:"
$('#image_container') .vieImageSearch({ //URLs are generated dependence of the given <Entity-Type, Service> tuple ts_url : { "Product" : { 'gimage' : function (entity, serviceId) { var widget = this; var service = widget.options.services[serviceId]; var url = service.base_url.replace("\/$", ""); if (entity.has("brand")) { var brand = entity.get("brand"); var brandName = VIE.Util.extractLanguageString( brand, ["name", "rdf:label"], widget.options.lang); url += "&q=" + brandName.replace(/ /g, '+'); // *no* type-specific keywords url += (service.tail_url)? service.tail_url(widget, service) : ""; return url; } else { return undefined; } } } }, ts_data : { //data is generated in dependence of the given <Entity-Type, Service> tuple "Product" : { 'ookaboo' : function (entity, serviceId) { var widget = this; var service = widget.options.services[serviceId]; // this might return some data, usually not needed return ...; } } } });
User: "Nice! And how can I extend it to query my own CMS repository?"
Me: "Phew... Tough one... Not! (assuming you have implemented it with proper CORS support!)"
"What you need is to add another service to the options like shown below and implement the following skeleton:"
$('#image_container').vieImageSearch({ services : { 'myowncmsimageservicebackend' : { use : {true|false}, weight : 1.0, api_key : undefined, base_url : "...", // this method performs the query and forwards the result to the callback! query : function (serviceId, entity, queryId) { var widget = this; var service = widget.options.services[serviceId]; var ret = widget._entityToUrlData.call(widget, entity, serviceId); jQuery.ajax({ url : ret["url"], data : JSON.stringify(ret["data"]), dataType : "application/json", contentType : "application/json", type : "POST", complete : service.callback(widget, entity.getSubjectUri(), serviceId, queryId) }); }, tail_url : function (widget, service) { var url = "&sort=relevance"; url += "&max_elements=" + widget.options.max_objects; url += "&api_key=" + service.api_key; url += "format=json"; return url; }, // this method transforms the raw data into the VIE world! callback : function (widget, entityId, serviceId, queryId) { return function (response) { var objects = []; //... var data = {entityId : entityId, serviceId: serviceId, queryId : queryId, time: new Date(), objects: objects}; widget._trigger('end_query', undefined, data); widget.render(data); }; } } } });Me: "Eventually that would look something like that:"
$('#image_container').vieImageSearch({ services : { 'myowncmsimageservicebackend' : { use : true, weight : 1.0, api_key : undefined, base_url : "http://mycms.com/images/?method=related", query : function (serviceId, entity, queryId) { var widget = this; var service = widget.options.services[serviceId]; var ret = widget._entityToUrlData.call(widget, entity, serviceId); if (ret && ret["url"]) { jQuery.getJSON(ret["url"], service.callback(widget, entity.getSubjectUri(), serviceId, queryId)); } else { widget._trigger("error", undefined, { msg: "No type-specific URL can be acquired for entity. Please add/overwrite widget.options[\"Thing\"][" + serviceId + "]!", id : entity.getSubjectUri(), service : serviceId, queryId : queryId}); } }, tail_url : function (widget, service) { var url = "&sort=relevance"; url += "&max_elements=" + widget.options.max_objects; url += "&api_key=" + service.api_key; url += "format=json"; return url; }, callback : function (widget, entityId, serviceId, queryId) { return function (data) { var service = widget.options.services[serviceId]; var objects = []; if (data.stat === 'ok' && data.photos.total > 0) { //put them into bins for (var i = 0; i < data.photos.photo.length; i++) { var photo = data.photos.photo[i]; var obj = { "weight" : (service.weight)? service.weight : 1.0, "thumbnail" : photo["thumbnail"], "original" : photo["origional"], "height" : photo["height"], "width" : photo["width"] }; objects.push(obj); } } var data = {entityId : entityId, serviceId: serviceId, queryId : queryId, time: new Date(), objects: objects}; widget._trigger('end_query', undefined, data); widget.render(data); }; } } } });
User: "Awesome! Are there any other options/events/methods, I should know about?"
Me: "Not that many... Here they are:"
Events
Pre-init
// the event is thrown when a service-query is started $('#image_container').bind('vieimagesearchstart_query', function (ev) {...}); // the event is thrown when a service-query ends $('#image_container').bind('vieimagesearchend_query', function (ev) {...}); // the event is thrown when a warning arised $('#image_container').bind('vieimagesearchwarn', function (ev) {...}); // the event is thrown when an error happened $('#image_container').bind('vieimagesearcherror', function (ev) {...});
Options
Post-init
// the instance of VIE $('#image_container').vieImageSearch("option", "vie", myVIE); // the maximal number of elements to be queried from each service $('#image_container').vieImageSearch("option", "max_objects", 10); // an array of languages that is used to perform language-specific queries $('#image_container').vieImageSearch("option", "lang", ["en", "de", "es", ...]); // a timeout if the queries take too long. default: 10 seconds $('#image_container').vieImageSearch("option", "timeout", 1000); // you might probably want to change the rendering of the images. // using the options, you can overwrite the "render" key with a method // that takes an array of VIE Entities (typeof 'VIEImageResult') // as input to render the images $('#image_container').vieImageSearch("option", "render", function (objEntities) {...});
Options
Pre-init
$('#image_container').vieImageSearch({ "vie" : myVIE, "render" : function (objEntities) {...}, "max_objects" : 10, "lang" : ["en", "de", "es", ...], "timeout" : 10000, "services" : { "flickr" : { "use" : {true|false}, "weight" : 1.0, "api_key" : "...", "base_url" : "..." ... }, ... } });
Methods
// get the widget's instance var widget = $('#image_container').data("vieImageSearch"); // list all available services widget.options.services; // change to use a service, e.g., gimages, post-init) $('#image_container').vieImageSearch("useService", 'gimage', {true|false});