<commit_before>Util.querySelector = u.qs = function(query, scope) {
	scope = scope ? scope : document;
	return scope.querySelector(query);
}
Util.querySelectorAll = u.qsa = function(query, scope) {
	scope = scope ? scope : document;
	return scope.querySelectorAll(query);
}

// Get element (id/class/tag)
// Returns elementById if possible
// else Returns first element with (partial) matching classname from target
// If no matches, return first element with tagname from target
Util.getElement = u.ge = function(identifier, scope) {
	var node, i, regexp;
	if(document.getElementById(identifier)) {
		return document.getElementById(identifier);
	}
	scope = scope ? scope : document;
	regexp = new RegExp("(^|\\s)" + identifier + "(\\s|$|\:)");
	for(i = 0; node = scope.getElementsByTagName("*")[i]; i++) {
		if(regexp.test(node.className)) {
			return node;
		}
	}
	return scope.getElementsByTagName(identifier).length ? scope.getElementsByTagName(identifier)[0] : false;
}
// Get elements (class/tag)
// Returns all elements with (partial) matching classname from target
// If no matches, return elements with tagname from target
Util.getElements = u.ges = function(identifier, scope) {
	var node, i, regexp;
	var nodes = new Array();
	scope = scope ? scope : document;
	regexp = new RegExp("(^|\\s)" + identifier + "(\\s|$|\:)");
	for(i = 0; node = scope.getElementsByTagName("*")[i]; i++) {
		if(regexp.test(node.className)) {
			nodes.push(node);
		}
	}
	return nodes.length ? nodes : scope.getElementsByTagName(identifier);
}

// get parent node of type
Util.parentNode = u.pn = function(node, node_type) {
	if(node_type) {
		if(node.parentNode) {
			var parent = node.parentNode;
		}
//		u.bug(e.parentNode.nodeName.toLowerCase());
		while(parent.nodeName.toLowerCase() != node_type.toLowerCase()) {
			if(parent.parentNode) {
				parent = parent.parentNode;
			}
			else {
				return false;
			}
		}
		return parent;
	}
	else {
		return node.parentNode;
	}
}

// Returns previous sibling, not counting text nodes as siblings and ignoring optional exclude=className/nodeName
Util.previousSibling = u.ps = function(node, exclude) {
	node = node.previousSibling;
	while(node && (node.nodeType == 3 || node.nodeType == 8 || exclude && (u.hc(node, exclude) || node.nodeName.toLowerCase().match(exclude)))) {
		node = node.previousSibling;
	}
	return node;
}

// Returns next sibling, not counting text nodes as siblings and ignoring optional exclude=className/nodeName
Util.nextSibling = u.ns = function(node, exclude) {
	node = node.nextSibling;
	while(node && (node.nodeType == 3 || node.nodeType == 8 || exclude && (u.hc(node, exclude) || node.nodeName.toLowerCase().match(exclude)))) {
		node = node.nextSibling;
	}
	return node;
}

Util.childNodes = u.cn = function(node, exclude) {
	var i, child;
	var children = new Array();
	for(i = 0; child = node.childNodes[i]; i++) {
		if(child && child.nodeType != 3 && child.nodeType != 8 && (!exclude || (!u.hc(child, exclude) && !child.nodeName.toLowerCase().match(exclude) ))) {
			children.push(child);
		}
	}
	return children;
}

/**
* append child to element e
* add reference to e on node
* @param node e html element to append new child to
* @param String node_type new node to create and append - can be either node type of actual node
* @param Optional ClassName or attribute object
* return HTML node
*/
Util.appendElement = u.ae = function(parent, node_type, attributes) {
	try {
		// is node_type already DOM node
		var node = (typeof(node_type) == "object") ? node_type : document.createElement(node_type);
		node = parent.appendChild(node);

		// add attributes
		if(attributes) {
			var attribute;
			for(attribute in attributes) {
				if(attribute == "html") {
					node.innerHTML = attributes[attribute]
				}
				else {
					node.setAttribute(attribute, attributes[attribute]);
				}
			}
		}
		return node;
	}
	catch(exception) {
		u.bug("Exception ("+exception+") in u.ae, called from: "+arguments.callee.caller.name);
		u.bug("node:" + u.nodeId(parent, 1));
		u.xInObject(attributes);
	}
	return false;
}

/**
* Insert element
*/
Util.insertElement = u.ie = function(parent, node_type, attributes) {
	try {
		var node = (typeof(node_type) == "object") ? node_type : document.createElement(node_type);
		node = parent.insertBefore(node, parent.firstChild);
		// add attributes
		if(attributes) {
			var attribute;
			for(attribute in attributes) {
				if(attribute == "html") {
					node.innerHTML = attributes[attribute];
				}
				else {
					node.setAttribute(attribute, attributes[attribute]);
				}
			}
		}
		return node;
	}
	catch(exception) {
		u.bug("Exception ("+exception+") in u.ie, called from: "+arguments.callee.caller);
		u.bug("node:" + u.nodeId(parent, 1));
		u.xInObject(attributes);
	}
	return false;
}

// insert element in wrap-element and return wrapper
Util.wrapElement = u.we = function(node, node_type, attributes) {
	try {
		var wrapper_node = node.parentNode.insertBefore(document.createElement(node_type), node);
		if(attributes) {
			var attribute;
			for(attribute in attributes) {
				wrapper_node.setAttribute(attribute, attributes[attribute]);
			}
		}
		wrapper_node.appendChild(node);
		return wrapper_node;
	}
	catch(exception) {
		u.bug("Exception ("+exception+") in u.we, called from: "+arguments.callee.caller);
		u.bug("node:" + u.nodeId(node, 1));
		u.xInObject(attributes);
	}
	return false;
}

// get node textcontent shorthand (basically this is not needed for newer browsers, but required to align syntax for older browsers)
Util.textContent = u.text = function(node) {
	return node.textContent;
}

// make element clickable
Util.clickableElement = u.ce = function(node) {
	// look for link
	var a = (node.nodeName.toLowerCase() == "a" ? node : u.qs("a", node));
	if(a) {
		u.ac(node, "link");
		// only set url, if a has href attribute
		if(a.getAttribute("href") !== null) {
			node.url = a.href;
			a.removeAttribute("href");
		}
	}
	if(typeof(u.e.click) == "function") {
		u.e.click(node);
	}
	return node;
}
// TODO: DEPRECATED
u.link = u.ce;

// Get JSS class value
Util.classVar = u.cv = function(node, var_name) {
//	u.bug(u.nodeId(e) + ":" + e.className);
	try {
		var regexp = new RegExp(var_name + ":[?=\\w/\\#~:.?+=?&%@!\\-]*");
		if(node.className.match(regexp)) {
			return node.className.match(regexp)[0].replace(var_name + ":", "");
		}
	}
	catch(exception) {
		u.bug("Exception ("+exception+") in u.cv, called from: "+arguments.callee.caller);
	}
	return false;
}
// TODO: DEPRECATED
u.getIJ = u.cv;

// set classname on element, replacing all others
Util.setClass = u.sc = function(node, classname) {
	try {
		var old_class = node.className;
		node.className = classname;

		// force dom update
		node.offsetTop;
		return old_class;
	}
	catch(exception) {
		u.bug("Exception ("+exception+") in u.setClass, called from: "+arguments.callee.caller);
	}
	// return false on error
	return false;
}
// Element has classname
Util.hasClass = u.hc = function(node, classname) {
	try {
		if(classname) {
			var regexp = new RegExp("(^|\\s)(" + classname + ")(\\s|$)");
			if(regexp.test(node.className)) {
				return true;
			}
		}
	}
	catch(exception) {
		u.bug("Exception ("+exception+") in u.hasClass("+u.nodeId(node)+"), called from: "+arguments.callee.caller);
	}
	// return false on error
	return false;
}

