Skip to content

Commit

Permalink
Added ability to reference the fragment / hash on a URI
Browse files Browse the repository at this point in the history
Added LWC and Apex to support it (Apex does not support reading)
  • Loading branch information
rob-baillie-ortoo committed Feb 10, 2022
1 parent bfc8c10 commit b88cb53
Show file tree
Hide file tree
Showing 2 changed files with 175 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,41 @@ describe( 'getUriFragmentAsObject', () => {

const got = UriUtils.getUriFragmentAsObject();

expect( got ).toEqual( expected );
})
it( 'will decode %20 as space in property names', () => {

location.hash = 'property%201=%22value%22';

const expected = {};
expected[ 'property 1' ] = 'value';
const got = UriUtils.getUriFragmentAsObject();

expect( got ).toEqual( expected );
})

it( 'will skip strings with no equals in them', () => {

location.hash = 'property1=%22value1%22&ignorethisbit&property2=%22value2%22';

const expected = {
property1: 'value1',
property2: 'value2'
};
const got = UriUtils.getUriFragmentAsObject();

expect( got ).toEqual( expected );
})
it( 'will skip values that do not decode properly', () => {

location.hash = 'property1=%22value1%22&invalid=%22incompleteJson&property2=%22value2%22';

const expected = {
property1: 'value1',
property2: 'value2'
};
const got = UriUtils.getUriFragmentAsObject();

expect( got ).toEqual( expected );
})
});
Expand Down Expand Up @@ -177,19 +212,83 @@ describe( 'setUriFragmentObject / getUriFragmentAsObject', () => {
});
});

