DataTables: How to set page in preInit? - datatables

The docs say that I should be able to set the page via DataTablesApiInstance.page(pageNumber), but I can't get it to work.
All the other API methods like search and order seem to work fine.
Here's my code:
$(document)
.on('preInit.dt', (ev, settings) => {
let tableId = ev.target.id;
let tableState = _.get(['datatables', tableId], history.state) || {};
let api = new $.fn.dataTable.Api(settings);
if(tableState.hasOwnProperty('page')) {
api.page(tableState.page); // <-- problem is here; page doesn't get set
}
if(tableState.hasOwnProperty('search')) {
api.search(tableState.search);
}
if(tableState.hasOwnProperty('order')) {
api.order(tableState.order);
}
const setState = (key, value) => {
history.replaceState(_.set(['datatables', tableId, key], value, history.state), '');
};
api.on('page', ev => {
let info = api.page.info();
// console.log('page', tableId, info.page);
setState('page', info.page);
});
api.on('order', ev => {
let order = api.order();
// console.log('order', tableId, order);
setState('order', order);
});
api.on('search', ev => {
setState('search', api.search());
});
});
The method is hit, but the page isn't set. Am I using the wrong API method? Is there another way to set the page before the data loads?
I'm using datatables.net#1.10.12.
If I defer the call to init instead of preInit then the correct page number is highlighted, but the data is still from the first page. If I add a 0ms delay on top of that (as below), it does work, but causes a 2nd data fetch + draw.
if(tableState.page) {
api.on('init', ev => {
setTimeout(() => {
api.page(tableState.page).draw('page');
}, 0);
});
}
How can I set the page without incurring a 2nd ajax request?

You can use displayStart option to define the starting point for data display when using DataTables with pagination as recommended by the author of jQuery DataTables.
It works correctly with table in server-side processing mode and only 1 Ajax request is performed.
var table = $("#example").DataTable({
"processing": true,
"serverSide": true,
"ajax": "/test",
"displayStart": 200
});
From the documentation:
Note that this parameter is the number of records (counting from 0), rather than the page number, so if you have 10 records per page and want to start on the third page, it should be 20 rather than 2 or 3.

This doesn't (directly) answer the question of how to set the page in the preInit, but it solves my problem.
We can use the stateLoadCallback to load the state (including page) from the history API instead of using localStorage as the default implementation does (which will remember the state even when navigating away and then back again).
$.extend(true, $.fn.dataTable.defaults, {
stateSave: true,
stateSaveCallback: (settings, data) => {
let tableId = settings.nTable.id;
if(!tableId) {
// console.warn(`DataTable is missing an ID; cannot save its state`);
return;
}
history.replaceState(_.set(['datatables', tableId], data, history.state), '');
},
stateLoadCallback: settings => {
let tableId = settings.nTable.id;
if(!tableId) {
console.warn(`DataTable is missing an ID; cannot load its state`);
return;
}
return _.get(['datatables', tableId], history.state) || null;
}
});

Related

Vue Reading in parallel in a loop

I have an Set array which contains multiple Ids.I would like to loop through the Set and make the api calls in parallel for each id and get back the user object, add it to a map.How can i achieve it.
The value is Set
userIds :Set[2]
0:"1"
1:"2"
data() {
return {
userIds: new Set(),
};
},
const res = getUsers(userId)
hope it will resolve your issues. i did not test, just writing code here directly.
// set requests
let allRequests = []
//you can use other loop based on your decession
this.userIds.forEach(id => { allRequests.push(axios.get(`your_url\${id}`)) })
// you can use await it is based on you requirement
axios.all(allRequests).then(axios.spread((...responses) => {
//make your map here using responses
})).catch(errors => {
// react on errors.
})
you can check this reference

Angular2 - Multiple dependent sequential http api calls

