src/js/model.js
changeset 983 97fef7a4b189
parent 980 9ee8c00ae5b7
child 986 f9d51dd4a3fe
--- a/src/js/model.js	Fri Nov 16 17:36:56 2012 +0100
+++ b/src/js/model.js	Wed Nov 21 16:33:51 2012 +0100
@@ -1,7 +1,6 @@
 /* TODO: Separate Project-specific data from Source */
 
 /* model.js is where data is stored in a standard form, whatever the serializer */
-
 IriSP.Model = (function (ns) {
     
     function pad(n, x, b) {
@@ -18,7 +17,21 @@
     }
     
     var uidbase = rand16(8) + "-" + rand16(4) + "-", uidincrement = Math.floor(Math.random()*0x10000);
-
+    
+    var charsub = [
+        [ 'a', 'á', 'à', 'â', 'ä' ],
+        [ 'c', 'ç' ],
+        [ 'e', 'é', 'è', 'ê', 'ë' ],
+        [ 'i', 'í', 'ì', 'î', 'ï' ],
+        [ 'o', 'ó', 'ò', 'ô', 'ö' ]
+    ];
+    
+    var removeChars = [
+        String.fromCharCode(768), String.fromCharCode(769), String.fromCharCode(770), String.fromCharCode(771), String.fromCharCode(807),
+        "{", "}", "(", ")", "[", "]", "【", "】", "、", "・", "‥", "。", "「", "」", "『", "』", "〜", ":", "!", "?", " ",
+        ",", " ", ";", "(", ")", ".", "*", "+", "\\", "?", "|", "{", "}", "[", "]", "^", "#", "/"
+    ]
+    
 var Model = {
     _SOURCE_STATUS_EMPTY : 0,
     _SOURCE_STATUS_WAITING : 1,
@@ -26,6 +39,13 @@
     getUID : function() {
         return uidbase + pad(4, (++uidincrement % 0x10000), 16) + "-" + rand16(4) + "-" + rand16(6) + rand16(6);
     },
+    isLocalURL : function(url) {
+        var matches = url.match(/^(\w+:)\/\/([^/]+)/);
+        if (matches) {
+            return(matches[1] === document.location.protocol && matches[2] === document.location.host)
+        }
+        return true;
+    },
     regexpFromTextOrArray : function(_textOrArray, _testOnly, _iexact) {
         var _testOnly = _testOnly || false,
             _iexact = _iexact || false;
@@ -46,6 +66,30 @@
         }
         return new RegExp( _source, _flags);
     },
+    fullTextRegexps: function(_text) {
+        var remsrc = "[\\" + removeChars.join("\\") + "]",
+            remrx = new RegExp(remsrc,"gm"),
+            txt = _text.toLowerCase().replace(remrx,"")
+            res = [],
+            charsrc = ns._(charsub).map(function(c) {
+                return "(" + c.join("|") + ")";
+            }),
+            charsrx = ns._(charsrc).map(function(c) {
+                return new RegExp(c);
+            }),
+            src = "";
+        for (var j = 0; j < txt.length; j++) {
+            if (j) {
+                src += remsrc + "*";
+            }
+            var l = txt[j];
+            ns._(charsrc).each(function(v, k) {
+                l = l.replace(charsrx[k], v);
+            });
+            src += l;
+        }
+        return "(" + src + ")";
+    },
     isoToDate : function(_str) {
         // http://delete.me.uk/2005/03/iso8601.html
         var regexp = "([0-9]{4})(-([0-9]{2})(-([0-9]{2})(T([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?(Z|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?";
@@ -71,7 +115,8 @@
         _res.setTime(Number(time));
         return _res;
     },
-    dateToIso : function(d) {
+    dateToIso : function(_d) {
+        var d = _d ? new Date(_d) : new Date();
         return d.getUTCFullYear()+'-'  
             + pad(2, d.getUTCMonth()+1)+'-'  
             + pad(2, d.getUTCDate())+'T'  
@@ -93,6 +138,15 @@
         console.trace();
         throw "Error : new Model.List(directory): directory is undefined";
     }
+    var _this =  this;
+    this.on("clear-search", function() {
+        _this.searching = false;
+        _this.regexp = undefined;
+        _this.forEach(function(_element) {
+            _element.found = undefined;
+        });
+        _this.trigger("search-cleared");
+    })
 }
 
 Model.List.prototype = new Array();
@@ -191,6 +245,28 @@
     });
 }
 
