<commit_before>/**
 Copyright 2016, RadiantBlue Technologies, Inc.

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 */

(function () {
    'use strict';
    angular
        .module('SAKapp')
        .controller('SearchController', ['$scope', '$http', 'toaster', 'discover', 'Auth', SearchController]);

    function SearchController($scope, $http, toaster, discover, Auth) {
        $scope.size = 100;
        $scope.from = 0;

        $scope.pageOptions = [10, 50, 100];

        $scope.$watch("size", function(newValue, oldValue) {
            $scope.from = 0;
            if (angular.isDefined($scope.searchTerm)) {
                $scope.getSearchResults();
            }
            $scope.searchTerm = "";
        });

        $scope.getResultsCount = function(query) {
            $http({
                method: "POST",
                url: "/proxy/" + discover.searchHost + "/api/v1/recordcount",
                data: query
            }).then(function successCallback( html ) {
                $scope.totalResults = html.data;
                if ($scope.totalResults == 0) {
                    $scope.errorMsg = "No results to display";
                }
            }, function errorCallback(response){
                console.log("search.controller results count fail: " + response.status);
                toaster.pop('error', "Error", "There was an issue with your request.");
            });
        };

        $scope.getSearchResults = function($event) {
            console.log($event);
            $scope.errorMsg = "";
            $scope.tagMsg = "";
            if (!angular.isUndefined($event)) {
                // Reset to first page if new search
                $scope.from = 0;
            }

            var params = {
                from: $scope.from,
                size: $scope.size
            };

            var q = "";

            if (!angular.isUndefined($scope.searchTerm) && $scope.searchTerm !== "") {
                q = {
                    "fuzzy_like_this": {
                        "like_text": $scope.searchTerm
                    }
                };
            } else {
                q = {
                    "match_all": {}
                };
            }

            var data = {
                "userName": "api-key-sakui",
                "jobType": {
                    "type": "search-query",
                    "data": {
                        "query": q,
                        "size": $scope.size,
                        "from": $scope.from
                    }
                }
            };

            var formData = new FormData();
            formData.append('body', JSON.stringify(data));

            $scope.getResultsCount(q);

            $http({
                method: "POST",
                url: "/proxy/" + discover.gatewayHost + "/job",
                data: formData,
                headers: {
                    "Content-Type": undefined,
                    "Authorization": "Basic " + Auth.id
                }
            }).then(function successCallback( html ) {
                $scope.searchResults = html.data.data;
            }, function errorCallback(response){
                console.log("search.controller get search results fail: " + response.status);
                toaster.pop('error', "Error", "There was an issue with your search request.");
            });

        };

        var isUndefinedOrEmpty = function(str) {
            return angular.isUndefined(str) || str.lengh == 0;
        };

        $scope.downloadFile = function(accessData, isFromList) {
            var location;
            if (isFromList) {
                location = accessData.dataType.location;
            } else {
                location = accessData.data.dataType.location;
            }
            var url = "http://" + location.bucketName + "." + location.domainName + "/" + location.fileName;
            window.location=url;
        };

        $scope.addTags = function() {
            $scope.tagMsg = "";

            // Validation
            if (isUndefinedOrEmpty($scope.indexId) ||
                isUndefinedOrEmpty($scope.typeId) ||
                isUndefinedOrEmpty($scope.documentId) ||
                isUndefinedOrEmpty($scope.tag)) {
                $scope.tagMsg = "A required field is empty";
                return;
            }

            var url = "";
            // TODO: Once elasticsearch is completely setup we probably don't need all these inputs
            url = "/proxy?url=" + discover.searchHost + "/" + $scope.indexId + "/" + $scope.typeId + "/" + $scope.documentId;
            $http({
                method: "GET",
                url: url
            }).then(function successCallback( html ) {
                $scope.document = html.data._source;
                var returnObj = {};
                if (angular.isUndefined($scope.document.keywords)) {
                    returnObj = {
                        "doc": {
                            "keywords": [$scope.tag]
                        }
                    };
                } else {
                    var tags = $scope.document.keywords;
                    tags.push($scope.tag);
                    returnObj = {
                        "doc": {
                            "keywords": tags
                        }
                    };
                }

                $http.post(
                    url + "/_update",
                    returnObj,
                    {
                        headers: {
                            "Content-Type": "application/x-www-form-urlencoded"
                        }
                    }
                ).then(function successCallback(res) {
                    $scope.tagMsg = "Keyword added successfully";
                    console.log("Success!");
                }, function errorCallback(res) {
                    console.log("search.controller tag fail: " + res.status);
                    toaster.pop('error', "Error", "There was an issue with your tag request.");
                });

            }, function errorCallback(response){
                console.log("search.controller search fail");
                toaster.pop('error', "Error", "There was an issue with your search request.");
            });

        };

        $scope.nextPage = function() {
            if ($scope.from < $scope.totalResults-$scope.size) {
                $scope.from += $scope.size;
                $scope.getSearchResults();
                $('html, body').scrollTop(0);
                // $('html, body').animate({ scrollTop: 0 }, 'fast');
            }
        };

        $scope.prevPage = function() {
            if ($scope.from > 0) {
                $scope.from -= $scope.size;
                $scope.getSearchResults();
                $('html, body').scrollTop(0);
                // $('html, body').animate({ scrollTop: 0 }, 'fast');
            }
        };

        $scope.getLastIndex = function() {
            var endingPoint = $scope.from + $scope.size;
            if (endingPoint > $scope.totalResults) {
                endingPoint = $scope.totalResults;
            }
            return endingPoint;
        };
    }
})();
<commit_msg>3990 Fix search page to use new gateway api<commit_after>/**
 Copyright 2016, RadiantBlue Technologies, Inc.

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 */

(function () {
    'use strict';
    angular
        .module('SAKapp')
        .controller('SearchController', ['$scope', '$http', 'toaster', 'discover', 'gateway', SearchController]);

    function SearchController($scope, $http, toaster, discover, gateway) {
        $scope.size = 100;
        $scope.from = 0;

        $scope.pageOptions = [10, 50, 100];

        $scope.$watch("size", function(newValue, oldValue) {
            $scope.from = 0;
            if (angular.isDefined($scope.searchTerm)) {
                $scope.getSearchResults();
            }
            $scope.searchTerm = "";
        });

        $scope.getResultsCount = function(query) {
            $http({
                method: "POST",
                url: "/proxy/" + discover.searchHost + "/api/v1/recordcount",
                data: query
            }).then(function successCallback( html ) {
                $scope.totalResults = html.data;
                if ($scope.totalResults == 0) {
                    $scope.errorMsg = "No results to display";
                }
            }, function errorCallback(response){
                console.log("search.controller results count fail: " + response.status);
                toaster.pop('error', "Error", "There was an issue with your request.");
            });
        };

        $scope.getSearchResults = function($event) {
            console.log($event);
            $scope.errorMsg = "";
            $scope.tagMsg = "";
            if (!angular.isUndefined($event)) {
                // Reset to first page if new search
                $scope.from = 0;
            }

            var q = "";

            if (!angular.isUndefined($scope.searchTerm) && $scope.searchTerm !== "") {
                q = {
                    "match": {
                        "_all": $scope.searchTerm
                    }
                };
            } else {
                q = {
                    "match_all": {}
                };
            }

            var data = {
                "query": q,
                "size": $scope.size,
                "from": $scope.from
            };

            $scope.getResultsCount(q);

            gateway.async(
                "POST",
                "/data/query",
                data
            ).then(function successCallback( html ) {
                $scope.searchResults = html.data.data;
            }, function errorCallback(response){
                console.log("search.controller get search results fail: " + response.status);
                toaster.pop('error', "Error", "There was an issue with your search request.");
            });

        };

        var isUndefinedOrEmpty = function(str) {
            return angular.isUndefined(str) || str.lengh == 0;
        };

        $scope.downloadFile = function(accessData, isFromList) {
            var location;
            if (isFromList) {
                location = accessData.dataType.location;
            } else {
                location = accessData.data.dataType.location;
            }
            var url = "http://" + location.bucketName + "." + location.domainName + "/" + location.fileName;
            window.location=url;
        };

        $scope.addTags = function() {
            $scope.tagMsg = "";

            // Validation
            if (isUndefinedOrEmpty($scope.indexId) ||
                isUndefinedOrEmpty($scope.typeId) ||
                isUndefinedOrEmpty($scope.documentId) ||
                isUndefinedOrEmpty($scope.tag)) {
                $scope.tagMsg = "A required field is empty";
                return;
            }

            var url = "";
            // TODO: Once elasticsearch is completely setup we probably don't need all these inputs
            url = "/proxy?url=" + discover.searchHost + "/" + $scope.indexId + "/" + $scope.typeId + "/" + $scope.documentId;
            $http({
                method: "GET",
                url: url
            }).then(function successCallback( html ) {
                $scope.document = html.data._source;
                var returnObj = {};
                if (angular.isUndefined($scope.document.keywords)) {
                    returnObj = {
                        "doc": {
                            "keywords": [$scope.tag]
                        }
                    };
                } else {
                    var tags = $scope.document.keywords;
                    tags.push($scope.tag);
                    returnObj = {
                        "doc": {
                            "keywords": tags
                        }
                    };
                }

                $http.post(
                    url + "/_update",
                    returnObj,
                    {
                        headers: {
                            "Content-Type": "application/x-www-form-urlencoded"
                        }
                    }
                ).then(function successCallback(res) {
                    $scope.tagMsg = "Keyword added successfully";
                    console.log("Success!");
                }, function errorCallback(res) {
                    console.log("search.controller tag fail: " + res.status);
                    toaster.pop('error', "Error", "There was an issue with your tag request.");
                });

            }, function errorCallback(response){
                console.log("search.controller search fail");
                toaster.pop('error', "Error", "There was an issue with your search request.");
            });

        };

        $scope.nextPage = function() {
            if ($scope.from < $scope.totalResults-$scope.size) {
                $scope.from += $scope.size;
                $scope.getSearchResults();
                $('html, body').scrollTop(0);
                // $('html, body').animate({ scrollTop: 0 }, 'fast');
            }
        };

        $scope.prevPage = function() {
            if ($scope.from > 0) {
                $scope.from -= $scope.size;
                $scope.getSearchResults();
                $('html, body').scrollTop(0);
                // $('html, body').animate({ scrollTop: 0 }, 'fast');
            }
        };

        $scope.getLastIndex = function() {
            var endingPoint = $scope.from + $scope.size;
            if (endingPoint > $scope.totalResults) {
                endingPoint = $scope.totalResults;
            }
            return endingPoint;
        };
    }
})();
<|endoftext|>


