diff --git a/CHANGELOG.md b/CHANGELOG.md index 45d3012b..8b79948d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ vNext Release notes (TBD) * None ### Enhancements -* None. +* Added `indexOf()` method on `Realm.Results` and `Realm.List` that returns the index of the object in the collection. ### Bug fixes * Fix opening synced realms with a logged-in admin user. diff --git a/docs/collection.js b/docs/collection.js index d81fa882..5cb8852e 100644 --- a/docs/collection.js +++ b/docs/collection.js @@ -164,6 +164,16 @@ class Collection { */ findIndex(callback, thisArg) {} + /** + Finds the index of the given object in the collection. + * @param {Realm.Object} [object] - The object to search for in the collection. + * @throws {Error} If the argument does not belong to the realm. + * @returns {number} representing the index where the object was found, or `-1` + * if not in collection. + * @since 1.8.2 + */ + indexOf(object) {} + /** * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach Array.prototype.forEach} * @param {function} callback - Function to execute on each object in the collection. diff --git a/lib/browser/lists.js b/lib/browser/lists.js index df4e63c7..3b67de52 100644 --- a/lib/browser/lists.js +++ b/lib/browser/lists.js @@ -30,7 +30,8 @@ createMethods(List.prototype, objectTypes.LIST, [ 'filtered', 'sorted', 'snapshot', - 'isValid', + 'isValid', + 'indexOf', 'addListener', 'removeListener', 'removeAllListeners', diff --git a/lib/browser/results.js b/lib/browser/results.js index 6dd4e6b0..72abba67 100644 --- a/lib/browser/results.js +++ b/lib/browser/results.js @@ -30,6 +30,7 @@ createMethods(Results.prototype, objectTypes.RESULTS, [ 'sorted', 'snapshot', 'isValid', + 'indexOf', 'addListener', 'removeListener', 'removeAllListeners', diff --git a/lib/collection-methods.js b/lib/collection-methods.js index 24f5b791..e2632d4d 100644 --- a/lib/collection-methods.js +++ b/lib/collection-methods.js @@ -36,7 +36,6 @@ Object.defineProperty(iteratorPrototype, Symbol.iterator, { 'concat', 'join', 'slice', - 'indexOf', 'lastIndexOf', 'every', 'some', diff --git a/src/js_list.hpp b/src/js_list.hpp index 92189718..6b201cf4 100644 --- a/src/js_list.hpp +++ b/src/js_list.hpp @@ -72,7 +72,8 @@ struct ListClass : ClassDefinition, CollectionClass> { static void filtered(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); static void sorted(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); static void is_valid(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); - + static void index_of(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); + // observable static void add_listener(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); static void remove_listener(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); @@ -90,6 +91,7 @@ struct ListClass : ClassDefinition, CollectionClass> { {"filtered", wrap}, {"sorted", wrap}, {"isValid", wrap}, + {"indexOf", wrap}, {"addListener", wrap}, {"removeListener", wrap}, {"removeAllListeners", wrap}, @@ -256,7 +258,33 @@ template void ListClass::is_valid(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { return_value.set(get_internal>(this_object)->is_valid()); } - + +template +void ListClass::index_of(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { + validate_argument_count(argc, 1); + + ObjectType arg = Value::validated_to_object(ctx, arguments[0]); + if (Object::template is_instance>(ctx, arg)) { + auto object = get_internal>(arg); + if (!object->is_valid()) { + throw std::runtime_error("Object is invalid. Either it has been previously deleted or the Realm it belongs to has been closed."); + } + + auto list = get_internal>(this_object); + size_t ndx = list->find(object->row()); + + if (ndx == realm::not_found) { + return_value.set(-1); + } + else { + return_value.set((uint32_t)ndx); + } + } + else { + return_value.set(-1); + } +} + template void ListClass::add_listener(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { validate_argument_count(argc, 1); diff --git a/src/js_results.hpp b/src/js_results.hpp index 0b1158d9..894a6c81 100644 --- a/src/js_results.hpp +++ b/src/js_results.hpp @@ -74,6 +74,8 @@ struct ResultsClass : ClassDefinition, CollectionClass< static void sorted(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); static void is_valid(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); + static void index_of(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); + // observable static void add_listener(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); static void remove_listener(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); @@ -89,6 +91,7 @@ struct ResultsClass : ClassDefinition, CollectionClass< {"addListener", wrap}, {"removeListener", wrap}, {"removeAllListeners", wrap}, + {"indexOf", wrap}, }; PropertyMap const properties = { @@ -238,6 +241,38 @@ void ResultsClass::is_valid(ContextType ctx, FunctionType, ObjectType this_ob return_value.set(get_internal>(this_object)->is_valid()); } +template +void ResultsClass::index_of(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { + validate_argument_count(argc, 1); + + ObjectType arg = Value::validated_to_object(ctx, arguments[0]); + if (Object::template is_instance>(ctx, arg)) { + auto object = get_internal>(arg); + if (!object->is_valid()) { + throw std::runtime_error("Object is invalid. Either it has been previously deleted or the Realm it belongs to has been closed."); + } + + size_t ndx; + try { + auto results = get_internal>(this_object); + ndx = results->index_of(object->row()); + } + catch (realm::Results::IncorrectTableException &) { + throw std::runtime_error("Object type does not match the type contained in result"); + } + + if (ndx == realm::not_found) { + return_value.set(-1); + } + else { + return_value.set((uint32_t)ndx); + } + } + else { + return_value.set(-1); + } +} + template void ResultsClass::add_listener(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { validate_argument_count(argc, 1); diff --git a/tests/js/list-tests.js b/tests/js/list-tests.js index 9389add2..8fc71910 100644 --- a/tests/js/list-tests.js +++ b/tests/js/list-tests.js @@ -583,6 +583,7 @@ module.exports = { var index = list.findIndex(function(p) {return p.name == 'Tim'}); TestCase.assertEqual(index, 1); + TestCase.assertEqual(list.indexOf(list[index]), index); TestCase.assertEqual(list.reduce(function(n, p) {return n + p.age}, 0), 33); TestCase.assertEqual(list.reduceRight(function(n, p) {return n + p.age}, 0), 33); diff --git a/tests/js/results-tests.js b/tests/js/results-tests.js index 40d332d8..3e974910 100644 --- a/tests/js/results-tests.js +++ b/tests/js/results-tests.js @@ -383,6 +383,42 @@ module.exports = { TestCase.assertEqual(snapshot.length, 0); }); }, + + testResultsFindIndexOfObject: function() { + var realm = new Realm({schema: [schemas.TestObject]}); + + var object1, object2, object3; + realm.write(function() { + object1 = realm.create('TestObject', {doubleCol: 1}); + object2 = realm.create('TestObject', {doubleCol: 2}); + object3 = realm.create('TestObject', {doubleCol: 2}); + }); + + // Search in base table + const objects = realm.objects('TestObject'); + TestCase.assertEqual(objects.indexOf(object1), 0); + TestCase.assertEqual(objects.indexOf(object2), 1); + TestCase.assertEqual(objects.indexOf(object3), 2); + + // Search in filtered query + const results = objects.filtered("doubleCol == 2"); + TestCase.assertEqual(results.indexOf(object1), -1); + TestCase.assertEqual(results.indexOf(object2), 0); + TestCase.assertEqual(results.indexOf(object3), 1); + + const nonRealmObject = {test: "this is an object"}; + TestCase.assertEqual(objects.indexOf(nonRealmObject), -1); + + // Searching for object from the wrong realm + var realm2 = new Realm({path: '2.realm', schema: realm.schema}); + var object4; + realm2.write(function() { + object4 = realm2.create('TestObject', {doubleCol: 1}); + }); + TestCase.assertThrows(function() { + objects.indexOf(object4); + }); + }, testAddListener: function() { return new Promise((resolve, _reject) => { @@ -408,4 +444,6 @@ module.exports = { }); }) } + + };