I am building an Angular2 app and one of the components needs to make multiple API calls which are dependent on the previous ones.
I currently have a service which makes an API call to get a list of TV shows. For each show, I then need to call a different API multiple times to step through the structure to determine if the show exists on a Plex server.
The API documentation is here
For each show, I need to make the following calls and get the correct data to determine if it exists: (Assume we have variables <TVShow>, <Season>, <Episode>)
http://baseURL/library/sections/?X-Plex-Token=xyz will tell me:
title="TV Shows" key="2"
http://baseURL/library/sections/2/all?X-Plex-Token=xyz&title=<TVShow> will tell me: key="/library/metadata/2622/children"
http://baseURL/library/metadata/2622/children?X-Plex-Token=xyz will tell me: index="<Season>" key="/library/metadata/14365/children"
http://baseURL/library/metadata/14365/children?X-Plex-Token=xyz will tell me: index="<Episode>" which implies that the episode I have exists.
The responses are in json, I have removed a lot of the excess text. At each stage I need to check that the right fields exist (<TVShow>, <Season>, <Episode>) so that they can be used for the next call. If not, I need to return that the show does not exist. If it does, I will probably want to return an id for the show.
I have looked at lots of examples including promise, async & flatmap, but am not sure how to solve this based on the other examples I have seen.
How to chain Http calls in Angular2
Angular 2.0 And Http
Angular 2 - What to do when an Http request depends on result of another Http request
Angular 2 chained Http Get Requests with Iterable Array
nodejs async: multiple dependant HTTP API calls
How to gather the result of Web APIs on nodeJS with 'request' and 'async'
Here is what I have for getting the list of shows. (shows.service.ts)
export class ShowsHttpService {
getShows(): Observable<Show[]> {
let shows$ = this._http
.get(this._showHistoryUrl)
.map(mapShows)
.catch(this.handleError);
return shows$;
}
}
function mapShows(response:Response): Show[] {
return response.json().data.map(toShow);
}
function toShow(r:any): Show {
let show = <Show>({
episode: r.episode,
show_name: r.show_name,
season: r.season,
available : false, // I need to fill in this variable if the show is available when querying the Plex API mentioned above.
});
// My best guess is here would be the right spot to call the Plex API as we are dealing with a single show at a time at this point, but I cannot see how.
return show;
}
Here is the relevant code from the component (shows.component.ts)
public getShows():any {
this._ShowsHttpService
.getShows()
.subscribe(w => this.shows = w);
console.log(this.shows);
}
Bonus points
Here are the obvious next questions that are interesting, but not necessary:
The first API query will be much faster than waiting for all of the other queries to take place (4 queries * ~10 shows). Can the initial list be returned and then updated with the available status when it is ready.
The first Plex call to get the key="2" only needs to be performed once. It could be hard coded, but instead, can it be performmed once and remembered?
Is there a way to reduce the number of API calls? I can see that I could remove the show filter, and search through the results on the client, but this doesn't seam ideal either.
The 4 calls for each show must be done sequentially, but each show can be queried in parallel for speed. Is this achievable?
Any thoughts would be much appreciated!
Not sure if I totally understand your question, but here is what I do:
I make the first http call, then when the subscribe fires, it calls completeLogin. I could then fire another http call with its own complete function and repeat the chain.
Here is the component code. The user has filled in the login information and pressed login:
onSubmit() {
console.log(' in on submit');
this.localUser.email = this.loginForm.controls["email"].value;
this.localUser.password = this.loginForm.controls["password"].value;
this.loginMessage = "";
this.checkUserValidation();
}
checkUserValidation() {
this.loginService.getLoggedIn()
.subscribe(loggedIn => {
console.log("in logged in user validation")
if(loggedIn.error != null || loggedIn.error != undefined || loggedIn.error != "") {
this.loginMessage = loggedIn.error;
}
});
this.loginService.validateUser(this.localUser);
}
This calls the loginservice ValidateUser method
validateUser(localUser: LocalUser) {
this.errorMessage = "";
this.email.email = localUser.email;
var parm = "validate~~~" + localUser.email + "/"
var creds = JSON.stringify(this.email);
var headers = new Headers();
headers.append("content-type", this.constants.jsonContentType);
console.log("making call to validate");
this.http.post(this.constants.taskLocalUrl + parm, { headers: headers })
.map((response: Response) => {
console.log("json = " + response.json());
var res = response.json();
var result = <AdminResponseObject>response.json();
console.log(" result: " + result);
return result;
})
.subscribe(
aro => {
this.aro = aro
},
error => {
console.log("in error");
var errorObject = JSON.parse(error._body);
this.errorMessage = errorObject.error_description;
console.log(this.errorMessage);
},
() => this.completeValidateUser(localUser));
console.log("done with post");
}
completeValidateUser(localUser: LocalUser) {
if (this.aro != undefined) {
if (this.aro.errorMessage != null && this.aro.errorMessage != "") {
console.log("aro err " + this.aro.errorMessage);
this.setLoggedIn({ email: localUser.email, password: localUser.password, error: this.aro.errorMessage });
} else {
console.log("log in user");
this.loginUser(localUser);
}
} else {
this.router.navigate(['/verify']);
}
}
In my login service I make a call to the authorization service which returns an observable of token.
loginUser(localUser: LocalUser) {
this.auth.loginUser(localUser)
.subscribe(
token => {
console.log('token = ' + token)
this.token = token
},
error => {
var errorObject = JSON.parse(error._body);
this.errorMessage = errorObject.error_description;
console.log(this.errorMessage);
this.setLoggedIn({ email: "", password: "", error: this.errorMessage });
},
() => this.completeLogin(localUser));
}
In the authorization service:
loginUser(localUser: LocalUser): Observable<Token> {
var email = localUser.email;
var password = localUser.password;
var headers = new Headers();
headers.append("content-type", this.constants.formEncodedContentType);
var creds:string = this.constants.grantString + email + this.constants.passwordString + password;
return this.http.post(this.constants.tokenLocalUrl, creds, { headers: headers })
.map(res => res.json())
}
The point here in this code, is to first call the validateUser method of the login service, upon response, based on the return information, if its valid, I call the loginUser method on the login service. This chain could continue as long as you need it to. You can set class level variables to hold the information that you need in each method of the chain to make decisions on what to do next.
Notice also that you can subscribe to the return in the service and process it there, it doesn't have to return to the component.
Okay, Here goes:
public getShows():any {
this._ShowsHttpService
.getShows()
.subscribe(
w => this.shows = w,
error => this.errorMessage = error,
() => this.completeGetShows());
}
completeGetShow() {
//any logic here to deal with previous get;
this.http.get#2()
.subscribe(
w => this.??? = w),
error => this.error = error,
() => this.completeGet#2);
}
completeGet#2() {
//any logic here to deal with previous get;
this.http.get#3()
.subscribe(
w => this.??? = w),
error => this.error = error,
() => this.completeGet#3);
}
completeGet#3() {
//any logic here to deal with previous get;
//another http: call like above to infinity....
}

