Using query strings in backbone (1.0.0) - backbone.js

i have a situation here that i can't seem to figure out. Please if anybody knows how to resolve this i would love to hear suggestions.Thanks
I have a "global view" that is visible on a subnavbar in the app, that is a calendar, this calendar serves as a global calendar throughout the application, so almost all the views use the calendar view & model to set show data according to the date selected.
This calendar view/model should have some way to store in history each time the date is changed, this (i think) is done using a single URL or query string parameters each time the date is changed, something like
webapp/view1?date=20120301
and when the date is changed, so its the query string.
I would like to use query string parameters for this so i don't have to specify on each route the (/:date) optional parameter.
THE THING IS backbone stopped firing a route change or a history event when query strings are changed, they simply ignore query strings on the Backbone.History implementation, this is breaking all my implementation cause i can't track each time the querystring is changed, so the "back" button will not fire a change event and therefore i can't change the date on the model that would change the date on the calendar.
I know a simple solution to this would be just using "pretty URL" and adding that date parameter to each view, but im trying to avoid that.
Any suggestions?
Thanks in advance
UPDATE
I ended up using "pretty URLs" like the Backbone documentation suggests, cause using query strings would bring me a lot of trouble for tracking the URL change and history, also wouldn't work as expected when using hashchange instead of pushState.
So, my code ended up like this:
Attaching somewhere in your router, view, controller, something, to the "route" event of your router, to check the URI for the date and set this date to the calendar picker:
this.listenTo(myRouter, "route", this.routeChanged);
Then, this method would do something like:
checkURIdateParameter: function (route, arr) {
var found = false;
for (var i = 0; i < arr.length ; i++) {
if (arr && arr[i] && arr[i].indexOf("date=") != -1) {
if (app.models.dateControl) {
var dateFromParameter = new Date(arr[i].substring("date=".length).replace(/\$/g, ":"));
dateFromParameter = (isNaN(dateFromParameter.getTime())) ? app.models.dateControl.defaults.date : dateFromParameter;
app.models.dateControl.set("date", dateFromParameter);
found = true;
}
}
}
if (!found) app.models.dateControl.set("date", app.models.dateControl.defaults.date, {changeURI:false});
}
This serves as the function that will read params from the URI and reflect those changes on the dateControl.
There should be another method that will be the one in charge of updating the URI to a new one each time the date is changed (so that the params are in sync with the view) and the link can be copied and pasted with no problems.
Somewhere on your router, view, controller:
this.listenTo(app.models.dateControl, "change:date", this.updateURIdateParameter);
This is a method that is attached to a model that has the current date, this model is updated by the calendar picker (the UI control) or the method that was linked with the route event (routeChanged, look above).
This method should do something like this:
, updateURIdateParameter: function (a, b, c) {
if (c && c.changeURI == false) return; //this is in case i don't want to refresh the URI case the default date is set.
var currentFragment = Backbone.history.fragment;
var newFragment = "";
var dateEncoded = app.models.dateControl.get("date").toJSON().replace(/\:/g, "$");
newFragment = currentFragment.replace(/\/date=([^/]+)/, "") + "/date=" + dateEncoded;
if (newFragment != currentFragment) {
app.router.navigate(newFragment, false);
}
}
This method gets the currentDate selected from the corresponding model, parses it, then takes the URL from the Backbone.history.fragment, execs a nice regexp against it that will replace the old date parameter (if exists) and then appends the new encoded date.
Then navigates to this route in silent mode so that the method that checks the route is not called (this prevents annoying loops).
I hope this helps.

I would suggest using "Pretty URL".
FYI Page URL in the browser bar will blink in this example.
Somewhere inside your Backbone.Router:
this.route('*action', function() {
console.log('404');
});
this.route(/^(.*?)\?date=(\d+)$/, function(route, date) {
// same current route (with ?date)
var fragment = Backbone.history.fragment;
// navigate to main route (to create views etc.)
this.navigate(route, {trigger:true, replace:true});
// silent change hash
this.navigate(fragment);
});
this.route('any', function() {
// need wait for hash change
_.defer(function() {
console.log('parse date here and do what you want', window.location.hash.match(/^(.*?)\?date=(\d+)$/));
});
});
this.route('route', function() {
// need wait for hash change
_.defer(function() {
console.log('parse date here and do what you want', window.location.hash.match(/^(.*?)\?date=(\d+)$/));
});
});

