<commit_before>/* defines the basice prototype of map class */
(function() {
  'use strict';
  window.ten20 = window.ten20 || {};

  window.ten20.createMapProto = function () {

    function Map(mapDiv, param) {
      this.mapDiv = mapDiv;
      // copy parameters to this object
      for(var k in param) {
        this[k]=param[k];
      }
      this.markers = [];

      this.init();
    }

     Map.prototype._tweakMapControlStyles = function() {
        // tweak map control styles
        var layerLabel = $('.leaflet-control-layers-base label');

        // bind click
        layerLabel.on('click', function (e) {

            $(this).parent().children().removeClass('active');
            $(this).addClass('active');

            if ($(this).is(':last-child')) {
                $('.leaflet-control-layers-list').addClass('darker');
            } else {
                $('.leaflet-control-layers-list').removeClass('darker');
            }
        });
        layerLabel.first().click();
    }

    // user map or home page map
    Map.prototype.getMapType = function () {
      // override in children class
      return '';
    };

    Map.prototype.init = function() {
      var self = this;
      var tileLayers = {};

      // config mapbox
      L.mapbox.config.HTTPS_URLS = [ 'https://dnv9my2eseobd.cloudfront.net/v3/' ];
      L.mapbox.config.FORCE_HTTPS = true;
      // create map
      this.map = L.mapbox.map(this.mapDiv, this.tile, { zoomControl: this.zoomControl});
      // add layers to map
      if(this.layers) {
        for (var i = 0; i < this.layers.length; i++) {
          var layer = this.layers[i];
          tileLayers[layer.label] = L.mapbox.tileLayer(layer.tileLayer);
        }

        if (!this.tile) {
            // Add first layer to map
            tileLayers[this.layers[0].label].addTo(this.map);
        }

        L.control.layers(tileLayers).addTo(this.map);
      }
      // set map geometry
      this.map.setView([this.lat, this.lng], this.zoomLevel);
      this.map.scrollWheelZoom.disable();

      // disable dragging on touch devices and user dashboard
      if ('ontouchstart' in document.documentElement ||
          this.getMapType === 'UserMap') {
        this.map.dragging.disable();
      }

      this._tweakMapControlStyles();

      this.afterInit();
    };

    Map.prototype.afterInit = function() {
      // override in children class
    };

    Map.prototype.getMapObject= function() {
      return this.map;
    };

    Map.prototype.addPoint = function(latlng, opts) {
      // circle options
      var pointOpt = {
        stroke: true,
        weight: 5,
        color: '#eee',
        fillColor: '#f60',
        fillOpacity: 1
      };

      for (var key in opts) {
        pointOpt[key] = opts[key];
      };

      return L.circleMarker(latlng, pointOpt).addTo(this.map);
    };

    Map.prototype.addPolyline = function(latlng, opts) {
      var polyOption = {
        stroke: true,
        weight: 2,
        color: '#f60',
        dashArray: '3,4',
        smoothFactor: 1
      };

      for (var key in opts) {
        polyOption[key] = opts[key];
      };

      return L.polyline(latlng, polyOption).addTo(this.map);
    }

    return Map;

  };

  window.ten20.HomeMap = (function () {
    // inherit from Map
    var MapRender = window.ten20.createMapProto();

    var LatlngBound = {
      lat: 0.001,
      lng: 0.005
    };

    var virtualVenceMarkerOuterOpt = {
      stroke: true,
      weight: 1,
      color: '#f60',
      fillColor: '#f60',
      radius:100,
      fillOpacity:0.2
    };

    var virtualVenceMarkerInnerOpt = {
      stroke: false,
      weight: 1,
      fillColor: '#f60',
      fillOpacity: 1,
      radius:2
    };

    MapRender.prototype.getMapType = function () {
      return 'HomeMap';
    };

    MapRender.prototype._getRandomVisibleLatLng = function() {
      var bounds = this.map.getBounds();
      var lat = _randomBetween(bounds.getNorth(), bounds.getSouth());
      var lng = _randomBetween(bounds.getWest(), bounds.getEast());
      return [lat,lng];
    };

    MapRender.prototype.addMarkers = function() {
      for (var i = 0; i < this.numberOfTrackers; i++) {
        var latlng = this._getRandomVisibleLatLng();
        var marker = this.addPoint(latlng)

        if (this.showTail) {
          var tailer = _preTailer(marker);
          marker.tail = this.addPolyline(tailer);
        }

        this.map.addLayer(marker);
        this.markers.push(marker);
      };
    };

    MapRender.prototype.updateNextMarker = function() {
      var self = this;

      if (this.markers.length == 0) {
        return;
      }

      if (this.currentMarkerIndex === undefined ||
          this.currentMarkerIndex === this.markers.length) {
        this.currentMarkerIndex = 0;
      }

      var marker = this.markers[this.currentMarkerIndex];
      _moveMarker(marker, self);
      this.twinkleMarker(marker);
      if (this.showTail) {
        _updateTail(marker);
      }

      if (this.followFirstTracker && this.markers.length > 0) {
        this.map.panTo(this.markers[0].getLatLng());
      }
      this.currentMarkerIndex++;
    }

    MapRender.prototype.setupVirtualFence = function() {
      if (this.addVirtualFence) {
        var markerPosition = this.map.getCenter();

        this.addPoint(markerPosition, virtualVenceMarkerOuterOpt);
        this.addPoint(markerPosition, virtualVenceMarkerInnerOpt);
      }
    };

    // twinkling effects func for a circle
    MapRender.prototype.twinkleMarker = function(marker) {
      var radius = [
        10, 13, 16, 19,
        22, 21, 20, 19,
        18, 17, 16, 15,
        14, 13, 12, 11];
      var index = 0;

      var intervalId = setInterval(function() {
        if (index == (radius.length - 1)) {
          clearInterval(intervalId);
        } else {
          index++;
        }
        marker.setRadius(radius[index]);
      }, 100);
    };

    // tool functions
    function _randomBetween(min, max) {
      return (Math.random()*(max - min))+min;
    }

    function _getDelta(delta) {
      var random;
      if (delta === undefined) {
        random = _randomBetween(-0.001, 0.001);// (Math.random() - 0.5) * 2 / 1000;
      } else {
        random = delta + _randomBetween(-0.0005, 0.0005);
      }
      return random;
    }

    function _moveMarker(marker, self) {
      var latLng = marker.getLatLng();
      var marginLat, marginLng, deltaLat = 0, deltaLng = 0;

      marker.latDelta = _getDelta(marker.latDelta);
      marker.lngDelta = _getDelta(marker.lngDelta);

      // restrict marker activity in a boundary
      if (self.restrict) {
        marginLat = Math.abs(marker.latDelta + latLng.lat - self.lat);
        marginLng = Math.abs(marker.lngDelta + latLng.lng - self.lng);

        if (marginLat > LatlngBound.lat) {
          if (marker.latDelta + latLng.lat < self.lat - LatlngBound.lat) {
            deltaLat =  marginLat - LatlngBound.lat;
          } else {
            deltaLat = -(marginLat - LatlngBound.lat);
          }
        }

        if (marginLng > LatlngBound.lng) {
          if (marker.lngDelta + latLng.lng < self.lng - LatlngBound.lng) {
            deltaLng =  marginLng - LatlngBound.lng;
          } else {
            deltaLng =  -(marginLng - LatlngBound.lng);
          }
        }
      }

      marker.setLatLng([latLng.lat + marker.latDelta + deltaLat/10,
          latLng.lng + marker.lngDelta + deltaLng/10]);

    }

    function _preTailer(marker) {
      var latlngTail = [];
      var tmpPos = [marker.getLatLng().lat, marker.getLatLng().lng];

      for (var i = 0; i < 20; i++) {
        latlngTail.unshift([tmpPos[0], tmpPos[1]]);
        marker.latDelta = _getDelta(marker.latDelta);
        marker.lngDelta = _getDelta(marker.lngDelta);
        tmpPos[0] += marker.latDelta;
        tmpPos[1] += marker.lngDelta;
      };

      return latlngTail;
    }

    function _updateTail(marker) {
      var latlngs = marker.tail.getLatLngs();

      latlngs.push(marker.getLatLng());

      if (latlngs.length > 100) {
        latlngs.shift();
      }

      marker.tail.setLatLngs(latlngs);
    }

    MapRender.prototype.afterInit = function (argument) {
      console.log('afterInit');

      var self = this;

      this.setupVirtualFence();
      this.addMarkers();

      setInterval(function() {
        self.updateNextMarker();
      }, 2000);
    }

    return MapRender;
  })();

  window.ten20.UserMap = (function () {
    // inherit from Map
    var MapRender = window.ten20.createMapProto();

    MapRender.prototype.getMapType = function () {
      return 'UserMap';
    };

    MapRender.prototype._addTracker = function(tracker) {
      var latlng = [
        tracker.lastMessage.location.latitude,
        tracker.lastMessage.location.longitude,
      ];

      var opt = {}, marker;

      if (tracker.settings) {
        opt.color = '#' + tracker.settings.iconColor;
        opt.fillColor = '#' + tracker.settings.iconColor;
      }

      marker = this.addPoint(latlng, opt);
      marker.trackerId = tracker._id;

      this.map.addLayer(marker);
      this.markers.push(marker);
      this.map.panTo(marker.getLatLng());

    };

    MapRender.prototype._findMarker = function(tracker) {
      var marker = null;

      for (var i = 0; i < this.markers.length; i++) {
        if (this.markers[i].trackerId === tracker._id) {
          marker = this.markers[i];
          break;
        }
      };

      return marker;
    };

    MapRender.prototype.updateTracker = function(tracker, pan) {
      var marker, location;

      // return if location not available for this tracker
      if (!tracker.lastMessage || !tracker.lastMessage.location) {return;}

      location = tracker.lastMessage.location;
      // find the tracker marker in existing markers
      marker = this._findMarker(tracker);
      // update existing marker location
      if (marker) {
        marker.setLatLng([location.latitude, location.longitude]);
        if (pan) {
          this.map.panTo(marker.getLatLng());
        }
      } else { // new tracker, need to add to map
        this._addTracker(tracker);
      }
    };

    MapRender.prototype._addPath = function(t, fitBounds) {
      var marker = this._findMarker(t);
      var latlngs = [];
      var optsLine = { weight: 2 };
      var optsPoint = { weight: 2, radius: 3 };

      if (marker) {
        marker.path = {line:null, points:[]};
        latlngs = _getLineCoordsFromMsg(t.path);
        if (t.settings) {
          optsLine.color = '#' + t.settings.iconColor;
          optsPoint.color = '#' + t.settings.iconColor;
          optsPoint.fillColor= '#' + t.settings.iconColor;
        }
        marker.path.line = this.addPolyline(latlngs, optsLine);
        if (fitBounds) {
          this.map.fitBounds(marker.path.line.getBounds());
        }
        this._addPathPoints(marker.path, t.path, optsPoint);
      }
    };

    // update recent message location to map
    MapRender.prototype.updatePath = function(t, fitBounds) {
      var marker = this._findMarker(t);
      var latlngs = _getLineCoordsFromMsg(t.path);
      var optsPoint = { weight: 2, radius: 4 };

      if (latlngs.length === 0) {
        return;
      }
      // pick a point to show marker
      if (!marker) {
        t.lastMessage.location = t.path[0];
        this.updateTracker(t, true);
        this.updatePath(t, true);
        return;
      }

      if (marker.path) {
        if (t.settings) {
          optsPoint.color = '#' + t.settings.iconColor;
          optsPoint.fillColor= '#' + t.settings.iconColor;
        }

        this._clearPathPoints(marker.path);
        marker.path.line.setLatLngs(latlngs);
        if (fitBounds) {
          this.map.fitBounds(marker.path.line.getBounds());
        }
        this._addPathPoints(marker.path, t.path, optsPoint);
      } else {
        this._addPath(t, fitBounds);
      }
    };

    MapRender.prototype._addPathPoints = function(path, msgs, opts) {
      var popup, self;
      var opt = {closeButton: false};

      self = this;

      // add msg points
      for (var i = 0; i < msgs.length; i++) {
        if (_comparePixelDist(msgs[i])) {
          _addPoint(msgs[i]);
        }
      }

      function _addPoint(msg) {
        var point = self.addPoint([
              msg.latitude, msg.longitude
            ], opts);

        popup = _generatePopup(msg, opts);
        point.bindPopup(popup, opt);
        path.points.push(point);
        _bindEvents(point);
      }

      function _comparePixelDist(msg) {
        var prePoint, current, distPixel;

        if (path.points.length === 0) {
          return true;
        }

        prePoint = self.map.latLngToLayerPoint(
          path.points[path.points.length - 1].getLatLng()
        );

        current = self.map.latLngToLayerPoint(
          L.latLng(msg.latitude, msg.longitude)
        );

        distPixel = current.distanceTo(prePoint);

        if (distPixel > 40) {
          return true;
        } else {
          return false;
        }

      }

    };

    MapRender.prototype._clearPathPoints = function(path) {
      for (var i = 0; i < path.points.length; i++) {
        path.points[i].unbindPopup();
        this.map.removeLayer(path.points[i]);
      };

      path.points = [];
    };

    function _getLineCoordsFromMsg(msgs) {
      var lines = [];

      for (var i = 0; i < msgs.length; i++) {
        lines.push([
            msgs[i].latitude,
            msgs[i].longitude
            ]);
      };

      return lines;
    }

    function _generatePopup(msg, opts) {
      var time = msg.timestamp.split('T')[1].slice(0, 5);

      return '
' + '' + time + '' +
             '' +
              + msg.latitude.toFixed(3) +
             '' +
              msg.longitude.toFixed(3) +'
';
    }

    function _bindEvents(marker) {
      marker.on('mouseover', function() {
        marker.openPopup();
      });
      marker.on('mouseout', function() {
        marker.closePopup();
      });
    }

    return MapRender;
  })();

})();

