<commit_before>gmaps = {
    // map object
    map: null,

    // google markers objects
    markers: [],

    // google lat lng objects
    latLngs: [],

    // our formatted marker data objects
    markerData: [],

    newMarker: undefined,

    newInfo: undefined,

    //newMarker: undefined,
    // add a marker given our formatted marker data object
    addMarker: function(marker) {
        var gLatLng = new google.maps.LatLng(marker.lat, marker.lng);
        var gMarker = new google.maps.Marker({
            position: gLatLng,
            map: this.map,
            title: marker.title,
            //animation: google.maps.Animation.DROP,
            icon:'http://maps.google.com/mapfiles/ms/icons/blue-dot.png'
        });
        this.latLngs.push(gLatLng);

        var spotFixInfo = getMarker(gLatLng.lat(), gLatLng.lng());
        var content = Blaze.toHTML(Blaze.With(spotFixInfo, function() { return Template.info; }));
        //Blaze.toHTML(Template.info)

        //temporarily create a new content:
        // var content = "Name: " + spotFixInfo.title +
        //                 "
Email: " + spotFixInfo.email +
        //                 "
Proposed Date: " + spotFixInfo.date +
        //                 "
Volunteers needed: " + spotFixInfo.volNeeded +
        //                 "
Volunteers Signed up: " + spotFixInfo.volunteered;

        this.showSpotfixWindow(gMarker, content);
        this.markers.push(gMarker);
        this.markerData.push(marker);
        var mc = new MarkerClusterer(this.map, this.markers);
        mc.repaint();
        return gMarker;
    },

    // calculate and move the bound box based on our markers
    calcBounds: function() {
        var bounds = new google.maps.LatLngBounds();
        for (var i = 0, latLngLength = this.latLngs.length; i < latLngLength; i++) {
            bounds.extend(this.latLngs[i]);
        }
        this.map.fitBounds(bounds);
    },

    // check if a marker already exists
    markerExists: function(key, val) {
        _.each(this.markers, function(storedMarker) {
            if (storedMarker[key] == val)
                return true;
        });
        return false;
    },

    // initialize the map
    initialize: function() {
        console.log("[+] Intializing Google Maps...");
        var mapOptions = {
            zoom: 12,
            mapTypeControl: true,
        	mapTypeControlOptions: {
            style: google.maps.MapTypeControlStyle.DROPDOWN_MENU
        	},
        	navigationControl: true,
        	mapTypeId: google.maps.MapTypeId.ROADMAP
        };

        this.map = new google.maps.Map(
            document.getElementById('map-canvas'),
            mapOptions
        );

          // set center and bounds to India
         var geocoder = new google.maps.Geocoder();
         var country = "India";
         var that = this;
         geocoder.geocode({'address' : country}, function (results, status) {
            if (status == google.maps.GeocoderStatus.OK) {
                that.map.setCenter(results[0].geometry.location);
                that.map.fitBounds(results[0].geometry.bounds);
            } else {
                Console.log("Geocode was not successful for the following reason: " + status);
              }
        });

        this.addSearchBox();

        // global flag saying we intialized already
        Session.set('map', true);
    },

    resizeMap: function(map) {

    },

    // add markers on map clicks
    addClickListener: function() {
        var that = this;
        google.maps.event.addListener(this.map, 'click', function(e) {
            if (this.newMarker != undefined) {
                this.newMarker.setVisible(false);
                this.newMarker = undefined;
            }
            this.newMarker = new google.maps.Marker({
                position: e.latLng,
                icon:'http://maps.google.com/mapfiles/ms/icons/red-dot.png',
                map: that.map,
            });
            Session.set('lat', this.newMarker.position.lat());
            Session.set('lng', this.newMarker.position.lng());

            var content = Blaze.toHTML(Template.action);

            that.showSpotfixWindow(this.newMarker, content);
        });
    },

    // show infowindow on marker click
    showSpotfixWindow: function(marker, content) {
        google.maps.event.addListener(marker, 'click', function() {
            console.log("infowindow" + gmaps.newInfo);
            if(gmaps.newInfo != undefined) {
                console.log("trying to close infowindow " + gmaps.newInfo);
                gmaps.newInfo.close();
            }
            gmaps.newInfo = new google.maps.InfoWindow({
                content: content,
                maxWidth: 350
            });
            gmaps.newInfo.open(this.map,marker);
        });

    },

    // add autocomplete search box to the map
    addSearchBox : function() {
        var input = (document.getElementById('pac-input'));
        this.map.controls[google.maps.ControlPosition.TOP_LEFT].push(input);
        var searchBox = new google.maps.places.SearchBox((input));
        this.addSearchMarkers(searchBox, this.map);
    },

    // Listen for the event fired when the user selects an item from the
    // pick list. Retrieve the matching places for that item.
    addSearchMarkers: function(searchBox, map) {

          var that = this;
          google.maps.event.addListener(searchBox, 'places_changed', function() {

            var markers = [];
            var places = searchBox.getPlaces();

            if (places.length == 0) {
              return;
            }
            for (var i = 0, marker; marker = markers[i]; i++) {
              marker.setMap(null);
            }

            // For each place, get the icon, place name, and location.
            markers = [];
            var bounds = new google.maps.LatLngBounds();
            for (var i = 0, place; place = places[i]; i++) {
              var image = {
                url: place.icon,
                size: new google.maps.Size(71, 71),
                origin: new google.maps.Point(0, 0),
                anchor: new google.maps.Point(17, 34),
                scaledSize: new google.maps.Size(25, 25)
              };

              if (this.newMarker != undefined) {
                this.newMarker.setVisible(false);
                this.newMarker = undefined;
            }
              // Create a marker for each place.
              this.newMarker = new google.maps.Marker({
                map: map,
                icon: image,
                title: place.name,
                position: place.geometry.location
              });
                Session.set('lat', this.newMarker.position.lat());
                Session.set('lng', this.newMarker.position.lng());

                var content = Blaze.toHTML(Template.action);

              that.showSpotfixWindow(this.newMarker, content);
              //markers.push(marker);

              bounds.extend(place.geometry.location);
            }

            map.fitBounds(bounds);
          });

          // Bias the SearchBox results towards places that are within the bounds of the
          // current map's viewport.
          google.maps.event.addListener(map, 'bounds_changed', function() {
            var bounds = map.getBounds();
            searchBox.setBounds(bounds);
          });
    },

}<commit_msg>resize<commit_after>gmaps = {
    // map object
    map: null,

    // google markers objects
    markers: [],

    // google lat lng objects
    latLngs: [],

    // our formatted marker data objects
    markerData: [],

    //marker data
    newMarker: undefined,

    //Info popup
    newInfo: undefined,

    //map center
    center: undefined,

    // add a marker given our formatted marker data object
    addMarker: function(marker) {
        var gLatLng = new google.maps.LatLng(marker.lat, marker.lng);
        var gMarker = new google.maps.Marker({
            position: gLatLng,
            map: this.map,
            title: marker.title,
            //animation: google.maps.Animation.DROP,
            icon:'http://maps.google.com/mapfiles/ms/icons/blue-dot.png'
        });
        this.latLngs.push(gLatLng);

        var spotFixInfo = getMarker(gLatLng.lat(), gLatLng.lng());
        var content = Blaze.toHTML(Blaze.With(spotFixInfo, function() { return Template.info; }));

        //temporarily create a new content:
        // var content = "Name: " + spotFixInfo.title +
        //                 "
Email: " + spotFixInfo.email +
        //                 "
Proposed Date: " + spotFixInfo.date +
        //                 "
Volunteers needed: " + spotFixInfo.volNeeded +
        //                 "
Volunteers Signed up: " + spotFixInfo.volunteered;

        this.showSpotfixWindow(gMarker, content);
        this.markers.push(gMarker);
        this.markerData.push(marker);
        var mc = new MarkerClusterer(this.map, this.markers);
        mc.repaint();
        return gMarker;
    },

    // calculate and move the bound box based on our markers
    calcBounds: function() {
        var bounds = new google.maps.LatLngBounds();
        for (var i = 0, latLngLength = this.latLngs.length; i < latLngLength; i++) {
            bounds.extend(this.latLngs[i]);
        }
        this.map.fitBounds(bounds);
    },

    // check if a marker already exists
    markerExists: function(key, val) {
        _.each(this.markers, function(storedMarker) {
            if (storedMarker[key] == val)
                return true;
        });
        return false;
    },

    // initialize the map
    initialize: function() {
        console.log("[+] Intializing Google Maps...");
        var mapOptions = {
            zoom: 12,
            mapTypeControl: true,
        	mapTypeControlOptions: {
            style: google.maps.MapTypeControlStyle.DROPDOWN_MENU
        	},
        	navigationControl: true,
        	mapTypeId: google.maps.MapTypeId.ROADMAP
        };

        this.map = new google.maps.Map(
            document.getElementById('map-canvas'),
            mapOptions
        );

          // set center and bounds to current location
         var geocoder = new google.maps.Geocoder();
         var country = "India";
         var that = this;
         geocoder.geocode({'address' : country}, function (results, status) {
            if (status == google.maps.GeocoderStatus.OK) {
                that.map.setCenter(results[0].geometry.location);
                that.map.fitBounds(results[0].geometry.bounds);
            } else {
                Console.log("Geocode was not successful for the following reason: " + status);
              }
        });

        this.addSearchBox();

        // global flag saying we intialized already
        Session.set('map', true);
    },

    idleMap: function() {
        google.maps.event.addDomListener(this.map, 'idle', function() {
              this.center = map.getCenter();
            });
    },

    resizeMap: function() {
        google.maps.event.addDomListener(window, 'resize', function() {
            this.map.setCenter(this.center);
        });
    },

    // add markers on map clicks
    addClickListener: function() {
        var that = this;
        google.maps.event.addListener(this.map, 'click', function(e) {
            if (this.newMarker != undefined) {
                this.newMarker.setVisible(false);
                this.newMarker = undefined;
            }
            this.newMarker = new google.maps.Marker({
                position: e.latLng,
                icon:'http://maps.google.com/mapfiles/ms/icons/red-dot.png',
                map: that.map,
            });
            Session.set('lat', this.newMarker.position.lat());
            Session.set('lng', this.newMarker.position.lng());

            var content = Blaze.toHTML(Template.action);

            that.showSpotfixWindow(this.newMarker, content);
        });
    },

    // show infowindow on marker click
    showSpotfixWindow: function(marker, content) {
        google.maps.event.addListener(marker, 'click', function() {
            console.log("infowindow" + gmaps.newInfo);
            if(gmaps.newInfo != undefined) {
                console.log("trying to close infowindow " + gmaps.newInfo);
                gmaps.newInfo.close();
            }
            gmaps.newInfo = new google.maps.InfoWindow({
                content: content,
                maxWidth: 350
            });
            gmaps.newInfo.open(this.map,marker);
        });

    },

    // add autocomplete search box to the map
    addSearchBox : function() {
        var input = (document.getElementById('pac-input'));
        this.map.controls[google.maps.ControlPosition.TOP_LEFT].push(input);
        var searchBox = new google.maps.places.SearchBox((input));
        this.addSearchMarkers(searchBox, this.map);
    },

    // Listen for the event fired when the user selects an item from the
    // pick list. Retrieve the matching places for that item.
    addSearchMarkers: function(searchBox, map) {

          var that = this;
          google.maps.event.addListener(searchBox, 'places_changed', function() {

            var markers = [];
            var places = searchBox.getPlaces();

            if (places.length == 0) {
              return;
            }
            for (var i = 0, marker; marker = markers[i]; i++) {
              marker.setMap(null);
            }

            // For each place, get the icon, place name, and location.
            markers = [];
            var bounds = new google.maps.LatLngBounds();
            for (var i = 0, place; place = places[i]; i++) {
              var image = {
                url: place.icon,
                size: new google.maps.Size(71, 71),
                origin: new google.maps.Point(0, 0),
                anchor: new google.maps.Point(17, 34),
                scaledSize: new google.maps.Size(25, 25)
              };

              if (this.newMarker != undefined) {
                this.newMarker.setVisible(false);
                this.newMarker = undefined;
            }
              // Create a marker for each place.
              this.newMarker = new google.maps.Marker({
                map: map,
                icon: image,
                title: place.name,
                position: place.geometry.location
              });
                Session.set('lat', this.newMarker.position.lat());
                Session.set('lng', this.newMarker.position.lng());

                var content = Blaze.toHTML(Template.action);

              that.showSpotfixWindow(this.newMarker, content);
              //markers.push(marker);

              bounds.extend(place.geometry.location);
            }

            map.fitBounds(bounds);
          });

          // Bias the SearchBox results towards places that are within the bounds of the
          // current map's viewport.
          google.maps.event.addListener(map, 'bounds_changed', function() {
            var bounds = map.getBounds();
            searchBox.setBounds(bounds);
          });
    },

}<|endoftext|>