// Add classname to element if it is not already there
Util.addClass = u.ac = function(node, classname, dom_update) {
	try {
		if(classname) {
			var regexp = new RegExp("(^|\\s)" + classname + "(\\s|$)");
			if(!regexp.test(node.className)) {
				node.className += node.className ? " " + classname : classname;

				// force dom update (performance killer, but will make rendering more detailed)
				dom_update === false ? false : node.offsetTop;
			}
			return node.className;
		}
	}
	catch(exception) {
		u.bug("Exception ("+exception+") in u.addClass, called from: "+arguments.callee.caller);
	}
	return false;
}
// Remove all instances of classname from element
Util.removeClass = u.rc = function(node, classname, dom_update) {
	try {
		if(classname) {
			var regexp = new RegExp("(\\b)" + classname + "(\\s|$)", "g");
			node.className = node.className.replace(regexp, " ").trim().replace(/[\s]{2}/g, " ");
			// force dom update (performance killer, but will make rendering more detailed)
			dom_update === false ? false : node.offsetTop;
			return node.className;
		}
	}
	catch(exception) {
		u.bug("Exception ("+exception+") in u.removeClass, called from: "+arguments.callee.caller);
	}
	return false;
}
// Toggle classname on element
// if class is applied, then remove
// if not applied, then apply
// if _classname is given as parameter, switch between to two classnames
Util.toggleClass = u.tc = function(node, classname, _classname, dom_update) {
	try {
		var regexp = new RegExp("(^|\\s)" + classname + "(\\s|$|\:)");
		if(regexp.test(node.className)) {
			u.rc(node, classname, false);
			if(_classname) {
				u.ac(node, _classname, false);
			}
		}
		else {
			u.ac(node, classname, false);
			if(_classname) {
				u.rc(node, _classname, false);
			}
		}

		dom_update === false ? false : node.offsetTop;
		return node.className;
	}
	catch(exception) {
		u.bug("Exception ("+exception+") in u.toggleClass, called from: "+arguments.callee.caller);
	}
	return false;
}

// apply style
// set style value and query DOM to force update
Util.applyStyle = u.as = function(node, property, value, dom_update) {
	try {
		node.style[property] = value;

		dom_update === false ? false : node.offsetTop;
	}
	catch(exception) {
		u.bug("Exception ("+exception+") in u.applyStyle("+u.nodeId(node)+", "+property+", "+value+") called from: "+arguments.callee.caller);
	}
}

// Get elements computed style value for css property
Util.getComputedStyle = u.gcs = function(node, property) {
	// query DOM to force update
	node.offsetHeight;
	// return computed style if method is supported
	if(document.defaultView && document.defaultView.getComputedStyle) {
		return document.defaultView.getComputedStyle(node, null).getPropertyValue(property);
	}
	return false;
}

// has fixed parent - lots of things needs to be calculated differently if parent is fixed
Util.hasFixedParent = u.hfp = function(node) {
	while(node.nodeName.toLowerCase() != "body") {
		if(u.gcs(node.parentNode, "position").match("fixed")) {
			return true;
		}
		node = node.parentNode;
	}
	return false;
}
<commit_msg>comment<commit_after>Util.querySelector = u.qs = function(query, scope) {
	scope = scope ? scope : document;
	return scope.querySelector(query);
}
Util.querySelectorAll = u.qsa = function(query, scope) {
	scope = scope ? scope : document;
	return scope.querySelectorAll(query);
}

// Get element (id/class/tag)
// Returns elementById if possible
// else Returns first element with (partial) matching classname from target
// If no matches, return first element with tagname from target
Util.getElement = u.ge = function(identifier, scope) {
	var node, i, regexp;
	if(document.getElementById(identifier)) {
		return document.getElementById(identifier);
	}
	scope = scope ? scope : document;
	regexp = new RegExp("(^|\\s)" + identifier + "(\\s|$|\:)");
	for(i = 0; node = scope.getElementsByTagName("*")[i]; i++) {
		if(regexp.test(node.className)) {
			return node;
		}
	}
	return scope.getElementsByTagName(identifier).length ? scope.getElementsByTagName(identifier)[0] : false;
}
// Get elements (class/tag)
// Returns all elements with (partial) matching classname from target
// If no matches, return elements with tagname from target
Util.getElements = u.ges = function(identifier, scope) {
	var node, i, regexp;
	var nodes = new Array();
	scope = scope ? scope : document;
	regexp = new RegExp("(^|\\s)" + identifier + "(\\s|$|\:)");
	for(i = 0; node = scope.getElementsByTagName("*")[i]; i++) {
		if(regexp.test(node.className)) {
			nodes.push(node);
		}
	}
	return nodes.length ? nodes : scope.getElementsByTagName(identifier);
}

// get parent node of type
Util.parentNode = u.pn = function(node, node_type) {
	if(node_type) {
		if(node.parentNode) {
			var parent = node.parentNode;
		}
//		u.bug(e.parentNode.nodeName.toLowerCase());
		while(parent.nodeName.toLowerCase() != node_type.toLowerCase()) {
			if(parent.parentNode) {
				parent = parent.parentNode;
			}
			else {
				return false;
			}
		}
		return parent;
	}
	else {
		return node.parentNode;
	}
}

// Returns previous sibling, not counting text nodes as siblings and ignoring optional exclude=className/nodeName
Util.previousSibling = u.ps = function(node, exclude) {
	node = node.previousSibling;
	while(node && (node.nodeType == 3 || node.nodeType == 8 || exclude && (u.hc(node, exclude) || node.nodeName.toLowerCase().match(exclude)))) {
		node = node.previousSibling;
	}
	return node;
}

// Returns next sibling, not counting text nodes as siblings and ignoring optional exclude=className/nodeName
Util.nextSibling = u.ns = function(node, exclude) {
	node = node.nextSibling;
	while(node && (node.nodeType == 3 || node.nodeType == 8 || exclude && (u.hc(node, exclude) || node.nodeName.toLowerCase().match(exclude)))) {
		node = node.nextSibling;
	}
	return node;
}

Util.childNodes = u.cn = function(node, exclude) {
	var i, child;
	var children = new Array();
	for(i = 0; child = node.childNodes[i]; i++) {
		if(child && child.nodeType != 3 && child.nodeType != 8 && (!exclude || (!u.hc(child, exclude) && !child.nodeName.toLowerCase().match(exclude) ))) {
			children.push(child);
		}
	}
	return children;
}

/**
* append child to element e
* add reference to e on node
* @param node e html element to append new child to
* @param String node_type new node to create and append - can be either node type of actual node
* @param Optional ClassName or attribute object
* return HTML node
*/
Util.appendElement = u.ae = function(parent, node_type, attributes) {
	try {
		// is node_type already DOM node
		var node = (typeof(node_type) == "object") ? node_type : document.createElement(node_type);
		node = parent.appendChild(node);

		// add attributes
		if(attributes) {
			var attribute;
			for(attribute in attributes) {
				if(attribute == "html") {
					node.innerHTML = attributes[attribute]
				}
				else {
					node.setAttribute(attribute, attributes[attribute]);
				}
			}
		}
		return node;
	}
	catch(exception) {
		u.bug("Exception ("+exception+") in u.ae, called from: "+arguments.callee.caller.name);
		u.bug("node:" + u.nodeId(parent, 1));
		u.xInObject(attributes);
	}
	return false;
}

/**
* Insert element
*/
Util.insertElement = u.ie = function(parent, node_type, attributes) {
	try {
		var node = (typeof(node_type) == "object") ? node_type : document.createElement(node_type);
		node = parent.insertBefore(node, parent.firstChild);
		// add attributes
		if(attributes) {
			var attribute;
			for(attribute in attributes) {
				if(attribute == "html") {
					node.innerHTML = attributes[attribute];
				}
				else {
					node.setAttribute(attribute, attributes[attribute]);
				}
			}
		}
		return node;
	}
	catch(exception) {
		u.bug("Exception ("+exception+") in u.ie, called from: "+arguments.callee.caller);
		u.bug("node:" + u.nodeId(parent, 1));
		u.xInObject(attributes);
	}
	return false;
}

// insert element in wrap-element and return wrapper
Util.wrapElement = u.we = function(node, node_type, attributes) {
	try {
		var wrapper_node = node.parentNode.insertBefore(document.createElement(node_type), node);
		if(attributes) {
			var attribute;
			for(attribute in attributes) {
				wrapper_node.setAttribute(attribute, attributes[attribute]);
			}
		}
		wrapper_node.appendChild(node);
		return wrapper_node;
	}
	catch(exception) {
		u.bug("Exception ("+exception+") in u.we, called from: "+arguments.callee.caller);
		u.bug("node:" + u.nodeId(node, 1));
		u.xInObject(attributes);
	}
	return false;
}

// get node textcontent shorthand (basically this is not needed for newer browsers, but required to align syntax for older browsers)
Util.textContent = u.text = function(node) {
	return node.textContent;
}

// make element clickable
Util.clickableElement = u.ce = function(node) {
	// look for link
	var a = (node.nodeName.toLowerCase() == "a" ? node : u.qs("a", node));
	if(a) {
		u.ac(node, "link");
		// only set url, if a has href attribute
		if(a.getAttribute("href") !== null) {
			node.url = a.href;
			a.removeAttribute("href");
		}
	}
	if(typeof(u.e.click) == "function") {
		u.e.click(node);
	}
	return node;
}
// TODO: DEPRECATED
u.link = u.ce;

