@@ -224,10 +224,16 @@ H5P.init = function (target) {
224224 instance . getCurrentState instanceof Function ||
225225 typeof instance . getCurrentState === 'function' ) ) {
226226
227- var saveTimer , save = function ( ) {
227+ var saveTimer , lastSavedState , save = function ( ) {
228228 var state = instance . getCurrentState ( ) ;
229- if ( state !== undefined ) {
229+
230+ // Compare the last state with current state
231+ var equalStates = H5P . isObjectEqual ( state , lastSavedState ) ;
232+
233+ // Save user data when the current state changes
234+ if ( state !== undefined && ! equalStates ) {
230235 H5P . setUserData ( contentId , 'state' , state , { deleteOnChange : true } ) ;
236+ lastSavedState = state ;
231237 }
232238 if ( H5PIntegration . saveFreq ) {
233239 // Continue autosave
@@ -1400,6 +1406,145 @@ H5P.openEmbedDialog = function ($element, embedCode, resizeCode, size, instance)
14001406 dialog . open ( ) ;
14011407} ;
14021408
1409+
1410+ /**
1411+ * Internal recursive function that determines if two objects are equivalent
1412+ *
1413+ * Returns true if equal and false otherwise
1414+ *
1415+ * @param {object } aObject First object being compared
1416+ * @param {object } bObject Second object being compared
1417+ * @param {Array } [aStack] a stack reprentation of the first objects' internal object stack
1418+ * @param {Array } [bStack] a stack reprentation of the second objects' internal object stack
1419+ */
1420+ H5P . isObjectEqual = function ( aObject , bObject , aStack , bStack ) {
1421+
1422+ // Handle circular object comparison
1423+ const pushStack = function ( ) {
1424+ aStack = aStack || [ ] ;
1425+ bStack = bStack || [ ] ;
1426+ var aLength = aStack . length ;
1427+ for ( let i = 0 ; i < aLength ; i ++ ) {
1428+ if ( aStack [ i ] === aObject ) {
1429+ return bStack [ i ] === bObject ;
1430+ }
1431+ }
1432+
1433+ aStack . push ( aObject ) ;
1434+ bStack . push ( bObject ) ;
1435+ }
1436+
1437+ if ( aObject === null || bObject === null ) {
1438+ return false ;
1439+ }
1440+
1441+ if ( aObject !== aObject ) {
1442+ return bObject !== bObject ;
1443+ }
1444+
1445+ // Compare objects via their types
1446+
1447+ var className = toString . call ( aObject ) ;
1448+ if ( className !== toString . call ( bObject ) ) {
1449+ return false ;
1450+ }
1451+
1452+ switch ( className ) {
1453+ case '[object String]' :
1454+ return '' + aObject === '' + bObject ;
1455+
1456+ case '[object Number]' :
1457+ if ( + aObject !== + aObject ) {
1458+ return + bObject !== + bObject ;
1459+ }
1460+
1461+ case '[object Boolean]' :
1462+ return + aObject === + bObject ;
1463+
1464+ case '[object Undefined]' :
1465+ return true ;
1466+
1467+ case '[object Array]' :
1468+ pushStack ( ) ;
1469+ aLength = aObject . length ;
1470+ if ( aLength !== bObject . length ) {
1471+ return false ;
1472+ }
1473+
1474+ // Compare array contents recursively
1475+ for ( let i = 0 ; i < aLength ; i ++ ) {
1476+ if ( ! H5P . isObjectEqual ( aObject [ i ] , bObject [ i ] , aStack , bStack ) ) {
1477+ return false ;
1478+ }
1479+ }
1480+ aStack . pop ( ) ;
1481+ bStack . pop ( ) ;
1482+
1483+ case '[object Object]' :
1484+ pushStack ( ) ;
1485+ if ( typeof aObject != 'object' || typeof bObject != 'object' ) {
1486+ return false ;
1487+ }
1488+ var _keys = H5P . keys ( aObject ) ;
1489+ var aLength = _keys . length ;
1490+
1491+ if ( H5P . keys ( bObject ) . length !== aLength ) {
1492+ return false ;
1493+ }
1494+
1495+ // Compare nested objects recursively
1496+ for ( let i = 0 ; i < aLength ; i ++ ) {
1497+ key = _keys [ i ] ;
1498+ if ( ! ( H5P . hasKey ( bObject , key ) && H5P . isObjectEqual ( aObject [ key ] , bObject [ key ] , aStack , bStack ) ) ) {
1499+ return false ;
1500+ }
1501+ }
1502+ aStack . pop ( ) ;
1503+ bStack . pop ( ) ;
1504+ }
1505+
1506+ return true ;
1507+ }
1508+
1509+ /**
1510+ * Helper function to determine if an object has a given key
1511+ *
1512+ * @param {object } obj
1513+ * @param {string } key
1514+ */
1515+ H5P . hasKey = function ( obj , key ) {
1516+ return obj != null && typeof obj !== 'undefined' && hasOwnProperty . call ( obj , key ) ;
1517+ }
1518+
1519+ /**
1520+ * Helper function to determine if a given variable is an object
1521+ *
1522+ * @param {object } obj
1523+ */
1524+ H5P . isObject = function ( obj ) {
1525+ var type = typeof obj ;
1526+ return type === 'function' || type === 'object' && ! ! obj ;
1527+ }
1528+
1529+ /**
1530+ * Helper function that creates an array of an objects' respective keys
1531+ *
1532+ * @param {object } obj
1533+ */
1534+ H5P . keys = function ( obj ) {
1535+
1536+ var keys = [ ] ;
1537+ if ( ! H5P . isObject ( obj ) ) {
1538+ return keys ;
1539+ }
1540+ for ( var key in obj ) {
1541+ if ( H5P . hasKey ( obj , key ) ) {
1542+ keys . push ( key ) ;
1543+ }
1544+ }
1545+ return keys ;
1546+ }
1547+
14031548/**
14041549 * Show a toast message.
14051550 *
0 commit comments