<commit_before>/**
 * Cobalt Fragment type
 * A Fragment is a combination of plain text and a list of annotations.
 * The list of annotations will always be sorted by start and then end position.
 * A fragment is immutable, all operations on it return a new fragment.
 */
module.exports = function(text, annotations) {

    /**
     * The annotations list of a fragment, with the public api.
     */
    function cobaltAnnotationList( annotations ) {
        this.type = 'cobaltAnnotationList';
        this.list = [];
        if ( cobalt.implements(annotations,'cobaltAnnotationList') ) {
            // FIXME: create private factory method for cobaltAnnotationList
            // that just returns the input object in this case.
            this.list = annotations.list;
        } else if ( Array.isArray( annotations) ) {
            this.list = annotations;
        } else {
            this.list = parseAnnotations( annotations + '' );
        }
        // clear unset ranges.
        this.list = this.list.filter( function(ann) {
            return ann.range.start != null;
        });
        // sort by range
        this.list.sort( function(a, b) {
            if ( a.range.start < b.range.start ) {
                return -1;
            } else if ( a.range.start > b.range.start ) {
                return 1;
            }
            return 0;
        });
        Object.freeze(this);
        Object.freeze(this.list);
    }

    function parseAnnotations(annotations) {
        var reMarkupLine = /^(?:(([0-9]+\-[0-9]+)(,[0-9]+\-[0-9]+)*)):(.*)$/m;
        var matches = [];
        var list = [];
        while ( annotations && ( matches = annotations.match(reMarkupLine) ) ) {
            list.push(
                cobalt.annotation(
                    matches[1].split(',').map(function(rs) {
                        return rs.split('-').map(function(re) {
                            return parseInt(re);
                        });
                    }),
                    matches[matches.length-1]
                )
            );
            annotations = annotations.substr( matches[0].length + 1 );
        }
        return list;
    }

    cobaltAnnotationList.prototype = {
        constructor: cobaltAnnotationList,
        /**
         * Returns the number of annotations in the list.
         */
        get count () {
            return this.list.length;
        },
        /**
         * Returns a string with all annotations joined by newlines.
         */
        toString: function() {
            return this.list.join('\n');
        },
        /**
         * Returns a new list with a new annotation added.
         */
        apply: function(range, tag) {
            if ( range instanceof cobaltAnnotationList ) {
                return new cobaltAnnotationList(
                    this.list.concat(range.list)
                );
            } else {
                if ( typeof range.tag != 'undefined' ) {
                    tag   = range.tag;
                    range = range.range;
                }
                var list = this.list.slice();
                list.push(
                    cobalt.annotation(range,tag)
                );

                return new cobaltAnnotationList(list);
            }
        },
        /**
         * Returns a new list, with the annotations with the given
         * tag cleared on the given range.
         */
        remove: function(range, tag) {
            if ( range instanceof cobaltAnnotationList ) {
                // FIXME: fill in
            } else {
                if ( typeof range.tag != 'undefined' ) {
                    tag   = range.tag;
                    range = range.range;
                }
                var result = [];
                this.list.forEach(function(ann) {
                    if ( ann.has(tag) ) {
                        var r = ann.range.exclude(range);
                        if ( r && r.start != null ) {
                            result.push( cobalt.annotation( r, ann.tag ) );
                        }
                    } else {
                        result.push(ann);
                    }
                });
                return new cobaltAnnotationList(result);
            }
        },

        /**
         * Returns a new list with all annotations cleared on the given range.
         * Clear doesn't move the annotations, it just removes parts or all of them.
         */
        clear: function(range) {
            var result = [];
            this.list.forEach(function(ann) {
                var r = ann.range.exclude(range);
                if ( r.start != null ) {
                    result.push( cobalt.annotation( r, ann.tag ) );
                }
            });
            return new cobaltAnnotationList(result);
        },
        /**
         * Returns a new list where all annotations have the given range deleted.
         * This means that all positions in the given range are cut from all annotations.
         * Annotations to the right of the given range will move left.
         * Annotations that are contained by the given range are removed.
         */
        delete: function(range) {
            var result = [];
            this.list.forEach(function(ann) {
                var r = ann.range.delete(range);
                if ( r.start != null ) {
                    result.push( cobalt.annotation( r, ann.tag ) );
                }
            });
            return new cobaltAnnotationList(result);
        },
        /**
         * Returns a new list with the positions in the given range inserted
         * into all annotations. Annotations to the right of the range will move right.
         */
        insert: function(range) {
            var result = [];
            this.list.forEach(function(ann) {
                result.push(cobalt.annotation( ann.insert(range), ann.tag ) );
            });
            return new cobaltAnnotationList(result);
        },
        /**
         * Returns a new list with only the annotations that matched the filter.
         */
        filter: function( f ) {
            return new cobaltAnnotationList( this.list.filter(f) );
        },
        /**
         * Returns a new list with the annotations changed by the map function.
         */
        map: function( f ) {
            return new cobaltAnnotationList( this.list.map(f) );
        },
        /**
         *
         */
        reduce: function( previousValue, f ) {
            return this.list.reduce( previousValue, f);
        },
        /**
         * Search through all annotations and return a new annotation list with
         * only those annotations that match the selector.
         */
        query: function( selector ) {
            //FIXME: design a selector syntax that makes sense for cobalt (no nesting)
            //then implement
        },
        forEach: function( f ) {
            this.list.forEach(f);
        },
/*        copy: function( range ) {
            return this.filter(function(a) {
                if (a.range.overlaps(range)) {
                    return a;
                }
            });
        },
*/
        has: function( range, tag ) {
            return this.filter(function(a) {
                if (a.range.overlaps(range) && a.tag==tag) {
                    return true;
                }
            }).count>0;
        }
    };

    /**
     * Non public text api.
     */
    var cobaltText = {
        delete: function(text, range) {
            range.ranges.reverse().forEach(function(re) {
                text = text.slice(0, re.start) + text.slice(re.end);
            });
            return text;
        },
        copy: function(text, range) {
            var result = '';
            range.ranges.forEach(function(re) {
                result += text.slice(re.start, re.end);
            });
            return result;
        },
        insert: function(text, insert, position) {
            return text.slice(0,position) + insert + text.slice(position);
        }
    };

    /**
     * Non public annotations api.
     */
    var cobaltAnnotations = {
        delete: function(annotations, range) {
            result = [];
            annotations.forEach(function(ann) {
                ann = ann.delete(range);
                if ( ann ) {
                    result.push(ann);
                }
            });
            return new cobaltAnnotationList(result);
        },
        copy: function(annotations, range) {
            result = [];
            annotations.forEach(function(ann) {
                if ( ann.range.overlaps(range) ) {
                    result.push( cobalt.annotation( ann.range.intersect(range), ann.tag ) );
                }
            });
            return new cobaltAnnotationList(result);
        },
        insert: function(annotations, position, size ) {
            result = [];
            var insertedRange = cobalt.range(position, position+size);
            annotations.forEach(function(ann) {
                result.push( cobalt.annotation( ann.range.insert( insertedRange ), ann.tag ));
            });
            return new cobaltAnnotationList(result);
        }
    };

    /**
     * Cobalt Fragment type and public api.
     */
    function cobaltFragment( text, annotations ) {
        this.type = 'cobaltFragment';
        if ( typeof annotations == 'undefined' ) {
            var result = parseFragment(text);
            if ( result.text || result.annotations) {
                text = result.text;
                annotations = result.annotations;
            }
        }
        this.text = '' + text;
        this.annotations = new cobaltAnnotationList( annotations );
        Object.freeze(this);
    }

    function parseFragment(fragment) {
        var info = cobalt.mime.decode( fragment );
        var text = '', annotations = '';
        info.parts.forEach( function(part) {
            switch( part.headers['content-type'] ) {
                case 'text/plain' :
                    text = part.message;
                break;
                case 'text/hope' :
                    annotations = part.message;
                break;
            }
        });
        return { text: text, annotations: annotations };
    }

    cobaltFragment.prototype = {
        constructor: cobaltFragment,
        /**
         * Returns a new fragment where the given range is deleted (cut).
         */
        delete: function( range ) {
            range = cobalt.range(range);
            return new cobaltFragment(
                cobaltText.delete( this.text, range ),
                cobaltAnnotations.delete( this.annotations, range )
            );
        },
        /**
         * Returns a new fragment with only the parts that overlap the given range.
         * The new fragment adds all text slices matching the range together as one
         * single text. All annotations are moved/cut to match this new text.
         */
        copy: function( range ) {
            range = cobalt.range(range);
            return new cobaltFragment(
                cobaltText.copy( this.text, range ),
                cobaltAnnotations.copy(
                    this.annotations,
                    range.delete(
                        range.invert(this.text.length)
                    )
                )
            );
        },
        /**
         * Returns a combined new fragment with the inserted fragment text and annotations
         * inserted at the given position.
         */
        insert: function( range, fragment ) {
            range = cobalt.range(range);
            fragment = new cobaltFragment(fragment);
            result = this.delete(range);
            return new cobaltFragment(
                cobaltText.insert( result.text, fragment.text, range.start ),
                cobaltAnnotations
                .insert( result.annotations, range.start, fragment.text.length )
                .apply( cobaltAnnotations.insert( fragment.annotations, 0, range.start ) )
            );
        },
        /**
         * Returns a new range, with the given range/tag or annotation or annotationlist applied
         */
        apply: function( range, tag ) {
            range = cobalt.range(range);
            return new cobaltFragment(
                this.text,
                this.annotations.apply( range, tag )
            );
        },
        /**
         * Returns a new range, with the given range/tag or annotation or annotationlist removed
         */
        remove: function( range, tag ) {
            range = cobalt.range(range);
            return new cobaltFragment(
                this.text,
                this.annotations.remove( range, tag )
            );
        },
        /**
         * Search through the text using a regular expression or string. Returns a range object
         * encompassing all matches.
         */
        search: function( searchRe ) {
            function escapeRegExp(str) {
                return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
            }
            if ( typeof(searchRe) === 'string' ) {
                searchRe = new RegExp(escapeRegExp(searchRe), 'g');
            }
            var result = [];
            if (this.text.length) {
                var match, lastIndex;
                while (match = searchRe.exec(this.text)) {
                    result.push([match.index, searchRe.lastIndex]);
                    if (lastIndex==searchRe.lastIndex) {
                        searchRe.lastIndex++;
                    }
                    lastIndex = searchRe.lastIndex;
                }
            }
            return cobalt.range(result);
        },
        /**
         * Search through the annotations using a query language approximately like css selectors.
         * Returns a range object encompassing all matched ranges.
         */
        query: function( selector ) {
            //FIXME: design a selector syntax that makes sense for cobalt (no nesting)
            //then implement
        },
        /*
         * Returns a mime encoded string with the text and annotations.
         */
        toString: function() {
            return cobalt.mime.encode( [
                'Content-type: text/plain\n\n' + this.text,
                'Content-type: text/cobalt\n\n' + this.annotations
            ]);
        }
    };

    if ( cobalt.implements( text, 'cobaltFragment' ) ) {
        // because cobaltFragment is immutable, we can just return it.
        return text;
    } else {
        return new cobaltFragment(text, annotations);
    }
};<commit_msg>fixed annotationsList.has test for an empty range (start/end the same)<commit_after>/**
 * Cobalt Fragment type
 * A Fragment is a combination of plain text and a list of annotations.
 * The list of annotations will always be sorted by start and then end position.
 * A fragment is immutable, all operations on it return a new fragment.
 */