// Get JSS class value
Util.classVar = u.cv = function(node, var_name) {
//	u.bug(u.nodeId(node) + ":" + node.className);
	try {
		var regexp = new RegExp(var_name + ":[?=\\w/\\#~:.?+=?&%@!\\-]*");
		if(node.className.match(regexp)) {
			return node.className.match(regexp)[0].replace(var_name + ":", "");
		}
	}
	catch(exception) {
		u.bug("Exception ("+exception+") in u.cv, called from: "+arguments.callee.caller);
	}
	return false;
}
// TODO: DEPRECATED
u.getIJ = u.cv;

// set classname on element, replacing all others
Util.setClass = u.sc = function(node, classname) {
	try {
		var old_class = node.className;
		node.className = classname;

		// force dom update
		node.offsetTop;
		return old_class;
	}
	catch(exception) {
		u.bug("Exception ("+exception+") in u.setClass, called from: "+arguments.callee.caller);
	}
	// return false on error
	return false;
}
// Element has classname
Util.hasClass = u.hc = function(node, classname) {
	try {
		if(classname) {
			var regexp = new RegExp("(^|\\s)(" + classname + ")(\\s|$)");
			if(regexp.test(node.className)) {
				return true;
			}
		}
	}
	catch(exception) {
		u.bug("Exception ("+exception+") in u.hasClass("+u.nodeId(node)+"), called from: "+arguments.callee.caller);
	}
	// return false on error
	return false;
}

// Add classname to element if it is not already there
Util.addClass = u.ac = function(node, classname, dom_update) {
	try {
		if(classname) {
			var regexp = new RegExp("(^|\\s)" + classname + "(\\s|$)");
			if(!regexp.test(node.className)) {
				node.className += node.className ? " " + classname : classname;

				// force dom update (performance killer, but will make rendering more detailed)
				dom_update === false ? false : node.offsetTop;
			}
			return node.className;
		}
	}
	catch(exception) {
		u.bug("Exception ("+exception+") in u.addClass, called from: "+arguments.callee.caller);
	}
	return false;
}
// Remove all instances of classname from element
Util.removeClass = u.rc = function(node, classname, dom_update) {
	try {
		if(classname) {
			var regexp = new RegExp("(\\b)" + classname + "(\\s|$)", "g");
			node.className = node.className.replace(regexp, " ").trim().replace(/[\s]{2}/g, " ");
			// force dom update (performance killer, but will make rendering more detailed)
			dom_update === false ? false : node.offsetTop;
			return node.className;
		}
	}
	catch(exception) {
		u.bug("Exception ("+exception+") in u.removeClass, called from: "+arguments.callee.caller);
	}
	return false;
}
// Toggle classname on element
// if class is applied, then remove
// if not applied, then apply
// if _classname is given as parameter, switch between to two classnames
Util.toggleClass = u.tc = function(node, classname, _classname, dom_update) {
	try {
		var regexp = new RegExp("(^|\\s)" + classname + "(\\s|$|\:)");
		if(regexp.test(node.className)) {
			u.rc(node, classname, false);
			if(_classname) {
				u.ac(node, _classname, false);
			}
		}
		else {
			u.ac(node, classname, false);
			if(_classname) {
				u.rc(node, _classname, false);
			}
		}

		dom_update === false ? false : node.offsetTop;
		return node.className;
	}
	catch(exception) {
		u.bug("Exception ("+exception+") in u.toggleClass, called from: "+arguments.callee.caller);
	}
	return false;
}

// apply style
// set style value and query DOM to force update
Util.applyStyle = u.as = function(node, property, value, dom_update) {
	try {
		node.style[property] = value;

		dom_update === false ? false : node.offsetTop;
	}
	catch(exception) {
		u.bug("Exception ("+exception+") in u.applyStyle("+u.nodeId(node)+", "+property+", "+value+") called from: "+arguments.callee.caller);
	}
}

// Get elements computed style value for css property
Util.getComputedStyle = u.gcs = function(node, property) {
	// query DOM to force update
	node.offsetHeight;
	// return computed style if method is supported
	if(document.defaultView && document.defaultView.getComputedStyle) {
		return document.defaultView.getComputedStyle(node, null).getPropertyValue(property);
	}
	return false;
}

// has fixed parent - lots of things needs to be calculated differently if parent is fixed
Util.hasFixedParent = u.hfp = function(node) {
	while(node.nodeName.toLowerCase() != "body") {
		if(u.gcs(node.parentNode, "position").match("fixed")) {
			return true;
		}
		node = node.parentNode;
	}
	return false;
}
<|endoftext|>


<commit_before>/**
 * @fileOverview 자동완성 컴포넌트 중에서 입력창에 대한 기능을 제공하는 클래스
 * @author kihyun.lee@nhnent.com
 */

ne = window.ne || {};
ne.component = ne.component || {};

/**
 * 자동완성 컴포넌트의 구성 요소중 검색어 입력받는 입력창의 동작과 관련된 클래스
 * 단독으로 생성될 수 없으며 ne.component.AutoComplete클래스 내부에서 생성되어 사용된다.
 * @constructor
 */