<commit_before>(function(W) {
	'use strict';

	W.fn.make('events', {
		// Add bindings to bound object with optional exec object and/or init boolean
		// DEPRECATED
		map: function(evts, a, b) {
			this.$set('mapped', W.$extend(this.$get('mapped', {}), evts));

			if (a === true || b === true) {
				this.bind(evts, a === true ? {} : a);
			}
		},
		// Bind against elements stored in the DOM reference
		// DEPRECATED
		bind: function(evts, opt) {
			var mapped = evts || this.$get('mapped');
			evts = [];

			if (mapped) {
				var keys = Object.keys(mapped),
					i = 0;

				for (; i < keys.length; i++) {
					var key = keys[i];
					evts['ref:' + key] = mapped[key];
				}

				this.on(W.$extend({}, evts), opt);
			}
		},
		// Remove bindings to bound object
		// DEPRECATED
		unbind: function(id, evt) {
			this.off('ref:' + id, evt);
		},
		// Execute specific function by name and event
		// DEPRECATED
		fire: function(name, evt) {
			var bound = this.$get('mapped');

			if (bound.hasOwnProperty(name) && bound[name].hasOwnProperty(evt)) {
				W.$exec(bound[name][evt]);
			}
		},
		// Bind specified function to specified element and event
		// Options include arguments, context, one, scope, and delegate
		on: function(target, a, b, c) {
			var evts = [];

			if (W.$isObject(target) && ! target._$) {
				var keys = Object.keys(target),
					i = 0;

				for (; i < keys.length; i++) {
					var key = keys[i];
					evts = target[key];

					this.$private('bind', key, evts, a);
				}
			} else {
				if (W.$isString(a)) {
					evts[a] = b;
				} else {
					evts = a;
					c = b;
				}

				this.$private('bind', target, evts, c);
			}
		},
		// Bind specified function to specified element and event for single execution
		// DEPRECATED
		one: function(sel, a, b, c) {
			if (W.$isString(a)) {
				var obj = [];
				obj[a] = b;
				a = obj;
			} else {
				c = b;
			}

			this.on(sel, a, W.$extend({
				one: true
			}, c));
		},
		// Remove specified function to specified element and optional event|function
		off: function(target, a, b) {
			var obj = a;

			if (a) {
				if (W.$isString(a)) {
					obj = [];
					obj[a] = b;
				}

				for (var key in obj) {
					var evts = key.split(' '),
						i = 0;

					for (; i < evts.length; i++) {
						var evt = evts[i],
						fn = obj[evt];

						this.$private('off', target, evt, fn);
					}
				}
			} else {
				this.$private('off', target);
			}
		},
		// Get currently bound events to optional specified element and event|function
		// Returns array of objects
		bound: function(target, event, fn) {
			var bound = this.$get('evts'),
				matches = [];

			if (bound) {
				if (target) {
					W.$each(target, function(el) {
						for (var e in bound) {
							var ev = bound[e];

							if (el !== ev.el || (event && (event !== ev.ev || (fn && String(fn) !== String(ev.fn))))) {
								continue;
							}

							matches.push(ev);
						}
					});
				} else {
					return bound;
				}
			}

			return matches;
		},
		// Execute event for each matching selection
		trigger: function(target, event) {
			W.$each(target, function(el) {
				if (W._win.createEvent) {
					var ev = W._win.createEvent('HTMLEvents');

					ev.initEvent(event, true, false);
					el.dispatchEvent(ev);
				} else {
					el.fireEvent('on' + event);
				}
			});
		}
	}, {
		bind: function(els, obj, c) {
			var scope = this;

			// Redefine variables when delegating
			if (c && c.delegate) {
				c.targ = els;
				els = c.delegate;
			}

			// For each element attach events
			W.$each(els, function(el) {
				// Loop through object events
				for (var key in obj) {
					var evts = key.split(' '),
						i = 0;

					for (; i < evts.length; i++) {
						var conf = W.$extend({
								args: [],
								one: false,
								scope: el
							}, c),
							fn = obj[key],
							evt = evts[i],
							ev = evt,
							f = fn;

						if (evt == 'mouseenter' || evt == 'mouseleave') {
							conf.args.unshift(fn);

							fn = scope.mouseEvent;
							evt = 'mouse' + ((evt == 'mouseenter') ? 'over' : 'out');

							conf.args.unshift(0, c && c.targ ? c.targ : el);
						} else {
							conf.args.unshift(0, el);
						}

						(function(el, evt, fn, f, conf) {
							var cb = function(e) {
								if (W._legacy) {
									e = W._win.event;
									e.target = e.srcElement;

									e.preventDefault = function() {
										e.returnValue = false;
									};
								}

								conf.args[0] = e;

								// If watch within parent make sure the target matches the selector
								if (conf.targ) {
									var t = conf.targ,
										sel = t._$ ? t.sel : t;

									// Update refs when targeting ref
									if (sel.indexOf('ref:') > -1) {
										W.$setRef(el);
									}

									t = W.$toArray(W.$(sel));

									if (! t.some(function(par) {
										return par.contains(e.target);
									})) {
										return false;
									}
								}

								W.$exec(fn, conf);

								// If the event is to be executed once unbind it immediately
								if (conf.one) {
									scope.$public.off(el, evt, f);
								}
							};

							// Ensure the specified element, event, and function combination hasn't already been bound
							if (evt != 'init' && scope.$public.bound(el, ev, f).length < 1) {
								W._legacy ?
									el.attachEvent('on' + evt, cb) :
									el.addEventListener(evt, cb);

								scope.$push('evts', {
									el: el,
									ev: ev,
									evt: evt,
									cb: cb,
									fn: f
								});
							}

							if (evt == 'init' || conf.init === true) {
								cb();
							}
						})(el, evt, fn, f, conf);
					}
				}
			});
		},
		// Ensure mouse has actually entered or left root element before firing event
		mouseEvent: function(e, parent, fn) {
			var child = e.relatedTarget,
				checkParent = function(parent, child) {
					if (parent !== child) {
						while (child && child !== parent) {
							child = child.parentNode;
						}

						return child === parent;
					}

					return false;
				};

			if (child === parent || checkParent(parent, child)) {
				return;
			}

			var args = W._slice.call(arguments);
			args.splice(2, 1);

			W.$exec(fn, {
				args: args,
				scope: this
			});
		},
		off: function(sel, evt, fn) {
			W.$each(this.$public.bound(sel, evt, fn), function(e) {
				W._legacy ?
					e.el.detachEvent('on' + e.evt, e.cb) :
					e.el.removeEventListener(e.evt, e.cb);

				// Remove object from the bound array
				var bound = this.$get('evts');

				bound.splice(bound.indexOf(e), 1);
			}, {
				scope: this
			});
		}
	});
})(Wee);<commit_msg>Resolve legacy check in trigger method<commit_after>(function(W) {
	'use strict';

	W.fn.make('events', {
		// Add bindings to bound object with optional exec object and/or init boolean
		// DEPRECATED
		map: function(evts, a, b) {
			this.$set('mapped', W.$extend(this.$get('mapped', {}), evts));

			if (a === true || b === true) {
				this.bind(evts, a === true ? {} : a);
			}
		},
		// Bind against elements stored in the DOM reference
		// DEPRECATED
		bind: function(evts, opt) {
			var mapped = evts || this.$get('mapped');
			evts = [];

			if (mapped) {
				var keys = Object.keys(mapped),
					i = 0;

				for (; i < keys.length; i++) {
					var key = keys[i];
					evts['ref:' + key] = mapped[key];
				}

				this.on(W.$extend({}, evts), opt);
			}
		},
		// Remove bindings to bound object
		// DEPRECATED
		unbind: function(id, evt) {
			this.off('ref:' + id, evt);
		},
		// Execute specific function by name and event
		// DEPRECATED
		fire: function(name, evt) {
			var bound = this.$get('mapped');

			if (bound.hasOwnProperty(name) && bound[name].hasOwnProperty(evt)) {
				W.$exec(bound[name][evt]);
			}
		},
		// Bind specified function to specified element and event
		// Options include arguments, context, one, scope, and delegate
		on: function(target, a, b, c) {
			var evts = [];

			if (W.$isObject(target) && ! target._$) {
				var keys = Object.keys(target),
					i = 0;

				for (; i < keys.length; i++) {
					var key = keys[i];
					evts = target[key];

					this.$private('bind', key, evts, a);
				}
			} else {
				if (typeof a == 'string') {
					evts[a] = b;
				} else {
					evts = a;
					c = b;
				}

				this.$private('bind', target, evts, c);
			}
		},
		// Bind specified function to specified element and event for single execution
		// DEPRECATED
		one: function(sel, a, b, c) {
			if (typeof a == 'string') {
				var obj = [];
				obj[a] = b;
				a = obj;
			} else {
				c = b;
			}

			this.on(sel, a, W.$extend({
				one: true
			}, c));
		},
		// Remove specified function to specified element and optional event|function
		off: function(target, a, b) {
			var obj = a;

			if (a) {
				if (typeof a == 'string') {
					obj = [];
					obj[a] = b;
				}

				for (var key in obj) {
					var evts = key.split(' '),
						i = 0;

					for (; i < evts.length; i++) {
						var evt = evts[i],
						fn = obj[evt];

						this.$private('off', target, evt, fn);
					}
				}
			} else {
				this.$private('off', target);
			}
		},
		// Get currently bound events to optional specified element and event|function
		// Returns array of objects
		bound: function(target, event, fn) {
			var bound = this.$get('evts'),
				matches = [];

			if (bound) {
				if (target) {
					W.$each(target, function(el) {
						for (var e in bound) {
							var ev = bound[e];

							if (el !== ev.el || (event && (event !== ev.ev || (fn && String(fn) !== String(ev.fn))))) {
								continue;
							}

							matches.push(ev);
						}
					});
				} else {
					return bound;
				}
			}

			return matches;
		},
		// Execute event for each matching selection
		trigger: function(target, event) {
			W.$each(target, function(el) {
				if (W._doc.createEvent) {
					var ev = W._doc.createEvent('HTMLEvents');

					ev.initEvent(event, true, false);
					el.dispatchEvent(ev);
				} else {
					el.fireEvent('on' + event);
				}
			});
		}
	}, {
		bind: function(els, obj, c) {
			var scope = this;

			// Redefine variables when delegating
			if (c && c.delegate) {
				c.targ = els;
				els = c.delegate;
			}

			// For each element attach events
			W.$each(els, function(el) {
				// Loop through object events
				for (var key in obj) {
					var evts = key.split(' '),
						i = 0;

					for (; i < evts.length; i++) {
						var conf = W.$extend({
								args: [],
								one: false,
								scope: el
							}, c),
							fn = obj[key],
							evt = evts[i],
							ev = evt,
							f = fn;

						if (evt == 'mouseenter' || evt == 'mouseleave') {
							conf.args.unshift(fn);

							fn = scope.mouseEvent;
							evt = 'mouse' + ((evt == 'mouseenter') ? 'over' : 'out');

							conf.args.unshift(0, c && c.targ ? c.targ : el);
						} else {
							conf.args.unshift(0, el);
						}

						(function(el, evt, fn, f, conf) {
							var cb = function(e) {
								if (W._legacy) {
									e = W._win.event;
									e.target = e.srcElement;

									e.preventDefault = function() {
										e.returnValue = false;
									};
								}

								conf.args[0] = e;

								// If watch within parent make sure the target matches the selector
								if (conf.targ) {
									var t = conf.targ,
										sel = t._$ ? t.sel : t;

									// Update refs when targeting ref
									if (sel.indexOf('ref:') > -1) {
										W.$setRef(el);
									}

									t = W.$toArray(W.$(sel));

									if (! t.some(function(par) {
										return par.contains(e.target);
									})) {
										return false;
									}
								}

								W.$exec(fn, conf);

								// If the event is to be executed once unbind it immediately
								if (conf.one) {
									scope.$public.off(el, evt, f);
								}
							};

							// Ensure the specified element, event, and function combination hasn't already been bound
							if (evt != 'init' && scope.$public.bound(el, ev, f).length < 1) {
								W._legacy ?
									el.attachEvent('on' + evt, cb) :
									el.addEventListener(evt, cb);

								scope.$push('evts', {
									el: el,
									ev: ev,
									evt: evt,
									cb: cb,
									fn: f
								});
							}

							if (evt == 'init' || conf.init === true) {
								cb();
							}
						})(el, evt, fn, f, conf);
					}
				}
			});
		},
		// Ensure mouse has actually entered or left root element before firing event
		mouseEvent: function(e, parent, fn) {
			var child = e.relatedTarget,
				checkParent = function(parent, child) {
					if (parent !== child) {
						while (child && child !== parent) {
							child = child.parentNode;
						}

						return child === parent;
					}

					return false;
				};

			if (child === parent || checkParent(parent, child)) {
				return;
			}

			var args = W._slice.call(arguments);
			args.splice(2, 1);

			W.$exec(fn, {
				args: args,
				scope: this
			});
		},
		off: function(sel, evt, fn) {
			W.$each(this.$public.bound(sel, evt, fn), function(e) {
				W._legacy ?
					e.el.detachEvent('on' + e.evt, e.cb) :
					e.el.removeEventListener(e.evt, e.cb);

				// Remove object from the bound array
				var bound = this.$get('evts');

				bound.splice(bound.indexOf(e), 1);
			}, {
				scope: this
			});
		}
	});
})(Wee);<|endoftext|>