module.exports = function(text, annotations) {

    /**
     * The annotations list of a fragment, with the public api.
     */
    function cobaltAnnotationList( annotations ) {
        this.type = 'cobaltAnnotationList';
        this.list = [];
        if ( cobalt.implements(annotations,'cobaltAnnotationList') ) {
            // FIXME: create private factory method for cobaltAnnotationList
            // that just returns the input object in this case.
            this.list = annotations.list;
        } else if ( Array.isArray( annotations) ) {
            this.list = annotations;
        } else {
            this.list = parseAnnotations( annotations + '' );
        }
        // clear unset ranges.
        this.list = this.list.filter( function(ann) {
            return ann.range.start != null;
        });
        // sort by range
        this.list.sort( function(a, b) {
            if ( a.range.start < b.range.start ) {
                return -1;
            } else if ( a.range.start > b.range.start ) {
                return 1;
            }
            return 0;
        });
        Object.freeze(this);
        Object.freeze(this.list);
    }

    function parseAnnotations(annotations) {
        var reMarkupLine = /^(?:(([0-9]+\-[0-9]+)(,[0-9]+\-[0-9]+)*)):(.*)$/m;
        var matches = [];
        var list = [];
        while ( annotations && ( matches = annotations.match(reMarkupLine) ) ) {
            list.push(
                cobalt.annotation(
                    matches[1].split(',').map(function(rs) {
                        return rs.split('-').map(function(re) {
                            return parseInt(re);
                        });
                    }),
                    matches[matches.length-1]
                )
            );
            annotations = annotations.substr( matches[0].length + 1 );
        }
        return list;
    }

    cobaltAnnotationList.prototype = {
        constructor: cobaltAnnotationList,
        /**
         * Returns the number of annotations in the list.
         */
        get count () {
            return this.list.length;
        },
        /**
         * Returns a string with all annotations joined by newlines.
         */
        toString: function() {
            return this.list.join('\n');
        },
        /**
         * Returns a new list with a new annotation added.
         */
        apply: function(range, tag) {
            if ( range instanceof cobaltAnnotationList ) {
                return new cobaltAnnotationList(
                    this.list.concat(range.list)
                );
            } else {
                if ( typeof range.tag != 'undefined' ) {
                    tag   = range.tag;
                    range = range.range;
                }
                var list = this.list.slice();
                list.push(
                    cobalt.annotation(range,tag)
                );

                return new cobaltAnnotationList(list);
            }
        },
        /**
         * Returns a new list, with the annotations with the given
         * tag cleared on the given range.
         */
        remove: function(range, tag) {
            if ( range instanceof cobaltAnnotationList ) {
                // FIXME: fill in
            } else {
                if ( typeof range.tag != 'undefined' ) {
                    tag   = range.tag;
                    range = range.range;
                }
                var result = [];
                this.list.forEach(function(ann) {
                    if ( ann.has(tag) ) {
                        var r = ann.range.exclude(range);
                        if ( r && r.start != null ) {
                            result.push( cobalt.annotation( r, ann.tag ) );
                        }
                    } else {
                        result.push(ann);
                    }
                });
                return new cobaltAnnotationList(result);
            }
        },

        /**
         * Returns a new list with all annotations cleared on the given range.
         * Clear doesn't move the annotations, it just removes parts or all of them.
         */
        clear: function(range) {
            var result = [];
            this.list.forEach(function(ann) {
                var r = ann.range.exclude(range);
                if ( r.start != null ) {
                    result.push( cobalt.annotation( r, ann.tag ) );
                }
            });
            return new cobaltAnnotationList(result);
        },
        /**
         * Returns a new list where all annotations have the given range deleted.
         * This means that all positions in the given range are cut from all annotations.
         * Annotations to the right of the given range will move left.
         * Annotations that are contained by the given range are removed.
         */
        delete: function(range) {
            var result = [];
            this.list.forEach(function(ann) {
                var r = ann.range.delete(range);
                if ( r.start != null ) {
                    result.push( cobalt.annotation( r, ann.tag ) );
                }
            });
            return new cobaltAnnotationList(result);
        },
        /**
         * Returns a new list with the positions in the given range inserted
         * into all annotations. Annotations to the right of the range will move right.
         */
        insert: function(range) {
            var result = [];
            this.list.forEach(function(ann) {
                result.push(cobalt.annotation( ann.insert(range), ann.tag ) );
            });
            return new cobaltAnnotationList(result);
        },
        /**
         * Returns a new list with only the annotations that matched the filter.
         */
        filter: function( f ) {
            return new cobaltAnnotationList( this.list.filter(f) );
        },
        /**
         * Returns a new list with the annotations changed by the map function.
         */
        map: function( f ) {
            return new cobaltAnnotationList( this.list.map(f) );
        },
        /**
         *
         */
        reduce: function( previousValue, f ) {
            return this.list.reduce( previousValue, f);
        },
        /**
         * Search through all annotations and return a new annotation list with
         * only those annotations that match the selector.
         */
        query: function( selector ) {
            //FIXME: design a selector syntax that makes sense for cobalt (no nesting)
            //then implement
        },
        forEach: function( f ) {
            this.list.forEach(f);
        },
/*        copy: function( range ) {
            return this.filter(function(a) {
                if (a.range.overlaps(range)) {
                    return a;
                }
            });
        },
*/
        has: function( range, tag ) {
            range = cobalt.range(range);
            return this.filter(function(a) {
                if (
                    ( (range.size && a.range.overlaps(range))
                       || (!range.size && range.start && a.range.overlaps(cobalt.range(range.start-1, range.start)) )
                    )
                    && a.tag==tag
                ) {
                    return true;
                }
            }).count>0;
        }
    };

    /**
     * Non public text api.
     */
    var cobaltText = {
        delete: function(text, range) {
            range.ranges.reverse().forEach(function(re) {
                text = text.slice(0, re.start) + text.slice(re.end);
            });
            return text;
        },
        copy: function(text, range) {
            var result = '';
            range.ranges.forEach(function(re) {
                result += text.slice(re.start, re.end);
            });
            return result;
        },
        insert: function(text, insert, position) {
            return text.slice(0,position) + insert + text.slice(position);
        }
    };

    /**
     * Non public annotations api.
     */
    var cobaltAnnotations = {
        delete: function(annotations, range) {
            result = [];
            annotations.forEach(function(ann) {
                ann = ann.delete(range);
                if ( ann ) {
                    result.push(ann);
                }
            });
            return new cobaltAnnotationList(result);
        },
        copy: function(annotations, range) {
            result = [];
            annotations.forEach(function(ann) {
                if ( ann.range.overlaps(range) ) {
                    result.push( cobalt.annotation( ann.range.intersect(range), ann.tag ) );
                }
            });
            return new cobaltAnnotationList(result);
        },
        insert: function(annotations, position, size ) {
            result = [];
            var insertedRange = cobalt.range(position, position+size);
            annotations.forEach(function(ann) {
                result.push( cobalt.annotation( ann.range.insert( insertedRange ), ann.tag ));
            });
            return new cobaltAnnotationList(result);
        }
    };

    /**
     * Cobalt Fragment type and public api.
     */
    function cobaltFragment( text, annotations ) {
        this.type = 'cobaltFragment';
        if ( typeof annotations == 'undefined' ) {
            var result = parseFragment(text);
            if ( result.text || result.annotations) {
                text = result.text;
                annotations = result.annotations;
            }
        }
        this.text = '' + text;
        this.annotations = new cobaltAnnotationList( annotations );
        Object.freeze(this);
    }

    function parseFragment(fragment) {
        var info = cobalt.mime.decode( fragment );
        var text = '', annotations = '';
        info.parts.forEach( function(part) {
            switch( part.headers['content-type'] ) {
                case 'text/plain' :
                    text = part.message;
                break;
                case 'text/hope' :
                    annotations = part.message;
                break;
            }
        });
        return { text: text, annotations: annotations };
    }

    cobaltFragment.prototype = {
        constructor: cobaltFragment,
        /**
         * Returns a new fragment where the given range is deleted (cut).
         */
        delete: function( range ) {
            range = cobalt.range(range);
            return new cobaltFragment(
                cobaltText.delete( this.text, range ),
                cobaltAnnotations.delete( this.annotations, range )
            );
        },
        /**
         * Returns a new fragment with only the parts that overlap the given range.
         * The new fragment adds all text slices matching the range together as one
         * single text. All annotations are moved/cut to match this new text.
         */
        copy: function( range ) {
            range = cobalt.range(range);
            return new cobaltFragment(
                cobaltText.copy( this.text, range ),
                cobaltAnnotations.copy(
                    this.annotations,
                    range.delete(
                        range.invert(this.text.length)
                    )
                )
            );
        },
        /**
         * Returns a combined new fragment with the inserted fragment text and annotations
         * inserted at the given position.
         */
        insert: function( range, fragment ) {
            range = cobalt.range(range);
            fragment = new cobaltFragment(fragment);
            result = this.delete(range);
            return new cobaltFragment(
                cobaltText.insert( result.text, fragment.text, range.start ),
                cobaltAnnotations
                .insert( result.annotations, range.start, fragment.text.length )
                .apply( cobaltAnnotations.insert( fragment.annotations, 0, range.start ) )
            );
        },
        /**
         * Returns a new range, with the given range/tag or annotation or annotationlist applied
         */
        apply: function( range, tag ) {
            range = cobalt.range(range);
            return new cobaltFragment(
                this.text,
                this.annotations.apply( range, tag )
            );
        },
        /**
         * Returns a new range, with the given range/tag or annotation or annotationlist removed
         */
        remove: function( range, tag ) {
            range = cobalt.range(range);
            return new cobaltFragment(
                this.text,
                this.annotations.remove( range, tag )
            );
        },
        /**
         * Search through the text using a regular expression or string. Returns a range object
         * encompassing all matches.
         */
        search: function( searchRe ) {
            function escapeRegExp(str) {
                return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
            }
            if ( typeof(searchRe) === 'string' ) {
                searchRe = new RegExp(escapeRegExp(searchRe), 'g');
            }
            var result = [];
            if (this.text.length) {
                var match, lastIndex;
                while (match = searchRe.exec(this.text)) {
                    result.push([match.index, searchRe.lastIndex]);
                    if (lastIndex==searchRe.lastIndex) {
                        searchRe.lastIndex++;
                    }
                    lastIndex = searchRe.lastIndex;
                }
            }
            return cobalt.range(result);
        },
        /**
         * Search through the annotations using a query language approximately like css selectors.
         * Returns a range object encompassing all matched ranges.
         */
        query: function( selector ) {
            //FIXME: design a selector syntax that makes sense for cobalt (no nesting)
            //then implement
        },
        /*
         * Returns a mime encoded string with the text and annotations.
         */
        toString: function() {
            return cobalt.mime.encode( [
                'Content-type: text/plain\n\n' + this.text,
                'Content-type: text/cobalt\n\n' + this.annotations
            ]);
        }
    };

    if ( cobalt.implements( text, 'cobaltFragment' ) ) {
        // because cobaltFragment is immutable, we can just return it.
        return text;
    } else {
        return new cobaltFragment(text, annotations);
    }
};<|endoftext|>


