EXTJS 6 - Store.Proxy.Sync - ExtraParams for Update but not Create - extjs

I have a store that I have added records to, and edited existing records in.
Now I want to sync that data back to the server.
Using store.sync()
This fires off separate requests for each of the sync types (C,R,U,D) (using the proxy api values)
For each sync type, I need to pass a dynamic extraParam (lets make it simple and say extraParam = {type: "Update"} for updates and extraParam = {type: "Add"} for adding), though in application this will be something more complex, like passing a JSON object of user details or params based on the records being synced.
There must be a way to do this, without me having to manually code out a sync function.
Can someone give an example of this if it is possible, or a better approach?

Your server proxy takes an api property that contains the various URLs:
api: {
read:'MyReadEndpoint',
create:'MyCreateEndpoint',
update:'MyUpdateEndpoint',
delete:'MyDeleteEndpoint'
}
As far as I know, you can directly add GET parameters into these urls:
api: {
read:'MyEndpoint?action=read',
create:'MyEndpoint?action=create',
update:'MyEndpoint?action=update',
delete:'MyEndpoint?action=delete'
}
When bundling the different endpoints, the sync does a string comparison, and since all endpoint definitions are different, each batch is fired separately.

This was my final solution
I needed to over ride the existing sync function, and could have done so by loading a new definition into the overrides folder, but instead chose to put this in my store.
The code for that follows:
Ext.define('db_mubin.store', {
extend: 'Ext.data.Store'
,alias: 'store.db_mubin-store'
,require: 'db_mubin.model'
,model: 'db_mubin.model'
,proxy: {
type: 'ajax'
,url: '/api'
,reader: {
type: 'json'
,rootProperty: 'data'
}
,writer: {
allowSingle: false
}
,extraParams: {
calling: 'mubin'
}
}
,listeners: {
//add: function(){this.sync({})},
//update: function(){this.sync({})},
//remove: function(){this.sync({})}
}
,sync: function(options) {
var me = this,
operations = {},
toCreate = me.getNewRecords(),
toUpdate = me.getUpdatedRecords(),
toDestroy = me.getRemovedRecords(),
listeners = me.getBatchListeners();
options = options || {};
options.params = options.params || {};
//<debug>
if (me.isSyncing) {
Ext.log.warn('Sync called while a sync operation is in progress. Consider configuring autoSync as false.');
}
//</debug>
me.needsSync = false;
me.isSyncing = true;
if (toCreate.length > 0) {
options.params.fetch = 'create';
operations.create = toCreate;
me.proxy.batch(Ext.apply(options, {
operations: operations,
listeners: listeners,
params: options.params
}));
operations = {};
}
if (toUpdate.length > 0) {
options.params.fetch = 'update';
operations.update = toUpdate;
me.proxy.batch(Ext.apply(options, {
operations: operations,
listeners: listeners,
params: options.params
}));
operations = {};
}
if (toDestroy.length > 0) {
options.params.fetch = 'destroy';
operations.destroy = toDestroy;
me.proxy.batch(Ext.apply(options, {
operations: operations,
listeners: listeners,
params: options.params
}));
operations = {};
}
me.isSyncing = false;
return me;
}
});
Now I can call sync at any time, and pass in extra details, such as being able to give the API Authentication details, user details, anything that I NEED to send, I can send.

Related

Comparing results from two API calls and returning their difference in MEAN app