<commit_before>(function() {
  'use strict';

  angular.module('searchSelect', ['ngSanitize']).directive('searchSelect', searchSelect);

  /** @ngInject */
  function searchSelect() {
    var directive = {
      require: "ngModel",
      restrict: 'EA',
      templateUrl: 'search-select.html',
      scope: {
        ngModel: '=',
        options: '=',
        disabled: '=?',
        required: '=?',
        idKey: '@',
        labelKeys: '@',
        placeholderText: '@',
        fontAwesomeIcon: '@'
      },
      controller: SearchSelectController,
      controllerAs: 'searchSelect',
      bindToController: true,
      link: function(scope, elt, attrs, ctrl){
        scope.triggerNgChange = function(value){
          ctrl.$setViewValue(value);
        };
      }
    };

    return directive;

    /** @ngInject */
    function SearchSelectController($scope, $sanitize) {
      var vm = this;

      var inputHandler = new KeyInputHandler();
      var labelKeys = vm.labelKeys.split(' ');
      var options = angular.copy(vm.options);
      var readyForKeyInput = true;

      vm.filteredOptions = {};
      vm.isOptionSelected = false;
      vm.keyboardFocusIndex = null;
      vm.searching = false;
      vm.searchString = '';
      vm.selectedIndex = null;

      vm.searchOptions = searchOptions;
      vm.selectOption = selectOption;
      vm.ssBlur = ssBlur;
      vm.ssFocus = ssFocus;

      //Watching the option list for pages that may not
      //have it as soon as the page loads.
      $scope.$watch(function(){
        return vm.options;
      }, function(newVal, oldVal){
        options = angular.copy(vm.options);
        intializeSearchSelect();
      }, true);

      function intializeSearchSelect(){
        validateParams();
        setParamDefaults();
        setIsOptionSelectedBoolean();
        for (var i=0; i<options.length; i++){
          setOptionIndex(i);
          checkIfSelected(i);
          setOptionDisplayName(i);
        }
        vm.filteredOptions = options;
        setSearchStringToOptionName();
      }

      function validateParams(){
        //consider a stronger set of vm.ngModel validations
        if (isUndefined(vm.ngModel)) {
          throw new Error('ngModel variable for storing selected option is undefined.');
        }
        if (!isUndefinedOrEmptyString(vm.idKey) && !vm.options[0].hasOwnProperty(vm.idKey)){
          throw new Error('No option attribute matched with specified idKey.');
        }
      }

      function setParamDefaults(){
        if (isUndefined(vm.idKey) || vm.idKey.trim() === '') { vm.idKey = 'id'; }

      function setIsOptionSelectedBoolean(){
        if (vm.ngModel !== null && Object.keys(vm.ngModel).length !== 0){
          vm.isOptionSelected = true;
          return;
        }
        vm.isOptionSelected = false;
      }

      function setOptionIndex(i){
        options[i].ss_index = i;
      }

      //Sets selected index if an option is already selected.
      function checkIfSelected(i){
        if (!vm.isOptionSelected) { return; }
        if (vm.ngModel[vm.idKey] === options[i][vm.idKey]){
          vm.ngModel = options[i];
          vm.selectedIndex = i;
        }
      }

      //Sets the ss_display_name for an option based on the
      //keys specified in the labelKeys variable.
      function setOptionDisplayName(i){
        var option = options[i];
        var ss_display_name = '';
        for (var j=0; j<labelKeys.length; j++){
          var key = labelKeys[j];
          if (!isUndefined(option[key])){
            ss_display_name += (option[key] + ' ');
          }
        }
        if (ss_display_name === ''){
          throw new Error('No option attribute matched with any key in labelKeys.');
        }
        ss_display_name = ss_display_name.slice(0, -1);
        options[i].ss_display_name = ss_display_name;
        options[i].ss_display_html = $sanitize(ss_display_name);
      }

      function selectOption(option){
        vm.ngModel = option;
        vm.selectedIndex = option.ss_index;
        vm.isOptionSelected = true;
        $scope.triggerNgChange(option);
        setSearchStringToOptionName();
      }

      function setSearchStringToOptionName(){
        vm.searching = false;
        if (vm.selectedIndex === null){
          vm.searchString = "";
          return;
        }
        vm.searchString = options[vm.selectedIndex].ss_display_name;
      }

      //Enables arrow key detection and resets search.
      function ssFocus(){
        if (vm.disabled) { return; }
        $element.on('keydown', inputHandler.run);
        $element.on('keyup', refreshKeyInput);

        resetSearch();
        searchOptions();
      }

      function resetSearch(){
        vm.searching = true;
        vm.searchString = '';
      }

      //Disables arrow key detection and sets the displayed input string.
      function ssBlur(){
        vm.keyboardFocusIndex = null;
        $element.off('keydown', inputHandler.run);
        $element.off('keyup', refreshKeyInput);
        setSearchStringToOptionName();
      }

      //Main search function.
      function searchOptions(){
        if (vm.searchString === '' || isUndefined(vm.searchString)){
          vm.filteredOptions = options;
          return;
        }

        var result = [];
        var searchString = vm.searchString.toLowerCase();

        for (var i=0; i<options.length; i++){
          var name = options[i].ss_display_name;
          var searchIndex = name.toLowerCase().indexOf(searchString);
          if (searchIndex !== -1){
            var option = angular.copy(options[i]);
            //Splitting option display name in order to style the matched substring.
            var substringOne = option.ss_display_name.substring(0, searchIndex);
            var substringTwo = option.ss_display_name.substring(searchIndex, searchIndex + searchString.length);
            var substringThree = option.ss_display_name.substring(searchIndex + searchString.length);
            option.ss_display_html = buildDisplayHtml(substringOne, substringTwo, substringThree);

            result.push(option);
          }
        }
        vm.filteredOptions = result;
      }

      //An object for handling key inputs while focused on search-select.
      function KeyInputHandler(){
        this.run = function(e){
          if (readyForKeyInput === false){
            return;
          }
          var key = e.keyCode ? e.keyCode : e.which;
          readyForKeyInput = false;

          if (key === 38) { up(); }
          if (key === 40) { down(); }
          if (key === 13) { enter(e); }
          if (key === 27) { escape(e); }

          $scope.$apply();
        };

        //Move to previous option on up key press.
        function up(){
          if (vm.keyboardFocusIndex === 0 || vm.keyboardFocusIndex === null){
            return;
          }
          vm.keyboardFocusIndex -= 1;
          adjustScroll(false);
        }

        //Move to next option on down key press.
        function down(){
          if (vm.keyboardFocusIndex === null){
            vm.keyboardFocusIndex = 0;
            adjustScroll(true);
            $scope.$apply();
            return;
          }
          if (vm.keyboardFocusIndex >= vm.filteredOptions.length - 1){
            return;
          }
          vm.keyboardFocusIndex += 1;
          adjustScroll(true);
        }

        //Close out search and select option on enter key press.
        function enter(e){
          if (vm.keyboardFocusIndex === null){
            return;
          }
          selectOption(vm.filteredOptions[vm.keyboardFocusIndex]);
          e.target.blur();
          readyForKeyInput = true;
        }

        //Close out search on escape key press.
        function escape(e){
          e.target.blur();
          readyForKeyInput = true;
        }

        //Adjusts the scroll value of the list based on which listItem is currently focused.
        function adjustScroll(isDownKey){
          var listId = 'option-list';
          var listItemId = 'option-list-item-' + vm.keyboardFocusIndex;

          //Gets the "next" list item based on whether the down key or up key was pressed.
          var nextListItemDirection = isDownKey ? 1 : -1;
          var nextListItemId = 'option-list-item-' + (vm.keyboardFocusIndex + nextListItemDirection);

          var list = document.getElementById(listId);
          var listItem = document.getElementById(listItemId);
          var nextListItem = document.getElementById(nextListItemId) || listItem;

          //Adjusts scroll value when the nextListItem is ~below~ the viewable window.
          if (nextListItem.offsetTop >= (list.offsetHeight + list.scrollTop)){
            list.scrollTop = nextListItem.offsetTop - list.offsetHeight + nextListItem.offsetHeight;
          }

          //Adjusts scroll value when the nextListItem is ~above~ the viewable window.
          if (list.scrollTop > nextListItem.offsetTop){
            list.scrollTop = nextListItem.offsetTop;
          }
        }
      }

      //Takes the divided display name, wraps the matched substring in a bold-styled span,
      //and creates the displayHtml.
      function buildDisplayHtml(substringOne, substringTwo, substringThree){
        var displayHtml = '';
        var boldSubstring = '' + substringTwo + '';

        return $sanitize(substringOne + boldSubstring + substringThree);
      }

      function refreshKeyInput(e){
        readyForKeyInput = true;
      }

      function isUndefined(variable){
        return (typeof variable === 'undefined') ? true : false;
      }

      function isUndefinedOrEmptyString(variable){
        return (isUndefined(variable) || variable.trim() === '') ? true : false;
      }
      }
    }
  }

})();
<commit_msg>Refactoring setParamDefaults function to use new helper shorthand.<commit_after>(function() {
  'use strict';

  angular.module('searchSelect', ['ngSanitize']).directive('searchSelect', searchSelect);

  /** @ngInject */
  function searchSelect() {
    var directive = {
      require: "ngModel",
      restrict: 'EA',
      templateUrl: 'search-select.html',
      scope: {
        ngModel: '=',
        options: '=',
        disabled: '=?',
        required: '=?',
        idKey: '@',
        labelKeys: '@',
        placeholderText: '@',
        fontAwesomeIcon: '@'
      },
      controller: SearchSelectController,
      controllerAs: 'searchSelect',
      bindToController: true,
      link: function(scope, elt, attrs, ctrl){
        scope.triggerNgChange = function(value){
          ctrl.$setViewValue(value);
        };
      }
    };

    return directive;

    /** @ngInject */
    function SearchSelectController($scope, $sanitize) {
      var vm = this;

      var inputHandler = new KeyInputHandler();
      var labelKeys = vm.labelKeys.split(' ');
      var options = angular.copy(vm.options);
      var readyForKeyInput = true;

      vm.filteredOptions = {};
      vm.isOptionSelected = false;
      vm.keyboardFocusIndex = null;
      vm.searching = false;
      vm.searchString = '';
      vm.selectedIndex = null;

      vm.searchOptions = searchOptions;
      vm.selectOption = selectOption;
      vm.ssBlur = ssBlur;
      vm.ssFocus = ssFocus;

      //Watching the option list for pages that may not
      //have it as soon as the page loads.
      $scope.$watch(function(){
        return vm.options;
      }, function(newVal, oldVal){
        options = angular.copy(vm.options);
        intializeSearchSelect();
      }, true);

      function intializeSearchSelect(){
        validateParams();
        setParamDefaults();
        setIsOptionSelectedBoolean();
        for (var i=0; i<options.length; i++){
          setOptionIndex(i);
          checkIfSelected(i);
          setOptionDisplayName(i);
        }
        vm.filteredOptions = options;
        setSearchStringToOptionName();
      }

      function validateParams(){
        //consider a stronger set of vm.ngModel validations
        if (isUndefined(vm.ngModel)) {
          throw new Error('ngModel variable for storing selected option is undefined.');
        }
        if (!isUndefinedOrEmptyString(vm.idKey) && !vm.options[0].hasOwnProperty(vm.idKey)){
          throw new Error('No option attribute matched with specified idKey.');
        }
      }

      function setParamDefaults(){
        if (isUndefinedOrEmptyString(vm.idKey)) { vm.idKey = 'id'; }
      }

      function setIsOptionSelectedBoolean(){
        if (vm.ngModel !== null && Object.keys(vm.ngModel).length !== 0){
          vm.isOptionSelected = true;
          return;
        }
        vm.isOptionSelected = false;
      }

      function setOptionIndex(i){
        options[i].ss_index = i;
      }

      //Sets selected index if an option is already selected.
      function checkIfSelected(i){
        if (!vm.isOptionSelected) { return; }
        if (vm.ngModel[vm.idKey] === options[i][vm.idKey]){
          vm.ngModel = options[i];
          vm.selectedIndex = i;
        }
      }

      //Sets the ss_display_name for an option based on the
      //keys specified in the labelKeys variable.
      function setOptionDisplayName(i){
        var option = options[i];
        var ss_display_name = '';
        for (var j=0; j<labelKeys.length; j++){
          var key = labelKeys[j];
          if (!isUndefined(option[key])){
            ss_display_name += (option[key] + ' ');
          }
        }
        if (ss_display_name === ''){
          throw new Error('No option attribute matched with any key in labelKeys.');
        }
        ss_display_name = ss_display_name.slice(0, -1);
        options[i].ss_display_name = ss_display_name;
        options[i].ss_display_html = $sanitize(ss_display_name);
      }

      function selectOption(option){
        vm.ngModel = option;
        vm.selectedIndex = option.ss_index;
        vm.isOptionSelected = true;
        $scope.triggerNgChange(option);
        setSearchStringToOptionName();
      }

      function setSearchStringToOptionName(){
        vm.searching = false;
        if (vm.selectedIndex === null){
          vm.searchString = "";
          return;
        }
        vm.searchString = options[vm.selectedIndex].ss_display_name;
      }

      //Enables arrow key detection and resets search.
      function ssFocus(){
        if (vm.disabled) { return; }
        $element.on('keydown', inputHandler.run);
        $element.on('keyup', refreshKeyInput);

        resetSearch();
        searchOptions();
      }

      function resetSearch(){
        vm.searching = true;
        vm.searchString = '';
      }

      //Disables arrow key detection and sets the displayed input string.
      function ssBlur(){
        vm.keyboardFocusIndex = null;
        $element.off('keydown', inputHandler.run);
        $element.off('keyup', refreshKeyInput);
        setSearchStringToOptionName();
      }

      //Main search function.
      function searchOptions(){
        if (vm.searchString === '' || isUndefined(vm.searchString)){
          vm.filteredOptions = options;
          return;
        }

        var result = [];
        var searchString = vm.searchString.toLowerCase();

        for (var i=0; i<options.length; i++){
          var name = options[i].ss_display_name;
          var searchIndex = name.toLowerCase().indexOf(searchString);
          if (searchIndex !== -1){
            var option = angular.copy(options[i]);
            //Splitting option display name in order to style the matched substring.
            var substringOne = option.ss_display_name.substring(0, searchIndex);
            var substringTwo = option.ss_display_name.substring(searchIndex, searchIndex + searchString.length);
            var substringThree = option.ss_display_name.substring(searchIndex + searchString.length);
            option.ss_display_html = buildDisplayHtml(substringOne, substringTwo, substringThree);

            result.push(option);
          }
        }
        vm.filteredOptions = result;
      }

      //An object for handling key inputs while focused on search-select.
      function KeyInputHandler(){
        this.run = function(e){
          if (readyForKeyInput === false){
            return;
          }
          var key = e.keyCode ? e.keyCode : e.which;
          readyForKeyInput = false;

          if (key === 38) { up(); }
          if (key === 40) { down(); }
          if (key === 13) { enter(e); }
          if (key === 27) { escape(e); }

          $scope.$apply();
        };

        //Move to previous option on up key press.
        function up(){
          if (vm.keyboardFocusIndex === 0 || vm.keyboardFocusIndex === null){
            return;
          }
          vm.keyboardFocusIndex -= 1;
          adjustScroll(false);
        }

        //Move to next option on down key press.
        function down(){
          if (vm.keyboardFocusIndex === null){
            vm.keyboardFocusIndex = 0;
            adjustScroll(true);
            $scope.$apply();
            return;
          }
          if (vm.keyboardFocusIndex >= vm.filteredOptions.length - 1){
            return;
          }
          vm.keyboardFocusIndex += 1;
          adjustScroll(true);
        }

        //Close out search and select option on enter key press.
        function enter(e){
          if (vm.keyboardFocusIndex === null){
            return;
          }
          selectOption(vm.filteredOptions[vm.keyboardFocusIndex]);
          e.target.blur();
          readyForKeyInput = true;
        }

        //Close out search on escape key press.
        function escape(e){
          e.target.blur();
          readyForKeyInput = true;
        }

        //Adjusts the scroll value of the list based on which listItem is currently focused.
        function adjustScroll(isDownKey){
          var listId = 'option-list';
          var listItemId = 'option-list-item-' + vm.keyboardFocusIndex;

          //Gets the "next" list item based on whether the down key or up key was pressed.
          var nextListItemDirection = isDownKey ? 1 : -1;
          var nextListItemId = 'option-list-item-' + (vm.keyboardFocusIndex + nextListItemDirection);

          var list = document.getElementById(listId);
          var listItem = document.getElementById(listItemId);
          var nextListItem = document.getElementById(nextListItemId) || listItem;

          //Adjusts scroll value when the nextListItem is ~below~ the viewable window.
          if (nextListItem.offsetTop >= (list.offsetHeight + list.scrollTop)){
            list.scrollTop = nextListItem.offsetTop - list.offsetHeight + nextListItem.offsetHeight;
          }

          //Adjusts scroll value when the nextListItem is ~above~ the viewable window.
          if (list.scrollTop > nextListItem.offsetTop){
            list.scrollTop = nextListItem.offsetTop;
          }
        }
      }

      //Takes the divided display name, wraps the matched substring in a bold-styled span,
      //and creates the displayHtml.
      function buildDisplayHtml(substringOne, substringTwo, substringThree){
        var displayHtml = '';
        var boldSubstring = '' + substringTwo + '';

        return $sanitize(substringOne + boldSubstring + substringThree);
      }

      function refreshKeyInput(e){
        readyForKeyInput = true;
      }

      function isUndefined(variable){
        return (typeof variable === 'undefined') ? true : false;
      }

      function isUndefinedOrEmptyString(variable){
        return (isUndefined(variable) || variable.trim() === '') ? true : false;
      }
      }
    }
  }

})();
<|endoftext|>