<commit_before>// @flow
import * as React from 'react'
import {
	TouchableOpacity,
	TouchableHighlight,
	TouchableNativeFeedback,
	Platform,
	View,
} from 'react-native'

type PropsType = {
	highlight?: boolean,
	onPress?: () => any,
	children?: any,
	borderless?: boolean,
	style?: any,
	containerStyle?: any,
}
export const Touchable = ({
	children,
	style,
	highlight = true,
	borderless = false,
	onPress = () => {},
	containerStyle,
	...props
}: PropsType) => {
	// The child <View> is required; the Touchable needs a View as its direct child.
	const content = <View style={style}>{children}</View>

	switch (Platform.OS) {
		default:
		case 'ios': {
			const Component = highlight ? TouchableHighlight : TouchableOpacity
			const innerProps = highlight
				? {underlayColor: '#d9d9d9'}
				: {activeOpacity: 0.65}
			return (
				<Component
					onPress={onPress}
					{...innerProps}
					style={containerStyle}
					{...props}
				>
					{content}
				</Component>
			)
		}
		case 'android': {
			const canBorderless = Platform.Version >= 21
			const background =
				borderless && canBorderless
					? TouchableNativeFeedback.SelectableBackgroundBorderless()
					: TouchableNativeFeedback.SelectableBackground()
			return (
				<TouchableNativeFeedback
					background={background}
					onPress={onPress}
					style={containerStyle}
					{...props}
				>
					{content}
				</TouchableNativeFeedback>
			)
		}
	}
}
<commit_msg>alphabetize props in Touchable and add a whitelist<commit_after>// @flow

import * as React from 'react'
import {
	TouchableOpacity,
	TouchableHighlight,
	TouchableNativeFeedback,
	Platform,
	View,
} from 'react-native'

type Props = {|
	accessibilityComponentType?: string,
	accessibilityLabel?: string,
	accessibilityTraits?: string,
	activeOpacity?: number,
	borderless?: boolean,
	children?: React.Element<*>,
	containerStyle?: any,
	highlight?: boolean,
	onPress?: () => any,
	style?: any,
	underlayColor?: string,
|}

export const Touchable = ({
	borderless = false,
	children,
	containerStyle,
	highlight = true,
	onPress = () => {},
	style,
	...props
}: Props) => {
	// The child <View> is required; the Touchable needs a View as its direct child.
	const content = <View style={style}>{children}</View>

	switch (Platform.OS) {
		default:
		case 'ios': {
			const Component = highlight ? TouchableHighlight : TouchableOpacity
			const innerProps = highlight
				? {underlayColor: '#d9d9d9'}
				: {activeOpacity: 0.65}

			return (
				<Component
					onPress={onPress}
					{...innerProps}
					style={containerStyle}
					{...props}
				>
					{content}
				</Component>
			)
		}

		case 'android': {
			const canBorderless = Platform.Version >= 21
			const background =
				borderless && canBorderless
					? TouchableNativeFeedback.SelectableBackgroundBorderless()
					: TouchableNativeFeedback.SelectableBackground()

			return (
				<TouchableNativeFeedback
					background={background}
					onPress={onPress}
					style={containerStyle}
					{...props}
				>
					{content}
				</TouchableNativeFeedback>
			)
		}
	}
}
<|endoftext|>