describe( 'registerUriFragmentListener', () => {
describe( 'addFragmentToUri', () => {

beforeAll(() => {
window.addEventListener = jest.fn();
})
it( 'when given a uri with no fragment and an object, will encode the object and add it as the fragment', () => {

const uri = 'theBaseUri';
const fragmentObject = { param: 'value' };

const got = UriUtils.addFragmentToUri( uri, fragmentObject );

expect( got ).toBe( 'theBaseUri#param=%22value%22' );
});

it( 'when given a uri with a fragment and an object, will encode the object and add to the end of the fragment', () => {

const uri = 'theBaseUri#originalfragment';
const fragmentObject = { param: 'value' };

const got = UriUtils.addFragmentToUri( uri, fragmentObject );

expect( got ).toBe( 'theBaseUri#originalfragment&param=%22value%22' );
});

it( 'when given a uri without a fragment and an empty object, will return the uri untouched', () => {

const uri = 'theBaseUri';
const fragmentObject = {};

const got = UriUtils.addFragmentToUri( uri, fragmentObject );

expect( got ).toBe( 'theBaseUri' );
});
it( 'when given a uri without a fragment and undefined, will return the uri untouched', () => {

const uri = 'theBaseUri';

const got = UriUtils.addFragmentToUri( uri );

expect( got ).toBe( 'theBaseUri' );
});

it( 'when given a uri without a fragment and an empty object, will return the uri untouched', () => {

const uri = 'theBaseUri';
const fragmentObject = {};

const got = UriUtils.addFragmentToUri( uri, fragmentObject );

expect( got ).toBe( 'theBaseUri' );
});

it( 'when given a uri with a fragment and an empty object, will return the uri untouched', () => {

const uri = 'theBaseUri#originalfragment';
const fragmentObject = {};

const got = UriUtils.addFragmentToUri( uri, fragmentObject );

expect( got ).toBe( 'theBaseUri#originalfragment' );
});

it( 'when given a uri and a string fragment, will return the uri with the string fragment added', () => {

const uri = 'theBaseUri';
const fragment = 'astring';

const got = UriUtils.addFragmentToUri( uri, fragment );

expect( got ).toBe( 'theBaseUri#astring' );
});

it( 'will register the given function as an event listener for hashchanges on the window', () => {
it( 'when given a uri with a fragment and a string fragment, will return the uri with the string fragment added', () => {

const handler = jest.fn();
const uri = 'theBaseUri#existingFragment';
const fragment = 'astring';

const got = UriUtils.registerUriFragmentListener( handler );
const got = UriUtils.addFragmentToUri( uri, fragment );

expect( window.addEventListener ).toHaveBeenCalledTimes( 1 );
expect( window.addEventListener ).toHaveBeenCalledWith( 'hashchange', handler );
expect( got ).toBe( 'theBaseUri#existingFragment&astring' );
});
});
123 changes: 67 additions & 56 deletions framework/default/ortoo-core/default/lwc/uriUtils/uriUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,48 +10,26 @@
* Warning: Whilst it will encode Date and DateTime properties it may not do what you expect (due to timezones).
* It is advised that you explicitly format the dates prior to using them in these functions.
*/
const isObject = potentialObject => typeof potentialObject === 'object' && !Array.isArray( potentialObject );

const flatten = objectToFlatten => {

const result = {};

for ( const propertyName in objectToFlatten ) {
if ( isObject( objectToFlatten[ propertyName ] ) ) {
const flattenedChild = flatten( objectToFlatten[ propertyName ] );
for ( const childPropertyName in flattenedChild ) {

result[ propertyName + '.' + childPropertyName ] = flattenedChild[ childPropertyName ];
}
} else {
result[ propertyName ] = objectToFlatten[ propertyName ];
}
}
return result;
};

const expand = ( objectToExpand, objectToAssignTo ) => {

! objectToAssignTo && ( objectToAssignTo = {} );

for ( const propertyName in objectToExpand ) {

if ( propertyName.includes( '.' ) ) {

const parentPropertyName = propertyName.substring( 0, propertyName.indexOf( '.' ) );
const childPropertyName = propertyName.substring( propertyName.indexOf( '.' ) + 1 );
const addFragmentToUri = ( uri, object ) => {
const fragment = buildUriFragment( object );
return fragment
? ( uri.includes( '#' ) )
? uri + '&' + fragment
: uri + '#' + fragment
: uri;
}

const child = [];
child[ childPropertyName ] = objectToExpand[ propertyName ];
!objectToAssignTo[ parentPropertyName ] && ( objectToAssignTo[ parentPropertyName ] = {} );
const setUriFragmentObject = object => {
location.hash = buildUriFragment( object );
}

expand( child, objectToAssignTo[ parentPropertyName ] );
const getUriFragmentAsObject = () => {
return interpretUriFragment( location.hash );
}

} else {
objectToAssignTo[ propertyName ] = objectToExpand[ propertyName ];
}
}
return objectToAssignTo;
const registerUriFragmentListener = handler => {
window.addEventListener( 'hashchange', handler );
}

const buildUriFragment = objectToHash => {
Expand Down Expand Up @@ -89,35 +67,68 @@ const interpretUriFragment = hashToInterpret => {
hashToInterpret
.split( '&' )
.forEach( thisParameterPairString => {
const [rawParameterName,rawParameterValue] = thisParameterPairString.split( '=' );

let parameterValue = '';
try {
parameterValue = JSON.parse( decodeURIComponent( rawParameterValue ) );
} catch ( e ) {
console.error( 'Invalid parameter value in URI fragment' );
if ( thisParameterPairString.includes( '=' ) ) {
const [rawParameterName,rawParameterValue] = thisParameterPairString.split( '=' );

let parameterValue = '';
try {
parameterValue = JSON.parse( decodeURIComponent( rawParameterValue ) );
flatObject[ decodeURIComponent( rawParameterName ) ] = parameterValue;
} catch ( e ) {
console.error( 'Invalid parameter value in URI fragment' );
}
}
flatObject[ decodeURIComponent( rawParameterName ) ] = parameterValue;
});

return expand( flatObject );
}

const setUriFragmentObject = object => {
location.hash = buildUriFragment( object );
}
const flatten = objectToFlatten => {

const getUriFragmentAsObject = () => {
return interpretUriFragment( location.hash );
}
const result = {};

const registerUriFragmentListener = handler => {
window.addEventListener( 'hashchange', handler );
for ( const propertyName in objectToFlatten ) {
if ( isObject( objectToFlatten[ propertyName ] ) ) {
const flattenedChild = flatten( objectToFlatten[ propertyName ] );
for ( const childPropertyName in flattenedChild ) {

result[ propertyName + '.' + childPropertyName ] = flattenedChild[ childPropertyName ];
}
} else {
result[ propertyName ] = objectToFlatten[ propertyName ];
}
}
return result;
};

const expand = ( objectToExpand, objectToAssignTo ) => {

! objectToAssignTo && ( objectToAssignTo = {} );

for ( const propertyName in objectToExpand ) {

if ( propertyName.includes( '.' ) ) {

const parentPropertyName = propertyName.substring( 0, propertyName.indexOf( '.' ) );
const childPropertyName = propertyName.substring( propertyName.indexOf( '.' ) + 1 );

const child = [];
child[ childPropertyName ] = objectToExpand[ propertyName ];
!objectToAssignTo[ parentPropertyName ] && ( objectToAssignTo[ parentPropertyName ] = {} );

expand( child, objectToAssignTo[ parentPropertyName ] );

} else {
objectToAssignTo[ propertyName ] = objectToExpand[ propertyName ];
}
}
return objectToAssignTo;
}

const isObject = potentialObject => typeof potentialObject === 'object' && !Array.isArray( potentialObject );

export default {
buildUriFragment : buildUriFragment,
interpretUriFragment : interpretUriFragment,
addFragmentToUri : addFragmentToUri,
setUriFragmentObject : setUriFragmentObject,
getUriFragmentAsObject : getUriFragmentAsObject,
registerUriFragmentListener : registerUriFragmentListener,
Expand Down

0 comments on commit b88cb53

Please sign in to comment.