<commit_msg>Twinkle marker on console<commit_after>/* defines the basice prototype of map class */
(function() {
  'use strict';
  window.ten20 = window.ten20 || {};

  window.ten20.createMapProto = function () {

    function Map(mapDiv, param) {
      this.mapDiv = mapDiv;
      // copy parameters to this object
      for(var k in param) {
        this[k]=param[k];
      }
      this.markers = [];

      this.init();
    }

     Map.prototype._tweakMapControlStyles = function() {
        // tweak map control styles
        var layerLabel = $('.leaflet-control-layers-base label');

        // bind click
        layerLabel.on('click', function (e) {

            $(this).parent().children().removeClass('active');
            $(this).addClass('active');

            if ($(this).is(':last-child')) {
                $('.leaflet-control-layers-list').addClass('darker');
            } else {
                $('.leaflet-control-layers-list').removeClass('darker');
            }
        });
        layerLabel.first().click();
    }

    // user map or home page map
    Map.prototype.getMapType = function () {
      // override in children class
      return '';
    };

    Map.prototype.init = function() {
      var self = this;
      var tileLayers = {};

      // config mapbox
      L.mapbox.config.HTTPS_URLS = [ 'https://dnv9my2eseobd.cloudfront.net/v3/' ];
      L.mapbox.config.FORCE_HTTPS = true;
      // create map
      this.map = L.mapbox.map(this.mapDiv, this.tile, { zoomControl: this.zoomControl});
      // add layers to map
      if(this.layers) {
        for (var i = 0; i < this.layers.length; i++) {
          var layer = this.layers[i];
          tileLayers[layer.label] = L.mapbox.tileLayer(layer.tileLayer);
        }

        if (!this.tile) {
            // Add first layer to map
            tileLayers[this.layers[0].label].addTo(this.map);
        }

        L.control.layers(tileLayers).addTo(this.map);
      }
      // set map geometry
      this.map.setView([this.lat, this.lng], this.zoomLevel);
      this.map.scrollWheelZoom.disable();

      // disable dragging on touch devices and user dashboard
      if ('ontouchstart' in document.documentElement ||
          this.getMapType === 'UserMap') {
        this.map.dragging.disable();
      }

      this._tweakMapControlStyles();

      this.afterInit();
    };

    Map.prototype.afterInit = function() {
      // override in children class
    };

    Map.prototype.getMapObject= function() {
      return this.map;
    };

    Map.prototype.addPoint = function(latlng, opts) {
      // circle options
      var pointOpt = {
        stroke: true,
        weight: 5,
        color: '#eee',
        fillColor: '#f60',
        fillOpacity: 1
      };

      for (var key in opts) {
        pointOpt[key] = opts[key];
      };

      return L.circleMarker(latlng, pointOpt).addTo(this.map);
    };

    Map.prototype.addPolyline = function(latlng, opts) {
      var polyOption = {
        stroke: true,
        weight: 2,
        color: '#f60',
        dashArray: '3,4',
        smoothFactor: 1
      };

      for (var key in opts) {
        polyOption[key] = opts[key];
      };

      return L.polyline(latlng, polyOption).addTo(this.map);
    }

      // twinkling effects func for a circle
    Map.prototype.twinkleMarker = function(marker) {
          var radius = [
              10, 13, 16, 19,
              22, 21, 20, 19,
              18, 17, 16, 15,
              14, 13, 12, 11];
          var index = 0;

          var intervalId = setInterval(function() {
              if (index == (radius.length - 1)) {
                  clearInterval(intervalId);
              } else {
                  index++;
              }
              marker.setRadius(radius[index]);
          }, 100);
     };

    return Map;

  };

  window.ten20.HomeMap = (function () {
    // inherit from Map
    var MapRender = window.ten20.createMapProto();

    var LatlngBound = {
      lat: 0.001,
      lng: 0.005
    };

    var virtualVenceMarkerOuterOpt = {
      stroke: true,
      weight: 1,
      color: '#f60',
      fillColor: '#f60',
      radius:100,
      fillOpacity:0.2
    };

    var virtualVenceMarkerInnerOpt = {
      stroke: false,
      weight: 1,
      fillColor: '#f60',
      fillOpacity: 1,
      radius:2
    };

    MapRender.prototype.getMapType = function () {
      return 'HomeMap';
    };

    MapRender.prototype._getRandomVisibleLatLng = function() {
      var bounds = this.map.getBounds();
      var lat = _randomBetween(bounds.getNorth(), bounds.getSouth());
      var lng = _randomBetween(bounds.getWest(), bounds.getEast());
      return [lat,lng];
    };

    MapRender.prototype.addMarkers = function() {
      for (var i = 0; i < this.numberOfTrackers; i++) {
        var latlng = this._getRandomVisibleLatLng();
        var marker = this.addPoint(latlng)

        if (this.showTail) {
          var tailer = _preTailer(marker);
          marker.tail = this.addPolyline(tailer);
        }

        this.map.addLayer(marker);
        this.markers.push(marker);
      };
    };

    MapRender.prototype.updateNextMarker = function() {
      var self = this;

      if (this.markers.length == 0) {
        return;
      }

      if (this.currentMarkerIndex === undefined ||
          this.currentMarkerIndex === this.markers.length) {
        this.currentMarkerIndex = 0;
      }

      var marker = this.markers[this.currentMarkerIndex];
      _moveMarker(marker, self);
      this.twinkleMarker(marker);
      if (this.showTail) {
        _updateTail(marker);
      }

      if (this.followFirstTracker && this.markers.length > 0) {
        this.map.panTo(this.markers[0].getLatLng());
      }
      this.currentMarkerIndex++;
    }

    MapRender.prototype.setupVirtualFence = function() {
      if (this.addVirtualFence) {
        var markerPosition = this.map.getCenter();

        this.addPoint(markerPosition, virtualVenceMarkerOuterOpt);
        this.addPoint(markerPosition, virtualVenceMarkerInnerOpt);
      }
    };

    // tool functions
    function _randomBetween(min, max) {
      return (Math.random()*(max - min))+min;
    }

    function _getDelta(delta) {
      var random;
      if (delta === undefined) {
        random = _randomBetween(-0.001, 0.001);// (Math.random() - 0.5) * 2 / 1000;
      } else {
        random = delta + _randomBetween(-0.0005, 0.0005);
      }
      return random;
    }

    function _moveMarker(marker, self) {
      var latLng = marker.getLatLng();
      var marginLat, marginLng, deltaLat = 0, deltaLng = 0;

      marker.latDelta = _getDelta(marker.latDelta);
      marker.lngDelta = _getDelta(marker.lngDelta);

      // restrict marker activity in a boundary
      if (self.restrict) {
        marginLat = Math.abs(marker.latDelta + latLng.lat - self.lat);
        marginLng = Math.abs(marker.lngDelta + latLng.lng - self.lng);

        if (marginLat > LatlngBound.lat) {
          if (marker.latDelta + latLng.lat < self.lat - LatlngBound.lat) {
            deltaLat =  marginLat - LatlngBound.lat;
          } else {
            deltaLat = -(marginLat - LatlngBound.lat);
          }
        }

        if (marginLng > LatlngBound.lng) {
          if (marker.lngDelta + latLng.lng < self.lng - LatlngBound.lng) {
            deltaLng =  marginLng - LatlngBound.lng;
          } else {
            deltaLng =  -(marginLng - LatlngBound.lng);
          }
        }
      }

      marker.setLatLng([latLng.lat + marker.latDelta + deltaLat/10,
          latLng.lng + marker.lngDelta + deltaLng/10]);

    }

    function _preTailer(marker) {
      var latlngTail = [];
      var tmpPos = [marker.getLatLng().lat, marker.getLatLng().lng];

      for (var i = 0; i < 20; i++) {
        latlngTail.unshift([tmpPos[0], tmpPos[1]]);
        marker.latDelta = _getDelta(marker.latDelta);
        marker.lngDelta = _getDelta(marker.lngDelta);
        tmpPos[0] += marker.latDelta;
        tmpPos[1] += marker.lngDelta;
      };

      return latlngTail;
    }

    function _updateTail(marker) {
      var latlngs = marker.tail.getLatLngs();

      latlngs.push(marker.getLatLng());

      if (latlngs.length > 100) {
        latlngs.shift();
      }

      marker.tail.setLatLngs(latlngs);
    }

    MapRender.prototype.afterInit = function (argument) {
      console.log('afterInit');

      var self = this;

      this.setupVirtualFence();
      this.addMarkers();

      setInterval(function() {
        self.updateNextMarker();
      }, 2000);
    }

    return MapRender;
  })();

  window.ten20.UserMap = (function () {
    // inherit from Map
    var MapRender = window.ten20.createMapProto();

    MapRender.prototype.getMapType = function () {
      return 'UserMap';
    };

    MapRender.prototype._addTracker = function(tracker) {
      var latlng = [
        tracker.lastMessage.location.latitude,
        tracker.lastMessage.location.longitude,
      ];

      var opt = {}, marker;

      if (tracker.settings) {
        opt.color = '#' + tracker.settings.iconColor;
        opt.fillColor = '#' + tracker.settings.iconColor;
      }

      marker = this.addPoint(latlng, opt);
      marker.trackerId = tracker._id;

      this.map.addLayer(marker);
      this.markers.push(marker);
      this.map.panTo(marker.getLatLng());

    };

    MapRender.prototype._findMarker = function(tracker) {
      var marker = null;

      for (var i = 0; i < this.markers.length; i++) {
        if (this.markers[i].trackerId === tracker._id) {
          marker = this.markers[i];
          break;
        }
      };

      return marker;
    };

    MapRender.prototype.updateTracker = function(tracker, pan) {
      var marker, location;

      // return if location not available for this tracker
      if (!tracker.lastMessage || !tracker.lastMessage.location) {return;}

      location = tracker.lastMessage.location;
      // find the tracker marker in existing markers
      marker = this._findMarker(tracker);
      // update existing marker location
      if (marker) {
        marker.setLatLng([location.latitude, location.longitude]);
        this.twinkleMarker(marker);
        if (pan) {
          this.map.panTo(marker.getLatLng());
        }
      } else { // new tracker, need to add to map
        this._addTracker(tracker);
      }
    };

    MapRender.prototype._addPath = function(t, fitBounds) {
      var marker = this._findMarker(t);
      var latlngs = [];
      var optsLine = { weight: 2 };
      var optsPoint = { weight: 2, radius: 3 };

      if (marker) {
        marker.path = {line:null, points:[]};
        latlngs = _getLineCoordsFromMsg(t.path);
        if (t.settings) {
          optsLine.color = '#' + t.settings.iconColor;
          optsPoint.color = '#' + t.settings.iconColor;
          optsPoint.fillColor= '#' + t.settings.iconColor;
        }
        marker.path.line = this.addPolyline(latlngs, optsLine);
        if (fitBounds) {
          this.map.fitBounds(marker.path.line.getBounds());
        }
        this._addPathPoints(marker.path, t.path, optsPoint);
      }
    };

    // update recent message location to map
    MapRender.prototype.updatePath = function(t, fitBounds) {
      var marker = this._findMarker(t);
      var latlngs = _getLineCoordsFromMsg(t.path);
      var optsPoint = { weight: 2, radius: 4 };

      if (latlngs.length === 0) {
        return;
      }
      // pick a point to show marker
      if (!marker) {
        t.lastMessage.location = t.path[0];
        this.updateTracker(t, true);
        this.updatePath(t, true);
        return;
      }

      if (marker.path) {
        if (t.settings) {
          optsPoint.color = '#' + t.settings.iconColor;
          optsPoint.fillColor= '#' + t.settings.iconColor;
        }

        this._clearPathPoints(marker.path);
        marker.path.line.setLatLngs(latlngs);
        if (fitBounds) {
          this.map.fitBounds(marker.path.line.getBounds());
        }
        this._addPathPoints(marker.path, t.path, optsPoint);
      } else {
        this._addPath(t, fitBounds);
      }
    };

    MapRender.prototype._addPathPoints = function(path, msgs, opts) {
      var popup, self;
      var opt = {closeButton: false};

      self = this;

      // add msg points
      for (var i = 0; i < msgs.length; i++) {
        if (_comparePixelDist(msgs[i])) {
          _addPoint(msgs[i]);
        }
      }

      function _addPoint(msg) {
        var point = self.addPoint([
              msg.latitude, msg.longitude
            ], opts);

        popup = _generatePopup(msg, opts);
        point.bindPopup(popup, opt);
        path.points.push(point);
        _bindEvents(point);
      }

      function _comparePixelDist(msg) {
        var prePoint, current, distPixel;

        if (path.points.length === 0) {
          return true;
        }

        prePoint = self.map.latLngToLayerPoint(
          path.points[path.points.length - 1].getLatLng()
        );

        current = self.map.latLngToLayerPoint(
          L.latLng(msg.latitude, msg.longitude)
        );

        distPixel = current.distanceTo(prePoint);

        if (distPixel > 40) {
          return true;
        } else {
          return false;
        }

      }

    };

    MapRender.prototype._clearPathPoints = function(path) {
      for (var i = 0; i < path.points.length; i++) {
        path.points[i].unbindPopup();
        this.map.removeLayer(path.points[i]);
      };

      path.points = [];
    };

    function _getLineCoordsFromMsg(msgs) {
      var lines = [];

      for (var i = 0; i < msgs.length; i++) {
        lines.push([
            msgs[i].latitude,
            msgs[i].longitude
            ]);
      };

      return lines;
    }

    function _generatePopup(msg, opts) {
      var time = msg.timestamp.split('T')[1].slice(0, 5);

      return '
' + '' + time + '' +
             '' +
              + msg.latitude.toFixed(3) +
             '' +
              msg.longitude.toFixed(3) +'
';
    }

    function _bindEvents(marker) {
      marker.on('mouseover', function() {
        marker.openPopup();
      });
      marker.on('mouseout', function() {
        marker.closePopup();
      });
    }

    return MapRender;
  })();

})();