ne.component.AutoComplete.InputManager = ne.util.defineClass(/**@lends ne.component.AutoComplete.InputManager.prototype */{

    keyCodeMap: {
        'TAB' : 9,
        'UP_ARROW' : 38,
        'DOWN_ARROW' : 40
    },

    /**
     * 초기화 함수
     * @param {Object} arguments
     */
    init: function() {
        if (arguments.length != 2) {
            alert('argument length error !');
        }
        this.autoCompleteObj = arguments[0];   //AutoComplete인스턴스를 저장한다.
        this.options = arguments[1];     //설정 옵션값을 저장한다.

        //Config에서 검색창 부분에서 필요한 엘리먼트 정보 가져온다.
        this.$searchBox = this.options.searchBoxElement;
        this.$toggleBtn = this.options.toggleBtnElement;
        this.$orgQuery = this.options.orgQueryElement;

        //입력값 저장해두기
        this.inputValue = this.$searchBox.val();

        this._attachEvent();
    },

    /**
     * 검색창에 세팅된 키워드값을 리턴한다.
     * @return {String} 검색창에 세팅된 키워드
     */
    getValue: function() {
        return this.$searchBox.val();
    },

    /**
     * 검색창에 키워드값을 세팅한다.
     * @param {String} str 검색창에 세팅할 키워드 값
     */
    setValue: function(str) {
        this.inputValue = str;
        this.$searchBox.val(str);
    },

    /**
     * on/off 토글버튼 이미지를 변경한다.
     * @param {Boolean} isUse 자동완성 사용 여부
     */
    setToggleBtnImg: function(isUse) {
        if (!this.options.toggleImg || !(this.$toggleBtn)) {
            return;
        }

        if (isUse) {
            this.$toggleBtn.attr('src', this.options.toggleImg.on);
        } else {
            this.$toggleBtn.attr('src', this.options.toggleImg.off);
        }
    },

    /**************************** Private Functions **************************/
    /**
     * 이벤트 바인딩 함수
     * @private
     */
    _attachEvent: function() {
        //검색창에 focus, keyup, keydown, click 이벤트 바인딩.
        this.$searchBox.bind('focus keyup keydown blur click', $.proxy(function(e) {
            if (e.type === 'focus') {
                this._onFocus();
            } else if (e.type === 'blur') {
                this._onBlur(e);
            } else if (e.type === 'keyup') {
                this._onKeyUp(e);
            } else if (e.type === 'keydown') {
                this._onKeyDown(e);
            } else if (e.type === 'click') {
                this._onClick();
            }
        }, this));

        if (this.$toggleBtn) {
            this.$toggleBtn.bind('click', $.proxy(function(e) {
                this._onClickToggle();
            }, this));
        }
    },

    /**
     * 사용자가 입력했던 원본 쿼리값을 html hidden value로 세팅한다.
     * @param {String} str 사용자가 검색창에 입력한 검색어 스트링.
     * @private
     */
    _setOrgQuery: function(str) {
        this.$orgQuery.val(str);
    },

    /**************************** Event Handlers *****************************/
    /**
     * 검색창이 click 되었을 때 실행되는 이벤트 핸들러 함수
     * @private
     */
    _onClick: function() {
        //입력된 키워드가 없거나 자동완성 기능 사용하지 않으면 펼칠 필요 없으므로 그냥 리턴하고 끝.
        if (!this.autoCompleteObj.getValue() ||
            !this.autoCompleteObj.isUseAutoComplete()) {
            return false;
        }

        if (this.autoCompleteObj.isShowResultList()) {
            //결과 리스트 영역이 show 상태이면(isResultShowing==true) 결과 리스트 hide 요청
            this.autoCompleteObj.hideResultList();
        } else {
            //결과 리스트 영역이 hide 상태이면(isResultShowing==false) 결과 리스트 show 요청
            this.autoCompleteObj.showResultList();
        }
    },

    /**
     * 검색창에 focus 되었을때 실행되는 이벤트 핸들러 함수
     * @private
     */
    _onFocus: function() {
        var self = this;

        //setInterval 설정해서 일정 시간 주기로 _onWatch 함수를 실행한다.
        this.intervalId = setInterval($.proxy(function() {
            self._onWatch();
        }), this, 200);
    },

    /**
     * 주기적으로 호출되면서 검색창의 입력값 변경 여부를 판단한다.
     * @private
     */
    _onWatch: function() {
        //입력창에 입력된 값이 없으면 orgQueryElement 의 값도 초기화한다.
        if (this.$searchBox.val() === '') {
            this._setOrgQuery('');
            this.autoCompleteObj.setMoved(false);
        }

        //입력값에 변경이 생겼다면 ([예] 운 --> 운동 --> 운동화) 서버에 데이터 요청하도록 한다.
        if (this.inputValue !== this.$searchBox.val()) {
            this.inputValue = this.$searchBox.val();
            this._onChange();
        } else if (!this.autoCompleteObj.getMoved()) {
            this._setOrgQuery(this.$searchBox.val());
        }
    },

    /**
     * 검색창이 keyup되었을때 실행되는 이벤트 핸들러 함수
     * @private
     */
    _onKeyUp: function() {
        //입력값에 변경이 생겼다면 ( 소녀 --> 소녀시 --> 소녀시대 )
        //_onChange함수를 통해 서버에 데이터 요청하도록 한다.

        if (this.inputValue !== this.$searchBox.val()) {
            this.inputValue = this.$searchBox.val();
            this._onChange();
        }
    },

    /**
     * 검색어 입력창의 입력값에 변화가 생겼을 때 실행되는 함수
     * DataManager에 변경된 검색어를 전송하여 서버로 request하도록 한다.
     * @private
     */
    _onChange: function() {
        if (!this.autoCompleteObj.isUseAutoComplete()) {
            return;
        }

        this.autoCompleteObj.request(this.$searchBox.val());
    },

    /**
     * 검색창의 blur이벤트 처리하는 핸들러
     * @private
     */
    _onBlur: function() {
        //onFocus에서 설정했던 setInterval함수 해제
        if (this.intervalId) {
            clearInterval(this.intervalId);
            this.intervalId = null;
        }
    },

    /**
     * 검색창 keydown event 처리 핸들러. 입력키값에 따라서 액션을 정의한다.
     * @param {Event} keyDown 이벤트 객체
     * @private
     */
    _onKeyDown: function(e) {
        if (!this.autoCompleteObj.isUseAutoComplete() ||
            !this.autoCompleteObj.isVisibleResult()) {
            return;
        }

        //입력키값(TAB,방향키)에 따른 액션 정의
        if (e.keyCode == this.keyCodeMap.TAB) {
            e.preventDefault();
            e.shiftKey ? this.autoCompleteObj.movePrevKeyword(e) : this.autoCompleteObj.moveNextKeyword(e);
        } else if (e.keyCode === this.keyCodeMap.DOWN_ARROW) {
            this.autoCompleteObj.moveNextKeyword(e);
        } else if (e.keyCode === this.keyCodeMap.UP_ARROW) {
            this.autoCompleteObj.movePrevKeyword(e);
        }
    },

    /**
     * 토글버튼에 대한 click event를 처리한다.
     * @private
     */
    _onClickToggle: function() {
        if (!this.autoCompleteObj.isUseAutoComplete()) {
            //자동완성 끄기 상태일 때 : off 버튼 클릭하면 on버튼으로 바꾸고 자동완성 기능 켜기.

            //on버튼으로 바꾸기
            this.setToggleBtnImg(true);

            //자동완성 기능 켜기 (cookie설정)
            this.autoCompleteObj.setCookieValue(true);

            //텍스트를 [자동완성 끄기] 로 설정하기
            this.autoCompleteObj.changeOnOffText(false);
        } else {
            //자동완성 켜기 상태일 때 : on 버튼 클릭하면 off버튼으로 바꾸고 자동완성 기능 끄기.

            this.autoCompleteObj.hideResultList();

            this.setToggleBtnImg(false);
            this.autoCompleteObj.setCookieValue(false);
            this.autoCompleteObj.changeOnOffText(true);
        }
    }
});<commit_msg>주석 수정<commit_after>/**
 * @fileOverview 자동완성 컴포넌트 중에서 입력창에 대한 기능을 제공하는 클래스
 * @author kihyun.lee@nhnent.com
 */

ne = window.ne || {};
ne.component = ne.component || {};

/**
 * 자동완성 컴포넌트의 구성 요소중 검색어 입력받는 입력창의 동작과 관련된 클래스. <br>
 * 단독으로 생성될 수 없으며 ne.component.AutoComplete클래스 내부에서 생성되어 사용된다.
 * @constructor
 */
