Skip to content
This repository has been archived by the owner on Mar 28, 2023. It is now read-only.

Commit

Permalink
Merge branch 'feature/248-expose-compositions-rebase'
Browse files Browse the repository at this point in the history
* feature/248-expose-compositions-rebase:
  (#248) expose compositions from page tooling API
  • Loading branch information
x1B committed Jan 13, 2016
2 parents ff83505 + 7f211c0 commit 934cf0e
Show file tree
Hide file tree
Showing 6 changed files with 173 additions and 86 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -2,6 +2,8 @@

## Last Changes

- [#248](https://github.com/LaxarJS/laxar/issues/248): tooling: added compositions to page inspection API
+ NEW FEATURE: see ticket for details
- [#247](https://github.com/LaxarJS/laxar/issues/247): tooling: added page inspection API
+ NEW FEATURE: see ticket for details
- [#244](https://github.com/LaxarJS/laxar/issues/244): documentation: added extensive documentation on visibility events
Expand Down
145 changes: 80 additions & 65 deletions lib/loaders/page_loader.js
Expand Up @@ -87,14 +87,13 @@ define( [
return processExtends( self, page, extensionChain );
} )
.then( function() {
return processCompositions( self, page, [], page );
return processCompositions( self, page, pageName );
} )
.then( function() {
return postProcessWidgets( self, page );
return checkForDuplicateWidgetIds( self, page );
} )
.then( function() {
pageTooling.setPageDefinition( pageName, page );

pageTooling.setPageDefinition( pageName, page, pageTooling.FLAT );
return page;
} );
}
Expand Down Expand Up @@ -144,69 +143,93 @@ define( [
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////

function processCompositions( self, page, compositionChain, topPage ) {
var promise = self.q_.when();
var seenCompositionIdCount = {};
function processCompositions( self, topPage, topPageName ) {

return processNestedCompositions( topPage, null, [] );

function processNestedCompositions( page, instanceId, compositionChain ) {

object.forEach( page.areas, function( widgets ) {
/*jshint loopfunc:true*/
for( var i = widgets.length - 1; i >= 0; --i ) {
( function( widgetSpec, index ) {
if( has( widgetSpec, 'composition' ) ) {
var promise = self.q_.when();
var seenCompositionIdCount = {};

object.forEach( page.areas, function( widgets ) {
/*jshint loopfunc:true*/
for( var i = widgets.length - 1; i >= 0; --i ) {
( function( widgetSpec, index ) {
if( widgetSpec.enabled === false ) {
return;
}

var compositionName = widgetSpec.composition;
if( compositionChain.indexOf( compositionName ) !== -1 ) {
var message = 'Cycle in compositions detected: ' +
compositionChain.concat( [ compositionName ] ).join( ' -> ' );
throwError( topPage, message );
}

if( !has( widgetSpec, 'id' ) ) {
var escapedCompositionName =
widgetSpec.composition.replace( SEGMENTS_MATCHER, dashToCamelcase );
widgetSpec.id = nextId( self, escapedCompositionName );
if( has( widgetSpec, 'widget' ) ) {
if( !has( widgetSpec, 'id' ) ) {
var widgetName = widgetSpec.widget.split( '/' ).pop();
widgetSpec.id = nextId( self, widgetName.replace( SEGMENTS_MATCHER, dashToCamelcase ) );
}
return;
}

if( widgetSpec.id in seenCompositionIdCount ) {
seenCompositionIdCount[ widgetSpec.id ]++;
}
else {
seenCompositionIdCount[ widgetSpec.id ] = 1;
if( has( widgetSpec, 'composition' ) ) {
var compositionName = widgetSpec.composition;
if( compositionChain.indexOf( compositionName ) !== -1 ) {
var message = 'Cycle in compositions detected: ' +
compositionChain.concat( [ compositionName ] ).join( ' -> ' );
throwError( topPage, message );
}

if( !has( widgetSpec, 'id' ) ) {
var escapedCompositionName =
widgetSpec.composition.replace( SEGMENTS_MATCHER, dashToCamelcase );
widgetSpec.id = nextId( self, escapedCompositionName );
}

if( widgetSpec.id in seenCompositionIdCount ) {
seenCompositionIdCount[ widgetSpec.id ]++;
}
else {
seenCompositionIdCount[ widgetSpec.id ] = 1;
}

// Loading compositionUrl can be started asynchronously, but replacing the according widgets
// in the page needs to take place in order. Otherwise the order of widgets could be messed up.
promise = promise
.then( function() {
return load( self, assetUrl( self.baseUrl_, compositionName ) );
} )
.then( function( composition ) {
return prefixCompositionIds( composition, widgetSpec );
} )
.then( function( composition ) {
return processCompositionExpressions( composition, widgetSpec, throwError.bind( null, topPage ) );
} )
.then( function( composition ) {
var chain = compositionChain.concat( compositionName );
return processNestedCompositions( composition, widgetSpec.id, chain )
.then( function() {
pageTooling.setCompositionDefinition( topPageName, widgetSpec.id, composition, pageTooling.FLAT );
return composition;
} );
} )
.then( function( composition ) {
mergeCompositionAreasWithPageAreas( composition, page, widgets, index );
} );
}
} )( widgets[ i ], i );
}
} );

// Loading compositionUrl can be started asynchronously, but replacing the according widgets
// in the page needs to take place in order. Otherwise the order of widgets could be messed up.
promise = promise
.then( function() {
return load( self, assetUrl( self.baseUrl_, compositionName ) );
} )
.then( function( composition ) {
return prefixCompositionIds( composition, widgetSpec );
} )
.then( function( composition ) {
return processCompositionExpressions( composition, widgetSpec, throwError.bind( null, topPage ) );
} )
.then( function( composition ) {
var chain = compositionChain.concat( compositionName );
return processCompositions( self, composition, chain, topPage )
.then( function() {
return composition;
} );
} )
.then( function( composition ) {
mergeCompositionAreasWithPageAreas( composition, page, widgets, index );
} );
}
} )( widgets[ i ], i );
// now that all IDs have been created, we can store a copy of the page prior to composition expansion
if( page === topPage ) {
pageTooling.setPageDefinition( topPageName, page, pageTooling.COMPACT );
}
} );
else {
pageTooling.setCompositionDefinition( topPageName, instanceId, page, pageTooling.COMPACT );
}

checkForDuplicateCompositionIds( page, seenCompositionIdCount );

checkForDuplicateCompositionIds( page, seenCompositionIdCount );
return promise;
}

return promise;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -349,23 +372,15 @@ define( [
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////

function postProcessWidgets( self, page ) {
function checkForDuplicateWidgetIds( self, page ) {
var idCount = {};

object.forEach( page.areas, function( widgetList, index ) {
page.areas[ index ] = widgetList.filter( function( widgetSpec ) {
if( widgetSpec.enabled === false ) {
return false;
}

if( has( widgetSpec, 'widget' ) ) {
if( !has( widgetSpec, 'id' ) ) {
var widgetName = widgetSpec.widget.split( '/' ).pop();
widgetSpec.id = nextId( self, widgetName.replace( SEGMENTS_MATCHER, dashToCamelcase ) );
}

idCount[ widgetSpec.id ] = idCount[ widgetSpec.id ] ? idCount[ widgetSpec.id ] + 1 : 1;
}
idCount[ widgetSpec.id ] = idCount[ widgetSpec.id ] ? idCount[ widgetSpec.id ] + 1 : 1;
return true;
} );
} );
Expand Down
16 changes: 8 additions & 8 deletions lib/loaders/spec/page_loader_spec.js
Expand Up @@ -107,8 +107,8 @@ define( [

var page = resolvedSpy.calls[0].args[0];
expect( page.areas.area1[0].id ).toEqual( 'id1' );
expect( page.areas.area1[1].id ).toEqual( 'widget2-id0' );
expect( page.areas.area1[2].id ).toEqual( 'widget3-id1' );
expect( page.areas.area1[1].id ).toEqual( 'widget2-id1' );
expect( page.areas.area1[2].id ).toEqual( 'widget3-id0' );
} );

/////////////////////////////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -184,11 +184,11 @@ define( [
var page = resolvedSpy.calls[0].args[0];
expect( page.areas.area1.length ).toBe( 6 );
expect( page.areas.area1[0].id ).toEqual( 'id1' );
expect( page.areas.area1[1].id ).toEqual( 'widget2-id0' );
expect( page.areas.area1[2].id ).toEqual( 'widget3-id1' );
expect( page.areas.area1[1].id ).toEqual( 'widget2-id1' );
expect( page.areas.area1[2].id ).toEqual( 'widget3-id0' );
expect( page.areas.area1[3].id ).toEqual( 'id2' );
expect( page.areas.area1[4].id ).toEqual( 'widget2-id2' );
expect( page.areas.area1[5].id ).toEqual( 'widget3-id3' );
expect( page.areas.area1[4].id ).toEqual( 'widget2-id3' );
expect( page.areas.area1[5].id ).toEqual( 'widget3-id2' );
} );

/////////////////////////////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -296,8 +296,8 @@ define( [
expect( page.areas.area1.length ).toBe( 5 );
expect( page.areas.area1[0] ).toEqual( { widget: 'someWidgetPath1', id: 'id1' } );
expect( page.areas.area1[1] ).toEqual( { widget: 'laxarjs/test_widget', id: 'simpleComposition-id0-idx1' } );
expect( page.areas.area1[2] ).toEqual( { widget: 'laxarjs/test_widget2', id: 'testWidget2-id1' } );
expect( page.areas.area1[3] ).toEqual( { widget: 'laxarjs/test_widget2', id: 'testWidget2-id2' } );
expect( page.areas.area1[2] ).toEqual( { widget: 'laxarjs/test_widget2', id: 'testWidget2-id2' } );
expect( page.areas.area1[3] ).toEqual( { widget: 'laxarjs/test_widget2', id: 'testWidget2-id1' } );
expect( page.areas.area1[4] ).toEqual( { widget: 'someWidgetPath1', id: 'id2' } );

expect( page.areas.area2.length ).toBe( 1 );
Expand Down
2 changes: 1 addition & 1 deletion lib/loaders/spec/spec_runner.html
Expand Up @@ -14,7 +14,7 @@
<script type="text/javascript">
( function() {
// assume that we live in an application under includes/lib
var defaultDepth = 6;
var defaultDepth = 3;
var depth = parseInt( window.location.search.replace( /[?&]folderDepth=([0-9]+)/, '$1' ), 10 );
if( isNaN( depth ) ) depth = defaultDepth;
var config = 'require_config.js';
Expand Down
86 changes: 78 additions & 8 deletions lib/tooling/pages.js
Expand Up @@ -4,40 +4,73 @@
* http://laxarjs.org/license
*/
define( [
'../utilities/object'
], function( object ) {
'../utilities/object',
'../logging/log'
], function( object, log ) {
'use strict';

var enabled = false;

var currentPageInfo = {
pageReference: null,
pageDefinitions: {},
compositionDefinitions: {},
widgetDescriptors: {}
};

var listeners = [];

return {
/** Use to access the flattened page model, where compositions have been expanded. */
FLAT: 'FLAT',
/** Use to access the compact page/composition model, where compositions have not been expanded. */
COMPACT: 'COMPACT',

/** Start collecting page/composition data. */
enable: function() {
enabled = true;
},

/** Stop collecting page/composition data and clean up. */
disable: function() {
enabled = false;
currentPageInfo.pageReference = null;
currentPageInfo.widgetDescriptors = {};
cleanup();
},

/**
* Access the current page information.
* Everything is returned as a copy, so this object cannot be used to modify the host application.
* Everything is returned as a copy, sothis object cannot be used to modify the host application.
*
* @return {Object}
* the current page information, with the following properties:
* - pageDefinitions {Object} the expanded page model for each page that was visited
* - widgetDescriptors {Object} the widget descriptor for each widget that was referenced
* - pageReference {String} the reference of the current page, for use against the page definitions
* - `pageDefinitions` {Object}
* both the original as well as the expanded/flattened page model for each available page
* - `compositionDefinitions` {Object}
* both the original as well as the expanded/flattened composition model for each composition of
* any available page
* - `widgetDescriptors` {Object}
* the widget descriptor for each widget that was referenced
* - `pageReference` {String}
* the reference for the current page, to lookup page/composition definitions
*/
current: function() {
if( !enabled ) {
log.warn( 'laxar page tooling: trying to access data, but collecting it was never enabled' );
}
return object.deepClone( currentPageInfo );
},

/**
* Add a listener function to be notified whenever the page information changes.
* As a side-effect, this also automatically enables collecting page/composition data.
*
* @param {Function}
* The listener to add. Will be called with the current page information whenever that changes.
*/
addListener: function( listener ) {
enabled = true;
listeners.push( listener );
},

Expand All @@ -53,21 +86,58 @@ define( [
} );
},

////////////////////////////////////////////////////////////////////////////////////////////////////////

/** @private */
setWidgetDescriptor: function( ref, descriptor ) {
if( !enabled ) { return; }
currentPageInfo.widgetDescriptors[ ref ] = descriptor;
},

/** @private */
setPageDefinition: function( ref, page ) {
currentPageInfo.pageDefinitions[ ref ] = page;
setPageDefinition: function( ref, page, type ) {
if( !enabled ) { return; }
var definitions = currentPageInfo.pageDefinitions;
definitions[ ref ] = definitions[ ref ] || {
FLAT: null,
COMPACT: null
};
definitions[ ref ][ type ] = object.deepClone( page );
},

/** @private */
setCompositionDefinition: function( pageRef, compositionInstanceId, composition, type ) {
if( !enabled ) { return; }
var definitions = currentPageInfo.compositionDefinitions;
var definitionsByInstance = definitions[ pageRef ] = definitions[ pageRef ] || {};
definitionsByInstance[ compositionInstanceId ] = definitionsByInstance[ compositionInstanceId ] || {
FLAT: null,
COMPACT: null
};
definitionsByInstance[ compositionInstanceId ][ type ] = object.deepClone( composition );
},

/** @private */
setCurrentPage: function( ref ) {
if( !enabled ) { return; }
currentPageInfo.pageReference = ref;
listeners.forEach( function( listener ) {
listener( object.deepClone( currentPageInfo ) );
} );
cleanup();
}
};

///////////////////////////////////////////////////////////////////////////////////////////////////////////

function cleanup() {
var currentRef = currentPageInfo.pageReference;
[ 'pageDefinitions', 'compositionDefinitions' ]
.forEach( function( collection ) {
Object.keys( currentPageInfo[ collection ] )
.filter( function( ref ) { return ref !== currentRef; } )
.forEach( function( ref ) { delete currentPageInfo[ collection ][ ref ]; } );
} );
}

} );

0 comments on commit 934cf0e

Please sign in to comment.