EDIT: Since I wasn't able to find a correct solution, I changed the
application's structure a bit and posted another question:
Mongoose - find documents not in a list
I have a MEAN app with three models: User, Task, and for keeping track of which task is assigned to which user I have UserTask, which looks like this:
const mongoose = require("mongoose");
const autopopulate = require("mongoose-autopopulate");
const UserTaskSchema = mongoose.Schema({
completed: { type: Boolean, default: false },
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
autopopulate: true
},
taskId: {
type: mongoose.Schema.Types.ObjectId,
ref: "Task",
autopopulate: true
}
});
UserTaskSchema.plugin(autopopulate);
module.exports = mongoose.model("UserTask", UserTaskSchema);
In my frontend app I have AngularJS services and I already have functions for getting all users, all tasks, and tasks which are assigned to a particular user (by getting all UserTasks with given userId. For example:
// user-task.service.js
function getAllUserTasksForUser(userId) {
return $http
.get("http://localhost:3333/userTasks/byUserId/" + userId)
.then(function(response) {
return response.data;
});
}
// task-service.js
function getAllTasks() {
return $http.get("http://localhost:3333/tasks").then(function(response) {
return response.data;
});
}
Then I'm using this data in my controllers like this:
userTaskService
.getAllUserTasksForUser($routeParams.id)
.then(data => (vm.userTasks = data));
...and because of autopopulate plugin I have complete User and Task objects inside the UserTasks that I get. So far, so good.
Now I need to get all Tasks which are not assigned to a particular User. I guess I should first get all Tasks, then all UserTasks for a given userId, and then make some kind of difference, with some "where-not-in" kind of filter.
I'm still a newbie for all the MEAN components, I'm not familiar with all those then()s and promises and stuff... and I'm really not sure how to do this. I tried using multiple then()s but with no success. Can anyone give me a hint?
You can do at server/API side that will more efficient.
In client side, if you want to do then try below
var userid = $routeParams.id;
userTaskService
.getAllTasks()
.then((data) => {
vm.userTasks = data.filter(task => task.userId !== userid)
});

Meteor Publish & Subscribe Not returning results using selector

I have the following code:
import { Meteor } from 'meteor/meteor';
import { Items } from './collection';
if (Meteor.isServer) {
Meteor.publish('items', function(options, owner) {
let selector = {
$and: [{ ownerId: owner}]
}
return Items.find(selector, options);
});
}
And on the client side I have:
this.subscribe('items', () => [{
limit: this.getReactively('querylimit'),
sort: {dateTime: -1}
},
this.getReactively('ownerId')
]);
The above does not return any results. However, when I change the return statement to the following, it works!
return Items.find({ ownerId: '7QcWm55wGw69hpuy2' }, options); //works !!!
I'm not very familiar with Mongo/Meteor query selectors. Passing the query as a variable to Items.find() seems to be messing something up. Can someone please help me figure this out!
Thanks
You are trying to pass a function as the selector, which won't work. Functions can't be serialized and sent from the client to the server. Instead you need to evaluate the options and the owner separately. Here's an example:
var owner = this.getReactively('ownerId');
var options = {
limit: this.getReactively('querylimit'),
sort: {dateTime: -1}
};
this.subscribe('items', options, owner);
Note that the published documents will not arrive in sorted order, so unless you are using a limit, the sort doesn't help here.
Also note that if you need the subscription to rerun after the owner or query limit change, you'll need to subscribe inside of an autorun.
Here's a start on an improved implementation:
Meteor.publish('items', function(options, owner) {
// DANGER! Actually check this against something safe!
check(options, Object);
// DANGER! Should any user subscribe for any owner's items?
check(owner, Match.Maybe(String));
// Publish the current user's items by default.
if (!owner) {
owner = this.userId;
}
return Items.find({ ownerId: owner }, options);
});

Connection state with doowb/angular-pusher

I am trying to build an Angular project with Pusher using the angular-pusher wrapper. It's working well but I need to detect when the user loses internet briefly so that they can retrieve missed changes to data from my server.
It looks like the way to handle this is to reload the data on Pusher.connection.state('connected'...) but this does not seem to work with angular-pusher - I am receiving "Pusher.connection" is undefined.
Here is my code:
angular.module('respondersapp', ['doowb.angular-pusher']).
config(['PusherServiceProvider',
function(PusherServiceProvider) {
PusherServiceProvider
.setToken('Foooooooo')
.setOptions({});
}
]);
var ResponderController = function($scope, $http, Pusher) {
$scope.responders = [];
Pusher.subscribe('responders', 'status', function (item) {
// an item was updated. find it in our list and update it.
var found = false;
for (var i = 0; i < $scope.responders.length; i++) {
if ($scope.responders[i].id === item.id) {
found = true;
$scope.responders[i] = item;
break;
}
}
if (!found) {
$scope.responders.push(item);
}
});
Pusher.subscribe('responders', 'unavail', function(item) {
$scope.responders.splice($scope.responders.indexOf(item), 1);
});
var retrieveResponders = function () {
// get a list of responders from the api located at '/api/responders'
console.log('getting responders');
$http.get('/app/dashboard/avail-responders')
.success(function (responders) {
$scope.responders = responders;
});
};
$scope.updateItem = function (item) {
console.log('updating item');
$http.post('/api/responders', item);
};
// load the responders
retrieveResponders();
};
Under this setup how would I go about monitoring connection state? I'm basically trying to replicate the Firebase "catch up" functionality for spotty connections, Firebase was not working overall for me, too confusing trying to manage multiple data sets (not looking to replace back-end at all).
Thanks!
It looks like the Pusher dependency only exposes subscribe and unsubscribe. See:
https://github.com/doowb/angular-pusher/blob/gh-pages/angular-pusher.js#L86
However, if you access the PusherService you get access to the Pusher instance (the one provided by the Pusher JS library) using PusherService.then. See:
https://github.com/doowb/angular-pusher/blob/gh-pages/angular-pusher.js#L91
I'm not sure why the PusherService provides a level of abstraction and why it doesn't just return the pusher instance. It's probably so that it can add some of the Angular specific functionality ($rootScope.$broadcast and $rootScope.$digest).
Maybe you can set the PusherService as a dependency and access the pusher instance using the following?
PusherService.then(function (pusher) {
var state = pusher.connection.state;
});
To clarify #leggetters answer, you might do something like:
app.controller("MyController", function(PusherService) {
PusherService.then(function(pusher) {
pusher.connection.bind("state_change", function(states) {
console.log("Pusher's state changed from %o to %o", states.previous, states.current);
});
});
});
Also note that pusher-js (which angular-pusher uses) has activityTimeout and pongTimeout configuration to tweak the connection state detection.
From my limited experiments, connection states can't be relied on. With the default values, you can go offline for many seconds and then back online without them being any the wiser.
Even if you lower the configuration values, someone could probably drop offline for just a millisecond and miss a message if they're unlucky.

Parameters for paging while http post in extjs

Hello I'm working on a extjs screen that has paging and the method to be used in the request must respond by HTTP POST verb.
I configured my store, so that all the read calls are posts. Realized that, extjs has simply passing the query string for the request payload, and I thought it would be possible to have the parameters separated, just as it would if it uses the params load of a store.
So I wonder if it's possible to have something like {params: {start: 0, page: 1, limit: 20}} in the request payload instead of the query string start = 0 & page = 1 & limit = 20.
I use Extjs 4.2 and Java with RESTEasy.
There isn't any built in functionality for this, but you can extend the store's proxy with the following:
Ext.define('Ext.ux.proxy.ModifiedAjax', {
extend: 'Ext.data.proxy.Ajax',
alias: 'proxy.modifiedajax',
defaultParams: 'param',
removeDefaultParams: true,
buildRequest: function(operation) {
var me = this,
request,
defaultParams = me.getParams(operation);
me.extraParams = me.extraParams || {};
if (me.defaultParams && defaultParams) {
me.extraParams[me.defaultParams] = me.applyEncoding(defaultParams);
}
var params = operation.params = Ext.apply({}, operation.params, me.extraParams);
if (me.removeDefaultParams != true) {
Ext.applyIf(params, defaultParams);
}
request = new Ext.data.Request({
params : params,
action : operation.action,
records : operation.records,
operation: operation,
url : operation.url,
proxy: me
});
request.url = me.buildUrl(request);
operation.request = request;
return request;
}
});
and in the store you need to set the proxy's type to 'modifiedajax'.

extjs4 - is there a non json/xml writer for proxies?

I'm building some models to interact with an existing API from a previous project.
The API relies on standard POST methods to save the data.
I've configured a model and proxy up to the point where it does push the data onto the server but there only seems to be two writer types, json & xml.
proxy: {
/* ... */
reader: {
type: 'json',
root: 'results'
},
writer: {
type: '???' // <-- can only see json or xml in the docs
}
}
Isn't there a standard POST writer that simply submits data in post fields?
I'm surprised that wouldn't be a standard writer type.
(Parsing the json format wouldn't be too hard to implement but that would mean updating a lot of the old api files.)
Ok, I was able to create that writer quite easily by checking the existing writers' source code.
One thing those existing writers are able to do - and that may be why the dev team only implemented a json and xml version - is that they can push multiple records at once.
That could be implemented in POST but would be a bit more complicated.
This writer will work if you're trying to push a single model to an api using POST:
Ext.define('Ext.data.writer.SinglePost', {
extend: 'Ext.data.writer.Writer',
alternateClassName: 'Ext.data.SinglePostWriter',
alias: 'writer.singlepost',
writeRecords: function(request, data) {
request.params = data[0];
return request;
}
});
and the use this for the writer in the proxy:
writer: {
type: 'singlepost'
}
Based on Ben answer I've implemented my own writer that will collect all properties of all models into arrays.
For example if you have model like with some fields:
fields:[
{name:'id', type:'int'}
{name:'name', type:'string'}
{name:'age', type:'date'}
]
A request string will be
id=1&id=2&id=...&name=oleks&name=max&name=...&age=...
Code:
Ext.define('Ext.data.writer.SinglePost', {
extend: 'Ext.data.writer.Writer',
alternateClassName: 'Ext.data.SinglePostWriter',
alias: 'writer.singlepost',
writeRecords: function(request, data) {
if(data && data[0]){
var keys = [];
for(var key in data[0]){
keys.push(key);
}
for(var i=0;i<keys.length;i++){
request.params[keys[i]] = [];
for(var j=0;j<data.length;j++){
request.params[keys[i]].push((data[j])[keys[i]]);
}
}
}
return request;
}
});
For Sencha touch 2.0, change the writeRecords method to:
writeRecords: function (request, data) {
var params = request.getParams() || {};
Ext.apply(params, data[0]);
request.setParams(params);
return request;
}
Here's my version, adapted from answers above:
// Subclass the original XmlWriter
Ext.define('MyApp.utils.data.writer.XmlInAPostParameter', {
extend : 'Ext.data.writer.Xml',
// give it an alias to use in writer 'type' property
alias : 'writer.xml_in_a_post_parameter',
// override the original method
writeRecords : function(request, data) {
// call the overriden method - it will put the data that I
// want into request.xmlData
this.callParent(arguments);
// copy the data in request.xmlData. In this case the XML
// data will always be in the parameter called 'XML'
Ext.apply(request.params, {
XML: request.xmlData
});
// Already copied the request payload and will not send it,
// so we delete it from the request
delete request.xmlData;
// return the modified request object
return request;
}
});
Ext.define("MyApp.model.MyModel", {
extend : "Ext.data.Model",
requires : [
'MyApp.utils.data.writer.XmlInAPostParameter'
],
fields : [ 'field_A', 'field_B' ],
proxy : {
type : 'ajax',
api : {
read : '/mymodel/read.whatever',
update : '/mymodel/write.whatever'
},
reader : {
type : 'xml'
},
writer : {
// use the alias we registered before
type : 'xml_in_a_post_parameter'
}
}
});

Resources