ne.component.AutoComplete.InputManager = ne.util.defineClass(/**@lends ne.component.AutoComplete.InputManager.prototype */{

    keyCodeMap: {
        'TAB' : 9,
        'UP_ARROW' : 38,
        'DOWN_ARROW' : 40
    },

    /**
     * 초기화 함수
     * @param {Object} arguments
     */
    init: function() {
        if (arguments.length != 2) {
            alert('argument length error !');
        }
        this.autoCompleteObj = arguments[0];   //AutoComplete인스턴스를 저장한다.
        this.options = arguments[1];     //설정 옵션값을 저장한다.

        //Config에서 검색창 부분에서 필요한 엘리먼트 정보 가져온다.
        this.$searchBox = this.options.searchBoxElement;
        this.$toggleBtn = this.options.toggleBtnElement;
        this.$orgQuery = this.options.orgQueryElement;

        //입력값 저장해두기
        this.inputValue = this.$searchBox.val();

        this._attachEvent();
    },

    /**
     * 검색창에 세팅된 키워드값을 리턴한다.
     * @return {String} 검색창에 세팅된 키워드
     */
    getValue: function() {
        return this.$searchBox.val();
    },

    /**
     * 검색창에 키워드값을 세팅한다.
     * @param {String} str 검색창에 세팅할 키워드 값
     */
    setValue: function(str) {
        this.inputValue = str;
        this.$searchBox.val(str);
    },

    /**
     * on/off 토글버튼 이미지를 변경한다.
     * @param {Boolean} isUse 자동완성 사용 여부
     */
    setToggleBtnImg: function(isUse) {
        if (!this.options.toggleImg || !(this.$toggleBtn)) {
            return;
        }

        if (isUse) {
            this.$toggleBtn.attr('src', this.options.toggleImg.on);
        } else {
            this.$toggleBtn.attr('src', this.options.toggleImg.off);
        }
    },

    /**************************** Private Functions **************************/
    /**
     * 이벤트 바인딩 함수
     * @private
     */
    _attachEvent: function() {
        //검색창에 focus, keyup, keydown, click 이벤트 바인딩.
        this.$searchBox.bind('focus keyup keydown blur click', $.proxy(function(e) {
            if (e.type === 'focus') {
                this._onFocus();
            } else if (e.type === 'blur') {
                this._onBlur(e);
            } else if (e.type === 'keyup') {
                this._onKeyUp(e);
            } else if (e.type === 'keydown') {
                this._onKeyDown(e);
            } else if (e.type === 'click') {
                this._onClick();
            }
        }, this));

        if (this.$toggleBtn) {
            this.$toggleBtn.bind('click', $.proxy(function(e) {
                this._onClickToggle();
            }, this));
        }
    },

    /**
     * 사용자가 입력했던 원본 쿼리값을 html hidden value로 세팅한다.
     * @param {String} str 사용자가 검색창에 입력한 검색어 스트링.
     * @private
     */
    _setOrgQuery: function(str) {
        this.$orgQuery.val(str);
    },

    /**************************** Event Handlers *****************************/
    /**
     * 검색창이 click 되었을 때 실행되는 이벤트 핸들러 함수
     * @private
     */
    _onClick: function() {
        //입력된 키워드가 없거나 자동완성 기능 사용하지 않으면 펼칠 필요 없으므로 그냥 리턴하고 끝.
        if (!this.autoCompleteObj.getValue() ||
            !this.autoCompleteObj.isUseAutoComplete()) {
            return false;
        }

        if (this.autoCompleteObj.isShowResultList()) {
            //결과 리스트 영역이 show 상태이면(isResultShowing==true) 결과 리스트 hide 요청
            this.autoCompleteObj.hideResultList();
        } else {
            //결과 리스트 영역이 hide 상태이면(isResultShowing==false) 결과 리스트 show 요청
            this.autoCompleteObj.showResultList();
        }
    },

    /**
     * 검색창에 focus 되었을때 실행되는 이벤트 핸들러 함수
     * @private
     */
    _onFocus: function() {
        var self = this;

        //setInterval 설정해서 일정 시간 주기로 _onWatch 함수를 실행한다.
        this.intervalId = setInterval($.proxy(function() {
            self._onWatch();
        }), this, 200);
    },

    /**
     * 주기적으로 호출되면서 검색창의 입력값 변경 여부를 판단한다.
     * @private
     */
    _onWatch: function() {
        //입력창에 입력된 값이 없으면 orgQueryElement 의 값도 초기화한다.
        if (this.$searchBox.val() === '') {
            this._setOrgQuery('');
            this.autoCompleteObj.setMoved(false);
        }

        //입력값에 변경이 생겼다면 ([예] 운 --> 운동 --> 운동화) 서버에 데이터 요청하도록 한다.
        if (this.inputValue !== this.$searchBox.val()) {
            this.inputValue = this.$searchBox.val();
            this._onChange();
        } else if (!this.autoCompleteObj.getMoved()) {
            this._setOrgQuery(this.$searchBox.val());
        }
    },

    /**
     * 검색창이 keyup되었을때 실행되는 이벤트 핸들러 함수
     * @private
     */
    _onKeyUp: function() {
        //입력값에 변경이 생겼다면 ( 소녀 --> 소녀시 --> 소녀시대 )
        //_onChange함수를 통해 서버에 데이터 요청하도록 한다.

        if (this.inputValue !== this.$searchBox.val()) {
            this.inputValue = this.$searchBox.val();
            this._onChange();
        }
    },

    /**
     * 검색어 입력창의 입력값에 변화가 생겼을 때 실행되는 함수
     * DataManager에 변경된 검색어를 전송하여 서버로 request하도록 한다.
     * @private
     */
    _onChange: function() {
        if (!this.autoCompleteObj.isUseAutoComplete()) {
            return;
        }

        this.autoCompleteObj.request(this.$searchBox.val());
    },

    /**
     * 검색창의 blur이벤트 처리하는 핸들러
     * @private
     */
    _onBlur: function() {
        //onFocus에서 설정했던 setInterval함수 해제
        if (this.intervalId) {
            clearInterval(this.intervalId);
            this.intervalId = null;
        }
    },

    /**
     * 검색창 keydown event 처리 핸들러. 입력키값에 따라서 액션을 정의한다.
     * @param {Event} keyDown 이벤트 객체
     * @private
     */
    _onKeyDown: function(e) {
        if (!this.autoCompleteObj.isUseAutoComplete() ||
            !this.autoCompleteObj.isVisibleResult()) {
            return;
        }

        //입력키값(TAB,방향키)에 따른 액션 정의
        if (e.keyCode == this.keyCodeMap.TAB) {
            e.preventDefault();
            e.shiftKey ? this.autoCompleteObj.movePrevKeyword(e) : this.autoCompleteObj.moveNextKeyword(e);
        } else if (e.keyCode === this.keyCodeMap.DOWN_ARROW) {
            this.autoCompleteObj.moveNextKeyword(e);
        } else if (e.keyCode === this.keyCodeMap.UP_ARROW) {
            this.autoCompleteObj.movePrevKeyword(e);
        }
    },

    /**
     * 토글버튼에 대한 click event를 처리한다.
     * @private
     */
    _onClickToggle: function() {
        if (!this.autoCompleteObj.isUseAutoComplete()) {
            //자동완성 끄기 상태일 때 : off 버튼 클릭하면 on버튼으로 바꾸고 자동완성 기능 켜기.

            //on버튼으로 바꾸기
            this.setToggleBtnImg(true);

            //자동완성 기능 켜기 (cookie설정)
            this.autoCompleteObj.setCookieValue(true);

            //텍스트를 [자동완성 끄기] 로 설정하기
            this.autoCompleteObj.changeOnOffText(false);
        } else {
            //자동완성 켜기 상태일 때 : on 버튼 클릭하면 off버튼으로 바꾸고 자동완성 기능 끄기.

            this.autoCompleteObj.hideResultList();

            this.setToggleBtnImg(false);
            this.autoCompleteObj.setCookieValue(false);
            this.autoCompleteObj.changeOnOffText(true);
        }
    }
});<|endoftext|>


<commit_before>/**
* svg_object syntax:
*	{
* 		"node":scene,
* 		"width":300,
*		"height":300,
*		"shapes":[
*			{
*				"type": "circle",
*				"class": "circleclass",
*				"cx": 110,
*				"cy": 110,
*				"r": 100,
*				"fill": "#00ff00",
*				"filter":"f1"
*			},
*			{
*				"type": "path",
*				"d": "M50,50 100,100"
*			}
*		],
*		"filters": [
*			"name":"f1",
*			"type": "feGaussianBlur",
*	  		"stdDeviation": 2
*		]
*	}
*/

Util.svg = function(svg_object) {
//	u.bug("create svg");

	var svg, shape, svg_shape;

	// when SVG's are used for icons or repeated graphics in website,
	// it is faster to clone than to recreate

	// if svg object has ID, store it in "cache", to be cloned if used again in same page

	// Look for svg in cache
	if(svg_object.name && u._svg_cache && u._svg_cache[svg_object.name]) {
		svg = u._svg_cache[svg_object.name].cloneNode(true);
	}

	// svg was not found in cache
	if(!svg) {
		svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");

		// TODO: implement filters
//		if(svg_object.filters[shape]) {}

		// loop through shapes in svg object
		for(shape in svg_object.shapes) {

			Util.svgShape(svg, svg_object.shapes[shape]);

		}

		// store svg in cache if it has an id
		if(svg_object.name) {
			if(!u._svg_cache) {
				u._svg_cache = {};
			}
			u._svg_cache[svg_object.name] = svg.cloneNode(true);
		}
	}

	// update metadata
	// width and height settings
	if(svg_object.title) {
		svg.setAttributeNS(null, "title", svg_object.title);
	}
	// Error in WKHTMLTO / Webkit 533, when using svg_object.class - but svg_object["class"] works
	if(svg_object["class"]) {
		svg.setAttributeNS(null, "class", svg_object["class"]);
	}

	if(svg_object.width) {
		svg.setAttributeNS(null, "width", svg_object.width);
	}
	if(svg_object.height) {
		svg.setAttributeNS(null, "height", svg_object.height);
	}
	if(svg_object.id) {
		svg.setAttributeNS(null, "id", svg_object.id);
	}
	if(svg_object.node) {
		svg.node = svg_object.node;
	}

	// if node is specified, append svg to node
	if(svg_object.node) {
		svg_object.node.appendChild(svg);
	}

	// return svg
	return svg;

}

/**
*	{
*		"type": "circle",
*		"class": "circleclass",
*		"cx": 110,
*		"cy": 110,
*		"r": 100,
*		"fill": "#00ff00",
*		"filter":"f1"
*	},
*/
Util.svgShape = function(svg, svg_object) {

	// create svg shape
	svg_shape = document.createElementNS("http://www.w3.org/2000/svg", svg_object["type"]);
	svg_object["type"] = null;
	delete svg_object["type"];

	// loop through the remaining details of shapes
	for(detail in svg_object) {

		svg_shape.setAttributeNS(null, detail, svg_object[detail]);
	}

	return svg.appendChild(svg_shape);

}
<commit_msg>Added viewBox  option<commit_after>/**
* svg_object syntax:
*	{
* 		"node":scene,
* 		"width":300,
*		"height":300,
*		"shapes":[
*			{
*				"type": "circle",
*				"class": "circleclass",
*				"cx": 110,
*				"cy": 110,
*				"r": 100,
*				"fill": "#00ff00",
*				"filter":"f1"
*			},
*			{
*				"type": "path",
*				"d": "M50,50 100,100"
*			}
*		],
*		"filters": [
*			"name":"f1",
*			"type": "feGaussianBlur",
*	  		"stdDeviation": 2
*		]
*	}
*/