Odoo UI widget - how to get settings from database?

I'm writing an Odoo v9 widget, which renders a URL, based on concatenation of a setting in the database, and the actual form fields.
The setting in the database I figure should live in ir_config_parameter. I'm inserting a default value with my module.
What's the best way to get this value when rendering the widget? Doing an async ajax call using
new Model("ir.config_parameter")
seems a little heavy handed. Is there a better way to be doing this?
Thanks.
Widget code:
var UrlWidget2 = form_common.FormWidget.extend({
start: function() {
this._super();
this.field_manager.on("field_changed:ref", this, this.display_result);
this.display_result();
},
display_result: function() {
var ref = this.field_manager.get_field_value("ref");
if (!ref) return;
var baseUrl = 'https://example.com'; //this is the value I want to get from the setting in the database.
var url = baseUrl + '/foo/' + ref;
this.$el.html('View Externally<br /><br/>');
}
});
You can use RPC for this. This is example which work for me:
var Model = require('web.DataModel');
var UrlWidget2 = form_common.FormWidget.extend({
// just example how to get parameter from backend
display_result: function() {
var parameter = new Model('ir.config_parameter');
// get fields value, key
parameter.query(['value', 'key'])
// criteria of search - record with id = 1
.filter([['id', '=', 1]])
// only one record
.limit(1)
.all()
.then(function (parameter) {
// here data from server
console.log(parameter);
});
// ...
}
});
Hope this helps you.