<|endoftext|>


<commit_before>var userid;
//arrays gebruikt voor autocompletes en selecteren waarde hiervan.
var studentLists = [];
var studentListsid = [];
var teachers = []
var teachersid = []

function pageViewModel(gvm) {
    gvm.projecttitle = ko.observable("");
    gvm.userId = -1;
    gvm.coupledCount = 0;

    // Page specific i18n bindings
    gvm.title = ko.computed(function (){i18n.setLocale(gvm.lang()); return gvm.app() + ' - ' + i18n.__("ProjectTitle") + ": " + gvm.projecttitle();}, gvm);
    gvm.pageHeader = ko.observable("Project");
    gvm.projectname = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("ProjectName");}, gvm);

    gvm.addBtn = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("AddBtn")}, gvm);

    gvm.addmoduleBtn = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("Addmodule");}, gvm);
    gvm.savePage = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("SaveBtn");}, gvm);

    gvm.getProjectInfo = function() {
        $.getJSON('/api/project/' + $("#projectHeader").data('value'), function(data) {
            gvm.pageHeader(data[0].code + ' - ' + data[0].name);
        });
    };

    gvm.coupledLists = ko.observableArray([]);
    gvm.availableLists = ko.observableArray([]);

    gvm.addCoupledList = function(id, name) {
        // Push data
        var tblOject = {tid: id, tname: name};
        gvm.coupledLists.push(tblOject);

        $("#uncouplebtn-" + id).click(function(){
            showYesNoModal("Bent u zeker dat u dit item wil ontkoppelen?", function(val){
                if(val){
                    $.ajax({
                        url: '/api/project/' + $("#projectHeader").data('value') +'/studentlist/uncouple/' + id,
                        type: "DELETE",
                        success: function() {
                            gvm.coupledCount--;
                            viewModel.coupledLists.remove(tblOject);
                        }
                    });

                }
            });
        });
    }

    gvm.addAvailableLists = function(id, name) {
        var tblOject = {tid: id, tname: name};
        gvm.availableLists.push(tblOject);

        $("#couplebtn-" + id).click(function(){
            if(gvm.coupledCount == 0) {
                $.ajax({
                    url: '/api/project/' + $("#projectHeader").data('value') + '/studentlist/' + id,
                    type: "POST",
                    success: function() {
                        gvm.coupledCount++;
                        viewModel.coupledLists.push(tblOject);
                    }
                });
            } else {
                alert("list already coupled");
            }
        });
    }

    gvm.getCoupledLists = function() {
        $.getJSON('/api/project/' + $("#projectHeader").data('value') + '/coupledlists', function(data) {
            $.each(data, function(i, item) {
                gvm.coupledCount++;
                gvm.addCoupledList(item.id, item.name);
            });
        });
    }

    gvm.getAvailableLists = function() {
        $.getJSON('/api/studentlists/' + gvm.userId, function(data) {
            $.each(data, function(i, item) {
                gvm.addAvailableLists(item.id, item.name);
            });
        });
    }
 }

 function getAllTeachers() {
     teachers = [];
     teachersid = [];
     $.getJSON('/api/getteacherrapport', function(data) {
        $.each(data, function(i, item) {
            teachers.push(item.firstname + " " + item.lastname);
            teachersid.push(item.id);
        });
    });
    return teachers;
 }

