<commit_before>"use strict";

Object.defineProperty(exports, "__esModule", {
	value: true
});
exports.render = render;

var _access = require("./access");

var _dom = require("./dom");

function same(node, vnode) {
	if (node === vnode) return true;
	if (node === undefined || vnode === undefined) return false;
	var inode = vnode.inode;
	if (node.nodeType !== vnode.type) return false;
	if (node["@@doc-depth"] !== inode._depth) return false;
	if (node.nodeValue !== null) {
		if (node.nodeValue !== vnode.value) return false;
	} else if (node.nodeName !== (inode._name + '').toUpperCase()) return false;
	return true;
}

function render(vnode, root) {
	// fixme stateless
	var parents = [{ domNode: root }];
	const attrFunc = (domNode, v, k) => (domNode.setAttribute(k, v), domNode);
	// ensure paths by calling iter
	var domNodes = (0, _dom.nodesList)(root);
	var i = 0;
	var skipDepth = 0,
	    append = false,
	    nextSame = false;
	var handleNode = function (node) {
		// TODO this won't work when pushed from server
		// we could diff an L3 buffer and update the tree (stateless)
		// perhaps it would be better to separate VNode and domNodes, but where to put the WeakMap?
		var type = node.type,
		    inode = node.inode,
		    domNode = node.domNode,
		    cur = domNodes[i],
		    next = domNodes[i + 1],
		    nn = (0, _access.nextNode)(node);
		var curSame = nextSame || same(cur, node);
		nextSame = same(next, nn);
		if (cur && curSame && nextSame) {
			// skip until next
			// console.log("same",cur,cur["@@doc-depth"],node.name,inode._depth);
			node.domNode = cur;
			skipDepth = cur["@@doc-depth"];
			if (type == 1) parents[inode._depth] = node;
		} else {
			if (cur) {
				if (cur["@@doc-depth"] == inode._depth - 1) {
					//console.log("append",cur);
					append = true;
				} else if (cur["@@doc-depth"] == inode._depth + 1) {
					// console.log("remove",cur);
					// don't remove text, it will be garbage collected
					if (cur.nodeType == 1) cur.parentNode.removeChild(cur);
					// remove from dom, retry this node
					// keep node untill everything is removed
					i++;
					return handleNode(node);
				} else {
					if (type == 1) {
						if (cur.nodeType != 17) cur.parentNode.removeChild(cur);
						// remove from dom, retry this node
						i++;
						return handleNode(node);
					} else if (type == 3) {
						// if we're updating a text node, we should be sure it's the same parent
						if (cur["@@doc-depth"] == skipDepth + 1) {
							cur.nodeValue = node.value;
						} else {
							append = true;
						}
					}
				}
			}
			if (!cur || append) {
				//console.log("empty",type, append)
				if (type == 1) {
					domNode = document.createElement(node.name);
					if (parents[inode._depth - 1]) parents[inode._depth - 1].domNode.appendChild(domNode);
					inode._attrs.reduce(attrFunc, domNode);
					parents[inode._depth] = node;
				} else if (type == 3) {
					domNode = document.createTextNode(node.value);
					parents[inode._depth - 1].domNode.appendChild(domNode);
				}
				node.domNode = domNode;
			}
		}
		if (!append) {
			i++;
		} else {
			append = false;
		}
	};
	(0, _access.iter)(vnode, handleNode);
	var l = domNodes.length;
	for (; --l >= i;) {
		var node = domNodes[l];
		if (node.nodeType == 1) node.parentNode.removeChild(node);
	}
}

function createProjection(vnode, projectionOptions = {}) {
	return {
		update: function (updatedVnode) {
			if (vnode.vnodeSelector !== updatedVnode.vnodeSelector) {
				throw new Error('The selector for the root VNode may not be changed. (consider using dom.merge and add one extra level to the virtual DOM)');
			}
			updateDom(vnode, updatedVnode, projectionOptions);
			vnode = updatedVnode;
		},
		domNode: vnode._domNode
	};
}

function updateDom(previous, vnode, projectionOptions) {
	let domNode = previous.domNode;
	let textUpdated = false;
	if (previous === vnode) {
		return false;
	}
}

function updateDom2(previous, vnode, projectionOptions) {
	let domNode = previous.domNode;
	let textUpdated = false;
	if (previous === vnode) {
		return false; // By contract, VNode objects may not be modified anymore after passing them to maquette
	}
	let updated = false;
	if (vnode.type == 3) {
		if (vnode.value !== previous.value) {
			let newVNode = document.createTextNode(vnode.value);
			domNode.parentNode.replaceChild(newVNode, domNode);
			vnode.domNode = newVNode;
			textUpdated = true;
			return textUpdated;
		}
	} else {
		// FIXME what type?
		if (vnode.vnodeSelector.lastIndexOf('svg', 0) === 0) {// lastIndexOf(needle,0)===0 means StartsWith
			//projectionOptions = extend(projectionOptions, { namespace: NAMESPACE_SVG });
		}
		if (previous.value !== vnode.value) {
			updated = true;
			if (vnode.value === undefined) {
				domNode.removeChild(domNode.firstChild); // the only textnode presumably
			} else {
				domNode.textContent = vnode.value;
			}
		}
		// FIXME recursion!
		updated = updateChildren(vnode, domNode, previous.children, vnode.children, projectionOptions) || updated;
		updated = updateProperties(domNode, previous.properties, vnode.properties, projectionOptions) || updated;
		if (vnode.properties && vnode.properties.afterUpdate) {
			vnode.properties.afterUpdate.apply(vnode.properties.bind || vnode.properties, [domNode, projectionOptions, vnode.vnodeSelector, vnode.properties, vnode.children]);
		}
	}
	if (updated && vnode.properties && vnode.properties.updateAnimation) {
		//vnode.properties.updateAnimation(domNode, vnode.properties, previous.properties);
	}
	vnode.domNode = previous.domNode;
	return textUpdated;
}

function updateChildren(vnode, domNode, oldChildren, newChildren, projectionOptions) {
	if (oldChildren === newChildren) {
		return false;
	}
	oldChildren = oldChildren || emptyArray;
	newChildren = newChildren || emptyArray;
	let oldChildrenLength = oldChildren.length;
	let newChildrenLength = newChildren.length;
	let transitions = projectionOptions.transitions;

	let oldIndex = 0;
	let newIndex = 0;
	let i;
	let textUpdated = false;
	while (newIndex < newChildrenLength) {
		let oldChild = oldIndex < oldChildrenLength ? oldChildren[oldIndex] : undefined;
		let newChild = newChildren[newIndex];
		if (oldChild !== undefined && same(oldChild, newChild)) {
			// FIXME recursion!
			// same node, so... what? update text just in case?
			textUpdated = updateDom(oldChild, newChild, projectionOptions) || textUpdated;
			oldIndex++;
		} else {
			let findOldIndex = findIndexOfChild(oldChildren, newChild, oldIndex + 1);
			if (findOldIndex >= 0) {
				// Remove preceding missing children
				for (i = oldIndex; i < findOldIndex; i++) {
					nodeToRemove(oldChildren[i], transitions);
					checkDistinguishable(oldChildren, i, vnode, 'removed');
				}
				// FIXME recusion!
				textUpdated = updateDom(oldChildren[findOldIndex], newChild, projectionOptions) || textUpdated;
				oldIndex = findOldIndex + 1;
			} else {
				// New child
				createDom(newChild, domNode, oldIndex < oldChildrenLength ? oldChildren[oldIndex].domNode : undefined, projectionOptions);
				nodeAdded(newChild, transitions);
				checkDistinguishable(newChildren, newIndex, vnode, 'added');
			}
		}
		newIndex++;
	}
	if (oldChildrenLength > oldIndex) {
		// Remove child fragments
		for (i = oldIndex; i < oldChildrenLength; i++) {
			nodeToRemove(oldChildren[i], transitions);
			checkDistinguishable(oldChildren, i, vnode, 'removed');
		}
	}
	return textUpdated;
}