Util.svg = function(svg_object) {
//	u.bug("create svg");

	var svg, shape, svg_shape;

	// when SVG's are used for icons or repeated graphics in website,
	// it is faster to clone than to recreate

	// if svg object has ID, store it in "cache", to be cloned if used again in same page

	// Look for svg in cache
	if(svg_object.name && u._svg_cache && u._svg_cache[svg_object.name]) {
		svg = u._svg_cache[svg_object.name].cloneNode(true);
	}

	// svg was not found in cache
	if(!svg) {
		svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");

		// TODO: implement filters
//		if(svg_object.filters[shape]) {}

		// loop through shapes in svg object
		for(shape in svg_object.shapes) {

			Util.svgShape(svg, svg_object.shapes[shape]);

		}

		// store svg in cache if it has an id
		if(svg_object.name) {
			if(!u._svg_cache) {
				u._svg_cache = {};
			}
			u._svg_cache[svg_object.name] = svg.cloneNode(true);
		}
	}

	// update metadata
	// width and height settings
	if(svg_object.title) {
		svg.setAttributeNS(null, "title", svg_object.title);
	}
	// Error in WKHTMLTO / Webkit 533, when using svg_object.class - but svg_object["class"] works
	if(svg_object["class"]) {
		svg.setAttributeNS(null, "class", svg_object["class"]);
	}

	if(svg_object.width) {
		svg.setAttributeNS(null, "width", svg_object.width);
	}
	if(svg_object.height) {
		svg.setAttributeNS(null, "height", svg_object.height);
	}
	if(svg_object.id) {
		svg.setAttributeNS(null, "id", svg_object.id);
	}
	if(svg_object.viewBox) {
		svg.setAttributeNS(null, "viewBox", svg_object.viewBox);
	}
	if(svg_object.node) {
		svg.node = svg_object.node;
	}

	// if node is specified, append svg to node
	if(svg_object.node) {
		svg_object.node.appendChild(svg);
	}

	// return svg
	return svg;

}

/**
*	{
*		"type": "circle",
*		"class": "circleclass",
*		"cx": 110,
*		"cy": 110,
*		"r": 100,
*		"fill": "#00ff00",
*		"filter":"f1"
*	},
*/
Util.svgShape = function(svg, svg_object) {

	// create svg shape
	svg_shape = document.createElementNS("http://www.w3.org/2000/svg", svg_object["type"]);
	svg_object["type"] = null;
	delete svg_object["type"];

	// loop through the remaining details of shapes
	for(detail in svg_object) {

		svg_shape.setAttributeNS(null, detail, svg_object[detail]);
	}

	return svg.appendChild(svg_shape);

}
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2014 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