+Model.List.prototype.search = function(_text) {
+    if (!_text) {
+        this.trigger("clear-search");
+        return this;
+    }
+    this.searching = true;
+    this.trigger("search", _text);
+    var rxsource = Model.fullTextRegexps(_text)
+        rgxp = new RegExp(rxsource,"im"),
+        this.regexp = new RegExp(rxsource,"gim");
+    var res = this.filter(function(_element, _k) {
+        var titlematch = rgxp.test(_element.title),
+            descmatch = rgxp.test(_element.description),
+            _isfound = !!(titlematch || descmatch);
+        _element.found = _isfound;
+        _element.trigger(_isfound ? "found" : "not-found");
+        return _isfound;
+    });
+    this.trigger(res.length ? "found" : "not-found",res);
+    return res;
+}
+
 Model.List.prototype.getTitles = function() {
     return this.map(function(_el) {
         return _el.title;
@@ -297,16 +373,16 @@
 }
 
 Model.Time.prototype.setMilliseconds = function(_milliseconds) {
-    var _ante = _milliseconds;
+    var _ante = this.milliseconds;
     switch(typeof _milliseconds) {
         case "string":
-            this.milliseconds = parseFloat(_milliseconds);
+            this.milliseconds = parseInt(_milliseconds);
             break;
         case "number":
-            this.milliseconds = _milliseconds;
+            this.milliseconds = Math.floor(_milliseconds);
             break;
         case "object":
-            this.milliseconds = parseFloat(_milliseconds.valueOf());
+            this.milliseconds = parseInt(_milliseconds.valueOf());
             break;
         default:
             this.milliseconds = 0;
@@ -329,7 +405,8 @@
     return {
         hours : Math.floor(_totalSeconds / 3600),
         minutes : (Math.floor(_totalSeconds / 60) % 60),
-        seconds : _totalSeconds % 60
+        seconds : _totalSeconds % 60,
+        milliseconds: this.milliseconds % 1000
     } 
 }
 
@@ -341,13 +418,16 @@
     return this.milliseconds;
 }
 
-Model.Time.prototype.toString = function() {
+Model.Time.prototype.toString = function(showCs) {
     var _hms = this.getHMS(),
         _res = '';
     if (_hms.hours) {
         _res += _hms.hours + ':'
     }
     _res += pad(2, _hms.minutes) + ':' + pad(2, _hms.seconds);
+    if (showCs) {
+        _res += "." + Math.round(_hms.milliseconds / 100)
+    }
     return _res;
 }
 
@@ -394,18 +474,20 @@
 
 Model.Element = function(_id, _source) {
     this.elementType = 'element';
+    this.title = "";
+    this.description = "";
+    this.__events = {}
     if (typeof _source === "undefined") {
         return;
     }
     if (typeof _id === "undefined" || !_id) {
         _id = Model.getUID();
     }
+    this.id = _id;
     this.source = _source;
-    this.id = _id;
-    this.title = "";
-    this.description = "";
-    this.__events = {}
-    this.source.directory.addElement(this);
+    if (_source !== this) {
+        this.source.directory.addElement(this);
+    }
 }
 
 Model.Element.prototype.toString = function() {
@@ -474,6 +556,20 @@
     });
     this.on("timeupdate", function(_time) {
         _this.currentTime = _time;
+        _this.getAnnotations().filter(function(_a) {
+            return (_a.end <= _time || _a.begin > _time) && _a.playing
+        }).forEach(function(_a) {
+            _a.playing = false;
+            _a.trigger("leave");
+            _this.trigger("leave-annotation",_a);
+        });
+        _this.getAnnotations().filter(function(_a) {
+            return _a.begin <= _time && _a.end > _time && !_a.playing
+        }).forEach(function(_a) {
+            _a.playing = true;
+            _a.trigger("enter");
+            _this.trigger("enter-annotation",_a);
+        });
     });
 }
 
@@ -515,6 +611,9 @@
     this.trigger("setpause");
 }
 
+Model.Playable.prototype.show = function() {}
+
+Model.Playable.prototype.hide = function() {}
 
 /* */
 
@@ -523,22 +622,7 @@
     this.elementType = 'media';
     this.duration = new Model.Time();
     this.video = '';
-    
     var _this = this;
-    this.on("timeupdate", function(_time) {
-        _this.getAnnotations().filter(function(_a) {
-            return (_a.end <= _time || _a.begin > _time) && _a.playing
-        }).forEach(function(_a) {
-            _a.playing = false;
-            _a.trigger("leave");
-        });
-        _this.getAnnotations().filter(function(_a) {
-            return _a.begin <= _time && _a.end > _time && !_a.playing
-        }).forEach(function(_a) {
-            _a.playing = true;
-            _a.trigger("enter");
-        });
-    });
 }
 
 Model.Media.prototype = new Model.Playable();