function updateProperties(domNode, previousProperties, properties, projectionOptions) {
	if (!properties) {
		return;
	}
	let propertiesUpdated = false;
	let propNames = Object.keys(properties);
	let propCount = propNames.length;
	for (let i = 0; i < propCount; i++) {
		let propName = propNames[i];
		// assuming that properties will be nullified instead of missing is by design
		let propValue = properties[propName];
		let previousValue = previousProperties[propName];
		if (propName === 'class') {
			if (previousValue !== propValue) {
				throw new Error('"class" property may not be updated. Use the "classes" property for conditional css classes.');
			}
		} else if (propName === 'classes') {
			let classList = domNode.classList;
			let classNames = Object.keys(propValue);
			let classNameCount = classNames.length;
			for (let j = 0; j < classNameCount; j++) {
				let className = classNames[j];
				let on = !!propValue[className];
				let previousOn = !!previousValue[className];
				if (on === previousOn) {
					continue;
				}
				propertiesUpdated = true;
				if (on) {
					classList.add(className);
				} else {
					classList.remove(className);
				}
			}
		} else if (propName === 'styles') {
			let styleNames = Object.keys(propValue);
			let styleCount = styleNames.length;
			for (let j = 0; j < styleCount; j++) {
				let styleName = styleNames[j];
				let newStyleValue = propValue[styleName];
				let oldStyleValue = previousValue[styleName];
				if (newStyleValue === oldStyleValue) {
					continue;
				}
				propertiesUpdated = true;
				if (newStyleValue) {
					checkStyleValue(newStyleValue);
					projectionOptions.styleApplyer(domNode, styleName, newStyleValue);
				} else {
					projectionOptions.styleApplyer(domNode, styleName, '');
				}
			}
		} else {
			if (!propValue && typeof previousValue === 'string') {
				propValue = '';
			}
			if (propName === 'value') {
				// value can be manipulated by the user directly and using event.preventDefault() is not an option
				if (domNode[propName] !== propValue && domNode['oninput-value'] && domNode['oninput-value'] !== propValue) {
					domNode[propName] = propValue; // Reset the value, even if the virtual DOM did not change
					domNode['oninput-value'] = undefined;
				} // else do not update the domNode, otherwise the cursor position would be changed
				if (propValue !== previousValue) {
					propertiesUpdated = true;
				}
			} else if (propValue !== previousValue) {
				let type = typeof propValue;
				if (type === 'function') {
					throw new Error('Functions may not be updated on subsequent renders (property: ' + propName + '). Hint: declare event handler functions outside the render() function.');
				}
				if (type === 'string' && propName !== 'innerHTML') {
					if (projectionOptions.namespace === NAMESPACE_SVG && propName === 'href') {
						domNode.setAttributeNS(NAMESPACE_XLINK, propName, propValue);
					} else if (propName === 'role' && propValue === '') {
						domNode.removeAttribute(propName);
					} else {
						domNode.setAttribute(propName, propValue);
					}
				} else {
					if (domNode[propName] !== propValue) {
						// Comparison is here for side-effects in Edge with scrollLeft and scrollTop
						domNode[propName] = propValue;
					}
				}
				propertiesUpdated = true;
			}
		}
	}
	return propertiesUpdated;
}

function createDom(vnode, parentNode, insertBefore, projectionOptions) {
	let domNode,
	    i,
	    c,
	    start = 0,
	    type,
	    found;
	let vnodeSelector = vnode.vnodeSelector;
	if (vnodeSelector === '') {
		domNode = vnode.domNode = document.createTextNode(vnode.text);
		if (insertBefore !== undefined) {
			parentNode.insertBefore(domNode, insertBefore);
		} else {
			parentNode.appendChild(domNode);
		}
	} else {
		// parse selector
		for (i = 0; i <= vnodeSelector.length; ++i) {
			c = vnodeSelector.charAt(i);
			// if EOS or selector found
			if (i === vnodeSelector.length || c === '.' || c === '#') {
				type = vnodeSelector.charAt(start - 1);
				found = vnodeSelector.slice(start, i);
				if (type === '.') {
					domNode.classList.add(found);
				} else if (type === '#') {
					domNode.id = found;
				} else {
					if (found === 'svg') {
						projectionOptions = extend(projectionOptions, { namespace: NAMESPACE_SVG });
					}
					if (projectionOptions.namespace !== undefined) {
						domNode = vnode.domNode = document.createElementNS(projectionOptions.namespace, found);
					} else {
						domNode = vnode.domNode = document.createElement(found);
						if (found === 'input' && vnode.properties && vnode.properties.type !== undefined) {
							// IE8 and older don't support setting input type after the DOM Node has been added to the document
							domNode.setAttribute("type", vnode.properties.type);
						}
					}
					if (insertBefore !== undefined) {
						parentNode.insertBefore(domNode, insertBefore);
					} else {
						parentNode.appendChild(domNode);
					}
				}
				start = i + 1;
			}
		}
		initPropertiesAndChildren(domNode, vnode, projectionOptions);
	}
}

function initPropertiesAndChildren(domNode, vnode, projectionOptions) {
	addChildren(domNode, vnode.children, projectionOptions); // children before properties, needed for value property of <select>.
	if (vnode.text) {
		domNode.textContent = vnode.text;
	}
	setProperties(domNode, vnode.properties, projectionOptions);
	if (vnode.properties && vnode.properties.afterCreate) {
		vnode.properties.afterCreate.apply(vnode.properties.bind || vnode.properties, [domNode, projectionOptions, vnode.vnodeSelector, vnode.properties, vnode.children]);
	}
}

function addChildren(domNode, children, projectionOptions) {
	if (!children) {
		return;
	}
	for (let i = 0; i < children.length; i++) {
		createDom(children[i], domNode, undefined, projectionOptions);
	}
}

function setProperties(domNode, properties, projectionOptions) {
	if (!properties) {
		return;
	}
	let eventHandlerInterceptor = projectionOptions.eventHandlerInterceptor;
	let propNames = Object.keys(properties);
	let propCount = propNames.length;
	for (let i = 0; i < propCount; i++) {
		let propName = propNames[i];
		let propValue = properties[propName];
		if (propName === 'className') {
			throw new Error('Property "className" is not supported, use "class".');
		} else if (propName === 'class') {
			propValue.split(/\s+/).forEach(token => domNode.classList.add(token));
		} else if (propName === 'classes') {
			// object with string keys and boolean values
			let classNames = Object.keys(propValue);
			let classNameCount = classNames.length;
			for (let j = 0; j < classNameCount; j++) {
				let className = classNames[j];
				if (propValue[className]) {
					domNode.classList.add(className);
				}
			}
		} else if (propName === 'styles') {
			// object with string keys and string (!) values
			let styleNames = Object.keys(propValue);
			let styleCount = styleNames.length;
			for (let j = 0; j < styleCount; j++) {
				let styleName = styleNames[j];
				let styleValue = propValue[styleName];
				if (styleValue) {
					checkStyleValue(styleValue);
					projectionOptions.styleApplyer(domNode, styleName, styleValue);
				}
			}
		} else if (propName !== 'key' && propValue !== null && propValue !== undefined) {
			let type = typeof propValue;
			if (type === 'function') {
				if (propName.lastIndexOf('on', 0) === 0) {
					// lastIndexOf(,0)===0 -> startsWith
					if (eventHandlerInterceptor) {
						propValue = eventHandlerInterceptor(propName, propValue, domNode, properties); // intercept eventhandlers
					}
					if (propName === 'oninput') {
						(function () {
							// record the evt.target.value, because IE and Edge sometimes do a requestAnimationFrame between changing value and running oninput
							let oldPropValue = propValue;
							propValue = function (_this /*HTMLElement*/, evt /*Event*/) {
								evt.target['oninput-value'] = evt.target /*as HTMLInputElement*/.value; // may be HTMLTextAreaElement as well
								oldPropValue.apply(_this, [evt]);
							};
						})();
					}
					domNode[propName] = propValue;
				}
			} else if (type === 'string' && propName !== 'value' && propName !== 'innerHTML') {
				if (projectionOptions.namespace === NAMESPACE_SVG && propName === 'href') {
					domNode.setAttributeNS(NAMESPACE_XLINK, propName, propValue);
				} else {
					domNode.setAttribute(propName, propValue);
				}
			} else {
				domNode[propName] = propValue;
			}
		}
	}
}

function nodeToRemove(vNode, transitions) {
	let domNode = vNode.domNode;
	if (vNode.properties) {
		let exitAnimation = vNode.properties.exitAnimation;
		if (exitAnimation) {
			domNode.style.pointerEvents = 'none';
			let removeDomNode = function () {
				if (domNode.parentNode) {
					domNode.parentNode.removeChild(domNode);
				}
			};
			if (typeof exitAnimation === 'function') {
				exitAnimation(domNode, removeDomNode, vNode.properties);
				return;
			} else {
				transitions.exit(vNode.domNode, vNode.properties, exitAnimation, removeDomNode);
				return;
			}
		}
	}
	if (domNode.parentNode) {
		domNode.parentNode.removeChild(domNode);
	}
}<commit_msg>added id/class check<commit_after>"use strict";

Object.defineProperty(exports, "__esModule", {
	value: true
});
exports.render = render;

var _access = require("./access");

var _dom = require("./dom");