Filtering epics from Kanban board

I would like to start by saying I have read Rally Kanban - hiding Epic Stories but I'm still having trouble on implementing my filter based on the filter process from the Estimation Board app. Currently I'm trying to add an items filter to my query object for my cardboard. The query object calls this._getItems to return an array of items to filter from. As far as I can tell the query calls the function, loads for a second or two, and then displays no results. Any input, suggestions, or alternative solutions are welcomed.
Here's my code
$that._redisplayBoard = function() {
that._getAndStorePrefData(displayBoard);
this._getItems = function(callback) {
//Build types based on checkbox selections
var queries = [];
queries.push({key:"HierarchicalRequirement",
type: "HierarchicalRequirement",
fetch: "Name,FormattedID,Owner,ObjectID,Rank,PlanEstimate,Children,Ready,Blocked",
order: "Rank"
});
function bucketItems(results) {
var items = [];
rally.forEach(queries, function(query) {
if (results[query.key]) {
rally.forEach(results[query.key], function(item) {
//exclude epic stories since estimates cannot be altered
if ((item._type !== 'HierarchicalRequirement') ||
(item._type === 'HierarchicalRequirement' && item.Children.length === 0)) {
items = items.concat(item);
}
});
}
});
callback(items);
}
rallyDataSource.findAll(queries, bucketItems);
};
function displayBoard() {
artifactTypes = [];
var cardboardConfig = {
types: [],
items: that._getItems,
attribute: kanbanField,
sortAscending: true,
maxCardsPerColumn: 200,
order: "Rank",
cardRenderer: KanbanCardRenderer,
cardOptions: {
showTaskCompletion: showTaskCompletion,
showAgeAfter: showAgeAfter
},
columnRenderer: KanbanColumnRenderer,
columns: columns,
fetch: "Name,FormattedID,Owner,ObjectID,Rank,Ready,Blocked,LastUpdateDate,Tags,State,Priority,StoryType,Children"
};
if (showTaskCompletion) {
cardboardConfig.fetch += ",Tasks";
}
if (hideLastColumnIfReleased) {
cardboardConfig.query = new rally.sdk.util.Query("Release = null").or(kanbanField + " != " + '"' + lastState + '"');
}
if (filterByTagsDropdown && filterByTagsDropdown.getDisplayedValue()) {
cardboardConfig.cardOptions.filterBy = { field: FILTER_FIELD, value: filterByTagsDropdown.getDisplayedValue() };
}
cardboardConfig.types.push("HierarchicalRequirement");
if (cardboard) {
cardboard.destroy();
}
artifactTypes = cardboardConfig.types;
cardboard = new rally.sdk.ui.CardBoard(cardboardConfig, rallyDataSource);
cardboard.addEventListener("preUpdate", that._onBeforeItemUpdated);
cardboard.addEventListener("onDataRetrieved", function(cardboard,args){ console.log(args.items); });
cardboard.display("kanbanBoard");
}
};
that.display = function(element) {
//Build app layout
this._createLayout(element);
//Redisplay the board
this._redisplayBoard();
};
};
Per Charles' hint in Rally Kanban - hiding Epic Stories
Here's how I approached this following Charles' hint for the Rally Catalog Kanban. First, modify the fetch statement inside the cardboardConfig so that it includes the Children collection, thusly:
fetch: "Name,FormattedID,Children,Owner,ObjectID,Rank,Ready,Blocked,LastUpdateDate,Tags,State"
Next, in between this statement:
cardboard.addEventListener("preUpdate", that._onBeforeItemUpdated);
And this statement:
cardboard.display("kanbanBoard");
Add the following event listener and callback:
cardboard.addEventListener("onDataRetrieved",
function(cardboard, args){
// Grab items hash
filteredItems = args.items;
// loop through hash keys (states)
for (var key in filteredItems) {
// Grab the workproducts objects (Stories, defects)
workproducts = filteredItems[key];
// Array to hold filtered results, childless work products
childlessWorkProducts = new Array();
// loop through 'em and filter for the childless
for (i=0;i<workproducts.length;i++) {
thisWorkProduct = workproducts[i];
// Check first if it's a User Story, since Defects don't have children
if (thisWorkProduct._type == "HierarchicalRequirement") {
if (thisWorkProduct.Children.length === 0 ) {
childlessWorkProducts.push(thisWorkProduct);
}
} else {
// If it's a Defect, it has no children so push it
childlessWorkProducts.push(thisWorkProduct);
}
}
filteredItems[key] = childlessWorkProducts;
}
// un-necessary call to cardboard.setItems() was here - removed
}
);
This callback should filter for only leaf-node items.
Mark's answer caused an obscure crash when cardboard.setItems(filteredItems) was called. However, since the filtering code is actually manipulating the actual references, it turns out that setItems() method is actually not needed. I pulled it out, and it now filters properly.
Not sure this is your problem but your cardboard config does not set the 'query' field. The fetch is the type of all data to retrieve if you want to filter it you add a "query:" value to the config object.
Something like :
var cardboardConfig = {
types: ["PortfolioItem", "HierarchicalRequirement", "Feature"],
attribute: dropdownAttribute,
fetch:"Name,FormattedID,Owner,ObjectID,ClassofService",
query : fullQuery,
cardRenderer: PriorityCardRenderer
};
Where fullQuery can be constructed using the the Rally query object. You find it by searching in the SDK. Hope that maybe helps.

