I'm trying to integrate Angular Bootstrap Calendar to my Laravel 5 project. Right now, the calendar works using the provided pre-populated demo list of events.
vm.events = [
{
title: 'An event',
type: 'warning',
startsAt: moment().startOf('week').subtract(2, 'days').add(8, 'hours').toDate(),
endsAt: moment().startOf('week').add(1, 'week').add(9, 'hours').toDate(),
draggable: true,
resizable: true
}, {
title: 'Event 2',
type: 'info',
startsAt: moment().subtract(1, 'day').toDate(),
endsAt: moment().add(5, 'days').toDate(),
draggable: true,
resizable: true
}, {
title: 'This is a really long event title that occurs on every year',
type: 'important',
startsAt: moment().startOf('day').add(7, 'hours').toDate(),
endsAt: moment().startOf('day').add(19, 'hours').toDate(),
recursOn: 'year',
draggable: true,
resizable: true
}
];
I would like to retrieve and format the events from my database like the example above, but I'm not sure how to tackle this from my controller.
On the Angular Calendar side, I've read that I can use the angular $http service to load the events, like this:
$http.get('/events').success(function(events) {
//TODO - format your array of events to match the format described in the docs
$scope.events = events; //Once formatted correctly add them to the scope variable and the calendar will update
});
Any help would be greatly appreciated
What you would want to do is create a service that takes care of all the HTTP request/response handling and have your controller consume it to get/save/update data. Something like:
// assuming that you have a REST service endpoint at /events
// create your service that will handle all HTTP interaction for the events resource
app.factory('EventsService', ['$http', function($http) {
return {
getAll: function() {
// fetch all events asynchronously
return $http.get('/events').success(function(response) {
var events = response.data;
// if you need to do any pre-processing of the events first, do it here
// pass your events to the next function in the promise chain.
return events;
}, function(err) {
// handle errors here
// pass your error object down the chain in case other error callbacks are added later on to the promise.
return err;
});
}
};
}]);
app.controller('YourController', ['$scope', 'EventsService', function($scope, EventsService) {
// call the asynchronous service method and add your promise success callback that returns your array of events to be bound to your context.
EventsService.getAll().then(function(evts) {
$scope.events = evts;
}, function(err) {
// do any additional error handling here
});
});
Related
I'm using ui-calendar to display events. To fill out the calendar model, the controller fetches the events from a Factory. The wierd part (which I can't figure out), is that when the Factory fetches the data from the API, the calendar shows the events just fine. However, in order to "speed things up" a little, the Factory saves the api fetched data in a local variable. If the Factory returns the data from the local variable, the calendar does not display the events. However if the Factory returns data from the API, the events are displayed just fine (so there must be something wrong with the way I am returning the local variable data from the Factory).
The Factory method is as follows:
function getAll() {
if (!_allEventsRequiresUpdate && _allEvents) {
var deferred = $q.defer();
deferred.resolve(_allEvents);
return deferred.promise;
}
else {
var request = $http({
method: "Get",
url: baseUrl
});
return request.then(function (response) {
_allEvents = response.data;
_allEventsRequiresUpdate = false;
return response.data;
}, handleError);
}
}
The _allEvents variable get filled when the data is fetched from the API. The data in both cases (returned from the API or the local variable), is exactly the same (at least to my knowledge), however, as stated previously, only the data fetched from the API gets rendered in ui-calendar/fullcalendar.
Any ideas? Is there something wrong as to how I am returning the local variable from the Factory?
BTW, in both cases, the controller resolves the promise.
UPDATE
The following is the method in the Angular controller that fetches the data from the Factory:
function getAllEvents() {
serviceAppointmentsServices.getAll()
.then(function (data) {
vm.events = angular.copy(data);
vm.fullCalendarEvents = [];
for (var i = 0; i < vm.events.length; i++) {
var event = {
id: vm.events[i].xrmId,
title: vm.events[i].xrmName,
start: moment(vm.events[i].startDate).tz('America/Santiago'),
end: moment(vm.events[i].endDate).tz('America/Santiago'),
stick: true
}
if (vm.events[i].xrmStatus.value == 1)
event.color = '#D2A15D';
vm.fullCalendarEvents.push(event);
}
uiCalendarConfig.calendars["calendar"].fullCalendar('removeEventSources');
uiCalendarConfig.calendars["calendar"].fullCalendar('addEventSource', vm.fullCalendarEvents);
}, function (mesage) {
toastr.error(mesage, "error!");
});
}
Here is the calendar config:
vm.uiConfig = {
calendar: {
height: 450,
editable: true,
eventClick: editEvent,
dayClick: addEvent,
eventDrop: $scope.alertOnDrop,
eventResize: $scope.alertOnResize,
eventAfterAllRender: documentReady,
locale: 'es',
timezone: 'America/Santiago',
customButtons: {
addEvents: {
text: 'nuevo',
click: function () {
vm.fx.addEvent();
$scope.$apply()
}
}
},
header: {
left: 'month basicWeek basicDay agendaWeek agendaDay',
center: 'title',
right: 'addEvents today prev,next'
},
eventRender: eventRender
}
};
I'm posting the answer in case anyone else out there gets into the same issue.
Thanks to #Javarome (https://stackoverflow.com/users/650104/javarome) in the post: Directive is being rendered before promise is resolved. I followed his suggestion and everything worked like a charm.
Summary: the issue was that the directive was getting fired before the promise resolved in the controller. So I followed his suggestion to wrap the directive in an ng-if (with the variable needed to be resolved as the trigger, and voila!!! Something like this:
<div class="container" ng-if="vm.fullCalendarEvents">
<div class="calendar" ng-model="eventSources" calendar="calendar" config="uiConfig.calendar" ng-disabled="waitingForHttp" ui-calendar="vm.uiConfig.calendar" ></div>
</div>
I can't seem to work out how to redraw my Angular-Datatable after I delete a record from my database. I don't get any errors, but the table never seems to redraw unless I manually refresh the page. I have been trying to work with many examples from the website documentation.
I have my datatable:
$scope.dtInstance = {};
$scope.selectedItems = [];
$scope.toggleItem = toggleItem;
$scope.reloadData = reloadData;
// Build the User table
$scope.dtOptions = DTOptionsBuilder
.fromFnPromise(function() {
var deferred = $q.defer();
deferred.resolve(users);
return deferred.promise;
})
.withBootstrap() // Style with Bootstrap
.withOption('responsive', true)
.withDisplayLength(15) // Show 15 items initially
.withOption('order', [0, 'asc']) // Sort by the first column
.withOption('lengthMenu', [15, 50, 100]) // Set the length menu items
.withOption('createdRow', function(row, data, dataIndex) {
// Recompiling so we can bind Angular directive to the DT
$compile(angular.element(row).contents())($scope);
})
.withOption('headerCallback', function(header) {
if (!$scope.headerCompiled) {
// Use this headerCompiled field to only compile header once
$scope.headerCompiled = true;
$compile(angular.element(header).contents())($scope);
}
})
.withOption('fnRowCallback', formatCell);
$scope.dtColumns = [
DTColumnBuilder.newColumn(null).withTitle('Username').withClass('col-md-2').renderWith(createUsernameHyperlink),
DTColumnBuilder.newColumn('Email').withTitle('Email'),
DTColumnBuilder.newColumn('Level').withTitle('Role').withClass('col-md-2'),
DTColumnBuilder.newColumn('LastConnected').withTitle('Last Accessed'),
DTColumnBuilder.newColumn('Verified').withTitle('Account Verified').withClass('col-md-2'),
DTColumnBuilder.newColumn(null).withTitle('')
.notSortable()
.renderWith(function(data, type, full, meta) {
return '<input type="checkbox" ng-click="toggleItem(' + data.Id + ')" />';
}).withClass("text-center")
];
// Reload the datatable
function reloadData() {
var resetPaging = false;
$scope.dtInstance.reloadData(callback, resetPaging);
};
function callback(json) {
console.log(json);
};
And then I have my delete function that sits in the same controller. Calling reloadData() on a successful response from the service. I can see from the console.log that it is calling the function correctly, but nothing happens.
$scope.deleteUser = function( selectedItems ) {
swal({
title: 'Are you sure?',
text: 'Are you sure you want to delete the selected account profile(s)? This process cannot be undone...',
type: 'warning',
showCancelButton: true,
confirmButtonText: 'Delete',
confirmButtonColor: "#DD6B55",
closeOnConfirm: false,
allowEscapeKey: true,
showLoaderOnConfirm: true
}, function() {
setTimeout( function() {
// Delete user
UsersService.deleteUser( selectedItems.toString() )
.then(function( data ) {
// Show a success modal
swal({
title: 'Success',
text: 'User has been deleted!',
type: 'success',
confirmButtonText: 'Close',
allowEscapeKey: false
}, function() {
reloadData(); //<== Calls the function but doesn't do anything
//$state.go('users');
});
}, function() {
// Show an error modal
swal({
title: 'Oops',
text: 'Something went wrong!',
type: 'error',
confirmButtonText: 'Close',
allowEscapeKey: true
});
});
}, 1000);
});
};
Just wondering if I have missed some step?
As suggested by #davidkonrad in a previous comment and more so from the Angular-Datatable's author, I was not reloading my content when attempting to redraw my table. Even though I was referencing my data (users) from an injected service, it was never getting updated within the controller and so my table content was never differing.
The author suggested that it is preferable to load the data from a promise that makes a HTTP request, thus allowing further calls to the promise each time the table redraws.
So instead of this:
// Build the User table
$scope.dtOptions = DTOptionsBuilder
.fromFnPromise(function() {
var deferred = $q.defer();
deferred.resolve(users);
return deferred.promise;
})
.withBootstrap() // Style with Bootstrap
I changed it to this:
// Build the User table
$scope.dtOptions = DTOptionsBuilder
.fromFnPromise(function() {
return UsersService.getUsers();
})
.withBootstrap() // Style with Bootstrap
Which now updates my table fine upon each redraw event with a call to $scope.dtInstance.reloadData();
My Github post can be found here
setTimeout function works from outside of the angular digest cycle since it's async. If you want actions you take inside a timeout to apply to the angular digest cycle you should use $timeout instead.
Another option is to use $scope.apply(), but this will just mimic the $timeout function.
Please note that you'll need to inject $timeout to your controller.
How do i get data in $scope.moduleSelected into label in the treedata_avm array instead of the hardcoded values present in the array?
app.controller('treeController',['$http','$scope','dataService',function( $http,$scope,dataService){
$http.get('WS/tree').success(function(data,status){
$scope.modules=data;
$scope.moduleSelected = $scope.modules[0].MODULE_NAME;
$scope.moduleSelectedId = $scope.modules[0].ID;
$scope.moduleSelectedParentId = $scope.modules[0].PARENT_ID;
console.log($scope.modules);
console.log($scope.moduleSelected);
console.log($scope.moduleSelectedId);
console.log($scope.moduleSelectedParentId);
}).error(function(data){
$scope.modules=data || "Request failed";
console.log("Request failed "+ $scope.modules);
});
}]);
treedata_avm:
treedata_avm = [
{
label: 'Animal',
children: [
{
label: 'Dog',
}, {
label: 'Cat',
}, {
label: 'Hippopotamus',
}, {
label: 'Chicken',
children: ['White Leghorn', 'Rhode Island Red', 'Jersey Giant']
}
]
}]
You can listen to event 'onchange' on the field, that's bound to $scope.moduleSelected and change your json when input field is changed. But I can't see, why would you need something like this?
$http with its success promise resolve function returns data in the current format: response.data
Meaning in your case you should first set
$http.get('WS/tree').success(function(response){
$scope.modules = response.data;
$scope.treedata_avm = response.data
}).error(function (){...})
If you are going to use your treedata_avm on a view, then it should be on the $scope as in $scope.treedata_avm if not then just use it as a variable inside the controller
Also my advice would be to put all of this $http logic into a provider (factory, service, provider) and then invoke it from there.
The advice is to make controllers as thin as possible and put the logic inside the providers (factories, services, providers), thus making it reausable
Hope this helps.
I have the following code Angular JS:
appService.get({id : id}).then(function (response) {
$scope.vm.events.push({title: 'New event', type: 'important', draggable: true, resizable: true});
})
This code returns response from AJAX service and puts object to array $scope.vm.events.
So, in template I dont see this added element as: {{vm.events}}
There is also one function in the same controller:
$scope.add = function (){
$scope.vm.events.push({title: 'New event', type: 'important', draggable: true, resizable: true});
}
When I call it I see new element in template: {{vm.events}}.
Why does not work code in the first case?
This is because the callback function in your service is outside the angularjs digest cycle.
For tackling this, there are two ways:
Method 1:
The first way would be to use $scope.$apply just after your callback in the service has finished as follows:
appService.get({id : id}).then(function (response) {
$scope.vm.events.push({title: 'New event', type: 'important', draggable: true, resizable: true});
$scope.$apply(); //this for updating your scope outside the digest cycle
})
Method 2:
Wrapping your service code inside a function inside the controller's scope as follows:
$scope.fn = function() {
appService.get({id : id}).then(function (response) {
$scope.vm.events.push({title: 'New event', type: 'important', draggable: true, resizable: true});
})
}
So, in this method, whenever you want to call the service just call this function. This is also the reason why your 'add' function updates the template because it is in the 'scope' of the controller and in the digest cycle.
If you are not seeing it, it is because the promise is not being resolved.
Do try putting something in the rejection handling part of the promise (on a second function within the 'then()' block):
appService.get({id : id}).then(function (response) {
$scope.vm.events.push({title: 'New event', type: 'important', draggable: true, resizable: true});
},
function(){
$scope.vm.events.push({title: 'ERROR_EVENT', type: 'ERROR_CALLBACK', draggable: true, resizable: true});
});
Like others suggested, if this is outside of angular, please call for $scope.$apply() or $scope.$digest() to trigger a digest cycle so that your data can be updated.
By adding this second function you should be able to see if the promise was being resolved or not, and if the problem lies elsewhere.
Based upon a prior SO article on injecting toastr into your app/controller. I have setup my app.js as follows:
(function () {
app = angular.module("app", ['breeze.angular']).value('ngToastr', toastr);
//added toaster as factory so it can be injected into any controller
angular.module('app').factory('ngNotifier', function (ngToastr) {
return {
notify: function (msg) {
ngToastr.success(msg);
},
notifyError: function (msg) {
ngToastr.error(msg);
},
notifyInfo: function (msg) {
ngToastr.info(msg);
}
}
});
})();
as one of the answers stated I now have access to the toastr control from any controller.
app.controller('reportController', function ($scope, reportLibraryService, ngNotifier, $log) {
//report section
var rvm = this;
rvm.getReportList = GetReportList;
rvm.onError = OnError;
rvm.onReportComplete = OnReportComplete;
$scope.userId = 1;
GetReportList($scope.userId);
function OnReportComplete(response) {
$scope.reportList = response;
ngNotifier.notify("Reports Loaded");
};
function OnError(reason) {
$scope.error = "Could not fetch the data.";
$log.error(reason);
};
function GetReportList(userId) {
$log.info("Getting reports for userid " + userId)
reportLibraryService.getAllReports($scope.userId).then(rvm.onReportComplete, rvm.onError);
};
});
The question I have is how do I override the default options? I have tried two approaches so far. First adding an toastr div within the html with the options set, which did not work. And then I tried adding them within the factory but they were ignored there as well.
angular.module('app').factory('ngNotifier', function (ngToastr) {
return {
notify: function (msg) {
ngToastr.success(msg);
ngToastr.options = {
"closeButton": false,
"debug": false,
"progressBar": false,
"positionClass": "toast-bottom-right",
"onclick": null,
"showDuration": "300",
"hideDuration": "1000",
"timeOut": "5000",
"extendedTimeOut": "1000",
"showEasing": "swing",
"hideEasing": "linear",
"showMethod": "fadeIn",
"hideMethod": "fadeOut"
}
}, ...
As a second part to this is toastr the correct tool to use or should I be using angular-toaster instead since this is an angular app? I currently do not have any jQuery dependencies anywhere else in my application.
thanks for any suggestions
For those trying to override a particular notification, rather than simply override the defaults globally, I was able to do so but with a catch. I wanted to make errors persist (set timeOut to 0) while success messages fade. So for a normal happy-path notification I just use:
toaster.success('Nothing to see here folks', 'Move along');
But for errors I want the message to persist so they can show their manager, write down the error message, whatever. This is easy with the original toastr project, you just pass a JSON object of override options as your last argument such as:
toastr.error('Original toastr example', 'Click to dismiss', {timeOut: 0});
Angularjs-toaster is different, you pass your params to the pop function.
But this did NOT work:
toaster.pop({
type: 'error',
title: 'Need More Information',
body: 'Error 42 occurred, run for the hills!',
timeOut: 0
});
I looked in the toaster.js code (I am using version 0.4.15) and it looks like you can pass ordered parameters to pop instead of a JSON object. This DID work:
toaster.pop(
'error',
'Need More Information',
'Error 42 occurred, run for the hills!',
0 );
Of course I'd prefer to pass an object with named params over a bunch of unlabeled params. I looked at their code closer and saw they changed the case sensitivity from timeOut to timeout!!! This works:
toaster.pop({
type: 'error',
title: 'Need More Information',
body: 'Error 42 occurred, run for the hills!',
timeout: 0
});
Just put toastr options in app.config
app.config(["toastrConfig",function(toastrConfig) {
var options = {
"closeButton": true,
"debug": false,
"newestOnTop": false,
"progressBar": true,
"positionClass": "toast-top-right",
"preventDuplicates": false,
"onclick": null,
"showDuration": "300",
"hideDuration": "1000",
"timeOut": "5000",
"extendedTimeOut": "1000",
"showEasing": "swing",
"hideEasing": "linear",
"showMethod": "fadeIn",
"hideMethod": "fadeOut"
};
angular.extend(toastrConfig, options);
})]);
Since "AngularJS Toaster" is an AngularJS port of the "toastr" jQuery library, it is definitely better to use it in your AngularJS application.
I use it in similar manner, and didn't have problems setting options in HTML template like this:
<toaster-container toaster-options="{'position-class': 'toast-bottom-right', 'close-button': true, 'body-output-type': 'trustedHtml', 'showDuration': '400', 'hideDuration': '200',}"></toaster-container>
If you have a base angular controller, you can add your site-wide angular.options there. Then, if necessary, you can override the options you want in the toastung controller.
toastr.options = {
"closeButton": true,
"debug": false,
"newestOnTop": false,
"progressBar": true,
"positionClass": "toast-top-right",
"preventDuplicates": false,
"onclick": null,
"showDuration": "300",
"hideDuration": "1000",
"timeOut": "5000",
"extendedTimeOut": "1000",
"showEasing": "swing",
"hideEasing": "linear",
"showMethod": "fadeIn",
"hideMethod": "fadeOut"
};