function same(node, vnode) {
	if (node === vnode) return true;
	if (node === undefined || vnode === undefined) return false;
	var inode = vnode.inode;
	if (node.nodeType !== vnode.type) return false;
	if (node["@@doc-depth"] !== inode._depth) return false;
	if (node.nodeValue !== null) {
		if (node.nodeValue !== vnode.value) return false;
	} else {
		if (vnode.value !== undefined) return false;
		if (node.nodeName !== (inode._name + '').toUpperCase()) return false;
		if (node.id && inode._attrs.get("id") !== node.id) return false;
		if (node.className && inode._attrs.get("class") !== node.className) return false;
	}
	return true;
}

function render(vnode, root) {
	// fixme stateless
	var parents = [{ domNode: root }];
	const attrFunc = (domNode, v, k) => (domNode.setAttribute(k, v), domNode);
	// ensure paths by calling iter
	var domNodes = (0, _dom.nodesList)(root);
	var i = 0;
	var skipDepth = 0,
	    append = false,
	    nextSame = false;
	var handleNode = function (node) {
		// TODO this won't work when pushed from server
		// we could diff an L3 buffer and update the tree (stateless)
		// perhaps it would be better to separate VNode and domNodes, but where to put the WeakMap?
		var type = node.type,
		    inode = node.inode,
		    domNode = node.domNode,
		    cur = domNodes[i],
		    next = domNodes[i + 1],
		    nn = (0, _access.nextNode)(node);
		var curSame = nextSame || same(cur, node);
		nextSame = same(next, nn);
		if (cur && curSame && nextSame) {
			// skip until next
			// console.log("same",cur,cur["@@doc-depth"],node.name,inode._depth);
			node.domNode = cur;
			skipDepth = cur["@@doc-depth"];
			if (type == 1) parents[inode._depth] = node;
		} else {
			if (cur) {
				if (cur["@@doc-depth"] == inode._depth - 1) {
					//console.log("append",cur);
					append = true;
				} else if (cur["@@doc-depth"] == inode._depth + 1) {
					// console.log("remove",cur);
					// don't remove text, it will be garbage collected
					if (cur.nodeType == 1) cur.parentNode.removeChild(cur);
					// remove from dom, retry this node
					// keep node untill everything is removed
					i++;
					return handleNode(node);
				} else {
					if (type == 1) {
						if (cur.nodeType != 17) cur.parentNode.removeChild(cur);
						// remove from dom, retry this node
						i++;
						return handleNode(node);
					} else if (type == 3) {
						// if we're updating a text node, we should be sure it's the same parent
						if (cur["@@doc-depth"] == skipDepth + 1) {
							cur.nodeValue = node.value;
						} else {
							append = true;
						}
					}
				}
			}
			if (!cur || append) {
				//console.log("empty",type, append)
				if (type == 1) {
					domNode = document.createElement(node.name);
					if (parents[inode._depth - 1]) parents[inode._depth - 1].domNode.appendChild(domNode);
					inode._attrs.reduce(attrFunc, domNode);
					parents[inode._depth] = node;
				} else if (type == 3) {
					domNode = document.createTextNode(node.value);
					parents[inode._depth - 1].domNode.appendChild(domNode);
				}
				node.domNode = domNode;
			}
		}
		if (!append) {
			i++;
		} else {
			append = false;
		}
	};
	(0, _access.iter)(vnode, handleNode);
	var l = domNodes.length;
	for (; --l >= i;) {
		var node = domNodes[l];
		if (node.nodeType == 1) node.parentNode.removeChild(node);
	}
}<|endoftext|>


<commit_before>angular.module('starter.controllers', [])

.controller('ListCtrl', function($scope, Detail) {

  $scope.category = "food";
  $scope.list = Detail.getList($scope.category);
  $scope.sref = "detail";

  // TODO: rename other to program

  $scope.searchTerm = "";
  $scope.clearSearch = function() {
    $scope.searchTerm = "";
  };

  $scope.updateCategoryFilter = function() {
    if($scope.category == 'stands') {
      $scope.sref = 'stand';
    } else {
      $scope.sref = 'detail';
    }
    $scope.list = Detail.getList($scope.category);
  };

})

.controller('ProgramCtrl', function($scope, Detail) {

  $scope.events = Detail.getEvents();
  $scope.stages = Detail.getStages();
  $scope.requestedDay = "6";
  $scope.requestedStage = "0";

  $scope.stageFilter = function(item,index) {
    if(parseInt($scope.requestedStage) === 0) {
      return true;
    };
    return parseInt($scope.requestedStage) === item.id;
  };
  $scope.dayFilter = function(item,index) {
    var date = new Date(item.start);
    return parseInt($scope.requestedDay) === date.getDay();
  };

})

.controller('BusCtrl', function($scope, Detail) {

  $scope.busstops   = Detail.getBusstops();
  $scope.directions = Detail.getDirections();
  $scope.requestedDay = "6";
  $scope.requestedDestination = "1";

  $scope.dayFilter = function(item,index) {
    var date = new Date(item.time);
    // let's also account for the two hour after midnight in this filter
    date.setHours(date.getHours()-2);
    return parseInt($scope.requestedDay) === date.getDay();
  };

})

.controller('DetailStandCtrl', ['$scope', '$stateParams', 'Detail', function($scope, $stateParams, Detail) {

  $scope.stand = Detail.getFilteredStands('stands',$stateParams.id);
  $scope.standName = $scope.stand[0].name;
  $scope.standURL = $scope.stand[0].url;
  $scope.standDescription = $scope.stand[0].description;
  $scope.standItemIDs = $scope.stand[0].items;
  $scope.itemArray = Detail.getList().filter(function(item) { return $scope.standItemIDs.indexOf(item.id) > -1; });

  $scope.mapDefaults = {
//    scrollWheelZoom: false,
//    doubleClickZoom: false,
    tileLayer: "http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
  };

  $scope.neckarsulm = {
    lat: 49.1920114764648,
    lng: 9.22389686107636,
    zoom: 17,
  };

  $scope.markersHash = Detail.getMarker($scope.stand);

}])

.controller('DetailCtrl', ['$scope', '$stateParams', 'Detail', function($scope, $stateParams, Detail) {

  $scope.filteredStands = Detail.getFilteredStands($stateParams.type,$stateParams.id);
  $scope.itemArray = Detail.getList($stateParams.type).filter(function(item) { return item.id == $stateParams.id; });
  $scope.itemName = $scope.itemArray[0].name;

  $scope.mapDefaults = {
//    scrollWheelZoom: false,
//    doubleClickZoom: false,
    tileLayer: "http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
  };

  $scope.neckarsulm = {
    lat: 49.1920114764648,
    lng: 9.22389686107636,
    zoom: 17,
  };

  $scope.markersHash = Detail.getMarker($scope.filteredStands);

}])

.controller('MapCtrl', function($scope, Detail) {

  $scope.stands = Detail.getList('stands');
  $scope.neckarsulm = {
    lat: 49.1920114764648,
    lng: 9.22389686107636,
    zoom: 17,
  };
  $scope.defaults = {
//    scrollWheelZoom: false,
//    doubleClickZoom: false,
    tileLayer: "http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
  };
  $scope.markers = Detail.getMarker();

});
<commit_msg>Get Zoom buttons to operate.<commit_after>angular.module('starter.controllers', [])

.controller('ListCtrl', function($scope, Detail) {

  $scope.category = "food";
  $scope.list = Detail.getList($scope.category);
  $scope.sref = "detail";

  // TODO: rename other to program

  $scope.searchTerm = "";
  $scope.clearSearch = function() {
    $scope.searchTerm = "";
  };

  $scope.updateCategoryFilter = function() {
    if($scope.category == 'stands') {
      $scope.sref = 'stand';
    } else {
      $scope.sref = 'detail';
    }
    $scope.list = Detail.getList($scope.category);
  };

})

.controller('ProgramCtrl', function($scope, Detail) {

  $scope.events = Detail.getEvents();
  $scope.stages = Detail.getStages();
  $scope.requestedDay = "6";
  $scope.requestedStage = "0";

  $scope.stageFilter = function(item,index) {
    if(parseInt($scope.requestedStage) === 0) {
      return true;
    };
    return parseInt($scope.requestedStage) === item.id;
  };
  $scope.dayFilter = function(item,index) {
    var date = new Date(item.start);
    return parseInt($scope.requestedDay) === date.getDay();
  };

})

.controller('BusCtrl', function($scope, Detail) {

  $scope.busstops   = Detail.getBusstops();
  $scope.directions = Detail.getDirections();
  $scope.requestedDay = "6";
  $scope.requestedDestination = "1";

  $scope.dayFilter = function(item,index) {
    var date = new Date(item.time);
    // let's also account for the two hour after midnight in this filter
    date.setHours(date.getHours()-2);
    return parseInt($scope.requestedDay) === date.getDay();
  };

})