define(function (require, exports) {
    "use strict";

    var Promise = require("bluebird"),
        Immutable = require("immutable");

    var os = require("adapter/os");

    var events = require("../events"),
        locks = require("../locks"),
        layers = require("js/actions/layers"),
        menu = require("./menu"),
        collection = require("js/util/collection"),
        headlights = require("js/util/headlights"),
        history = require("js/actions/history");

    /**
     * Native menu command IDs for Photoshop edit commands.
     *
     * @const
     * @private
     * @type {number}
     */
    var CUT_NATIVE_MENU_COMMMAND_ID = 103,
        COPY_NATIVE_MENU_COMMMAND_ID = 104,
        PASTE_NATIVE_MENU_COMMMAND_ID = 105,
        SELECT_ALL_NATIVE_MENU_COMMMAND_ID = 1017;

    var LAYER_CLIPBOARD_FORMAT = "com.adobe.photoshop.spaces.design.layers";

    /**
     * Determines whether the given element is an HTML input element.
     *
     * @private
     * @param {HTMLElement} el
     * @return {boolean}
     */
    var _isInput = function (el) {
        return el instanceof window.HTMLInputElement;
    };

    /**
     * Determines whether the given input element is a textual element.
     *
     * @private
     * @param {HTMLInputElement} el
     * @return {boolean}
     */
    var _isTextInput = function (el) {
        switch (el.type) {
            case "text":
            case "search":
            case "url":
            case "tel":
            case "password":
                return true;
            default:
                return false;
        }
    };

    /**
     * Execute a native cut command.
     *
     * @private
     * @return {Promise}
     */
    var nativeCut = function () {
        return this.transfer(menu.nativeModal, {
            commandID: CUT_NATIVE_MENU_COMMMAND_ID
        })
        .catch(function () {
            // Ignore errors from menu.nativeModal
        });
    };
    nativeCut.modal = true;
    nativeCut.reads = [];
    nativeCut.writes = [];
    nativeCut.transfers = [menu.nativeModal];

    /**
     * Execute a native copy command.
     *
     * @private
     * @return {Promise}
     */
    var nativeCopy = function () {
        return this.transfer(menu.nativeModal, {
            commandID: COPY_NATIVE_MENU_COMMMAND_ID
        })
        .catch(function () {
            // Ignore errors from menu.nativeModal
        });
    };
    nativeCopy.modal = true;
    nativeCopy.reads = [];
    nativeCopy.writes = [];
    nativeCopy.transfers = [menu.nativeModal];

    /**
     * Execute a native paste command.
     *
     * @private
     * @return {Promise}
     */
    var nativePaste = function () {
        return this.transfer(menu.nativeModal, {
            commandID: PASTE_NATIVE_MENU_COMMMAND_ID
        })
        .catch(function () {
            // Ignore errors from menu.nativeModal
        });
    };
    nativePaste.modal = true;
    nativePaste.reads = [];
    nativePaste.writes = [];
    nativePaste.transfers = [menu.nativeModal];

    /**
     * Execute a native selectAll command.
     *
     * @private
     * @param {boolean} waitForCompletion Flag for nativeModal
     * @return {Promise}
     */
    var nativeSelectAll = function (waitForCompletion) {
        waitForCompletion = waitForCompletion || false;

        return this.transfer(menu.nativeModal, {
            commandID: SELECT_ALL_NATIVE_MENU_COMMMAND_ID,
            waitForCompletion: waitForCompletion
        })
        .catch(function () {
            // Ignore errors from menu.nativeModal
        });
    };
    nativeSelectAll.modal = true;
    nativeSelectAll.reads = [];
    nativeSelectAll.writes = [];
    nativeSelectAll.transfers = [menu.nativeModal];

    /**
     * Execute either a cut or copy operation, depending on the value of the parameter.
     *
     * @private
     * @param {boolean} cut If true, perform a cut operation; otherwise, a copy.
     * @return {Promise}
     */
    var _cutOrCopy = function (cut) {
        return os.hasKeyboardFocus()
            .bind(this)
            .then(function (cefHasFocus) {
                var el = window.document.activeElement,
                    data;

                if (cefHasFocus && _isInput(el)) {
                    if (_isTextInput(el)) {
                        data = el.value.substring(el.selectionStart, el.selectionEnd);
                        if (cut) {
                            el.setRangeText("");
                        }
                    } else {
                        data = el.value;
                    }
                } else {
                    // Even if CEF doesn't have focus, a disabled input could have a selection
                    var selection = window.document.getSelection();
                    if (selection.type === "Range") {
                        data = selection.toString();
                    }
                }

                if (typeof data === "string") {
                    var cutCopyEvent = new window.Event(cut ? "cut" : "copy", { bubbles: true });
                    el.dispatchEvent(cutCopyEvent);

                    return os.clipboardWrite(data);
                }

                // If we're on modal state (type edit), we should go with native copy/cut
                if (this.flux.store("tool").getModalToolState()) {
                    if (cut) {
                        return this.transfer(nativeCut);
                    } else {
                        return this.transfer(nativeCopy);
                    }
                } else if (!cut) {
                    var applicationStore = this.flux.store("application"),
                        document = applicationStore.getCurrentDocument();

                    if (!document || document.unsupported) {
                        return;
                    }

                    var layerIDs = collection.pluck(document.layers.selectedNormalized, "id"),
                        payload = {
                            document: document.id,
                            layers: layerIDs
                        },
                        rawPayload = JSON.stringify(payload);

                    headlights.logEvent("edit", "layers", "copy_layers");
                    return os.clipboardWrite(rawPayload, LAYER_CLIPBOARD_FORMAT);
                }
            });
    };

    /**
     * Execute a cut operation on the currently active HTML element.
     *
     * @private
     * @return {Promise}
     */
    var cut = function () {
        return _cutOrCopy.call(this, true);
    };
    cut.modal = true;
    cut.reads = [locks.JS_TOOL, locks.PS_APP];
    cut.writes = [locks.JS_DOC, locks.PS_DOC, locks.OS_CLIPBOARD];
    cut.transfers = [nativeCut];

    /**
     * Execute a copy operation on the currently active HTML element.
     *
     * @private
     * @return {Promise}
     */
    var copy = function () {
        return _cutOrCopy.call(this, false);
    };
    copy.modal = true;
    copy.reads = [locks.JS_DOC, locks.JS_TOOL, locks.PS_APP];
    copy.writes = [locks.OS_CLIPBOARD];
    copy.transfers = [nativeCopy];

    /**
     * Execute a paste operation on the currently active HTML element.
     *
     * @private
     * @return {Promise}
     */
    var paste = function () {
        return os.hasKeyboardFocus()
            .bind(this)
            .then(function (cefHasFocus) {
                var el = window.document.activeElement;
                if (cefHasFocus && _isInput(el)) {
                    return os.clipboardRead()
                        .then(function (result) {
                            var data = result.data,
                                format = result.format;

                            if (format !== "string") {
                                return;
                            }

                            if (_isTextInput(el)) {
                                var selectionStart = el.selectionStart;
                                el.setRangeText(data);
                                el.setSelectionRange(selectionStart + data.length, selectionStart + data.length);
                            } else {
                                el.value = data;
                            }

                            var pasteEvent = new window.Event("paste", { bubbles: true });
                            el.dispatchEvent(pasteEvent);
                        });
                } else {
                    return os.clipboardRead([LAYER_CLIPBOARD_FORMAT])
                        .bind(this)
                        .then(function (result) {
                            var format = result.format;
                            if (format !== LAYER_CLIPBOARD_FORMAT) {
                                return this.transfer(nativePaste);
                            }

                            var applicationStore = this.flux.store("application"),
                                document = applicationStore.getCurrentDocument();

                            if (!document || document.unsupported) {
                                return;
                            }

                            var data = result.data,
                                payload = JSON.parse(data),
                                documentID = payload.document,
                                documentStore = this.flux.store("document"),
                                fromDocument = documentStore.getDocument(documentID);

                            if (!fromDocument || fromDocument.unsupported) {
                                return;
                            }

                            var layerIDs = payload.layers,
                                fromLayers = Immutable.List(layerIDs.reduce(function (layers, layerID) {
                                    var layer = fromDocument.layers.byID(layerID);
                                    if (layer) {
                                        layers.push(layer);
                                    }
                                    return layers;
                                }, []));

                            headlights.logEvent("edit", "layers", "paste_layers");
                            return this.transfer(layers.duplicate, document, fromDocument, fromLayers);
                        });
                }
            });
    };
    paste.modal = true;
    paste.reads = [locks.JS_DOC, locks.JS_APP, locks.OS_CLIPBOARD, locks.PS_APP];
    paste.writes = [];
    paste.transfers = [layers.duplicate, nativePaste];

    /**
     * Execute a select operation on the currently active HTML element.
     *
     * @private
     * @return {Promise}
     */
    var selectAll = function () {
        return os.hasKeyboardFocus()
            .bind(this)
            .then(function (cefHasFocus) {
                var el = window.document.activeElement;
                if (cefHasFocus && _isInput(el)) {
                    if (_isTextInput(el)) {
                        el.setSelectionRange(0, el.value.length);
                    }
                } else {
                    var toolStore = this.flux.store("tool");
                    if (toolStore.getModalToolState()) {
                        return this.transfer(nativeSelectAll);
                    } else {
                        return this.transfer(layers.selectAll);
                    }
                }
            });
    };
    selectAll.modal = true;
    selectAll.reads = [locks.JS_TOOL, locks.PS_APP];
    selectAll.writes = [];
    selectAll.transfers = [layers.selectAll, nativeSelectAll];

    /**
     * Step Backwards by transferring to the appropriate history action
     *
     * @private
     * @return {Promise}
     */
    var undo = function () {
        var currentDocument = this.flux.store("application").getCurrentDocument();
        if (!currentDocument) {
            return Promise.resolve();
        } else {
            return Promise.join(
                this.dispatchAsync(events.ui.TOGGLE_OVERLAYS, { enabled: false }),
                this.transfer(history.decrementHistory, currentDocument),
                function () {
                    return this.dispatchAsync(events.ui.TOGGLE_OVERLAYS, { enabled: true });
                }.bind(this));
        }
    };
    undo.reads = [locks.JS_APP, locks.JS_DOC];
    undo.writes = [locks.JS_UI];
    undo.transfers = [history.decrementHistory];
    undo.modal = true;

    /**
     * Step Forward by transferring to the appropriate history action
     *
     * @private
     * @return {Promise}
     */
    var redo = function () {
        var currentDocument = this.flux.store("application").getCurrentDocument();
        if (!currentDocument) {
            return Promise.resolve();
        } else {
            return Promise.join(
                this.dispatchAsync(events.ui.TOGGLE_OVERLAYS, { enabled: false }),
                this.transfer(history.incrementHistory, currentDocument),
                function () {
                    return this.dispatchAsync(events.ui.TOGGLE_OVERLAYS, { enabled: true });
                }.bind(this));
        }
    };
    redo.reads = [locks.JS_APP, locks.JS_DOC];
    redo.writes = [locks.JS_UI];
    redo.transfers = [history.incrementHistory];
    undo.modal = true;

    exports.nativeCut = nativeCut;
    exports.nativeCopy = nativeCopy;
    exports.nativePaste = nativePaste;
    exports.nativeSelectAll = nativeSelectAll;
    exports.cut = cut;
    exports.copy = copy;
    exports.paste = paste;
    exports.selectAll = selectAll;
    exports.undo = undo;
    exports.redo = redo;
});
<commit_msg>Suspend/Restore around native pasting.<commit_after>/*
 * Copyright (c) 2014 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

define(function (require, exports) {
    "use strict";

    var Promise = require("bluebird"),
        Immutable = require("immutable");

    var os = require("adapter/os");

    var events = require("../events"),
        locks = require("../locks"),
        layers = require("js/actions/layers"),
        menu = require("./menu"),
        collection = require("js/util/collection"),
        headlights = require("js/util/headlights"),
        history = require("js/actions/history"),
        policyActions = require("js/actions/policy");

    /**
     * Native menu command IDs for Photoshop edit commands.
     *
     * @const
     * @private
     * @type {number}
     */
    var CUT_NATIVE_MENU_COMMMAND_ID = 103,
        COPY_NATIVE_MENU_COMMMAND_ID = 104,
        PASTE_NATIVE_MENU_COMMMAND_ID = 105,
        SELECT_ALL_NATIVE_MENU_COMMMAND_ID = 1017;

    var LAYER_CLIPBOARD_FORMAT = "com.adobe.photoshop.spaces.design.layers";

    /**
     * Determines whether the given element is an HTML input element.
     *
     * @private
     * @param {HTMLElement} el
     * @return {boolean}
     */
    var _isInput = function (el) {
        return el instanceof window.HTMLInputElement;
    };

    /**
     * Determines whether the given input element is a textual element.
     *
     * @private
     * @param {HTMLInputElement} el
     * @return {boolean}
     */
    var _isTextInput = function (el) {
        switch (el.type) {
            case "text":
            case "search":
            case "url":
            case "tel":
            case "password":
                return true;
            default:
                return false;
        }
    };

    /**
     * Execute a native cut command.
     *
     * @private
     * @return {Promise}
     */
    var nativeCut = function () {
        return this.transfer(menu.nativeModal, {
            commandID: CUT_NATIVE_MENU_COMMMAND_ID
        })
        .catch(function () {
            // Ignore errors from menu.nativeModal
        });
    };
    nativeCut.modal = true;
    nativeCut.reads = [];
    nativeCut.writes = [];
    nativeCut.transfers = [menu.nativeModal];

    /**
     * Execute a native copy command.
     *
     * @private
     * @return {Promise}
     */
    var nativeCopy = function () {
        return this.transfer(menu.nativeModal, {
            commandID: COPY_NATIVE_MENU_COMMMAND_ID
        })
        .catch(function () {
            // Ignore errors from menu.nativeModal
        });
    };
    nativeCopy.modal = true;
    nativeCopy.reads = [];
    nativeCopy.writes = [];
    nativeCopy.transfers = [menu.nativeModal];

    /**
     * Execute a native paste command.
     *
     * @private
     * @return {Promise}
     */
    var nativePaste = function () {
        // FIXME: the suspend/restore policies hack is for pasting smart object from other sources (e.g. Illustrator).
        // The cause is similar to the place-object menu commands: DS is not receiving "toolModalStateChanged" events
        // until the object is committed.
        return this.transfer(policyActions.suspendAllPolicies)
            .bind(this)
            .then(function () {
                return this.transfer(menu.nativeModal, {
                    commandID: PASTE_NATIVE_MENU_COMMMAND_ID
                });
            })
            .finally(function () {
                if (this.flux.store("policy").areAllSuspended()) {
                    return this.transfer(policyActions.restoreAllPolicies);
                }
            });
    };
    nativePaste.modal = true;
    nativePaste.reads = [];
    nativePaste.writes = [];
    nativePaste.transfers = [menu.nativeModal, policyActions.suspendAllPolicies, policyActions.restoreAllPolicies];

    /**
     * Execute a native selectAll command.
     *
     * @private
     * @param {boolean} waitForCompletion Flag for nativeModal
     * @return {Promise}
     */
    var nativeSelectAll = function (waitForCompletion) {
        waitForCompletion = waitForCompletion || false;

        return this.transfer(menu.nativeModal, {
            commandID: SELECT_ALL_NATIVE_MENU_COMMMAND_ID,
            waitForCompletion: waitForCompletion
        })
        .catch(function () {
            // Ignore errors from menu.nativeModal
        });
    };
    nativeSelectAll.modal = true;
    nativeSelectAll.reads = [];
    nativeSelectAll.writes = [];
    nativeSelectAll.transfers = [menu.nativeModal];

    /**
     * Execute either a cut or copy operation, depending on the value of the parameter.
     *
     * @private
     * @param {boolean} cut If true, perform a cut operation; otherwise, a copy.
     * @return {Promise}
     */
    var _cutOrCopy = function (cut) {
        return os.hasKeyboardFocus()
            .bind(this)
            .then(function (cefHasFocus) {
                var el = window.document.activeElement,
                    data;

                if (cefHasFocus && _isInput(el)) {
                    if (_isTextInput(el)) {
                        data = el.value.substring(el.selectionStart, el.selectionEnd);
                        if (cut) {
                            el.setRangeText("");
                        }
                    } else {
                        data = el.value;
                    }
                } else {
                    // Even if CEF doesn't have focus, a disabled input could have a selection
                    var selection = window.document.getSelection();
                    if (selection.type === "Range") {
                        data = selection.toString();
                    }
                }

                if (typeof data === "string") {
                    var cutCopyEvent = new window.Event(cut ? "cut" : "copy", { bubbles: true });
                    el.dispatchEvent(cutCopyEvent);

                    return os.clipboardWrite(data);
                }

                // If we're on modal state (type edit), we should go with native copy/cut
                if (this.flux.store("tool").getModalToolState()) {
                    if (cut) {
                        return this.transfer(nativeCut);
                    } else {
                        return this.transfer(nativeCopy);
                    }
                } else if (!cut) {
                    var applicationStore = this.flux.store("application"),
                        document = applicationStore.getCurrentDocument();

                    if (!document || document.unsupported) {
                        return;
                    }

                    var layerIDs = collection.pluck(document.layers.selectedNormalized, "id"),
                        payload = {
                            document: document.id,
                            layers: layerIDs
                        },
                        rawPayload = JSON.stringify(payload);

                    headlights.logEvent("edit", "layers", "copy_layers");
                    return os.clipboardWrite(rawPayload, LAYER_CLIPBOARD_FORMAT);
                }
            });
    };

    /**
     * Execute a cut operation on the currently active HTML element.
     *
     * @private
     * @return {Promise}
     */
    var cut = function () {
        return _cutOrCopy.call(this, true);
    };
    cut.modal = true;
    cut.reads = [locks.JS_TOOL, locks.PS_APP];
    cut.writes = [locks.JS_DOC, locks.PS_DOC, locks.OS_CLIPBOARD];
    cut.transfers = [nativeCut];

    /**
     * Execute a copy operation on the currently active HTML element.
     *
     * @private
     * @return {Promise}
     */
    var copy = function () {
        return _cutOrCopy.call(this, false);
    };
    copy.modal = true;
    copy.reads = [locks.JS_DOC, locks.JS_TOOL, locks.PS_APP];
    copy.writes = [locks.OS_CLIPBOARD];
    copy.transfers = [nativeCopy];

    /**
     * Execute a paste operation on the currently active HTML element.
     *
     * @private
     * @return {Promise}
     */
    var paste = function () {
        return os.hasKeyboardFocus()
            .bind(this)
            .then(function (cefHasFocus) {
                var el = window.document.activeElement;
                if (cefHasFocus && _isInput(el)) {
                    return os.clipboardRead()
                        .then(function (result) {
                            var data = result.data,
                                format = result.format;

                            if (format !== "string") {
                                return;
                            }

                            if (_isTextInput(el)) {
                                var selectionStart = el.selectionStart;
                                el.setRangeText(data);
                                el.setSelectionRange(selectionStart + data.length, selectionStart + data.length);
                            } else {
                                el.value = data;
                            }

                            var pasteEvent = new window.Event("paste", { bubbles: true });
                            el.dispatchEvent(pasteEvent);
                        });
                } else {
                    return os.clipboardRead([LAYER_CLIPBOARD_FORMAT])
                        .bind(this)
                        .then(function (result) {
                            var format = result.format;
                            if (format !== LAYER_CLIPBOARD_FORMAT) {
                                return this.transfer(nativePaste);
                            }

                            var applicationStore = this.flux.store("application"),
                                document = applicationStore.getCurrentDocument();

                            if (!document || document.unsupported) {
                                return;
                            }

                            var data = result.data,
                                payload = JSON.parse(data),
                                documentID = payload.document,
                                documentStore = this.flux.store("document"),
                                fromDocument = documentStore.getDocument(documentID);

                            if (!fromDocument || fromDocument.unsupported) {
                                return;
                            }

                            var layerIDs = payload.layers,
                                fromLayers = Immutable.List(layerIDs.reduce(function (layers, layerID) {
                                    var layer = fromDocument.layers.byID(layerID);
                                    if (layer) {
                                        layers.push(layer);
                                    }
                                    return layers;
                                }, []));

                            headlights.logEvent("edit", "layers", "paste_layers");
                            return this.transfer(layers.duplicate, document, fromDocument, fromLayers);
                        });
                }
            });
    };
    paste.modal = true;
    paste.reads = [locks.JS_DOC, locks.JS_APP, locks.OS_CLIPBOARD, locks.PS_APP];
    paste.writes = [];
    paste.transfers = [layers.duplicate, nativePaste];

    /**
     * Execute a select operation on the currently active HTML element.
     *
     * @private
     * @return {Promise}
     */
    var selectAll = function () {
        return os.hasKeyboardFocus()
            .bind(this)
            .then(function (cefHasFocus) {
                var el = window.document.activeElement;
                if (cefHasFocus && _isInput(el)) {
                    if (_isTextInput(el)) {
                        el.setSelectionRange(0, el.value.length);
                    }
                } else {
                    var toolStore = this.flux.store("tool");
                    if (toolStore.getModalToolState()) {
                        return this.transfer(nativeSelectAll);
                    } else {
                        return this.transfer(layers.selectAll);
                    }
                }
            });
    };
    selectAll.modal = true;
    selectAll.reads = [locks.JS_TOOL, locks.PS_APP];
    selectAll.writes = [];
    selectAll.transfers = [layers.selectAll, nativeSelectAll];

    /**
     * Step Backwards by transferring to the appropriate history action
     *
     * @private
     * @return {Promise}
     */
    var undo = function () {
        var currentDocument = this.flux.store("application").getCurrentDocument();
        if (!currentDocument) {
            return Promise.resolve();
        } else {
            return Promise.join(
                this.dispatchAsync(events.ui.TOGGLE_OVERLAYS, { enabled: false }),
                this.transfer(history.decrementHistory, currentDocument),
                function () {
                    return this.dispatchAsync(events.ui.TOGGLE_OVERLAYS, { enabled: true });
                }.bind(this));
        }
    };
    undo.reads = [locks.JS_APP, locks.JS_DOC];
    undo.writes = [locks.JS_UI];
    undo.transfers = [history.decrementHistory];
    undo.modal = true;

    /**
     * Step Forward by transferring to the appropriate history action
     *
     * @private
     * @return {Promise}
     */
    var redo = function () {
        var currentDocument = this.flux.store("application").getCurrentDocument();
        if (!currentDocument) {
            return Promise.resolve();
        } else {
            return Promise.join(
                this.dispatchAsync(events.ui.TOGGLE_OVERLAYS, { enabled: false }),
                this.transfer(history.incrementHistory, currentDocument),
                function () {
                    return this.dispatchAsync(events.ui.TOGGLE_OVERLAYS, { enabled: true });
                }.bind(this));
        }
    };
    redo.reads = [locks.JS_APP, locks.JS_DOC];
    redo.writes = [locks.JS_UI];
    redo.transfers = [history.incrementHistory];
    undo.modal = true;

    exports.nativeCut = nativeCut;
    exports.nativeCopy = nativeCopy;
    exports.nativePaste = nativePaste;
    exports.nativeSelectAll = nativeSelectAll;
    exports.cut = cut;
    exports.copy = copy;
    exports.paste = paste;
    exports.selectAll = selectAll;
    exports.undo = undo;
    exports.redo = redo;
});
<|endoftext|>