function getAllStudentLists() {
    studentLists = [];
    studentListsid = [];
    $.getJSON('/api/studentlistsrapporten/' + userid, function (data) {
        $.each(data, function (i, item) {
            studentLists.push(item.name);
            studentListsid.push(item.id);
        });
    });
    return studentLists;
}

function getTeacherid() {

    // functie voor het opsplitsen van naam indien gewenst.
    // console.log(getTeacherID($('#teachersComplete').val().substr(0,$('#teachersComplete').val().indexOf(' ')), $('#teachersComplete').val().substr($('#teachersComplete').val().indexOf(' ')+1)));

    var i = 0;
    var teacher = 0;
    teachers.forEach(function(entry) {
        if (new String(entry).valueOf() == new String($('#teachersComplete').val()).valueOf()) {
            teacher = teachersid[i];
        }
        i+= 1;
    });
    return teacher
}

function getGroupid() {
    var i = 0;
    var studlijst;
    studentLists.forEach(function(entry) {
        if (new String(entry).valueOf() == new String($('#studentListComplete').val()).valueOf()) {
            studlijst = (studentListsid[i]);
        }
        i+= 1;
    });
    return studlijst;
}

 function addGroup(courseid, teacherid, studlijstid) {
     //TODO if teacher or studlijst = 0 dan bestaat deze niet!
     //TODO momenteel nog mogelijk om meer als 1 maal zelfde velden in te voeren.
     console.log("En als leerkracht " + teacherid);
     console.log("Groep toevoegen voor vak " + courseid);
     console.log("En met studentenlijst " + studlijstid);

         $.ajax({
            url: "/api/coursecouple/" + courseid + "/" + studlijstid + "/" + teacherid,
             type: "POST",
             data: {'course': courseid, 'teacher': teacherid, 'studentlist': teacherid},
             success: function(data) {
                 //console.log(data);
                 //callback(true);
             },
             error: function(data) {
                 console.log('Failed to add new student');
                 //callback(false);
             }
         });

 }

function loadTablePage(pagenr) {
    $.getJSON('/api/coursesrapport/page/' + pagenr, function (data) {

        /* Clear current table page */
       // viewModel.clearTable();

        // Load table data
        $.each(data.data, function (i, item) {
            viewModel.addTableData(item.id, item.code);
        });
    });
}

function initPage() {
    viewModel.getProjectInfo();
    viewModel.getCoupledLists();

    //Add Course members

    $('#addGroupForm').hide();

    $('#addCoursemembers').click(function() {
        $("#addGroupForm").show();
        $('#teachersComplete').autocomplete({ source: getAllTeachers() });
        $('#studentListComplete').autocomplete({ source: getAllStudentLists() });
    });

    $('#addGroupBtn').click(function() {
         addGroup($('#projectHeader').attr("data-value"), getTeacherid(), getGroupid());

        $('addGroupForm').hide();
    });

    $.getJSON('/api/currentuser', function(data) {
        viewModel.userId = data.id;
        userid = data.id;
        viewModel.getAvailableLists(data.id);
    });

    loadTablePage(1);

}
<commit_msg>New method getStudentGroupTeacherByCourseID<commit_after>var userid;
//arrays gebruikt voor autocompletes en selecteren waarde hiervan.
var studentLists = [];
var studentListsid = [];
var teachers = []
var teachersid = []

function pageViewModel(gvm) {
    gvm.projecttitle = ko.observable("");
    gvm.userId = -1;
    gvm.coupledCount = 0;

    // Page specific i18n bindings
    gvm.title = ko.computed(function (){i18n.setLocale(gvm.lang()); return gvm.app() + ' - ' + i18n.__("ProjectTitle") + ": " + gvm.projecttitle();}, gvm);
    gvm.pageHeader = ko.observable("Project");
    gvm.projectname = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("ProjectName");}, gvm);

    gvm.addBtn = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("AddBtn")}, gvm);

    gvm.addmoduleBtn = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("Addmodule");}, gvm);
    gvm.savePage = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("SaveBtn");}, gvm);

    gvm.getProjectInfo = function() {
        $.getJSON('/api/project/' + $("#projectHeader").data('value'), function(data) {
            gvm.pageHeader(data[0].code + ' - ' + data[0].name);
        });
    };

    gvm.coupledLists = ko.observableArray([]);
    gvm.availableLists = ko.observableArray([]);

    gvm.addCoupledList = function(id, name) {
        // Push data
        var tblOject = {tid: id, tname: name};
        gvm.coupledLists.push(tblOject);

        $("#uncouplebtn-" + id).click(function(){
            showYesNoModal("Bent u zeker dat u dit item wil ontkoppelen?", function(val){
                if(val){
                    $.ajax({
                        url: '/api/project/' + $("#projectHeader").data('value') +'/studentlist/uncouple/' + id,
                        type: "DELETE",
                        success: function() {
                            gvm.coupledCount--;
                            viewModel.coupledLists.remove(tblOject);
                        }
                    });

                }
            });
        });
    }

    gvm.addAvailableLists = function(id, name) {
        var tblOject = {tid: id, tname: name};
        gvm.availableLists.push(tblOject);

        $("#couplebtn-" + id).click(function(){
            if(gvm.coupledCount == 0) {
                $.ajax({
                    url: '/api/project/' + $("#projectHeader").data('value') + '/studentlist/' + id,
                    type: "POST",
                    success: function() {
                        gvm.coupledCount++;
                        viewModel.coupledLists.push(tblOject);
                    }
                });
            } else {
                alert("list already coupled");
            }
        });
    }

    gvm.getCoupledLists = function() {
        $.getJSON('/api/project/' + $("#projectHeader").data('value') + '/coupledlists', function(data) {
            $.each(data, function(i, item) {
                gvm.coupledCount++;
                gvm.addCoupledList(item.id, item.name);
            });
        });
    }

    gvm.getAvailableLists = function() {
        $.getJSON('/api/studentlists/' + gvm.userId, function(data) {
            $.each(data, function(i, item) {
                gvm.addAvailableLists(item.id, item.name);
            });
        });
    }

    gvm.clearTable = function() {
        gvm.tabledata.removeAll();
    }

 }

 function getAllTeachers() {
     teachers = [];
     teachersid = [];
     $.getJSON('/api/getteacherrapport', function(data) {
        $.each(data, function(i, item) {
            teachers.push(item.firstname + " " + item.lastname);
            teachersid.push(item.id);
        });
    });
    return teachers;
 }

function getAllStudentLists() {
    studentLists = [];
    studentListsid = [];
    $.getJSON('/api/studentlistsrapporten/' + userid, function (data) {
        $.each(data, function (i, item) {
            studentLists.push(item.name);
            studentListsid.push(item.id);
        });
    });
    return studentLists;
}

function getTeacherid() {

    // functie voor het opsplitsen van naam indien gewenst.
    // console.log(getTeacherID($('#teachersComplete').val().substr(0,$('#teachersComplete').val().indexOf(' ')), $('#teachersComplete').val().substr($('#teachersComplete').val().indexOf(' ')+1)));

    var i = 0;
    var teacher = 0;
    teachers.forEach(function(entry) {
        if (new String(entry).valueOf() == new String($('#teachersComplete').val()).valueOf()) {
            teacher = teachersid[i];
        }
        i+= 1;
    });
    return teacher
}

function getGroupid() {
    var i = 0;
    var studlijst;
    studentLists.forEach(function(entry) {
        if (new String(entry).valueOf() == new String($('#studentListComplete').val()).valueOf()) {
            studlijst = (studentListsid[i]);
        }
        i+= 1;
    });
    return studlijst;
}

 function addGroup(courseid, teacherid, studlijstid) {
     //TODO if teacher or studlijst = 0 dan bestaat deze niet!
     //TODO momenteel nog mogelijk om meer als 1 maal zelfde velden in te voeren.
     console.log("En als leerkracht " + teacherid);
     console.log("Groep toevoegen voor vak " + courseid);
     console.log("En met studentenlijst " + studlijstid);

         $.ajax({
            url: "/api/coursecouple/" + courseid + "/" + studlijstid + "/" + teacherid,
             type: "POST",
             data: {'course': courseid, 'teacher': teacherid, 'studentlist': teacherid},
             success: function(data) {
                 //console.log(data);
                 //callback(true);
             },
             error: function(data) {
                 console.log('Failed to add new student');
                 //callback(false);
             }
         });

 }

function loadTablePage(pagenr) {
    $.getJSON('/api/coursesrapport/page/' + pagenr, function (data) {

        /* Clear current table page */
            viewModel.clearTable();

        // Load table data
        $.each(data.data, function (i, item) {
           // viewModel.addTableData(item.id, item.code);
        });
    });
}

function initPage() {
    viewModel.getProjectInfo();
    viewModel.getCoupledLists();

    //Add Course members

    $('#addGroupForm').hide();

    $('#addCoursemembers').click(function() {
        $("#addGroupForm").show();
        $('#teachersComplete').autocomplete({ source: getAllTeachers() });
        $('#studentListComplete').autocomplete({ source: getAllStudentLists() });
    });

    $('#addGroupBtn').click(function() {
         addGroup($('#projectHeader').attr("data-value"), getTeacherid(), getGroupid());

        $('addGroupForm').hide();
    });

    $.getJSON('/api/currentuser', function(data) {
        viewModel.userId = data.id;
        userid = data.id;
        viewModel.getAvailableLists(data.id);
    });

    loadTablePage(1);

}
<|endoftext|>


<commit_before>var MjsController = function($q, mjs, notification, item, itemTypeMap, header, langManager, auth, user) {
	var self = this;

	this.$q = $q;
	this.notification = notification;
	this.mjs = mjs;
	this.itemTypeMap = itemTypeMap;
	this.item = item;
	this.langManager = langManager;
	this.header = header;
	this.user = user;
	this.selected_branch = 0;
	this.mjs_data = [];
	this.items_counter = [0,0,0,0];
	this.branch_edit_status = {
		1: false,
		2: false,
		3: false,
		4: false,
	};
	this.rmdialog_status = false;

	Object.defineProperty(this, 'signedin', {
		get: function() {
			return auth.is_signedin();
		}
	});

	this.init();
};