Related

Marionette Regions and routing

I'm using a LayoutView to display a collection in table form. When a user clicks on a tr I swap the CompositeView for an ItemView that shows the details using the same region. It all works except the functionality of the back button is broken. Is there a way to trap the back event and switch views?
Or should I use two Views and pass the model id and then refetch the model? The problem with that though is the extra request and I lose the filter and sort values of the table unless I use local storage.
Including more code would be better, but in any case I will try to give some guidance for your problem.
To avoid fetching the data twice, you can keep a common object in a "parent" component, for example in the Router.
var theObject;
var router = Marionette.AppRouter.extend({
routes: {
"routeA/:id": "goToRouteA",
"routeB/:id": "goToRouteB"
},
goToRouteA: function(id) {
MyRegion.show(new myLayout({
model: this._getCommonObject(id)
}));
},
goToRouteB: function(id) {
MyRegion.show(new myLayout({
model: this._getCommonObject(id)
}));
},
/*Return the common object for the views*/
_getCommonObject: function(id) {
theObject = (theObject && theObject.get('id') == id) ? theObject : MyApp.request('getTheObject');
return theObject;
}
});
In this way, you can keep the reference to the same object without loosing information.
You just have to make sure to delete the object when it is not needed to avoid keeping old information, for example on the Region close event.

AngularJS UI-calendar not updating events on Calendar