.controller('DetailStandCtrl', ['$scope', '$stateParams', 'Detail', function($scope, $stateParams, Detail) {

  $scope.stand = Detail.getFilteredStands('stands',$stateParams.id);
  $scope.standName = $scope.stand[0].name;
  $scope.standURL = $scope.stand[0].url;
  $scope.standDescription = $scope.stand[0].description;
  $scope.standItemIDs = $scope.stand[0].items;
  $scope.itemArray = Detail.getList().filter(function(item) { return $scope.standItemIDs.indexOf(item.id) > -1; });

  $scope.mapDefaults = {
//    scrollWheelZoom: false,
//    doubleClickZoom: false,
    tileLayer: "http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
    minZoom: 15,
  };

  $scope.neckarsulm = {
    lat: 49.1920114764648,
    lng: 9.22389686107636,
    zoom: 17,
    zoomControl: true,
  };

  $scope.markersHash = Detail.getMarker($scope.stand);

}])

.controller('DetailCtrl', ['$scope', '$stateParams', 'Detail', function($scope, $stateParams, Detail) {

  $scope.filteredStands = Detail.getFilteredStands($stateParams.type,$stateParams.id);
  $scope.itemArray = Detail.getList($stateParams.type).filter(function(item) { return item.id == $stateParams.id; });
  $scope.itemName = $scope.itemArray[0].name;

  $scope.mapDefaults = {
//    scrollWheelZoom: false,
//    doubleClickZoom: false,
    tileLayer: "http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
    minZoom: 15,
  };

  $scope.neckarsulm = {
    lat: 49.1920114764648,
    lng: 9.22389686107636,
    zoom: 17,
    zoomControl: true,
  };

  $scope.markersHash = Detail.getMarker($scope.filteredStands);

}])

.controller('MapCtrl', function($scope, Detail) {

  $scope.stands = Detail.getList('stands');
  $scope.neckarsulm = {
    lat: 49.1920114764648,
    lng: 9.22389686107636,
    zoom: 17,
    zoomControl: true,
  };
  $scope.defaults = {
//    scrollWheelZoom: false,
//    doubleClickZoom: false,
    tileLayer: "http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
    minZoom: 15,
  };
  $scope.markers = Detail.getMarker();

});
<|endoftext|>


<commit_before>/*
* @Author: gbk
* @Date:   2017-01-19 20:20:58
* @Last Modified by:   gbk
* @Last Modified time: 2017-01-23 15:04:13
*/

'use strict';

var fs = require('fs');
var path = require('path');

var parseUrl = require('parseurl');
var resolveCwd = require('resolve-cwd');

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

module.exports = function(root, injects) {
  root = path.resolve(root);
  injects = (injects || []).map(function(inject) {
    var res = (resolveCwd(inject) || inject).replace(process.cwd(), '');
    if (/\.css$/.test(res)) {
      return '<link rel="stylesheet" href="' + res + '" />';
    }
    return '<script src="' + res + '"></script>';
  }).join('');

  if (util.isDirectory(root) && injects.length) {

    // root must be a directory
    return function(req, res, next) {
      var pathname = parseUrl(req).pathname;
      if (pathname.substr(-1) === '/') {
        pathname += 'index.html';
      }
      if (/\.html?$/.test(pathname)) {
        var file = path.join(root, pathname);
        try {
          var content = fs.readFileSync(file).toString();
          if (/<\/title>/i.test(content)) {
            content = content.replace(/<\/title>/i, '</title>' + injects);
          } else if (/<link([^>]*)>/i.test(content)) {
            content = content.replace(/<link([^>]*)>/i, injects + '<link$1>');
          } else if (/<body([^>]*)>/i.test(content)) {
            content = content.replace(/<body([^>]*)>/i, '<body$1>' + injects);
          } else {
            content = injects + content;
          }
          res.end(content);
        } catch (e) {
          next();
        }
      } else {
        next();
      }
    };

  } else {

    // is not, do nothing
    return function(req, res, next) {
      next();
    };

  }
};
<commit_msg>feat:(injects) use customized comments to locate where injection should lands<commit_after>/*
* @Author: gbk
* @Date:   2017-01-19 20:20:58
* @Last Modified by:   Tommy Troy Lin
* @Last Modified time: 2017-02-08 18:06:34
*/

'use strict';

var fs = require('fs');
var path = require('path');

var parseUrl = require('parseurl');
var resolveCwd = require('resolve-cwd');

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

module.exports = function (root, injects) {
  root = path.resolve(root);
  injects = (injects || []).map(function (inject) {
    var res = (resolveCwd(inject) || inject).replace(process.cwd(), '');
    if (/\.css$/.test(res)) {
      return '<link rel="stylesheet" href="' + res + '" />';
    }
    return '<script src="' + res + '"></script>';
  }).join('');

  if (util.isDirectory(root) && injects.length) {

    // root must be a directory
    return function (req, res, next) {
      var pathname = parseUrl(req).pathname;
      if (pathname.substr(-1) === '/') {
        pathname += 'index.html';
      }
      if (/\.html?$/.test(pathname)) {
        var file = path.join(root, pathname);
        try {
          var content = fs.readFileSync(file).toString();
          var injectionComment = /<!--\s*Nowa\s*Server\s*Injects\s*-->/i;
          if (injectionComment.test(content)) {
            content = content.replace(injectionComment, injects);
          }
          res.end(content);
        } catch (e) {
          next();
        }
      } else {
        next();
      }
    };

  } else {

    // is not, do nothing
    return function (req, res, next) {
      next();
    };

  }
};
<|endoftext|>


<commit_before>
var __ff_ui;
$(function() {
  // Load the Facebook SDK, then initialize FirefeedUI;
  window.fbAsyncInit = function() {
    FB.init({
      appId      : "104907529680402",
      channelUrl : "channel.html"
    });
    __ff_ui = new FirefeedUI();
  };
  (function(d, debug){
    var js, id = "facebook-jssdk", ref = d.getElementsByTagName("script")[0];
    if (d.getElementById(id)) {return;}
    js = d.createElement("script"); js.id = id; js.async = true;
    js.src = "//connect.facebook.net/en_US/all" + (debug ? "/debug" : "") + ".js";
    ref.parentNode.insertBefore(js, ref);
  }(document, /*debug*/ false));
});

function FirefeedUI() {
  this._limit = 120;
  this._loggedIn = false;
  this._spinner = new Spinner();
  this._firefeed = new Firefeed("http://firefeed.firebaseio-staging.com/");

  // Figure out if the user is logged in or not, with silent login.
  var self = this;
  self.login(function(info) {
    self._loggedIn = info;
    self._pageController(window.location.href);
  });
}

FirefeedUI.prototype._pageController = function(url) {
  // Extract sub page from URL, if any.
  var idx = url.indexOf("?");
  var hash = (idx > 0) ? url.slice(idx + 1) : "";
  var value = hash.split("=");

  console.log("Handling " + url + " -> " + value[0] + " = " + value[1]);
  switch (value[0]) {
    case "profile":
      if (!value[1]) {
        this.render404({}, "", "/?404");
      } else {
        this.renderProfile(value[1]);
      }
      break;
    case "status":
      if (!value[1]) {
        this.render404({}, "", "/?404");
      } else {
        this.renderStatus(value[1]);
      }
      break;
    case "timeline":
    default:
      if (this._loggedIn) {
        this.renderTimeline(this._loggedIn);
      } else {
        this.renderHome();
      }
      break;
  }
};

FirefeedUI.prototype._postHandler = function(e) {
  var sparkText = $("#spark-input");
  var sparkButton = $("#spark-button");
  var containerEl = $("#spark-button-div");
  var message = $("<div>", { class: "msg" }).html("Posting...");

  var self = this;
  e.preventDefault();
  sparkButton.replaceWith(message);
  self._spinner.spin(containerEl.get(0));
  self._firefeed.post(sparkText.val(), function(err, done) {
    if (!err) {
      message.html("Posted!").css("background", "#008000");
      sparkText.val("");
    } else {
      message.html("Posting failed!").css("background", "#FF6347");
    }
    self._spinner.stop();
    message.css("visibility", "visible");
    message.fadeOut(1500, function() {
      message.replaceWith(sparkButton);
      sparkButton.click(self._postHandler.bind(self));
    });
  });
};