MjsController.prototype = {
	init: function() {

		this.notification.put({
			en: 'Loding Story...',
			he: 'טוען את הסיפור...'
		});
		this.refresh();
	},

	refresh: function() {
		var self = this;
		var items_ids = [];

		this.mjs.refresh().then(function () {
			var items_ids = [];
			//console.log(self.mjs.data);
			self.mjs.data.items.forEach(function (i) {
				items_ids.push(i.id);
			})
			self.item.get_items(items_ids).then (function (ret) {
				self.mjs_data = [];
				self.items_counter = [0, 0, 0, 0];
				self.mjs.data.items.forEach(function(i, _i) {
					ret[_i].branches = i.branches;
					ret[_i].item_string = i.id;
					self.mjs_data.push(ret[_i]);
					i.branches.forEach(function (flag, _j) {
						if (flag)
							self.items_counter[_j]++;
					});
				})
			});
		});
	},

	stopPropagation: function($event) {
		$event.stopPropagation();
	},

	update_branch_name: function(branch_num, new_name) {
		this.mjs.update_branch_name(branch_num, new_name);
	},

	toggle_branch_edit: function($event,index)  {
		this.branch_edit_status[index] = !(this.branch_edit_status[index]);
	},

	toggle_branch_rmdialog: function()  {
		this.rmdialog_status = !(this.rmdialog_status);
		console.log(this.rmdialog_status);

	}
};

angular.module('main').controller('MjsController', ['$q', 'mjs', 'notification', 'item', 'itemTypeMap', 'header',
	'langManager', 'auth', 'user', MjsController]);
<commit_msg>cleanup<commit_after>var MjsController = function(mjs, notification, item, auth) {
	var self = this;

	this.notification = notification;
	this.mjs = mjs;
	this.item = item;
	this.selected_branch = 0;
	this.mjs_data = [];
	this.items_counter = [0,0,0,0];
	this.branch_edit_status = {
		1: false,
		2: false,
		3: false,
		4: false,
	};
	this.rmdialog_status = false;

	Object.defineProperty(this, 'signedin', {
		get: function() {
			return auth.is_signedin();
		}
	});

	this.init();
};

MjsController.prototype = {
	init: function() {

		this.notification.put({
			en: 'Loding Story...',
			he: 'טוען את הסיפור...'
		});
		this.refresh();
	},

	refresh: function() {
		var self = this;
		var items_ids = [];

		this.mjs.refresh().then(function () {
			var items_ids = [];
			//console.log(self.mjs.data);
			self.mjs.data.items.forEach(function (i) {
				items_ids.push(i.id);
			})
			self.item.get_items(items_ids).then (function (ret) {
				self.mjs_data = [];
				self.items_counter = [0, 0, 0, 0];
				self.mjs.data.items.forEach(function(i, _i) {
					ret[_i].branches = i.branches;
					ret[_i].item_string = i.id;
					self.mjs_data.push(ret[_i]);
					i.branches.forEach(function (flag, _j) {
						if (flag)
							self.items_counter[_j]++;
					});
				})
			});
		});
	},

	stopPropagation: function($event) {
		$event.stopPropagation();
	},

	update_branch_name: function(branch_num, new_name) {
		this.mjs.update_branch_name(branch_num, new_name);
	},

	toggle_branch_edit: function($event,index)  {
		this.branch_edit_status[index] = !(this.branch_edit_status[index]);
	},

	toggle_branch_rmdialog: function()  {
		this.rmdialog_status = !(this.rmdialog_status);
		console.log(this.rmdialog_status);

	}
};

angular.module('main').controller('MjsController', ['mjs', 'notification',
								  'item', 'auth', MjsController]);
<|endoftext|>


<commit_before>/**
 * ColorSplitButton.js
 *
 * Copyright 2009, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://tinymce.moxiecode.com/license
 * Contributing: http://tinymce.moxiecode.com/contributing
 */

(function(tinymce) {
	var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each;

	/**
	 * This class is used to create UI color split button. A color split button will present show a small color picker
	 * when you press the open menu.
	 *
	 * @class tinymce.ui.ColorSplitButton
	 * @extends tinymce.ui.SplitButton
	 */
	tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', {
		/**
		 * Constructs a new color split button control instance.
		 *
		 * @constructor
		 * @method ColorSplitButton
		 * @param {String} id Control id for the color split button.
		 * @param {Object} s Optional name/value settings object.
		 * @param {Editor} ed The editor instance this button is for.
		 */
		ColorSplitButton : function(id, s, ed) {
			var t = this;

			t.parent(id, s, ed);

			/**
			 * Settings object.
			 *
			 * @property settings
			 * @type Object
			 */
			t.settings = s = tinymce.extend({
				colors : '000000,993300,333300,003300,003366,000080,333399,333333,800000,FF6600,808000,008000,008080,0000FF,666699,808080,FF0000,FF9900,99CC00,339966,33CCCC,3366FF,800080,999999,FF00FF,FFCC00,FFFF00,00FF00,00FFFF,00CCFF,993366,C0C0C0,FF99CC,FFCC99,FFFF99,CCFFCC,CCFFFF,99CCFF,CC99FF,FFFFFF',
				grid_width : 8,
				default_color : '#888888'
			}, t.settings);

			/**
			 * Fires when the menu is shown.
			 *
			 * @event onShowMenu
			 */
			t.onShowMenu = new tinymce.util.Dispatcher(t);

			/**
			 * Fires when the menu is hidden.
			 *
			 * @event onHideMenu
			 */
			t.onHideMenu = new tinymce.util.Dispatcher(t);

			/**
			 * Current color value.
			 *
			 * @property value
			 * @type String
			 */
			t.value = s.default_color;
		},

		/**
		 * Shows the color menu. The color menu is a layer places under the button
		 * and displays a table of colors for the user to pick from.
		 *
		 * @method showMenu
		 */
		showMenu : function() {
			var t = this, r, p, e, p2;

			if (t.isDisabled())
				return;

			if (!t.isMenuRendered) {
				t.renderMenu();
				t.isMenuRendered = true;
			}

			if (t.isMenuVisible)
				return t.hideMenu();

			e = DOM.get(t.id);
			DOM.show(t.id + '_menu');
			DOM.addClass(e, 'mceSplitButtonSelected');
			p2 = DOM.getPos(e);
			DOM.setStyles(t.id + '_menu', {
				left : p2.x,
				top : p2.y + e.clientHeight,
				zIndex : 200000
			});
			e = 0;

			Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
			t.onShowMenu.dispatch(t);

			if (t._focused) {
				t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) {
					if (e.keyCode == 27)
						t.hideMenu();
				});

				DOM.select('a', t.id + '_menu')[0].focus(); // Select first link
			}

			t.isMenuVisible = 1;
		},

		/**
		 * Hides the color menu. The optional event parameter is used to check where the event occured so it
		 * doesn't close them menu if it was a event inside the menu.
		 *
		 * @method hideMenu
		 * @param {Event} e Optional event object.
		 */
		hideMenu : function(e) {
			var t = this;

			if (t.isMenuVisible) {
				// Prevent double toogles by canceling the mouse click event to the button
				if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';}))
					return;

				if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) {
					DOM.removeClass(t.id, 'mceSplitButtonSelected');
					Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
					Event.remove(t.id + '_menu', 'keydown', t._keyHandler);
					DOM.hide(t.id + '_menu');
				}

				t.isMenuVisible = 0;
			}
		},

		/**
		 * Renders the menu to the DOM.
		 *
		 * @method renderMenu
		 */
		renderMenu : function() {
			var t = this, m, i = 0, s = t.settings, n, tb, tr, w, context;

			w = DOM.add(s.menu_container, 'div', {role: 'listbox', id : t.id + '_menu', 'class' : s['menu_class'] + ' ' + s['class'], style : 'position:absolute;left:0;top:-1000px;'});
			m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'});
			DOM.add(m, 'span', {'class' : 'mceMenuLine'});

			n = DOM.add(m, 'table', {role: 'presentation', 'class' : 'mceColorSplitMenu'});
			tb = DOM.add(n, 'tbody');

			// Generate color grid
			i = 0;
			each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) {
				c = c.replace(/^#/, '');

				if (!i--) {
					tr = DOM.add(tb, 'tr');
					i = s.grid_width - 1;
				}

				n = DOM.add(tr, 'td');
				n = DOM.add(n, 'a', {
					role : 'option',
					href : 'javascript:;',
					style : {
						backgroundColor : '#' + c
					},
					'title': t.editor.getLang('colors.' + c, c),
					'data-mce-color' : '#' + c
				});

				if (t.editor.forcedHighContrastMode) {
					n = DOM.add(n, 'canvas', { width: 16, height: 16, 'aria-hidden': 'true' });
					if (n.getContext && (context = n.getContext("2d"))) {
						context.fillStyle = '#' + c;
						context.fillRect(0, 0, 16, 16);
					} else {
						// No point leaving a canvas element around if it's not supported for drawing on anyway.
						DOM.remove(n);
					}
				}
			});

			if (s.more_colors_func) {
				n = DOM.add(tb, 'tr');
				n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'});
				n = DOM.add(n, 'a', {role: 'option', id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title);

				Event.add(n, 'click', function(e) {
					s.more_colors_func.call(s.more_colors_scope || this);
					return Event.cancel(e); // Cancel to fix onbeforeunload problem
				});
			}

			DOM.addClass(m, 'mceColorSplitMenu');

			new tinymce.ui.KeyboardNavigation({
				root: t.id + '_menu',
				items: DOM.select('a', t.id + '_menu'),
				onCancel: function() {
					t.hideMenu();
					t.focus();
				}
			});

			// Prevent IE from scrolling and hindering click to occur #4019
			Event.add(t.id + '_menu', 'mousedown', function(e) {return Event.cancel(e);});

			Event.add(t.id + '_menu', 'click', function(e) {
				var c;

				e = DOM.getParent(e.target, 'a', tb);

				if (e && e.nodeName.toLowerCase() == 'a' && (c = e.getAttribute('data-mce-color')))
					t.setColor(c);

				return Event.cancel(e); // Prevent IE auto save warning
			});

			return w;
		},

		/**
		 * Sets the current color for the control and hides the menu if it should be visible.
		 *
		 * @method setColor
		 * @param {String} c Color code value in hex for example: #FF00FF
		 */
		setColor : function(c) {
			this.displayColor(c);
			this.hideMenu();
			this.settings.onselect(c);
		},

		/**
		 * Change the currently selected color for the control.
		 *
		 * @method displayColor
		 * @param {String} c Color code value in hex for example: #FF00FF
		 */
		displayColor : function(c) {
			var t = this;

			DOM.setStyle(t.id + '_preview', 'backgroundColor', c);

			t.value = c;
		},

		/**
		 * Post render event. This will be executed after the control has been rendered and can be used to
		 * set states, add events to the control etc. It's recommended for subclasses of the control to call this method by using this.parent().
		 *
		 * @method postRender
		 */
		postRender : function() {
			var t = this, id = t.id;

			t.parent();
			DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'});
			DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value);
		},

		/**
		 * Destroys the control. This means it will be removed from the DOM and any
		 * events tied to it will also be removed.
		 *
		 * @method destroy
		 */
		destroy : function() {
			this.parent();

			Event.clear(this.id + '_menu');
			Event.clear(this.id + '_more');
			DOM.remove(this.id + '_menu');
		}
	});
})(tinymce);
<commit_msg>Fixed bug where the color split buttons would loose the selection on IE if the editor was placed in a frame/iframe.<commit_after>/**
 * ColorSplitButton.js
 *
 * Copyright 2009, Moxiecode Systems AB
 * Released under LGPL License.
 *
 * License: http://tinymce.moxiecode.com/license
 * Contributing: http://tinymce.moxiecode.com/contributing
 */

