Permalink
Browse files

Add enyo.Object.bindSafely() to bind methods to enyo.Object instances…

… where if the object's destroyed, the binding will do nothing.

Update Enyo internal & sample code to use bindSafely (to prove it works well and reap the benefits)

Update GestureSample to remove old property setting code that doesn't work in 2.3.

Enyo-DCO-1.1-Signed-Off-By: Ben Combee (ben.combee@lge.com)
  • Loading branch information...
1 parent 08ce2da commit 0dbe79700edaeee8f1907ec0c1ce0fb931b182fb Ben Combee committed Mar 27, 2013
@@ -4,7 +4,7 @@ enyo.kind({
classes: "gesture-sample enyo-fit enyo-unselectable",
components: [
{
- classes:"gesture-sample-pad",
+ classes:"gesture-sample-pad",
fit:true,
ondown: "handleEvent",
onup: "handleEvent",
@@ -54,9 +54,12 @@ enyo.kind({
this.inherited(arguments);
this.eventList = {};
this.eventCount = 0;
- enyo.forEach(["All events","down","up","tap","move","enter","leave","dragstart","drag","dragover","hold","release","holdpulse","flick","gesturestart","gesturechange","gestureend"], enyo.bind(this, function(event) {
- this.$.eventPicker.createComponent({content:event, style:"text-align:left"});
- }));
+ enyo.forEach(
+ ["All events","down","up","tap","move","enter","leave","dragstart","drag","dragover","hold","release",
+ "holdpulse","flick","gesturestart","gesturechange","gestureend"],
+ this.bindSafely(function(event) {
+ this.$.eventPicker.createComponent({content:event, style:"text-align:left"});
+ }));
},
handleEvent: function(inSender, inEvent) {
var event = enyo.clone(inEvent);
@@ -65,13 +68,13 @@ enyo.kind({
}
var eventItem = this.eventList[event.type];
if (eventItem) {
- eventItem.setEvent(event);
+ eventItem.set("event", event, true);
} else {
this.eventCount++;
eventItem = this.$.eventList.createComponent({
kind: "enyo.sample.EventItem",
event:event,
- truncate: this.$.truncateDetail.getValue(),
+ truncate: this.$.truncateDetail.get("value"),
persist: this.monitorEvent
});
this.eventList[event.type] = eventItem;
@@ -83,7 +86,7 @@ enyo.kind({
},
truncateChanged: function() {
for (var i in this.eventList) {
- this.eventList[i].setTruncate(this.$.truncateDetail.getValue());
+ this.eventList[i].set("truncate", this.$.truncateDetail.get("value"));
}
this.reflow();
return false;
@@ -108,7 +111,7 @@ enyo.kind({
this.reflow();
},
toggleSettings: function() {
- this.$.settings.setShowing(!this.$.settings.getShowing());
+ this.$.settings.set("showing", !this.$.settings.get("showing"));
this.reflow();
},
preventDefault: function() {
@@ -147,18 +150,14 @@ enyo.kind({
truncateChanged: function() {
this.$.eventProps.addRemoveClass("gesture-sample-truncate", this.truncate);
},
- // since event is an object, force set
- setEvent: function(inEvent) {
- this.setPropertyValue("event", inEvent, "eventChanged");
- },
eventChanged: function(inOld) {
if (this.event) {
if (this.timeout) {
clearTimeout(this.timeout);
this.timeout = null;
}
this.$.animator.stop();
- this.$.eventProps.setContent(this.getPropsString());
+ this.$.eventProps.set("content", this.getPropsString());
this.$.animator.play();
}
},
@@ -168,7 +167,7 @@ enyo.kind({
},
animationEnded: function() {
if (!this.persist) {
- this.timeout = setTimeout(enyo.bind(this, function() {
+ this.timeout = setTimeout(this.bindSafely(function() {
this.doDone({type:this.event.type});
}), 2000);
}
@@ -185,7 +184,7 @@ enyo.kind({
for (var i in this.event) {
if ((this.event[i] !== undefined) &&
(this.event[i] !== null) &&
- !(this.event[i] instanceof Object) &&
+ !(this.event[i] instanceof Object) &&
(i != "type")) {
props.push(i + ": " + this.event[i]);
}
View
@@ -123,7 +123,7 @@ enyo.kind({
this.xhr = enyo.xhr.request({
url: url,
method: this.method,
- callback: enyo.bind(this, "receive"),
+ callback: this.bindSafely("receive"),
body: body,
headers: xhr_headers,
sync: window.PalmSystem ? false : this.sync,
@@ -56,11 +56,11 @@ enyo.kind({
},
//* @protected
route: function(inAsync, inValue) {
- var r = enyo.bind(this, "respond");
+ var r = this.bindSafely("respond");
inAsync.response(function(inSender, inValue) {
r(inValue);
});
- var f = enyo.bind(this, "fail");
+ var f = this.bindSafely("fail");
inAsync.error(function(inSender, inValue) {
f(inValue);
});
@@ -84,7 +84,7 @@ enyo.kind({
startTimer: function() {
this.startTime = enyo.now();
if (this.timeout) {
- this.timeoutJob = setTimeout(enyo.bind(this, "timeoutComplete"), this.timeout);
+ this.timeoutJob = setTimeout(this.bindSafely("timeoutComplete"), this.timeout);
}
},
endTimer: function() {
@@ -48,7 +48,7 @@ enyo.kind({
script.charset = this.charset;
}
// most modern browsers also have an onerror handler
- script.onerror = enyo.bind(this, function() {
+ script.onerror = this.bindSafely(function() {
// we don't get an error code, so we'll just use the generic 400 error status
this.fail(400);
});
@@ -84,10 +84,10 @@ enyo.kind({
this.src = this.buildUrl(inParams, callbackFunctionName);
this.addScriptElement();
//
- window[callbackFunctionName] = enyo.bind(this, this.respond);
+ window[callbackFunctionName] = this.bindSafely(this.respond);
//
// setup cleanup handlers for JSONP completion and failure
- var cleanup = enyo.bind(this, function() {
+ var cleanup = this.bindSafely(function() {
this.removeScriptElement();
window[callbackFunctionName] = null;
});
@@ -473,7 +473,7 @@ enyo.kind({
this._silenced = true;
this._silence_count += 1;
},
-
+
//*@public
/**
If the internal silence counter is 0 this method will allow
@@ -500,7 +500,7 @@ enyo.kind({
}
// stop any existing jobs with same name
this.stopJob(inJobName);
- this.__jobs[inJobName] = setTimeout(enyo.bind(this, function() {
+ this.__jobs[inJobName] = setTimeout(this.bindSafely(function() {
this.stopJob(inJobName);
// call "inJob" with this bound to the component.
inJob.call(this);
@@ -36,7 +36,7 @@ enyo.kind({
enyo._objectCount++;
this.importProps(props);
},
-
+
importProps: function (props) {
if (props) {
for (var key in props) {
@@ -99,7 +99,7 @@ enyo.kind({
a string the object will attempt to be resolved. The goal is
to determine of the the property is a constructor, an instance or
nothing. See _lang.js#enyo.findAndInstance_ for more information.
-
+
If a method exists of the form `{property}FindAndInstance` it will
be used as the callback accepting two parameters, the constructor
if it was found and the instance if it was found or created,
@@ -113,15 +113,15 @@ enyo.kind({
// go ahead and call the enyo scoped version of this method
return enyo.findAndInstance.call(this, property, fn, this);
},
-
+
//*@public
/**
Call this method with the name (or path) to the desired property or
computed property. If it encounters a computed property it will return
the value of that property and not the function. If it cannot find
or resolve the requested path relative to the object it will return
undefined.
-
+
This method is backwards compatible and will automatically call any
existing _getter_ method that uses the getProperty convention although
this convention ought to be replaced using a computed property moving
@@ -137,7 +137,7 @@ enyo.kind({
been changed if the values are not the same. If the property it finds
is a computed property it will pass the intended value to the computed
property (but will not return the value).
-
+
This method is backwards compatible and will call any setter of the
setProperty convention although these methods should be replaced with
computed properties or observers where necessary.
@@ -146,14 +146,42 @@ enyo.kind({
return enyo.setPath.apply(this, arguments);
},
+ //*@public
+ /**
+ Bind a callback to this object. The bound method will be aborted cleanly with no
+ return value if the object has been destroyed. This usually should be used instead
+ of `enyo.bind` for running code in the context of a enyo.Object-derivative.
+ */
+ bindSafely: function(method/*, bound arguments*/) {
+ var scope = this;
+ if (enyo.isString(method)) {
+ if (this[method]) {
+ method = this[method];
+ } else {
+ throw(['enyo.Object.bindSafely: this["', method, '"] is null (this="', this, '")'].join(''));
+ }
+ }
+ if (enyo.isFunction(method)) {
+ var args = enyo.cloneArray(arguments, 2);
+ return function() {
+ if (scope.destroyed) {
+ return;
+ }
+ var nargs = enyo.cloneArray(arguments);
+ return method.apply(scope, args.concat(nargs));
+ };
+ } else {
+ throw(['enyo.Object.bindSafely: this["', method, '"] is not a function (this="', this, '")'].join(''));
+ }
+ },
//*@protected
destroy: function () {
// JS objects are never truly destroyed (GC'd) until all references are gone,
// we might have some delayed action on this object that needs to have access
// to this flag.
this.destroyed = true;
},
-
+
_is_object: true
});
@@ -219,7 +247,7 @@ enyo.Object.addGetterSetter = function (property, value, proto) {
fn = proto[getter];
// if there isn't already a getter provided create one
if ("function" !== typeof fn) {
- fn = proto[getter] = function () {return this.get(property)};
+ fn = proto[getter] = function () {return this.get(property);};
fn.overloaded = false;
} else if (false !== fn.overloaded) {
// otherwise we need to mark it as having been overloaded
@@ -229,7 +257,7 @@ enyo.Object.addGetterSetter = function (property, value, proto) {
// if there isn't already a setter provided create one
fn = proto[setter];
if ("function" !== typeof fn) {
- fn = proto[setter] = function () {return this.set(property, arguments[0])};
+ fn = proto[setter] = function () {return this.set(property, arguments[0]);};
fn.overloaded = false;
} else if (false !== fn.overloaded) {
// otherwise we need to mark it as having been overloaded
@@ -9,7 +9,7 @@
onscroll: function() {
// updateThumb will be called, but only when 1s has elapsed since the
// last onscroll
- enyo.job("updateThumb", enyo.bind(this, "updateThumb"), 1000);
+ enyo.job("updateThumb", this.bindSafely("updateThumb"), 1000);
}
*/
enyo.job = function(inJobName, inJob, inWait) {
@@ -151,7 +151,7 @@ enyo.kind({
// delta tracking
var x0, y0;
// animation handler
- var fn = enyo.bind(this, function() {
+ var fn = this.bindSafely(function() {
// wall-clock time
var t1 = enyo.now();
// schedule next frame
@@ -102,7 +102,7 @@ enyo.kind({
},
delayHide: function(inDelay) {
if (this.showing) {
- enyo.job(this.id + "hide", enyo.bind(this, "hide"), inDelay || 0);
+ enyo.job(this.id + "hide", this.bindSafely("hide"), inDelay || 0);
}
},
cancelDelayHide: function() {
@@ -522,7 +522,7 @@ enyo.kind({
// crossing into the overflow region, and bubble a scroll event
setCSSTransitionInterval: function() {
this.clearCSSTransitionInterval();
- this.scrollInterval = setInterval(enyo.bind(this, function() {
+ this.scrollInterval = setInterval(this.bindSafely(function() {
this.updateScrollPosition();
this.correctOverflow();
}), this.scrollIntervalMS);
@@ -531,7 +531,7 @@ enyo.kind({
// a scroll event (don't check for crossing into overflow since we're there already)
setOverflowTransitionInterval: function() {
this.clearCSSTransitionInterval();
- this.scrollInterval = setInterval(enyo.bind(this, function() {
+ this.scrollInterval = setInterval(this.bindSafely(function() {
this.updateScrollPosition();
}), this.scrollIntervalMS);
},
@@ -575,17 +575,17 @@ enyo.kind({
if(inEvent.originator !== this.$.client) {
return;
}
-
+
var posChanged = false;
-
+
if(this.isInTopOverScroll()) {
posChanged = true;
this.scrollTop = this.topBoundary;
} else if (this.isInBottomOverScroll()) {
posChanged = true;
this.scrollTop = -1*this.bottomBoundary;
}
-
+
if(this.isInLeftOverScroll()) {
posChanged = true;
this.scrollLeft = this.leftBoundary;
@@ -44,7 +44,7 @@ enyo.kind({
//* @protected
constructed: function() {
this.inherited(arguments);
- this._next = enyo.bind(this, "next");
+ this._next = this.bindSafely("next");
},
destroy: function() {
this.stop();
@@ -226,7 +226,7 @@ enyo.kind({
})
.error(this, function(inSender, inValue) {
// extra timeout is to make sure that timeout fail code cancels XHR
- enyo.job("timeouttest", enyo.bind(this, function() {this.finish("");}), 4000);
+ enyo.job("timeouttest", this.bindSafely(function() {this.finish("");}), 4000);
})
.go();
},
@@ -236,8 +236,8 @@ enyo.kind({
// getting success means server sent wrong response
return false;
}, function(inError) {
- return (inError === 500) &&
- req.xhrResponse &&
+ return (inError === 500) &&
+ req.xhrResponse &&
(req.xhrResponse.status === 500) &&
(req.xhrResponse.headers['content-type'] === "text/plain; charset=utf-8") &&
(req.xhrResponse.body === "my error description");
Oops, something went wrong.

0 comments on commit 0dbe797

Please sign in to comment.