angularjs - scope changes but function is not fired - angularjs

I'm working on an angular app which needs to display some data as a chart.
The library i'm using in nvd3.js.
The chart is a directive, and the code that creates the chart is the following:
angular.module('charts', []).factory('d3', [
function() {
return d3;
}
]).factory('nv', [
function() {
return nv;
}
]).directive('hgCharts', [
'$timeout', 'd3', 'nv', function($timeout, d3, nv) {
var linkFunction;
linkFunction = function(scope, element, attr) {
var chart, chartCreated, containerHeight, containerWidth, createChart, date, svg, xScale;
containerWidth = element[0].offsetWidth;
containerHeight = element[0].offsetHeight;
date = new Date;
svg = element.find('svg');
chart = void 0;
xScale = d3.time.scale().domain([date, date]).nice(d3.time.day).range([0, containerWidth - 50]);
createChart = function() {
debugger;
chart = nv.models.multiChart();
chart.options({
showLegend: true,
duration: 0,
margin: {
'left': 20,
'right': 30,
'top': 50,
'bottom': 50
},
yDomain1: [0, 30],
showXAxis: false,
showMinMax: false,
useVoronoi: true
});
chart.xAxis.scale(xScale).orient('bottom').ticks(3).duration(0).tickFormat(d3.time.format('%H:%M'));
chart.yAxis1.orient('left').ticks(7).duration(0);
chart.tooltip.enabled(true);
nv.utils.windowResize(function() {
containerWidth = element[0].offsetWidth;
containerHeight = element[0].offsetHeight;
chart.xAxis.range([0, containerWidth - 50]);
chart.update();
return d3.selectAll(".nv-axisMaxMin-x")[0][0].remove();
});
return chart;
};
chartCreated = function() {
console.log("Scope.data is: ", scope.data);
d3.select(svg[0]).attr('width', '100%').attr('height', '100%').datum(scope.data).call(chart);
return d3.selectAll(".nv-axisMaxMin-x")[0][0].remove();
};
scope.$watch(function() {
return scope.data;
}, function() {
if (scope.data) {
console.log("Scope data changed...");
return nv.addGraph(createChart, chartCreated);
}
});
};
return {
restrict: 'E',
scope: {
data: '='
},
template: '<svg></svg>',
link: linkFunction
};
}
]);
In my UI, I have a prev and next button.
Each of the buttons above have a function in the controller to get the right start and end time period using moment.js, and once they have the right times, the call a function.
This it the code for the funciton they call:
var loadChartData;
loadChartData = function(start, end) {
if (!start) {
start = 1467331200000;
}
if (!end) {
end = 1467417599000;
}
return hgChartData.loadData(start, end).then(function(res) {
return $scope.chartData = res;
});
};
The function calls a service to load my data, and returns them as a promise (res), and then creates (or modify) $scope.chartData with the new data.
Debugging this process a lot, I noticed that the problem I've got, is that even if $scope.chartData changes (I have the right result printed on my console), the debugger inside createChart function is never called, and so the chart with my new data is not created.
Am I doing something wrong with the watcher?
Strangely, it works only the first time I click on a button (it doesn't matter if it prev or next), but then it stops (as I said above, scope.chartData changes everytime according to the console messages I print.) The code for next and prev buttons are below:
var requiredDay;
requiredDay = '';
$scope.prevDay = function() {
if (!requiredDay) {
requiredDay = moment(1467377134000);
}
requiredDay = requiredDay.clone().subtract(1, 'day');
console.log("Required data period is: ", requiredDay);
displayRequestedData(requiredDay);
return requiredDay;
};
$scope.nextDay = function() {
if (!requiredDay) {
requiredDay = moment(1467377134000);
}
requiredDay = requiredDay.clone().add(1, 'day');
console.log("Required data period is: ", requiredDay);
displayRequestedData(requiredDay);
return requiredDay;
};
I think the problem is with my watcher, but I can't figure out what i'm doing wrong.
Thanks for any help
EDIT
After debugging this a bit more, I found out that the scope is actually watched, but as soon as I changes to empty array, it's not watched anymore.
I'll explain it a bit better:
scope.data (the scope i'm watching in my directive) is equal to an array of object, and each object has this structure (taken directly from console):
Object{
color:"red"
duration:0
key:"Temperature"
originalKey:"Temperature"
type:"line"
values:Array[10]
yAxis:1
}
Each array inside the previous object look like this:
Object{
series:0
x:1467330307000
y:20
}
what I found is that if my array changes its values (array values are the only thing that changes inside that scope), the scope is updated and the chart re-rendered, but as soon as I have no data for the requested time period, the number of arrays inside my object goes to 0 and then the watcher doesn't work anymore.
I hope this helps a bit more. I'm still having issues try to understand why and how to solve it.
Thanks again

Related

AngularJS - huge directive

I have a website that has to display different set of data on a map. The map will always be the same - Some areas with 'onhover' effect and tooltip.
There is about 10 different sets of data.
I created a directive to display the map
Directive (only draw the map)
angular.module('PluvioradApp.controllers')
.directive('map', function() {
return {
restrict: 'E',
link: function(scope, element, attrs) {
var svg = d3.select(element[0])
.append("div")
.classed("svg-container", true) //container class to make it responsive
.append("svg")
//responsive SVG needs these 2 attributes and no width and height attr
.attr("preserveAspectRatio", "xMinYMin meet")
.attr("viewBox", "0 0 1000 500")
//class to make it responsive
.classed("svg-content-responsive", true);
var g = svg.append("g");
//Scale / translate / ...
var lineFunction = d3.svg.line()
.x(function(d) { return (d[0]+50000)/500; })
.y(function(d) { return (-d[1]+170000)/500; })
.interpolate("linear");
//Draw map
var path = g.selectAll('path')
.data(data.areas)
.enter().append("path")
.attr("d", function(d){ return lineFunction(d.borders)})
.attr("fill", "#D5708B")
.on('mouseover', function(d) {
d3.select(this).style('fill', 'orange');
d3.select(this).text(function(d){return "yeah";})
})
.on('mouseout', function(d) {
g.selectAll('path').style('fill', '#D5708B');
});
// zoom and pan
var zoom = d3.behavior.zoom()
.on("zoom", function() {
g.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
g.selectAll("path")
.attr("d", function(d){ return lineFunction(d.borders)});
});
svg.call(zoom);
}
}
});
My idea was to get the data to display from the controller (it comes from an API) and send it to the directive. Inside the above directive I will add a big switch or multiple if to display the correct data with the correct colors, size,...
I am sure that there is another way to split the work.
For example :
1st directive to display the map. It can be reuse multiple time
2nd directive to display set 1
3rd directive to display set 2
If this is the correct way, how can I achieve this ?
Additional information
I have a menu with a dropdown to select which data will be displayed. For the moment, the items redirect to a page containing the map directive above
Have a folder where you will have a bunch of service, where each service will be one of your data set.
Set1Service, Set2Service. etc.
Each of this will have own logic.
Have a factory service which will return one of your service.
for example:
(new FactoryService())->get('dataSetItem'); //this will return one of services from point 1.
Inject FactoryService in you directive use it.
In factory you will have the logic how to parse your data set, to determine what DataSetService you will have to return
This is extensible an easy to use.
All that I described are more related to Strategy and Factory pattern, you can read more about those and this will help you to have more abstract implementation.
angular.module('PluvioradApp.controllers')
.directive('map', function(factoryService) {
return {
restrict: 'E',
scope: {
dataSet: '='
}
link: function(scope, element, attrs) {
//all your code
var dataSetService = factoryService.get(scope.dataset);
var result = dataSetService.seed(d3);
}
}).service('factoryService', function() {
this.get = function(name) {
var service = null;
switch (name) {
case 'set1'
service = new DataSet1();
break;
case 'set2'
service = new DataSet2();
break;
}
return service;
}
});
function DataSet1() {
}
DataSet1.prototype.seed = function(d3) {
//d3 logic you have access here;
}
function DataSet2() {
}
DataSet2.prototype.seed = function(d3) {
//d3 logic you have access here;
}

Any angular example for knob-style Rotary Switch?

Hi i'm looking for an Angular.js example like this,
http://www.jqueryscript.net/form/Knob-Style-Rotary-Switch-Plugin-with-jQuery-rotarySwitch.html
Ideally a complete path from rotary switch to posting some commands to the service side, based in the switch position.
Any good example please ?
Otherwise i plan to write a angular module using this sample jquery file, every time the rotary switch moves to a new location, it's call $http.post to let service side know. The main module will use this rotary switch module. But really don't want to write my own.
Like mentioned in the comments I also think that there is probably no directive for that rotary switch plugin.
So something like in the demo below should work for this. I've just created a directive where I've initialized the jquery plugin inside the link method (dom is ready to manipulate inside postLink).
To reduce http traffic I've added a timeout so that the callback is called at a lower rate. (see rotKnob_delayedCallback in DEFAULT_OPTS)
You could write a directive for every jQuery plugin where you can't find a wrapper directive.
Please also have a look at the browser console. In jsfiddle you can see the post echo of the position in the console.
Here is also the fiddle to the demo below.
angular.module('demoApp', ['rotaryKnobModule'])
.factory('knobService', KnobService)
.controller('mainController', MainController)
angular.module('rotaryKnobModule', [])
.constant('DEFAULT_OPTS', {
// Minimal value
minimum: 0,
// Maximum value
maximum: 12,
// Step size
step: 1,
// Snap to steps in motion
snapInMotion: true,
// Start point in deg
beginDeg: 0,
// Length in deg
lengthDeg: 360,
// // Which value will used, if the the start and the end point at the same deg.
minimumOverMaximum: true,
// Show input element
showInput: false,
// Show deg marks
showMarks: false,
// Theme class
themeClass: 'defaultTheme',
// custom option for directive
rotKnb_delayedCallback: 500 // in ms
})
.directive('rotaryKnob', RotaryKnob);
function MainController(knobService, $timeout) {
var vm = this;
vm.position = 0;
vm.options = {
minimum: 0,
maximum: 100
};
vm.onChange = function(pos) {
console.log('current position change handler', pos);
knobService.postPos(pos).then(function(response){
console.log('success', response)
}, function(response) {
console.log('failed');
});
}
}
function KnobService($http) {
return {
postPos: function(pos) {
var data = $.param({
json: JSON.stringify({
position: pos
})
});
return $http.post('/echo/json/', data);
}
}
}
function RotaryKnob($timeout) {
return {
scope: {},
bindToController: {
pos: '=',
options: '=?',
onChange: '&?'
},
controllerAs: 'rotKnobCtrl',
template: '<input type="text" class="rotarySwitch"/>',
link: function(scope, element, attrs) {
var options = scope.rotKnobCtrl.options,
ctrl = scope.rotKnobCtrl,
pos = 0, changeTimeout;
//console.log(options);
var $rotKnob = $(element).rotaryswitch(options);
$rotKnob.on('change', function() {
pos = parseInt($(this).val());
scope.rotKnobCtrl.pos = pos;
if ( !changeTimeout ) // reduce update rate to server
changeTimeout = $timeout(function() {
ctrl.onChange({pos: pos});
changeTimeout = null;
}, options.rotKnb_delayedCallback);
scope.$apply();
})
},
controller: function(DEFAULT_OPTS) {
var self = this;
self.pos = 0;
//console.log(self.options);
self.options = angular.extend({}, DEFAULT_OPTS, self.options);
}
}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<link href="https://cdn.rawgit.com/r12r/com.redwhitesilver.rotarySwitch/master/jquery.rotaryswitch.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.4/angular.js"></script>
<script src="https://cdn.rawgit.com/r12r/com.redwhitesilver.rotarySwitch/master/jquery.rotaryswitch.js"></script>
<div ng-app="demoApp" ng-controller="mainController as mainCtrl">
<rotary-knob
pos="mainCtrl.position"
options="mainCtrl.options"
on-change="mainCtrl.onChange(pos)"
></rotary-knob>
Current position {{mainCtrl.position}}
</div>

How to properly unit test directives with DOM manipulation?

Before asking my real question, I have a different one... Does it make sense to unit test DOM manipulation in Angular directives?
For instance, here's my complete linking function:
function linkFn(scope, element) {
var ribbon = element[0];
var nav = ribbon.children[0];
scope.ctrl.ribbonItemClick = function (index) {
var itemOffsetLeft;
var itemOffsetRight;
var item;
if (scope.ctrl.model.selectedIndex === index) {
return;
}
scope.ctrl.model.selectedIndex = index;
item = nav.querySelectorAll('.item')[index];
itemOffsetLeft = item.offsetLeft - ribbon.offsetLeft;
itemOffsetRight = itemOffsetLeft + item.clientWidth;
if (itemOffsetLeft < nav.scrollLeft) {
nav.scrollLeft = itemOffsetLeft - MAGIC_PADDING;
}
if(itemOffsetRight > nav.clientWidth + nav.scrollLeft) {
nav.scrollLeft = itemOffsetRight - nav.clientWidth + MAGIC_PADDING;
}
this.itemClick({
item: scope.ctrl.model.items[index],
index: index
});
$location.path(scope.ctrl.model.items[index].href);
};
$timeout(function $timeout() {
var item = nav.querySelector('.item.selected');
nav.scrollLeft = item.offsetLeft - ribbon.offsetLeft - MAGIC_PADDING;
});
}
This is for a scrollable tabbed component and I have no idea how to test the 3 instances of nav.scrollLeft = x.
The first two if statements happen when an item - which is only partially visible - is clicked. The left/right (each if) item will be snapped to the left/right border of the component.
The third one, is to place the selected item in view if it's not visible when the component is loaded.
How do I unit test this with Karma/Jasmine. does it even make sense to do it or should I do functional tests with Protractor instead?
When testing directives, look for things that set or return explicit values. These are generally easy to assert and it makes sense to unit test them with Jasmine and Karma.
Take a look at Angular's tests for ng-src. Here, they test that the directive works by asserting that the src attribute on the element gets set to the right values. It is explicit: either the src attribute has a specific value or it does not.
it('should not result empty string in img src', inject(function($rootScope, $compile) {
$rootScope.image = {};
element = $compile('<img ng-src="{{image.url}}">')($rootScope);
$rootScope.$digest();
expect(element.attr('src')).not.toBe('');
expect(element.attr('src')).toBe(undefined);
}));
The same with ng-bind. Here, they pass a string of HTML with to the $compiler and then assert that the return value has had its HTML populated with actual scope values. Again, it is explicit.
it('should set text', inject(function($rootScope, $compile) {
element = $compile('<div ng-bind="a"></div>')($rootScope);
expect(element.text()).toEqual('');
$rootScope.a = 'misko';
$rootScope.$digest();
expect(element.hasClass('ng-binding')).toEqual(true);
expect(element.text()).toEqual('misko');
}));
When you get into more complicated scenarios like testing against viewport visibility or testing whether specific elements are positioned in the right places on the page, you could try to test that CSS and style attributes get set properly, but that gets fiddly real quick and is not recommended. At this point you should be looking at Protractor or a similar e2e testing tool.
I would 100% want to test all the paths of your directive even if it isn't the easiest thing. But there are approaches you can take to make this process simpler.
Break Complicated Logic into Service
The first thing that stands out to me is the complicated piece of logic about setting the nav scrollLeft. Why not break this into a separate service that can be unit tested on its own?
app.factory('AutoNavScroller', function() {
var MAGIC_PADDING;
MAGIC_PADDING = 25;
return function(extraOffsetLeft) {
this.getScrollPosition = function(item, nav) {
var itemOffsetLeft, itemOffsetRight;
itemOffsetLeft = item.offsetLeft - extraOffsetLeft;
itemOffsetRight = itemOffsetLeft + item.clientWidth;
if ( !!nav && itemOffsetRight > nav.clientWidth + nav.scrollLeft) {
return itemOffsetRight - nav.clientWidth + MAGIC_PADDING;
} else {
return itemOffsetLeft - MAGIC_PADDING;
}
};
}
});
This makes it much easier to test all the paths and refactor (which you can see I was able to do above. The tests can be seen below:
describe('AutoNavScroller', function() {
var AutoNavScroller;
beforeEach(module('app'));
beforeEach(inject(function(_AutoNavScroller_) {
AutoNavScroller = _AutoNavScroller_;
}));
describe('#getScrollPosition', function() {
var scroller, item;
function getScrollPosition(nav) {
return scroller.getScrollPosition(item, nav);
}
beforeEach(function() {
scroller = new AutoNavScroller(50);
item = {
offsetLeft: 100
};
})
describe('with setting initial position', function() {
it('gets the initial scroll position', function() {
expect(getScrollPosition()).toEqual(25);
});
});
describe('with item offset left of the nav scroll left', function() {
it('gets the scroll position', function() {
expect(getScrollPosition({
scrollLeft: 100
})).toEqual(25);
});
});
describe('with item offset right of the nav width and scroll left', function() {
beforeEach(function() {
item.clientWidth = 300;
});
it('gets the scroll position', function() {
expect(getScrollPosition({
scrollLeft: 25,
clientWidth: 50
})).toEqual(325);
});
});
});
});
Test That Directive is Calling Service
Now that we've broken up our directive, we can just inject the service and make sure it is called correctly.
app.directive('ribbonNav', function(AutoNavScroller, $timeout) {
return {
link: function(scope, element) {
var navScroller;
var ribbon = element[0];
var nav = ribbon.children[0];
// Assuming ribbon offsetLeft remains the same
navScroller = new AutoNavScroller(ribbon.offsetLeft);
scope.ctrl.ribbonItemClick = function (index) {
if (scope.ctrl.model.selectedIndex === index) {
return;
}
scope.ctrl.model.selectedIndex = index;
item = nav.querySelectorAll('.item')[index];
nav.scrollLeft = navScroller.getScrollLeft(item, nav);
// ...rest of directive
};
$timeout(function $timeout() {
var item = nav.querySelector('.item.selected');
// Sets initial nav scroll left
nav.scrollLeft = navScroller.getScrollLeft(item);
});
}
}
});
The easiest way to make sure our directive keeps using the service, is to just spy on the methods that it will call and make sure they are receiving the correct parameters:
describe('ribbonNav', function() {
var $compile, $el, $scope, AutoNavScroller;
function createRibbonNav() {
$el = $compile($el)($scope);
angular.element(document)
$scope.$digest();
document.body.appendChild($el[0]);
}
beforeEach(module('app'));
beforeEach(module(function ($provide) {
AutoNavScroller = jasmine.createSpy();
AutoNavScroller.prototype.getScrollLeft = function(item, nav) {
return !nav ? 50 : 100;
};
spyOn(AutoNavScroller.prototype, 'getScrollLeft').and.callThrough();
$provide.provider('AutoNavScroller', function () {
this.$get = function () {
return AutoNavScroller;
}
});
}));
beforeEach(inject(function(_$compile_, $rootScope) {
$compile = _$compile_;
$el = "<div id='ribbon_nav' ribbon-nav><div style='width:50px;overflow:scroll;float:left;'><div class='item selected' style='height:100px;width:200px;float:left;'>An Item</div><div class='item' style='height:100px;width:200px;float:left;'>An Item</div></div></div>";
$scope = $rootScope.$new()
$scope.ctrl = {
model: {
selectedIndex: 0
}
};
createRibbonNav();
}));
afterEach(function() {
document.getElementById('ribbon_nav').remove();
});
describe('on link', function() {
it('calls AutoNavScroller with selected item', inject(function($timeout) {
expect(AutoNavScroller).toHaveBeenCalledWith(0);
}));
it('calls AutoNavScroller with selected item', inject(function($timeout) {
$timeout.flush();
expect(AutoNavScroller.prototype.getScrollLeft)
.toHaveBeenCalledWith($el[0].children[0].children[0]);
}));
it('sets the initial nav scrollLeft', inject(function($timeout) {
$timeout.flush();
expect($el[0].children[0].scrollLeft).toEqual(50);
}));
});
describe('ribbonItemClick', function() {
beforeEach(function() {
$scope.ctrl.ribbonItemClick(1);
});
it('calls AutoNavScroller with item', inject(function($timeout) {
expect(AutoNavScroller.prototype.getScrollLeft)
.toHaveBeenCalledWith($el[0].children[0].children[1], $el[0].children[0]);
}));
it('sets the nav scrollLeft', function() {
expect($el[0].children[0].scrollLeft).toEqual(100);
});
});
});
Now, obviously these specs can be refactored a 100 ways but you can see that a higher coverage is much easier to achieve once we started breaking out the complicated logic. There are some risks around mocking objects too much because it can make your tests brittle but I believe the tradeoff is worth it here. Plus I can definitely see that AutoNavScroller being generalized and reused elsewhere. That would not have been possible if the code existed in the directive before.
Conclusion
Anyways, the reason why I believe Angular is great is the ability to test these directives and how they interact with the DOM. These jasmine specs can be run in any browser and will quickly surface inconsistencies or regressions.
Also, here is a plunkr so you can see all the moving pieces and experiment: http://plnkr.co/edit/wvj4TmmJtxTG0KW7v9rn?p=preview

Refreshing FullCalendar upon saving events with Angular Resource $save

I'm building a calendar inspired by this example http://plnkr.co/edit/pIDltQRV6TQGD4KQYnj7?p=preview and adding $save to add new events using RESTful server connection.
I'm trying to find a way to make the calendar show the new events when they are $saved without manually refreshing the browser.
My attempt to get this to work was to add (or remove) the event data to the event array (gDataService.events). Although it does change the content of the array, the change is not shown in the calendar. (e.g., if I change the date of the event, the event won't move to the new date.)
Can someone point me in the right direction? Thanks!
HTML
<div ui-calendar="uiConfig.calendar" class="span8 calendar" ng-model="eventSources"></div>
Controller1 ... This saves new event.
$scope.ok = function () {
$scope.entry = new calFactory();
$scope.entry.data = data
$scope.entry.$save( function(){
// data saved. do something here.
toaster.pop('success','Message','Update successfully completed.');
});
};
Controller2 ... Main controller that defines eventSource
myApp.controller("MainCtrl", function($scope,$compile,uiCalendarConfig, calFactory,eventFactory, gDataService) {
gDataService.events = function(start, end, callback) {
var d = new Date(start);
var events;
events = calFactory.query({
start: start,
end: end
});
events.$promise.then(function(value){
gDataService.events = events;
//have to call the callback as well to keep the calendar happy
callback(gDataService.events);
$scope.statusTxt = $scope.statusTxt + " ... Event loading completed at " + moment(new Date()).format("HH:mm:ss");
}
);
};
/* event sources array*/
$scope.eventSources = [gDataService.events]; /*, $scope.eventSource, $scope.eventsF*/
})
Factory
myApp.factory("calFactory",['$resource','$filter', function($resource, $filter) {
return $resource("/griddata/", {}, {
get: {
method: 'GET'
},
save: {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'},
transformRequest: function(data, headersGetter) {
data = angular.toJson(data);
data = $.parseJSON(data);
return $.param(data.data);
}
}
});
}]);
gDataService ... This stores event data and make it available to other parts of the program
myApp.factory("gDataService", function ($rootScope, calFactory) {
var service = {};
service.events = [];
service.addData = function(object, no_broadcast) {
this.events.push({
__id: object.task_id, title: object.task, start: object.duedates,
backgroundColor: bColor, textColor: bTextColor, borderColor: bColor
});
if (!no_broadcast) {$rootScope.$broadcast("gDataUpdated")};
};
service.removeData = function(task_id, no_broadcast) {
var arr_index = _.findIndex(this.events, {'__id': task_id});
this.events.splice(arr_index, 1);
if (!no_broadcast) {$rootScope.$broadcast("gDataUpdated")};
};
return service;
});
Nobody would answer to this much sophisticated question.
You have some typos:
Missed closing with ";", "]"...
What's inside 'function calFactory'? What is '$broadcast'?
Why do you put '$' leading the JavaScript object? Is this meaning "private var"?
"if (!no_broadcast) ..." is not coding, but commenting.
In "$scope.entry.$save( function(){",
Why entry doesn't have '$', but scope and save have it?
Here is the answer from chris-rock https://github.com/angular-ui/ui-calendar/issues/200.
Changed scope.init in calendar.js like this:
scope.init = function(){
calendar.fullCalendar(options);
window.calendar = calendar; /// This is the key
};
Now I can add or remove events dynamically using window.calendar.fullCalendar('removeEvents' or 'renderEvent')!!
Here is how I changed my code.
gDataService
service.addData = function(object, no_broadcast) {
//add additional project
this.added_event = {
__id: object.task_id, title: object.task, start: object.duedates,
backgroundColor: bColor, textColor: bTextColor, borderColor: bColor
};
this.events.push(this.added_event);
if (!no_broadcast) {$rootScope.$broadcast("gDataUpdated")};
};
service.removeData = function(_id, no_broadcast) {
var arr_index = _.findIndex(this.events, {'_id': _id});
this.delete_id = _id;
this.events.splice(arr_index, 1);
if (!no_broadcast) {$rootScope.$broadcast("gDataUpdated")};
};
return service;
Controller
$scope.$on('gDataUpdated', function(){
if (gDataService.delete_id) {
window.calendar.fullCalendar('removeEvents',gDataService.delete_id); // This removes this event from the calendar
gDataService.delete_id = null;
};
if (gDataService.added_event) {
window.calendar.fullCalendar('renderEvent',gDataService.added_event,false); // This adds this event to the calendar
gDataService.added_event = null;
};

Unable to save data to database using dhtmlx scheduler, dataprocessor and angularjs

I found in the docs dat we can send data from the scheduler to the database using this:
var dp = new dataProcessor(url)
dp.init(myobj)
EDIT
I don't get an infinite loop anymore, when I put those lines of code inside the $watch-part of the code. But it still does not save anything to the DB.
myAppProfile.directive('dhxScheduler', function() {
return {
restrict: 'A',
scope: false,
transclude: true,
template:'<div class="dhx_cal_navline" ng-transclude></div><div class="dhx_cal_header"></div><div class="dhx_cal_data"></div>',
link:function ($scope, $element, $attrs, $controller){
//default state of the scheduler
if (!$scope.scheduler)
$scope.scheduler = {};
$scope.scheduler.mode = $scope.scheduler.mode || "month";
$scope.scheduler.date = $scope.scheduler.date || new Date();
//watch data collection, reload on changes
$scope.$watch($attrs.data, function(collection){
if(collection) {
scheduler.clearAll();
scheduler.parse(collection, "json");
//this does not cause infinite loop but does not work either
var dp = new dataProcessor("agendaController.php");
dp.init(scheduler);
}
}, true);
//watch mode and date
$scope.$watch(function(){
return $scope.scheduler.mode + $scope.scheduler.date.toString();
}, function(nv, ov) {
var mode = scheduler.getState();
if (nv.date != mode.date || nv.mode != mode.mode)
scheduler.setCurrentView($scope.scheduler.date, $scope.scheduler.mode);
}, true);
//size of scheduler
$scope.$watch(function() {
return $element[0].offsetWidth + "." + $element[0].offsetHeight;
}, function() {
scheduler.setCurrentView();
});
//styling for dhtmlx scheduler
$element.addClass("dhx_cal_container");
//init scheduler
scheduler.config.xml_date="%Y-%m-%d %H:%i";
scheduler.init($element[0], new Date(), "month");
scheduler.load("agendaController.php", "json");
//This gave infinite loop
//var dp = new dataProcessor("agendaController.php");
//dp.init(scheduler);
}
}
});
This is my controller code:
include('connections.php');
include('/db-connector/scheduler_connector.php');
$scheduler = new JSONSchedulerConnector($conn);
$scheduler->render_table("events","id","start_date,end_date,text");
The error that I get in the console is:
RangeError: Maximum call stack size exceeded at Object.dataProcessor.init
[as init_original]
can anyone help me with getting started saving events to db? Thanks!
I found the error, it was because I added connector.js and it is already included in dhtmlxscheduler.js. I removed this include and it started working.

Resources