// Copyright 2012 United States Government, as represented by the Secretary of Defense, Under
// Secretary of Defense (Personnel & Readiness).
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
// in compliance with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software distributed under the License
// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
// or implied. See the License for the specific language governing permissions and limitations under
// the License.
/// @module vwf/view/blockly
/// @requires vwf/view
define( [ "module", "vwf/view", "jquery", "vwf/model/blockly/JS-Interpreter/acorn" ], function( module, view, $, acorn ) {
var self;
var createBlocklyDivs = true;
var blocksInWorkspace = {};
var handleChangeEvents = true;
var blockIdIterator;
return view.load( module, {
// == Module Definition ====================================================================
// -- initialize ---------------------------------------------------------------------------
initialize: function( options ) {
self = this;
this.arguments = Array.prototype.slice.call( arguments );
if ( options === undefined ) { options = {}; }
this.delayedProperties = {
"blockly_toolbox": undefined,
"blockly_defaultXml": undefined,
"blockly_autoClose": undefined
};
this.options = ( options !== undefined ? options : {} );
this.options.blocklyPath = options.blocklyPath ? options.blocklyPath : './blockly/';
this.options.divParent = options.divParent ? options.divParent : 'blocklyWrapper';
this.options.divName = options.divName ? options.divName : 'blocklyDiv';
this.options.toolbox = options.toolbox ? options.toolbox : 'toolbox';
this.options.createButton = options.createButton !== undefined ? options.createButton : true;
},
createdNode: function( nodeID, childID, childExtendsID, childImplementsIDs,
childSource, childType, childIndex, childName, callback /* ( ready ) */) {
if ( childID == this.kernel.application() ) {
if ( createBlocklyDivs ) {
this.state.scenes[ childID ] = {
"toolbox": undefined,
"defaultXml": undefined
}
if ( this.options.createButton ) {
$( 'body' ).append(
"
" ).children(":last");
} else {
$( 'body' ).append(
"" ).children(":last");
}
createBlocklyDivs = false;
}
} else {
var node = this.state.nodes[ childID ];
if ( node === undefined && isBlockly3Node( childID ) ) {
this.state.nodes[ childID ] = node = this.state.createNode( nodeID, childID, childExtendsID, childImplementsIDs,
childSource, childType, childIndex, childName, callback );
}
}
},
initializedNode: function( nodeID, childID, childExtendsID, childImplementsIDs, childSource, childType, childName ) {
self = this;
if ( childID === this.kernel.application() ) {
self.kernel.setProperty( childID, "toolbox", self.options.toolbox );
Blockly.inject( document.getElementById( self.options.divName ), {
path: this.options.blocklyPath,
toolbox: document.getElementById( self.options.toolbox ),
trashcan: false,
} );
// HACK: Fix Blockly's hijacking of the backspace and delete keys in password fields
Blockly.isTargetInput_ = function ( event ){
return event.target.type === "textarea" || event.target.type === "text" || event.target.type === "password";
};
Blockly.addChangeListener( function( event ) {
if ( handleChangeEvents ) {
if ( self.state.blockly.node !== undefined ) {
var i, block;
var previousBlockIds = Object.keys( blocksInWorkspace );
var previousBlockCount = previousBlockIds.length;
var blocks = Blockly.mainWorkspace.getAllBlocks();
var blockCount = blocks.length;
var topBlockCount = Blockly.mainWorkspace.topBlocks_.length;
self.kernel.fireEvent( self.kernel.application(), "blocklyContentChanged", [ true ] );
self.kernel.setProperty( self.state.blockly.node.ID, "blockly_blockCount", blockCount );
self.kernel.setProperty( self.state.blockly.node.ID, "blockly_topBlockCount", topBlockCount );
if ( blockCount > previousBlockCount ) {
for ( i = 0; i < blocks.length; i++ ) {
block = blocks[ i ];
if ( blocksInWorkspace[ block.id ] === undefined ) {
blocksInWorkspace[ block.id ] = { "id": block.id, "type": block.type };
self.kernel.fireEvent( self.state.blockly.node.ID, "blocklyBlockAdded", [ block.id, block.type ] );
}
}
} else if ( blockCount < previousBlockCount ) {
var activeBlocks = {};
for ( i = 0; i < blocks.length; i++ ) {
activeBlocks[ blocks[ i ].id ] = blocks[ i ];
}
var blockIDsRemoved = [];
for ( var id in blocksInWorkspace ) {
if ( activeBlocks[ id ] === undefined ) {
blockIDsRemoved.push( id );
}
}
for ( i = 0; i < blockIDsRemoved.length; i++ ) {
block = blocksInWorkspace[ blockIDsRemoved[ i ] ];
self.kernel.fireEvent( self.state.blockly.node.ID, "blocklyBlockRemoved", [ block.id, block.type ] );
delete blocksInWorkspace[ blockIDsRemoved[ i ] ];
}
} else {
// Set the appropriate model properties based on this change
var xmlDom = Blockly.Xml.workspaceToDom( Blockly.getMainWorkspace() );
console.log (xmlDom);
if ( xmlDom ) {
var newXmlText = Blockly.Xml.domToText( xmlDom );
self.kernel.setProperty( self.state.blockly.node.ID, "blockly_xml",
Blockly.Xml.domToText( xmlDom ) );
}
self.kernel.setProperty( self.state.blockly.node.ID, "blockly_code",
Blockly.JavaScript.workspaceToCode() );
}
}
} else {
handleChangeEvents = true;
}
});
}
},
// -- deletedNode ------------------------------------------------------------------------------
deletedNode: function( nodeID ) {
if ( this.state.nodes[ nodeID ] !== undefined ) {
delete this.state.nodes[ nodeID ];
}
},
// -- addedChild -------------------------------------------------------------------------------
//addedChild: function( nodeID, childID, childName ) { },
// -- removedChild -----------------------------------------------------------------------------
//removedChild: function( nodeID, childID ) { },
// -- createdProperty --------------------------------------------------------------------------
createdProperty: function (nodeID, propertyName, propertyValue) {
this.satProperty(nodeID, propertyName, propertyValue);
},
// -- initializedProperty ----------------------------------------------------------------------
initializedProperty: function ( nodeID, propertyName, propertyValue ) {
this.satProperty(nodeID, propertyName, propertyValue);
},
// TODO: deletedProperty
// -- satProperty ------------------------------------------------------------------------------
satProperty: function ( nodeID, propertyName, propertyValue ) {
var node = this.state.nodes[ nodeID ];
//this.logger.infox( "S === satProperty ", nodeID, propertyName, propertyValue );
if ( propertyValue === undefined ) {
return;
}
if ( nodeID == this.kernel.application() ) {
var app = this.state.scenes[ nodeID ];
switch ( propertyName ) {
case "blockly_activeNodeID":
var newActiveNodeId = propertyValue;
var previousActiveNode = this.state.blockly.node;
var blocklyNode = this.state.nodes[ newActiveNodeId ];
// If the new node is the same as the old - exit early to prevent
// breaking synchronization.
if ( previousActiveNode === newActiveNodeId ) {
break;
}
if ( blocklyNode !== undefined ) {
var show = true;
//If there was already an active blockly node, deal with it before
//activating the new one
if ( previousActiveNode !== undefined ) {
getBlockXML( previousActiveNode );
setBlocklyUIVisibility( previousActiveNode, false );
show = ( previousActiveNode.ID !== newActiveNodeId );
this.state.blockly.node = undefined;
}
// If the new active node is different than the old,
// then we need to load its program into the toolbox
if ( show ) {
if ( blocklyNode.toolbox !== undefined ) {
loadToolbox( blocklyNode.toolbox );
} else if ( app.toolbox !== undefined ) {
loadToolbox( app.toolbox );
}
if ( blocklyNode.defaultXml !== undefined ) {
loadDefaultXml( blocklyNode.defaultXml );
} else if ( app.defaultXml !== undefined ) {
loadDefaultXml( app.defaultXml );
}
this.state.blockly.node = blocklyNode;
setBlockXML( blocklyNode );
setBlocklyUIVisibility( blocklyNode, true );
}
} else {
if ( previousActiveNode !== undefined ) {
getBlockXML( previousActiveNode );
setBlocklyUIVisibility( previousActiveNode, false );
this.state.blockly.node = undefined;
}
}
break;
case "blockly_toolbox":
app.toolbox = propertyValue;
loadToolbox( propertyValue );
break;
case "blockly_defaultXml":
app.defaultXml = propertyValue;
loadDefaultXml( propertyValue )
break;
case "blockly_autoClose":
if ( Blockly && Blockly.Toolbox && Blockly.Toolbox.flyout_ ){
Blockly.Toolbox.flyout_.autoClose = Boolean( propertyValue );
} else {
this.delayedProperties.blockly_autoClose = Boolean( propertyValue );
}
break;
}
} else if ( this.state.blockly.node && ( nodeID === this.state.blockly.node.ID ) ) {
switch ( propertyName ) {
case "blockly_xml":
var clientThatSetProperty = this.kernel.client();
var me = this.kernel.moniker();
if ( clientThatSetProperty !== me ) {
var xmlText = propertyValue;
handleChangeEvents = false;
setWorkspaceFromXmlText( xmlText, true );
}
break;
default:
break;
}
}
},
// -- gotProperty ------------------------------------------------------------------------------
// gotProperty: function ( nodeID, propertyName, propertyValue ) {
// },
// -- calledMethod -----------------------------------------------------------------------------
// calledMethod: function( nodeID, methodName, methodParameters, methodValue ) {
// },
// -- firedEvent -----------------------------------------------------------------------------
// firedEvent: function( nodeID, eventName, parameters ) {
// },
// -- ticked -----------------------------------------------------------------------------------
//ticked: function( vwfTime ) {
//},
// -- render -----------------------------------------------------------------------------------
//render: function(renderer, scene, camera) {
//}
} );
function isBlockly3Node( nodeID ) {
return self.kernel.test( nodeID,
"self::element(*,'http://vwf.example.com/blockly/controller.vwf')",
nodeID );
}
function isBlocklyNode( implementsIDs ) {
var found = false;
if ( implementsIDs ) {
for ( var i = 0; i < implementsIDs.length && !found; i++ ) {
found = ( implementsIDs[i] == "http://vwf.example.com/blockly/controller.vwf" );
}
}
return found;
}
function setBlockXML( node ) {
handleChangeEvents = false;
var xmlText = node.blocks;
var clearBeforeSet = true;
setWorkspaceFromXmlText( xmlText, clearBeforeSet );
var blocks = Blockly.mainWorkspace.getAllBlocks();
var blockCount = blocks.length;
var topBlockCount = Blockly.mainWorkspace.topBlocks_.length;
blocksInWorkspace = {};
for ( var i = 0; i < blocks.length; i++ ) {
blocksInWorkspace[ blocks[ i ].id ] = { "id": blocks[ i ].id, "type": blocks[ i ].type };
}
self.kernel.setProperty( node.ID, "blockly_blockCount", blockCount );
self.kernel.setProperty( node.ID, "blockly_topBlockCount", topBlockCount );
}
function getBlockXML( node ) {
var xml = Blockly.Xml.workspaceToDom( Blockly.getMainWorkspace() );
if ( xml ) {
node.blocks = Blockly.Xml.domToText( xml );
}
node.code = Blockly.JavaScript.workspaceToCode();
Blockly.mainWorkspace.clear();
}
function setBlocklyUIVisibility( node, show ) {
var div = document.getElementById( self.options.divParent );
div.style.visibility = show ? 'visible' : 'hidden';
div.style.pointerEvents = show ? 'all' : 'none';
if ( self.delayedProperties !== undefined ) {
for ( var prop in self.delayedProperties ) {
if ( self.delayedProperties[ prop ] !== undefined ) {
self.satProperty( self.kernel.application(), prop, self.delayedProperties[ prop ] );
}
}
self.delayedProperties = undefined;
}
self.kernel.fireEvent( node.ID, "blocklyVisibleChanged", [ show ] );
}
function loadToolbox( toolboxDef ) {
// check the 'Changing the Toolbox' section at
// https://code.google.com/p/blockly/wiki/Toolbox
// for more information on this function call
if ( Blockly && Blockly.mainWorkspace ) {
var len = toolboxDef.length;
if ( toolboxDef.indexOf( '.xml' ) === ( len - 4 ) ) {
$.ajax( {
url: toolboxDef,
type: 'GET',
dataType: 'text',
timeout: 1000,
async: false,
error: function( jqXHR, textStatus, errorThrown ) {
self.logger.errorx( "loadToolbox",
"Error loading XML document (" + textStatus + "): " + toolboxDef );
},
success: function( xml ) {
cleanUpdateToolbox( xml );
}
} );
} else if ( toolboxDef.indexOf( '