<commit_before>(function (controllers, undefined) {

    /**
     * @ngdoc controller
     * @name Merchello.Editors.Customer.ViewController
     * @function
     *
     * @description
     * The controller for the Customer view page
     */
    controllers.CustomerViewController = function($scope, $routeParams, dialogService, merchelloCustomerService, merchelloGravatarService, merchelloInvoiceService, merchelloSettingsService, notificationsService) {

        /**
         * @ngdoc method
         * @name getDefaultAddresses
         * @function
         *
         * @description
         * Load the default addresses from the customer's list of addresses.
         */
        $scope.getDefaultAddresses = function() {
            var addresses = $scope.customer.addresses;
            $scope.billingAddresses = [];
            var haveBillingDefault = false;
            var haveShippingDefault = false;
            $scope.shippingAddresses = [];
            if (addresses.length > 0) {
                _.each(addresses, function(address) {
                    if (address.addressType.toLowerCase() === 'billing') {
                        if (address.isDefault) {
                            $scope.defaultBillingAddress = new merchello.Models.CustomerAddress(address);
                            haveBillingDefault = true;
                        }
                        $scope.billingAddresses.push(new merchello.Models.CustomerAddress(address));
                    } else {
                        if (address.isDefault) {
                            $scope.defaultShippingAddress = new merchello.Models.CustomerAddress(address);
                            haveShippingDefault = true;
                        }
                        $scope.shippingAddresses.push(new merchello.Models.CustomerAddress(address));
                    }
                });
            }
            if (!haveBillingDefault && $scope.billingAddresses.length > 0) {
                $scope.defaultBillingAddress = $scope.billingAddresses[0];
            }
            if (!haveShippingDefault && $scope.shippingAddresses.length > 0) {
                $scope.defaultShippingAddress = $scope.shippingAddresses[0];
            }
        };

        /**
         * @ngdoc method
         * @name init
         * @function
         *
         * @description
         * Inititalizes the scope.
         */
        $scope.init = function() {
            $scope.setVariables();
            $scope.loadCountries();
            $scope.loadCustomer();
        };

        /**
         * @ngdoc method
         * @name loadCountries
         * @function
         *
         * @description
         * Load a list of countries and provinces from the API.
         */
        $scope.loadCountries = function () {
            $scope.countries = [];
            var promiseCountries = merchelloSettingsService.getAllCountries();
            promiseCountries.then(function (countriesResponse) {
                for (var i = 0; i < countriesResponse.length; i++) {
                    var country = countriesResponse[i];
                    var newCountry = {
                        id: i,
                        countryCode: country.countryCode,
                        name: country.name,
                        provinces: country.provinces,
                        provinceLabel: country.provinceLabel
                    };
                    $scope.countries.push(newCountry);
                }
                $scope.countries.sort($scope.sortCountries);
                $scope.countries.unshift({ id: -1, name: 'Select Country', countryCode: '00', provinces: {}, provinceLabel: '' });
            });
        };

        /**
         * @ngdoc method
         * @name loadCustomer
         * @function
         *
         * @description
         * Load the customer information if needed.
         */
        $scope.loadCustomer = function() {
            if ($routeParams.id !== "keygoeshere") {
                $scope.customerKey = $routeParams.id;
                var promiseLoadCustomer = merchelloCustomerService.GetCustomer($scope.customerKey);
                promiseLoadCustomer.then(function(customerResponse) {
                    $scope.customer = new merchello.Models.Customer(customerResponse);
                    $scope.avatarUrl = merchelloGravatarService.avatarUrl($scope.customer.email);
                    $scope.getDefaultAddresses();
                    $scope.loadInvoices();
                    $scope.loaded = true;
                }, function(reason) {
                    notificationsService.error("Failed to load customer", reason.message);
                });
            }
        };

        /**
         * @ngdoc method
         * @name loadCustomer
         * @function
         *
         * @description
         * Load the invoices for the customer.
         */
        $scope.loadInvoices = function() {
            //var promiseInvoices = merchelloInvoiceService.getByCustomerKey($scope.customer.key);
            //promiseInvoices.then(function(invoicesResponse) {
            //    $scope.invoices = _.map(invoicesResponse.items, function (invoice) {
            //        return new merchello.Models.Invoice(invoice);
            //    });
            //    for (var i = 0; i < invoicesResponse.length; i++) {
            //        $scope.invoiceTotal += (invoicesResponse[i].total * 1);
            //    }

            //});
            $scope.invoices = $scope.customer.invoices;
            for (var i = 0; i < $scope.invoices.length; i++) {
                $scope.invoiceTotal += $scope.invoices[i].total * 1;
            }
        };

        /**
        * @ngdoc method
        * @name openAddressEditDialog
        * @function
        *
        * @description
        * Opens the edit address dialog via the Umbraco dialogService.
        */
        $scope.openAddressEditDialog = function(type) {
            var dialogData = {};
            dialogData.addressToReturn = new merchello.Models.CustomerAddress();
            dialogData.countries = $scope.countries;
            dialogData.shouldDelete = false;
            $scope.currentAddress = new merchello.Models.CustomerAddress();
            dialogData.addressType = type;
            if (type == 'billing') {
                dialogData.addresses = _.map($scope.billingAddresses, function (billingAddress) {
                    return new merchello.Models.CustomerAddress(billingAddress);
                });
            } else {
                dialogData.addresses = _.map($scope.shippingAddresses, function (shippingAddress) {
                    return new merchello.Models.CustomerAddress(shippingAddress);
                });
            }
            var aliases = [];
            for (var i = 0; i < dialogData.addresses.length; i++) {
                var address = dialogData.addresses[i];
                var alias = address.label;
                aliases.push({ id: i, name: alias });
            };
            aliases.unshift({ id: -1, name: 'New Address' });
            dialogData.addressAliases = aliases;
            dialogData.addresses.unshift(new merchello.Models.CustomerAddress());
            dialogData.type = [
                { id: 0, name: 'Billing' },
                { id: 1, name: 'Shipping' }
            ];
            dialogData.filters = {
                address: dialogData.addressAliases[0],
                country: $scope.countries[0],
                province: {},
                type: {}
            };
            if (type === 'billing') {
                dialogData.filters.type = dialogData.type[0];
            } else {
                dialogData.filters.type = dialogData.type[1];
            }
            dialogService.open({
                template: '/App_Plugins/Merchello/Modules/Customer/Dialogs/customer.editaddress.html',
                show: true,
                callback: $scope.processEditAddressDialog,
                dialogData: dialogData
            });
        };

        /**
        * @ngdoc method
        * @name openDeleteCustomerDialog
        * @function
        *
        * @description
        * Opens the delete customer dialog via the Umbraco dialogService.
        */
        $scope.openDeleteCustomerDialog = function () {
            var dialogData = {};
            dialogData.name = $scope.customer.firstName + ' ' + $scope.customer.lastName;
            dialogService.open({
                template: '/App_Plugins/Merchello/Common/Js/Dialogs/deleteconfirmation.html',
                show: true,
                callback: $scope.processDeleteCustomerDialog,
                dialogData: dialogData
            });
        };

        /**
        * @ngdoc method
        * @name openEditInfoDialog
        * @function
        *
        * @description
        * Opens the edit customer info dialog via the Umbraco dialogService.
        */
        $scope.openEditInfoDialog = function() {
            var dialogData = {
                firstName: $scope.customer.firstName,
                lastName: $scope.customer.lastName,
                email: $scope.customer.email
            };
            dialogService.open({
                template: '/App_Plugins/Merchello/Modules/Customer/Dialogs/customer.editinfo.html',
                show: true,
                callback: $scope.processEditInfoDialog,
                dialogData: dialogData
            });
        };

        /**
        * @ngdoc method
        * @name prepareAddressesForSave
        * @function
        *
        * @description
        * Prepare a list of addresses to save with the customer
        */
        $scope.prepareAddressesForSave = function () {
            var addresses = [];
            _.each($scope.billingAddresses, function(address) {
                addresses.push(new merchello.Models.CustomerAddress(address));
            });
            _.each($scope.shippingAddresses, function(address) {
                addresses.push(new merchello.Models.CustomerAddress(address));
            });
            return addresses;
        };

        /**
        * @ngdoc method
        * @name processEditAddressDialog
        * @function
        *
        * @description
        * Edit an address and update the associated lists.
        */
        $scope.processEditAddressDialog = function (data) {
            var addresses = data.addresses;
            //  Filter out an address if it's marked to be deleted.
            if (data.shouldDelete) {
                addresses = _.reject(addresses, function(address) {
                    return address.key == data.addressToReturn.key;
                });
            }
            // Insert the applicable customer, billing, and shipping keys and types into new addresses.
            _.each(addresses, function(address) {
                address.customerKey = $scope.customer.key;
                if (data.addressType.toLowerCase() === 'billing') {
                    address.addressType = 'Billing';
                } else {
                    address.addressType = 'Shipping';
                }
            });
            // Update the appropriate address list.
            if (data.addressType.toLowerCase() === 'billing') {
                $scope.billingAddresses = _.map(addresses, function(address) {
                    return new merchello.Models.CustomerAddress(address);
                });
            } else {
                $scope.shippingAddresses = _.map(addresses, function (address) {
                    return new merchello.Models.CustomerAddress(address);
                });
            }
            notificationsService.info("Preparing addresses for updating...", "");
            // Combine the address lists and update the customer.
            $scope.customer.addresses = $scope.prepareAddressesForSave();
            $scope.saveCustomer();
        };

        /**
         * @ngdoc method
         * @name processDeleteCustomerDialog
         * @function
         *
         * @description
         * Delete a customer.
         */
        $scope.processDeleteCustomerDialog = function (dialogData) {
            notificationsService.info("Deleting " + $scope.customer.firstName + " " + $scope.customer.lastName, "");
            var promiseDeleteCustomer = merchelloCustomerService.DeleteCustomer($scope.customer.key);
            promiseDeleteCustomer.then(function() {
                notificationsService.success("Customer deleted.", "");
                window.location.hash = "#/merchello/merchello/CustomerList/manage";
            }, function(reason) {
                notificationsService.error("Customer Deletion Failed", reason.message);
            });
        };

        /**
         * @ngdoc method
         * @name processEditInfoDialog
         * @function
         *
         * @description
         * Update the customer info and save.
         */
        $scope.processEditInfoDialog = function (data) {
            $scope.customer.firstName = data.firstName;
            $scope.customer.lastName = data.lastName;
            $scope.customer.email = data.email;
            $scope.saveCustomer();
        };

        /**
         * @ngdoc method
         * @name saveCustoemr
         * @function
         *
         * @description
         * Save the customer with the new note.
         */
        $scope.saveCustomer = function() {
            notificationsService.info("Saving...", "");
            var promiseSaveCustomer = merchelloCustomerService.SaveCustomer($scope.customer);
            promiseSaveCustomer.then(function(customerResponse) {
                $scope.customer = new merchello.Models.Customer(customerResponse);
                notificationsService.success("Customer Saved", "");
                $scope.getDefaultAddresses();
            }, function(reason) {
                notificationsService.error("Customer  Failed", reason.message);
            });
        };

        /**
         * @ngdoc method
         * @name setVariables
         * @function
         *
         * @description
         * Set the $scope variables.
         */
        $scope.setVariables = function () {
            $scope.avatarUrl = "";
            $scope.billingAddresses = [];
            $scope.countries = [];
            $scope.customer = new merchello.Models.Customer();
            $scope.customerKey = false;
            $scope.currentAddress = {};
            $scope.defaultBillingAddress = false;
            $scope.defaultShippingAddress = false;
            $scope.filters = {
                province: {}
            };
            $scope.invoices = [];
            $scope.invoiceTotal = 0;
            $scope.loaded = false;
            $scope.shippingAddresses = [];
        };

        $scope.init();

    };

    angular.module("umbraco").controller("Merchello.Editors.Customer.ViewController", ['$scope', '$routeParams', 'dialogService', 'merchelloCustomerService', 'merchelloGravatarService', 'merchelloInvoiceService', 'merchelloSettingsService', 'notificationsService', merchello.Controllers.CustomerViewController]);

}(window.merchello.Controllers = window.merchello.Controllers || {}));
<commit_msg>m-545<commit_after>(function (controllers, undefined) {

    /**
     * @ngdoc controller
     * @name Merchello.Editors.Customer.ViewController
     * @function
     *
     * @description
     * The controller for the Customer view page
     */
    controllers.CustomerViewController = function ($scope, $routeParams, dialogService, assetsService, merchelloCustomerService, merchelloGravatarService, merchelloInvoiceService, merchelloSettingsService, notificationsService) {

        assetsService.loadCss("/App_Plugins/Merchello/Common/Css/merchello.css");

        /**
         * @ngdoc method
         * @name getDefaultAddresses
         * @function
         *
         * @description
         * Load the default addresses from the customer's list of addresses.
         */
        $scope.getDefaultAddresses = function() {
            var addresses = $scope.customer.addresses;
            $scope.billingAddresses = [];
            var haveBillingDefault = false;
            var haveShippingDefault = false;
            $scope.shippingAddresses = [];
            if (addresses.length > 0) {
                _.each(addresses, function(address) {
                    if (address.addressType.toLowerCase() === 'billing') {
                        if (address.isDefault) {
                            $scope.defaultBillingAddress = new merchello.Models.CustomerAddress(address);
                            haveBillingDefault = true;
                        }
                        $scope.billingAddresses.push(new merchello.Models.CustomerAddress(address));
                    } else {
                        if (address.isDefault) {
                            $scope.defaultShippingAddress = new merchello.Models.CustomerAddress(address);
                            haveShippingDefault = true;
                        }
                        $scope.shippingAddresses.push(new merchello.Models.CustomerAddress(address));
                    }
                });
            }
            if (!haveBillingDefault && $scope.billingAddresses.length > 0) {
                $scope.defaultBillingAddress = $scope.billingAddresses[0];
            }
            if (!haveShippingDefault && $scope.shippingAddresses.length > 0) {
                $scope.defaultShippingAddress = $scope.shippingAddresses[0];
            }
        };

        /**
         * @ngdoc method
         * @name init
         * @function
         *
         * @description
         * Inititalizes the scope.
         */
        $scope.init = function() {
            $scope.setVariables();
            $scope.loadCountries();
            $scope.loadCustomer();
        };

        /**
         * @ngdoc method
         * @name loadCountries
         * @function
         *
         * @description
         * Load a list of countries and provinces from the API.
         */
        $scope.loadCountries = function () {
            $scope.countries = [];
            var promiseCountries = merchelloSettingsService.getAllCountries();
            promiseCountries.then(function (countriesResponse) {
                for (var i = 0; i < countriesResponse.length; i++) {
                    var country = countriesResponse[i];
                    var newCountry = {
                        id: i,
                        countryCode: country.countryCode,
                        name: country.name,
                        provinces: country.provinces,
                        provinceLabel: country.provinceLabel
                    };
                    $scope.countries.push(newCountry);
                }
                $scope.countries.sort($scope.sortCountries);
                $scope.countries.unshift({ id: -1, name: 'Select Country', countryCode: '00', provinces: {}, provinceLabel: '' });
            });
        };

        /**
         * @ngdoc method
         * @name loadCustomer
         * @function
         *
         * @description
         * Load the customer information if needed.
         */
        $scope.loadCustomer = function() {
            if ($routeParams.id !== "keygoeshere") {
                $scope.customerKey = $routeParams.id;
                var promiseLoadCustomer = merchelloCustomerService.GetCustomer($scope.customerKey);
                promiseLoadCustomer.then(function(customerResponse) {
                    $scope.customer = new merchello.Models.Customer(customerResponse);
                    $scope.avatarUrl = merchelloGravatarService.avatarUrl($scope.customer.email);
                    $scope.getDefaultAddresses();
                    $scope.loadInvoices();
                    $scope.loaded = true;
                }, function(reason) {
                    notificationsService.error("Failed to load customer", reason.message);
                });
            }
        };

        /**
         * @ngdoc method
         * @name loadCustomer
         * @function
         *
         * @description
         * Load the invoices for the customer.
         */
        $scope.loadInvoices = function() {
            //var promiseInvoices = merchelloInvoiceService.getByCustomerKey($scope.customer.key);
            //promiseInvoices.then(function(invoicesResponse) {
            //    $scope.invoices = _.map(invoicesResponse.items, function (invoice) {
            //        return new merchello.Models.Invoice(invoice);
            //    });
            //    for (var i = 0; i < invoicesResponse.length; i++) {
            //        $scope.invoiceTotal += (invoicesResponse[i].total * 1);
            //    }

            //});
            $scope.invoices = $scope.customer.invoices;
            for (var i = 0; i < $scope.invoices.length; i++) {
                $scope.invoiceTotal += $scope.invoices[i].total * 1;
            }
        };

        /**
        * @ngdoc method
        * @name openAddressEditDialog
        * @function
        *
        * @description
        * Opens the edit address dialog via the Umbraco dialogService.
        */
        $scope.openAddressEditDialog = function(type) {
            var dialogData = {};
            dialogData.addressToReturn = new merchello.Models.CustomerAddress();
            dialogData.countries = $scope.countries;
            dialogData.shouldDelete = false;
            $scope.currentAddress = new merchello.Models.CustomerAddress();
            dialogData.addressType = type;
            if (type == 'billing') {
                dialogData.addresses = _.map($scope.billingAddresses, function (billingAddress) {
                    return new merchello.Models.CustomerAddress(billingAddress);
                });
            } else {
                dialogData.addresses = _.map($scope.shippingAddresses, function (shippingAddress) {
                    return new merchello.Models.CustomerAddress(shippingAddress);
                });
            }
            var aliases = [];
            for (var i = 0; i < dialogData.addresses.length; i++) {
                var address = dialogData.addresses[i];
                var alias = address.label;
                aliases.push({ id: i, name: alias });
            };
            aliases.unshift({ id: -1, name: 'New Address' });
            dialogData.addressAliases = aliases;
            dialogData.addresses.unshift(new merchello.Models.CustomerAddress());
            dialogData.type = [
                { id: 0, name: 'Billing' },
                { id: 1, name: 'Shipping' }
            ];
            dialogData.filters = {
                address: dialogData.addressAliases[0],
                country: $scope.countries[0],
                province: {},
                type: {}
            };
            if (type === 'billing') {
                dialogData.filters.type = dialogData.type[0];
            } else {
                dialogData.filters.type = dialogData.type[1];
            }
            dialogService.open({
                template: '/App_Plugins/Merchello/Modules/Customer/Dialogs/customer.editaddress.html',
                show: true,
                callback: $scope.processEditAddressDialog,
                dialogData: dialogData
            });
        };

        /**
        * @ngdoc method
        * @name openDeleteCustomerDialog
        * @function
        *
        * @description
        * Opens the delete customer dialog via the Umbraco dialogService.
        */
        $scope.openDeleteCustomerDialog = function () {
            var dialogData = {};
            dialogData.name = $scope.customer.firstName + ' ' + $scope.customer.lastName;
            dialogService.open({
                template: '/App_Plugins/Merchello/Common/Js/Dialogs/deleteconfirmation.html',
                show: true,
                callback: $scope.processDeleteCustomerDialog,
                dialogData: dialogData
            });
        };

        /**
        * @ngdoc method
        * @name openEditInfoDialog
        * @function
        *
        * @description
        * Opens the edit customer info dialog via the Umbraco dialogService.
        */
        $scope.openEditInfoDialog = function() {
            var dialogData = {
                firstName: $scope.customer.firstName,
                lastName: $scope.customer.lastName,
                email: $scope.customer.email
            };
            dialogService.open({
                template: '/App_Plugins/Merchello/Modules/Customer/Dialogs/customer.editinfo.html',
                show: true,
                callback: $scope.processEditInfoDialog,
                dialogData: dialogData
            });
        };

        /**
        * @ngdoc method
        * @name prepareAddressesForSave
        * @function
        *
        * @description
        * Prepare a list of addresses to save with the customer
        */
        $scope.prepareAddressesForSave = function () {
            var addresses = [];
            _.each($scope.billingAddresses, function(address) {
                addresses.push(new merchello.Models.CustomerAddress(address));
            });
            _.each($scope.shippingAddresses, function(address) {
                addresses.push(new merchello.Models.CustomerAddress(address));
            });
            return addresses;
        };

        /**
        * @ngdoc method
        * @name processEditAddressDialog
        * @function
        *
        * @description
        * Edit an address and update the associated lists.
        */
        $scope.processEditAddressDialog = function (data) {
            var addresses = data.addresses;
            //  Filter out an address if it's marked to be deleted.
            if (data.shouldDelete) {
                addresses = _.reject(addresses, function(address) {
                    return address.key == data.addressToReturn.key;
                });
            }
            // Insert the applicable customer, billing, and shipping keys and types into new addresses.
            _.each(addresses, function(address) {
                address.customerKey = $scope.customer.key;
                if (data.addressType.toLowerCase() === 'billing') {
                    address.addressType = 'Billing';
                } else {
                    address.addressType = 'Shipping';
                }
            });
            // Update the appropriate address list.
            if (data.addressType.toLowerCase() === 'billing') {
                $scope.billingAddresses = _.map(addresses, function(address) {
                    return new merchello.Models.CustomerAddress(address);
                });
            } else {
                $scope.shippingAddresses = _.map(addresses, function (address) {
                    return new merchello.Models.CustomerAddress(address);
                });
            }
            notificationsService.info("Preparing addresses for updating...", "");
            // Combine the address lists and update the customer.
            $scope.customer.addresses = $scope.prepareAddressesForSave();
            $scope.saveCustomer();
        };

        /**
         * @ngdoc method
         * @name processDeleteCustomerDialog
         * @function
         *
         * @description
         * Delete a customer.
         */
        $scope.processDeleteCustomerDialog = function (dialogData) {
            notificationsService.info("Deleting " + $scope.customer.firstName + " " + $scope.customer.lastName, "");
            var promiseDeleteCustomer = merchelloCustomerService.DeleteCustomer($scope.customer.key);
            promiseDeleteCustomer.then(function() {
                notificationsService.success("Customer deleted.", "");
                window.location.hash = "#/merchello/merchello/CustomerList/manage";
            }, function(reason) {
                notificationsService.error("Customer Deletion Failed", reason.message);
            });
        };

        /**
         * @ngdoc method
         * @name processEditInfoDialog
         * @function
         *
         * @description
         * Update the customer info and save.
         */
        $scope.processEditInfoDialog = function (data) {
            $scope.customer.firstName = data.firstName;
            $scope.customer.lastName = data.lastName;
            $scope.customer.email = data.email;
            $scope.saveCustomer();
        };

        /**
         * @ngdoc method
         * @name saveCustoemr
         * @function
         *
         * @description
         * Save the customer with the new note.
         */
        $scope.saveCustomer = function() {
            notificationsService.info("Saving...", "");
            var promiseSaveCustomer = merchelloCustomerService.SaveCustomer($scope.customer);
            promiseSaveCustomer.then(function(customerResponse) {
                $scope.customer = new merchello.Models.Customer(customerResponse);
                notificationsService.success("Customer Saved", "");
                $scope.getDefaultAddresses();
            }, function(reason) {
                notificationsService.error("Customer  Failed", reason.message);
            });
        };

        /**
         * @ngdoc method
         * @name setVariables
         * @function
         *
         * @description
         * Set the $scope variables.
         */
        $scope.setVariables = function () {
            $scope.avatarUrl = "";
            $scope.billingAddresses = [];
            $scope.countries = [];
            $scope.customer = new merchello.Models.Customer();
            $scope.customerKey = false;
            $scope.currentAddress = {};
            $scope.defaultBillingAddress = false;
            $scope.defaultShippingAddress = false;
            $scope.filters = {
                province: {}
            };
            $scope.invoices = [];
            $scope.invoiceTotal = 0;
            $scope.loaded = false;
            $scope.shippingAddresses = [];
        };

        $scope.init();

    };

    angular.module("umbraco").controller("Merchello.Editors.Customer.ViewController", ['$scope', '$routeParams', 'dialogService', 'assetsService', 'merchelloCustomerService', 'merchelloGravatarService', 'merchelloInvoiceService', 'merchelloSettingsService', 'notificationsService', merchello.Controllers.CustomerViewController]);

}(window.merchello.Controllers = window.merchello.Controllers || {}));
<|endoftext|>