FirefeedUI.prototype._handleNewSpark = function(limit, func) {
  func(
    limit,
    function(sparkId, spark) {
      spark.sparkId = sparkId;
      var sparkEl = $(Mustache.to_html($("#tmpl-spark").html(), spark)).hide();
      $("#spark-list").prepend(sparkEl);
      sparkEl.slideDown("slow");
    }, function(sparkId) {
      $("#spark-" + sparkId).slideToggle("slow", function() {
        $(this).remove();
      });
    }
  );
};

FirefeedUI.prototype._editableHandler = function(id, value) {
  if (id == "inputLocation") {
    this._firefeed.setProfileField("location", value);
  }
  if (id == "inputBio") {
    this._firefeed.setProfileField("bio", value);
  }
  return true;
}

FirefeedUI.prototype.login = function(cb) {
  // Try silent login in case the user is already logged in.
  var self = this;
  self._firefeed.login(true, function(err, info) {
    if (!err && info) {
      cb(info);
    } else {
      cb(false);
    }
  });
};

FirefeedUI.prototype.logout = function(e) {
  if (e) {
    e.preventDefault();
  }
  this._firefeed.logout();
  this._loggedIn = false;
};

FirefeedUI.prototype.render404 == function() {

};

FirefeedUI.prototype.renderHome = function(e) {
  if (e) {
    e.preventDefault();
  }
  $("#header").html($("#tmpl-index-header").html());

  // Preload animation.
  var path = "img/curl-animate.gif";
  var img = new Image();
  img.src = path;

  // Steup curl on hover.
  $(".ribbon-curl").find("img").hover(function() {
    $(this).attr("src", path);
  }, function() {
    $(this).attr("src", "img/curl-static.gif");
  });

  var body = Mustache.to_html($("#tmpl-content").html(), {
    classes: "cf home", content: $("#tmpl-index-content").html()
  });
  $("#body").html(body);

  var self = this;
  var loginButton = $("#login-button");
  loginButton.click(function(e) {
    e.preventDefault();
    loginButton.css("visibility", "hidden");
    self._spinner.spin($("#login-div").get(0));
    self._firefeed.login(false, function(err, info) {
      if (err) {
        self._spinner.stop();
        loginButton.css("visibility", "visible");
        console.log(err);
      } else {
        self.renderTimeline(info);
      }
    });
  });

  $("#about-link").remove();
};

FirefeedUI.prototype.renderTimeline = function(info) {
  $("#header").html($("#tmpl-page-header").html());
  $("#top-logo").click(this.renderHome.bind(this));
  $("#logout-button").click(this.logout.bind(this));

  // Render placeholders for location / bio if not filled in.
  info.location = info.location || "Your Location...";
  info.bio = info.bio || "Your Bio...";

  // Render body.
  var content = Mustache.to_html($("#tmpl-timeline-content").html(), info);
  var body = Mustache.to_html($("#tmpl-content").html(), {
    classes: "cf", content: content
  });
  $("#body").html(body);

  // Attach textarea handlers.
  var self = this;
  var charCount = $("#c-count");
  var sparkText = $("#spark-input");
  $("#spark-button").css("visibility", "hidden");
  function _textAreaHandler() {
    var text = sparkText.val();
    charCount.text("" + (self._limit - text.length));
    if (text.length > self._limit) {
      charCount.css("color", "#FF6347");
      $("#spark-button").css("visibility", "hidden");
    } else if (text.length == 0) {
      $("#spark-button").css("visibility", "hidden");
    } else {
      charCount.css("color", "#999");
      $("#spark-button").css("visibility", "visible");
    }
  };
  charCount.text(self._limit);
  sparkText.keyup(_textAreaHandler);
  sparkText.blur(_textAreaHandler);

  // Attach post spark button.
  $("#spark-button").click(self._postHandler.bind(self));

  // Attach new spark event handler, capped to 10 for now.
  self._handleNewSpark(
    10, self._firefeed.onNewSpark.bind(self._firefeed)
  );

  // Attach suggested user event.
  self._firefeed.onNewSuggestedUser(function(userid, info) {
    info.id = userid;
    $(Mustache.to_html($("#tmpl-suggested-user").html(), info)).
      appendTo("#suggested-users");
    var button = $("#followBtn-" + userid);
    // Fade out the suggested user if they were followed successfully.
    button.click(function(e) {
      e.preventDefault();
      button.remove();
      self._firefeed.follow(userid, function(err, done) {
        // TODO FIXME: Check for errors!
        $("#followBox-" + userid).fadeOut(1500);
      });
    });
  });

  // Make profile fields editable.
  $(".editable").editable(function(value, settings) {
    self._editableHandler($(this).attr("id"), value);
    return value;
  });
};

FirefeedUI.prototype.renderProfile = function(uid) {
  var self = this;
  $("#header").html($("#tmpl-page-header").html());
  $("#top-logo").click(this.renderHome.bind(this));
  if (self._loggedIn) {
    $("#logout-button").click(self.logout.bind(self));
  } else {
    $("#logout-button").remove();
  }

  // Render profile page body.
  self._firefeed.getUserInfo(uid, function(info) {
    var content = Mustache.to_html($("#tmpl-profile-content").html(), info);
    var body = Mustache.to_html($("#tmpl-content").html(), {
      classes: "cf", content: content
    });
    $("#body").html(body);
  });

  // Render this user's tweets. Capped to 5 for now.
  self._handleNewSpark(
    5, self._firefeed.onNewSparkFor.bind(self._firefeed, uid)
  );
};

FirefeedUI.prototype.renderStatus = function(id) {
  $("#header").html($("#tmpl-page-header").html());
  $("#top-logo").click(this.renderHome.bind(this));
  $("#logout-button").click(this.logout.bind(this));

  // Render profile page body.
  var self = this;
  self._firefeed.getSpark(id, function(info) {
    if (info !== null && info.author) {
      self._firefeed.getUserInfo(info.author, function(authorInfo) {
        for (var key in authorInfo) info[key] = authorInfo[key];
        var content = Mustache.to_html($("#tmpl-spark-content").html(), info);
        var body = Mustache.to_html($("#tmpl-content").html(), {
          classes: "cf", content: content
        });
        $("#body").html(body);
      });
    }
  });
};
<commit_msg>Redirect index page to timeline if logged in<commit_after>
var __ff_ui;
$(function() {
  // Load the Facebook SDK, then initialize FirefeedUI;
  window.fbAsyncInit = function() {
    FB.init({
      appId      : "104907529680402",
      channelUrl : "channel.html"
    });
    __ff_ui = new FirefeedUI();
  };
  (function(d, debug){
    var js, id = "facebook-jssdk", ref = d.getElementsByTagName("script")[0];
    if (d.getElementById(id)) {return;}
    js = d.createElement("script"); js.id = id; js.async = true;
    js.src = "//connect.facebook.net/en_US/all" + (debug ? "/debug" : "") + ".js";
    ref.parentNode.insertBefore(js, ref);
  }(document, /*debug*/ false));
});

function FirefeedUI() {
  this._limit = 120;
  this._loggedIn = false;
  this._spinner = new Spinner();
  this._firefeed = new Firefeed("http://firefeed.firebaseio-staging.com/");

  // Figure out if the user is logged in or not, with silent login.
  var self = this;
  self.login(function(info) {
    self._loggedIn = info;
    self._pageController(window.location.href);
  });
}

FirefeedUI.prototype._pageController = function(url) {
  // Extract sub page from URL, if any.
  var idx = url.indexOf("?");
  var hash = (idx > 0) ? url.slice(idx + 1) : "";
  var value = hash.split("=");

  console.log("Handling " + url + " -> " + value[0] + " = " + value[1]);
  switch (value[0]) {
    case "profile":
      if (!value[1]) {
        this.render404({}, "", "/?404");
      } else {
        this.renderProfile(value[1]);
      }
      break;
    case "status":
      if (!value[1]) {
        this.render404({}, "", "/?404");
      } else {
        this.renderStatus(value[1]);
      }
      break;
    case "timeline":
    default:
      if (this._loggedIn) {
        this.renderTimeline(this._loggedIn);
      } else {
        this.renderHome();
      }
      break;
  }
};

FirefeedUI.prototype._postHandler = function(e) {
  var sparkText = $("#spark-input");
  var sparkButton = $("#spark-button");
  var containerEl = $("#spark-button-div");
  var message = $("<div>", { class: "msg" }).html("Posting...");

  var self = this;
  e.preventDefault();
  sparkButton.replaceWith(message);
  self._spinner.spin(containerEl.get(0));
  self._firefeed.post(sparkText.val(), function(err, done) {
    if (!err) {
      message.html("Posted!").css("background", "#008000");
      sparkText.val("");
    } else {
      message.html("Posting failed!").css("background", "#FF6347");
    }
    self._spinner.stop();
    message.css("visibility", "visible");
    message.fadeOut(1500, function() {
      message.replaceWith(sparkButton);
      sparkButton.click(self._postHandler.bind(self));
    });
  });
};