<commit_before>/** @jsx React.DOM */

// JSX Annotation
// --------------

// The `@jsx React.DOM` annotation above tells the JSXTransformer to compile
// this file as JSX. It has to be the first line in the file, which is why
// the description you're reading is below the annotation.

// Todo Model
// ----------

// Our basic **Todo** model has `title` and `done` attributes.
var Todo = Backbone.Model.extend({

  // Default attributes for the Todo item.
  defaults: function() {
    return {
      title: "",
      done: false
    };
  }

});

// Todo Collection
// ---------------

// The collection of Todos is backed by *localStorage* instead of a remote
// server.
var TodoList = Backbone.Collection.extend({

  // Reference to this collection's model.
  model: Todo,

  // Save all of the todo items under the `"todos-react"` namespace.
  localStorage: new Backbone.LocalStorage("todos-react"),

  // Filter down the list of all todo items that are finished.
  done: function() {
    return this.where({done: true});
  },

  // Filter down the list to only todo items that are still not finished.
  remaining: function() {
    return this.where({done: false});
  }

});

// Backbone/React Integration
// --------------------------

// Notifies React components when their Backbone models or collections change.
var BackboneMixin = {

  // Listen to all events on this component's collection or model and force an
  // update when they fire. Let React decide whether the DOM should change.
  componentDidMount: function() {
    this._boundForceUpdate = this.forceUpdate.bind(this, null);
    this.getBackboneObject().on("all", this._boundForceUpdate, this);
  },

  // Clean up the listener when the component will be removed.
  componentWillUnmount: function() {
    this.getBackboneObject().off("all", this._boundForceUpdate);
  },

  // The property `collection` or `model` is assumed to be a
  // `Backbone.Collection` or a `Backbone.Model`.
  getBackboneObject: function() {
    return this.props.collection || this.props.model;
  }

};

// Todo List Item Component
// ------------------------

// The DOM for a todo item...
var TodoListItemComponent = React.createClass({

  // If the component updates and is in edit mode, send focus to the `<input>`.
  componentDidUpdate: function() {
    if (this.state.editing) {
      this.refs.editInput.getDOMNode().focus();
    }
  },

  // Destroying the model fires a `remove` event on the model's collection,
  // which forces an update and removes this **TodoListItemComponent** from the
  // DOM. We don't have to do any other cleanup!
  destroy: function() {
    this.props.model.destroy();
  },

  // List items are initially not in edit mode.
  getInitialState: function() {
    return {
      editing: false
    };
  },

  // Stop editing if the input gets an "Enter" keypress.
  handleEditKeyPress: function(event) {
    if (13 === event.keyCode) {
      this.stopEditing();
    }
  },

  render: function() {
    var inputStyles = {};
    var viewStyles = {};

    // Hide the `.view` when editing
    if (this.state.editing) {
      viewStyles.display = "none";

    // ... and hide the `<input>` when not editing
    } else {
      inputStyles.display = "none";
    }

    return (

          <input className="toggle" type="checkbox"
            checked={this.props.model.get("done")}
            onChange={this.toggleDone} />
          {this.props.model.get("title")}

        <input className="edit" ref="editInput" type="text"
          onBlur={this.stopEditing}
          onChange={this.setTitle}
          onKeyPress={this.handleEditKeyPress}
          style={inputStyles}
          value={this.props.model.get("title")} />

    );
  },

  // Set the title of this item's model when the value of the `<input>` changes.
  setTitle: function(event) {
    this.props.model.set("title", event.target.value);
  },

  // Enter edit mode. Changing the state triggers this component to be
  // re-rendered, and on the next pass the `<input>` will be shown and `.view`
  // will be hidden.
  startEditing: function() {
    this.setState({editing: true});
  },

  // Exit edit mode, trigger a render.
  stopEditing: function() {
    this.setState({editing: false});
  },

  toggleDone: function(event) {
    this.props.model.set("done", $(event.target).is(":checked"));
  }

});