<commit_before>/**
 * Stampit
 **
 * Create objects from reusable, composable behaviors.
 **
 * Copyright (c) 2013 Eric Elliott
 * http://opensource.org/licenses/MIT
 **/
'use strict';
var forEach = require('lodash/collection/forEach');
var map = require('lodash/collection/map');
var forOwn = require('lodash/object/forOwn');
var deepClone = require('lodash/lang/cloneDeep');
var isFunction = require('lodash/lang/isFunction');
var isArray = Array.isArray;
var isObject = require('lodash/lang/isObject');
var create = Object.create;
var slice = require('lodash/array/slice');

var mixer = require('./mixer');

// Avoiding JSHist W003 violations.
var stampit;

function extractFunctions(arg) {
  if (isFunction(arg)) {
    return map(slice(arguments), function (fn) {
      if (isFunction(fn)) {
        return fn;
      }
    });
  } else if (isObject(arg)) {
    var arr = [];
    forEach(slice(arguments), function (obj) {
      forOwn(obj, function (fn) {
        arr.push(fn);
      });
    });
    return arr;
  } else if (isArray(arg)) {
    return slice(arg);
  }
  return [];
}

function addMethods(fixed, methods) {
  var args = [fixed.methods].concat(methods);
  mixer.mixinFunctions.apply(null, args);
  return fixed.methods;
}
function addRefs(fixed, refs) {
  var args = [fixed.refs || fixed.state].concat(refs);
  fixed.refs = fixed.state = mixer.mixin.apply(null, args);
  return fixed.refs;
}
function addInit(fixed, inits) {
  inits = isArray(inits) ? extractFunctions.apply(null, inits) : extractFunctions(inits);
  fixed.init = fixed.enclose = (fixed.init || fixed.enclose).concat(inits);
  return fixed.init;
}
function addProps(fixed, propses) {
  var args = [fixed.props].concat(propses);
  fixed.props = mixer.merge.apply(null, args);
  return fixed.props;
}