FirefeedUI.prototype._handleNewSpark = function(limit, func) {
  func(
    limit,
    function(sparkId, spark) {
      spark.sparkId = sparkId;
      var sparkEl = $(Mustache.to_html($("#tmpl-spark").html(), spark)).hide();
      $("#spark-list").prepend(sparkEl);
      sparkEl.slideDown("slow");
    }, function(sparkId) {
      $("#spark-" + sparkId).slideToggle("slow", function() {
        $(this).remove();
      });
    }
  );
};

FirefeedUI.prototype._editableHandler = function(id, value) {
  if (id == "inputLocation") {
    this._firefeed.setProfileField("location", value);
  }
  if (id == "inputBio") {
    this._firefeed.setProfileField("bio", value);
  }
  return true;
}

FirefeedUI.prototype.login = function(cb) {
  // Try silent login in case the user is already logged in.
  var self = this;
  self._firefeed.login(true, function(err, info) {
    if (!err && info) {
      cb(info);
    } else {
      cb(false);
    }
  });
};

FirefeedUI.prototype.logout = function(e) {
  if (e) {
    e.preventDefault();
  }
  this._firefeed.logout();
  this._loggedIn = false;
};

FirefeedUI.prototype.render404 == function() {

};

FirefeedUI.prototype.renderHome = function(e) {
  if (e) {
    e.preventDefault();
  }
  if (this._loggedIn) {
    this.renderTimeline(this._loggedIn);
    return;
  }

  $("#header").html($("#tmpl-index-header").html());

  // Preload animation.
  var path = "img/curl-animate.gif";
  var img = new Image();
  img.src = path;

  // Steup curl on hover.
  $(".ribbon-curl").find("img").hover(function() {
    $(this).attr("src", path);
  }, function() {
    $(this).attr("src", "img/curl-static.gif");
  });

  var body = Mustache.to_html($("#tmpl-content").html(), {
    classes: "cf home", content: $("#tmpl-index-content").html()
  });
  $("#body").html(body);

  var self = this;
  var loginButton = $("#login-button");
  loginButton.click(function(e) {
    e.preventDefault();
    loginButton.css("visibility", "hidden");
    self._spinner.spin($("#login-div").get(0));
    self._firefeed.login(false, function(err, info) {
      if (err) {
        self._spinner.stop();
        loginButton.css("visibility", "visible");
        console.log(err);
      } else {
        self.renderTimeline(info);
      }
    });
  });

  $("#about-link").remove();
};

FirefeedUI.prototype.renderTimeline = function(info) {
  $("#header").html($("#tmpl-page-header").html());
  $("#top-logo").click(this.renderHome.bind(this));
  $("#logout-button").click(this.logout.bind(this));

  // Render placeholders for location / bio if not filled in.
  info.location = info.location || "Your Location...";
  info.bio = info.bio || "Your Bio...";

  // Render body.
  var content = Mustache.to_html($("#tmpl-timeline-content").html(), info);
  var body = Mustache.to_html($("#tmpl-content").html(), {
    classes: "cf", content: content
  });
  $("#body").html(body);

  // Attach textarea handlers.
  var self = this;
  var charCount = $("#c-count");
  var sparkText = $("#spark-input");
  $("#spark-button").css("visibility", "hidden");
  function _textAreaHandler() {
    var text = sparkText.val();
    charCount.text("" + (self._limit - text.length));
    if (text.length > self._limit) {
      charCount.css("color", "#FF6347");
      $("#spark-button").css("visibility", "hidden");
    } else if (text.length == 0) {
      $("#spark-button").css("visibility", "hidden");
    } else {
      charCount.css("color", "#999");
      $("#spark-button").css("visibility", "visible");
    }
  };
  charCount.text(self._limit);
  sparkText.keyup(_textAreaHandler);
  sparkText.blur(_textAreaHandler);

  // Attach post spark button.
  $("#spark-button").click(self._postHandler.bind(self));

  // Attach new spark event handler, capped to 10 for now.
  self._handleNewSpark(
    10, self._firefeed.onNewSpark.bind(self._firefeed)
  );

  // Attach suggested user event.
  self._firefeed.onNewSuggestedUser(function(userid, info) {
    info.id = userid;
    $(Mustache.to_html($("#tmpl-suggested-user").html(), info)).
      appendTo("#suggested-users");
    var button = $("#followBtn-" + userid);
    // Fade out the suggested user if they were followed successfully.
    button.click(function(e) {
      e.preventDefault();
      button.remove();
      self._firefeed.follow(userid, function(err, done) {
        // TODO FIXME: Check for errors!
        $("#followBox-" + userid).fadeOut(1500);
      });
    });
  });

  // Make profile fields editable.
  $(".editable").editable(function(value, settings) {
    self._editableHandler($(this).attr("id"), value);
    return value;
  });
};

FirefeedUI.prototype.renderProfile = function(uid) {
  var self = this;
  $("#header").html($("#tmpl-page-header").html());
  $("#top-logo").click(this.renderHome.bind(this));
  if (self._loggedIn) {
    $("#logout-button").click(self.logout.bind(self));
  } else {
    $("#logout-button").remove();
  }

  // Render profile page body.
  self._firefeed.getUserInfo(uid, function(info) {
    var content = Mustache.to_html($("#tmpl-profile-content").html(), info);
    var body = Mustache.to_html($("#tmpl-content").html(), {
      classes: "cf", content: content
    });
    $("#body").html(body);
  });

  // Render this user's tweets. Capped to 5 for now.
  self._handleNewSpark(
    5, self._firefeed.onNewSparkFor.bind(self._firefeed, uid)
  );
};

FirefeedUI.prototype.renderStatus = function(id) {
  $("#header").html($("#tmpl-page-header").html());
  $("#top-logo").click(this.renderHome.bind(this));
  $("#logout-button").click(this.logout.bind(this));

  // Render profile page body.
  var self = this;
  self._firefeed.getSpark(id, function(info) {
    if (info !== null && info.author) {
      self._firefeed.getUserInfo(info.author, function(authorInfo) {
        for (var key in authorInfo) info[key] = authorInfo[key];
        var content = Mustache.to_html($("#tmpl-spark-content").html(), info);
        var body = Mustache.to_html($("#tmpl-content").html(), {
          classes: "cf", content: content
        });
        $("#body").html(body);
      });
    }
  });
};
<|endoftext|>


<commit_before>var EventEmitter = require('events').EventEmitter;
var util = require('util');

var Ribbon = module.exports = function(opts) {
  EventEmitter.call(this);

  this.opts = {
    // Invoke a restart when dropped
    autoRestart: false,
    backoffCoefficient: 1.05,
    restartDelay: 1000,
    maxRestartAttempts: -1,

    actionTimeout: 5000
  };

  // Shallow copy
  if(typeof opts == 'object'){
    for(var i in opts){
      this.opts[i] = opts[i];
    }
  }

  this._client = null;

  // State
  this.state = null;
  this.previousState = null;

  this.stats = {
    startUp: 0,
    shutDown: 0,
    restart: 0,
    dropped: 0,
    terminate: 0
  };

  this.clientFns = {
    restart: function(ribbon, client, cb){
      ribbon.once('down', function(){
        ribbon.startUp(cb);
      });
      ribbon.shutDown();
    }
  };

  this._clientFnStatus = {};

  this.restartDelay = this.opts.restartDelay;
  this.restartAttempts = 0;

  this.instanceID = Math.floor(Math.random()*9999).toString(16);
};

util.inherits(Ribbon, EventEmitter);

/**
 * State modifiers
 */
Ribbon.prototype.changeState = function(state){
  this.previousState = this.state;
  this.state = state;
};

Ribbon.prototype.declareUp = function(){
  this.changeState(true);
  this.emit('up');
  if(this.wasDown()) this.emit('revived');
};

Ribbon.prototype.declareDown = function(){
  this.changeState(false);
  this.emit('down');
};

Ribbon.prototype.declareDropped = function(){
  this.changeState(false);
  this.emit('dropped');
};

/**
 * State querying
 */
Ribbon.prototype.isUp = function(){
  return !!this.state;
};

Ribbon.prototype.isDown = function(){
  return !this.state;
};

Ribbon.prototype.wasUp = function(){
  return !!this.previousState;
};

Ribbon.prototype.wasDown = function(){
  return !this.previousState;
};

Ribbon.prototype.isShuttingDown = function(){
  return this.clientFnStatus('shutDown');
};

Ribbon.prototype.isStartingUp = function(){
  return this.clientFnStatus('startUp');
};

/**
 * Client integration
 */