@@ -610,11 +694,17 @@
 Model.Annotation.prototype.setBegin = function(_beginMs) {
     this.begin.setMilliseconds(Math.max(0,_beginMs));
     this.trigger("change-begin");
+    if (this.end < this.begin) {
+        this.setEnd(this.begin);
+    }
 }
 
 Model.Annotation.prototype.setEnd = function(_endMs) {
-    this.end.setMilliseconds(Math.min(_endMs));
+    this.end.setMilliseconds(Math.min(_endMs, this.getMedia().duration.milliseconds));
     this.trigger("change-end");
+    if (this.end < this.begin) {
+        this.setBegin(this.end);
+    }
 }
 
 Model.Annotation.prototype.setDuration = function(_durMs) {
@@ -659,8 +749,9 @@
     Model.Element.call(this, _mashup.id + "_" + _annotation.id, _annotation.source);
     this.elementType = 'mashedAnnotation';
     this.annotation = _annotation;
-    this.begin = new Model.Time(_mashup.duration);
-    this.end = new Model.Time(_mashup.duration + _annotation.getDuration());
+    this.begin = new Model.Time();
+    this.end = new Model.Time();
+    this.duration = new Model.Time();
     this.title = this.annotation.title;
     this.description = this.annotation.description;
     this.color = this.annotation.color;
@@ -668,6 +759,12 @@
     this.on("click", function() {
         _mashup.setCurrentTime(_this.begin);
     });
+    this.on("enter", function() {
+        _this.annotation.trigger("enter");
+    });
+    this.on("leave", function() {
+        _this.annotation.trigger("leave");
+    });
 }
 
 Model.MashedAnnotation.prototype = new Model.Element(null);
@@ -692,6 +789,12 @@
     return this.annotation.getDuration();
 }
 
+Model.MashedAnnotation.prototype.setBegin = function(_begin) {
+    this.begin.setMilliseconds(_begin);
+    this.duration.setMilliseconds(this.annotation.getDuration());
+    this.end.setMilliseconds(_begin + this.duration);
+}
+
 /* */
 
 Model.Mashup = function(_id, _source) {
@@ -699,55 +802,145 @@
     this.elementType = 'mashup';
     this.duration = new Model.Time();
     this.segments = new Model.List(_source.directory);
-    this.medias = new Model.List(_source.directory);
-    var _currentMedia = null;
+    this.loaded = false;
     var _this = this;
-    this.on("timeupdate", function(_time) {
-        _this.getAnnotations().filter(function(_a) {
-            return (_a.end <= _time || _a.begin > _time) && _a.playing
-        }).forEach(function(_a) {
-            _a.playing = false;
-            _a.trigger("leave");
-        });
-        _this.getAnnotations().filter(function(_a) {
-            return _a.begin <= _time && _a.end > _time && !_a.playing
-        }).forEach(function(_a) {
-            _a.playing = true;
-            _a.trigger("enter");
-            var _m = _a.getMedia();
-            if (_m !== _currentMedia) {
-                if (_currentMedia) {
-                    _currentMedia.trigger("leave");
-                }
-                _m.trigger("enter");
-                _currentMedia = _m;
-            }
-        });
-    });
+    this._updateTimes = function() {
+        _this.updateTimes();
+        _this.trigger("change");
+    }
+    this.on("add", this._updateTimes);
+    this.on("remove", this._updateTimes);
 }
 
 Model.Mashup.prototype = new Model.Playable();
 