// Todo List Component
// -------------------

// Renders a list of todos.
var TodoListComponent = React.createClass({

  // Pass the `key` attribute[1] a unique ID so React can track the elements
  // properly.
  //
  // [1] http://facebook.github.io/react/docs/multiple-components.html#dynamic-children
  render: function() {
    return (

        {this.props.collection.map(function(model) {
          return <TodoListItemComponent key={model.id} model={model} />
        })}

    );
  }

});

// Footer Component
// ----------------

// The footer shows the total number of todos and how many are completed.
var FooterComponent = React.createClass({

  render: function() {
    var clearCompletedStyles = {};

    // Hide the "Clear X completed items" button if there are no completed
    // items.
    if (0 === this.props.itemsDoneCount) {
      clearCompletedStyles.display = "none";
    }

    // Clicking the "Clear X completed items" button calls the
    // "clearCompletedItems" function passed in on `props`.
    return (

          Clear {this.props.itemsDoneCount} completed
          {1 === this.props.itemsDoneCount ? " item" : " items"}

          {this.props.itemsRemainingCount}
          {1 === this.props.itemsRemainingCount ? " item" : " items"} left

    );
  }

});

// Main Component
// --------------

// The main component contains the list of todos and the footer.
var MainComponent = React.createClass({

  // Tell the **App** to toggle the *done* state of all **Todo** items.
  toggleAllItemsCompleted: function(event) {
    this.props.toggleAllItemsCompleted(event.target.checked);
  },

  render: function() {
    var style = {};

    // Hide the "Mark all as complete" button and the footer if there are no
    // **Todo** items.
    if (0 === this.props.collection.length) {
      style.display = "none";
    }

    return (

        <input id="toggle-all" type="checkbox"
          checked={0 === this.props.collection.remaining().length}
          onChange={this.toggleAllItemsCompleted} />

          Mark all as complete

        <TodoListComponent collection={this.props.collection} />
        <FooterComponent
          clearCompletedItems={this.props.clearCompletedItems}
          itemsRemainingCount={this.props.collection.remaining().length}
          itemsDoneCount={this.props.collection.done().length} />

    );
  }

});

var AppComponent = React.createClass({

  // Clear all done todo items, destroying their models.
  clearCompletedItems: function() {
    _.invoke(this.props.collection.done(), "destroy");
  },

  // Fetch Todos before the App is rendered to the DOM.
  componentWillMount: function() {
    this.props.collection.fetch();
  },

  // If "Enter" is pressed in the main input field, create a new **Todo**
  // in *localStorage*.
  handleKeyPress: function(event) {
    if (13 !== event.keyCode) return;

    var $input = $(event.target);
    if (!$input.val()) return;

    this.props.collection.create({title: $input.val()});
    $input.val("");
  },

  toggleAllItemsCompleted: function(completed) {
    this.props.collection.each(function(todo) {
      todo.save({"done": completed});
    });
  },

  // Force updates whenever this **App**'s collection receives events.
  mixins: [BackboneMixin],

  render: function() {
    return (

Todos

          <input placeholder="What needs to be done?" type="text"
            onKeyPress={this.handleKeyPress} />

        <MainComponent
          clearCompletedItems={this.clearCompletedItems}
          collection={this.props.collection}
          toggleAllItemsCompleted={this.toggleAllItemsCompleted} />

    );
  }
})

// Create a new Todo collection and render the **App** into `#todoapp`.
React.renderComponent(
  <AppComponent collection={new TodoList()} />,
  document.getElementById("todoapp")
);
<commit_msg>Store title in state rather than the DOM<commit_after>/** @jsx React.DOM */

// JSX Annotation
// --------------

// The `@jsx React.DOM` annotation above tells the JSXTransformer to compile
// this file as JSX. It has to be the first line in the file, which is why
// the description you're reading is below the annotation.

// Todo Model
// ----------

// Our basic **Todo** model has `title` and `done` attributes.
var Todo = Backbone.Model.extend({

  // Default attributes for the Todo item.
  defaults: function() {
    return {
      title: "",
      done: false
    };
  }

});

// Todo Collection
// ---------------

// The collection of Todos is backed by *localStorage* instead of a remote
// server.
var TodoList = Backbone.Collection.extend({

  // Reference to this collection's model.
  model: Todo,

  // Save all of the todo items under the `"todos-react"` namespace.
  localStorage: new Backbone.LocalStorage("todos-react"),

  // Filter down the list of all todo items that are finished.
  done: function() {
    return this.where({done: true});
  },

  // Filter down the list to only todo items that are still not finished.
  remaining: function() {
    return this.where({done: false});
  }

});

// Backbone/React Integration
// --------------------------

// Notifies React components when their Backbone models or collections change.
var BackboneMixin = {

  // Listen to all events on this component's collection or model and force an
  // update when they fire. Let React decide whether the DOM should change.
  componentDidMount: function() {
    this._boundForceUpdate = this.forceUpdate.bind(this, null);
    this.getBackboneObject().on("all", this._boundForceUpdate, this);
  },

  // Clean up the listener when the component will be removed.
  componentWillUnmount: function() {
    this.getBackboneObject().off("all", this._boundForceUpdate);
  },

  // The property `collection` or `model` is assumed to be a
  // `Backbone.Collection` or a `Backbone.Model`.
  getBackboneObject: function() {
    return this.props.collection || this.props.model;
  }

};

// Todo List Item Component
// ------------------------

// The DOM for a todo item...
var TodoListItemComponent = React.createClass({

  // If the component updates and is in edit mode, send focus to the `<input>`.
  componentDidUpdate: function() {
    if (this.state.editing) {
      this.refs.editInput.getDOMNode().focus();
    }
  },

  // Destroying the model fires a `remove` event on the model's collection,
  // which forces an update and removes this **TodoListItemComponent** from the
  // DOM. We don't have to do any other cleanup!
  destroy: function() {
    this.props.model.destroy();
  },

  // List items are initially not in edit mode.
  getInitialState: function() {
    return {
      editing: false
    };
  },

  // Stop editing if the input gets an "Enter" keypress.
  handleEditKeyPress: function(event) {
    if (13 === event.keyCode) {
      this.stopEditing();
    }
  },

  render: function() {
    var inputStyles = {};
    var viewStyles = {};

    // Hide the `.view` when editing
    if (this.state.editing) {
      viewStyles.display = "none";

    // ... and hide the `<input>` when not editing
    } else {
      inputStyles.display = "none";
    }

    return (

          <input className="toggle" type="checkbox"
            checked={this.props.model.get("done")}
            onChange={this.toggleDone} />
          {this.props.model.get("title")}

        <input className="edit" ref="editInput" type="text"
          onBlur={this.stopEditing}
          onChange={this.setTitle}
          onKeyPress={this.handleEditKeyPress}
          style={inputStyles}
          value={this.props.model.get("title")} />

    );
  },

  // Set the title of this item's model when the value of the `<input>` changes.
  setTitle: function(event) {
    this.props.model.set("title", event.target.value);
  },

  // Enter edit mode. Changing the state triggers this component to be
  // re-rendered, and on the next pass the `<input>` will be shown and `.view`
  // will be hidden.
  startEditing: function() {
    this.setState({editing: true});
  },

  // Exit edit mode, trigger a render.
  stopEditing: function() {
    this.setState({editing: false});
  },

  toggleDone: function(event) {
    this.props.model.set("done", $(event.target).is(":checked"));
  }

});

// Todo List Component
// -------------------

// Renders a list of todos.
var TodoListComponent = React.createClass({

  // Pass the `key` attribute[1] a unique ID so React can track the elements
  // properly.
  //
  // [1] http://facebook.github.io/react/docs/multiple-components.html#dynamic-children
  render: function() {
    return (

        {this.props.collection.map(function(model) {
          return <TodoListItemComponent key={model.id} model={model} />
        })}

    );
  }

});

// Footer Component
// ----------------

// The footer shows the total number of todos and how many are completed.
var FooterComponent = React.createClass({

  render: function() {
    var clearCompletedStyles = {};

    // Hide the "Clear X completed items" button if there are no completed
    // items.
    if (0 === this.props.itemsDoneCount) {
      clearCompletedStyles.display = "none";
    }

    // Clicking the "Clear X completed items" button calls the
    // "clearCompletedItems" function passed in on `props`.
    return (

          Clear {this.props.itemsDoneCount} completed
          {1 === this.props.itemsDoneCount ? " item" : " items"}

          {this.props.itemsRemainingCount}
          {1 === this.props.itemsRemainingCount ? " item" : " items"} left

    );
  }

});

// Main Component
// --------------

// The main component contains the list of todos and the footer.
var MainComponent = React.createClass({

  // Tell the **App** to toggle the *done* state of all **Todo** items.
  toggleAllItemsCompleted: function(event) {
    this.props.toggleAllItemsCompleted(event.target.checked);
  },

  render: function() {
    var style = {};

    // Hide the "Mark all as complete" button and the footer if there are no
    // **Todo** items.
    if (0 === this.props.collection.length) {
      style.display = "none";
    }

    return (

        <input id="toggle-all" type="checkbox"
          checked={0 === this.props.collection.remaining().length}
          onChange={this.toggleAllItemsCompleted} />

          Mark all as complete

        <TodoListComponent collection={this.props.collection} />
        <FooterComponent
          clearCompletedItems={this.props.clearCompletedItems}
          itemsRemainingCount={this.props.collection.remaining().length}
          itemsDoneCount={this.props.collection.done().length} />

    );
  }

});

var AppComponent = React.createClass({

  // Clear all done todo items, destroying their models.
  clearCompletedItems: function() {
    _.invoke(this.props.collection.done(), "destroy");
  },

  // Fetch Todos before the App is rendered to the DOM.
  componentWillMount: function() {
    this.props.collection.fetch();
  },

  // Start the app with a blank `<input>`.
  getInitialState: function() {
    return {
      title: ""
    };
  },

  // Set the state of the title when the `<input>` is changed.
  handleTitleChange: function(event) {
    this.setState({title: event.target.value});
  },

  // If "Enter" is pressed in the main input field, create a new **Todo**
  // in *localStorage*.
  handleTitleKeyPress: function(event) {
    if (13 !== event.keyCode) return;

    var title = event.target.value;
    if ("" === title) return;

    this.props.collection.create({title: title});
    this.setState({title: ""});
  },

  toggleAllItemsCompleted: function(completed) {
    this.props.collection.each(function(todo) {
      todo.save({"done": completed});
    });
  },

  // Force updates whenever this **App**'s collection receives events.
  mixins: [BackboneMixin],

  render: function() {
    return (

Todos

          <input placeholder="What needs to be done?" type="text"
            onChange={this.handleTitleChange}
            onKeyPress={this.handleTitleKeyPress}
            value={this.state.title} />

        <MainComponent
          clearCompletedItems={this.clearCompletedItems}
          collection={this.props.collection}
          toggleAllItemsCompleted={this.toggleAllItemsCompleted} />

    );
  }
})