(function(tinymce) {
	var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each;

	/**
	 * This class is used to create UI color split button. A color split button will present show a small color picker
	 * when you press the open menu.
	 *
	 * @class tinymce.ui.ColorSplitButton
	 * @extends tinymce.ui.SplitButton
	 */
	tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', {
		/**
		 * Constructs a new color split button control instance.
		 *
		 * @constructor
		 * @method ColorSplitButton
		 * @param {String} id Control id for the color split button.
		 * @param {Object} s Optional name/value settings object.
		 * @param {Editor} ed The editor instance this button is for.
		 */
		ColorSplitButton : function(id, s, ed) {
			var t = this;

			t.parent(id, s, ed);

			/**
			 * Settings object.
			 *
			 * @property settings
			 * @type Object
			 */
			t.settings = s = tinymce.extend({
				colors : '000000,993300,333300,003300,003366,000080,333399,333333,800000,FF6600,808000,008000,008080,0000FF,666699,808080,FF0000,FF9900,99CC00,339966,33CCCC,3366FF,800080,999999,FF00FF,FFCC00,FFFF00,00FF00,00FFFF,00CCFF,993366,C0C0C0,FF99CC,FFCC99,FFFF99,CCFFCC,CCFFFF,99CCFF,CC99FF,FFFFFF',
				grid_width : 8,
				default_color : '#888888'
			}, t.settings);

			/**
			 * Fires when the menu is shown.
			 *
			 * @event onShowMenu
			 */
			t.onShowMenu = new tinymce.util.Dispatcher(t);

			/**
			 * Fires when the menu is hidden.
			 *
			 * @event onHideMenu
			 */
			t.onHideMenu = new tinymce.util.Dispatcher(t);

			/**
			 * Current color value.
			 *
			 * @property value
			 * @type String
			 */
			t.value = s.default_color;
		},

		/**
		 * Shows the color menu. The color menu is a layer places under the button
		 * and displays a table of colors for the user to pick from.
		 *
		 * @method showMenu
		 */
		showMenu : function() {
			var t = this, r, p, e, p2;

			if (t.isDisabled())
				return;

			if (!t.isMenuRendered) {
				t.renderMenu();
				t.isMenuRendered = true;
			}

			if (t.isMenuVisible)
				return t.hideMenu();

			e = DOM.get(t.id);
			DOM.show(t.id + '_menu');
			DOM.addClass(e, 'mceSplitButtonSelected');
			p2 = DOM.getPos(e);
			DOM.setStyles(t.id + '_menu', {
				left : p2.x,
				top : p2.y + e.clientHeight,
				zIndex : 200000
			});
			e = 0;

			Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
			t.onShowMenu.dispatch(t);

			if (t._focused) {
				t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) {
					if (e.keyCode == 27)
						t.hideMenu();
				});

				DOM.select('a', t.id + '_menu')[0].focus(); // Select first link
			}

			t.isMenuVisible = 1;
		},

		/**
		 * Hides the color menu. The optional event parameter is used to check where the event occured so it
		 * doesn't close them menu if it was a event inside the menu.
		 *
		 * @method hideMenu
		 * @param {Event} e Optional event object.
		 */
		hideMenu : function(e) {
			var t = this;

			if (t.isMenuVisible) {
				// Prevent double toogles by canceling the mouse click event to the button
				if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';}))
					return;

				if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) {
					DOM.removeClass(t.id, 'mceSplitButtonSelected');
					Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
					Event.remove(t.id + '_menu', 'keydown', t._keyHandler);
					DOM.hide(t.id + '_menu');
				}

				t.isMenuVisible = 0;
				t.onHideMenu.dispatch();
			}
		},

		/**
		 * Renders the menu to the DOM.
		 *
		 * @method renderMenu
		 */
		renderMenu : function() {
			var t = this, m, i = 0, s = t.settings, n, tb, tr, w, context;

			w = DOM.add(s.menu_container, 'div', {role: 'listbox', id : t.id + '_menu', 'class' : s['menu_class'] + ' ' + s['class'], style : 'position:absolute;left:0;top:-1000px;'});
			m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'});
			DOM.add(m, 'span', {'class' : 'mceMenuLine'});

			n = DOM.add(m, 'table', {role: 'presentation', 'class' : 'mceColorSplitMenu'});
			tb = DOM.add(n, 'tbody');

			// Generate color grid
			i = 0;
			each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) {
				c = c.replace(/^#/, '');

				if (!i--) {
					tr = DOM.add(tb, 'tr');
					i = s.grid_width - 1;
				}

				n = DOM.add(tr, 'td');
				n = DOM.add(n, 'a', {
					role : 'option',
					href : 'javascript:;',
					style : {
						backgroundColor : '#' + c
					},
					'title': t.editor.getLang('colors.' + c, c),
					'data-mce-color' : '#' + c
				});

				if (t.editor.forcedHighContrastMode) {
					n = DOM.add(n, 'canvas', { width: 16, height: 16, 'aria-hidden': 'true' });
					if (n.getContext && (context = n.getContext("2d"))) {
						context.fillStyle = '#' + c;
						context.fillRect(0, 0, 16, 16);
					} else {
						// No point leaving a canvas element around if it's not supported for drawing on anyway.
						DOM.remove(n);
					}
				}
			});

			if (s.more_colors_func) {
				n = DOM.add(tb, 'tr');
				n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'});
				n = DOM.add(n, 'a', {role: 'option', id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title);

				Event.add(n, 'click', function(e) {
					s.more_colors_func.call(s.more_colors_scope || this);
					return Event.cancel(e); // Cancel to fix onbeforeunload problem
				});
			}

			DOM.addClass(m, 'mceColorSplitMenu');

			new tinymce.ui.KeyboardNavigation({
				root: t.id + '_menu',
				items: DOM.select('a', t.id + '_menu'),
				onCancel: function() {
					t.hideMenu();
					t.focus();
				}
			});

			// Prevent IE from scrolling and hindering click to occur #4019
			Event.add(t.id + '_menu', 'mousedown', function(e) {return Event.cancel(e);});

			Event.add(t.id + '_menu', 'click', function(e) {
				var c;

				e = DOM.getParent(e.target, 'a', tb);

				if (e && e.nodeName.toLowerCase() == 'a' && (c = e.getAttribute('data-mce-color')))
					t.setColor(c);

				return Event.cancel(e); // Prevent IE auto save warning
			});

			return w;
		},

		/**
		 * Sets the current color for the control and hides the menu if it should be visible.
		 *
		 * @method setColor
		 * @param {String} c Color code value in hex for example: #FF00FF
		 */
		setColor : function(c) {
			this.displayColor(c);
			this.hideMenu();
			this.settings.onselect(c);
		},

		/**
		 * Change the currently selected color for the control.
		 *
		 * @method displayColor
		 * @param {String} c Color code value in hex for example: #FF00FF
		 */
		displayColor : function(c) {
			var t = this;

			DOM.setStyle(t.id + '_preview', 'backgroundColor', c);

			t.value = c;
		},

		/**
		 * Post render event. This will be executed after the control has been rendered and can be used to
		 * set states, add events to the control etc. It's recommended for subclasses of the control to call this method by using this.parent().
		 *
		 * @method postRender
		 */
		postRender : function() {
			var t = this, id = t.id;

			t.parent();
			DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'});
			DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value);
		},

		/**
		 * Destroys the control. This means it will be removed from the DOM and any
		 * events tied to it will also be removed.
		 *
		 * @method destroy
		 */
		destroy : function() {
			this.parent();

			Event.clear(this.id + '_menu');
			Event.clear(this.id + '_more');
			DOM.remove(this.id + '_menu');
		}
	});
})(tinymce);
<|endoftext|>