-Model.Mashup.prototype.addSegment = function(_annotation) {
-    var _mashedAnnotation = new Model.MashedAnnotation(this, _annotation);
-    this.duration.setMilliseconds(_mashedAnnotation.end);
+Model.Mashup.prototype.checkLoaded = function() {
+    var loaded = !!this.segments.length;
+    this.getMedias().forEach(function(_m) {
+        loaded = loaded && _m.loaded;
+    });
+    this.loaded = loaded;
+    if (loaded) {
+        this.trigger("loadedmetadata");
+    }
+}
+
+Model.Mashup.prototype.updateTimes = function() {
+    var _time = 0;
+    this.segments.forEach(function(_segment) {
+        _segment.setBegin(_time);
+        _time = _segment.end;
+    });
+    this.duration.setMilliseconds(_time);
+}
+
+Model.Mashup.prototype.addAnnotation = function(_annotation, _defer) {
+    var _mashedAnnotation = new Model.MashedAnnotation(this, _annotation),
+        _defer = _defer || false;
     this.segments.push(_mashedAnnotation);
-    this.medias.push(_annotation.getMedia());
+    _annotation.on("change-begin", this._updateTimes);
+    _annotation.on("change-end", this._updateTimes);
+    if (!_defer) {
+        this.trigger("add");
+    }
+}
+
+Model.Mashup.prototype.addAnnotationById = function(_elId, _defer) {
+    var _annotation = this.source.getElement(_elId),
+        _defer = _defer || false;
+    if (typeof _annotation !== "undefined") {
+        this.addAnnotation(_annotation, _defer);
+    }
+}
+
+Model.Mashup.prototype.addAnnotations = function(_segments) {
+    var _this = this;
+    ns._(_segments).forEach(function(_segment) {
+        _this.addAnnotation(_segment, true);
+    });
+    this.trigger("add");
 }
 
-Model.Mashup.prototype.addSegmentById = function(_elId) {
-    var _annotation = this.source.getElement(_elId);
-    if (typeof _annotation !== "undefined") {
-        this.addSegment(_annotation);
+Model.Mashup.prototype.addAnnotationsById = function(_segments) {
+    var _this = this;
+    ns._(_segments).forEach(function(_segment) {
+        _this.addAnnotationById(_segment, true);
+    });
+    this.trigger("add");
+}
+
+Model.Mashup.prototype.removeAnnotation = function(_annotation, _defer) {
+    var _defer = _defer || false;
+    _annotation.off("change-begin", this._updateTimes);
+    _annotation.off("change-end", this._updateTimes);
+    this.segments.removeId(this.id + "_" + _annotation.id);
+    if (!_defer) {
+        this.trigger("remove");
+    }
+}
+
+Model.Mashup.prototype.removeAnnotationById = function(_annId, _defer) {
+    var _defer = _defer || false;
+    var _annotation = this.source.getElement(_annId);
+
+    if (_annotation) {
+        this.removeAnnotation(_annotation, _defer);
+    }
+    if (!_defer) {
+        this.trigger("remove");
     }
 }
 
+Model.Mashup.prototype.setAnnotations = function(_segments) {
+    while (this.segments.length) {
+        this.removeAnnotation(this.segments[0].annotation, true);
+    }
+    this.addAnnotations(_segments);
+}
+
+Model.Mashup.prototype.setAnnotationsById = function(_segments) {
+    while (this.segments.length) {
+        this.removeAnnotation(this.segments[0].annotation, true);
+    }
+    this.addAnnotationsById(_segments);
+}
+
+Model.Mashup.prototype.hasAnnotation = function(_annotation) {
+    return !!ns._(this.segments).find(function(_s) {
+        return _s.annotation === _annotation
+    });
+}
+
+Model.Mashup.prototype.getAnnotation = function(_annotation) {
+    return ns._(this.segments).find(function(_s) {
+        return _s.annotation === _annotation
+    });
+}
+
+Model.Mashup.prototype.getAnnotationById = function(_id) {
+    return ns._(this.segments).find(function(_s) {
+        return _s.annotation.id === _id
+    });
+}
+
 Model.Mashup.prototype.getAnnotations = function() {
     return this.segments;
 }
 
+Model.Mashup.prototype.getOriginalAnnotations = function() {
+    var annotations = new Model.List(this.source.directory);
+    this.segments.forEach(function(_s) {
+        annotations.push(_s.annotation);
+    });
+    return annotations;
+}
+
 Model.Mashup.prototype.getMedias = function() {
-    return this.medias;
+    var medias = new Model.List(this.source.directory);
+    this.segments.forEach(function(_annotation) {
+        medias.push(_annotation.getMedia())
+    })
+    return medias;
 }
 
 Model.Mashup.prototype.getAnnotationsByTypeTitle = function(_title) {
@@ -784,6 +977,7 @@
 /* */
 
 Model.Source = function(_config) {
+    Model.Element.call(this, false, this);
     this.status = Model._SOURCE_STATUS_EMPTY;
     this.elementType = "source";
     if (typeof _config !== "undefined") {
@@ -939,10 +1133,18 @@
 
 Model.RemoteSource.prototype.get = function() {
     this.status = Model._SOURCE_STATUS_WAITING;
-    var _this = this;
-    this.serializer.loadData(this.url, function(_result) {
-        _this.deSerialize(_result);
-        _this.handleCallbacks();
+    var _this = this,
+        urlparams = this.url_params || {},
+        dataType = (Model.isLocalURL(this.url) ? "json" : "jsonp");
+    urlparams.format = dataType;
+    ns.jQuery.ajax({
+        url: this.url,
+        dataType: dataType,
+        data: urlparams,
+        success: function(_result) {
+            _this.deSerialize(_result);
+            _this.handleCallbacks();
+        }
     });
 }
 
@@ -958,10 +1160,12 @@
         throw "Error : Model.Directory.remoteSource(configuration): configuration.url is undefined";
     }
     var _config = ns._({ directory: this }).extend(_properties);
-    if (typeof this.remoteSources[_properties.url] === "undefined") {
-        this.remoteSources[_properties.url] = new Model.RemoteSource(_config);
+    _config.url_params = _config.url_params || {};
+    var _hash = _config.url + "?" + ns.jQuery.param(_config.url_params);
+    if (typeof this.remoteSources[_hash] === "undefined") {
+        this.remoteSources[_hash] = new Model.RemoteSource(_config);
     }
-    return this.remoteSources[_properties.url];
+    return this.remoteSources[_hash];
 }
 
 Model.Directory.prototype.newLocalSource = function(_properties) {