// Create a new Todo collection and render the **App** into `#todoapp`.
React.renderComponent(
  <AppComponent collection={new TodoList()} />,
  document.getElementById("todoapp")
);
<|endoftext|>


<commit_before>/**
 * Created by seunggabi on 2017. 2. 26..
 */

module.exports = {
	XLSX: require("xlsx-style"),
	UTIL: require("./excel-merge-tool-utils.js"),
	LOG: require("./excel-merge-tool-log.js"),
	DATA: require("./excel-merge-tool-data.js"),

	PATH: {
		READ: "files/",
		WRITE: "files/output/"
	},
	WRITE_NAME: {
		NONE: "merge.xlsx",
		CONFLICT: "merge_conflict.xlsx",
		LIST: "merge_list.xlsx"
	},
	EXTENSION: ".xlsx",
	WRITE_MODE: {
		NONE: "NONE",
		CONFLICT: "CONFLICT",
		ALL: "ALL",
		LIST: "LIST"
	},
	DEFAULT: {
		WRITE_MODE: "LIST",
		LOG_MODE: true,
		IGNORE_LENGTH: 2,
		TITLE_RANGE: "A1:D1"
	},
	MSG: {
		UNDEFINED: "사용되지 않는 모드입니다."
	},
	LOG_TYPE: {
		SYSTEM: "SYSTEM  ",
		MERGE: "MERGE   ",
		NEW: "NEW     ",
		CONFLICT: "CONFLICT"
	},
	REG: {
		regCol: /\w+/g,
		regRow: /\d+/g
	},
	USING_CHECK: "$",
	RANGE_KEY: "!ref",
	FORMULA_KEY: "f",

	write_mode: null,
	log_mode: null,
	ignore_length: null,
	title: {
		range: "",
		cols: [],
		rows: []
	},

	init: function(data) {
		data = data || {};
		this.write_mode = data.write_mode || this.DEFAULT.WRITE_MODE;
		this.LOG.status = data.log_mode || this.DEFAULT.LOG_MODE;
		this.ignore_length = data.ignore_length || this.DEFAULT.IGNORE_LENGTH;
		this.title.range = data.title.range || this.DEFAULT.TITLE_RANGE;
		this.setListTitle(this.title.range);

		this.LOG.addItem(this.LOG_TYPE.SYSTEM, "EMT init");
	},

	readFiles: function(fileNames) {
		var wbList = [];
		fileNames.forEach(function(fileName) {
			var wb = this.XLSX.readFile(this.PATH.READ + fileName, {cellStyles: true});
			wb.fileName = fileName;
			wbList.push(wb);

			this.LOG.addItem(this.LOG_TYPE.SYSTEM, "Read "+fileName);
		}.bind(this));
		return wbList;
	},

	selectXLSX: function(fileNames) {
		var filesXLSX = [];

		fileNames.forEach(function(fileName) {
			if(fileName.lastIndexOf(this.EXTENSION) >= 0
				&& fileName.lastIndexOf(this.USING_CHECK) < 0) {
				filesXLSX.push(fileName);
			}
		}.bind(this));
		return filesXLSX;
	},

	_mergeSheets: function(sheets) {
		return sheets.reduce(this._mergeSheet.bind(this));
	},

	_mergeSheet: function(wb1, wb2) {
		this.LOG.addItem(this.LOG_TYPE.MERGE, "TO "+wb1.fileName+", FROM "+wb2.fileName);

		for(var s in wb2.Sheets) {
			if(wb1.Sheets.hasOwnProperty(s)) {
				this.LOG.addItem(this.LOG_TYPE.CONFLICT, s+" Sheet ==> Conflict");
				wb1.Sheets[s].fileName = wb1.fileName;
				wb2.Sheets[s].fileName = wb2.fileName;
				wb1.Sheets[s] = this._mergeCells(wb1.Sheets[s], wb2.Sheets[s]);
			} else {
				this.LOG.addItem(this.LOG_TYPE.NEW, s+" Sheet ==> New");
				wb1.Sheets[s] = wb2.Sheets[s];
				wb1.SheetNames.push(s);
			}
		}
		return wb1;
	},

	_mergeCells: function(s1, s2) {
		this._setCellFomula(s1);
		this._setCellFomula(s2);

		for(var c in s2) {
			var v2 = String(s2[c].v);
			v2 = this.UTIL.enterOnce(v2);

			if(s1.hasOwnProperty(c)) {
				var v1 = String(s1[c].v);
				v1 = this.UTIL.enterOnce(v1);

				if(c === this.RANGE_KEY) {
					this._extendsRange(s1[c], s2[c]);
				}
				else if(v1.length <= this.ignore_length && v2.length <= this.ignore_length) {
					v1 = v1.toUpperCase();
					v2 = v2.toUpperCase();

					if(v1 === v2) {
						s1[c].t = "s";
						s1[c].v = v1;
					} else {
						s1[c].v = v1 + String.fromCharCode(13) + v2;
					}
				}
				else if(!this.UTIL.isInclude(v1, v2)) {
					s1[c].t = "s";
					s1[c].v = this._concatFileName(s1.fileName, v1)
						+ String.fromCharCode(13)
						+ this._concatFileName(s2.fileName, v2);
					this.LOG.addItem(this.LOG_TYPE.CONFLICT, c+" Cell ==> Conflict ("+s1[c].v+")");
				}
			} else {
				if(v2.length <= this.ignore_length) {
					s2[c].v = v2;
				} else {
					s2[c].v =  this._concatFileName(s2.fileName, v2);
				}
				s1[c] = s2[c];
			}
		}
		return s1;
	},

	_setCellFomula: function(s) {
		for(var c in s) {
			if(s[c].hasOwnProperty(this.FORMULA_KEY)) {
				s[c].t = "s";
				s[c].v = "="+s[c].f;
			}
		}
	},

	_extendsRange: function(r1, r2) {
		var r;
		var r1Col = r1.match(this.REG.regCol);
		var r1Row = r1.match(this.REG.regRow);

		var r2Col = r2.match(this.REG.regCol);
		var r2Row = r2.match(this.REG.regRow);

		r = this.UTIL.min(r1Col[0], r2Col[0])
			+ this.UTIL.min(r1Row[0], r2Row[0])
			+ ":"
			+ this.UTIL.max(r1Col[1], r2Col[1])
			+ this.UTIL.max(r1Row[1], r2Row[1]);

		return r;
	},

	_concatFileName: function(fileName, text) {
		var concatText = text;
		if(this.write_mode === this.WRITE_MODE.CONFLICT) {
			concatText = "["+fileName+"]" + String.fromCharCode(13) + text;
		}

		return concatText;
	},

	writeFile: function(wbList) {
		switch(this.write_mode) {
			case this.WRITE_MODE.NONE:
			case this.WRITE_MODE.CONFLICT:
				this.LOG.addItem(this.LOG_TYPE.SYSTEM, "Mode is "+this.write_mode);
				this._writeFile(this.UTIL.clone(wbList));
				this.LOG.writeFile();
				break;
			case this.WRITE_MODE.ALL:
				this.write_mode = this.WRITE_MODE.NONE;
				this.LOG.addItem(this.LOG_TYPE.SYSTEM, "Mode is "+this.write_mode);
				this._writeFile(this.UTIL.clone(wbList));
				this.write_mode = this.WRITE_MODE.CONFLICT;
				this.LOG.addItem(this.LOG_TYPE.SYSTEM, "Mode is "+this.write_mode);
				this._writeFile(this.UTIL.clone(wbList));
				this.write_mode = this.WRITE_MODE.ALL;
				this.LOG.writeFile();
				break;
			default:
				console.log(this.MSG.UNDEFINED);
		}
	},

	_writeFile: function(wbList) {
		var wb = EMT._mergeSheets(wbList);
		this.XLSX.writeFile(wb, this.PATH.WRITE + this.WRITE_NAME[this.write_mode]);
		this.LOG.addItem(this.LOG_TYPE.SYSTEM, "Write "+this.WRITE_NAME[this.write_mode]);
	},

	readSheets: function(wb) {
		for(var s in wb.Sheets) {
			this.readCells(wb[s]);
		}
	},

	readCells: function(s) {
		//key list 제외하고 셀,
		//셀인 경우에 범위 내에서만 동작하도록함
		//row[1] 보다 무조건 커야함
		//col[0] ~ col[1] 사이의 것만
	},

	setListTitle: function(titleRnage) {
		var cols = titleRnage.match(this.REG.Col);
		var rows = titleRnage.match(this.REG.Row);

		this.title.range = titleRnage;
		this.title.cols = cols;
		this.title.rows = rows;
	},
};<commit_msg>간단한 리팩토링 및 알고리즘 설계<commit_after>/**
 * Created by seunggabi on 2017. 2. 26..
 */