<commit_before>define(["github/adioo/bind/v0.2.2/bind", "github/adioo/events/v0.1.2/events", "/jquery.js"], function(Bind, Events) {

    function Cart(module) {

        var self;
        var config;
        var container;
        var template;
        var items = [];

        function processConfig(config) {
            config.options = config.options || {};
            config.options.type = config.options.type || "cookie";

            if (config.options.type === "cookie") {
                config.options.cookie = "cart";
            }

            config.options.priceKey = config.options.priceKey || "price";
            config.options.quantityKey = config.options.quantityKey || "quantity";

            config.template.binds = config.template.binds || [];

            var optClasses = config.options.classes || {}
            optClasses.item = optClasses.item || "item";
            optClasses.empty = optClasses.empty || "empty";
            optClasses.content = optClasses.content || "content";
            optClasses.confirm = optClasses.config || "confirm";
            config.options.classes = optClasses;

            return config;
        }

        function init(conf) {

            // initialize the globals
            self = this;
            config = processConfig(conf);
            if (config.container) {
                container = $(config.container, module.dom);
            } else {
                container = module.dom;
            }
            template = $(config.template.value, module.dom);

            // **************************************
            // generate general binds from the config
            var binds = [];

            for (var i in config.controls) {
                switch (i) {
                    case "add":
                        binds.push({
                            target: config.controls[i],
                            context: ".controls",
                            on: [{
                                name: "click",
                                emit: "requestNewItem"
                            }]
                        });
                        break;
                    case "delete":
                        binds.push({
                            target: config.controls[i],
                            context: ".controls",
                            on: [{
                                name: "click",
                                handler: "removeSelected"
                            }]
                        });
                        break;
                }
            }

            // run the internal binds
            for (var i in binds) {
                Bind.call(self, binds[i]);
            }

            // run the binds
            for (var i in config.binds) {
                Bind.call(self, config.binds[i]);
            }

            Events.call(self, config);

            showEmpty(true);

            self.read();
        }

        // TODO call this from a checkout operation link response if success
        function checkoutDone() {
            clearList();
            items = [];
            if (config.options.type === "cookie") {
                $.cookie.json = true;
                $.cookie("cart", items, { path: "/" });
            }

            showError();
            showOrdered();
        }

        function checkout() {

            var agb = $(".agb", self.dom).find("input").andSelf().find("input[type='checkbox']");
            if (agb.length) {
                if (agb.is(":checked")) {
                    agb.get(0).checked = false;
                    checkoutDone();
                }
                else {
                    showError("Please agree to the terms and conditions");
                }
                return;
            }
            checkoutDone();
        }

        var renderers = {
            selector: function(item) {
                // TODO where do we know the key is the _id
                var existingItem = container.find("#" + item._id);

                // new item type in the cart
                if (!existingItem.length) {

                    var newItem = $(template).clone();
                    newItem
                        .removeClass("template")
                        .addClass(config.options.classes.item)
                        .appendTo(container)
                        .show();

                    for (var i in config.template.binds) {
                        var bindObj = config.template.binds[i];
                        bindObj.context = newItem;
                        Bind.call(self, bindObj, item);
                    }

                    // now there should be an element with this id
                    existingItem = container.find("#" + item._id);
                }

                var qKey = config.options.quantityKey;
                existingItem.find(".quantity").each(function() {

                    var elem = $(this);

                    switch (this.tagName) {
                        case "INPUT":
                            elem.attr("value", item[qKey]);
                            elem.val(item[qKey]);
                            break;
                        default:
                            elem.text(item[qKey]);
                    }
                });

                return;
            }
        };

        function render(items) {
            if (!items || !items.length) {
                showEmpty(true);
                return;
            }

            showEmpty(false);

            var renderer = renderers[config.template.type];
            if (!renderer) {
                showError("No renderer found for templates of type: " + config.template.type);
            }
            for (var i in items) {
                renderer.call(self, items[i]);
            }
        }

        function renderItem(item) {

            if (!item) {
                return;
            }

            var renderer = renderers[config.template.type];
            if (!renderer) {
                showError("No renderer found for templates of type: " + config.template.type);
            }
            showEmpty(false);
            renderer.call(self, item);
        }

        function clearList() {
            $("." + config.options.classes.item, container).remove();
        }

        // ********************************
        // Public functions ***************
        // ********************************

        var readFrom = {
            memory: function(callback) {
                callback(null, items);
            },
            cookie: function(callback) {
                $.cookie.json = true;
                items = $.cookie(config.options.cookie) || [];
                callback(null, items);
            },
            server: function(callback) {
                self.link(config.crud.read, function(err, data) {
                    if (err) { return callback(err); }
                    items = [];
                    for (var i in data) {
                        items.push(data[i]);
                    }
                    callback(null, items);
                });
            }
        }

        function read() {

            clearList();

            var reader = readFrom[config.options.type];
            if (render) {
                reader.call(self, function(err, items) {
                    if (err) {
                        showError(JSON.stringify(err));
                        return;
                    }
                    render.call(self, items);
                    updateTotal();
                });
            }
        }

        function showEmpty(empty) {
            if (empty) {
                $("." + config.options.classes.empty, self.dom).show();
                $("." + config.options.classes.content, self.dom).hide();
                $("." + config.options.classes.confirm, self.dom).hide();
            } else {
                $("." + config.options.classes.empty, self.dom).hide();
                $("." + config.options.classes.content, self.dom).show();
                $("." + config.options.classes.confirm, self.dom).hide();
            }
        }

        function showOrdered() {
            $("." + config.options.classes.empty, self.dom).hide();
            $("." + config.options.classes.content, self.dom).hide();
            $("." + config.options.classes.confirm, self.dom).show();
        }

        function showError(message) {
            if (!message) {
                $(".error").text("").hide();
            } else {
                $(".error").text(message).show();
            }
        }

        var adders = {
            cookie: function(item, callback) {
                $.cookie.json = true;
                var cart = $.cookie("cart") || [];
                var found = false;
                for (var i in cart) {
                    if (cart[i]._id === item._id) {
                        // TODO support incomming item.quantity
                        ++(cart[i][config.options.quantityKey]);
                        item = cart[i];
                        found = true;
                        break;
                    }
                }
                if (!found) {
                    item[config.options.quantityKey] = 1;
                    cart.push(item);
                    item = cart[cart.length - 1];
                }

                $.cookie.json = true;
                $.cookie("cart", cart, { path: "/" });
                items = cart;

                callback(null, item);
            },

            server: function(item, callback) {
                self.link(config.crud.create, { data: item }, function(err, confirmedItem) {
                    if (err) { return callback(err); }

                    var found = false;
                    for (var i in items) {
                        if (items[i]._id === confirmedItem._id) {
                            items[i] = confirmedItem;
                            found = true;
                        }
                    }

                    if (!found) {
                        items.push(confirmedItem);
                    }

                    callback(null, confirmedItem);
                });
            },

            memory: function(item, callback) {
                // TODO in memory type the quantity is saved in the memory object and will
                // be reused later, which is wrong
                var newItem = {};
                var qKey = config.options.quantityKey;

                for (var i in item) {
                    newItem[i] = item[i];
                }
                item = newItem;

                var found = false;
                for (var i in items) {
                    if (items[i]._id === item._id) {
                        item = items[i];
                        ++(item[qKey]);
                        found = true;
                    }
                }

                if (!found) {
                    item[qKey] = 1;
                    items.push(item);
                }

                callback(null, item);
            }
        };

        function addItem(itemData) {
            showError();

            var adder = adders[config.options.type];
            if (!adder) {
                showError("No item adder was found for cart type: " + config.options.type);
                return;
            }
            adder.call(self, itemData, function(err, item) {
                if (err) {
                    showError(JSON.stringify(err));
                    return;
                }
                renderItem(item);
                updateTotal();
            });
        }

        function updateTotal() {
            var total = 0;
            for (var i in items) {
                var priceStr = items[i][config.options.priceKey];
                var quantityStr = items[i][config.options.quantityKey];
                var price = parseInt(priceStr);
                var quantity = parseInt(quantityStr);

                if (!isNaN(price) && !isNaN(quantity)) {
                    total += price * quantity;
                }
            }
            $("#total", self.dom).text(total.toFixed(2));
        }

        function readQuantityFromItem(elem) {

            var quantityElem = elem.find(".quantity").first();
            if (!quantityElem.length) {
                return 1;
            }

            var value = 1;
            var textVal = null;

            switch (quantityElem[0].tagName) {
                case "INPUT":
                    textVal = quantityElem.val();
                    break;
                default:
                    textVal = quantityElem.text();
            }
            var parsedVal = parseInt(textVal);
            if (!isNaN(parsedVal)) {
                value = parsedVal + 1;
            }

            return value;
        }

        function emptyCart() {
            $("." + config.options.classes.item, self.dom).each(function() {
                var item = {
                    _id: $(this).attr("id")
                }
                removeItem(item);
            });
        }

        function removeItem(itemData) {
            showError();

            function removeUiItem() {
                // TODO where do we get the _id key from?
                container.find("#" + itemData._id).remove();
                updateTotal();

                // if no more items, show empty cart
                if (container.find("." + config.options.classes.item).length == 0) {
                    showEmpty(true);
                }
            }

            function removeCacheItem() {
                var newItems = [];
                for (var i in items) {
                    if (items[i]._id !== itemData._id) {
                        newItems.push(items[i]);
                    }
                }
                items = newItems;
                updateTotal();
            }

            switch (config.options.type) {
                case "server":
                    self.link(config.crud.delete, { data: { _id: itemData._id } }, function(err, data) {
                        if (err) { return; }
                        removeUiItem();
                        removeCacheItem();
                    });
                    break;

                case "memory":
                    // a memory-based cart should not make any requests, just remove the item
                    removeUiItem();
                    removeCacheItem();
                    // and show an empty cart when no more items in it
                    var itemElems = container.find("." + config.options.classes.item);
                    showEmpty(!itemElems.length);
                    break;

                case "cookie":
                    // search in the cart cookie for the item with this ID and remove it
                    var cart = $.cookie("cart");
                    for (var i in cart) {
                        if (cart[i]._id === itemData._id) {
                            cart.splice(i, 1);
                            break;
                        }
                    }
                    // if the cart is already empty it should be displayed as so
                    showEmpty(!cart || !cart.length);
                    // save back the updated cart in the cookie
                    $.cookie("cart", cart, { path: "/"});
                    // and also remove the item from the UI
                    removeUiItem();
                    removeCacheItem();
                    break;
            }
        }

        function show() {
            $(self.dom).parent().show();
        }

        function hide() {
            $(self.dom).parent().hide();
        }

         return {
            init: init,
            read: read,
            addItem: addItem,
            removeItem: removeItem,
            emptyCart: emptyCart,
            checkout: checkout,
            show: show,
            hide: hide
        };
    }

    return function(module, config) {

        var cart = new Cart(module);
        for (var i in cart) {
            cart[i] = module[i] || cart[i];
        }
        cart = Object.extend(cart, module);

        cart.init(config);

        return cart;
    }
});