looping through DOM / mootools sortables

I can't seem to get a handle on my list of sortables. They are a list of list elements, each with a
form inside, which I need to get the values from.
Sortables.implement({
serialize: function(){
var serial = [];
this.list.getChildren().each(function(el, i){
serial[i] = el.getProperty('id');
}, this);
return serial;
}
});
var sort = new Sortables('.teams', {
handle: '.drag-handle',
clone: true,
onStart: function(el) {
el.fade('hide');
},
onComplete: function(el) {
//go go gadget go
order = this.serialize();
alert(order);
for(var i=0; i<order.length;i++) {
if (order[i]) {
//alert(order[i].substr(5, order[i].length));
}
}
}
});
the sortables list is then added to a list in a loop with sort.addItems(li); . But when I try to get the sortables outside of the sortables onComplete declaration, js says this.list is undefined.
Approaching the problem from another angle:
Trying to loop through the DOM gives me equally bizarre results. Here are the firebug console results for some code:
var a = document.getElementById('teams').childNodes;
var b = document.getElementById('teams').childNodes.length;
try {
console.log('myVar: ', a);
console.log('myVar.length: ', b);
} catch(e) {
alert("error logging");
}
Hardcoding one li element into the HTML (rather than being injected via JS) changes length == 1, and allows me to access that single element, leading me to believe that accessing injected elements via the DOM is the problem (for this method)
Trying to get the objects with document.getElementById('teams').childNodes[i] returns undefined.
thank you for any help!
not sure why this would fail, i tried it in several ways and it all works
http://www.jsfiddle.net/M7zLG/ test case along with html markup
here is the source that works for local refernece, using the native built-in .serialize method as well as a custom one that walks the dom and gets a custom attribute rel, which can be your DB IDs in their new order (I tend to do that)
var order = []; // global
var sort = new Sortables('.teams', {
handle: '.drag-handle',
clone: true,
onStart: function(el) {
el.fade('hide');
},
onComplete: function(el) {
//go go gadget go
order = this.serialize();
}
});
var mySerialize = function(parentEl) {
var myIds = [];
parentEl.getElements("li").each(function(el) {
myIds.push(el.get("rel"));
});
return myIds;
};
$("saveorder").addEvents({
click: function() {
console.log(sort.serialize());
console.log(order);
console.log(mySerialize($("teams")));
}
});