From 43b8305367156c1ceb09eb4a056bdae3f325b5eb Mon Sep 17 00:00:00 2001 From: Bea Lam Date: Thu, 27 Jan 2011 15:34:34 +1000 Subject: Allow property bindings to be easily created from JavaScript Properties can now be assigned a function that returns the binding value. Task-number: QTBUG-14964 Reviewed-by: Aaron Kennedy --- doc/src/declarative/javascriptblocks.qdoc | 42 ++------- doc/src/declarative/propertybinding.qdoc | 103 +++++++++++++++++---- src/declarative/qml/qdeclarativebinding.cpp | 20 ++++ src/declarative/qml/qdeclarativebinding_p.h | 9 ++ src/declarative/qml/qdeclarativeexpression.cpp | 41 +++++++- src/declarative/qml/qdeclarativeexpression.h | 3 + src/declarative/qml/qdeclarativeexpression_p.h | 12 +++ .../qml/qdeclarativeobjectscriptclass.cpp | 19 +++- src/declarative/qml/qdeclarativeproperty.cpp | 25 +++-- src/declarative/qml/qdeclarativeproperty_p.h | 5 + .../qml/qdeclarativevaluetypescriptclass.cpp | 32 ++++++- .../data/functionAssignment.2.qml | 66 ++++++++++++- .../data/functionAssignment.js | 17 ++++ .../tst_qdeclarativeecmascript.cpp | 64 ++++++++++--- 14 files changed, 375 insertions(+), 83 deletions(-) create mode 100644 tests/auto/declarative/qdeclarativeecmascript/data/functionAssignment.js diff --git a/doc/src/declarative/javascriptblocks.qdoc b/doc/src/declarative/javascriptblocks.qdoc index d221205..65877f9 100644 --- a/doc/src/declarative/javascriptblocks.qdoc +++ b/doc/src/declarative/javascriptblocks.qdoc @@ -200,37 +200,12 @@ Likewise, the \l {Component::onDestruction} attached property is triggered on component destruction. -\section1 Property Assignment vs Property Binding +\section1 JavaScript and Property Binding -When working with both QML and JavaScript, it is important to differentiate between -QML \l {Property Binding} and JavaScript value assignment. In QML, a property -binding is created using the \e {property: value} syntax: +Property bindings can be created in JavaScript by assigning the property with a \c function +that returns the required value. -\code -Rectangle { - width: otherItem.width -} -\endcode - -The \c width of the above \l Rectangle is updated whenever \c otherItem.width changes. On the other -hand, take the following JavaScript code snippet, that runs when the \l Rectangle is created: - -\code -Rectangle { - - Component.onCompleted: { - width = otherItem.width; - } -} -\endcode - -The \c width of this \l Rectangle is \e assigned the value of \c otherItem.width using the -\e {property = value} syntax in JavaScript. Unlike the QML \e {property: value} syntax, this -does not invoke QML property binding; the \c rectangle.width property is set to the value -of \c otherItem.width at the time of the assignment and will not be updated if that value -changes. - -See \l {Property Binding} for more information. +See \l {Binding Properties from JavaScript} for details. \section1 Receiving QML Signals in JavaScript @@ -315,10 +290,13 @@ This restriction exists as the QML environment is not yet fully established. To run code after the environment setup has completed, refer to \l {Running JavaScript at Startup}. -\o The value of \c this is currently undefined in QML +\o The value of \c this is currently undefined in QML in the majority of contexts + +The \c this keyword is supported when \l {Binding Properties from JavaScript} +{binding properties from JavaScript}. In all other situations, the value of +\c this is undefined in QML. -The value of \c this is undefined in QML. To refer to any element, provide -an \c id. For example: +To refer to any element, provide an \c id. For example: \qml Item { diff --git a/doc/src/declarative/propertybinding.qdoc b/doc/src/declarative/propertybinding.qdoc index 2fa95d4..379a4ec 100644 --- a/doc/src/declarative/propertybinding.qdoc +++ b/doc/src/declarative/propertybinding.qdoc @@ -95,44 +95,109 @@ Rectangle { \endcode -\section1 Effects of Property Assignment in JavaScript +\section1 Binding Properties from JavaScript -Assigning a property value from JavaScript does \e not create a property binding. -For example: +When working with both QML and JavaScript, it is important to differentiate between +\l {Property Binding} syntax in QML and simple \e {property assignment} in JavaScript. Take +the example below, which uses property binding to ensure the item's \c height is always twice +its \c width: + +\qml +Item { + width: 100 + height: width * 2 +} +\endqml + +On the other hand, take the following JavaScript code snippet, which \e assigns, rather +than \e binds, the value of the \c height property: \code -Rectangle { +Item { + width: 100 Component.onCompleted: { - width = otherItem.width; + height = width * 2 // if width changes later, height is not updated! } } \endcode -Instead of creating a property binding, this simply sets the \c width of the \l Rectangle -to the value of \c other.width at the time the JavaScript code is invoked. See -\l {Property Assignment vs Property Binding} for more details. +Instead of creating a property binding, this simply sets the \c height property to the correct +value \e {at the time that} the JavaScript code is invoked. Unlike the first example, the +\c height will never change if \c width changes. + +The \e {property : value} syntax for property binding is QML-specific and cannot be used in +JavaScript. Instead, to bind a property from JavaScript, assign a \e function to the property +that returns the required value. The following code correctly sets the property binding +created in the first example, but creates the binding in JavaScript rather than QML: + +\qml +Item { + width: 100 + + Component.onCompleted: { + height = (function() { return width * 2 }) + } +} +\endqml + + +\section2 Using \c this to create a binding + +When creating a property binding from JavaScript, QML allows the use of the \c this keyword to +refer to the object to which the property binding will be assigned. This allows one to +explicitly refer to a property within an object when there may be ambiguity about the exact +property that should be used for the binding. -Also note that assigning a value to a property that is currently bound will remove the binding. -A property can only have one value at a time, and if any code explicitly sets -this value, the binding is removed. The \l Rectangle in the example below will have -a width of 13, regardless of the \c otherItem's width. +For example, the \c Component.onCompleted handler below is defined within the scope of the +\l Item, and references to \c width within this scope would refer to the \l Item's width, rather +than that of the \l Rectangle. To bind the \l Rectangle's \c height to its own \c width, the +function needs to explicitly refer to \c this.width rather than just \c width. Otherwise, the +height of the \l Rectangle would be bound to the width of the \l Item and not the \l Rectangle. + +\qml +Item { + width: 500 + height: 500 + + Rectangle { + id: rect + width: 100 + color: "yellow" + } + + Component.onCompleted: { + rect.height = (function() { return this.width * 2 }) + } +} +\endqml + +(In this case, the function could also have referred to \c rect.width rather than \c this.width.) + +Note that the value of \c this is not defined outside of its use in property binding. +See \l {QML JavaScript Restrictions} for details. + + +\section2 Effects of property assignment + +Note that assigning a value to a property that is currently bound will remove the binding. +A property can only have one value at a time, and if any code explicitly sets this value, the +binding is removed. In the following example, although \c width has been bound to \c height, +the binding is removed by the JavaScript code that assigns \c width to 50: \code -Rectangle { - width: otherItem.width +Item { + width: height * 2 + height: 100 Component.onCompleted: { - width = 13; + width = 50; } } \endcode -There is no way to create a property binding directly from imperative JavaScript code, -although it is possible to set up a \l Binding object (shown below). - -\section1 Binding Element +\section1 The Binding Element The implicit binding syntax shown previously is easy to use and works perfectly for most uses of bindings. In some advanced cases, it is necessary to create bindings explicitly using the diff --git a/src/declarative/qml/qdeclarativebinding.cpp b/src/declarative/qml/qdeclarativebinding.cpp index b2d0738..a5bd604 100644 --- a/src/declarative/qml/qdeclarativebinding.cpp +++ b/src/declarative/qml/qdeclarativebinding.cpp @@ -274,6 +274,13 @@ QDeclarativeBinding::QDeclarativeBinding(const QString &str, QObject *obj, QDecl setNotifyOnValueChanged(true); } +QDeclarativeBinding::QDeclarativeBinding(const QScriptValue &func, QObject *obj, QDeclarativeContextData *ctxt, QObject *parent) +: QDeclarativeExpression(ctxt, obj, func, *new QDeclarativeBindingPrivate) +{ + setParent(parent); + setNotifyOnValueChanged(true); +} + QDeclarativeBinding::~QDeclarativeBinding() { } @@ -292,6 +299,19 @@ QDeclarativeProperty QDeclarativeBinding::property() const return d->property; } +void QDeclarativeBinding::setEvaluateFlags(EvaluateFlags flags) +{ + Q_D(QDeclarativeBinding); + d->setEvaluateFlags(QDeclarativeQtScriptExpression::EvaluateFlags(static_cast(flags))); +} + +QDeclarativeBinding::EvaluateFlags QDeclarativeBinding::evaluateFlags() const +{ + Q_D(const QDeclarativeBinding); + return QDeclarativeBinding::EvaluateFlags(static_cast(d->evaluateFlags())); +} + + class QDeclarativeBindingProfiler { public: QDeclarativeBindingProfiler(QDeclarativeBinding *bind) diff --git a/src/declarative/qml/qdeclarativebinding_p.h b/src/declarative/qml/qdeclarativebinding_p.h index 19e7099..0260b95 100644 --- a/src/declarative/qml/qdeclarativebinding_p.h +++ b/src/declarative/qml/qdeclarativebinding_p.h @@ -147,14 +147,21 @@ class Q_DECLARATIVE_PRIVATE_EXPORT QDeclarativeBinding : public QDeclarativeExpr { Q_OBJECT public: + enum EvaluateFlag { RequiresThisObject = 0x01 }; + Q_DECLARE_FLAGS(EvaluateFlags, EvaluateFlag) + QDeclarativeBinding(const QString &, QObject *, QDeclarativeContext *, QObject *parent=0); QDeclarativeBinding(const QString &, QObject *, QDeclarativeContextData *, QObject *parent=0); QDeclarativeBinding(void *, QDeclarativeRefCount *, QObject *, QDeclarativeContextData *, const QString &, int, QObject *parent); + QDeclarativeBinding(const QScriptValue &, QObject *, QDeclarativeContextData *, QObject *parent=0); void setTarget(const QDeclarativeProperty &); QDeclarativeProperty property() const; + void setEvaluateFlags(EvaluateFlags flags); + EvaluateFlags evaluateFlags() const; + bool enabled() const; // Inherited from QDeclarativeAbstractBinding @@ -177,6 +184,8 @@ private: Q_DECLARE_PRIVATE(QDeclarativeBinding) }; +Q_DECLARE_OPERATORS_FOR_FLAGS(QDeclarativeBinding::EvaluateFlags) + QT_END_NAMESPACE Q_DECLARE_METATYPE(QDeclarativeBinding*) diff --git a/src/declarative/qml/qdeclarativeexpression.cpp b/src/declarative/qml/qdeclarativeexpression.cpp index 26c91a5..7a85ada 100644 --- a/src/declarative/qml/qdeclarativeexpression.cpp +++ b/src/declarative/qml/qdeclarativeexpression.cpp @@ -103,6 +103,19 @@ void QDeclarativeExpressionPrivate::init(QDeclarativeContextData *ctxt, const QS expressionFunctionValid = false; } +void QDeclarativeExpressionPrivate::init(QDeclarativeContextData *ctxt, const QScriptValue &func, + QObject *me) +{ + expression = func.toString(); + + QDeclarativeAbstractExpression::setContext(ctxt); + scopeObject = me; + + expressionFunction = func; + expressionFunctionMode = ExplicitContext; + expressionFunctionValid = true; +} + void QDeclarativeExpressionPrivate::init(QDeclarativeContextData *ctxt, void *expr, QDeclarativeRefCount *rc, QObject *me, const QString &srcUrl, int lineNumber) @@ -304,6 +317,19 @@ QDeclarativeExpression::QDeclarativeExpression(QDeclarativeContextData *ctxt, QO d->setNotifyObject(this, QDeclarativeExpression_notifyIdx); } +/*! \internal */ +QDeclarativeExpression::QDeclarativeExpression(QDeclarativeContextData *ctxt, QObject *scope, const QScriptValue &func, + QDeclarativeExpressionPrivate &dd) +: QObject(dd, 0) +{ + Q_D(QDeclarativeExpression); + d->init(ctxt, func, scope); + + if (QDeclarativeExpression_notifyIdx == -1) + QDeclarativeExpression_notifyIdx = QDeclarativeExpression::staticMetaObject.indexOfMethod("_q_notify()"); + d->setNotifyObject(this, QDeclarativeExpression_notifyIdx); +} + /*! Destroy the QDeclarativeExpression instance. */ @@ -412,6 +438,16 @@ void QDeclarativeQtScriptExpression::setNotifyObject(QObject *object, int notify } } +void QDeclarativeQtScriptExpression::setEvaluateFlags(EvaluateFlags flags) +{ + evalFlags = flags; +} + +QDeclarativeQtScriptExpression::EvaluateFlags QDeclarativeQtScriptExpression::evaluateFlags() const +{ + return evalFlags; +} + QScriptValue QDeclarativeQtScriptExpression::scriptValue(QObject *secondaryScope, bool *isUndefined) { Q_ASSERT(context() && context()->engine); @@ -476,7 +512,10 @@ QScriptValue QDeclarativeQtScriptExpression::eval(QObject *secondaryScope, bool oldOverride = ep->contextClass->setOverrideObject(expressionContext, secondaryScope); } - QScriptValue svalue = expressionFunction.call(); // This could cause this to be deleted + QScriptValue thisObject; + if (evalFlags & RequiresThisObject) + thisObject = ep->objectClass->newQObject(scopeObject); + QScriptValue svalue = expressionFunction.call(thisObject); // This could cause this c++ object to be deleted if (isShared) { ep->sharedContext = oldSharedContext; diff --git a/src/declarative/qml/qdeclarativeexpression.h b/src/declarative/qml/qdeclarativeexpression.h index 72c5947..d40094b 100644 --- a/src/declarative/qml/qdeclarativeexpression.h +++ b/src/declarative/qml/qdeclarativeexpression.h @@ -59,6 +59,7 @@ class QDeclarativeEngine; class QDeclarativeContext; class QDeclarativeExpressionPrivate; class QDeclarativeContextData; +class QScriptValue; class Q_DECLARATIVE_EXPORT QDeclarativeExpression : public QObject { Q_OBJECT @@ -94,6 +95,8 @@ Q_SIGNALS: protected: QDeclarativeExpression(QDeclarativeContextData *, QObject *, const QString &, QDeclarativeExpressionPrivate &dd); + QDeclarativeExpression(QDeclarativeContextData *, QObject *, const QScriptValue &, + QDeclarativeExpressionPrivate &dd); QDeclarativeExpression(QDeclarativeContextData *, void *, QDeclarativeRefCount *rc, QObject *me, const QString &, int, QDeclarativeExpressionPrivate &dd); diff --git a/src/declarative/qml/qdeclarativeexpression_p.h b/src/declarative/qml/qdeclarativeexpression_p.h index 1d25609..51cae0f 100644 --- a/src/declarative/qml/qdeclarativeexpression_p.h +++ b/src/declarative/qml/qdeclarativeexpression_p.h @@ -113,6 +113,9 @@ class QDeclarativeQtScriptExpression : public QDeclarativeAbstractExpression, public: enum Mode { SharedContext, ExplicitContext }; + enum EvaluateFlag { RequiresThisObject = 0x01 }; + Q_DECLARE_FLAGS(EvaluateFlags, EvaluateFlag) + QDeclarativeQtScriptExpression(); virtual ~QDeclarativeQtScriptExpression(); @@ -131,6 +134,9 @@ public: void resetNotifyOnChange(); void setNotifyObject(QObject *, int ); + void setEvaluateFlags(EvaluateFlags flags); + EvaluateFlags evaluateFlags() const; + QScriptValue scriptValue(QObject *secondaryScope, bool *isUndefined); class DeleteWatcher { @@ -157,8 +163,13 @@ private: QObject *guardObject; int guardObjectNotifyIndex; bool *deleted; + + EvaluateFlags evalFlags; }; +Q_DECLARE_OPERATORS_FOR_FLAGS(QDeclarativeQtScriptExpression::EvaluateFlags) + + class QDeclarativeExpression; class QString; class QDeclarativeExpressionPrivate : public QObjectPrivate, public QDeclarativeQtScriptExpression @@ -169,6 +180,7 @@ public: ~QDeclarativeExpressionPrivate(); void init(QDeclarativeContextData *, const QString &, QObject *); + void init(QDeclarativeContextData *, const QScriptValue &, QObject *); void init(QDeclarativeContextData *, void *, QDeclarativeRefCount *, QObject *, const QString &, int); QVariant value(QObject *secondaryScope = 0, bool *isUndefined = 0); diff --git a/src/declarative/qml/qdeclarativeobjectscriptclass.cpp b/src/declarative/qml/qdeclarativeobjectscriptclass.cpp index cbbf2b9..7701a23 100644 --- a/src/declarative/qml/qdeclarativeobjectscriptclass.cpp +++ b/src/declarative/qml/qdeclarativeobjectscriptclass.cpp @@ -359,8 +359,20 @@ void QDeclarativeObjectScriptClass::setProperty(QObject *obj, } } + QDeclarativeBinding *newBinding = 0; + if (value.isFunction() && !value.isRegExp()) { + QScriptContextInfo ctxtInfo(context); + QDeclarativePropertyCache::ValueTypeData valueTypeData; + + newBinding = new QDeclarativeBinding(value, obj, evalContext); + newBinding->setSourceLocation(ctxtInfo.fileName(), ctxtInfo.functionStartLineNumber()); + newBinding->setTarget(QDeclarativePropertyPrivate::restore(*lastData, valueTypeData, obj, evalContext)); + if (newBinding->expression().contains("this")) + newBinding->setEvaluateFlags(newBinding->evaluateFlags() | QDeclarativeBinding::RequiresThisObject); + } + QDeclarativeAbstractBinding *delBinding = - QDeclarativePropertyPrivate::setBinding(obj, lastData->coreIndex, -1, 0); + QDeclarativePropertyPrivate::setBinding(obj, lastData->coreIndex, -1, newBinding); if (delBinding) delBinding->destroy(); @@ -379,10 +391,7 @@ void QDeclarativeObjectScriptClass::setProperty(QObject *obj, QString error = QLatin1String("Cannot assign [undefined] to ") + QLatin1String(QMetaType::typeName(lastData->propType)); context->throwError(error); - } else if (!value.isRegExp() && value.isFunction()) { - QString error = QLatin1String("Cannot assign a function to a property."); - context->throwError(error); - } else { + } else if (!value.isFunction()) { QVariant v; if (lastData->flags & QDeclarativePropertyCache::Data::IsQList) v = enginePriv->scriptValueToVariant(value, qMetaTypeId >()); diff --git a/src/declarative/qml/qdeclarativeproperty.cpp b/src/declarative/qml/qdeclarativeproperty.cpp index 76829b7..61e3002 100644 --- a/src/declarative/qml/qdeclarativeproperty.cpp +++ b/src/declarative/qml/qdeclarativeproperty.cpp @@ -1480,19 +1480,28 @@ QDeclarativePropertyPrivate::restore(const QByteArray &data, QObject *object, QD if (data.isEmpty()) return prop; - prop.d = new QDeclarativePropertyPrivate; - prop.d->object = object; - prop.d->context = ctxt; - prop.d->engine = ctxt->engine; - const SerializedData *sd = (const SerializedData *)data.constData(); if (sd->isValueType) { const ValueTypeSerializedData *vt = (const ValueTypeSerializedData *)sd; - prop.d->core = vt->core; - prop.d->valueType = vt->valueType; + return restore(vt->core, vt->valueType, object, ctxt); } else { - prop.d->core = sd->core; + QDeclarativePropertyCache::ValueTypeData data; + return restore(sd->core, data, object, ctxt); } +} + +QDeclarativeProperty +QDeclarativePropertyPrivate::restore(const QDeclarativePropertyCache::Data &data, const QDeclarativePropertyCache::ValueTypeData &valueType, QObject *object, QDeclarativeContextData *ctxt) +{ + QDeclarativeProperty prop; + + prop.d = new QDeclarativePropertyPrivate; + prop.d->object = object; + prop.d->context = ctxt; + prop.d->engine = ctxt->engine; + + prop.d->core = data; + prop.d->valueType = valueType; return prop; } diff --git a/src/declarative/qml/qdeclarativeproperty_p.h b/src/declarative/qml/qdeclarativeproperty_p.h index fa0fef4..bd2c891 100644 --- a/src/declarative/qml/qdeclarativeproperty_p.h +++ b/src/declarative/qml/qdeclarativeproperty_p.h @@ -112,7 +112,12 @@ public: static QByteArray saveValueType(const QMetaObject *, int, const QMetaObject *, int); static QByteArray saveProperty(const QMetaObject *, int); + static QDeclarativeProperty restore(const QByteArray &, QObject *, QDeclarativeContextData *); + static QDeclarativeProperty restore(const QDeclarativePropertyCache::Data &, + const QDeclarativePropertyCache::ValueTypeData &, + QObject *, + QDeclarativeContextData *); static bool equal(const QMetaObject *, const QMetaObject *); static bool canConvert(const QMetaObject *from, const QMetaObject *to); diff --git a/src/declarative/qml/qdeclarativevaluetypescriptclass.cpp b/src/declarative/qml/qdeclarativevaluetypescriptclass.cpp index 558ef90..200cc1c 100644 --- a/src/declarative/qml/qdeclarativevaluetypescriptclass.cpp +++ b/src/declarative/qml/qdeclarativevaluetypescriptclass.cpp @@ -46,6 +46,8 @@ #include "private/qdeclarativeengine_p.h" #include "private/qdeclarativeguard_p.h" +#include + QT_BEGIN_NAMESPACE struct QDeclarativeValueTypeObject : public QScriptDeclarativeClass::Object { @@ -161,17 +163,41 @@ void QDeclarativeValueTypeScriptClass::setProperty(Object *obj, const Identifier if (o->objectType == QDeclarativeValueTypeObject::Reference) { QDeclarativeValueTypeReference *ref = static_cast(obj); + ref->type->read(ref->object, ref->property); + QMetaProperty p = ref->type->metaObject()->property(m_lastIndex); + + QDeclarativeBinding *newBinding = 0; + if (value.isFunction() && !value.isRegExp()) { + QDeclarativeContextData *ctxt = QDeclarativeEnginePrivate::get(engine)->getContext(context()); + + QDeclarativePropertyCache::Data cacheData; + cacheData.flags = QDeclarativePropertyCache::Data::IsWritable; + cacheData.propType = ref->object->metaObject()->property(ref->property).userType(); + cacheData.coreIndex = ref->property; + + QDeclarativePropertyCache::ValueTypeData valueTypeData; + valueTypeData.valueTypeCoreIdx = m_lastIndex; + valueTypeData.valueTypePropType = p.userType(); + + newBinding = new QDeclarativeBinding(value, ref->object, ctxt); + QScriptContextInfo ctxtInfo(context()); + newBinding->setSourceLocation(ctxtInfo.fileName(), ctxtInfo.functionStartLineNumber()); + QDeclarativeProperty prop = QDeclarativePropertyPrivate::restore(cacheData, valueTypeData, ref->object, ctxt); + newBinding->setTarget(prop); + if (newBinding->expression().contains("this")) + newBinding->setEvaluateFlags(newBinding->evaluateFlags() | QDeclarativeBinding::RequiresThisObject); + } + QDeclarativeAbstractBinding *delBinding = - QDeclarativePropertyPrivate::setBinding(ref->object, ref->property, m_lastIndex, 0); + QDeclarativePropertyPrivate::setBinding(ref->object, ref->property, m_lastIndex, newBinding); if (delBinding) delBinding->destroy(); - ref->type->read(ref->object, ref->property); - QMetaProperty p = ref->type->metaObject()->property(m_lastIndex); if (p.isEnumType() && (QMetaType::Type)v.type() == QMetaType::Double) v = v.toInt(); p.write(ref->type, v); ref->type->write(ref->object, ref->property, 0); + } else { QDeclarativeValueTypeCopy *copy = static_cast(obj); copy->type->setValue(copy->value); diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/functionAssignment.2.qml b/tests/auto/declarative/qdeclarativeecmascript/data/functionAssignment.2.qml index 948b39c..c8c926a 100644 --- a/tests/auto/declarative/qdeclarativeecmascript/data/functionAssignment.2.qml +++ b/tests/auto/declarative/qdeclarativeecmascript/data/functionAssignment.2.qml @@ -1,13 +1,73 @@ import Qt.test 1.0 +import QtQuick 1.0 + +import "functionAssignment.js" as Script MyQmlObject { property variant a - property bool runTest: false - onRunTestChanged: { + property int aNumber: 10 + + property bool assignToProperty: false + property bool assignToPropertyFromJsFile: false + + property bool assignWithThis: false + property bool assignWithThisFromJsFile: false + + property bool assignToValueType: false + + property bool assignFuncWithoutReturn: false + property bool assignWrongType: false + property bool assignWrongTypeToValueType: false + + + onAssignToPropertyChanged: { + function myFunction() { + return aNumber * 10; + } + a = myFunction; + } + + property QtObject obj: QtObject { + property int aNumber: 4212 + function myFunction() { + return this.aNumber * 10; // should use the aNumber from root, not this object + } + } + onAssignWithThisChanged: { + a = obj.myFunction; + } + + onAssignToPropertyFromJsFileChanged: { + Script.bindPropertyWithThis() + } + + onAssignWithThisFromJsFileChanged: { + Script.bindProperty() + } + + property Text text: Text { } + onAssignToValueTypeChanged: { + text.font.pixelSize = (function() { return aNumber * 10; }) + a = (function() { return text.font.pixelSize; }) + } + + + // detecting errors: + + onAssignFuncWithoutReturnChanged: { function myFunction() { - console.log("hello world"); } a = myFunction; } + onAssignWrongTypeChanged: { + function myFunction() { + return 'a string'; + } + aNumber = myFunction; + } + + onAssignWrongTypeToValueTypeChanged: { + text.font.pixelSize = (function() { return 'a string'; }) + } } diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/functionAssignment.js b/tests/auto/declarative/qdeclarativeecmascript/data/functionAssignment.js new file mode 100644 index 0000000..14daa76 --- /dev/null +++ b/tests/auto/declarative/qdeclarativeecmascript/data/functionAssignment.js @@ -0,0 +1,17 @@ +function bindProperty() +{ + a = (function(){ return aNumber * 10 }) +} + + +function TestObject() { } +TestObject.prototype.aNumber = 928349 +TestObject.prototype.bindFunction = function() { + return this.aNumber * 10 // this should not use the TestObject's aNumber +} +var testObj = new TestObject() + +function bindPropertyWithThis() +{ + a = testObj.bindFunction +} diff --git a/tests/auto/declarative/qdeclarativeecmascript/tst_qdeclarativeecmascript.cpp b/tests/auto/declarative/qdeclarativeecmascript/tst_qdeclarativeecmascript.cpp index cfe8cad..e7f9a2c 100644 --- a/tests/auto/declarative/qdeclarativeecmascript/tst_qdeclarativeecmascript.cpp +++ b/tests/auto/declarative/qdeclarativeecmascript/tst_qdeclarativeecmascript.cpp @@ -155,7 +155,10 @@ private slots: void qtcreatorbug_1289(); void noSpuriousWarningsAtShutdown(); void canAssignNullToQObject(); - void functionAssignment(); + void functionAssignment_fromBinding(); + void functionAssignment_fromJS(); + void functionAssignment_fromJS_data(); + void functionAssignmentfromJS_invalid(); void eval(); void function(); void qtbug_10696(); @@ -2514,9 +2517,8 @@ void tst_qdeclarativeecmascript::canAssignNullToQObject() } } -void tst_qdeclarativeecmascript::functionAssignment() +void tst_qdeclarativeecmascript::functionAssignment_fromBinding() { - { QDeclarativeComponent component(&engine, TEST_FILE("functionAssignment.1.qml")); QString url = component.url().toString(); @@ -2529,26 +2531,64 @@ void tst_qdeclarativeecmascript::functionAssignment() QVERIFY(!o->property("a").isValid()); delete o; - } +} - { +void tst_qdeclarativeecmascript::functionAssignment_fromJS() +{ + QFETCH(QString, triggerProperty); + + QDeclarativeComponent component(&engine, TEST_FILE("functionAssignment.2.qml")); + QVERIFY2(component.errorString().isEmpty(), qPrintable(component.errorString())); + + MyQmlObject *o = qobject_cast(component.create()); + QVERIFY(o != 0); + QVERIFY(!o->property("a").isValid()); + + o->setProperty("aNumber", QVariant(5)); + o->setProperty(triggerProperty.toUtf8().constData(), true); + QCOMPARE(o->property("a"), QVariant(50)); + + o->setProperty("aNumber", QVariant(10)); + QCOMPARE(o->property("a"), QVariant(100)); + + delete o; +} + +void tst_qdeclarativeecmascript::functionAssignment_fromJS_data() +{ + QTest::addColumn("triggerProperty"); + + QTest::newRow("assign to property") << "assignToProperty"; + QTest::newRow("assign to property, from JS file") << "assignToPropertyFromJsFile"; + + QTest::newRow("assign to value type") << "assignToValueType"; + + QTest::newRow("use 'this'") << "assignWithThis"; + QTest::newRow("use 'this' from JS file") << "assignWithThisFromJsFile"; +} + +void tst_qdeclarativeecmascript::functionAssignmentfromJS_invalid() +{ QDeclarativeComponent component(&engine, TEST_FILE("functionAssignment.2.qml")); + QVERIFY2(component.errorString().isEmpty(), qPrintable(component.errorString())); MyQmlObject *o = qobject_cast(component.create()); QVERIFY(o != 0); + QVERIFY(!o->property("a").isValid()); + o->setProperty("assignFuncWithoutReturn", true); QVERIFY(!o->property("a").isValid()); - + QString url = component.url().toString(); - QString warning = url + ":10: Error: Cannot assign a function to a property."; + QString warning = url + ":63: Unable to assign QString to int"; QTest::ignoreMessage(QtWarningMsg, warning.toLatin1().constData()); - - o->setProperty("runTest", true); - - QVERIFY(!o->property("a").isValid()); + o->setProperty("assignWrongType", true); + + warning = url + ":70: Unable to assign QString to int"; + QTest::ignoreMessage(QtWarningMsg, warning.toLatin1().constData()); + o->setProperty("assignWrongTypeToValueType", true); delete o; - } } void tst_qdeclarativeecmascript::eval() -- cgit v0.12