module.exports = {
	XLSX: require("xlsx-style"),
	UTIL: require("./excel-merge-tool-utils.js"),
	LOG: require("./excel-merge-tool-log.js"),
	DATA: require("./excel-merge-tool-data.js"),

	PATH: {
		READ: "files/",
		WRITE: "files/output/"
	},
	WRITE_NAME: {
		NONE: "merge.xlsx",
		CONFLICT: "merge_conflict.xlsx",
		LIST: "merge_list.xlsx"
	},
	EXTENSION: ".xlsx",
	WRITE_MODE: {
		NONE: "NONE",
		CONFLICT: "CONFLICT",
		ALL: "ALL",
		LIST: "LIST"
	},
	DEFAULT: {
		WRITE_MODE: "LIST",
		LOG_MODE: true,
		IGNORE_LENGTH: 2,
		TITLE_RANGE: "A1:D1"
	},
	MSG: {
		UNDEFINED: "사용되지 않는 모드입니다."
	},
	LOG_TYPE: {
		SYSTEM: "SYSTEM  ",
		MERGE: "MERGE   ",
		NEW: "NEW     ",
		CONFLICT: "CONFLICT"
	},
	REG: {
		COL: /\w+/g,
		ROW: /\d+/g,
		CELL: /\w+\d+/g
	},
	USING_CHECK: "$",
	RANGE_KEY: "!ref",
	FORMULA_KEY: "f",

	write_mode: null,
	log_mode: null,
	ignore_length: null,
	title: {
		range: "",
		cols: [],
		rows: []
	},

	init: function(data) {
		data = data || {};
		this.write_mode = data.write_mode || this.DEFAULT.WRITE_MODE;
		this.LOG.status = data.log_mode || this.DEFAULT.LOG_MODE;
		this.ignore_length = data.ignore_length || this.DEFAULT.IGNORE_LENGTH;
		this.title.range = (data.title && data.title.range) || this.DEFAULT.TITLE_RANGE;
		this.setListTitle(this.title.range);

		this.LOG.addItem(this.LOG_TYPE.SYSTEM, "EMT init");
	},

	readFiles: function(fileNames) {
		var wbList = [];
		fileNames.forEach(function(fileName) {
			var wb = this.XLSX.readFile(this.PATH.READ + fileName, {cellStyles: true});
			wb.fileName = fileName;
			wbList.push(wb);

			this.LOG.addItem(this.LOG_TYPE.SYSTEM, "Read "+fileName);
		}.bind(this));
		return wbList;
	},

	selectXLSX: function(fileNames) {
		var filesXLSX = [];

		fileNames.forEach(function(fileName) {
			if(fileName.lastIndexOf(this.EXTENSION) >= 0
				&& fileName.lastIndexOf(this.USING_CHECK) < 0) {
				filesXLSX.push(fileName);
			}
		}.bind(this));
		return filesXLSX;
	},

	_mergeSheets: function(sheets) {
		return sheets.reduce(this._mergeSheet.bind(this));
	},

	_mergeSheet: function(wb1, wb2) {
		this.LOG.addItem(this.LOG_TYPE.MERGE, "TO "+wb1.fileName+", FROM "+wb2.fileName);

		for(var s in wb2.Sheets) {
			if(wb1.Sheets.hasOwnProperty(s)) {
				this.LOG.addItem(this.LOG_TYPE.CONFLICT, s+" Sheet ==> Conflict");
				wb1.Sheets[s].fileName = wb1.fileName;
				wb2.Sheets[s].fileName = wb2.fileName;
				wb1.Sheets[s] = this._mergeCells(wb1.Sheets[s], wb2.Sheets[s]);
			} else {
				this.LOG.addItem(this.LOG_TYPE.NEW, s+" Sheet ==> New");
				wb1.Sheets[s] = wb2.Sheets[s];
				wb1.SheetNames.push(s);
			}
		}
		return wb1;
	},

	_mergeCells: function(s1, s2) {
		this._setCellFomula(s1);
		this._setCellFomula(s2);

		for(var c in s2) {
			var v2 = String(s2[c].v);
			v2 = this.UTIL.enterOnce(v2);

			if(s1.hasOwnProperty(c)) {
				var v1 = String(s1[c].v);
				v1 = this.UTIL.enterOnce(v1);

				if(c === this.RANGE_KEY) {
					this._extendsRange(s1[c], s2[c]);
				}
				else if(v1.length <= this.ignore_length && v2.length <= this.ignore_length) {
					v1 = v1.toUpperCase();
					v2 = v2.toUpperCase();

					if(v1 === v2) {
						s1[c].t = "s";
						s1[c].v = v1;
					} else {
						s1[c].v = v1 + String.fromCharCode(13) + v2;
					}
				}
				else if(!this.UTIL.isInclude(v1, v2)) {
					s1[c].t = "s";
					s1[c].v = this._concatFileName(s1.fileName, v1)
						+ String.fromCharCode(13)
						+ this._concatFileName(s2.fileName, v2);
					this.LOG.addItem(this.LOG_TYPE.CONFLICT, c+" Cell ==> Conflict ("+s1[c].v+")");
				}
			} else {
				if(v2.length <= this.ignore_length) {
					s2[c].v = v2;
				} else {
					s2[c].v =  this._concatFileName(s2.fileName, v2);
				}
				s1[c] = s2[c];
			}
		}
		return s1;
	},

	_setCellFomula: function(s) {
		for(var c in s) {
			if(s[c].hasOwnProperty(this.FORMULA_KEY)) {
				s[c].t = "s";
				s[c].v = "="+s[c].f;
			}
		}
	},

	_extendsRange: function(r1, r2) {
		var r;
		var r1Col = r1.match(this.REG.COL);
		var r1Row = r1.match(this.REG.ROW);

		var r2Col = r2.match(this.REG.COL);
		var r2Row = r2.match(this.REG.ROW);

		r = this.UTIL.min(r1Col[0], r2Col[0])
			+ this.UTIL.min(r1Row[0], r2Row[0])
			+ ":"
			+ this.UTIL.max(r1Col[1], r2Col[1])
			+ this.UTIL.max(r1Row[1], r2Row[1]);

		return r;
	},

	_concatFileName: function(fileName, text) {
		var concatText = text;
		if(this.write_mode === this.WRITE_MODE.CONFLICT) {
			concatText = "["+fileName+"]" + String.fromCharCode(13) + text;
		}

		return concatText;
	},

	writeFile: function(wbList) {
		switch(this.write_mode) {
			case this.WRITE_MODE.NONE:
			case this.WRITE_MODE.CONFLICT:
				this.LOG.addItem(this.LOG_TYPE.SYSTEM, "Mode is "+this.write_mode);
				this._writeFile(this.UTIL.clone(wbList));
				this.LOG.writeFile();
				break;
			case this.WRITE_MODE.ALL:
				this.write_mode = this.WRITE_MODE.NONE;
				this.LOG.addItem(this.LOG_TYPE.SYSTEM, "Mode is "+this.write_mode);
				this._writeFile(this.UTIL.clone(wbList));
				this.write_mode = this.WRITE_MODE.CONFLICT;
				this.LOG.addItem(this.LOG_TYPE.SYSTEM, "Mode is "+this.write_mode);
				this._writeFile(this.UTIL.clone(wbList));
				this.write_mode = this.WRITE_MODE.ALL;
				this.LOG.writeFile();
				break;
			default:
				console.log(this.MSG.UNDEFINED);
		}
	},

	_writeFile: function(wbList) {
		var wb = EMT._mergeSheets(wbList);
		this.XLSX.writeFile(wb, this.PATH.WRITE + this.WRITE_NAME[this.write_mode]);
		this.LOG.addItem(this.LOG_TYPE.SYSTEM, "Write "+this.WRITE_NAME[this.write_mode]);
	},

	readSheets: function(wb) {
		for(var s in wb.Sheets) {
			this.readCells(wb[s]);
		}
	},

	readCells: function(s) {
		var item = [];

		var lineNumber = row[1]+1;
		for(var c in s) {
			if(c.match(this.REG.CELL)) {

			}
		}
		//key list 제외하고 셀,
		//셀인 경우에 범위 내에서만 동작하도록함
		//col[0] ~ col[1] 사이의 것만
		//끝 lineNumber 구해야함. range사용

		//1. 타이틀 영역부터 데이터에 넣어야함
		//2. 라인 넘버로 골라내서 접근. 키 인덱스오브
		//3. 라인 계속 더한 후 마지막 한셀식 직접 입력
	},

	setListTitle: function(titleRnage) {
		var cols = titleRnage.match(this.REG.COL);
		var rows = titleRnage.match(this.REG.ROW);

		this.title.range = titleRnage;
		this.title.cols = cols;
		this.title.rows = rows;
	},
};<|endoftext|>


<commit_before>
importModule('core.string');
importModule('helma.rhino', 'rhino');

var __shared__ = true;

