diff --git a/zeppelin-web/src/app/app.controller.js b/zeppelin-web/src/app/app.controller.js index 2c302b542d..6ff6d8e319 100644 --- a/zeppelin-web/src/app/app.controller.js +++ b/zeppelin-web/src/app/app.controller.js @@ -14,7 +14,7 @@ 'use strict'; angular.module('zeppelinWebApp').controller('MainCtrl', function($scope, $rootScope, $window) { - $rootScope.compiledScope = $scope.$new(true, $rootScope); + $rootScope.notebookScope = $scope.$new(true, $rootScope); $scope.looknfeel = 'default'; var init = function() { diff --git a/zeppelin-web/src/app/notebook/notebook.controller.js b/zeppelin-web/src/app/notebook/notebook.controller.js index e10d725308..fe938cdcf4 100644 --- a/zeppelin-web/src/app/notebook/notebook.controller.js +++ b/zeppelin-web/src/app/notebook/notebook.controller.js @@ -627,8 +627,8 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl', }; $scope.$on('angularObjectUpdate', function(event, data) { - if (data.noteId === $scope.note.id) { - var scope = $rootScope.compiledScope; + if (data.noteId === $scope.note.id && !data.paragraphId) { + var scope = $rootScope.notebookScope; var varName = data.angularObject.name; if (angular.equals(data.angularObject.object, scope[varName])) { @@ -650,7 +650,7 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl', angularObjectRegistry[varName].skipEmit = false; return; } - websocketMsgSrv.updateAngularObject($routeParams.noteId, varName, newValue, angularObjectRegistry[varName].interpreterGroupId); + websocketMsgSrv.updateAngularObject($routeParams.noteId, undefined, varName, newValue, angularObjectRegistry[varName].interpreterGroupId); }); } scope[varName] = data.angularObject.object; @@ -658,8 +658,8 @@ angular.module('zeppelinWebApp').controller('NotebookCtrl', }); $scope.$on('angularObjectRemove', function(event, data) { - if (!data.noteId || data.noteId === $scope.note.id) { - var scope = $rootScope.compiledScope; + if (!data.noteId || (data.noteId === $scope.note.id && !data.paragraphId)) { + var scope = $rootScope.notebookScope; var varName = data.name; // clear watcher diff --git a/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js b/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js index 6a70f48ee3..43715abbc6 100644 --- a/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js +++ b/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js @@ -17,10 +17,12 @@ angular.module('zeppelinWebApp') .controller('ParagraphCtrl', function($scope,$rootScope, $route, $window, $element, $routeParams, $location, $timeout, $compile, websocketMsgSrv) { - + var ANGULAR_FUNCTION_OBJECT_NAME_PREFIX = '_Z_ANGULAR_FUNC_'; $scope.paragraph = null; $scope.originalText = ''; $scope.editor = null; + var paragraphScope = $rootScope.notebookScope.$new(false, $rootScope.notebookScope); + var angularObjectRegistry = {}; var editorModes = { 'ace/mode/scala': /^%spark/, @@ -82,7 +84,7 @@ angular.module('zeppelinWebApp') try { angular.element('#p'+$scope.paragraph.id+'_angular').html($scope.paragraph.result.msg); - $compile(angular.element('#p'+$scope.paragraph.id+'_angular').contents())($rootScope.compiledScope); + $compile(angular.element('#p'+$scope.paragraph.id+'_angular').contents())(paragraphScope); } catch(err) { console.log('ANGULAR rendering error %o', err); } @@ -94,6 +96,78 @@ angular.module('zeppelinWebApp') }; + $scope.$on('angularObjectUpdate', function(event, data) { + if (data.noteId && data.paragraphId === $scope.paragraph.id) { + var scope = paragraphScope; + var varName = data.angularObject.name; + + if (angular.equals(data.angularObject.object, scope[varName])) { + // return when update has no change + return; + } + + if (!angularObjectRegistry[varName]) { + angularObjectRegistry[varName] = { + interpreterGroupId : data.interpreterGroupId, + noteId : data.noteId, + paragraphId : data.paragraphId + }; + } + + angularObjectRegistry[varName].skipEmit = true; + + if (!angularObjectRegistry[varName].clearWatcher) { + angularObjectRegistry[varName].clearWatcher = scope.$watch(varName, function(newValue, oldValue) { + if (angularObjectRegistry[varName].skipEmit) { + angularObjectRegistry[varName].skipEmit = false; + return; + } + websocketMsgSrv.updateAngularObject( + angularObjectRegistry[varName].noteId, + angularObjectRegistry[varName].paragraphId, + varName, + newValue, + angularObjectRegistry[varName].interpreterGroupId); + }); + } + scope[varName] = data.angularObject.object; + + // create proxy for AngularFunction + if (varName.startsWith(ANGULAR_FUNCTION_OBJECT_NAME_PREFIX)) { + var funcName = varName.substring((ANGULAR_FUNCTION_OBJECT_NAME_PREFIX).length); + scope[funcName] = function() { + scope[varName] = arguments; + console.log('angular function invoked %o', arguments); + }; + + console.log('angular function created %o', scope[funcName]); + } + } + }); + + + $scope.$on('angularObjectRemove', function(event, data) { + if (!data.noteId || data.noteId && data.paragraphId === $scope.paragraph.id) { + var scope = paragraphScope; + var varName = data.name; + + // clear watcher + if (angularObjectRegistry[varName]) { + angularObjectRegistry[varName].clearWatcher(); + angularObjectRegistry[varName] = undefined; + } + + // remove scope variable + scope[varName] = undefined; + + // remove proxy for AngularFunction + if (varName.startsWith(ANGULAR_FUNCTION_OBJECT_NAME_PREFIX)) { + var funcName = varName.substring((ANGULAR_FUNCTION_OBJECT_NAME_PREFIX).length); + scope[funcName] = undefined; + } + } + }); + var initializeDefault = function() { var config = $scope.paragraph.config; diff --git a/zeppelin-web/src/components/websocketEvents/websocketMsg.service.js b/zeppelin-web/src/components/websocketEvents/websocketMsg.service.js index b8f22045d8..df44010094 100644 --- a/zeppelin-web/src/components/websocketEvents/websocketMsg.service.js +++ b/zeppelin-web/src/components/websocketEvents/websocketMsg.service.js @@ -57,11 +57,12 @@ angular.module('zeppelinWebApp').service('websocketMsgSrv', function($rootScope, websocketEvents.sendNewEvent({ op: 'INSERT_PARAGRAPH', data : {index: newIndex}}); }, - updateAngularObject: function(noteId, name, value, interpreterGroupId) { + updateAngularObject: function(noteId, paragraphId, name, value, interpreterGroupId) { websocketEvents.sendNewEvent({ op: 'ANGULAR_OBJECT_UPDATED', data: { noteId: noteId, + paragraphId: paragraphId, name: name, value: value, interpreterGroupId: interpreterGroupId