/* global define,module,require,google */ (function (root, factory) { 'use strict'; if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module. define(['jquery', 'marker-animate'], factory.bind(null, root)); } else if (typeof module !== "undefined" && module !== null && module.exports != null) { // jshint ignore:line // Node module. module.exports = factory(root, require('jquery'), require('marker-animate')); } else { // Browser globals root.SlidingMarker = factory(root, root.jQuery); } }(this, function (root, $) { 'use strict'; //Let jQuery be soft dependency $ = $ || {}; $.extend = $.extend || function extend(obj) { //Taken (and modified) from here: http://stackoverflow.com/a/14604815/1691132 Array.prototype.slice.call(arguments, 1).forEach(function (source) { if (source) { for (var prop in source) { if (source[prop] && source[prop].constructor === Object) { if (!obj[prop] || obj[prop].constructor === Object) { obj[prop] = obj[prop] || {}; extend(obj[prop], source[prop]); } else { obj[prop] = source[prop]; } } else { obj[prop] = source[prop]; } } } }); return obj; }; var GoogleMarker = google.maps.Marker; //Store original in case it will be replaced in initializeGlobally(). var animateTo, markerAnimate_AnimateTo_Wrapper = function(destPosition, easing, duration) { //default implementation based on markerAnimate if (destPosition === null || destPosition === undefined) { //as markerAnimate provides no means to stop animation, do it manually, even though it leaks markerAnimate implementation. if (window.cancelAnimationFrame) { window.cancelAnimationFrame(this.AT_animationHandler); } else { clearTimeout(this.AT_animationHandler); } return; } google.maps.Marker.prototype.animateTo.apply(this, arguments); }; //default options var defaultOptions = { easing: "easeInOutQuint", duration: 1000, animateFunctionAdapter: function (marker, destPosition, easing, duration) { if (!animateTo) { if (!google.maps.Marker.prototype.animateTo) { throw new Error("Please either reference markerAnimate.js, or provide an alternative animateFunctionAdapter"); } animateTo = markerAnimate_AnimateTo_Wrapper; } animateTo.call(marker, destPosition, { easing: easing, duration: duration, complete: function () { } }); } }; var inherits = function (childCtor, parentCtor) { /* @constructor */ function TempCtor() {} TempCtor.prototype = parentCtor.prototype; childCtor.superClass_ = parentCtor.prototype; childCtor.prototype = new TempCtor(); /* @override */ childCtor.prototype.constructor = childCtor; }; var decorates = function (childCtor, parentCtor) { inherits(childCtor, parentCtor); var parentPrototype = parentCtor.prototype; //TODO: google.maps.MVCObject.prototype.set if object instanceof GoogleMarker $.extend(childCtor.prototype, { _instance: null, //override it in constructor originalSet: function () { return parentPrototype.set.apply(this, arguments); }, //from MVCObject set: function (key, value) { var that = this; that.originalSet.apply(that, arguments); if (key === "position" && that instanceof SlidingMarker) { that._setInstancePositionAnimated(value); } else { that.originalSet.apply(that._instance, arguments); } }, _setInstancePositionAnimated: function (position) { var that = this; if (that._constructing) { //pass by return; } if (!position || !that._instance.getPosition()) { //If position is set for a first time, no animation should be applied if (that._instance.getPosition() !== position) { that._instance.setPosition(position); } return; } //apply animation function //this will cause many animationposition_changed events that.get("animateFunctionAdapter").call(null, that._instance, position, that.get("easing"), that.get("duration")); }, //setValues() will call set(), no need to override originalAddListener: function () { return parentPrototype.addListener.apply(this, arguments); }, //from MVCObject addListener: function (eventName, handler) { var target = (eventName === "map_changed") ? this._instance : getEventTarget.call(this, eventName); return this.originalAddListener.apply(target, arguments); }, map_changed: function () { //Should be empty //Prevents this marker from appearing on map. Only this._instance marker will appear. }, //This will be called by binding created with marker.bindTo() method, instead of call to set("position"). position_changed: function () { if (!this._suppress_animation) { this._setInstancePositionAnimated(this.getPosition()); } else { delete this._suppress_animation; } } }); }; //call it on SlidingMarker var getEventTarget = function (eventName) { //redirect _changed events to this, other events to _instance if (eventName.endsWith("_changed")) { //all _changed redirect to this return this; } return this._instance; }; var originalAddListener = google.maps.event.addListener; google.maps.event.addListener = function (instance, eventName, handler) { //Redirect listener to target if (instance instanceof SlidingMarker) { var target = (eventName === "map_changed") ? instance._instance : getEventTarget.call(instance, eventName); return originalAddListener.call(this, target, eventName, handler); } return originalAddListener.apply(this, arguments); }; var originalTrigger = google.maps.event.trigger; google.maps.event.trigger = function (instance, eventName) { //Replace instance parameter to target if (instance instanceof SlidingMarker) { var target = (eventName === "map_changed") ? instance : getEventTarget.call(instance, eventName), newArgs = [target].concat(Array.prototype.slice.call(arguments, 1)); //replaces instance parameter with target return originalTrigger.apply(this, newArgs); } return originalTrigger.apply(this, arguments); }; //just string helper String.prototype.endsWith = String.prototype.endsWith || function(suffix) { return this.indexOf(suffix, this.length - suffix.length) !== -1; }; //constructor var SlidingMarker = function (opt_options) { opt_options = $.extend({}, defaultOptions, opt_options); this._instance = new GoogleMarker(opt_options); this.animationPosition = null; this._constructing = true; // Call the parent constructor. GoogleMarker.call(this, opt_options); delete this._constructing; this.bindTo("animationPosition", this._instance, "position"); this.bindTo("anchorPoint", this._instance, "anchorPoint"); //This makes InfoWindow.open(map, marker) work. this.bindTo("internalPosition", this._instance, "internalPosition"); //This makes InfoWindow.open(map, marker) work. }; decorates(SlidingMarker, GoogleMarker); //Overrides $.extend(SlidingMarker.prototype, { getAnimationPosition: function () { return this.get("animationPosition"); }, //Changes marker position immediately setPositionNotAnimated: function (position) { this._suppress_animation = true; //will be unset by position_changed handler this.get("animateFunctionAdapter").call(null, this._instance, null); //stop current animation this.originalSet("position", position); this._instance.setPosition(position); }, setDuration: function (value) { this.set("duration", value); }, getDuration: function () { return this.get("duration"); }, setEasing: function (value) { this.set("easing", value); }, getEasing: function () { return this.get("easing"); } }); SlidingMarker.initializeGlobally = function () { google.maps.Marker = SlidingMarker; }; return SlidingMarker; }));