(function() {

    var configured = false;
    var responseLogEnabled = false;
    var self = this;

    /**
     * Configure log4j using the given file resource.
     * Make sure to set the reset property to true in the <log4j:configuration> header
     * e.g. <log4j:configuration xmlns:log4j='http://jakarta.apache.org/log4j/' reset="true">
     */
    this.setConfig = function(resource) {
        var configurator = resource.endsWith('.properties') || resource.endsWith('.props') ?
           org.apache.log4j.PropertyConfigurator :
           org.apache.log4j.xml.DOMConfigurator;
        configurator.configure(resource);
        configurator.configureAndWatch(resource, 2000);
        configured = true;
    }

    /**
     * Get a logger for the given name.
     */
    this.getLogger = function(name) {
        if (!configured) {
            // getResource('foo').name gets us the absolute path to a local resource
            this.setConfig(getResource('log4j.properties').path);
        }
        return org.apache.log4j.Logger.getLogger(name);
    }

    /**
     * Render log4j messages to response buffer in the style of helma 1 res.debug().
     */
    this.enableResponseLog = function() {
        // onLogEvent() callback is called by org.helma.util.RhinoAppender
        rhino.addCallback("onLogEvent", "responseLog", function(msg, javaStack, scriptStack) {
            if (!global.res) {
                return;
            }
            var buffer = res.getBuffer("responseLog");
            buffer.write("
");
            buffer.write(msg);
            if (scriptStack) {
                buffer.write("
Script Stack
");
                buffer.write("
", scriptStack, "
");
            }
            if (javaStack) {
                buffer.write("
Java Stack
");
                buffer.write("
", javaStack, "
");
            }
            buffer.writeln("
");
            return null;
        });
        // add an onResponse callback to automatically flush the response log
        rhino.addCallback("onResponse", "responseLogFlusher", function(res) {
            if (res.status == 200 || res.status >= 400) {
                self.flushResponseLog();
            }
        });
        responseLogEnabled = true;
    }

    /**
     * Stop log4j response buffer logging.
     */
    this.disableResponseLog = function() {
        // unregister handlers added in startResponseLog()
        rhino.removeCallback("onLogEvent", "responseLog");
        rhino.removeCallback("onResponse", "responseLogFlusher");
        responseLogEnabled = false;
    };

    /**
     * Write the log4j response buffer to the main response buffer and reset it.
     * This can either be called manually to insert the log buffer at any given position
     * in the response, or it will called by the log4j response listener after the
     * response has been generated.
     */
    this.flushResponseLog = function() {
        var buffer = res.getBuffer("responseLog");
        res.write(buffer);
        buffer.reset();
        return null;
    };

    /**
     * Return true if response logging is enabled, false otherwise
     */
    this.responseLogEnabled = function() {
        return responseLogEnabled;
    }

}).call(this);
<commit_msg>Defer import of rhino module to keep rhino from importing an uninitialized logging module<commit_after>
importModule('core.string');

var __shared__ = true;

(function() {

    var configured = false;
    var responseLogEnabled = false;
    var self = this;

    /**
     * Configure log4j using the given file resource.
     * Make sure to set the reset property to true in the <log4j:configuration> header
     * e.g. <log4j:configuration xmlns:log4j='http://jakarta.apache.org/log4j/' reset="true">
     */
    this.setConfig = function(resource) {
        var configurator = resource.endsWith('.properties') || resource.endsWith('.props') ?
           org.apache.log4j.PropertyConfigurator :
           org.apache.log4j.xml.DOMConfigurator;
        configurator.configure(resource);
        configurator.configureAndWatch(resource, 2000);
        configured = true;
    }

    /**
     * Get a logger for the given name.
     */
    this.getLogger = function(name) {
        if (!configured) {
            // getResource('foo').name gets us the absolute path to a local resource
            this.setConfig(getResource('log4j.properties').path);
        }
        return org.apache.log4j.Logger.getLogger(name);
    }

    /**
     * Render log4j messages to response buffer in the style of helma 1 res.debug().
     */
    this.enableResponseLog = function() {
        // onLogEvent() callback is called by org.helma.util.RhinoAppender
        var rhino = importModule('helma.rhino');
        rhino.addCallback("onLogEvent", "responseLog", function(msg, javaStack, scriptStack) {
            if (!global.res) {
                return;
            }
            var buffer = res.getBuffer("responseLog");
            buffer.write("
");
            buffer.write(msg);
            if (scriptStack) {
                buffer.write("
Script Stack
");
                buffer.write("
", scriptStack, "
");
            }
            if (javaStack) {
                buffer.write("
Java Stack
");
                buffer.write("
", javaStack, "
");
            }
            buffer.writeln("
");
            return null;
        });
        // add an onResponse callback to automatically flush the response log
        rhino.addCallback("onResponse", "responseLogFlusher", function(res) {
            if (res.status == 200 || res.status >= 400) {
                self.flushResponseLog();
            }
        });
        responseLogEnabled = true;
    }

    /**
     * Stop log4j response buffer logging.
     */
    this.disableResponseLog = function() {
        // unregister handlers added in startResponseLog()
        var rhino = importModule('helma.rhino');
        rhino.removeCallback("onLogEvent", "responseLog");
        rhino.removeCallback("onResponse", "responseLogFlusher");
        responseLogEnabled = false;
    };

    /**
     * Write the log4j response buffer to the main response buffer and reset it.
     * This can either be called manually to insert the log buffer at any given position
     * in the response, or it will called by the log4j response listener after the
     * response has been generated.
     */
    this.flushResponseLog = function() {
        var buffer = res.getBuffer("responseLog");
        res.write(buffer);
        buffer.reset();
        return null;
    };

    /**
     * Return true if response logging is enabled, false otherwise
     */
    this.responseLogEnabled = function() {
        return responseLogEnabled;
    }

}).call(this);
<|endoftext|>


<commit_before>(function($) {

    jQuery.fn.comboEdit = function(options){
        options.selector = jQuery(this).selector;

        var onClick = function(e){

            var toBeReplaced = jQuery(this);

            var data = {};

            var parent_id = jQuery(this).parent().parent().find("."+options.parentIdClass).val();
            data.parent_id = parent_id;

            jQuery.get(options.getComboBoxURL, data, function(response){
                toBeReplaced.replaceWith(response);

                jQuery('option').click(function(){
                    var theValue = jQuery(this).val();
                    var toBeReplaced = jQuery(this).parent().parent();

                    data.id = theValue;

                    jQuery.post(options.submitURL, data, function(response){

                        toBeReplaced.replaceWith(response);

                        jQuery(options.selector).click(onClick);

                    }, 'html');

                });

            }, 'html');

        }

        return this.each(function(){
            jQuery(this).click(onClick);
        });
    }

})(jQuery);<commit_msg>Add child_id params as well to prevent clash in some cases<commit_after>(function($) {

    jQuery.fn.comboEdit = function(options){
        options.selector = jQuery(this).selector;

        var onClick = function(e){

            var toBeReplaced = jQuery(this);

            var data = {};

            var parent_id = jQuery(this).parent().parent().find("."+options.parentIdClass).val();
            data.parent_id = parent_id;

            jQuery.get(options.getComboBoxURL, data, function(response){
                toBeReplaced.replaceWith(response);

                jQuery('option').click(function(){
                    var theValue = jQuery(this).val();
                    var toBeReplaced = jQuery(this).parent().parent();

                    data.id = theValue;
                    data.child_id = theValue;
                    data.parent_id = parent_id;

                    jQuery.post(options.submitURL, data, function(response){

                        toBeReplaced.replaceWith(response);

                        jQuery(options.selector).click(onClick);

                    }, 'html');

                });

            }, 'html');

        }

        return this.each(function(){
            jQuery(this).click(onClick);
        });
    }

})(jQuery);<|endoftext|>


<commit_before>'use strict'

var lfmAPI = require('./lfmAPI');
var config = require('../config');

var lfm = {};

lfm.getArtistImage = function(artist, send) {
    lfmAPI.artist.getInfo(artist, function(artistInfo) {
        if (artistInfo.error) {
            send(artistInfo.message)
        } else if (artistInfo.artist.image[4]['#text'] === ''){
            send('This artist does not have an image');
        } else {
            send(artistInfo.artist.image[4]['#text']);
        };
    });
};

lfm.getSimilarArtists = function (artist, send) {
    lfmAPI.artist.getSimilar(artist, function(artists) {
        if (artists.error) {
            send(artists.message);
        } else if (artists.similarartists.artist.length === 0) {
            send('No similar artists');
        } else {
            var htmlMarkupArtists = artists.similarartists.artist.map( function(artist) {
                return lfm._HTMLUrlTag(artist.url, artist.name);
            }).concat(lfm._getMoreSimilarURL(artist)).join('\n');;
            send(htmlMarkupArtists);
        }
    });
};

lfm.getTrackInfo = function (artist, track, send) {
    lfmAPI.track.getInfo(artist, track, function (track) {
        if (track.error) {
            send(track.message);
        } else {
            track = track.track;
            var trackInfo = 'Listeners: ' + track.listeners;
            trackInfo += '\nScrobbles: ' + track.playcount;
            if (track.album) {
                trackInfo += '\n' + lfm._HTMLUrlTag(track.album.url, 'Album: ' + track.album.title);
            };
            if (track.toptags.tag.length > 0) {
                trackInfo += '\nTags: ';
                track.toptags.tag.forEach( function (tag) {
                    trackInfo += tag.name + ' ';
                });
            };
            if (track.wiki) {
                trackInfo += '\nInfo: ' + track.wiki.summary;
            };
            send(trackInfo);
        };
    });
};

lfm.getTopTracks = function (artist, page, send) {
    lfmAPI.artist.getTopTracks(artist, page, function (tracks) {
        if (tracks.error) {
            send(tracks.message);
        } else if (tracks.toptracks.track.length === 0) {
            send('No tracks found. Try lower page number');
        } else {
            //Last.fm API got a bug, limit ignored on pages 5 and 10.
            //https://getsatisfaction.com/lastfm/topics/gettoptracks-returns-ignores-limit-on-pages-5-and-10
            //So I can't just map results

            var htmlMarkupTracks = [];
            for(var i = 0; i < 10; ++i) {
                htmlMarkupTracks.push(lfm._HTMLUrlTag(tracks.toptracks.track[i].url, tracks.toptracks.track[i].name));
            };
            send(htmlMarkupTracks.join('\n'));
        }
    });
};

//Not in official API. Dirty.
lfm.getYouTubeLink = function (artist, track, send) {
    var songURL;
    lfmAPI.track.getInfo(artist, track, function (track) {
        if (track.error) {
            send(track.message);
        } else {
            songURL=track.track.url.replace(/https:/, 'http:');
        };
        if (songURL) {
            lfmAPI.downloadFullPage(songURL, function (body) {
                var url = body.match(/data-youtube-url="(.*?)"/);
                if (url) {
                    send(url[1]);
                } else {
                    send('No YouTube link found')
                };
            });
        };
    });
};

lfm._HTMLUrlTag = function (url, description) {
    return '' + lfm._HTMLEscape(description) + '';
};

lfm._HTMLEscape = function (string) {
    var replacements = [
        [ /\&/g, '&'],
        [ /\</g, '<' ],
        [ /\>/g, '>' ]
    ];
    return replacements.reduce(
        function(string, replacement) {
            return string.replace(replacement[0], replacement[1])
        },
        string);
};

lfm._dotsEncode = function (string) {
    //String returned by Lfm already escaped, but we need to
    //replace dots after domain for Telegram to correctly parse it
    return config.LFM_URL + string.replace(config.LFM_URL, '').replace(/\./g, '%2E');
};

lfm._getMoreSimilarURL = function (artist) {
    return '\nFind more similar artists';
};

module.exports = lfm;
<commit_msg>Remove workaround because of Last.fm bug Looks like bug reproduces only with limit=10 https://getsatisfaction.com/lastfm/topics/gettoptracks-returns-ignores-limit-on-pages-5-and-10<commit_after>'use strict'

var lfmAPI = require('./lfmAPI');
var config = require('../config');

var lfm = {};

lfm.getArtistImage = function(artist, send) {
    lfmAPI.artist.getInfo(artist, function(artistInfo) {
        if (artistInfo.error) {
            send(artistInfo.message)
        } else if (artistInfo.artist.image[4]['#text'] === ''){
            send('This artist does not have an image');
        } else {
            send(artistInfo.artist.image[4]['#text']);
        };
    });
};

lfm.getSimilarArtists = function (artist, send) {
    lfmAPI.artist.getSimilar(artist, function(artists) {
        if (artists.error) {
            send(artists.message);
        } else if (artists.similarartists.artist.length === 0) {
            send('No similar artists');
        } else {
            var htmlMarkupArtists = artists.similarartists.artist.map( function(artist) {
                return lfm._HTMLUrlTag(artist.url, artist.name);
            }).concat(lfm._getMoreSimilarURL(artist)).join('\n');
            send(htmlMarkupArtists);
        }
    });
};

lfm.getTrackInfo = function (artist, track, send) {
    lfmAPI.track.getInfo(artist, track, function (track) {
        if (track.error) {
            send(track.message);
        } else {
            track = track.track;
            var trackInfo = 'Listeners: ' + track.listeners;
            trackInfo += '\nScrobbles: ' + track.playcount;
            if (track.album) {
                trackInfo += '\n' + lfm._HTMLUrlTag(track.album.url, 'Album: ' + track.album.title);
            };
            if (track.toptags.tag.length > 0) {
                trackInfo += '\nTags: ';
                track.toptags.tag.forEach( function (tag) {
                    trackInfo += tag.name + ' ';
                });
            };
            if (track.wiki) {
                trackInfo += '\nInfo: ' + track.wiki.summary;
            };
            send(trackInfo);
        };
    });
};

lfm.getTopTracks = function (artist, page, send) {
    lfmAPI.artist.getTopTracks(artist, page, function (tracks) {
        if (tracks.error) {
            send(tracks.message);
        } else if (tracks.toptracks.track.length === 0) {
            send('No tracks found. Try lower page number');
        } else {
            var htmlMarkupTracks = tracks.toptracks.track.map(function (track) {
                return lfm._HTMLUrlTag(track.url, track.name);
            }).join('\n');
            send(htmlMarkupTracks);
        };
    });
};

//Not in official API. Dirty.
lfm.getYouTubeLink = function (artist, track, send) {
    var songURL;
    lfmAPI.track.getInfo(artist, track, function (track) {
        if (track.error) {
            send(track.message);
        } else {
            songURL=track.track.url.replace(/https:/, 'http:');
        };
        if (songURL) {
            lfmAPI.downloadFullPage(songURL, function (body) {
                var url = body.match(/data-youtube-url="(.*?)"/);
                if (url) {
                    send(url[1]);
                } else {
                    send('No YouTube link found')
                };
            });
        };
    });
};

lfm._HTMLUrlTag = function (url, description) {
    return '' + lfm._HTMLEscape(description) + '';
};

lfm._HTMLEscape = function (string) {
    var replacements = [
        [ /\&/g, '&'],
        [ /\</g, '<' ],
        [ /\>/g, '>' ]
    ];
    return replacements.reduce(
        function(string, replacement) {
            return string.replace(replacement[0], replacement[1])
        },
        string);
};

lfm._dotsEncode = function (string) {
    //String returned by Lfm already escaped, but we need to
    //replace dots after domain for Telegram to correctly parse it
    return config.LFM_URL + string.replace(config.LFM_URL, '').replace(/\./g, '%2E');
};

lfm._getMoreSimilarURL = function (artist) {
    return '\nFind more similar artists';
};

module.exports = lfm;
<|endoftext|>