<commit_before>NEWSBLUR.Views.StoryTitleView = Backbone.View.extend({

    className: 'NB-story-title-container',

    events: {
        "dblclick .NB-story-title"      : "open_story_in_story_view",
        "click .NB-story-title"         : "select_story",
        "contextmenu .NB-story-title"   : "show_manage_menu",
        "click .NB-story-manage-icon"   : "show_manage_menu",
        "click .NB-storytitles-shares"  : "select_story_shared",
        "mouseenter .NB-story-title"    : "mouseenter_manage_icon",
        "mouseleave .NB-story-title"    : "mouseleave_manage_icon"
    },

    initialize: function() {
        this.model.bind('change', this.toggle_classes, this);
        this.model.bind('change:read_status', this.toggle_read_status, this);
        this.model.bind('change:selected', this.toggle_selected, this);
        this.model.bind('change:starred', this.toggle_starred, this);
        this.model.bind('change:intelligence', this.toggle_intelligence, this);
        this.model.story_title_view = this;
    },

    render: function() {
        this.$el.html(this.template({
            story    : this.model,
            feed     : (NEWSBLUR.reader.flags.river_view || NEWSBLUR.reader.flags.social_view) &&
                        NEWSBLUR.assets.get_feed(this.model.get('story_feed_id')),
            options  : this.options
        }));
        this.$st = this.$(".NB-story-title");
        this.toggle_classes();
        this.toggle_read_status();
        this.color_feedbar();

        return this;
    },

    template: _.template('\

\

\

\

\
            <a href="<%= story.get("story_permalink") %>" class="story_title NB-hidden-fade">\
                <% if (feed) { %>\

\
                        <img class="feed_favicon" src="<%= $.favicon(feed) %>">\
                        <%= feed.get("feed_title") %>\

\
                <% } %>\

\

\
                <%= story.get("story_title") %>\
                <%= story.get("story_authors") %>\
            \
            <%= story.get("short_parsed_date") %>\
            <% if (story.get("comment_count_friends")) { %>\

\
                    <% _.each(story.get("commented_by_friends"), function(user_id) { %>\
                        <img class="NB-user-avatar" src="<%= NEWSBLUR.assets.user_profiles.find(user_id).get("photo_url") %>">\
                    <% }) %>\

\
            <% } %>\

\

\

\
    '),

    render_inline_story_detail: function() {
        if (NEWSBLUR.reader.story_view == 'text') {
            this.text_view = new NEWSBLUR.Views.TextTabView({
                el: null
            });
            this.text_view.fetch_and_render(this.model);
            this.$(".NB-story-detail").html(this.text_view.$el);
        } else {
            this.story_detail = new NEWSBLUR.Views.StoryDetailView({
                model: this.model,
                collection: this.model.collection,
                tagName: 'div',
                inline_story_title: true
            }).render();
            this.$(".NB-story-detail").html(this.story_detail.$el);
            this.story_detail.watch_images_for_story_height();
        }

        this.$st.hide();
    },

    destroy_inline_story_detail: function() {
        this.$st.show();

        if (this.story_detail) {
            this.story_detail.destroy();
        }
        if (this.text_view) {
            this.text_view.destroy();
        }
    },

    // ============
    // = Bindings =
    // ============

    color_feedbar: function() {
        var $inner = this.$st.find(".NB-storytitles-feed-border-inner");
        var $outer = this.$st.find(".NB-storytitles-feed-border-outer");
        var feed = NEWSBLUR.assets.get_feed(this.model.get('story_feed_id'));

        $inner.css('background-color', '#' + feed.get('favicon_fade'));
        $outer.css('background-color', '#' + feed.get('favicon_color'));
    },

    toggle_classes: function() {
        var changes = this.model.changedAttributes();

        if (changes && _.all(_.keys(changes), function(change) {
            return _.contains(['intelligence', 'read_status', 'selected'], change);
        })) return;

        var story = this.model;
        var unread_view = NEWSBLUR.reader.get_unread_view_score();
        var score = story.score();

        this.$st.toggleClass('NB-story-starred', !!story.get('starred'));
        this.$st.toggleClass('NB-story-shared', !!story.get('shared'));
        this.$st.removeClass('NB-story-negative NB-story-neutral NB-story-postiive')
                .addClass('NB-story-'+story.score_name(score));

        if (unread_view > score) {
            this.$st.addClass("NB-hidden");
        }

        if (NEWSBLUR.assets.preference('show_tooltips')) {
            this.$('.NB-story-sentiment').tipsy({
                delayIn: 375,
                gravity: 's'
            });
        }
    },

    toggle_intelligence: function() {
        var score = this.model.score();
        this.$st.removeClass('NB-story-negative NB-story-neutral NB-story-postiive')
                .addClass('NB-story-'+this.model.score_name(score));
    },

    toggle_read_status: function(model, read_status, options) {
        options = options || {};
        this.$st.toggleClass('read', !!this.model.get('read_status'));

        if (options.error_marking_unread) {
            var pane_alignment = NEWSBLUR.assets.preference('story_pane_anchor');
            var $star = this.$('.NB-storytitles-sentiment');

            $star.attr({'title': options.message || 'Failed to mark as unread'});
            $star.tipsy({
                gravity: pane_alignment == 'north' ? 'nw' : 'sw',
                fade: true,
                trigger: 'manual',
                offsetOpposite: -1
            });
            var tipsy = $star.data('tipsy');
            _.defer(function() {
                tipsy.enable();
                tipsy.show();
            });

            $star.animate({
                'opacity': 1
            }, {
                'duration': 1850,
                'queue': false,
                'complete': function() {
                    if (tipsy.enabled) {
                        tipsy.hide();
                        tipsy.disable();
                    }
                }
            });

        }
    },

    toggle_selected: function(model, selected, options) {
        this.$st.toggleClass('NB-selected', !!this.model.get('selected'));

        if (this.model.get('selected')) {
            if (NEWSBLUR.assets.preference('story_layout') == 'list') {
                this.render_inline_story_detail();
            }
            NEWSBLUR.app.story_titles.scroll_to_selected_story(this.model, options);
        } else {
            this.destroy_inline_story_detail();
        }
    },

    toggle_starred: function() {
        var pane_alignment = NEWSBLUR.assets.preference('story_pane_anchor');
        var $star = this.$('.NB-storytitles-star');
        NEWSBLUR.app.story_titles.scroll_to_selected_story(this.model);

        if (this.model.get('starred')) {
            $star.attr({'title': 'Saved!'});
            $star.tipsy({
                gravity: pane_alignment == 'north' ? 'nw' : 'sw',
                fade: true,
                trigger: 'manual',
                offsetOpposite: -1
            });
            var tipsy = $star.data('tipsy');
            _.defer(function() {
                tipsy.enable();
                tipsy.show();
            });

            $star.animate({
                'opacity': 1
            }, {
                'duration': 850,
                'queue': false,
                'complete': function() {
                    if (tipsy.enabled) {
                        tipsy.hide();
                        tipsy.disable();
                    }
                }
            });
        } else {
            this.$st.one('mouseout', _.bind(function() {
                this.$st.removeClass('NB-unstarred');
            }, this));
            $star.attr({'title': 'Removed'});

            $star.tipsy({
                gravity: pane_alignment == 'north' ? 'nw' : 'sw',
                fade: true,
                trigger: 'manual',
                offsetOpposite: -1
            });
            var tipsy = $star.data('tipsy');
            tipsy.enable();
            tipsy.show();

            _.delay(function() {
                if (tipsy.enabled) {
                    tipsy.hide();
                    tipsy.disable();
                }
            }, 850);

        }
    },

    // ==========
    // = Events =
    // ==========

    select_story: function(e) {
        e.preventDefault();
        e.stopPropagation();
        if (e.which == 1 && $('.NB-menu-manage-container:visible').length) return;

        this.model.set('selected', true, {'click_on_story_title': true});

        if (NEWSBLUR.hotkeys.command) {
            this.model.open_story_in_new_tab();
        }
    },

    select_story_shared: function(e) {
        e.preventDefault();
        e.stopPropagation();

        this.model.set('selected', true, {'click_on_story_title': true});
        if (NEWSBLUR.reader.story_view == 'page') {
            NEWSBLUR.reader.switch_taskbar_view('feed', {skip_save_type: 'page'});
        }

        NEWSBLUR.app.story_list.scroll_to_selected_story(this.model, {
            scroll_to_comments: true,
            scroll_offset: -50
        });
    },

    show_manage_menu: function(e) {
        e.preventDefault();
        e.stopPropagation();
        // NEWSBLUR.log(["showing manage menu", this.model.is_social() ? 'socialfeed' : 'feed', $(this.el), this]);
        NEWSBLUR.reader.show_manage_menu('story', this.$st, {
            story_id: this.model.id,
            feed_id: this.model.get('story_feed_id'),
            rightclick: e.which >= 2
        });
        return false;
    },

    mouseenter_manage_icon: function() {
        var menu_height = 270;
        if (this.$el.offset().top > $(window).height() - menu_height) {
            this.$st.addClass('NB-hover-inverse');
        }
    },

    mouseleave_manage_icon: function() {
        this.$st.removeClass('NB-hover-inverse');
    },

    open_story_in_story_view: function(e) {
        e.preventDefault();
        e.stopPropagation();
        NEWSBLUR.app.story_tab_view.open_story(this.model, true);
        return false;
    }

});<commit_msg>Fixing issue around rendering story titles with no feed.<commit_after>NEWSBLUR.Views.StoryTitleView = Backbone.View.extend({

    className: 'NB-story-title-container',

    events: {
        "dblclick .NB-story-title"      : "open_story_in_story_view",
        "click .NB-story-title"         : "select_story",
        "contextmenu .NB-story-title"   : "show_manage_menu",
        "click .NB-story-manage-icon"   : "show_manage_menu",
        "click .NB-storytitles-shares"  : "select_story_shared",
        "mouseenter .NB-story-title"    : "mouseenter_manage_icon",
        "mouseleave .NB-story-title"    : "mouseleave_manage_icon"
    },

    initialize: function() {
        this.model.bind('change', this.toggle_classes, this);
        this.model.bind('change:read_status', this.toggle_read_status, this);
        this.model.bind('change:selected', this.toggle_selected, this);
        this.model.bind('change:starred', this.toggle_starred, this);
        this.model.bind('change:intelligence', this.toggle_intelligence, this);
        this.model.story_title_view = this;
    },

    render: function() {
        this.$el.html(this.template({
            story    : this.model,
            feed     : (NEWSBLUR.reader.flags.river_view || NEWSBLUR.reader.flags.social_view) &&
                        NEWSBLUR.assets.get_feed(this.model.get('story_feed_id')),
            options  : this.options
        }));
        this.$st = this.$(".NB-story-title");
        this.toggle_classes();
        this.toggle_read_status();
        this.color_feedbar();

        return this;
    },

    template: _.template('\

\

\

\

\
            <a href="<%= story.get("story_permalink") %>" class="story_title NB-hidden-fade">\
                <% if (feed) { %>\

\
                        <img class="feed_favicon" src="<%= $.favicon(feed) %>">\
                        <%= feed.get("feed_title") %>\

\
                <% } %>\

\

\
                <%= story.get("story_title") %>\
                <%= story.get("story_authors") %>\
            \
            <%= story.get("short_parsed_date") %>\
            <% if (story.get("comment_count_friends")) { %>\

\
                    <% _.each(story.get("commented_by_friends"), function(user_id) { %>\
                        <img class="NB-user-avatar" src="<%= NEWSBLUR.assets.user_profiles.find(user_id).get("photo_url") %>">\
                    <% }) %>\

\
            <% } %>\

\

\

\
    '),

    render_inline_story_detail: function() {
        if (NEWSBLUR.reader.story_view == 'text') {
            this.text_view = new NEWSBLUR.Views.TextTabView({
                el: null
            });
            this.text_view.fetch_and_render(this.model);
            this.$(".NB-story-detail").html(this.text_view.$el);
        } else {
            this.story_detail = new NEWSBLUR.Views.StoryDetailView({
                model: this.model,
                collection: this.model.collection,
                tagName: 'div',
                inline_story_title: true
            }).render();
            this.$(".NB-story-detail").html(this.story_detail.$el);
            this.story_detail.watch_images_for_story_height();
        }

        this.$st.hide();
    },

    destroy_inline_story_detail: function() {
        this.$st.show();

        if (this.story_detail) {
            this.story_detail.destroy();
        }
        if (this.text_view) {
            this.text_view.destroy();
        }
    },

    // ============
    // = Bindings =
    // ============

    color_feedbar: function() {
        var $inner = this.$st.find(".NB-storytitles-feed-border-inner");
        var $outer = this.$st.find(".NB-storytitles-feed-border-outer");
        var feed = NEWSBLUR.assets.get_feed(this.model.get('story_feed_id'));
        if (!feed) return;

        $inner.css('background-color', '#' + feed.get('favicon_fade'));
        $outer.css('background-color', '#' + feed.get('favicon_color'));
    },

    toggle_classes: function() {
        var changes = this.model.changedAttributes();

        if (changes && _.all(_.keys(changes), function(change) {
            return _.contains(['intelligence', 'read_status', 'selected'], change);
        })) return;

        var story = this.model;
        var unread_view = NEWSBLUR.reader.get_unread_view_score();
        var score = story.score();

        this.$st.toggleClass('NB-story-starred', !!story.get('starred'));
        this.$st.toggleClass('NB-story-shared', !!story.get('shared'));
        this.$st.removeClass('NB-story-negative NB-story-neutral NB-story-postiive')
                .addClass('NB-story-'+story.score_name(score));

        if (unread_view > score) {
            this.$st.addClass("NB-hidden");
        }

        if (NEWSBLUR.assets.preference('show_tooltips')) {
            this.$('.NB-story-sentiment').tipsy({
                delayIn: 375,
                gravity: 's'
            });
        }
    },

    toggle_intelligence: function() {
        var score = this.model.score();
        this.$st.removeClass('NB-story-negative NB-story-neutral NB-story-postiive')
                .addClass('NB-story-'+this.model.score_name(score));
    },

    toggle_read_status: function(model, read_status, options) {
        options = options || {};
        this.$st.toggleClass('read', !!this.model.get('read_status'));

        if (options.error_marking_unread) {
            var pane_alignment = NEWSBLUR.assets.preference('story_pane_anchor');
            var $star = this.$('.NB-storytitles-sentiment');

            $star.attr({'title': options.message || 'Failed to mark as unread'});
            $star.tipsy({
                gravity: pane_alignment == 'north' ? 'nw' : 'sw',
                fade: true,
                trigger: 'manual',
                offsetOpposite: -1
            });
            var tipsy = $star.data('tipsy');
            _.defer(function() {
                tipsy.enable();
                tipsy.show();
            });

            $star.animate({
                'opacity': 1
            }, {
                'duration': 1850,
                'queue': false,
                'complete': function() {
                    if (tipsy.enabled) {
                        tipsy.hide();
                        tipsy.disable();
                    }
                }
            });

        }
    },

    toggle_selected: function(model, selected, options) {
        this.$st.toggleClass('NB-selected', !!this.model.get('selected'));

        if (this.model.get('selected')) {
            if (NEWSBLUR.assets.preference('story_layout') == 'list') {
                this.render_inline_story_detail();
            }
            NEWSBLUR.app.story_titles.scroll_to_selected_story(this.model, options);
        } else {
            this.destroy_inline_story_detail();
        }
    },

    toggle_starred: function() {
        var pane_alignment = NEWSBLUR.assets.preference('story_pane_anchor');
        var $star = this.$('.NB-storytitles-star');
        NEWSBLUR.app.story_titles.scroll_to_selected_story(this.model);

        if (this.model.get('starred')) {
            $star.attr({'title': 'Saved!'});
            $star.tipsy({
                gravity: pane_alignment == 'north' ? 'nw' : 'sw',
                fade: true,
                trigger: 'manual',
                offsetOpposite: -1
            });
            var tipsy = $star.data('tipsy');
            _.defer(function() {
                tipsy.enable();
                tipsy.show();
            });

            $star.animate({
                'opacity': 1
            }, {
                'duration': 850,
                'queue': false,
                'complete': function() {
                    if (tipsy.enabled) {
                        tipsy.hide();
                        tipsy.disable();
                    }
                }
            });
        } else {
            this.$st.one('mouseout', _.bind(function() {
                this.$st.removeClass('NB-unstarred');
            }, this));
            $star.attr({'title': 'Removed'});

            $star.tipsy({
                gravity: pane_alignment == 'north' ? 'nw' : 'sw',
                fade: true,
                trigger: 'manual',
                offsetOpposite: -1
            });
            var tipsy = $star.data('tipsy');
            tipsy.enable();
            tipsy.show();

            _.delay(function() {
                if (tipsy.enabled) {
                    tipsy.hide();
                    tipsy.disable();
                }
            }, 850);

        }
    },

    // ==========
    // = Events =
    // ==========

    select_story: function(e) {
        e.preventDefault();
        e.stopPropagation();
        if (e.which == 1 && $('.NB-menu-manage-container:visible').length) return;

        this.model.set('selected', true, {'click_on_story_title': true});

        if (NEWSBLUR.hotkeys.command) {
            this.model.open_story_in_new_tab();
        }
    },

    select_story_shared: function(e) {
        e.preventDefault();
        e.stopPropagation();

        this.model.set('selected', true, {'click_on_story_title': true});
        if (NEWSBLUR.reader.story_view == 'page') {
            NEWSBLUR.reader.switch_taskbar_view('feed', {skip_save_type: 'page'});
        }

        NEWSBLUR.app.story_list.scroll_to_selected_story(this.model, {
            scroll_to_comments: true,
            scroll_offset: -50
        });
    },

    show_manage_menu: function(e) {
        e.preventDefault();
        e.stopPropagation();
        // NEWSBLUR.log(["showing manage menu", this.model.is_social() ? 'socialfeed' : 'feed', $(this.el), this]);
        NEWSBLUR.reader.show_manage_menu('story', this.$st, {
            story_id: this.model.id,
            feed_id: this.model.get('story_feed_id'),
            rightclick: e.which >= 2
        });
        return false;
    },

    mouseenter_manage_icon: function() {
        var menu_height = 270;
        if (this.$el.offset().top > $(window).height() - menu_height) {
            this.$st.addClass('NB-hover-inverse');
        }
    },

    mouseleave_manage_icon: function() {
        this.$st.removeClass('NB-hover-inverse');
    },

    open_story_in_story_view: function(e) {
        e.preventDefault();
        e.stopPropagation();
        NEWSBLUR.app.story_tab_view.open_story(this.model, true);
        return false;
    }

});<|endoftext|>


<commit_before>dojo.provide("dijit.form.Form");

dojo.require("dijit._Widget");
dojo.require("dijit._Templated");

dojo.declare("dijit.form._FormMixin", null,
	{
		/*
		summary:
			Widget corresponding to <form> tag, for validation and serialization

		usage:
			<form dojoType="dijit.form.Form" id="myForm">
				Name: <input type="text" name="name" />
			</form>
			myObj={name: "John Doe"};
			dijit.byId('myForm').setValues(myObj);

			myObj=dijit.byId('myForm').getValues();

		*/

		// execute: Function
		//	User defined function to do stuff when the user hits the submit button
		execute: function(/*Object*/ formContents){},

   		templateString: "<form dojoAttachPoint='containerNode' dojoAttachEvent='onsubmit:_onSubmit' enctype='multipart/form-data'></form>",

 		_onSubmit: function(/*event*/e) {
 			// summary: callback when user hits submit button
    		dojo.stopEvent(e);
    		this.execute(this.getValues());
  		},

		submit: function() {
			// summary: programatically submit form
			this.containerNode.submit();
		},

		setValues: function(/*object*/obj) {
			// summary: fill in form values from a JSON structure

			// generate map from name --> [list of widgets with that name]
			var map = {};
			dojo.forEach(this.getDescendants(), function(widget){
				if(!widget.name){ return; }
				var entry = map[widget.name] || (map[widget.name] = [] );
				entry.push(widget);
			});

			// call setValue() or setChecked() for each widget, according to obj
			for(var name in map){
				var widgets = map[name],						// array of widgets w/this name
					values = dojo.getObject(name, false, obj);	// list of values for those widgets
				if(!dojo.isArray(values)){
					values = [ values ];
				}
				if(widgets[0].setChecked){
					// for checkbox/radio, values is a list of which widgets should be checked
					dojo.forEach(widgets, function(w, i){
						w.setChecked(dojo.indexOf(values, w.value) != -1);
					});
				}else{
					// otherwise, values is a list of values to be assigned sequentially to each widget
					dojo.forEach(widgets, function(w, i){
						w.setValue(values[i]);
					});
				}
			}

			/***
			 * 	TODO: code for plain input boxes (this shouldn't run for inputs that are part of widgets

			dojo.forEach(this.containerNode.elements, function(element){
				if (element.name == ''){return};	// like "continue"
				var namePath = element.name.split(".");
				var myObj=obj;
				var name=namePath[namePath.length-1];
				for(var j=1,len2=namePath.length;j<len2;++j) {
					var p=namePath[j - 1];
					// repeater support block
					var nameA=p.split("[");
					if (nameA.length > 1) {
						if(typeof(myObj[nameA[0]]) == "undefined") {
							myObj[nameA[0]]=[ ];
						} // if

						nameIndex=parseInt(nameA[1]);
						if(typeof(myObj[nameA[0]][nameIndex]) == "undefined") {
							myObj[nameA[0]][nameIndex]={};
						}
						myObj=myObj[nameA[0]][nameIndex];
           				continue;
  					 }  // repeater support ends

					if(typeof(myObj[p]) == "undefined") {
						myObj=undefined;
						break;
					};
					myObj=myObj[p];
				}

				if (typeof(myObj) == "undefined") {
					return;		// like "continue"
				}
				if (typeof(myObj[name]) == "undefined" && this.ignoreNullValues) {
					return;		// like "continue"
				}

				// TODO: widget values (just call setValue() on the widget)

				switch(element.type) {
					case "checkbox":
						element.checked = (name in myObj) &&
							dojo.some(myObj[name], function(val){ return val==element.value; });
						break;
					case "radio":
						element.checked = (name in myObj) && myObj[name]==element.value;
						break;
					case "select-multiple":
						element.selectedIndex=-1;
						dojo.forEach(element.options, function(option){
							option.selected = dojo.some(myObj[name], function(val){ return option.value == val; });
						});
						break;
					case "select-one":
						element.selectedIndex="0";
						dojo.forEach(element.options, function(option){
							option.selected = option.value == myObj[name];
						});
						break;
					case "hidden":
					case "text":
					case "textarea":
					case "password":
						element.value = myObj[name] || "";
						break;
				}
      		});
      		*/
		},

		getValues: function() {
			// summary: generate JSON structure from form values

			// get widget values
			var obj = {};
			dojo.forEach(this.getDescendants(), function(widget){
				var value = widget.getValue ? widget.getValue() : widget.value;
				var name = widget.name;
				if(!value || !name){ return; }
				if("checked" in widget && !widget.checked){ return; }

				// store widgets' values as an array, even if there's only value
				// (just for consistency; there might be more than one value at another time)
				var existingObj=dojo.getObject(name, false, obj);
				if(existingObj){
					existingObj.push(value);
				}else{
					dojo.setObject(name, [value], obj);
				}
			});

			/***
			 * code for plain input boxex
			var obj = { };
			dojo.forEach(this.containerNode.elements, function(elm){
				if (!elm.name)	{
					return;		// like "continue"
				}
				var namePath = elm.name.split(".");
				var myObj=obj;
				var name=namePath[namePath.length-1];
				for(var j=1,len2=namePath.length;j<len2;++j) {
					var nameIndex = null;
					var p=namePath[j - 1];
					var nameA=p.split("[");
					if (nameA.length > 1) {
						if(typeof(myObj[nameA[0]]) == "undefined") {
							myObj[nameA[0]]=[ ];
						} // if
						nameIndex=parseInt(nameA[1]);
						if(typeof(myObj[nameA[0]][nameIndex]) == "undefined") {
							myObj[nameA[0]][nameIndex]={};
						}
					} else if(typeof(myObj[nameA[0]]) == "undefined") {
						myObj[nameA[0]]={}
					} // if

					if (nameA.length == 1) {
						myObj=myObj[nameA[0]];
					} else {
						myObj=myObj[nameA[0]][nameIndex];
					} // if
				} // for

				if ((elm.type != "select-multiple" && elm.type != "checkbox" && elm.type != "radio") || (elm.type=="radio" && elm.checked)) {
					if(name == name.split("[")[0]) {
						myObj[name]=elm.value;
					} else {
						// can not set value when there is no name
					}
				} else if (elm.type == "checkbox" && elm.checked) {
					if(typeof(myObj[name]) == 'undefined') {
						myObj[name]=[ ];
					}
					myObj[name].push(elm.value);
				} else if (elm.type == "select-multiple") {
					if(typeof(myObj[name]) == 'undefined') {
						myObj[name]=[ ];
					}
					for (var jdx=0,len3=elm.options.length; jdx<len3; ++jdx) {
						if (elm.options[jdx].selected) {
							myObj[name].push(elm.options[jdx].value);
						}
					}
				} // if
				name=undefined;
			}); // forEach
			***/
			return obj;
		},

	 	isValid: function() {
	 		// TODO: Combobox might need time to process a recently input value.  This should be async?
	 		// make sure that every widget that has a validator function returns true
	 		return dojo.every(this.getDescendants(), function(widget){
	 			return !widget.isValid || widget.isValid();
	 		});
		}
	});

dojo.declare(
	"dijit.form.Form",
	[dijit._Widget, dijit._Templated, dijit.form._FormMixin],
	null
);

<commit_msg>Only create arrays where necessary (ie, for Checkboxes). Joose tells me that normal fields inside arrays will likely have [] in their name, like employee[3].name.first, so we'll deal with that another day. Refs #3835.<commit_after>dojo.provide("dijit.form.Form");

dojo.require("dijit._Widget");
dojo.require("dijit._Templated");

dojo.declare("dijit.form._FormMixin", null,
	{
		/*
		summary:
			Widget corresponding to <form> tag, for validation and serialization

		usage:
			<form dojoType="dijit.form.Form" id="myForm">
				Name: <input type="text" name="name" />
			</form>
			myObj={name: "John Doe"};
			dijit.byId('myForm').setValues(myObj);

			myObj=dijit.byId('myForm').getValues();
		TODO:
		* Repeater
		* better handling for arrays.  Often form elements have names with [] like
		* people[3].sex (for a list of people [{name: Bill, sex: M}, ...])

		*/

		// execute: Function
		//	User defined function to do stuff when the user hits the submit button
		execute: function(/*Object*/ formContents){},

   		templateString: "<form dojoAttachPoint='containerNode' dojoAttachEvent='onsubmit:_onSubmit' enctype='multipart/form-data'></form>",

 		_onSubmit: function(/*event*/e) {
 			// summary: callback when user hits submit button
    		dojo.stopEvent(e);
    		this.execute(this.getValues());
  		},

		submit: function() {
			// summary: programatically submit form
			this.containerNode.submit();
		},

		setValues: function(/*object*/obj) {
			// summary: fill in form values from a JSON structure

			// generate map from name --> [list of widgets with that name]
			var map = {};
			dojo.forEach(this.getDescendants(), function(widget){
				if(!widget.name){ return; }
				var entry = map[widget.name] || (map[widget.name] = [] );
				entry.push(widget);
			});

			// call setValue() or setChecked() for each widget, according to obj
			for(var name in map){
				var widgets = map[name],						// array of widgets w/this name
					values = dojo.getObject(name, false, obj);	// list of values for those widgets
				if(!dojo.isArray(values)){
					values = [ values ];
				}
				if(widgets[0].setChecked){
					// for checkbox/radio, values is a list of which widgets should be checked
					dojo.forEach(widgets, function(w, i){
						w.setChecked(dojo.indexOf(values, w.value) != -1);
					});
				}else{
					// otherwise, values is a list of values to be assigned sequentially to each widget
					dojo.forEach(widgets, function(w, i){
						w.setValue(values[i]);
					});
				}
			}

			/***
			 * 	TODO: code for plain input boxes (this shouldn't run for inputs that are part of widgets

			dojo.forEach(this.containerNode.elements, function(element){
				if (element.name == ''){return};	// like "continue"
				var namePath = element.name.split(".");
				var myObj=obj;
				var name=namePath[namePath.length-1];
				for(var j=1,len2=namePath.length;j<len2;++j) {
					var p=namePath[j - 1];
					// repeater support block
					var nameA=p.split("[");
					if (nameA.length > 1) {
						if(typeof(myObj[nameA[0]]) == "undefined") {
							myObj[nameA[0]]=[ ];
						} // if

						nameIndex=parseInt(nameA[1]);
						if(typeof(myObj[nameA[0]][nameIndex]) == "undefined") {
							myObj[nameA[0]][nameIndex]={};
						}
						myObj=myObj[nameA[0]][nameIndex];
           				continue;
  					 }  // repeater support ends

					if(typeof(myObj[p]) == "undefined") {
						myObj=undefined;
						break;
					};
					myObj=myObj[p];
				}

				if (typeof(myObj) == "undefined") {
					return;		// like "continue"
				}
				if (typeof(myObj[name]) == "undefined" && this.ignoreNullValues) {
					return;		// like "continue"
				}

				// TODO: widget values (just call setValue() on the widget)

				switch(element.type) {
					case "checkbox":
						element.checked = (name in myObj) &&
							dojo.some(myObj[name], function(val){ return val==element.value; });
						break;
					case "radio":
						element.checked = (name in myObj) && myObj[name]==element.value;
						break;
					case "select-multiple":
						element.selectedIndex=-1;
						dojo.forEach(element.options, function(option){
							option.selected = dojo.some(myObj[name], function(val){ return option.value == val; });
						});
						break;
					case "select-one":
						element.selectedIndex="0";
						dojo.forEach(element.options, function(option){
							option.selected = option.value == myObj[name];
						});
						break;
					case "hidden":
					case "text":
					case "textarea":
					case "password":
						element.value = myObj[name] || "";
						break;
				}
      		});
      		*/
		},

		getValues: function() {
			// summary: generate JSON structure from form values

			// get widget values
			var obj = {};
			dojo.forEach(this.getDescendants(), function(widget){
				var value = widget.getValue ? widget.getValue() : widget.value;
				var name = widget.name;
				if(!name){ return; }

				// Store widget's value(s) as a scalar, except for checkboxes which are automatically arrays
				if(widget.setChecked){
					if(/Radio/.test(widget.declaredClass)){
						// radio button
						if(widget.checked){
							dojo.setObject(name, value, obj);
						}
					}else{
						// checkbox/toggle button
						var ary=dojo.getObject(name, false, obj);
						if(!ary){
							ary=[];
							dojo.setObject(name, ary, obj);
						}
						if(widget.checked){
							ary.push(value);
						}
					}
				}else{
					// plain input
					dojo.setObject(name, value, obj);
				}
			});

			/***
			 * code for plain input boxex
			var obj = { };
			dojo.forEach(this.containerNode.elements, function(elm){
				if (!elm.name)	{
					return;		// like "continue"
				}
				var namePath = elm.name.split(".");
				var myObj=obj;
				var name=namePath[namePath.length-1];
				for(var j=1,len2=namePath.length;j<len2;++j) {
					var nameIndex = null;
					var p=namePath[j - 1];
					var nameA=p.split("[");
					if (nameA.length > 1) {
						if(typeof(myObj[nameA[0]]) == "undefined") {
							myObj[nameA[0]]=[ ];
						} // if
						nameIndex=parseInt(nameA[1]);
						if(typeof(myObj[nameA[0]][nameIndex]) == "undefined") {
							myObj[nameA[0]][nameIndex]={};
						}
					} else if(typeof(myObj[nameA[0]]) == "undefined") {
						myObj[nameA[0]]={}
					} // if

					if (nameA.length == 1) {
						myObj=myObj[nameA[0]];
					} else {
						myObj=myObj[nameA[0]][nameIndex];
					} // if
				} // for

				if ((elm.type != "select-multiple" && elm.type != "checkbox" && elm.type != "radio") || (elm.type=="radio" && elm.checked)) {
					if(name == name.split("[")[0]) {
						myObj[name]=elm.value;
					} else {
						// can not set value when there is no name
					}
				} else if (elm.type == "checkbox" && elm.checked) {
					if(typeof(myObj[name]) == 'undefined') {
						myObj[name]=[ ];
					}
					myObj[name].push(elm.value);
				} else if (elm.type == "select-multiple") {
					if(typeof(myObj[name]) == 'undefined') {
						myObj[name]=[ ];
					}
					for (var jdx=0,len3=elm.options.length; jdx<len3; ++jdx) {
						if (elm.options[jdx].selected) {
							myObj[name].push(elm.options[jdx].value);
						}
					}
				} // if
				name=undefined;
			}); // forEach
			***/
			return obj;
		},

	 	isValid: function() {
	 		// TODO: Combobox might need time to process a recently input value.  This should be async?
	 		// make sure that every widget that has a validator function returns true
	 		return dojo.every(this.getDescendants(), function(widget){
	 			return !widget.isValid || widget.isValid();
	 		});
		}
	});

dojo.declare(
	"dijit.form.Form",
	[dijit._Widget, dijit._Templated, dijit.form._FormMixin],
	null
);

<|endoftext|>


<commit_before>describe('Testing environmental search', function() {
    var appScope, mockAppCtrl, searchScope, envScope, mockSearchCtrl, mockEnvCtrl, mockSearchModelService, mockColorMapService, mockFindSocieties, mockEnvVar, mockEnvVal, mockMinAndMax;
    beforeEach(function() {
        module('dplaceServices');
        module('dplace');
    });

    beforeEach(inject(function($rootScope, $controller, searchModelService, colorMapService, FindSocieties, EnvironmentalVariable, EnvironmentalValue, MinAndMax, $httpBackend) {
        appScope = $rootScope.$new();

        mockSearchModelService = searchModelService;
        mockAppCtrl = $controller('AppCtrl', {$scope: appScope, searchModelService: mockSearchModelService});
        spyOn(appScope, 'setActive');

        mockColorMapService = colorMapService;
        mockFindSocieties = FindSocieties;
        spyOn(mockFindSocieties, 'find');
        mockEnvVal = EnvironmentalValue;
        mockEnvVar = EnvironmentalVariable;
        spyOn(mockEnvVar, 'query');
        mockMinAndMax = MinAndMax;

        spyOn(mockMinAndMax, 'query');

        searchScope = appScope.$new();
        mockSearchCtrl = $controller('SearchCtrl', {
            $scope: searchScope,
            colorMapService: mockColorMapService,
            searchModelService: mockSearchModelService,
            FindSocieties: mockFindSocieties
        });
        spyOn(searchScope, 'search').and.callThrough();
        spyOn(mockSearchModelService, 'updateSearchQuery');
        spyOn(searchScope, 'searchSocieties').and.callThrough();
        spyOn(searchScope, 'getCodeIDs');

        envScope = searchScope.$new();

        mockEnvCtrl = $controller('EnvironmentalCtrl', {
            $scope: envScope,
            searchModelService: mockSearchModelService,
            EnvironmentalVariable: mockEnvVar,
            EnvironmentalValue: mockEnvVal,
            MinAndMax: mockMinAndMax
        });
        spyOn(envScope, 'filterChanged');

        //These API calls are made in searchModelService or environmental.js
        //We just respond with 200 OK because this is testing the controller logic
        $httpBackend.whenGET('/api/v1/categories?page_size=1000')
            .respond(200);
        $httpBackend.whenGET('/api/v1/get_dataset_sources')
            .respond(200);
        $httpBackend.whenGET('/api/v1/geographic_regions?page_size=1000')
            .respond(200);
        $httpBackend.whenGET('/api/v1/environmental_categories')
            .respond(200);
        $httpBackend.whenGET('/api/v1/language_families?page_size=1000')
            .respond(200);
        $httpBackend.whenPOST('/api/v1/find_societies')
            .respond(200); //return a result here?
        $httpBackend.whenGET('/api/v1/languages?page_size=1000')
            .respond(200);
        $httpBackend.whenGET('/api/v1/environmental_variables?category=7&page_size=1000')
            .respond(200);
        $httpBackend.whenGET('/api/v1/min_and_max?query=%7B%22environmental_id%22:7%7D')
            .respond(JSON.stringify({
                "min": 1.111,
                "max": 500
            }));
    }));
    it('should update badgeValue and call search', function() {
        var variable = {
            'category': 1,
            'codebook_info': '',
            'id': 7,
            'max': 500,
            'min': 1.111,
            'name': "Precipitation",
            'range': 409.999,
            'units': 'mm'
        };
        var category = {
            'id': 7,
            'name': 'Climate'
        };
        var form = {
            $valid: true,
            $dirty: false,
            $setPristine: function() {}
        };
        envScope.environmentalData.selectedVariables[0].selectedCategory = category;
        envScope.environmentalData.selectedVariables[0].EnvironmentalForm = form;

        //test change category and EnvironmentalVariable API call
        envScope.categoryChanged(envScope.environmentalData.selectedVariables[0]);
        expect(mockEnvVar.query).toHaveBeenCalled();

        //test variable changed when no variable selected
        envScope.variableChanged(envScope.environmentalData.selectedVariables[0]);
        envScope.$digest();
        expect(envScope.environmentalData.badgeValue).toEqual(0);
        expect(envScope.filterChanged).not.toHaveBeenCalled();

        //test variable changed with variable selected
        envScope.environmentalData.selectedVariables[0].selectedVariable = variable;
        envScope.variableChanged(envScope.environmentalData.selectedVariables[0]);
        envScope.$digest();
        expect(envScope.environmentalData.badgeValue).toEqual(1);
        expect(mockMinAndMax.query).toHaveBeenCalled();
        expect(envScope.filterChanged).toHaveBeenCalledWith(envScope.environmentalData.selectedVariables[0]);

        //test filter changed - need to figure out how to do this

        //test add variable
        expect(envScope.environmentalData.selectedVariables.length).toEqual(1);
        envScope.addVariable();
        envScope.$digest();
        expect(envScope.environmentalData.selectedVariables.length).toEqual(2);

        //test remove variable
        searchScope.removeFromSearch(variable, 'environmental');
        envScope.$digest();
        expect(envScope.environmentalData.selectedVariables.length).toEqual(1);
        expect(envScope.environmentalData.selectedVariables.indexOf(variable)).toEqual(-1);

        //test search
        envScope.environmentalData.selectedVariables[0].selectedVariable = variable;
        envScope.environmentalData.selectedVariables[0].selectedFilter = envScope.environmentalData.filters[0];
        envScope.environmentalData.selectedVariables[0].vals = [1.0, 500];
        envScope.doSearch();
        expect(searchScope.search).toHaveBeenCalled();
        searchScope.$digest();

        expected_query = {
            'e': [[
                variable.id,
                envScope.environmentalData.selectedVariables[0].selectedFilter.operator,
                [1.0, 500]
            ]]
        };

        expect(mockSearchModelService.updateSearchQuery).toHaveBeenCalled();
        expect(mockSearchModelService.updateSearchQuery).toHaveBeenCalledWith(expected_query);
        expect(searchScope.searchSocieties).toHaveBeenCalled();
        expect(mockFindSocieties.find).toHaveBeenCalled();

    });
});
<commit_msg>Update test_environmental.js<commit_after>describe('Testing environmental search', function() {
    var appScope, mockAppCtrl, searchScope, envScope, mockSearchCtrl, mockEnvCtrl, mockSearchModelService, mockColorMapService, mockFindSocieties, mockEnvVar, mockEnvVal, mockMinAndMax;
    beforeEach(function() {
        module('dplaceServices');
        module('dplace');
    });

    beforeEach(inject(function($rootScope, $controller, searchModelService, colorMapService, FindSocieties, EnvironmentalVariable, EnvironmentalValue, MinAndMax, $httpBackend) {
        appScope = $rootScope.$new();

        mockSearchModelService = searchModelService;
        mockAppCtrl = $controller('AppCtrl', {$scope: appScope, searchModelService: mockSearchModelService});
        spyOn(appScope, 'setActive');

        mockColorMapService = colorMapService;
        mockFindSocieties = FindSocieties;
        spyOn(mockFindSocieties, 'find');
        mockEnvVal = EnvironmentalValue;
        mockEnvVar = EnvironmentalVariable;
        spyOn(mockEnvVar, 'query');
        mockMinAndMax = MinAndMax;

        spyOn(mockMinAndMax, 'query');

        searchScope = appScope.$new();
        mockSearchCtrl = $controller('SearchCtrl', {
            $scope: searchScope,
            colorMapService: mockColorMapService,
            searchModelService: mockSearchModelService,
            FindSocieties: mockFindSocieties
        });
        spyOn(searchScope, 'search').and.callThrough();
        spyOn(mockSearchModelService, 'updateSearchQuery');
        spyOn(searchScope, 'searchSocieties').and.callThrough();
        spyOn(mockSearchModelService, 'getCodeIDs');

        envScope = searchScope.$new();

        mockEnvCtrl = $controller('EnvironmentalCtrl', {
            $scope: envScope,
            searchModelService: mockSearchModelService,
            EnvironmentalVariable: mockEnvVar,
            EnvironmentalValue: mockEnvVal,
            MinAndMax: mockMinAndMax
        });
        spyOn(envScope, 'filterChanged');

        //These API calls are made in searchModelService or environmental.js
        //We just respond with 200 OK because this is testing the controller logic
        $httpBackend.whenGET('/api/v1/categories?page_size=1000')
            .respond(200);
        $httpBackend.whenGET('/api/v1/get_dataset_sources')
            .respond(200);
        $httpBackend.whenGET('/api/v1/geographic_regions?page_size=1000')
            .respond(200);
        $httpBackend.whenGET('/api/v1/environmental_categories')
            .respond(200);
        $httpBackend.whenGET('/api/v1/language_families?page_size=1000')
            .respond(200);
        $httpBackend.whenPOST('/api/v1/find_societies')
            .respond(200); //return a result here?
        $httpBackend.whenGET('/api/v1/languages?page_size=1000')
            .respond(200);
        $httpBackend.whenGET('/api/v1/environmental_variables?category=7&page_size=1000')
            .respond(200);
        $httpBackend.whenGET('/api/v1/min_and_max?query=%7B%22environmental_id%22:7%7D')
            .respond(JSON.stringify({
                "min": 1.111,
                "max": 500
            }));
    }));
    it('should update badgeValue and call search', function() {
        var variable = {
            'category': 1,
            'codebook_info': '',
            'id': 7,
            'max': 500,
            'min': 1.111,
            'name': "Precipitation",
            'range': 409.999,
            'units': 'mm'
        };
        var category = {
            'id': 7,
            'name': 'Climate'
        };
        var form = {
            $valid: true,
            $dirty: false,
            $setPristine: function() {}
        };
        envScope.environmentalData.selectedVariables[0].selectedCategory = category;
        envScope.environmentalData.selectedVariables[0].EnvironmentalForm = form;

        //test change category and EnvironmentalVariable API call
        envScope.categoryChanged(envScope.environmentalData.selectedVariables[0]);
        expect(mockEnvVar.query).toHaveBeenCalled();

        //test variable changed when no variable selected
        envScope.variableChanged(envScope.environmentalData.selectedVariables[0]);
        envScope.$digest();
        expect(envScope.environmentalData.badgeValue).toEqual(0);
        expect(envScope.filterChanged).not.toHaveBeenCalled();

        //test variable changed with variable selected
        envScope.environmentalData.selectedVariables[0].selectedVariable = variable;
        envScope.variableChanged(envScope.environmentalData.selectedVariables[0]);
        envScope.$digest();
        expect(envScope.environmentalData.badgeValue).toEqual(1);
        expect(mockMinAndMax.query).toHaveBeenCalled();
        expect(envScope.filterChanged).toHaveBeenCalledWith(envScope.environmentalData.selectedVariables[0]);

        //test filter changed - need to figure out how to do this

        //test add variable
        expect(envScope.environmentalData.selectedVariables.length).toEqual(1);
        envScope.addVariable();
        envScope.$digest();
        expect(envScope.environmentalData.selectedVariables.length).toEqual(2);

        //test remove variable
        searchScope.removeFromSearch(variable, 'environmental');
        envScope.$digest();
        expect(envScope.environmentalData.selectedVariables.length).toEqual(1);
        expect(envScope.environmentalData.selectedVariables.indexOf(variable)).toEqual(-1);

        //test search
        envScope.environmentalData.selectedVariables[0].selectedVariable = variable;
        envScope.environmentalData.selectedVariables[0].selectedFilter = envScope.environmentalData.filters[0];
        envScope.environmentalData.selectedVariables[0].vals = [1.0, 500];
        envScope.doSearch();
        expect(searchScope.search).toHaveBeenCalled();
        searchScope.$digest();

        expected_query = {
            'e': [[
                variable.id,
                envScope.environmentalData.selectedVariables[0].selectedFilter.operator,
                [1.0, 500]
            ]]
        };

        expect(mockSearchModelService.updateSearchQuery).toHaveBeenCalled();
        expect(mockSearchModelService.updateSearchQuery).toHaveBeenCalledWith(expected_query);
        expect(searchScope.searchSocieties).toHaveBeenCalled();
        expect(mockFindSocieties.find).toHaveBeenCalled();

    });
});
<|endoftext|>


<commit_before>'use strict';

(function() {

    /**
     * @ngdoc overview
     * @name ngStorage
     */

    angular.module('ngStorage', []).

    /**
     * @ngdoc object
     * @name ngStorage.$localStorage
     * @requires $rootScope
     * @requires $window
     */

    factory('$localStorage', _storageFactory('localStorage')).

    /**
     * @ngdoc object
     * @name ngStorage.$sessionStorage
     * @requires $rootScope
     * @requires $window
     */

    factory('$sessionStorage', _storageFactory('sessionStorage'));

    function _storageFactory(storageType) {
        return [
            '$rootScope',
            '$window',
            '$log',

            function(
                $rootScope,
                $window,
                $log
            ){
                // #9: Assign a placeholder object if Web Storage is unavailable to prevent breaking the entire AngularJS app
                var webStorage = $window[storageType] || ($log.warn('This browser does not support Web Storage!'), {}),
                    $storage = {
                        $default: function(items) {
                            for (var k in items) {
                                angular.isDefined($storage[k]) || ($storage[k] = items[k]);
                            }

                            return $storage;
                        },
                        $reset: function(items) {
                            for (var k in $storage) {
                                '$' === k[0] || delete $storage[k];
                            }

                            return $storage.$default(items);
                        }
                    },
                    _last$storage,
                    _debounce;

                for (var i = 0, k; i < webStorage.length; i++) {
                    // #8, #10: `webStorage.key(i)` may be an empty string (or throw an exception in IE9 if `webStorage` is empty)
                    (k = webStorage.key(i)) && 'ngStorage-' === k.slice(0, 10) && ($storage[k.slice(10)] = angular.fromJson(webStorage.getItem(k)));
                }

                _last$storage = angular.copy($storage);

                $rootScope.$watch(function() {
                    _debounce || (_debounce = setTimeout(function() {
                        _debounce = null;

                        if (!angular.equals($storage, _last$storage)) {
                            angular.forEach($storage, function(v, k) {
                                angular.isDefined(v) && '$' !== k[0] && webStorage.setItem('ngStorage-' + k, angular.toJson(v));

                                delete _last$storage[k];
                            });

                            for (var k in _last$storage) {
                                webStorage.removeItem('ngStorage-' + k);
                            }

                            _last$storage = angular.copy($storage);
                        }
                    }, 100));
                });

                // #6: Use `$window.addEventListener` instead of `angular.element` to avoid the jQuery-specific `event.originalEvent`
                'localStorage' === storageType && $window.addEventListener && $window.addEventListener('storage', function(event) {
                    if ('ngStorage-' === event.key.slice(0, 10)) {
                        event.newValue ? $storage[event.key.slice(10)] = angular.fromJson(event.newValue) : delete $storage[event.key.slice(10)];

                        _last$storage = angular.copy($storage);

                        $rootScope.$apply();
                    }
                });

                return $storage;
            }
        ];
    }

})();
<commit_msg>Catch against IE's broken storage events<commit_after>'use strict';

(function() {

    /**
     * @ngdoc overview
     * @name ngStorage
     */

    angular.module('ngStorage', []).

    /**
     * @ngdoc object
     * @name ngStorage.$localStorage
     * @requires $rootScope
     * @requires $window
     */

    factory('$localStorage', _storageFactory('localStorage')).

    /**
     * @ngdoc object
     * @name ngStorage.$sessionStorage
     * @requires $rootScope
     * @requires $window
     */

    factory('$sessionStorage', _storageFactory('sessionStorage'));

    function _storageFactory(storageType) {
        return [
            '$rootScope',
            '$window',
            '$log',

            function(
                $rootScope,
                $window,
                $log
            ){
                // #9: Assign a placeholder object if Web Storage is unavailable to prevent breaking the entire AngularJS app
                var webStorage = $window[storageType] || ($log.warn('This browser does not support Web Storage!'), {}),
                    $storage = {
                        $default: function(items) {
                            for (var k in items) {
                                angular.isDefined($storage[k]) || ($storage[k] = items[k]);
                            }

                            return $storage;
                        },
                        $reset: function(items) {
                            for (var k in $storage) {
                                '$' === k[0] || delete $storage[k];
                            }

                            return $storage.$default(items);
                        }
                    },
                    _last$storage,
                    _debounce;

                for (var i = 0, k; i < webStorage.length; i++) {
                    // #8, #10: `webStorage.key(i)` may be an empty string (or throw an exception in IE9 if `webStorage` is empty)
                    (k = webStorage.key(i)) && 'ngStorage-' === k.slice(0, 10) && ($storage[k.slice(10)] = angular.fromJson(webStorage.getItem(k)));
                }

                _last$storage = angular.copy($storage);

                $rootScope.$watch(function() {
                    _debounce || (_debounce = setTimeout(function() {
                        _debounce = null;

                        if (!angular.equals($storage, _last$storage)) {
                            angular.forEach($storage, function(v, k) {
                                angular.isDefined(v) && '$' !== k[0] && webStorage.setItem('ngStorage-' + k, angular.toJson(v));

                                delete _last$storage[k];
                            });

                            for (var k in _last$storage) {
                                webStorage.removeItem('ngStorage-' + k);
                            }

                            _last$storage = angular.copy($storage);
                        }
                    }, 100));
                });

                // #6: Use `$window.addEventListener` instead of `angular.element` to avoid the jQuery-specific `event.originalEvent`
                'localStorage' === storageType && $window.addEventListener && $window.addEventListener('storage', function(event) {
                    if ((!$window.document.hasFocus || !$window.document.hasFocus()) && 'ngStorage-' === event.key.slice(0, 10)) {
                        event.newValue ? $storage[event.key.slice(10)] = angular.fromJson(event.newValue) : delete $storage[event.key.slice(10)];

                        _last$storage = angular.copy($storage);

                        $rootScope.$apply();
                    }
                });

                return $storage;
            }
        ];
    }

})();
<|endoftext|>


<commit_before>'use strict';
const algoliasearch = require('algoliasearch');

const State = Object.freeze({
    ARTIST_ONE: {context: "artistOne", event: "ARTIST_ONE"},
    ARTIST_OTHER: {context: "artistOther", event: "ARTIST_OTHER"},
    ARTIST_MISS: {context: "artistMiss", event: "ARTIST_MISS"},
    ARTIST_MANY: {context: "artistMany", event: "ARTIST_MANY"},
});

class Fulfiller {

    constructor(server) {
        this.server = server;
        this.version = 6;
        this.client = algoliasearch("TDNMRH8LS3", "ec222292c9b89b658fe00b34ff341194");
        this.index = this.client.initIndex("songs");
        this.resetResponse();
    }

    resetResponse() {
        this.response = {
            'source': "Algolia",
            'backend_version': this.version,
            data: []
        };
        this.responseIntent = undefined;
    }

    log(message) {
        this.server.log("Fulfiller", message);
    }

    sendReply(message, code) {
        if (message !== undefined) {
            this.response["speech"] = message;
            this.response["displayText"] = message;
        }
        if (code === undefined) {
            code = 200;
        }
        this.log("Reply(" + code + "):" + this.response["speech"]);
        this.reply(this.response).code(code);
    }

    serve(req, reply) {
        this.resetResponse();
        this.reply = reply;
        let artist = '', artistOriginal = '', period = '';
        let period_start = 0, period_end = 0;
        let artistNames = [], songs = [];

        if (!(req.mime && req.mime === "application/json")) {
            this.sendReply("Your request MUST be application/json.", 400);
            return;
        }

        if (req.payload.result.parameters['artist']) {
            artist = req.payload.result.parameters['artist'];
            artistOriginal = req.payload.result.contexts[0].parameters['artist.original'];
            this.log("Artist: " + artist);
        }
        if (req.payload.result.parameters['period']) {
            period = req.payload.result.parameters['period'];
            this.log("Period: " + period);
            [period_start, period_end] = period.split("/").map(Date.parse).map(x => x / 1000 /* engine expects second-level precision */);
            this.log("Split: " + period_start + " to " + period_end + " -> " + new Date(period_start) + " to " + new Date(period_end));
        }

        let searchQuery = '';
        const searchOptions = {};
        if (artist !== '') {
            searchOptions['restrictSearchableAttributes'] = ['artistName'];
            searchQuery = artist;
        }
        if (period !== '') {
            const filter = "release_timestamp: " + period_start + ' TO ' + period_end;
            this.log("filter: " + filter);
            searchOptions['filters'] = filter;
        } else if (artist === '' && period === '') {
            this.log("No artist nor period -> nothing to search.");
            this.sendReply("I can't search for nothing. Please ask me about music by an artist or from a date/perion!");
            return;
        }

        this.index.search(searchQuery, searchOptions, (err, content) => {
                if (err) {
                    this.log(err);
                    return;
                }

                this.log("Searching for " + artist + " returned " + content.nbHits + " results.");
                if (content.nbHits > 0) {
                    for (let i in content.hits) {
                        let hit = content.hits[i];
                        if (artistNames.indexOf(hit.artistName) === -1) {
                            artistNames.push(hit.artistName);
                        }
                        this.log('Hit(' + hit.objectID + '): ' + hit.trackName);
                        songs.push(hit);
                    }
                    this.response["data"] = {"songs": songs};
                    const artistIsFoundExact = artistNames.indexOf(artist) !== -1;

                    if (artistNames.length === 1) {
                        if (artistIsFoundExact) { // We found the expected artist -> trigger ARTIST_ONE event
                            this.responseIntent = State.ARTIST_ONE;
                        } else { // We found another artist -> trigger ARTIST_OTHER event
                            this.responseIntent = State.ARTIST_OTHER;
                        }
                    } else { // We found several artists -> trigger ARTIST_MANY
                        this.responseIntent = State.ARTIST_MANY;
                    }
                } else { // We found no artists -> trigger ARTIST_MISS
                    this.responseIntent = State.ARTIST_MISS;
                }

                this.response["contextOut"] = [{
                    name: this.responseIntent.context,
                    parameters: {
                        'artistNames': artistNames,
                        'artistOriginal': artistOriginal,
                        'songTitles': songs.map(hit => hit.trackName)
                    },
                    lifespan: 1
                }];
                this.response['followupEvent'] = {
                    name: this.responseIntent.event,
                };
                this.sendReply();
            }
        );
    };

}

let fulfiller = undefined;
module.exports = function (request, reply) {
    if (fulfiller === undefined) {
        fulfiller = new Fulfiller(require("./server"));
        fulfiller.log("New fulfiller!");
    }
    fulfiller.serve(request, reply);
};<commit_msg>feat(fulfiller): Log response json<commit_after>'use strict';
const algoliasearch = require('algoliasearch');

const State = Object.freeze({
    ARTIST_ONE: {context: "artistOne", event: "ARTIST_ONE"},
    ARTIST_OTHER: {context: "artistOther", event: "ARTIST_OTHER"},
    ARTIST_MISS: {context: "artistMiss", event: "ARTIST_MISS"},
    ARTIST_MANY: {context: "artistMany", event: "ARTIST_MANY"},
});

class Fulfiller {

    constructor(server) {
        this.server = server;
        this.version = 6;
        this.client = algoliasearch("TDNMRH8LS3", "ec222292c9b89b658fe00b34ff341194");
        this.index = this.client.initIndex("songs");
        this.resetResponse();
    }

    resetResponse() {
        this.response = {
            'source': "Algolia",
            'backend_version': this.version,
            data: []
        };
        this.responseIntent = undefined;
    }

    log(message) {
        this.server.log("Fulfiller", message);
    }

    sendReply(message, code) {
        if (message !== undefined) {
            this.response["speech"] = message;
            this.response["displayText"] = message;
        }
        if (code === undefined) {
            code = 200;
        }

        this.log("Reply(" + code + "):" + JSON.stringify(this.response, (key, value) => {
            switch (key) {
                case 'data':
                    return '[...]';
                case 'source':
                    return
            }
            return value;
        }, 1));
        this.reply(this.response).code(code);
    }

    serve(req, reply) {
        this.resetResponse();
        this.reply = reply;
        let artist = '', artistOriginal = '', period = '';
        let period_start = 0, period_end = 0;
        let artistNames = [], songs = [];

        if (!(req.mime && req.mime === "application/json")) {
            this.sendReply("Your request MUST be application/json.", 400);
            return;
        }

        if (req.payload.result.parameters['artist']) {
            artist = req.payload.result.parameters['artist'];
            artistOriginal = req.payload.result.contexts[0].parameters['artist.original'];
            this.log("Artist: " + artist);
        }
        if (req.payload.result.parameters['period']) {
            period = req.payload.result.parameters['period'];
            this.log("Period: " + period);
            [period_start, period_end] = period.split("/").map(Date.parse).map(x => x / 1000 /* engine expects second-level precision */);
            this.log("Split: " + period_start + " to " + period_end + " -> " + new Date(period_start) + " to " + new Date(period_end));
        }

        let searchQuery = '';
        const searchOptions = {};
        if (artist !== '') {
            searchOptions['restrictSearchableAttributes'] = ['artistName'];
            searchQuery = artist;
        }
        if (period !== '') {
            const filter = "release_timestamp: " + period_start + ' TO ' + period_end;
            this.log("filter: " + filter);
            searchOptions['filters'] = filter;
        } else if (artist === '' && period === '') {
            this.log("No artist nor period -> nothing to search.");
            this.sendReply("I can't search for nothing. Please ask me about music by an artist or from a date/perion!");
            return;
        }

        this.index.search(searchQuery, searchOptions, (err, content) => {
                if (err) {
                    this.log(err);
                    return;
                }

                this.log("Searching for " + artist + " returned " + content.nbHits + " results.");
                if (content.nbHits > 0) {
                    for (let i in content.hits) {
                        let hit = content.hits[i];
                        if (artistNames.indexOf(hit.artistName) === -1) {
                            artistNames.push(hit.artistName);
                        }
                        this.log('Hit(' + hit.objectID + '): ' + hit.trackName);
                        songs.push(hit);
                    }
                    this.response["data"] = {"songs": songs};
                    const artistIsFoundExact = artistNames.indexOf(artist) !== -1;

                    if (artistNames.length === 1) {
                        if (artistIsFoundExact) { // We found the expected artist -> trigger ARTIST_ONE event
                            this.responseIntent = State.ARTIST_ONE;
                        } else { // We found another artist -> trigger ARTIST_OTHER event
                            this.responseIntent = State.ARTIST_OTHER;
                        }
                    } else { // We found several artists -> trigger ARTIST_MANY
                        this.responseIntent = State.ARTIST_MANY;
                    }
                } else { // We found no artists -> trigger ARTIST_MISS
                    this.responseIntent = State.ARTIST_MISS;
                }

                this.response["contextOut"] = [{
                    name: this.responseIntent.context,
                    parameters: {
                        'artistNames': artistNames,
                        'artistOriginal': artistOriginal,
                        'songTitles': songs.map(hit => hit.trackName)
                    },
                    lifespan: 1
                }];
                this.response['followupEvent'] = {
                    name: this.responseIntent.event,
                };
                this.sendReply();
            }
        );
    };

}

let fulfiller = undefined;
module.exports = function (request, reply) {
    if (fulfiller === undefined) {
        fulfiller = new Fulfiller(require("./server"));
        fulfiller.log("New fulfiller!");
    }
    fulfiller.serve(request, reply);
};<|endoftext|>


<commit_before>/*
Script: ART.Alerts.js

License:
	MIT-style license.
*/

// Window Widget. Work in progress.

ART.Sheet.defineStyle('window.alert', {
	'content-background-color': hsb(0, 0, 91),
	'footer-height': 30,
	'height': 140,
	'width': 300,
	'footer-background-color': hsb(0, 0, 91),
	'footer-reflection-color': hsb(0, 0, 91),
	'content-border-bottom-color': hsb(0, 0, 91)
});

ART.Sheet.defineStyle('window.alert footer', {
	'float': 'right',
	'width': 'auto'
});

ART.Sheet.defineStyle('window.alert content', {
	'padding': 20,
	'font-size': 14,
	'text-align': 'center'
});

ART.Sheet.defineStyle('window.alert input.prompt', {
	'width': '100%'
});

ART.Sheet.defineStyle('window.alert button.confirmations', {
	'padding-right': 15,
	'border':'none',
	'float': 'left'
});

ART.StickyWin.DefaultManager.setLayer('alerts', 99);
(function(){

var tmp = new Element('div');

ART.Alert = new Class({

	Extends: ART.Window,

	name: 'window',

	options: {
		close: false,
		minimize: false,
		maximize: false,
		className: 'alert',
		resizable: false,
		windowManagerLayer: 'alerts',
		destroyOnClose: true,
		autosize: true,
		buttons: [
			{
				text: 'Ok'
			}
		]
	},

	initialize: function(){
		this.parent.apply(this, arguments);
		this.addEvent('show', function(){
			var button = this.alertButtons[0];
			if (button) button.focus();
		}.bind(this));
		this.attachKeys({
			'keyup:esc': function(e) {
				this.hide();
			}.bind(this)
		});
	},

	redraw: function(){
		this.parent.apply(this, arguments);
		if (!this.content) return;
		var style = ART.Sheet.lookupStyle(this.getSelector() + ' content');
		if (style.margin || style.padding) {
			tmp.setStyles(style);
			var w = 0,
					h = 0;
			['margin', 'padding'].each(function(space) {
				['Top', 'Left', 'Bottom', 'Right'].each(function(side, i) {
					if (i%2) w += tmp.getStyle(space+side).toInt();
					else h += tmp.getStyle(space+side).toInt();
				});
			});

			tmp.style.cssText = '';

			style.width = this.contentSize.w - w; //border is hard coded to 1 on each side
			style.height = this.contentSize.h - h; //border is hard coded to 1 on each side
		}
		this.content.setStyles(style);
		this.footer.setStyles(ART.Sheet.lookupStyle(this.getSelector() + ' footer'));
	},

	makeButtons: function(){
		this.parent();
		this.alertButtons = this.options.buttons.map(function(button){
			button.className = ((button.className || '') + ' confirmations').trim();
			button.parentWidget = this;
			var b = new ART.Button(button);
			b.addEvent('press', function(e){
				if ($(b).hasClass(this.options.closeClass)) {
					if (e) e.preventDefault();
					this.hide();
				}
			}.bind(this));

			button.properties = button.properties || {};
			if (!button.properties['class']) button.properties['class'] = this.options.closeClass;
			$(b).set(button.properties);

			$(b).inject(this.footer).setStyles(ART.Sheet.lookupStyle(b.getSelector()));
			return b;
		}, this);
		if (this.alertButtons[0]) this.alertButtons[0].enable().focus();
	}

});

ART.alert = function(caption, content, callback, options) {
	return new ART.Alert(
		$extend(options || {}, {
			caption: caption,
			content: content,
			onHide: callback || $empty
		})
	);
};

ART.Confirm = new Class({

	Extends: ART.Alert,

	options: {
		className: 'alert confirm',
		resizable: false,
		buttons: [
			{
				text: 'Cancel'
			},
			{
				text: 'Ok',
				onPress: function(){
					this.parentWidget.fireEvent('confirm');
				}
			}
		]
	}
});
ART.confirm = function(caption, content, callback, options) {
	return new ART.Confirm(
		$extend(options || {}, {
			caption: caption,
			content: content,
			onConfirm: callback || $empty
		})
	);
};

ART.Prompt = new Class({

	Extends: ART.Confirm,

	options: {
		onShow: function(){
			var input = this.content.getElement('input, textarea');
			if (input) input.select();
		},
		buttons: [
			{
				text: 'Cancel'
			},
			{
				text: 'Ok',
				onPress: function(){
					this.parentWidget.fireEvent('confirm', this.parentWidget.getPromptValue());
				}
			}
		]
	},
	initialize: function(){
		this.parent.apply(this, arguments);
		this.makePromptInput();
	},
	makePromptInput: function(){
		if (!this.content.getElement('form') && !this.content.getElements('input, textarea, select').length) {
			var styles = ART.Sheet.lookupStyle(this.getSelector() + ' input.prompt');
			this.inputContainer = new Element('div', {
				'class': 'inputContainer',
				styles: {
					position: 'relative'
				}
			}).inject(this.content);
			this.input = new Element('input', {
				value: this.options.defaultValue || '',
				type: 'text',
				styles: styles,
				events: {
					keydown: function(e) {
						if (e.key == 'enter') {
							this.fireEvent('confirm', this.getPromptValue());
							this.hide();
							Keyboard.stop(e);
						}
					}.bind(this)
				}
			}).inject(this.inputContainer);
		}
	},
	getPromptValue: function(){
		if (this.input) return this.input.get('value');
		var form = this.content.getElement('form');
		return form ? form.toQueryString() : this.content.toQueryString();
	},
	setContent: function(){
		if (this.inputContainer) this.inputContainer.dispose();
		this.parent.apply(this, arguments);
		if (this.inputContainer) this.inputContainer.inject(this.content);
		return this;
	},
	show: function(){
		this.parent.apply(this, arguments);
		this.alertButtons[0].disable().blur();
		var input = this.content.getElement('input, textarea');
		if (input) input.select();
		return this;
	}
});

ART.prompt = function(caption, content, callback, options) {
	return new ART.Prompt(
		$extend(options || {}, {
			caption: caption,
			content: content,
			onConfirm: callback || $empty
		})
	);
};

ART.Window.AlertTools = new Class({

	Implements: ART.WindowTools,

	alerts: {},

	alert: function(caption, content, callback, options, type){
		type = type || 'alert';
		var win = this.getWindow();
		var currentKb = win.keyboard.activeKB;
		if (win && !win.alertManager) {
			win.alertManager = new ART.WindowManager(win);
			win.alertManager.setLayer('alerts', 99);
		}
		var winsize = win.getSize();
		options = $merge({
			relativeTo: $(win) || $(this),
			inject: {
				target: $(win) || $(this),
				where: 'bottom'
			},
			mask: true,
			maskOptions: {
				inject: {
					target: $(this.getWindow().content),
					where: 'after'
				}
			},
			constrainToContainer: true,
			width: (winsize.width * 0.9).toInt(),
			max: {
				height: (winsize.height * 0.9).toInt(),
				width: (winsize.width * 0.9).toInt()
			}
		}, options);
		options.parentWidget = win || this;
		options.windowManager = win ? win.alertManager : null;
		var alert = ART[type](caption, content, callback, options);
		var enableKB = function(){
			win.keyboard.activate(win.alertManager.keyboard);
			alert.keyboard.activate();
		};
		enableKB();
		if (win) {
			var shader = function(dragging) {
				$(alert).setStyle('display', dragging ? 'none' : 'block');
			};
			alert.addEvents({
				destroy: function(){
					win.removeEvent('shade', shader);
					win.keyboard.activate(currentKb);
				},
				hide: function(){
					win.keyboard.activate(currentKb);
				},
				show: enableKB
			});
			win.addEvent('shade', shader);
		}
		return alert;
	},
	confirm: function(cap, cont, fn, opt) {
		return this.alert(cap, cont, fn, opt, 'confirm');
	},
	prompt: function(cap, cont, fn, opt) {
		return this.alert(cap, cont, fn, opt, 'prompt');
	}
});

ART.Window.implement(new ART.Window.AlertTools);

})();<commit_msg>making alerts a little more flexible for extension<commit_after>/*
Script: ART.Alerts.js

License:
	MIT-style license.
*/

// Window Widget. Work in progress.

ART.Sheet.defineStyle('window.alert', {
	'content-background-color': hsb(0, 0, 91),
	'footer-height': 30,
	'height': 140,
	'width': 300,
	'footer-background-color': hsb(0, 0, 91),
	'footer-reflection-color': hsb(0, 0, 91),
	'content-border-bottom-color': hsb(0, 0, 91)
});

ART.Sheet.defineStyle('window.alert footer', {
	'float': 'right',
	'width': 'auto'
});

ART.Sheet.defineStyle('window.alert content', {
	'padding': 20,
	'font-size': 14,
	'text-align': 'center'
});

ART.Sheet.defineStyle('window.alert input.prompt', {
	'width': '100%'
});

ART.Sheet.defineStyle('window.alert button.confirmations', {
	'padding-right': 15,
	'border':'none',
	'float': 'left'
});

ART.StickyWin.DefaultManager.setLayer('alerts', 99);
(function(){

var tmp = new Element('div');

ART.Alert = new Class({

	Extends: ART.Window,

	name: 'window',

	options: {
		close: false,
		minimize: false,
		maximize: false,
		className: 'alert',
		resizable: false,
		windowManagerLayer: 'alerts',
		destroyOnClose: true,
		autosize: true,
		buttons: [
			{
				text: 'Ok'
			}
		]
	},

	initialize: function(){
		this.parent.apply(this, arguments);
		this.addEvent('show', function(){
			var button = this.alertButtons[0];
			if (button) button.focus();
		}.bind(this));
		this.attachKeys({
			'keyup:esc': function(e) {
				this.hide();
			}.bind(this)
		});
	},

	redraw: function(){
		this.parent.apply(this, arguments);
		if (!this.content) return;
		var style = ART.Sheet.lookupStyle(this.getSelector() + ' content');
		if (style.margin || style.padding) {
			tmp.setStyles(style);
			var w = 0,
					h = 0;
			['margin', 'padding'].each(function(space) {
				['Top', 'Left', 'Bottom', 'Right'].each(function(side, i) {
					if (i%2) w += tmp.getStyle(space+side).toInt();
					else h += tmp.getStyle(space+side).toInt();
				});
			});

			tmp.style.cssText = '';

			style.width = this.contentSize.w - w; //border is hard coded to 1 on each side
			style.height = this.contentSize.h - h; //border is hard coded to 1 on each side
		}
		this.content.setStyles(style);
		this.footer.setStyles(ART.Sheet.lookupStyle(this.getSelector() + ' footer'));
	},

	makeButtons: function(){
		this.parent();
		this.alertButtons = this.options.buttons.map(function(button){
			button.className = ((button.className || '') + ' confirmations').trim();
			button.parentWidget = this;
			var b = new ART.Button(button);
			b.addEvent('press', function(e){
				if ($(b).hasClass(this.options.closeClass)) {
					if (e) e.preventDefault();
					this.hide();
				}
			}.bind(this));

			button.properties = button.properties || {};
			if (!button.properties['class']) button.properties['class'] = this.options.closeClass;
			$(b).set(button.properties);

			$(b).inject(this.footer).setStyles(ART.Sheet.lookupStyle(b.getSelector()));
			return b;
		}, this);
		if (this.alertButtons[0]) this.alertButtons[0].enable().focus();
	}

});

ART.alert = function(caption, content, callback, options) {
	return new ART.Alert(
		$extend(options || {}, {
			caption: caption,
			content: content,
			onHide: callback || $empty
		})
	);
};

ART.Confirm = new Class({

	Extends: ART.Alert,

	options: {
		className: 'alert confirm',
		resizable: false,
		buttons: [
			{
				text: 'Cancel'
			},
			{
				text: 'Ok',
				onPress: function(){
					this.parentWidget.fireEvent('confirm');
				}
			}
		]
	}
});
ART.confirm = function(caption, content, callback, options) {
	return new ART.Confirm(
		$extend(options || {}, {
			caption: caption,
			content: content,
			onConfirm: callback || $empty
		})
	);
};

ART.Prompt = new Class({

	Extends: ART.Confirm,

	options: {
		onShow: function(){
			var input = this.content.getElement('input, textarea');
			if (input) input.select();
		},
		buttons: [
			{
				text: 'Cancel'
			},
			{
				text: 'Ok',
				onPress: function(){
					this.parentWidget.fireEvent('confirm', this.parentWidget.getPromptValue());
				}
			}
		]
	},
	initialize: function(){
		this.parent.apply(this, arguments);
		this.makePromptInput();
	},
	makePromptInput: function(){
		if (!this.content.getElement('form') && !this.content.getElements('input, textarea, select').length) {
			var styles = ART.Sheet.lookupStyle(this.getSelector() + ' input.prompt');
			this.inputContainer = new Element('div', {
				'class': 'inputContainer',
				styles: {
					position: 'relative'
				}
			}).inject(this.content);
			this.input = new Element('input', {
				value: this.options.defaultValue || '',
				type: 'text',
				styles: styles,
				events: {
					keydown: function(e) {
						if (e.key == 'enter') {
							this.fireEvent('confirm', this.getPromptValue());
							this.hide();
							Keyboard.stop(e);
						}
					}.bind(this)
				}
			}).inject(this.inputContainer);
		}
	},
	getPromptValue: function(){
		if (this.input) return this.input.get('value');
		var form = this.content.getElement('form');
		return form ? form.toQueryString() : this.content.toQueryString();
	},
	setContent: function(){
		if (this.inputContainer) this.inputContainer.dispose();
		this.parent.apply(this, arguments);
		if (this.inputContainer) this.inputContainer.inject(this.content);
		return this;
	},
	show: function(){
		this.parent.apply(this, arguments);
		this.alertButtons[0].disable().blur();
		var input = this.content.getElement('input, textarea');
		if (input) input.select();
		return this;
	}
});

ART.prompt = function(caption, content, callback, options) {
	return new ART.Prompt(
		$extend(options || {}, {
			caption: caption,
			content: content,
			onConfirm: callback || $empty
		})
	);
};

ART.Window.AlertTools = new Class({

	Implements: ART.WindowTools,

	alerts: {},

	alert: function(caption, content, callback, options, alerter){
		alerter = alerter || ART.alert;
		var win = this.getWindow();
		var currentKb = win.keyboard.activeKB;
		if (win && !win.alertManager) {
			win.alertManager = new ART.WindowManager(win);
			win.alertManager.setLayer('alerts', 99);
		}
		var winsize = win.getSize();
		options = $merge({
			relativeTo: $(win) || $(this),
			inject: {
				target: $(win) || $(this),
				where: 'bottom'
			},
			mask: true,
			maskOptions: {
				inject: {
					target: $(this.getWindow().content),
					where: 'after'
				}
			},
			constrainToContainer: true,
			width: (winsize.width * 0.9).toInt(),
			max: {
				height: (winsize.height * 0.9).toInt(),
				width: (winsize.width * 0.9).toInt()
			}
		}, options);
		options.parentWidget = win || this;
		options.windowManager = win ? win.alertManager : null;
		var alert = alerter(caption, content, callback, options);
		var enableKB = function(){
			win.keyboard.activate(win.alertManager.keyboard);
			alert.keyboard.activate();
		};
		enableKB();
		if (win) {
			win.fireEvent('alert');
			var shader = function(dragging) {
				$(alert).setStyle('display', dragging ? 'none' : 'block');
			};
			alert.addEvents({
				destroy: function(){
					win.removeEvent('shade', shader);
					win.keyboard.activate(currentKb);
				},
				hide: function(){
					win.keyboard.activate(currentKb);
					win.fireEvent('endAlert');
				},
				show: function(){
					enableKB();
					win.fireEvent('alert');
				}
			});
			win.addEvent('shade', shader);
		}
		return alert;
	},
	confirm: function(cap, cont, fn, opt) {
		return this.alert(cap, cont, fn, opt, ART.confirm);
	},
	prompt: function(cap, cont, fn, opt) {
		return this.alert(cap, cont, fn, opt, ART.prompt);
	}
});

ART.Window.implement(new ART.Window.AlertTools);

})();<|endoftext|>


<commit_before>var tools = require("./quickReplies.js");
var request = require('request');
var variables = require("./variables.js");
var token = process.env.FACEBOOK_TOKEN;
var menu = require("./menu.js");

var self = module.exports = {

	// Function to check if input is numeric
	isNumeric: function(n) {

  		return !isNaN(parseFloat(n)) && isFinite(n);
	},

	// Check if the string just have numbers of characters
	validate: function(strValue) {
  		var objRegExp  = /^[a-zA-Z\u00C0-\u00ff\s]+$/;
  		return objRegExp.test(strValue);

	},

	// Check if character exists in a given string
	checkVar: function(vari, char) {
  		return vari.indexOf(char) > -1;
	},

	// Check for unwanted errors from users
	checkError: function(str, char) {

  		return (str[1].trim() == "" || self.validate(str[1]) == true && self.checkVar(str[1], char) == false);
	},

	// Check for unwanted errors when querying an image
	checkErrorImg: function(str) {

  		return (str[1].trim() == "" || self.validate(str[1]) == true);
	},

	// Convert integer to binary
	toBinary: function(a) {
  		return (a >>> 0).toString(2);
	},

	// Send POST to Facebook graph to load the quick replies
	quickReplies: function(res, buttons) {

  		request({

    		url: 'https://graph.facebook.com/v2.6/me/messages',
    		qs: { access_token : token },
    		method: 'POST',
    		headers: {'Content-Type': 'application/json'},
    		form: buttons
  		},
  		function (error, response, body) {

    	if (!error && response.statusCode == 200) {
      	  console.log("ok");
      	  res.end();

    	} else {
       	 console.log("error");
    	}
  		});

	},

	// Postback for quickreplies function
	receivedPostback: function(res,event) {
   		var senderID = event.sender.id;
    	var recipientID = event.recipient.id;
    	var timeOfMessage = event.timestamp;
    	var payload = event.postback.payload;
    	var replies = tools;

    	switch(payload)
    	{
        case 'getstarted':
            self.quickReplies(res, replies.getStartedEx(senderID));
            break;

        case 'GRAPH_COMMAND':
            self.quickReplies(res, replies.graphEx(senderID));
            break;

        case 'SIMPLIFY_COMMAND':
            self.quickReplies(res, replies.simplifyEx(senderID));
            break;

        case 'DERIVATE_COMMAND':
            self.quickReplies(res, replies.derivateEx(senderID));
            break;

        case 'GCD_COMMAND':
            self.quickReplies(res, replies.gcdEx(senderID));
            break;

        case 'XGCD_COMMAND':
            self.quickReplies(res, replies.xgcdEx(senderID));
            break;

        case 'INTEGRAL_COMMAND':
            self.quickReplies(res, replies.integralEx(senderID));
            break;

        case 'LCM_COMMAND':
            self.quickReplies(res, replies.lcmEx(senderID));
            break;

        case 'SOLVE_COMMAND':
            self.quickReplies(res, replies.solveEx(senderID));
            break;

        case 'TOBINARY_COMMAND':
            self.quickReplies(res, replies.tobinaryEx(senderID));
            break;

        default :
            self.quickReplies(res, replies.getStartedEx(senderID));
        break;
    	}

	},

	// Function to send plain message to the bot
	replyToSender: function(res,sender, text) {
  		messageData = {
   		 text : text
  	};

  	request({
    	url: 'https://graph.facebook.com/v2.6/me/messages',
    	qs: { access_token : token },
    	method: 'POST',
    	json: {
       		recipient: { id : sender },
       		message: messageData,
    	}

 	}, function(error, response, body) {
       	if (!error && response.statusCode == 200) {
      	  console.log("ok");
      	  res.end();

    	} else {
       	 console.log("error");
    	}
     	});
	},

	// Function to retrive image from bot
	replyToSenderImage: function(res,sender, image) {

  		request({
    		url: 'https://graph.facebook.com/v2.6/me/messages',
    		qs: { access_token : token },
    		method: 'POST',
    		json: {
       			recipient: { id : sender },
       			message: {
        			attachment: {
          				type: "image",
          				payload: {
            				url: image
          				}
        			}
      			}
    		}

  		}, function(error, response, body) {

       			if (!error && response.statusCode == 200) {
      	  			console.log("ok");
      	  			res.end();

    			} else {
       	 			console.log("error");
    			}
     	});

	},

	// FACEBOOK SETUP AREA

	// Greeting message for first time interaction with the bot.
	// TODO: Multi-locale
	setupGreetingText: function(res){

  		request({
    		url: 'https://graph.facebook.com/v2.6/me/messenger_profile',
    		qs: { access_token : token },
    		method: 'POST',
    		headers: {'Content-Type': 'application/json'},
    		form: variables.getStartedEx()
  		},
  		function (error, response, body) {

    		if (!error && response.statusCode == 200) {

        		res.send(response.body);
        		res.end();

    		} else {

        		res.send(body);
        		res.end();
    		}
  		});

	},

	// Persistent Menu For Messenger
	setupPersistentMenu: function(res){

		request({
   			url: "https://graph.facebook.com/v2.6/me/messenger_profile",
    		qs: { access_token : token },
    		method: 'POST',
    		headers: {'Content-Type': 'application/json'},
    		form: menu.persistentMenu
		},
		function (error, response, body) {
    		if (!error && response.statusCode == 200) {

        		res.send(response.body);
        		res.end();

    		} else {

        		res.send(body);
        		res.end();
   			}
		});

	},

	// Get started button setup
	setupGetStartedButton: function(res){
		var messageData = {
        	"get_started":{

            	"payload":"getstarted"
        	}
		};

		request({
    		url: "https://graph.facebook.com/v2.6/me/messenger_profile",
    		qs: { access_token : token },
    		method: 'POST',
    		headers: {'Content-Type': 'application/json'},
    		form: messageData
		},
		function (error, response, body) {

    		if (!error && response.statusCode == 200) {

       		 	res.send(response);
       		 	res.end();

    		} else {

        		res.send(body);
        		res.end();
    		}
		});

	}

	// FACEBOOK SETUP END
};
<commit_msg>Convert command added<commit_after>var tools = require("./quickReplies.js");
var request = require('request');
var variables = require("./variables.js");
var token = process.env.FACEBOOK_TOKEN;
var menu = require("./menu.js");

var self = module.exports = {

	// Function to check if input is numeric
	isNumeric: function(n) {

  		return !isNaN(parseFloat(n)) && isFinite(n);
	},

	// Check if the string just have numbers of characters
	validate: function(strValue) {
  		var objRegExp  = /^[a-zA-Z\u00C0-\u00ff\s]+$/;
  		return objRegExp.test(strValue);

	},

	// Check if character exists in a given string
	checkVar: function(vari, char) {
  		return vari.indexOf(char) > -1;
	},

	// Check for unwanted errors from users
	checkError: function(str, char) {

  		return (str[1].trim() == "" || self.validate(str[1]) == true && self.checkVar(str[1], char) == false);
	},

	// Check for unwanted errors when querying an image
	checkErrorImg: function(str) {

  		return (str[1].trim() == "" || self.validate(str[1]) == true);
	},

	// Convert integer to binary
	toBinary: function(a) {
  		return (a >>> 0).toString(2);
	},

	// Send POST to Facebook graph to load the quick replies
	quickReplies: function(res, buttons) {

  		request({

    		url: 'https://graph.facebook.com/v2.6/me/messages',
    		qs: { access_token : token },
    		method: 'POST',
    		headers: {'Content-Type': 'application/json'},
    		form: buttons
  		},
  		function (error, response, body) {

    	if (!error && response.statusCode == 200) {
      	  console.log("ok");
      	  res.end();

    	} else {
       	 console.log("error");
    	}
  		});

	},

	// Postback for quickreplies function
	receivedPostback: function(res,event) {
   		var senderID = event.sender.id;
    	var recipientID = event.recipient.id;
    	var timeOfMessage = event.timestamp;
    	var payload = event.postback.payload;
    	var replies = tools;

    	switch(payload)
    	{
        case 'getstarted':
            self.quickReplies(res, replies.getStartedEx(senderID));
            break;

        case 'GRAPH_COMMAND':
            self.quickReplies(res, replies.graphEx(senderID));
            break;

        case 'SIMPLIFY_COMMAND':
            self.quickReplies(res, replies.simplifyEx(senderID));
            break;

        case 'DERIVATE_COMMAND':
            self.quickReplies(res, replies.derivateEx(senderID));
            break;

        case 'GCD_COMMAND':
            self.quickReplies(res, replies.gcdEx(senderID));
            break;

        case 'XGCD_COMMAND':
            self.quickReplies(res, replies.xgcdEx(senderID));
            break;

        case 'INTEGRAL_COMMAND':
            self.quickReplies(res, replies.integralEx(senderID));
            break;

        case 'LCM_COMMAND':
            self.quickReplies(res, replies.lcmEx(senderID));
            break;

        case 'SOLVE_COMMAND':
            self.quickReplies(res, replies.solveEx(senderID));
            break;

        case 'TOBINARY_COMMAND':
            self.quickReplies(res, replies.tobinaryEx(senderID));
            break;

        case 'CONVERT_COMMAND':
              self.quickReplies(res, replies.convertEx(senderID));
              break;

        default :
            self.quickReplies(res, replies.getStartedEx(senderID));
        break;
    	}

	},

	// Function to send plain message to the bot
	replyToSender: function(res,sender, text) {
  		messageData = {
   		 text : text
  	};

  	request({
    	url: 'https://graph.facebook.com/v2.6/me/messages',
    	qs: { access_token : token },
    	method: 'POST',
    	json: {
       		recipient: { id : sender },
       		message: messageData,
    	}

 	}, function(error, response, body) {
       	if (!error && response.statusCode == 200) {
      	  console.log("ok");
      	  res.end();

    	} else {
       	 console.log("error");
    	}
     	});
	},

	// Function to retrive image from bot
	replyToSenderImage: function(res,sender, image) {

  		request({
    		url: 'https://graph.facebook.com/v2.6/me/messages',
    		qs: { access_token : token },
    		method: 'POST',
    		json: {
       			recipient: { id : sender },
       			message: {
        			attachment: {
          				type: "image",
          				payload: {
            				url: image
          				}
        			}
      			}
    		}

  		}, function(error, response, body) {

       			if (!error && response.statusCode == 200) {
      	  			console.log("ok");
      	  			res.end();

    			} else {
       	 			console.log("error");
    			}
     	});

	},

	// FACEBOOK SETUP AREA

	// Greeting message for first time interaction with the bot.
	// TODO: Multi-locale
	setupGreetingText: function(res){

  		request({
    		url: 'https://graph.facebook.com/v2.6/me/messenger_profile',
    		qs: { access_token : token },
    		method: 'POST',
    		headers: {'Content-Type': 'application/json'},
    		form: variables.getStartedEx()
  		},
  		function (error, response, body) {

    		if (!error && response.statusCode == 200) {

        		res.send(response.body);
        		res.end();

    		} else {

        		res.send(body);
        		res.end();
    		}
  		});

	},

	// Persistent Menu For Messenger
	setupPersistentMenu: function(res){

		request({
   			url: "https://graph.facebook.com/v2.6/me/messenger_profile",
    		qs: { access_token : token },
    		method: 'POST',
    		headers: {'Content-Type': 'application/json'},
    		form: menu.persistentMenu
		},
		function (error, response, body) {
    		if (!error && response.statusCode == 200) {

        		res.send(response.body);
        		res.end();

    		} else {

        		res.send(body);
        		res.end();
   			}
		});

	},

	// Get started button setup
	setupGetStartedButton: function(res){
		var messageData = {
        	"get_started":{

            	"payload":"getstarted"
        	}
		};

		request({
    		url: "https://graph.facebook.com/v2.6/me/messenger_profile",
    		qs: { access_token : token },
    		method: 'POST',
    		headers: {'Content-Type': 'application/json'},
    		form: messageData
		},
		function (error, response, body) {

    		if (!error && response.statusCode == 200) {

       		 	res.send(response);
       		 	res.end();

    		} else {

        		res.send(body);
        		res.end();
    		}
		});

	}

	// FACEBOOK SETUP END
};
<|endoftext|>