<commit_msg>when emptying the cart, uncheck the GTC ceckbox<commit_after>define(["github/adioo/bind/v0.2.2/bind", "github/adioo/events/v0.1.2/events", "/jquery.js"], function(Bind, Events) {

    function Cart(module) {

        var self;
        var config;
        var container;
        var template;
        var items = [];

        function processConfig(config) {
            config.options = config.options || {};
            config.options.type = config.options.type || "cookie";

            if (config.options.type === "cookie") {
                config.options.cookie = "cart";
            }

            config.options.priceKey = config.options.priceKey || "price";
            config.options.quantityKey = config.options.quantityKey || "quantity";

            config.template.binds = config.template.binds || [];

            var optClasses = config.options.classes || {}
            optClasses.item = optClasses.item || "item";
            optClasses.empty = optClasses.empty || "empty";
            optClasses.content = optClasses.content || "content";
            optClasses.confirm = optClasses.config || "confirm";
            config.options.classes = optClasses;

            return config;
        }

        function init(conf) {

            // initialize the globals
            self = this;
            config = processConfig(conf);
            if (config.container) {
                container = $(config.container, module.dom);
            } else {
                container = module.dom;
            }
            template = $(config.template.value, module.dom);

            // **************************************
            // generate general binds from the config
            var binds = [];

            for (var i in config.controls) {
                switch (i) {
                    case "add":
                        binds.push({
                            target: config.controls[i],
                            context: ".controls",
                            on: [{
                                name: "click",
                                emit: "requestNewItem"
                            }]
                        });
                        break;
                    case "delete":
                        binds.push({
                            target: config.controls[i],
                            context: ".controls",
                            on: [{
                                name: "click",
                                handler: "removeSelected"
                            }]
                        });
                        break;
                }
            }

            // run the internal binds
            for (var i in binds) {
                Bind.call(self, binds[i]);
            }

            // run the binds
            for (var i in config.binds) {
                Bind.call(self, config.binds[i]);
            }

            Events.call(self, config);

            showEmpty(true);

            self.read();
        }

        // TODO call this from a checkout operation link response if success
        function checkoutDone() {
            clearList();
            items = [];
            if (config.options.type === "cookie") {
                $.cookie.json = true;
                $.cookie("cart", items, { path: "/" });
            }

            showError();
            showOrdered();
        }

        function checkout() {

            var agb = $(".agb", self.dom).find("input").andSelf().find("input[type='checkbox']");
            if (agb.length) {
                if (agb.is(":checked")) {
                    agb.get(0).checked = false;
                    checkoutDone();
                }
                else {
                    showError("Please agree to the terms and conditions");
                }
                return;
            }
            checkoutDone();
        }

        var renderers = {
            selector: function(item) {
                // TODO where do we know the key is the _id
                var existingItem = container.find("#" + item._id);

                // new item type in the cart
                if (!existingItem.length) {

                    var newItem = $(template).clone();
                    newItem
                        .removeClass("template")
                        .addClass(config.options.classes.item)
                        .appendTo(container)
                        .show();

                    for (var i in config.template.binds) {
                        var bindObj = config.template.binds[i];
                        bindObj.context = newItem;
                        Bind.call(self, bindObj, item);
                    }

                    // now there should be an element with this id
                    existingItem = container.find("#" + item._id);
                }

                var qKey = config.options.quantityKey;
                existingItem.find(".quantity").each(function() {

                    var elem = $(this);

                    switch (this.tagName) {
                        case "INPUT":
                            elem.attr("value", item[qKey]);
                            elem.val(item[qKey]);
                            break;
                        default:
                            elem.text(item[qKey]);
                    }
                });

                return;
            }
        };

        function render(items) {
            if (!items || !items.length) {
                showEmpty(true);
                return;
            }

            showEmpty(false);

            var renderer = renderers[config.template.type];
            if (!renderer) {
                showError("No renderer found for templates of type: " + config.template.type);
            }
            for (var i in items) {
                renderer.call(self, items[i]);
            }
        }

        function renderItem(item) {

            if (!item) {
                return;
            }

            var renderer = renderers[config.template.type];
            if (!renderer) {
                showError("No renderer found for templates of type: " + config.template.type);
            }
            showEmpty(false);
            renderer.call(self, item);
        }

        function clearList() {
            $("." + config.options.classes.item, container).remove();
        }

        // ********************************
        // Public functions ***************
        // ********************************

        var readFrom = {
            memory: function(callback) {
                callback(null, items);
            },
            cookie: function(callback) {
                $.cookie.json = true;
                items = $.cookie(config.options.cookie) || [];
                callback(null, items);
            },
            server: function(callback) {
                self.link(config.crud.read, function(err, data) {
                    if (err) { return callback(err); }
                    items = [];
                    for (var i in data) {
                        items.push(data[i]);
                    }
                    callback(null, items);
                });
            }
        }

        function read() {

            clearList();

            var reader = readFrom[config.options.type];
            if (render) {
                reader.call(self, function(err, items) {
                    if (err) {
                        showError(JSON.stringify(err));
                        return;
                    }
                    render.call(self, items);
                    updateTotal();
                });
            }
        }

        function showEmpty(empty) {
            if (empty) {
                $("." + config.options.classes.empty, self.dom).show();
                $("." + config.options.classes.content, self.dom).hide();
                $("." + config.options.classes.confirm, self.dom).hide();
                $(".agb", self.dom).get(0).checked = false;
            } else {
                $("." + config.options.classes.empty, self.dom).hide();
                $("." + config.options.classes.content, self.dom).show();
                $("." + config.options.classes.confirm, self.dom).hide();
            }
        }

        function showOrdered() {
            $("." + config.options.classes.empty, self.dom).hide();
            $("." + config.options.classes.content, self.dom).hide();
            $("." + config.options.classes.confirm, self.dom).show();
        }

        function showError(message) {
            if (!message) {
                $(".error").text("").hide();
            } else {
                $(".error").text(message).show();
            }
        }

        var adders = {
            cookie: function(item, callback) {
                $.cookie.json = true;
                var cart = $.cookie("cart") || [];
                var found = false;
                for (var i in cart) {
                    if (cart[i]._id === item._id) {
                        // TODO support incomming item.quantity
                        ++(cart[i][config.options.quantityKey]);
                        item = cart[i];
                        found = true;
                        break;
                    }
                }
                if (!found) {
                    item[config.options.quantityKey] = 1;
                    cart.push(item);
                    item = cart[cart.length - 1];
                }

                $.cookie.json = true;
                $.cookie("cart", cart, { path: "/" });
                items = cart;

                callback(null, item);
            },

            server: function(item, callback) {
                self.link(config.crud.create, { data: item }, function(err, confirmedItem) {
                    if (err) { return callback(err); }

                    var found = false;
                    for (var i in items) {
                        if (items[i]._id === confirmedItem._id) {
                            items[i] = confirmedItem;
                            found = true;
                        }
                    }

                    if (!found) {
                        items.push(confirmedItem);
                    }

                    callback(null, confirmedItem);
                });
            },

            memory: function(item, callback) {
                // TODO in memory type the quantity is saved in the memory object and will
                // be reused later, which is wrong
                var newItem = {};
                var qKey = config.options.quantityKey;

                for (var i in item) {
                    newItem[i] = item[i];
                }
                item = newItem;

                var found = false;
                for (var i in items) {
                    if (items[i]._id === item._id) {
                        item = items[i];
                        ++(item[qKey]);
                        found = true;
                    }
                }

                if (!found) {
                    item[qKey] = 1;
                    items.push(item);
                }

                callback(null, item);
            }
        };

        function addItem(itemData) {
            showError();

            var adder = adders[config.options.type];
            if (!adder) {
                showError("No item adder was found for cart type: " + config.options.type);
                return;
            }
            adder.call(self, itemData, function(err, item) {
                if (err) {
                    showError(JSON.stringify(err));
                    return;
                }
                renderItem(item);
                updateTotal();
            });
        }

        function updateTotal() {
            var total = 0;
            for (var i in items) {
                var priceStr = items[i][config.options.priceKey];
                var quantityStr = items[i][config.options.quantityKey];
                var price = parseInt(priceStr);
                var quantity = parseInt(quantityStr);

                if (!isNaN(price) && !isNaN(quantity)) {
                    total += price * quantity;
                }
            }
            $("#total", self.dom).text(total.toFixed(2));
        }

        function readQuantityFromItem(elem) {

            var quantityElem = elem.find(".quantity").first();
            if (!quantityElem.length) {
                return 1;
            }

            var value = 1;
            var textVal = null;

            switch (quantityElem[0].tagName) {
                case "INPUT":
                    textVal = quantityElem.val();
                    break;
                default:
                    textVal = quantityElem.text();
            }
            var parsedVal = parseInt(textVal);
            if (!isNaN(parsedVal)) {
                value = parsedVal + 1;
            }

            return value;
        }

        function emptyCart() {
            $("." + config.options.classes.item, self.dom).each(function() {
                var item = {
                    _id: $(this).attr("id")
                }
                removeItem(item);
            });
        }

        function removeItem(itemData) {
            showError();

            function removeUiItem() {
                // TODO where do we get the _id key from?
                container.find("#" + itemData._id).remove();
                updateTotal();

                // if no more items, show empty cart
                if (container.find("." + config.options.classes.item).length == 0) {
                    showEmpty(true);
                }
            }

            function removeCacheItem() {
                var newItems = [];
                for (var i in items) {
                    if (items[i]._id !== itemData._id) {
                        newItems.push(items[i]);
                    }
                }
                items = newItems;
                updateTotal();
            }

            switch (config.options.type) {
                case "server":
                    self.link(config.crud.delete, { data: { _id: itemData._id } }, function(err, data) {
                        if (err) { return; }
                        removeUiItem();
                        removeCacheItem();
                    });
                    break;

                case "memory":
                    // a memory-based cart should not make any requests, just remove the item
                    removeUiItem();
                    removeCacheItem();
                    // and show an empty cart when no more items in it
                    var itemElems = container.find("." + config.options.classes.item);
                    showEmpty(!itemElems.length);
                    break;

                case "cookie":
                    // search in the cart cookie for the item with this ID and remove it
                    var cart = $.cookie("cart");
                    for (var i in cart) {
                        if (cart[i]._id === itemData._id) {
                            cart.splice(i, 1);
                            break;
                        }
                    }
                    // if the cart is already empty it should be displayed as so
                    showEmpty(!cart || !cart.length);
                    // save back the updated cart in the cookie
                    $.cookie("cart", cart, { path: "/"});
                    // and also remove the item from the UI
                    removeUiItem();
                    removeCacheItem();
                    break;
            }
        }

        function show() {
            $(self.dom).parent().show();
        }

        function hide() {
            $(self.dom).parent().hide();
        }

         return {
            init: init,
            read: read,
            addItem: addItem,
            removeItem: removeItem,
            emptyCart: emptyCart,
            checkout: checkout,
            show: show,
            hide: hide
        };
    }

    return function(module, config) {

        var cart = new Cart(module);
        for (var i in cart) {
            cart[i] = module[i] || cart[i];
        }
        cart = Object.extend(cart, module);

        cart.init(config);

        return cart;
    }
});

<|endoftext|>