Ribbon.prototype.registerHandler = function(name, fn){
  this.clientFns[name] = fn;
}

Ribbon.prototype.setStartUp = function(fn){
  this.registerHandler('startUp', fn);
};

Ribbon.prototype.setShutDown = function(fn){
  this.registerHandler('shutDown', fn);
};

Ribbon.prototype.setRestart = function(fn){
  this.registerHandler('restart', fn);
};

Ribbon.prototype.setTerminate = function(fn){
  this.registerHandler('terminate', fn);
};

/**
 * Client mutators
 */

Ribbon.prototype.clientFnStatus = function(name, status){
  if (typeof status !== 'undefined') this._clientFnStatus[name] = status;

  return this._clientFnStatus[name];
};

Ribbon.prototype.clientFn = function(name, done){
  var fn;

  if (typeof (fn = this.clientFns[name]) !== 'function') return;

  this.clientFnStatus(name, true);
  this.stats[name] += 1;

  return fn(this, this.client(), done);
};

Ribbon.prototype.startUp = function(cb){
  var self = this;
  var name = 'startUp';
  cb = cb || function(){};

  var done = function(err, client){
    self.clientFnStatus(name, false);
    self.client(client);

    cb(err, client || self.client());

    if (err){
      self.declareDropped();
    } else {
      self.declareUp();
    }
  };

  self.clientFn(name, done);
};

Ribbon.prototype.shutDown = function(cb){
  var self = this;
  var name = 'shutDown';
  cb = cb || function(){};

  var done = function(err, client){
    self.clientFnStatus(name, false);

    cb(err, client || self.client());

    self.declareDown();
  };

  self.clientFn(name, done);
};

Ribbon.prototype.restart = function(cb){
  var self = this;
  var name = 'restart';
  cb = cb || function(){};

  var done = function(err, client){
    self.clientFnStatus(name, false);
    self.client(client);

    cb(err, client || self.client());

    if (err){
      self.declareDropped();
    } else {
      self.declareUp();
    }
  };

  self.clientFn(name, done);

};

Ribbon.prototype.terminate = function(cb){
  var self = this;
  var name = 'terminate';
  cb = cb || function(){};

  var done = function(err, client){
    self.clientFnStatus(name, false);

    cb(err, client || self.client());

    self.declareDown();
  };

  self.clientFn(name, done);
};

Ribbon.prototype.client = function(client){
  if (typeof client === 'object') this._client = client;

  return this._client;
};

<commit_msg>Update state before invoking cb + listeners<commit_after>var EventEmitter = require('events').EventEmitter;
var util = require('util');

var Ribbon = module.exports = function(opts) {
  EventEmitter.call(this);

  this.opts = {
    // Invoke a restart when dropped
    autoRestart: false,
    backoffCoefficient: 1.05,
    restartDelay: 1000,
    maxRestartAttempts: -1,

    actionTimeout: 5000
  };

  // Shallow copy
  if(typeof opts == 'object'){
    for(var i in opts){
      this.opts[i] = opts[i];
    }
  }

  this._client = null;

  // State
  this.state = null;
  this.previousState = null;

  this.stats = {
    startUp: 0,
    shutDown: 0,
    restart: 0,
    dropped: 0,
    terminate: 0
  };

  this.clientFns = {
    restart: function(ribbon, client, cb){
      ribbon.once('down', function(){
        ribbon.startUp(cb);
      });
      ribbon.shutDown();
    }
  };

  this._clientFnStatus = {};

  this.restartDelay = this.opts.restartDelay;
  this.restartAttempts = 0;

  this.instanceID = Math.floor(Math.random()*9999).toString(16);
};

util.inherits(Ribbon, EventEmitter);

/**
 * State modifiers
 */
Ribbon.prototype.changeState = function(state){
  this.previousState = this.state;
  this.state = state;
};

Ribbon.prototype.declareUp = function(){
  this.emit('up');
  if(this.wasDown()) this.emit('revived');
};

Ribbon.prototype.declareDown = function(){
  this.emit('down');
};

Ribbon.prototype.declareDropped = function(){
  this.emit('dropped');
};

/**
 * State querying
 */
Ribbon.prototype.isUp = function(){
  return !!this.state;
};

Ribbon.prototype.isDown = function(){
  return !this.state;
};

Ribbon.prototype.wasUp = function(){
  return !!this.previousState;
};

Ribbon.prototype.wasDown = function(){
  return !this.previousState;
};

Ribbon.prototype.isShuttingDown = function(){
  return this.clientFnStatus('shutDown');
};

Ribbon.prototype.isStartingUp = function(){
  return this.clientFnStatus('startUp');
};

/**
 * Client integration
 */

Ribbon.prototype.registerHandler = function(name, fn){
  this.clientFns[name] = fn;
}

Ribbon.prototype.setStartUp = function(fn){
  this.registerHandler('startUp', fn);
};

Ribbon.prototype.setShutDown = function(fn){
  this.registerHandler('shutDown', fn);
};

Ribbon.prototype.setRestart = function(fn){
  this.registerHandler('restart', fn);
};

Ribbon.prototype.setTerminate = function(fn){
  this.registerHandler('terminate', fn);
};

/**
 * Client mutators
 */

Ribbon.prototype.clientFnStatus = function(name, status){
  if (typeof status !== 'undefined') this._clientFnStatus[name] = status;

  return this._clientFnStatus[name];
};

Ribbon.prototype.clientFn = function(name, done){
  var fn;

  if (typeof (fn = this.clientFns[name]) !== 'function') return;

  this.clientFnStatus(name, true);
  this.stats[name] += 1;

  return fn(this, this.client(), done);
};

Ribbon.prototype.startUp = function(cb){
  var self = this;
  var name = 'startUp';
  cb = cb || function(){};

  var done = function(err, client){
    self.clientFnStatus(name, false);
    this.changeState(!!err);
    self.client(client);

    cb(err, client || self.client());

    if (err){
      self.declareDropped();
    } else {
      self.declareUp();
    }
  };

  self.clientFn(name, done);
};

Ribbon.prototype.shutDown = function(cb){
  var self = this;
  var name = 'shutDown';
  cb = cb || function(){};

  var done = function(err, client){
    self.clientFnStatus(name, false);
    this.changeState(false);

    cb(err, client || self.client());

    self.declareDown();
  };

  self.clientFn(name, done);
};

Ribbon.prototype.restart = function(cb){
  var self = this;
  var name = 'restart';
  cb = cb || function(){};

  var done = function(err, client){
    self.clientFnStatus(name, false);
    this.changeState(!!err);
    self.client(client);

    cb(err, client || self.client());

    if (err){
      self.declareDropped();
    } else {
      self.declareUp();
    }
  };

  self.clientFn(name, done);

};

Ribbon.prototype.terminate = function(cb){
  var self = this;
  var name = 'terminate';
  cb = cb || function(){};

  var done = function(err, client){
    self.clientFnStatus(name, false);
    this.changeState(false);

    cb(err, client || self.client());

    self.declareDown();
  };

  self.clientFn(name, done);
};

Ribbon.prototype.client = function(client){
  if (typeof client === 'object') this._client = client;

  return this._client;
};

<|endoftext|>


<commit_before>var builder = require('ltx'),
    Base    = require('./base')

var Roster = function() {}

Roster.prototype = new Base()

Roster.prototype.NS = "jabber:iq:roster"

Roster.prototype._events = {
    'xmpp.roster.add': 'add',
    'xmpp.roster.get': 'get',
    'xmpp.roster.group': 'group',
    'xmpp.roster.remove': 'remove'
}

Roster.prototype.handles = function(stanza) {
    return !!(stanza.is('iq') &&
        (stanza.getChild('query', this.NS)))
}

Roster.prototype.handle = function(stanza) {
    if ('set' == stanza.attr('type'))
        return this.handleRemoteAdd(stanza)
    return false
}

Roster.prototype.get = function(data, callback) {
    if (typeof callback !== 'function')
        return this._clientError('Missing callback')
    var self   = this
    var stanza = new builder.Element(
        'iq',
        { from: this.manager.jid.split('/')[0], type: 'get', id: this._getId() }
    ).c('query', {xmlns: this.NS}).up()

    this.manager.trackId(stanza.root().attr('id'), function(stanza) {
        self.handleRoster(stanza, callback)
    })
    this.client.send(stanza)
}

