src/js/widgets-container/widget.js
changeset 1072 ac1eacb3aa33
parent 1068 7623f9af9272
child 1073 687133dc13cf
equal deleted inserted replaced
1071:02c04d2c8fd8 1072:ac1eacb3aa33
     1 /* widgetsDefinition of an ancestor for the Widget classes */
     1 /* widgetsDefinition of an ancestor for the Widget classes */
     2 
     2 import Mustache from "mustache";
     3 if (typeof IriSP.Widgets === "undefined") {
     3 import jQuery from "jquery";
     4     IriSP.Widgets = {};
       
     5 }
       
     6 
     4 
     7 /**
     5 /**
     8  * @class IriSP.Widget is an "abstract" class. It's mostly used to define some properties common to every widget.
     6  * @class IriSP.Widget is an "abstract" class. It's mostly used to define some properties common to every widget.
     9  *
     7  *
    10  *  Note that widget constructors are never called directly by the user. Instead, the widgets are instantiated by functions
     8  *  Note that widget constructors are never called directly by the user. Instead, the widgets are instantiated by functions
    11  *  defined in init.js
     9  *  defined in init.js
    12  *
    10  *
    13  * @constructor
    11  * @constructor
    14  * @param player - a reference to the player widget
    12  * @param player - a reference to the player widget
    15  * @param config - configuration options for the widget
    13  * @param config - configuration options for the widget
       
    14  *
    16  */
    15  */
    17 
    16 
    18 
    17 import _ from "lodash";
    19 IriSP.Widgets.Widget = function(player, config) {
    18 
    20 
    19 export default function (IriSP) {
    21     if( typeof player === "undefined") {
    20   const Widgets = {};
       
    21 
       
    22   Widgets.Widget = class {
       
    23 
       
    24     static defaults = {};
       
    25 
       
    26     static template = "";
       
    27 
       
    28     static messages = { en: {} };
       
    29 
       
    30     constructor(player, config) {
       
    31       if (typeof player === "undefined") {
    22         /* Probably an abstract call of the class when
    32         /* Probably an abstract call of the class when
    23          * individual widgets set their prototype */
    33          * individual widgets set their prototype */
    24         return;
    34         return;
    25     }
    35       }
    26 
    36 
    27     this.__subwidgets = [];
    37       this.__subwidgets = [];
    28 
    38 
    29     /* Setting all the configuration options */
    39       /* Setting all the configuration options */
    30     var _type = config.type || "(unknown)",
    40       var _type = config.type || "(unknown)",
    31         _config = IriSP._.defaults({}, config, (player && player.config ? player.config.default_options : {}), this.defaults),
    41         _config = _.defaults(
       
    42           {},
       
    43           config,
       
    44           player && player.config ? player.config.default_options : {},
       
    45           this.constructor.defaults
       
    46         ),
    32         _this = this;
    47         _this = this;
    33 
    48 
    34     IriSP._(_config).forEach(function(_value, _key) {
    49       _(_config).forEach(function (_value, _key) {
    35        _this[_key] = _value;
    50         _this[_key] = _value;
    36     });
    51       });
    37 
    52 
    38     this.$ = IriSP.jQuery('#' + this.container);
    53       this.$ = jQuery("#" + this.container);
    39 
    54 
    40     if (typeof this.width === "undefined") {
    55       if (typeof this.width === "undefined") {
    41         this.width = this.$.width();
    56         this.width = this.$.width();
    42     } else {
    57       } else {
    43         this.$.css("width", this.width);
    58         this.$.css("width", this.width);
    44     }
    59       }
    45 
    60 
    46     if (typeof this.height !== "undefined") {
    61       if (typeof this.height !== "undefined") {
    47         this.$.css("height", this.height);
    62         this.$.css("height", this.height);
    48     }
    63       }
    49 
    64 
    50     /* Setting this.player at the end in case it's been overriden
    65       /* Setting this.player at the end in case it's been overriden
    51      * by a configuration option of the same name :-(
    66        * by a configuration option of the same name :-(
    52      */
    67        */
    53     this.player = player || new IriSP.FakeClass(["on","trigger","off","loadWidget","loadMetadata"]);
    68       this.player =
    54 
    69         player ||
    55     /* Adding classes and html attributes */
    70         new IriSP.FakeClass([
    56     this.$.addClass("Ldt-TraceMe Ldt-Widget").attr("widget-type", _type);
    71           "on",
    57 
    72           "trigger",
    58     this.l10n = (
    73           "off",
    59         typeof this.messages[IriSP.language] !== "undefined"
    74           "loadWidget",
    60         ? this.messages[IriSP.language]
    75           "loadMetadata",
    61         : (
    76         ]);
    62             IriSP.language.length > 2 && typeof this.messages[IriSP.language.substr(0,2)] !== "undefined"
    77 
    63             ? this.messages[IriSP.language.substr(0,2)]
    78       /* Adding classes and html attributes */
    64             : this.messages["en"]
    79       this.$.addClass("Ldt-TraceMe Ldt-Widget").attr("widget-type", _type);
    65         )
    80 
    66     );
    81       this.l10n =
    67 
    82         typeof this.constructor.messages[IriSP.language] !== "undefined"
    68     /* Loading Metadata if required */
    83           ? this.constructor.messages[IriSP.language]
    69 
    84           : IriSP.language.length > 2 &&
    70     function onsourceloaded() {
    85             typeof this.constructor.messages[IriSP.language.substr(0, 2)] !== "undefined"
       
    86           ? this.constructor.messages[IriSP.language.substr(0, 2)]
       
    87           : this.constructor.messages["en"];
       
    88 
       
    89       /* Loading Metadata if required */
       
    90 
       
    91       function onsourceloaded() {
    71         if (_this.localannotations) {
    92         if (_this.localannotations) {
    72             _this.localsource = player.loadLocalAnnotations(_this.localannotations);
    93           _this.localsource = player.loadLocalAnnotations(
    73             _this.source.merge(_this.localsource);
    94             _this.localannotations
       
    95           );
       
    96           _this.source.merge(_this.localsource);
    74         }
    97         }
    75         if (_this.media_id) {
    98         if (_this.media_id) {
    76                 _this.media = this.getElement(_this.media_id);
    99           _this.media = this.getElement(_this.media_id);
    77             } else {
   100         } else {
    78                 var _mediaopts = {
   101           var _mediaopts = {
    79                     is_mashup: _this.is_mashup || false
   102             is_mashup: _this.is_mashup || false,
    80                 };
   103           };
    81                 _this.media = _this.source.getCurrentMedia(_mediaopts);
   104           _this.media = _this.source.getCurrentMedia(_mediaopts);
    82             }
   105         }
    83         if (_this.pre_draw_callback){
   106         if (_this.pre_draw_callback) {
    84             IriSP.jQuery.when(_this.pre_draw_callback()).done(_this.draw());
   107           jQuery.when(_this.pre_draw_callback()).done(_this.draw());
    85         }
   108         } else {
    86         else {
   109           _this.draw();
    87             _this.draw();
       
    88         }
   110         }
    89         _this.player.trigger("widget-loaded");
   111         _this.player.trigger("widget-loaded");
    90     }
   112       }
    91 
   113 
    92     if (this.metadata) {
   114       if (this.metadata) {
    93         /* Getting metadata */
   115         /* Getting metadata */
    94         this.source = player.loadMetadata(this.metadata);
   116         this.source = player.loadMetadata(this.metadata);
    95         /* Call draw when loaded */
   117         /* Call draw when loaded */
    96         this.source.onLoad(onsourceloaded);
   118         this.source.onLoad(onsourceloaded);
    97     } else {
   119       } else {
    98         if (this.source) {
   120         if (this.source) {
    99             onsourceloaded();
   121           onsourceloaded();
   100         }
   122         }
   101     }
   123       }
   102 
   124     }
   103 
   125 
   104 };
   126     toString() {
   105 
   127       return "Widget " + this.type;
   106 IriSP.Widgets.Widget.prototype.defaults = {};
   128     }
   107 
   129 
   108 IriSP.Widgets.Widget.prototype.template = '';
   130     templateToHtml(_template) {
   109 
   131       return Mustache.render(_template, this);
   110 IriSP.Widgets.Widget.prototype.messages = {"en":{}};
   132     }
   111 
   133 
   112 IriSP.Widgets.Widget.prototype.toString = function() {
   134     renderTemplate() {
   113     return "Widget " + this.type;
   135       this.$.append(this.templateToHtml(this.constructor.template));
   114 };
   136     }
   115 
   137 
   116 IriSP.Widgets.Widget.prototype.templateToHtml = function(_template) {
   138     functionWrapper(_name) {
   117     return Mustache.to_html(_template, this);
   139       var _this = this,
   118 };
       
   119 
       
   120 IriSP.Widgets.Widget.prototype.renderTemplate = function() {
       
   121     this.$.append(this.templateToHtml(this.template));
       
   122 };
       
   123 
       
   124 IriSP.Widgets.Widget.prototype.functionWrapper = function(_name) {
       
   125     var _this = this,
       
   126         _function = this[_name];
   140         _function = this[_name];
   127     if (typeof _function !== "undefined") {
   141       if (typeof _function !== "undefined") {
   128         return function() {
   142         return function () {
   129             return _function.apply(_this, Array.prototype.slice.call(arguments, 0));
   143           return _function.apply(
       
   144             _this,
       
   145             Array.prototype.slice.call(arguments, 0)
       
   146           );
   130         };
   147         };
   131     } else {
   148       } else {
   132         console.log("Error, Unknown function IriSP.Widgets." + this.type + "." + _name);
   149         console.log(
   133     }
   150           "Error, Unknown function IriSP.Widgets." + this.type + "." + _name
   134 };
   151         );
   135 
   152       }
   136 IriSP.Widgets.Widget.prototype.getFunctionOrName = function(_functionOrName) {
   153     }
   137     switch (typeof _functionOrName) {
   154 
       
   155     getFunctionOrName(_functionOrName) {
       
   156       switch (typeof _functionOrName) {
   138         case "function":
   157         case "function":
   139             return _functionOrName;
   158           return _functionOrName;
   140         case "string":
   159         case "string":
   141             return this.functionWrapper(_functionOrName);
   160           return this.functionWrapper(_functionOrName);
   142         default:
   161         default:
   143             return undefined;
   162           return undefined;
   144     }
   163       }
   145 };
   164     }
   146 
   165 
   147 IriSP.Widgets.Widget.prototype.onMdpEvent = function(_eventName, _functionOrName) {
   166     onMdpEvent(_eventName, _functionOrName) {
   148     this.player.on(_eventName, this.getFunctionOrName(_functionOrName));
   167       this.player.on(_eventName, this.getFunctionOrName(_functionOrName));
   149 };
   168     }
   150 
   169 
   151 IriSP.Widgets.Widget.prototype.onMediaEvent = function(_eventName, _functionOrName) {
   170     onMediaEvent(_eventName, _functionOrName) {
   152     this.media.on(_eventName, this.getFunctionOrName(_functionOrName));
   171       this.media.on(_eventName, this.getFunctionOrName(_functionOrName));
   153 };
   172     }
   154 
   173 
   155 IriSP.Widgets.Widget.prototype.getWidgetAnnotations = function() {
   174     getWidgetAnnotations() {
   156     var result = null;
   175       var result = null;
   157     if (typeof this.annotation_type === "undefined") {
   176       if (typeof this.annotation_type === "undefined") {
   158         result = this.media.getAnnotations();
   177         result = this.media.getAnnotations();
   159     } else if (this.annotation_type.elementType === "annotationType") {
   178       } else if (this.annotation_type.elementType === "annotationType") {
   160         result = this.annotation_type.getAnnotations();
   179         result = this.annotation_type.getAnnotations();
   161     } else {
   180       } else {
   162         result = this.media.getAnnotationsByTypeTitle(this.annotation_type);
   181         result = this.media.getAnnotationsByTypeTitle(this.annotation_type);
   163     }
   182       }
   164     if (typeof this.annotation_filter !== "undefined") {
   183       if (typeof this.annotation_filter !== "undefined") {
   165         return this.annotation_filter(result);
   184         return this.annotation_filter(result);
   166     } else {
   185       } else {
   167         return result;
   186         return result;
   168     }
   187       }
   169 };
   188     }
   170 
   189 
   171 IriSP.Widgets.Widget.prototype.getWidgetAnnotationsAtTime = function() {
   190     getWidgetAnnotationsAtTime() {
   172     var _time = this.media.getCurrentTime();
   191       var _time = this.media.getCurrentTime();
   173     return this.getWidgetAnnotations().filter(function(_annotation) {
   192       return this.getWidgetAnnotations().filter(function (_annotation) {
   174         return _annotation.begin <= _time && _annotation.end > _time;
   193         return _annotation.begin <= _time && _annotation.end > _time;
   175     });
   194       });
   176 };
   195     }
   177 
   196 
   178 IriSP.Widgets.Widget.prototype.isLoaded = function() {
   197     isLoaded() {
   179     var isloaded = !IriSP._(this.__subwidgets).any(function(w) {
   198       var isloaded = !_(this.__subwidgets).some(function (w) {
   180         return !(w && w.isLoaded());
   199         return !(w && w.isLoaded());
   181     });
   200       });
   182     return isloaded;
   201       return isloaded;
   183 };
   202     }
   184 
   203 
   185 IriSP.Widgets.Widget.prototype.insertSubwidget = function(_selector, _widgetoptions, _propname) {
   204     insertSubwidget(_selector, _widgetoptions, _propname) {
   186     var _id = _selector.attr("id"),
   205       var _id = _selector.attr("id"),
   187         _this = this,
   206         _this = this,
   188         _type = _widgetoptions.type,
       
   189         $L = $LAB,
       
   190         key = this.__subwidgets.length;
   207         key = this.__subwidgets.length;
   191     this.__subwidgets.push(null);
   208       this.__subwidgets.push(null);
   192     if (typeof _id == "undefined") {
   209       if (typeof _id == "undefined") {
   193         _id = IriSP._.uniqueId(this.container + '_sub_widget_' + _widgetoptions.type);
   210         _id = _.uniqueId(this.container + "_sub_widget_" + _widgetoptions.type);
   194         _selector.attr("id", _id);
   211         _selector.attr("id", _id);
   195     }
   212       }
   196     _widgetoptions.container = _id;
   213       _widgetoptions.container = _id;
   197     if (typeof IriSP.widgetsRequirements[_type] !== "undefined" && typeof IriSP.widgetsRequirements[_type].requires !== "undefined" ) {
   214       _this.player.loadWidget(_widgetoptions, function (_widget) {
   198         for (var _j = 0; _j < IriSP.widgetsRequirements[_type].requires.length; _j++) {
   215         if (_propname) {
   199             $L.script(IriSP.getLib(IriSP.widgetsRequirements[_type].requires[_j]));
   216           _this[_propname] = _widget;
   200         }
   217         }
   201     }
   218         _this.__subwidgets[key] = _widget;
   202     $L.wait(function() {
   219       });
   203         _this.player.loadWidget(_widgetoptions, function(_widget) {
   220     }
   204             if (_propname) {
   221 
   205                 _this[_propname] = _widget;
   222     /*
       
   223      * Position the player to the next/previous annotations based on current player position
       
   224      *
       
   225      * Parameter: offset: -1 for previous annotation, +1 for next annotation
       
   226      */
       
   227     navigate(offset) {
       
   228       // offset is normally either -1 (previous slide) or +1 (next slide)
       
   229       var _this = this;
       
   230       var currentTime = _this.media.getCurrentTime();
       
   231       var annotations = _this
       
   232         .getWidgetAnnotations()
       
   233         .sortBy(function (_annotation) {
       
   234           return _annotation.begin;
       
   235         });
       
   236       for (var i = 0; i < annotations.length; i++) {
       
   237         if (
       
   238           annotations[i].begin <= currentTime &&
       
   239           currentTime < annotations[i].end
       
   240         ) {
       
   241           // Found a current annotation - clamp i+offset value to [0, length - 1]
       
   242           i = Math.min(annotations.length - 1, Math.max(0, i + offset));
       
   243           _this.media.setCurrentTime(annotations[i].begin);
       
   244           break;
       
   245         }
       
   246       }
       
   247     }
       
   248 
       
   249     /*
       
   250      * Propose an export of the widget's annotations
       
   251      *
       
   252      * Parameter: a list of annotations. If not specified, the widget's annotations will be exported.
       
   253      */
       
   254     exportAnnotations(annotations) {
       
   255       var widget = this;
       
   256       if (annotations === undefined) annotations = this.getWidgetAnnotations();
       
   257       var $ = jQuery;
       
   258 
       
   259       // FIXME: this should belong to a proper serialize/deserialize component?
       
   260       var content =
       
   261         Mustache.render("[video:{{url}}]\n", { url: widget.media.url }) +
       
   262         annotations
       
   263           .map(function (a) {
       
   264             return Mustache.render(
       
   265               "[{{ a.begin }}]{{ a.title }} {{ a.description }}[{{ a.end }}]",
       
   266               { a: a }
       
   267             );
       
   268           })
       
   269           .join("\n");
       
   270 
       
   271       var el = $("<pre>")
       
   272         .addClass("exportContainer")
       
   273         .text(content)
       
   274         .dialog({
       
   275           title: "Annotation export",
       
   276           open: function (event, ui) {
       
   277             // Select text
       
   278             var range;
       
   279             if (document.selection) {
       
   280               range = document.body.createTextRange();
       
   281               range.moveToElementText(this[0]);
       
   282               range.select();
       
   283             } else if (window.getSelection) {
       
   284               range = document.createRange();
       
   285               range.selectNode(this[0]);
       
   286               window.getSelection().addRange(range);
   206             }
   287             }
   207             _this.__subwidgets[key] = _widget;
   288           },
       
   289           autoOpen: true,
       
   290           width: "80%",
       
   291           minHeight: "400",
       
   292           height: 400,
       
   293           buttons: [
       
   294             {
       
   295               text: "Close",
       
   296               click: function () {
       
   297                 $(this).dialog("close");
       
   298               },
       
   299             },
       
   300             {
       
   301               text: "Download",
       
   302               click: function () {
       
   303                 a = document.createElement("a");
       
   304                 a.setAttribute(
       
   305                   "href",
       
   306                   "data:text/plain;base64," + btoa(content)
       
   307                 );
       
   308                 a.setAttribute(
       
   309                   "download",
       
   310                   "Annotations - " +
       
   311                     widget.media.title.replace(/[^ \w]/g, "") +
       
   312                     ".txt"
       
   313                 );
       
   314                 a.click();
       
   315               },
       
   316             },
       
   317           ],
   208         });
   318         });
   209     });
   319     }
   210 };
   320 
   211 
   321     /**
   212 /*
   322      * This method responsible of drawing a widget on screen.
   213  * Position the player to the next/previous annotations based on current player position
   323      */
   214  *
   324     draw() {
   215  * Parameter: offset: -1 for previous annotation, +1 for next annotation
   325       /* implemented by "sub-classes" */
   216  */
   326     }
   217 IriSP.Widgets.Widget.prototype.navigate = function(offset) {
   327 
   218     // offset is normally either -1 (previous slide) or +1 (next slide)
   328     //Generates uid
   219     var _this = this;
   329     //source : http://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript
   220     var currentTime = _this.media.getCurrentTime();
   330     generateUid() {
   221     var annotations = _this.getWidgetAnnotations().sortBy(function(_annotation) {
   331       return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(
   222         return _annotation.begin;
   332         /[xy]/g,
   223     });
   333         function (c) {
   224     for (var i = 0; i < annotations.length; i++) {
   334           var r = (Math.random() * 16) | 0,
   225         if (annotations[i].begin <= currentTime && currentTime < annotations[i].end) {
   335             v = c === "x" ? r : (r & 0x3) | 0x8;
   226             // Found a current annotation - clamp i+offset value to [0, length - 1]
   336           return v.toString(16);
   227             i = Math.min(annotations.length - 1, Math.max(0, i + offset));
   337         }
   228             _this.media.setCurrentTime(annotations[i].begin);
   338       );
   229             break;
   339     }
   230         }
   340   };
   231     };
   341 
   232 };
   342   return Widgets;
   233 
   343 }
   234 /*
       
   235  * Propose an export of the widget's annotations
       
   236  *
       
   237  * Parameter: a list of annotations. If not specified, the widget's annotations will be exported.
       
   238  */
       
   239 IriSP.Widgets.Widget.prototype.exportAnnotations = function(annotations) {
       
   240     var widget = this;
       
   241     if (annotations === undefined)
       
   242         annotations = this.getWidgetAnnotations();
       
   243     var $ = IriSP.jQuery;
       
   244 
       
   245     // FIXME: this should belong to a proper serialize/deserialize component?
       
   246     var content = Mustache.to_html("[video:{{url}}]\n", {url: widget.media.url}) + annotations.map( function(a) { return Mustache.to_html("[{{ a.begin }}]{{ a.title }} {{ a.description }}[{{ a.end }}]", { a: a }); }).join("\n");
       
   247 
       
   248     var el = $("<pre>")
       
   249             .addClass("exportContainer")
       
   250             .text(content)
       
   251             .dialog({
       
   252                 title: "Annotation export",
       
   253                 open: function( event, ui ) {
       
   254                     // Select text
       
   255                     var range;
       
   256                     if (document.selection) {
       
   257 		                range = document.body.createTextRange();
       
   258                         range.moveToElementText(this[0]);
       
   259 		                range.select();
       
   260 		            } else if (window.getSelection) {
       
   261 		                range = document.createRange();
       
   262 		                range.selectNode(this[0]);
       
   263 		                window.getSelection().addRange(range);
       
   264 		            }
       
   265                 },
       
   266                 autoOpen: true,
       
   267                 width: '80%',
       
   268                 minHeight: '400',
       
   269                 height: 400,
       
   270                 buttons: [ { text: "Close", click: function() { $( this ).dialog( "close" ); } },
       
   271                            { text: "Download", click: function () {
       
   272                                a = document.createElement('a');
       
   273                                a.setAttribute('href', 'data:text/plain;base64,' + btoa(content));
       
   274                                a.setAttribute('download', 'Annotations - ' + widget.media.title.replace(/[^ \w]/g, '') + '.txt');
       
   275                                a.click();
       
   276                            } } ]
       
   277             });
       
   278 };
       
   279 
       
   280 /**
       
   281  * This method responsible of drawing a widget on screen.
       
   282  */
       
   283 IriSP.Widgets.Widget.prototype.draw = function() {
       
   284     /* implemented by "sub-classes" */
       
   285 };