function cloneAndExtend(fixed, extensionFunction, args) {
  args = arguments.length > 3 ? slice(arguments, 2, arguments.length) : args;
  // We might end up having two different stampit modules loaded and used in conjunction.
  // These || operators ensure that old stamps could be combined with the current version stamps.
  var stamp = stampit(fixed);
  extensionFunction(stamp.fixed, args);
  return stamp;
}

function compose(factories) {
  factories = isArray(factories) ? factories : slice(arguments);
  var result = stampit();
  forEach(factories, function (source) {
    if (source && source.fixed) {
      addMethods(result.fixed, source.fixed.methods);
      addRefs(result.fixed, source.fixed.refs || source.fixed.state); // 'state' is the old name for 'refs'
      addInit(result.fixed, source.fixed.init || source.fixed.enclose); // 'enclose' is the old name for 'init'
      addProps(result.fixed, source.fixed.props);
    }
  });
  return result;
}

/**
 * Return a factory function that will produce new objects using the
 * prototypes that are passed in or composed.
 *
 * @param  {Object} [options] Options to build stamp from: `{ methods, refs, init, props }`
 * @return {Function} factory A factory to produce objects using the given prototypes.
 * @return {Function} factory.create Just like calling the factory function.
 * @return {Object} factory.fixed An object map containing the fixed prototypes.
 * @return {Function} factory.methods Add methods to the prototype. Chainable.
 * @return {Function} factory.refs Add references to the prototype. Chainable.
 * @return {Function} factory.state Alias to refs(). Deprecated.
 * @return {Function} factory.init Add a closure which called on object instantiation. Chainable.
 * @return {Function} factory.enclose Alias to init(). Deprecated.
 * @return {Function} factory.props Add deeply cloned properties to the produced objects. Chainable.
 */
stampit = function stampit(options) {
  var fixed = {methods: {}, refs: {}, init: [], props: {}};
  fixed.state = fixed.refs; // Backward compatibility. 'state' is the old name for 'refs'.
  fixed.enclose = fixed.init; // Backward compatibility. 'enclose' is the old name for 'init'.
  if (options) {
    addMethods(fixed, options.methods);
    addRefs(fixed, options.refs);
    addInit(fixed, options.init);
    addProps(fixed, options.props);
  }

  var factory = function Factory(properties, args) {
    properties = properties ? mixer.merge({}, fixed.props, properties) : deepClone(fixed.props);
    var instance = mixer.mixin(create(fixed.methods), fixed.refs, properties); // props are taking over refs

    if (fixed.init.length > 0) {
      args = slice(arguments, 1, arguments.length);
      forEach(fixed.init, function (fn) {
        if (isFunction(fn)) {
          instance = fn.call(instance, { args: args, instance: instance, stamp: factory }) || instance;
        }
      });
    }

    return instance;
  };

  var refsMethod = cloneAndExtend.bind(null, fixed, addRefs);
  var initMethod = cloneAndExtend.bind(null, fixed, addInit);
  return mixer.mixin(factory, {
    create: factory,
    fixed: fixed,

    /**
     * Take n objects and add them to the methods prototype.
     * @return {Object} stamp  The factory in question (`this`).
     */
    methods: cloneAndExtend.bind(null, fixed, addMethods),

    /**
     * Take n objects and add them to the newly stamped objects.
     * @return {Object} stamp  The factory in question (`this`).
     */
    refs: refsMethod,

    /**
     * Alias to refs(). Deprecated.
     * @return {Object} stamp  The factory in question (`this`).
     */
    state: refsMethod,

    /**
     * Take n functions, an array of functions, or n objects and add
     * the functions to the enclose prototype.
     * @return {Object} The factory in question (`this`).
     */
    init: initMethod,

    /**
     * Alias to init(). Deprecated.
     * @return {Object} The factory in question (`this`).
     */
    enclose: initMethod,

    /**
     * Take n objects and add deep clone them to the instantiated object.
     * @return {Object} stamp  The factory in question (`this`).
     */
    props: cloneAndExtend.bind(null, fixed, addProps),

    /**
     * Take one or more factories produced from stampit() and
     * combine them with `this` to produce and return a new factory.
     * Combining overrides properties with last-in priority.
     * @param {[Function]|...Function} factories Stampit factories.
     * @return {Function} A new stampit factory composed from arguments.
     */
    compose: function (factories) {
      var args = isArray(factories) ? factories : slice(arguments);
      args = [this].concat(args);
      return compose(args);
    }
  });
};

// Static methods

function isStamp(obj) {
  return (
  isFunction(obj) &&
  isFunction(obj.methods) &&
  // isStamp can be called for old stampit factory object. We should check old names (state and enclose) too.
  (isFunction(obj.refs) || isFunction(obj.state)) &&
  (isFunction(obj.init) || isFunction(obj.enclose)) &&
  isFunction(obj.props) &&
  isObject(obj.fixed)
  );
}

function convertConstructor(Constructor) {
  var stamp = stampit();
  mixer.mixinChainFunctions(stamp.fixed.methods, Constructor.prototype);
  stamp.fixed.refs = stamp.fixed.state = mixer.mergeChainNonFunctions(stamp.fixed.refs, Constructor.prototype);
  addInit(stamp.fixed, function (opts) {
    return Constructor.apply(opts.instance, opts.args);
  });
  return stamp;
}

function shortcutMethod(extensionFunction) {
  var stamp = stampit();
  extensionFunction(stamp.fixed, slice(arguments, 1));
  return stamp;
}