Roster.prototype.add = function(data, callback) {
    if (typeof callback !== 'function')
        return this._clientError('Missing callback', data)
    if (!data.jid)
        return this._clientError("Missing 'jid' key", data, callback)
    var self = this
    var item = {jid: data.jid}
    if (data.name) item.name = data.name

    var stanza = new builder.Element(
        'iq',
        {type: 'set', id: this._getId() }
    ).c('query', { xmlns: this.NS }).c('item', item)
    if (data.groups)
        data.groups.forEach(function(group) {
            stanza.c('group').t(group).up()
        })
    this.manager.trackId(stanza.root().attr('id'), function(stanza) {
        if (stanza.attrs.type == 'error') return callback(self._parseError(stanza), null)
        callback(null, true)
    })
    this.client.send(stanza)
}

Roster.prototype.remove = function(data, callback) {
    if (typeof callback !== 'function')
        return this._clientError('Missing callback', data)
    if (!data.jid)
        return this._clientError("Missing 'jid' key", data, callback)
    var self = this
    var item = { jid: data.jid, subscription: 'remove' }

    var stanza = new builder.Element(
        'iq',
        {type: 'set', id: this._getId() }
    ).c('query', { xmlns: this.NS }).c('item', item)

    this.manager.trackId(stanza.root().attr('id'), function(stanza) {
        if (stanza.attrs.type == 'error') return callback(self._parseError(stanza), null)
        callback(null, true)
    })
    this.client.send(stanza)
}
Roster.prototype.group = function(data, callback) {
    var self = this
    if (!data.jid)
        return this._clientError("Missing 'jid' key", data, callback)
    if (!data.groups)
        return this._clientError("Missing 'groups' key", data, callback)
    if (typeof callback !== 'function')
        return this._clientError('Missing callback', data)
    if (false === (data.groups instanceof Array))
        return this._clientError("Groups should be an array", data, callback)
    var stanza = new builder.Element(
        'iq',
        {type: 'set', id: this._getId() }
    ).c('query', { xmlns: this.NS }).c('item', {jid: data.jid})
    data.groups.forEach(function(group) {
        stanza.c('group').t(group).up()
    })
    this.manager.trackId(stanza.root().attr('id'), function(stanza) {
        if ('error' == stanza.attrs.type) return callback(self._parseError(stanza))
        callback(null, true)
    })
    this.client.send(stanza)
}

Roster.prototype.handleRoster = function(stanza, callback) {
    if (typeof callback !== 'function')
        return this._clientError('Missing callback')
    var self  = this
    var items = []
    if ('error' == stanza.attrs.type)
        return callback(this._parseError(stanza), null)
    stanza.getChild('query').getChildren('item').forEach(function(item) {
        var entry = {
            jid: self._getJid(item.attrs.jid),
            subscription: item.attrs.subscription,
        }
        if (item.attrs.name) entry.name = item.attrs.name
        if (item.attrs.ask) entry.ask = item.attrs.ask
        var groups
        if (0 !== (groups = item.getChildren('group')).length) {
            entry.groups = []
            groups.forEach(function(group) {
                entry.groups.push(group.getText())
            })
        }
        items.push(entry)
    })
    callback(null, items)
}

Roster.prototype.handleRemoteAdd = function(stanza) {
    var data = stanza.getChild('query').getChild('item')
    var rosterItem = {
        jid: this._getJid(data.attrs.jid),
        subscription:  data.attrs.subscription
    }
    var groups
    if (data.attrs.name) rosterItem.name = data.attrs.name
    if (data.attrs.ask) rosterItem.ask = data.attrs.ask
    if (0 !== (groups = data.getChildren('group')).length) {
        rosterItem.groups = []
        groups.forEach(function(group) {
            rosterItem.groups.push(group.getText())
        })
    }
    this.socket.emit('xmpp.roster.push', rosterItem)
    return true
}

module.exports = Roster
<commit_msg>Edit a roster item MUST provide name to not remove it. So rename “group” method by “edit”.<commit_after>var builder = require('ltx'),
    Base    = require('./base')

var Roster = function() {}

Roster.prototype = new Base()

Roster.prototype.NS = "jabber:iq:roster"

Roster.prototype._events = {
    'xmpp.roster.add': 'add',
    'xmpp.roster.get': 'get',
    'xmpp.roster.edit': 'edit',
    'xmpp.roster.remove': 'remove'
}

Roster.prototype.handles = function(stanza) {
    return !!(stanza.is('iq') &&
        (stanza.getChild('query', this.NS)))
}

Roster.prototype.handle = function(stanza) {
    if ('set' == stanza.attr('type'))
        return this.handleRemoteAdd(stanza)
    return false
}

Roster.prototype.get = function(data, callback) {
    if (typeof callback !== 'function')
        return this._clientError('Missing callback')
    var self   = this
    var stanza = new builder.Element(
        'iq',
        { from: this.manager.jid.split('/')[0], type: 'get', id: this._getId() }
    ).c('query', {xmlns: this.NS}).up()

    this.manager.trackId(stanza.root().attr('id'), function(stanza) {
        self.handleRoster(stanza, callback)
    })
    this.client.send(stanza)
}

Roster.prototype.add = function(data, callback) {
    if (typeof callback !== 'function')
        return this._clientError('Missing callback', data)
    if (!data.jid)
        return this._clientError("Missing 'jid' key", data, callback)
    var self = this
    var item = {jid: data.jid}
    if (data.name) item.name = data.name

    var stanza = new builder.Element(
        'iq',
        {type: 'set', id: this._getId() }
    ).c('query', { xmlns: this.NS }).c('item', item)
    if (data.groups)
        data.groups.forEach(function(group) {
            stanza.c('group').t(group).up()
        })
    this.manager.trackId(stanza.root().attr('id'), function(stanza) {
        if (stanza.attrs.type == 'error') return callback(self._parseError(stanza), null)
        callback(null, true)
    })
    this.client.send(stanza)
}

Roster.prototype.remove = function(data, callback) {
    if (typeof callback !== 'function')
        return this._clientError('Missing callback', data)
    if (!data.jid)
        return this._clientError("Missing 'jid' key", data, callback)
    var self = this
    var item = { jid: data.jid, subscription: 'remove' }

    var stanza = new builder.Element(
        'iq',
        {type: 'set', id: this._getId() }
    ).c('query', { xmlns: this.NS }).c('item', item)

    this.manager.trackId(stanza.root().attr('id'), function(stanza) {
        if (stanza.attrs.type == 'error') return callback(self._parseError(stanza), null)
        callback(null, true)
    })
    this.client.send(stanza)
}
Roster.prototype.edit = function(data, callback) {
    var self = this
    if (!data.jid)
        return this._clientError("Missing 'jid' key", data, callback)
    if (!data.groups)
        return this._clientError("Missing 'groups' key", data, callback)
    if (!data.name)
        return this._clientError("Missing 'name' key", data, callback)
    if (typeof callback !== 'function')
        return this._clientError('Missing callback', data)
    if (false === (data.groups instanceof Array))
        return this._clientError("Groups should be an array", data, callback)
    var stanza = new builder.Element(
        'iq',
        {type: 'set', id: this._getId() }
    ).c('query', { xmlns: this.NS }).c('item', {jid: data.jid, name: data.name})
    data.groups.forEach(function(group) {
        stanza.c('group').t(group).up()
    })
    this.manager.trackId(stanza.root().attr('id'), function(stanza) {
        if ('error' == stanza.attrs.type) return callback(self._parseError(stanza))
        callback(null, true)
    })
    this.client.send(stanza)
}

Roster.prototype.handleRoster = function(stanza, callback) {
    if (typeof callback !== 'function')
        return this._clientError('Missing callback')
    var self  = this
    var items = []
    if ('error' == stanza.attrs.type)
        return callback(this._parseError(stanza), null)
    stanza.getChild('query').getChildren('item').forEach(function(item) {
        var entry = {
            jid: self._getJid(item.attrs.jid),
            subscription: item.attrs.subscription,
        }
        if (item.attrs.name) entry.name = item.attrs.name
        if (item.attrs.ask) entry.ask = item.attrs.ask
        var groups
        if (0 !== (groups = item.getChildren('group')).length) {
            entry.groups = []
            groups.forEach(function(group) {
                entry.groups.push(group.getText())
            })
        }
        items.push(entry)
    })
    callback(null, items)
}

Roster.prototype.handleRemoteAdd = function(stanza) {
    var data = stanza.getChild('query').getChild('item')
    var rosterItem = {
        jid: this._getJid(data.attrs.jid),
        subscription:  data.attrs.subscription
    }
    var groups
    if (data.attrs.name) rosterItem.name = data.attrs.name
    if (data.attrs.ask) rosterItem.ask = data.attrs.ask
    if (0 !== (groups = data.getChildren('group')).length) {
        rosterItem.groups = []
        groups.forEach(function(group) {
            rosterItem.groups.push(group.getText())
        })
    }
    this.socket.emit('xmpp.roster.push', rosterItem)
    return true
}

module.exports = Roster
<|endoftext|>