Skip to content

Commit

Permalink
[INTERNAL] Core: move registries from Core to Component and Element
Browse files Browse the repository at this point in the history
Introduces a new mixin "ManagedObjectRegistry" that can be applied to a
ManagedObject subclass that introduces an ID scope (like Elements or
Components or Templates do). The mixin implements a registry of objects,
keyed by their ID. It allows to get a map object with all currently
known instances, to retrieving a single instance by its key, to execute
some callback on each instance, to filter all instances by a given
condition (predicate function) and to get the number of instances.

The change applies the mixin to Component and Element and removes the
previous implementation from the Core. Existing Core APIs redirect to
the functions provided by the mixin.

Existing workarounds to access the old internal registries in the Core
are replaced by the new APIs.

Change-Id: Ie8fab2fdf591a0cc6a9b8146ced5f18a6c7e672b
JIRA: CPOUIFPHOENIXCORE-2725
  • Loading branch information
codeworrior committed May 16, 2019
1 parent 2328598 commit 54df6ca
Show file tree
Hide file tree
Showing 26 changed files with 873 additions and 447 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,24 +26,14 @@
jQuery(function() {
sap.ui.require(
[
"jquery.sap.global"
"jquery.sap.global",
"sap/ui/core/Element"
],
function(jQuery) {
var oCore,
iAllControls = 0,
function(jQuery, Element) {
var iAllControls = 0,
iFullyTestedControls = 0,
iTestedWithoutRenderingControls = 0;

// get access to the real core object to access the control list
sap.ui.getCore().registerPlugin({
startPlugin: function(oRealCore) {
oCore = oRealCore;
},
stopPlugin: function() {
oCore = undefined;
}
});

/**
* Iterates over all loaded libraries, but also all available libraries and tries to load them and their control lists.
* @returns an object that maps each library name to an array of control names in this library
Expand All @@ -60,7 +50,7 @@
if (!mLoadedLibraries[sInfoLibName]) {
jQuery.sap.log.info("Library '" + sInfoLibName + "' is not loaded! Trying...");
try {
var oLibrary = oCore.loadLibrary(sInfoLibName);
var oLibrary = sap.ui.getCore().loadLibrary(sInfoLibName);
mLoadedLibraries[sInfoLibName] = oLibrary.controls;
jQuery.sap.log.info("Library '" + sInfoLibName + "...successfully.");
} catch (e) {
Expand All @@ -75,9 +65,9 @@
}


// gets the map of all currently registered controls from the Core
// gets a snapshot of all currently registered controls (keyed by their ID)
function getAllAliveControls() {
return jQuery.extend({}, oCore.mElements);
return Element.registry.all();
}


Expand Down
28 changes: 7 additions & 21 deletions src/sap.ui.commons/test/sap/ui/commons/qunit/Panel.qunit.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
sap.ui.define([
"sap/ui/qunit/utils/createAndAppendDiv",
"sap/ui/core/library",
"sap/ui/core/Element",
"sap/ui/commons/Panel",
"sap/ui/commons/ListBox",
"sap/ui/thirdparty/jquery",
Expand All @@ -13,6 +14,7 @@ sap.ui.define([
], function(
createAndAppendDiv,
coreLibrary,
Element,
Panel,
ListBox,
jQuery,
Expand Down Expand Up @@ -65,21 +67,6 @@ sap.ui.define([
oCtrl.addContent(oContent);
oCtrl.placeAt("uiArea1");

var oCore;
sap.ui.getCore().registerPlugin({
startPlugin: function(oCoreRef) {
oCore = oCoreRef;
}
});

function sizeof(o) {
var count = 0;
for (var i in o) { // eslint-disable-line no-unused-vars
count++;
}
return count;
}

QUnit.test("Initial Check", function(assert) {
assert.ok(oCtrl, "Panel should exist after creating");

Expand Down Expand Up @@ -404,18 +391,17 @@ sap.ui.define([
});

QUnit.test("Clone", function(assert) {
assert.ok(!!oCore, "ref to oCore is required");
oCtrl.setTitle(null);
var n = sizeof(oCore.mElements);
var n = Element.registry.size;
var oClone1 = oCtrl.clone("-0");
assert.ok(n < sizeof(oCore.mElements), "Clone 1 created");
assert.ok(n < Element.registry.size, "Clone 1 created");
oClone1.setText("Some Title");
oClone1.setTitle(new CoreTitle({text:"Some other Title"}));
oClone1.destroy();
assert.equal(sizeof(oCore.mElements), n, "Clone 1 destroyed");
assert.equal(Element.registry.size, n, "Clone 1 destroyed");
var oClone2 = oCtrl.clone("-0");
assert.ok(n < sizeof(oCore.mElements), "Clone 2 created");
assert.ok(n < Element.registry.size, "Clone 2 created");
oClone2.destroy();
assert.equal(sizeof(oCore.mElements), n, "Clone 2 destroyed");
assert.equal(Element.registry.size, n, "Clone 2 destroyed");
});
});
2 changes: 1 addition & 1 deletion src/sap.ui.core/src/sap/ui/base/ManagedObject.js
Original file line number Diff line number Diff line change
Expand Up @@ -5078,7 +5078,7 @@ sap.ui.define([
* </ol>
* This separation is necessary as the models for the bindings might be updated
* in some ManagedObject or in the Core and the order in which the objects are visited
* is not defined (order of Core.mElements)
* is not defined.
*
* @private
*/
Expand Down
185 changes: 185 additions & 0 deletions src/sap.ui.core/src/sap/ui/base/ManagedObjectRegistry.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
/*!
* ${copyright}
*/

sap.ui.define(["sap/ui/base/ManagedObject", "sap/base/Log", "sap/base/assert"],
function(ManagedObject, Log, assert) {
"use strict";

function apply(FNClass, oOptions) {

if ( typeof FNClass !== 'function' || !(FNClass.prototype instanceof ManagedObject) ) {
throw new TypeError("ManagedObjectRegistry mixin can only be applied to subclasses of sap.ui.base.ManagedObject");
}

oOptions = oOptions || {};

var fnOnDuplicate = oOptions.onDuplicate || function(sId, oldInstance, newInstance) {
var sStereotype = FNClass.getMetadata().getStereotype();
Log.error("adding object \"" + sStereotype + "\" with duplicate id '" + sId + "'");
throw new Error("Error: adding object \"" + sStereotype + "\" with duplicate id '" + sId + "'");
};

var fnOnDeregister = oOptions.onDeregister || null;

/**
* Map (object) of objects keyed by their ID.
* @private
*/
var mInstances = Object.create(null);

/**
* Number of objects in <code>mInstances</code>.
* @private
*/
var mInstancesCount = 0;

FNClass.prototype.register = function register() {
var sId = this.getId(),
old = mInstances[sId];

if ( old && old !== this ) {
fnOnDuplicate(sId, old, this);
// executes only if duplicate check succeeds
mInstancesCount--;
}

mInstances[sId] = this;
mInstancesCount++;
};

FNClass.prototype.deregister = function deregister() {
if ( mInstances[this.sId] ) {
if ( fnOnDeregister ) {
fnOnDeregister(this.sId);
}
delete mInstances[this.sId];
mInstancesCount--;
}
};

FNClass["registry"] = Object.freeze({

/*
* Returns the number of existing objects.
*
* @returns {int} Number of currently existing objects.
*/
get size() {
return mInstancesCount;
},

/*
* Return an object with all registered object instances, keyed by their ID.
*
* Each call creates a new snapshot object. Depending on the size of the UI,
* this operation therefore might be expensive. Consider to use the <code>forEach</code>
* or <code>filter</code> method instead of executing the same operations on the returned
* object.
*
* <b>Note</b>: The returned object is created by a call to <code>Object.create(null)</code>,
* so it doesn't have a prototype and therefore no <code>toString</code> method.
*
* @returns {object} Object with all elements, keyed by their ID
*/
all: function() {
var mResults = Object.create(null);
return Object.assign(mResults, mInstances);
},

/*
* Retrieves an object by its ID.
*
* @returns {sap.ui.core.Element} Object with the given ID or <code>undefined</code>
*/
get: function(id) {
assert(id == null || typeof id === "string", "id must be a string when defined");
// allow null, as this occurs frequently and it is easier to check whether there is a control in the end than
// first checking whether there is an ID and then checking for a control
return id == null ? undefined : mInstances[id];
},

/*
* Calls the given <code>callback</code> for each object.
*
* If objects are created or destroyed during the <code>forEach</code> loop, then the behavior
* is undefined. Newly added elements might or might not be visited. If an element is destroyed
* during the loop and was not visited yet, it won't be visited.
*
* <code>function callback(element, id)</code>
*
* If a <code>thisArg</code> is given, it will be provided as <code>this</code> context when calling
* <code>callback</code>. The <code>this</code> value that the implementation of <code>callback</code>
* sees, depends on the usual resolution mechanism. E.g. when <code>callback</code> was bound to some
* context object, that object wins over the given <code>thisArg</code>.
*
* @param {function(sap.ui.core.Element,sap.ui.core.ID)} callback
* Function to call for each element
* @param {Object} [thisArg=undefined]
* Context object to provide as <code>this</code> in each call of <code>callback</code>
* @throws {TypeError} If <code>callback</code> is not a function
*/
forEach: function(callback, thisArg) {
if (typeof callback !== "function") {
throw new TypeError(callback + " is not a function");
}
if ( thisArg != null ) {
callback = callback.bind(thisArg);
}
for ( var id in mInstances ) {
callback(mInstances[id], id);
}
},

/*
* Collects all elements for which the given <code>callback</code> returns a value that coerces
* to <code>true</code>.
*
* If elements are created or destroyed within the <code>callback</code>, then the behavior is
* undefined. Newly added objects might or might not be visited. If an element is destroyed
* during the filtering and was not visited yet, it won't be visited.
*
* If a <code>thisArg</code> is given, it will be provided as <code>this</code> context when calling
* <code>callback</code>. The <code>this</code> value that the implementation of <code>callback</code>
* sees, depends on the usual resolution mechanism. E.g. when <code>callback</code> was bound to some
* context object, that object wins over the given <code>thisArg</code>.
*
* This function returns an array with all elements matching the given predicate. The order of the
* elements in the array is undefined and might change between calls (over time and across different
* versions of UI5).
*
* @param {function(sap.ui.core.Element,sap.ui.core.ID)} callback
* predicate against which each element is tested
* @param {Object} thisArg
* context object to provide as <code>this</code> in each call of <code>callback</code>
* @returns {sap.ui.core.Element[]}
* Array of elements matching the predicate; order is undefined and might change in newer versions of UI5
* @throws {TypeError} If <code>callback</code> is not a function
*/
filter: function(callback, thisArg) {
if (typeof callback !== "function") {
throw new TypeError(callback + " is not a function");
}
if ( thisArg != null ) {
callback = callback.bind(thisArg);
}
var result = [],
id;
for ( id in mInstances ) {
if ( callback(mInstances[id], id) ) {
result.push(mInstances[id]);
}
}

return result;
}

});

}

return {
apply : apply
};

});

0 comments on commit 54df6ca

Please sign in to comment.