blob: 1000094526302b2a835bbffbed705aa243337670 [file] [log] [blame]
<!--
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-->
<import src="observe.sky" as="observe" />
<script>
Node.prototype.bind = function(name, observable, oneTime) {
var self = this;
if (oneTime) {
this[name] = observable;
return;
}
this[name] = observable.open(function(value) {
self[name] = value;
});
return observable;
};
function sanitizeValue(value) {
return value == null ? '' : value;
}
function updateText(node, value) {
node.data = sanitizeValue(value);
}
function textBinding(node) {
return function(value) {
return updateText(node, value);
};
}
Text.prototype.bind = function(name, value, oneTime) {
if (name !== 'textContent')
return Node.prototype.bind.call(this, name, value, oneTime);
if (oneTime)
return updateText(this, value);
var observable = value;
updateText(this, observable.open(textBinding(this)));
return observable;
}
function updateAttribute(el, name, value) {
el.setAttribute(name, sanitizeValue(value));
}
function attributeBinding(el, name) {
return function(value) {
updateAttribute(el, name, value);
};
}
function bindAsAttribute(el, name) {
if (name == 'style' || name == 'class')
return true;
if (el.tagName == 'a' && name == 'href')
return true;
}
Element.prototype.bind = function(name, value, oneTime) {
if (!bindAsAttribute(this, name))
return Node.prototype.bind.call(this, name, value, oneTime);
if (oneTime)
return updateAttribute(this, name, value);
var observable = value;
updateAttribute(this, name, observable.open(attributeBinding(this, name)));
return observable;
}
function getFragmentRoot(node) {
var p;
while (p = node.parentNode) {
node = p;
}
return node;
}
function searchRefId(node, id) {
if (!id)
return;
var ref;
var selector = '#' + id;
while (!ref) {
node = getFragmentRoot(node);
if (node.protoContent_)
ref = node.protoContent_.querySelector(selector);
else if (node.getElementById)
ref = node.getElementById(id);
if (ref || !node.templateCreator_)
break
node = node.templateCreator_;
}
return ref;
}
function getInstanceRoot(node) {
while (node.parentNode) {
node = node.parentNode;
}
return node.templateCreator_ ? node : null;
}
var BIND = 'bind';
var REPEAT = 'repeat';
var IF = 'if';
var templateAttributeDirectives = {
'template': true,
'repeat': true,
'bind': true,
'ref': true
};
function isTemplate(el) {
if (el.isTemplate_ === undefined)
el.isTemplate_ = el.tagName == 'template';
return el.isTemplate_;
}
function mixin(to, from) {
Object.getOwnPropertyNames(from).forEach(function(name) {
Object.defineProperty(to, name,
Object.getOwnPropertyDescriptor(from, name));
});
}
function getTemplateStagingDocument(template) {
if (!template.stagingDocument_) {
var owner = template.ownerDocument;
if (!owner.stagingDocument_) {
// FIXME(sky): Does this need to create a Document without a registration
// context?
owner.stagingDocument_ = new Document();
owner.stagingDocument_.isStagingDocument = true;
owner.stagingDocument_.stagingDocument_ = owner.stagingDocument_;
}
template.stagingDocument_ = owner.stagingDocument_;
}
return template.stagingDocument_;
}
var templateObserver;
if (typeof MutationObserver == 'function') {
templateObserver = new MutationObserver(function(records) {
for (var i = 0; i < records.length; i++) {
records[i].target.refChanged_();
}
});
}
var contentDescriptor = {
get: function() {
return this.content_;
},
enumerable: true,
configurable: true
};
function ensureSetModelScheduled(template) {
if (!template.setModelFn_) {
template.setModelFn_ = function() {
template.setModelFnScheduled_ = false;
var map = getBindings(template,
template.delegate_ && template.delegate_.prepareBinding);
processBindings(template, map, template.model_);
};
}
if (!template.setModelFnScheduled_) {
template.setModelFnScheduled_ = true;
Observer.runEOM_(template.setModelFn_);
}
}
mixin(HTMLTemplateElement.prototype, {
bind: function(name, value, oneTime) {
if (name != 'ref')
return Element.prototype.bind.call(this, name, value, oneTime);
var self = this;
var ref = oneTime ? value : value.open(function(ref) {
self.setAttribute('ref', ref);
self.refChanged_();
});
this.setAttribute('ref', ref);
this.refChanged_();
if (oneTime)
return;
if (!this.bindings_) {
this.bindings_ = { ref: value };
} else {
this.bindings_.ref = value;
}
return value;
},
processBindingDirectives_: function(directives) {
if (this.iterator_)
this.iterator_.closeDeps();
if (!directives.if && !directives.bind && !directives.repeat) {
if (this.iterator_) {
this.iterator_.close();
this.iterator_ = undefined;
}
return;
}
if (!this.iterator_) {
this.iterator_ = new TemplateIterator(this);
}
this.iterator_.updateDependencies(directives, this.model_);
if (templateObserver) {
templateObserver.observe(this, { attributes: true,
attributeFilter: ['ref'] });
}
return this.iterator_;
},
createInstance: function(model, bindingDelegate, delegate_) {
if (bindingDelegate)
delegate_ = this.newDelegate_(bindingDelegate);
else if (!delegate_)
delegate_ = this.delegate_;
if (!this.refContent_)
this.refContent_ = this.ref_.content;
var content = this.refContent_;
if (content.firstChild === null)
return emptyInstance;
var map = getInstanceBindingMap(content, delegate_);
var stagingDocument = getTemplateStagingDocument(this);
var instance = stagingDocument.createDocumentFragment();
instance.templateCreator_ = this;
instance.protoContent_ = content;
instance.bindings_ = [];
instance.terminator_ = null;
var instanceRecord = instance.templateInstance_ = {
firstNode: null,
lastNode: null,
model: model
};
var i = 0;
var collectTerminator = false;
for (var child = content.firstChild; child; child = child.nextSibling) {
// The terminator of the instance is the clone of the last child of the
// content. If the last child is an active template, it may produce
// instances as a result of production, so simply collecting the last
// child of the instance after it has finished producing may be wrong.
if (child.nextSibling === null)
collectTerminator = true;
var clone = cloneAndBindInstance(child, instance, stagingDocument,
map.children[i++],
model,
delegate_,
instance.bindings_);
clone.templateInstance_ = instanceRecord;
if (collectTerminator)
instance.terminator_ = clone;
}
instanceRecord.firstNode = instance.firstChild;
instanceRecord.lastNode = instance.lastChild;
instance.templateCreator_ = undefined;
instance.protoContent_ = undefined;
return instance;
},
get model() {
return this.model_;
},
set model(model) {
this.model_ = model;
ensureSetModelScheduled(this);
},
get bindingDelegate() {
return this.delegate_ && this.delegate_.raw;
},
refChanged_: function() {
if (!this.iterator_ || this.refContent_ === this.ref_.content)
return;
this.refContent_ = undefined;
this.iterator_.valueChanged();
this.iterator_.updateIteratedValue(this.iterator_.getUpdatedValue());
},
clear: function() {
this.model_ = undefined;
this.delegate_ = undefined;
if (this.bindings_ && this.bindings_.ref)
this.bindings_.ref.close()
this.refContent_ = undefined;
if (!this.iterator_)
return;
this.iterator_.valueChanged();
this.iterator_.close()
this.iterator_ = undefined;
},
setDelegate_: function(delegate) {
this.delegate_ = delegate;
this.bindingMap_ = undefined;
if (this.iterator_) {
this.iterator_.instancePositionChangedFn_ = undefined;
this.iterator_.instanceModelFn_ = undefined;
}
},
newDelegate_: function(bindingDelegate) {
if (!bindingDelegate)
return;
function delegateFn(name) {
var fn = bindingDelegate && bindingDelegate[name];
if (typeof fn != 'function')
return;
return function() {
return fn.apply(bindingDelegate, arguments);
};
}
return {
bindingMaps: {},
raw: bindingDelegate,
prepareBinding: delegateFn('prepareBinding'),
prepareInstanceModel: delegateFn('prepareInstanceModel'),
prepareInstancePositionChanged:
delegateFn('prepareInstancePositionChanged')
};
},
set bindingDelegate(bindingDelegate) {
if (this.delegate_) {
throw Error('Template must be cleared before a new bindingDelegate ' +
'can be assigned');
}
this.setDelegate_(this.newDelegate_(bindingDelegate));
},
get ref_() {
var ref = searchRefId(this, this.getAttribute('ref'));
if (!ref)
ref = this.instanceRef_;
if (!ref)
return this;
var nextRef = ref.ref_;
return nextRef ? nextRef : ref;
}
});
// Returns
// a) undefined if there are no mustaches.
// b) [TEXT, (ONE_TIME?, PATH, DELEGATE_FN, TEXT)+] if there is at least
// one mustache.
function parseMustaches(s, name, node, prepareBindingFn) {
if (!s || !s.length)
return;
var tokens;
var length = s.length;
var startIndex = 0, lastIndex = 0, endIndex = 0;
var onlyOneTime = true;
while (lastIndex < length) {
var startIndex = s.indexOf('{{', lastIndex);
var oneTimeStart = s.indexOf('[[', lastIndex);
var oneTime = false;
var terminator = '}}';
if (oneTimeStart >= 0 &&
(startIndex < 0 || oneTimeStart < startIndex)) {
startIndex = oneTimeStart;
oneTime = true;
terminator = ']]';
}
endIndex = startIndex < 0 ? -1 : s.indexOf(terminator, startIndex + 2);
if (endIndex < 0) {
if (!tokens)
return;
tokens.push(s.slice(lastIndex)); // TEXT
break;
}
tokens = tokens || [];
tokens.push(s.slice(lastIndex, startIndex)); // TEXT
var pathString = s.slice(startIndex + 2, endIndex).trim();
tokens.push(oneTime); // ONE_TIME?
onlyOneTime = onlyOneTime && oneTime;
var delegateFn = prepareBindingFn &&
prepareBindingFn(pathString, name, node);
// Don't try to parse the expression if there's a prepareBinding function
if (delegateFn == null) {
tokens.push(observe.Path.get(pathString)); // PATH
} else {
tokens.push(null);
}
tokens.push(delegateFn); // DELEGATE_FN
lastIndex = endIndex + 2;
}
if (lastIndex === length)
tokens.push(''); // TEXT
tokens.hasOnePath = tokens.length === 5;
tokens.isSimplePath = tokens.hasOnePath &&
tokens[0] == '' &&
tokens[4] == '';
tokens.onlyOneTime = onlyOneTime;
tokens.combinator = function(values) {
var newValue = tokens[0];
for (var i = 1; i < tokens.length; i += 4) {
var value = tokens.hasOnePath ? values : values[(i - 1) / 4];
if (value !== undefined)
newValue += value;
newValue += tokens[i + 3];
}
return newValue;
}
return tokens;
};
function processOneTimeBinding(name, tokens, node, model) {
if (tokens.hasOnePath) {
var delegateFn = tokens[3];
var value = delegateFn ? delegateFn(model, node, true) :
tokens[2].getValueFrom(model);
return tokens.isSimplePath ? value : tokens.combinator(value);
}
var values = [];
for (var i = 1; i < tokens.length; i += 4) {
var delegateFn = tokens[i + 2];
values[(i - 1) / 4] = delegateFn ? delegateFn(model, node) :
tokens[i + 1].getValueFrom(model);
}
return tokens.combinator(values);
}
function processSinglePathBinding(name, tokens, node, model) {
var delegateFn = tokens[3];
var observer = delegateFn ? delegateFn(model, node, false) :
new observe.PathObserver(model, tokens[2]);
return tokens.isSimplePath ? observer :
new observe.ObserverTransform(observer, tokens.combinator);
}
function processBinding(name, tokens, node, model) {
if (tokens.onlyOneTime)
return processOneTimeBinding(name, tokens, node, model);
if (tokens.hasOnePath)
return processSinglePathBinding(name, tokens, node, model);
var observer = new observe.CompoundObserver();
for (var i = 1; i < tokens.length; i += 4) {
var oneTime = tokens[i];
var delegateFn = tokens[i + 2];
if (delegateFn) {
var value = delegateFn(model, node, oneTime);
if (oneTime)
observer.addPath(value)
else
observer.addObserver(value);
continue;
}
var path = tokens[i + 1];
if (oneTime)
observer.addPath(path.getValueFrom(model))
else
observer.addPath(model, path);
}
return new observe.ObserverTransform(observer, tokens.combinator);
}
function processBindings(node, bindings, model, instanceBindings) {
for (var i = 0; i < bindings.length; i += 2) {
var name = bindings[i]
var tokens = bindings[i + 1];
var value = processBinding(name, tokens, node, model);
var binding = node.bind(name, value, tokens.onlyOneTime);
if (binding && instanceBindings)
instanceBindings.push(binding);
}
if (!bindings.isTemplate)
return;
node.model_ = model;
var iter = node.processBindingDirectives_(bindings);
if (instanceBindings && iter)
instanceBindings.push(iter);
}
function parseWithDefault(el, name, prepareBindingFn) {
var v = el.getAttribute(name);
return parseMustaches(v == '' ? '{{}}' : v, name, el, prepareBindingFn);
}
function addEventHandler(element, name, method) {
element.addEventListener(name, function(event) {
var scope = element.ownerScope;
var host = scope.host;
var handler = host && host[method];
if (handler instanceof Function)
return handler.call(host, event);
});
}
function parseAttributeBindings(element, prepareBindingFn) {
var bindings = [];
var ifFound = false;
var bindFound = false;
var attributes = element.getAttributes();
for (var i = 0; i < attributes.length; i++) {
var attr = attributes[i];
var name = attr.name;
var value = attr.value;
if (isTemplate(element) &&
(name === IF || name === BIND || name === REPEAT)) {
continue;
}
if (name.startsWith('on-')) {
if (!bindings.eventHandlers)
bindings.eventHandlers = new Map();
bindings.eventHandlers.set(name.substring(3), value);
continue;
}
var tokens = parseMustaches(value, name, element,
prepareBindingFn);
if (!tokens)
continue;
bindings.push(name, tokens);
}
if (isTemplate(element)) {
bindings.isTemplate = true;
bindings.if = parseWithDefault(element, IF, prepareBindingFn);
bindings.bind = parseWithDefault(element, BIND, prepareBindingFn);
bindings.repeat = parseWithDefault(element, REPEAT, prepareBindingFn);
if (bindings.if && !bindings.bind && !bindings.repeat)
bindings.bind = parseMustaches('{{}}', BIND, element, prepareBindingFn);
}
return bindings;
}
function getBindings(node, prepareBindingFn) {
if (node instanceof Element) {
return parseAttributeBindings(node, prepareBindingFn);
}
if (node instanceof Text) {
var tokens = parseMustaches(node.data, 'textContent', node,
prepareBindingFn);
if (tokens)
return ['textContent', tokens];
}
return [];
}
function cloneAndBindInstance(node, parent, stagingDocument, bindings, model,
delegate,
instanceBindings,
instanceRecord) {
var clone = parent.appendChild(stagingDocument.importNode(node, false));
var i = 0;
for (var child = node.firstChild; child; child = child.nextSibling) {
cloneAndBindInstance(child, clone, stagingDocument,
bindings.children[i++],
model,
delegate,
instanceBindings);
}
if (bindings.isTemplate) {
clone.instanceRef_ = node;
if (delegate)
clone.setDelegate_(delegate);
}
if (bindings.eventHandlers) {
bindings.eventHandlers.forEach(function(handler, eventName) {
addEventHandler(clone, eventName, handler);
});
}
processBindings(clone, bindings, model, instanceBindings);
return clone;
}
function createInstanceBindingMap(node, prepareBindingFn) {
var map = getBindings(node, prepareBindingFn);
map.children = {};
var index = 0;
for (var child = node.firstChild; child; child = child.nextSibling) {
map.children[index++] = createInstanceBindingMap(child, prepareBindingFn);
}
return map;
}
var contentUidCounter = 1;
// TODO(rafaelw): Setup a MutationObserver on content which clears the id
// so that bindingMaps regenerate when the template.content changes.
function getContentUid(content) {
var id = content.id_;
if (!id)
id = content.id_ = contentUidCounter++;
return id;
}
// Each delegate is associated with a set of bindingMaps, one for each
// content which may be used by a template. The intent is that each binding
// delegate gets the opportunity to prepare the instance (via the prepare*
// delegate calls) once across all uses.
// TODO(rafaelw): Separate out the parse map from the binding map. In the
// current implementation, if two delegates need a binding map for the same
// content, the second will have to reparse.
function getInstanceBindingMap(content, delegate_) {
var contentId = getContentUid(content);
if (delegate_) {
var map = delegate_.bindingMaps[contentId];
if (!map) {
map = delegate_.bindingMaps[contentId] =
createInstanceBindingMap(content, delegate_.prepareBinding) || [];
}
return map;
}
var map = content.bindingMap_;
if (!map) {
map = content.bindingMap_ =
createInstanceBindingMap(content, undefined) || [];
}
return map;
}
Object.defineProperty(Node.prototype, 'templateInstance', {
get: function() {
var instance = this.templateInstance_;
return instance ? instance :
(this.parentNode ? this.parentNode.templateInstance : undefined);
}
});
var emptyInstance = document.createDocumentFragment();
emptyInstance.bindings_ = [];
emptyInstance.terminator_ = null;
function TemplateIterator(templateElement) {
this.closed = false;
this.templateElement_ = templateElement;
this.instances = [];
this.deps = undefined;
this.iteratedValue = [];
this.presentValue = undefined;
this.arrayObserver = undefined;
}
TemplateIterator.prototype = {
closeDeps: function() {
var deps = this.deps;
if (deps) {
if (deps.ifOneTime === false)
deps.ifValue.close();
if (deps.oneTime === false)
deps.value.close();
}
},
updateDependencies: function(directives, model) {
this.closeDeps();
var deps = this.deps = {};
var template = this.templateElement_;
var ifValue = true;
if (directives.if) {
deps.hasIf = true;
deps.ifOneTime = directives.if.onlyOneTime;
deps.ifValue = processBinding(IF, directives.if, template, model);
ifValue = deps.ifValue;
// oneTime if & predicate is false. nothing else to do.
if (deps.ifOneTime && !ifValue) {
this.valueChanged();
return;
}
if (!deps.ifOneTime)
ifValue = ifValue.open(this.updateIfValue, this);
}
if (directives.repeat) {
deps.repeat = true;
deps.oneTime = directives.repeat.onlyOneTime;
deps.value = processBinding(REPEAT, directives.repeat, template, model);
} else {
deps.repeat = false;
deps.oneTime = directives.bind.onlyOneTime;
deps.value = processBinding(BIND, directives.bind, template, model);
}
var value = deps.value;
if (!deps.oneTime)
value = value.open(this.updateIteratedValue, this);
if (!ifValue) {
this.valueChanged();
return;
}
this.updateValue(value);
},
/**
* Gets the updated value of the bind/repeat. This can potentially call
* user code (if a bindingDelegate is set up) so we try to avoid it if we
* already have the value in hand (from Observer.open).
*/
getUpdatedValue: function() {
var value = this.deps.value;
if (!this.deps.oneTime)
value = value.discardChanges();
return value;
},
updateIfValue: function(ifValue) {
if (!ifValue) {
this.valueChanged();
return;
}
this.updateValue(this.getUpdatedValue());
},
updateIteratedValue: function(value) {
if (this.deps.hasIf) {
var ifValue = this.deps.ifValue;
if (!this.deps.ifOneTime)
ifValue = ifValue.discardChanges();
if (!ifValue) {
this.valueChanged();
return;
}
}
this.updateValue(value);
},
updateValue: function(value) {
if (!this.deps.repeat)
value = [value];
var observe = this.deps.repeat &&
!this.deps.oneTime &&
Array.isArray(value);
this.valueChanged(value, observe);
},
valueChanged: function(value, observeValue) {
if (!Array.isArray(value))
value = [];
if (value === this.iteratedValue)
return;
this.unobserve();
this.presentValue = value;
if (observeValue) {
this.arrayObserver = new observe.ArrayObserver(this.presentValue);
this.arrayObserver.open(this.handleSplices, this);
}
this.handleSplices(observe.ArrayObserver.calculateSplices(this.presentValue,
this.iteratedValue));
},
getLastInstanceNode: function(index) {
if (index == -1)
return this.templateElement_;
var instance = this.instances[index];
var terminator = instance.terminator_;
if (!terminator)
return this.getLastInstanceNode(index - 1);
if (terminator.nodeType !== Node.ELEMENT_NODE ||
this.templateElement_ === terminator) {
return terminator;
}
var subtemplateIterator = terminator.iterator_;
if (!subtemplateIterator)
return terminator;
return subtemplateIterator.getLastTemplateNode();
},
getLastTemplateNode: function() {
return this.getLastInstanceNode(this.instances.length - 1);
},
insertInstanceAt: function(index, fragment) {
var previousInstanceLast = this.getLastInstanceNode(index - 1);
var parent = this.templateElement_.parentNode;
this.instances.splice(index, 0, fragment);
parent.insertBefore(fragment, previousInstanceLast.nextSibling);
},
extractInstanceAt: function(index) {
var previousInstanceLast = this.getLastInstanceNode(index - 1);
var lastNode = this.getLastInstanceNode(index);
var parent = this.templateElement_.parentNode;
var instance = this.instances.splice(index, 1)[0];
while (lastNode !== previousInstanceLast) {
var node = previousInstanceLast.nextSibling;
if (node == lastNode)
lastNode = previousInstanceLast;
instance.appendChild(parent.removeChild(node));
}
return instance;
},
getDelegateFn: function(fn) {
fn = fn && fn(this.templateElement_);
return typeof fn === 'function' ? fn : null;
},
handleSplices: function(splices) {
if (this.closed || !splices.length)
return;
var template = this.templateElement_;
if (!template.parentNode) {
this.close();
return;
}
observe.ArrayObserver.applySplices(this.iteratedValue, this.presentValue,
splices);
var delegate = template.delegate_;
if (this.instanceModelFn_ === undefined) {
this.instanceModelFn_ =
this.getDelegateFn(delegate && delegate.prepareInstanceModel);
}
if (this.instancePositionChangedFn_ === undefined) {
this.instancePositionChangedFn_ =
this.getDelegateFn(delegate &&
delegate.prepareInstancePositionChanged);
}
// Instance Removals
var instanceCache = new Map;
var removeDelta = 0;
for (var i = 0; i < splices.length; i++) {
var splice = splices[i];
var removed = splice.removed;
for (var j = 0; j < removed.length; j++) {
var model = removed[j];
var instance = this.extractInstanceAt(splice.index + removeDelta);
if (instance !== emptyInstance) {
instanceCache.set(model, instance);
}
}
removeDelta -= splice.addedCount;
}
// Instance Insertions
for (var i = 0; i < splices.length; i++) {
var splice = splices[i];
var addIndex = splice.index;
for (; addIndex < splice.index + splice.addedCount; addIndex++) {
var model = this.iteratedValue[addIndex];
var instance = instanceCache.get(model);
if (instance) {
instanceCache.delete(model);
} else {
if (this.instanceModelFn_) {
model = this.instanceModelFn_(model);
}
if (model === undefined) {
instance = emptyInstance;
} else {
instance = template.createInstance(model, undefined, delegate);
}
}
this.insertInstanceAt(addIndex, instance);
}
}
instanceCache.forEach(function(instance) {
this.closeInstanceBindings(instance);
}, this);
if (this.instancePositionChangedFn_)
this.reportInstancesMoved(splices);
},
reportInstanceMoved: function(index) {
var instance = this.instances[index];
if (instance === emptyInstance)
return;
this.instancePositionChangedFn_(instance.templateInstance_, index);
},
reportInstancesMoved: function(splices) {
var index = 0;
var offset = 0;
for (var i = 0; i < splices.length; i++) {
var splice = splices[i];
if (offset != 0) {
while (index < splice.index) {
this.reportInstanceMoved(index);
index++;
}
} else {
index = splice.index;
}
while (index < splice.index + splice.addedCount) {
this.reportInstanceMoved(index);
index++;
}
offset += splice.addedCount - splice.removed.length;
}
if (offset == 0)
return;
var length = this.instances.length;
while (index < length) {
this.reportInstanceMoved(index);
index++;
}
},
closeInstanceBindings: function(instance) {
var bindings = instance.bindings_;
for (var i = 0; i < bindings.length; i++) {
bindings[i].close();
}
},
unobserve: function() {
if (!this.arrayObserver)
return;
this.arrayObserver.close();
this.arrayObserver = undefined;
},
close: function() {
if (this.closed)
return;
this.unobserve();
for (var i = 0; i < this.instances.length; i++) {
this.closeInstanceBindings(this.instances[i]);
}
this.instances.length = 0;
this.closeDeps();
this.templateElement_.iterator_ = undefined;
this.closed = true;
}
};
</script>