I am using Angular UI-Calendar to show some events on the Calendar. The events are showing fine on the Calendar. But when I update any event's details, the event's detail is actually modified, but not modified on the Calendar display(eg: start).
Initially, after I modified the event's details, I did a page reload to display modified changes and it worked too.In that method, I had empty $scope.events = []; array, which I filled after retrieving entries from DB.
But now, I want to avoid that page reload. So, once the event's details are modified from modal window, I clear the contents of $scope.events array using $scope.events = []; and then using API call, I fill the new events again in $scope.events array. This is working fine as the List view shows the modified events details. But the Calendar itself shows old entries. e.g if I change start from 11 April to 13 April, the calendar shows event on 11 April whereas List views shows the modified data i.e 13 April. Using Page reload, corrects this i.e event is shown on modified date(13 April).
How can I ensure that the event is modified on Calendar too without a Page reload ?
I tried calendar.fullCalendar('render'); after fetching new entries from DB, but it does not solve the Problem.
Here are some codes :
Initially I did this to send data to DB and then did a page reload to retrieve updated data.
$http.put(url,senddata).success(function(data){$window.location.reload();});
Now I want to avoid the page reload, so I did
$http.put(url,senddata).success(function(data){//$window.location.reload();//sends modified data to server
$scope.events = []; //clear events array
$http.get(url2).success(function(data){ //fetch new events from server, and push in array
$scope.schedule = data;
for(var i=0;i<data.length;i++)
{
$scope.events.push({
idx: data[i].idx,
title: data[i].title,
description : data[i].description,
allDay: false,
start: new Date(data[i].start),
end: new Date(data[i].end),
});
calendar.fullCalendar('render'); //Tried even this, didn't work
}
});
Above code pushes modified event in event array as visible in List view, but calendar still shows old event until page is reloaded.
Try maintaining the same array instance.
Instead of doing:
$scope.events = []
Try:
$scope.events.slice(0, $scope.events.length);
Then when your server request comes back, add each item individually to the existing array:
for(var i = 0; i < newEvents.length; ++i) {
$scope.events.push(newEvents[i]);
}
The reason I suggest this is because what you're describing sounds like the calendar might be holding onto the old list of events. The calendar might not know to update its reference, so instead, let it keep the same reference but change the contents of the array.
Just a quick correction to Studio4Development's answer. You should use "splice" not "slice". Slice returns the trimmed array. What we want to do is actually alter the original array. So you can use:
$scope.events.splice(0, $scope.events.length)
and to add new events:
$scope.events.push(newEvent)
Don't know if you found the solution to your problem, but what worked for me was:
calendar.fullCalendar('refetchEvents');
Here is how I fixed a similar problem on my page.
view (simplified, note using jade)
div#calendarNugget(ng-show="activeCalendar == 'Nugget'" ui-calendar="uiConfig.calendarNugget" ng-model="eventSources")
div#calendarWillow(ng-show="activeCalendar == 'Willow'" ui-calendar="uiConfig.calendarWillow" ng-model="eventSources2")
controller:
As per ui-calendar docs, I start with an empty array for my event sources. Ignore that I should probably rename these eventSources to something about the shop names (willow, nugget)
$scope.eventSources = [];
$scope.eventSources2 = [];
I then call a factory function that makes an http request and pulls a series of events from the DB. The events all have "title", "start", "end" properties (make sure your Date format is correct). They also have a "shop" property, which tells me which calendar to add the event to.
So after receiving the data I make two local arrays, loop through the received http data, and assign the events to those local arrays by shop. Finally, I can re-render the calendars with the proper event data by calling addEventSource, which automatically re-renders as per the fullCalendar docs
It looks something along the lines of this iirc:
function splitShiftsByShop(shifts) {
var nuggetShifts = [];
var willowShifts = [];
for (var i=0; i<shifts.length; i++) {
if (shifts[i].shop === "Nugget") {
var nshift = {
title : shifts[i].employee,
start : new Date(shifts[i].start),
end : new Date(shifts[i].end),
allDay: false
}
nuggetShifts.push(nshift);
} else if (shifts[i].shop === "Willow") {
var wshift = {
title : shifts[i].employee,
start : new Date(shifts[i].start),
end : new Date(shifts[i].end),
allDay: false
}
willowShifts.push(wshift);
}
}
/*render the calendars again*/
$('#calendarNugget').fullCalendar('addEventSource', nuggetShifts);
$('#calendarWillow').fullCalendar('addEventSource', willowShifts);
}
I was having some similar issues where events weren't being refetched after emptying my "eventSource" array ($scope.eventSources = [$scope.completedEvents]) and repopulating it with new events. I was able to overcome this at first by calling 'removeEvents',
uiCalendarConfig.calendars.calendar.fullcalendar('removeEvents')
This is hackish, so after further tinkering I found that events were refetched when my child array is modified,
$scope.completedEvents.splice(0, $scope.completedEvents.length)
After reviewing the source, I can see that the eventSource array is being watched, but the 'refetch' is never occurring. furthermore, I was never able to get the following to work,
uiCalendarConfig.calendars.calendar.fullcalendar('refetchEvents')
According to ui-calendar code, it does actually watch eventSources but never the actual individual sources. So doing a push to, let's say $scope.events ($scope.eventSources = [$scope.events]), will never trigger anything.
So push events to $scope.eventSources[0]
$scope.eventSources[0].splice(0, $scope.eventSources[0].length);
$scope.eventSources[0].push({
title : title,
start : start,
end : end
});
As you already know Full Calendar is dependant on JQuery. Therefore, all you need to do is put an ID on your calendar like this:
<div id="calendar" ui-calendar="uiConfig.calendar" class="span8 calendar" ng-model="eventSources"></div>
and then when ever you want to update the calendar just call:
$('#calendar').fullCalendar('refetchEvents')
I struggled with this for some time as well and this fixed all my problems. I hope this will help you too!
Goodluck!
Although it's late response but hope it may help some. I am putting it forth step by step.
You will need to maintain the same event source as Studio4Development mentioned earlier.
$scope.events //no need to reinitialize it
You will remove the updated event from events array like this (for last event in event array):
$scope.events.splice($scope.events.length - 1, 1);
For event that may exist anywhere in the events array you'll need to find its index using:
var eventIndex = $scope.events.map(function (x) { return x.Id; }).indexOf(eventId);
and then you'll remove it from events array like this:
$scope.events.splice(eventIndex, 1);
Next you'll fetch this event from the API again like this:
getEvent(data.Id);
and in the callback method you'll again add the event in events array:
var getSingleEventSuccessCallback = function (event) {
$scope.events.push(event);
};
Was stuck on this for a while.
What seems to be working for me turned out to be quite simple
I fetch my calendar from google and run an event formatter function. In this I define a new array and at the end of the function I set my eventSource equal to the new array. Then I call $scope.$apply
x = []
for e in events
x.push(
title: e.summary
etc...
)
$scope.eventSource = x
$scope.$apply()
Try this. It worked for me.
function flushEvents()
{
$scope.events.splice(0,$scope.events.length);
for(var i=0; i<$scope.events.length; i++)
{
$scope.events.splice(i,1);
}
}
I found this and it helped me:
$(id_your_div).fullCalendar('removeEvents');
$(id_your_div).fullCalendar('refetchEvents');

Using gotoDate in fullCalendar with Angular UI

I've setup a calendar using FullCalendar with Angular UI. It works fine, I can toggle categories of events nicely, but every time the eventSource is updated the calender view is set to the current date.
I've tried using the gotoDate method and I can see that it works (it also works from the console), but almost immediately after the calender is reverted to the current date. As I'm new to AngularJS I've probably put the gotoDate in the wrong place. But I'm clueless were to put it elsewhere.
I'm using a service that returns a bunch of event objects and pushes them into eventSources, the ng-model of the calendar element. Nothing special, in the controller I have:
$scope.eventSources = [];
var promise = UserCalendarEvents.get(groupName);
promise.then(
function(events) {
$scope.eventSources.push(events);
$('#events-calendar').fullCalendar('gotoDate', 2012, 11);
},
function(reason) {
console.log('Error: ' + reason);
}
);
In this case events are fetched and $scope.eventSources is populated. The calender view is then set to december 2012 and after that, almost instantly, the view swithes to current date. Is it some kind of watch of the ng-model that rerenders the fullcalender and if so how can I set the date of choice?
Update: I ended using joshkurz fix, but in a modified version that honors the selected view, ie if the user has selected basicWeek and changes source data the view shouldn't change to for example month view. That's what I need for my users.
function update() {
scope.calendar = elm.html('');
var view = scope.calendar.fullCalendar('getView');
var m;
var xtraOptions = {};
//calendar object exposed on scope
if(view){
var viewDate = new Date(view.start);
if(m !== 'Invalid Date'){
y = viewDate.getFullYear();
m = viewDate.getMonth();
d = viewDate.getDate();
if(!isNaN(y) && !isNaN(m) && !isNaN(d)){
xtraOptions = {
year: y,
month: m,
date: d
};
}
}
view = view.name; //setting the default view to be whatever the current view is. This can be overwritten.
}
/* If the calendar has options added then render them */
var expression,
options = { defaultView : view, eventSources: sources };
if (attrs.uiCalendar) {
expression = scope.$eval(attrs.uiCalendar);
// Override defaultView if is set in ui-calendar attribute - OK?
if (expression.defaultView) {
expression.defaultView = view;
}
} else {
expression = {};
}
angular.extend(options, uiConfig.uiCalendar, expression, xtraOptions);
scope.calendar.fullCalendar(options);
}
This is a bug with the calendar. You are the first one to say anything about it on StackOverflow. Kudos.
There are a couple of ways that this could be fixed. Its been proposed on github https://github.com/angular-ui/angular-ui/pull/520 that we do away with how the directive re-creates itself anytime the watch is fired, which would stop this behavior. I believe that if we can get this method to work in production then it will be the best solution.
Until then however the fix is to get the current month from a date object created from the view.start field. This month should be added to the options which are used to render the calendar.
Here is a snippet of what the new update function should look like inside of the calendar directive.
/* update the calendar with the correct options */
function update() {
scope.calendar = elm.html('');
var view = scope.calendar.fullCalendar('getView');
var m;
var xtraOptions = {};
//calendar object exposed on scope
if(view){
var d = new Date(view.start);
m = new Date(view.start);
if(m !== 'Invalid Date'){
m = m.getMonth();
if(!isNaN(m)){
xtraOptions = {
month: m
};
}
}
view = view.name; //setting the default view to be whatever the current view is. This can be overwritten.
}
// console.log(m)
/* If the calendar has options added then render them */
var expression,
options = {
defaultView : view,
eventSources: sources
};
if (attrs.exCalendar) {
expression = scope.$eval(attrs.exCalendar);
} else {
expression = {};
}
angular.extend(options, uiConfig.exCalendar, expression, xtraOptions);
scope.calendar.fullCalendar(options);
}
This has not been properly tested on the angular-ui CI server, but it works fine as I am using it in production currently.
With AngularUI wrapping FullCalendar in a directive for you, the calendar object can be accessed via $scope.calendar. "The AngularJS Way" is to avoid direct DOM manipulation in controllers.
In your particular case, you'd write this instead:
$scope.calendar.fullCalendar('gotoDate', 2012, 11);
AngularUI does have a watch on eventSource and event that calls an update function every time the length of either changes. You can view the source here:
https://github.com/angular-ui/angular-ui/blob/master/modules/directives/calendar/calendar.js
You can see that the calendar object at $scope.calendar gets recreated with a new set of events everytime the event model changes. This is why your date change isn't going through -- the event is being added, triggering the update, your date change goes in, the whole calendar is changed and your date change is lost.
Two (not the best) things pop up at me without changing AngularUI's code:
You can use AngularJS's $timeout service and wait a set
time after events are loaded, the calendar is finished updating,
then call your date change.
You can add a $watch on the scope that triggers your date change
everytime the calendar object changes:
http://plnkr.co/edit/Ww08VX?p=preview
I created that example above. In the test() method, I'm just loading some fake data into events via a promise (they'll be added in March 2013) then changing the date to December 2012. I'm watching $scope.calendar and everytime it changes (an update is triggered in the directive) I resend the date command. You should be sent to December 2012 without even seeing the new events go in, but if you go back to March 2013, they should be there. I stuck the watch in another .then assuming you'll use some value that's returned to set the date dynamically.
Another way to solve the issue without changing the Angular-UI source is to declare the calendar like this:
<div ui-calendar="{viewDisplay:viewDisplayHandler,month:monthVal,year:yearVal}" ng-model="eventsArr"></div>
And to have a viewDisplayHandler function in the scope that sets monthVal and yearVal to the appropriate values in order to have the date on the calendar set after the whole calendar recreation:
$scope.viewDisplayHandler = function(view) {
var viewStart = view.start;
$scope.yearVal = viewStart.getFullYear();
$scope.monthVal = viewStart.getMonth();
}
This is how i solved it before issuing the pull request on GitHub; it's not the optimal method i guess, but i have been using it in production for a while and it seems to be ok and does not require changing Angular-UI's code.

Sencha - store.each() - Data are not available, they are only deep in data class

I have got:
Ext.define("catcher.view.Login", {
extend: "Ext.form.Panel",
// creating login form, including selectfield
Store "Tournaments" is created in stores (autoload:true), have its model. Everyting is set.
need to dynamicly fill selectfield (still in view.Login class):
initialize: function(){
var store = Ext.getStore("Tournaments");
var options = new Array();
store.each(function(radek){
options[radek.get("tournament_id")] = radek.get("tournament_name");
});
}
I do not want to use store:"Tournaments" config options, because of later form.submit(); does not send correct data from selectfield.
There is the problem: console.log(store.getCount()); returns 0. Using store.add({ ... }) I can add anything and getCount() returns corrent count (0 + add()).
Weird part: console.log(store) returns whole class including data object with all items inside. And next weird part - If I use the same code in controller, everything works, the Store is loaded properly and I can use mystore.each();
Store loading is asynchronous, by the time you're accessing it, it's not loaded. You need to listen to the store load event.
Something like:
store.on('load', function(storeRef, records, successful){
//Loop through records
}, this);
on() documentation
load event documentation

How to clear the store and update a paging toolbar?

i need to reset paging toolbar parameters as "page", "start", "limit" when i click on a search button to re-load grid store with different parametres!
how can i do it?
the problem is that when i am on the next page, and i do a new search, i have the parameters page=2, start=25, limit=25 dirty, instead i need to reset this parametres.
my code:
listeners: {
click: function(){
Ext.getCmp('GrlGio').getStore().removeAll();
Ext.getCmp('GrlGio').store.load({
params:{
mode: "RIC",
DataRicerca: dd,
Pit: Ext.getCmp('cmbPiattaforma').getValue()
}
});
}
}
thanks!
In Ext 4, I found loadPage() worked pretty well for resetting the data store and making the paging toolbar go back to the first page. Example:
store.loadPage(1) // note: 1-based, not 0-based
Guys currentPage=1 did the trick for me
before loading the store every time call the below
By the way i am getting 500 results and loading in cache
Pagination is for local, any way you can try this before any new search
var store = Ext.getStore('MyStoreS');
store.proxy.extraParams = { employeeId : searchStr};
store.currentPage = 1;
store.load();
you can manualy reset the params
Ext.getCmp('GrlGio').getStore().getProxy().pageParam =1;
Ext.getCmp('GrlGio').getStore().getProxy().startParam =0;
and then do the store load. I know it looks hardcoded but it's the only solution i found...
Try this -
pagingToolbar.moveFirst();
Define following function "resetStartParam" , by overriding ext.data.store:
Ext.override(Ext.data.Store, {
resetStartParam:function(){
//get the latest store options
var storeOptions=this.lastOptions;
if(storeOptions!=undefined){
//get the param names
var pn = this.paramNames;
//get the params from options
var params=storeOptions.params;
//change the param start value to zero
params[pn.start] = 0;
//reset options params with this new params
storeOptions.params=params;
//apply this new options to store options
this.storeOptions(storeOptions);
}
}
});
Now call this function on click of your search button:
Ext.getCmp('GrlGio').getStore().resetStartParam();
Thats it.It should work.
I know that this is an old post but I thought I'd add in my pennies work. I'm using EXTJS 4 and had a similar problem. When I did a new search the page number etc did not reset. The solution I found, which appears to work with the nav bar automatically is using the currentPage attribute of the store. I do have a slight odd setup but doing this.currentPage = 1 when I do a new search works fine for me
try this in your handler
Ext.getCmp('gridpanel').getStore().removeAll();
Ext.getCmp('PagingToolbar').moveFirst();
after this, put your search query and load the store accordingly
Ext.getCmp('gridpanel').getStore().load({params : { start : 0, limit : maxRecords, searchText : _searchText } });
hope it helps
just call pagingToolbar.onLoad() after removeAll(). Plain and simple.
Here is how I achieved search with paging. It only does 1 request and it refreshes the paging data.
onExecuteSearch: function(){
var params = this.getSearchForm().getForm().getFieldValues()
, proxy = this.getSomeGrid().getStore().getProxy();
proxy.extraParams = params;
this.getPagingToolbar().moveFirst();
}
getFieldValues() documentation: http://docs.sencha.com/ext-js/4-0/#!/api/Ext.form.Basic-method-getFieldValues
For more about the proxy "extraParams" look here: ExtJs4 - Store baseParams config property?
This work fine (refresh correctly the paging info):
myStore.removeAll();
myStore.fireEvent('load', myStore, [], {});
Had to change the page size to 500 for printing the WHOLE store/grid, and once printed, restore the grid to the original page size of 25.
// 500 records are now in the store and on the grid
ux.core.grid.Printer.print(this.getOrderList());
store.pageSize = this.displaySize; // new page size is 25
this.getPagingToolbar().doRefresh(); // equivalent of pressing a refresh button on the toolbar
does the trick - reloads store with the same sorters/filters/currentPage

Resources