define(['jquery', 'underscore', 'backbone', 'views/ExpandCollapseListView', 'text!templates/provStatement.html'], function($, _, Backbone, ExpandCollapseList, ProvTemplate) { 'use strict'; /* * Constructs a list of provenance statements based on the indexed prov fields of Solr documents. * Renders a list of paragraph tags with sentences and links to the objects in the sentence. * The Prov Statement template can be used to display other UI elements along with the textual prov statements. */ var ProvStatementView = Backbone.View.extend({ /* * OPTIONS * model : A SolrResult model that the statements are using as a context. The prov statements will call this model "this data" or "this image", etc. * Provenance traces that do not involve this model will not be displayed. * relatedModels : an array of SolrResult models that this view will look through to find the prov trace. */ initialize: function(options){ if((options === undefined) || (!options)) var options = {}; this.className += options.className || ""; this.model = options.model || null; this.relatedModels = options.relatedModels || new Array(); this.currentlyViewing = options.currentlyViewing || null; this.relatedModels = _.uniq(_.flatten(this.relatedModels)); }, template: _.template(ProvTemplate), tagName : "p", className : "provenance-statement-container", //Prov fields / predicates in the prov statements that we do not want to display skipPredicates: ["prov_generatedByExecution", "prov_generatedByUser", "prov_instanceOfClass", "prov_wasGeneratedBy", "prov_usedByExecution", "prov_usedByUser", "prov_wasExecutedByExecution", "prov_wasExecutedByUser"], subviews: new Array(), events: { }, /* * Creates a provenance statement and inserts it into the template */ render: function(){ //We need a SolrResult model in order to create a statement if(!this.model) return false; var view = this, statementsContainer = $(document.createElement("div")); //Add the provenance statement HTML from the template this.$el.html(view.template()); //Make a triple for each prov property function Triple(s, p, o){ this.subject = s; this.predicate = p; this.object = o; if(typeof s === "object") this.subjectID = s.get("id"); else this.subjectID = s; if(typeof o === "object") this.objectID = o.get("id"); else this.objectID = o; } var allTriples = new Array(); //Make a list of predicates that we want to use in the prov statements var predicates = _.difference(appSearchModel.getProvFields(), this.skipPredicates); //Look for prov traces in all the models var allModels = _.union([this.model], this.relatedModels); _.each(allModels, function(model, i, list){ _.each(predicates, function(p, ii){ if((typeof model.get(p) !== "undefined") && model.get(p)){ var predicateValues = model.get(p), inversePredicate = view.getInversePredicate(p); _.each(predicateValues, function(value, iii){ //Find a model in the "relatedModels" option if there is one that matches var modelFromID = _.find(view.relatedModels, function(m){ return(m.get("id") == value); }), tripleObject; if(typeof modelFromID === "undefined") tripleObject = value; else tripleObject = modelFromID; //Create the Triple var triple = new Triple(model, p, tripleObject); //Make sure this triple does not already exist as an inverse var hasInverse = false, x = 0; if((typeof allTriples[inversePredicate] !== "undefined")){ while (!hasInverse && (x < allTriples[inversePredicate].length-1)){ var inverseTriple = allTriples[inversePredicate][x]; //If the triples match, then this triple's subject == its inverse's object AND this triple's object == its inverse's subject. if((inverseTriple.subjectID == triple.objectID) && (inverseTriple.objectID == triple.subjectID)) hasInverse = true; x++; } } if(!hasInverse){ if((typeof allTriples[p] === "undefined") || (!allTriples[p])) allTriples[p] = new Array(triple); else allTriples[p].push(triple); } }); } }); }); //Basic info about our context model if(this.model.type == "Package") var noun = "package"; else var noun = this.model.getType(); var id = this.model.get("id"); //Get a list of the predicates that we saved earlier var populatedPredicates = Object.keys(allTriples); //Go through this array of triples, sorted by predicate _.each(populatedPredicates, function(predicate, i){ //Start the statement/sentence when this model is the subject var subjStatementBegin = "This " + noun + " " + view.getPredicate(predicate), subjList = new Array(); //State the statement/sentence when this model is the object var objStatementBegin = "This " + noun + " " + view.getPredicate(predicate, true), objList = new Array(); //Go through each triple type, based on predicate, in order to make prov statements _.each(allTriples[predicate], function(triple, ii){ var type = ""; //If the subject of this triple equals the id of this model, then structure the sentence as so if((triple.subject == id) || ((typeof triple.subject === "object") && (triple.subject.get("id") == id))){ //Get information about the object of this triple var objectId = "", objectName = ""; if(typeof triple.object === "string"){ objectId = triple.object; objectName = triple.object; } else{ objectId = triple.object.get("id"); objectName = triple.object.get("fileName"); } var objectModel = _.find(view.relatedModels, function(m){ return(m.get("id") == objectId); }); //Get the type of object this is so we can make an icon out of it if(typeof objectModel !== "undefined") type = objectModel.getType(); var icon = $(document.createElement("i")).attr("class", "icon " + view.getIconType(type)); if(!objectName){ if(typeof objectModel !== "undefined") objectName = objectModel.get("id") else objectName = type || ""; } var linkText = $(document.createElement("span")).text(objectName).prepend(icon); //Make a link out of the object ID var link = $(document.createElement("a")).attr("href", "#view/" + objectId) .prepend(linkText) .attr("data-id", objectId) .addClass("highlight-node preview node-link"); //Is the triple object the entity the user is currently viewing? if(view.currentlyViewing && (view.currentlyViewing.get("id") == objectId)){ $(linkText).addClass("currently-viewing pointer highlight-node node-link").attr("data-id", objectId); var linkContainer = $(document.createElement("span")) .prepend($(document.createElement("span")).text("the " + view.currentlyViewing.getType() + " you are currently viewing, "), linkText); subjList.push(linkContainer); } else subjList.push(link); } //If the object of this triple equals the id of this model, then structure the sentence as so else if((triple.object == id) || ((typeof triple.object === "object") && (triple.object.get("id") == id))){ //Get information about the subject of this triple var subjectId = "", subjectName = ""; if(typeof triple.subject === "string"){ subjectId = triple.subject; subjectName = triple.subject; } else{ subjectId = triple.subject.get("id"); subjectName = triple.subject.get("fileName"); } var subjectModel = _.find(view.relatedModels, function(m){ return(m.get("id") == subjectId); }); //Get the type of object this is so we can make an icon out of it if(typeof subjectModel !== "undefined") type = subjectModel.getType(); var icon = $(document.createElement("i")).attr("class", "icon " + view.getIconType(type)); if(!subjectName){ if(typeof subjectModel !== "undefined") subjectName = subjectModel.get("id") else subjectName = type || ""; } var linkText = $(document.createElement("span")).text(subjectName); //Make a link of the subject ID var link = $(document.createElement("a")).attr("href", "#view/" + subjectId) .prepend(icon, linkText) .attr("data-id", subjectId) .addClass("highlight-node preview"); //Is the subject the entity the user is currently viewing? if(view.currentlyViewing && (view.currentlyViewing.get("id") == subjectId)){ $(linkText).addClass("currently-viewing pointer highlight-node").attr("data-id", subjectId); var linkContainer = $(document.createElement("span")) .prepend($(document.createElement("span")).text("the " + view.currentlyViewing.getType() + " you are currently viewing, "), icon, linkText); objList.push(linkContainer); } else objList.push(link); } }); //Add these statements to our element if(subjList.length > 0){ var collapseListView = new ExpandCollapseList({list: subjList, prependText: subjStatementBegin, appendText: ". "}); view.subviews.push(collapseListView); var singleStatement = collapseListView.render().el; $(singleStatement).addClass("list-group-item statement"); } if(objList.length > 0){ var collapseListView = new ExpandCollapseList({list: objList, prependText: objStatementBegin, appendText: ". "}); view.subviews.push(collapseListView); var singleStatement = collapseListView.render().el; $(singleStatement).addClass("list-group-item statement"); } //Does this provenance statement already exist? (This can happen when a triple is written in inverse) var allStatements = $(statementsContainer).children(".statement"), isUnique = true; for(var x=0; x