module.exports = mixer.mixin(stampit, {

  methods: shortcutMethod.bind(null, addMethods),

  refs: shortcutMethod.bind(null, addRefs),

  init: shortcutMethod.bind(null, addInit),

  props: shortcutMethod.bind(null, addProps),

  /**
   * Take two or more factories produced from stampit() and
   * combine them to produce a new factory.
   * Combining overrides properties with last-in priority.
   * @param {[Function]|...Function} factories Stamps produced by stampit().
   * @return {Function} A new stampit factory composed from arguments.
   */
  compose: compose,

  /**
   * Alias for mixin
   */
  extend: mixer.mixin,
  /**
   * Alias for mixin
   */
  mixIn: mixer.mixin,
  /**
   * Alias for mixin
   */
  assign: mixer.mixin,
  /**
   * Take a destination object followed by one or more source objects,
   * and copy the source object properties to the destination object,
   * with last in priority overrides.
   * @param {Object} destination An object to copy properties to.
   * @param {...Object} source An object to copy properties from.
   * @returns {Object}
   */
  mixin: mixer.mixin,

  /**
   * Check if an object is a stamp.
   * @param {Object} obj An object to check.
   * @returns {Boolean}
   */
  isStamp: isStamp,

  /**
   * Take an old-fashioned JS constructor and return a stampit stamp
   * that you can freely compose with other stamps.
   * @param  {Function} Constructor
   * @return {Function}             A composable stampit factory
   *                                (aka stamp).
   */
  convertConstructor: convertConstructor
});
<commit_msg>Update stampit() function argument JSDoc.<commit_after>/**
 * Stampit
 **
 * Create objects from reusable, composable behaviors.
 **
 * Copyright (c) 2013 Eric Elliott
 * http://opensource.org/licenses/MIT
 **/
'use strict';
var forEach = require('lodash/collection/forEach');
var map = require('lodash/collection/map');
var forOwn = require('lodash/object/forOwn');
var deepClone = require('lodash/lang/cloneDeep');
var isFunction = require('lodash/lang/isFunction');
var isArray = Array.isArray;
var isObject = require('lodash/lang/isObject');
var create = Object.create;
var slice = require('lodash/array/slice');

var mixer = require('./mixer');

// Avoiding JSHist W003 violations.
var stampit;

function extractFunctions(arg) {
  if (isFunction(arg)) {
    return map(slice(arguments), function (fn) {
      if (isFunction(fn)) {
        return fn;
      }
    });
  } else if (isObject(arg)) {
    var arr = [];
    forEach(slice(arguments), function (obj) {
      forOwn(obj, function (fn) {
        arr.push(fn);
      });
    });
    return arr;
  } else if (isArray(arg)) {
    return slice(arg);
  }
  return [];
}

function addMethods(fixed, methods) {
  var args = [fixed.methods].concat(methods);
  mixer.mixinFunctions.apply(null, args);
  return fixed.methods;
}
function addRefs(fixed, refs) {
  var args = [fixed.refs || fixed.state].concat(refs);
  fixed.refs = fixed.state = mixer.mixin.apply(null, args);
  return fixed.refs;
}
function addInit(fixed, inits) {
  inits = isArray(inits) ? extractFunctions.apply(null, inits) : extractFunctions(inits);
  fixed.init = fixed.enclose = (fixed.init || fixed.enclose).concat(inits);
  return fixed.init;
}
function addProps(fixed, propses) {
  var args = [fixed.props].concat(propses);
  fixed.props = mixer.merge.apply(null, args);
  return fixed.props;
}

function cloneAndExtend(fixed, extensionFunction, args) {
  args = arguments.length > 3 ? slice(arguments, 2, arguments.length) : args;
  // We might end up having two different stampit modules loaded and used in conjunction.
  // These || operators ensure that old stamps could be combined with the current version stamps.
  var stamp = stampit(fixed);
  extensionFunction(stamp.fixed, args);
  return stamp;
}

function compose(factories) {
  factories = isArray(factories) ? factories : slice(arguments);
  var result = stampit();
  forEach(factories, function (source) {
    if (source && source.fixed) {
      addMethods(result.fixed, source.fixed.methods);
      addRefs(result.fixed, source.fixed.refs || source.fixed.state); // 'state' is the old name for 'refs'
      addInit(result.fixed, source.fixed.init || source.fixed.enclose); // 'enclose' is the old name for 'init'
      addProps(result.fixed, source.fixed.props);
    }
  });
  return result;
}

/**
 * Return a factory function that will produce new objects using the
 * prototypes that are passed in or composed.
 *
 * @param  {Object} [options] Options to build stamp from: `{ methods, refs, init, props }`
 * @param  {Object} [options.methods] A map of method names and bodies for delegation.
 * @param  {Object} [options.refs] A map of property names and values to be mixed into each new object.
 * @param  {Object} [options.init] A closure (function) used to create private data and privileged methods.
 * @param  {Object} [options.props] An object to be deeply cloned into each newly stamped object.
 * @return {Function} factory A factory to produce objects using the given prototypes.
 * @return {Function} factory.create Just like calling the factory function.
 * @return {Object} factory.fixed An object map containing the fixed prototypes.
 * @return {Function} factory.methods Add methods to the prototype. Chainable.
 * @return {Function} factory.refs Add references to the prototype. Chainable.
 * @return {Function} factory.init Add a closure which called on object instantiation. Chainable.
 * @return {Function} factory.props Add deeply cloned properties to the produced objects. Chainable.
 */
stampit = function stampit(options) {
  var fixed = {methods: {}, refs: {}, init: [], props: {}};
  fixed.state = fixed.refs; // Backward compatibility. 'state' is the old name for 'refs'.
  fixed.enclose = fixed.init; // Backward compatibility. 'enclose' is the old name for 'init'.
  if (options) {
    addMethods(fixed, options.methods);
    addRefs(fixed, options.refs);
    addInit(fixed, options.init);
    addProps(fixed, options.props);
  }

  var factory = function Factory(properties, args) {
    properties = properties ? mixer.merge({}, fixed.props, properties) : deepClone(fixed.props);
    var instance = mixer.mixin(create(fixed.methods), fixed.refs, properties); // props are taking over refs

    if (fixed.init.length > 0) {
      args = slice(arguments, 1, arguments.length);
      forEach(fixed.init, function (fn) {
        if (isFunction(fn)) {
          instance = fn.call(instance, { args: args, instance: instance, stamp: factory }) || instance;
        }
      });
    }

    return instance;
  };

  var refsMethod = cloneAndExtend.bind(null, fixed, addRefs);
  var initMethod = cloneAndExtend.bind(null, fixed, addInit);
  return mixer.mixin(factory, {
    create: factory,
    fixed: fixed,

    /**
     * Take n objects and add them to the methods prototype.
     * @return {Object} stamp  The factory in question (`this`).
     */
    methods: cloneAndExtend.bind(null, fixed, addMethods),

    /**
     * Take n objects and add them to the newly stamped objects.
     * @return {Object} stamp  The factory in question (`this`).
     */
    refs: refsMethod,

    /**
     * Alias to refs(). Deprecated.
     * @return {Object} stamp  The factory in question (`this`).
     */
    state: refsMethod,

    /**
     * Take n functions, an array of functions, or n objects and add
     * the functions to the enclose prototype.
     * @return {Object} The factory in question (`this`).
     */
    init: initMethod,

    /**
     * Alias to init(). Deprecated.
     * @return {Object} The factory in question (`this`).
     */
    enclose: initMethod,

    /**
     * Take n objects and add deep clone them to the instantiated object.
     * @return {Object} stamp  The factory in question (`this`).
     */
    props: cloneAndExtend.bind(null, fixed, addProps),

    /**
     * Take one or more factories produced from stampit() and
     * combine them with `this` to produce and return a new factory.
     * Combining overrides properties with last-in priority.
     * @param {[Function]|...Function} factories Stampit factories.
     * @return {Function} A new stampit factory composed from arguments.
     */
    compose: function (factories) {
      var args = isArray(factories) ? factories : slice(arguments);
      args = [this].concat(args);
      return compose(args);
    }
  });
};

// Static methods

function isStamp(obj) {
  return (
  isFunction(obj) &&
  isFunction(obj.methods) &&
  // isStamp can be called for old stampit factory object. We should check old names (state and enclose) too.
  (isFunction(obj.refs) || isFunction(obj.state)) &&
  (isFunction(obj.init) || isFunction(obj.enclose)) &&
  isFunction(obj.props) &&
  isObject(obj.fixed)
  );
}

function convertConstructor(Constructor) {
  var stamp = stampit();
  mixer.mixinChainFunctions(stamp.fixed.methods, Constructor.prototype);
  stamp.fixed.refs = stamp.fixed.state = mixer.mergeChainNonFunctions(stamp.fixed.refs, Constructor.prototype);
  addInit(stamp.fixed, function (opts) {
    return Constructor.apply(opts.instance, opts.args);
  });
  return stamp;
}

function shortcutMethod(extensionFunction) {
  var stamp = stampit();
  extensionFunction(stamp.fixed, slice(arguments, 1));
  return stamp;
}

module.exports = mixer.mixin(stampit, {

  methods: shortcutMethod.bind(null, addMethods),

  refs: shortcutMethod.bind(null, addRefs),

  init: shortcutMethod.bind(null, addInit),

  props: shortcutMethod.bind(null, addProps),

  /**
   * Take two or more factories produced from stampit() and
   * combine them to produce a new factory.
   * Combining overrides properties with last-in priority.
   * @param {[Function]|...Function} factories Stamps produced by stampit().
   * @return {Function} A new stampit factory composed from arguments.
   */
  compose: compose,

  /**
   * Alias for mixin
   */
  extend: mixer.mixin,
  /**
   * Alias for mixin
   */
  mixIn: mixer.mixin,
  /**
   * Alias for mixin
   */
  assign: mixer.mixin,
  /**
   * Take a destination object followed by one or more source objects,
   * and copy the source object properties to the destination object,
   * with last in priority overrides.
   * @param {Object} destination An object to copy properties to.
   * @param {...Object} source An object to copy properties from.
   * @returns {Object}
   */
  mixin: mixer.mixin,

  /**
   * Check if an object is a stamp.
   * @param {Object} obj An object to check.
   * @returns {Boolean}
   */
  isStamp: isStamp,

  /**
   * Take an old-fashioned JS constructor and return a stampit stamp
   * that you can freely compose with other stamps.
   * @param  {Function} Constructor
   * @return {Function}             A composable stampit factory
   *                                (aka stamp).
   */
  convertConstructor: convertConstructor
});
<|endoftext|>