I'm looking for an approach to reduce code duplication. Currently, whenever I save a Backbone model, I always provide the following code to deserialize the Rails stock 422 response into an errors property:
model.save({ name: "..." }, error: function(model, response) {
result = $.parseJSON(response.responseText)
model.errors = result.errors
...
});
Is it possible to configure Backbone so it deserializes errors before calling my callback? I tried overriding the sync method but at that point the callback is wrapped in a anonymous function (and no longer accessible).
You can use a ErrorHandler model that is extended by other models requiring error parsing. The ErrorHandler model should listen for the "error" event (triggered by the sync function when errors occur after a request).
var ErrorHandler = Backbone.Model.extend({
initialize: function() {
this.on("error", this.defaultErrorHandler, this);
},
defaultErrorHandler: function(model, error) {
result = $.parseJSON(response.responseText)
model.errors = result.errors
...
}
});
var OtherModel = ErrorHandler.extend({
...
});
var model = new OtherModel();
model.save({ name: "..." });
Related
I am new to JavaScript frameworks and currently trying to setup a falcor router calling an external api (for now consider it as an express api app + mango db, hosted at 3000 port).
Now, I am able to use the request package (commented out lines) and successfully call the Express Api app (which returns obj.rating = 4). But I am unable to send this value from the falcor router instead of the hard-coded value "5".
Below is the falcor-router's server.js code:
app.use('/rating.json', falcorExpress.dataSourceRoute(function (req, res) {
return new Router([
{
route: "rating",
get: function() {
var obj;
// request('http://localhost:3000/rating/101', function (error, response, body) {
// obj = JSON.parse(body);
// console.log('rating:', obj.rating); // obj.rating = 4
// });
return {path:["rating"], value:"5"};
}
}
]);
}));
The below is the code for index.html:
<script>
function showRating() {
var model = new falcor.Model({source: new falcor.HttpDataSource('http://localhost/rating.json') });
model.
get("rating").
then(function(response) {
document.getElementById('filmRating').innerText = JSON.stringify(response.json,null, 4);
});
}
</script>
I also tried to look at the global variable declaration, synchronize http request calls, promises, then statements etc. But nothing seemed to work, clearly I am missing out something here - not sure what.
The router's get handler expects the return value to be a promise or an observable that resolves to a pathValue. To get your request against the db to work, simply return a promise that resolves to a pathValue, e.g.
return new Router([
{
route: "rating",
get: function() {
return request('http://localhost:3000/rating/101', function (error, response, body) {
return { path: ["rating", value: JSON.parse(body).rating };
});
}
}
]);
I am trying send some data to sap gateway service.
I am using this example the method "save", but when I try do it in my code I get an error "OData is not defined"
Below is the method when I try do it.
handleConfirmationMessageBoxPress: function(oEvent) {
var bCompact = !!this.getView().$().closest(".sapUiSizeCompact").length;
MessageBox.confirm(
"Deseja confirmar a transferência?", {
icon: sap.m.MessageBox.Icon.SUCCESS,
title: "Confirmar",
actions: [sap.m.MessageBox.Action.OK, sap.m.MessageBox.Action.CANCEL],
onClose: function(oAction) {
if (oAction == "OK") {
var oParameters = {};
oParameters.loginfrom = this.getView().byId("multiInput").getValue();
oParameters.loginfrom = this.getView().byId("loginPara").getValue();
oParameters.loginfrom = this.getView().byId("datade").getValue();
oParameters.loginfrom = this.getView().byId("datapara").getValue();
OData.request({
requestUri : "http://<host name>:<port no>/sap/opu/odata/sap/ZMM_EMP_SRV/EmployeeSet",
method : "GET",
headers : {...}
},
function(data, response) {
...
var oHeaders = {
... };
OData.request({
requestUri : "http://<host name>:<port no>/sap/opu/odata/sap/ZMM_EMP_SRV/EmployeeSet",
method : "POST",
headers : oHeaders,
data:oParameters
},
function(data,request) {
MessageToast.show("Transferência realizada!");
location.reload(true);
}, function(err) {
MessageToast.show("A transferência falhou!");
});
}, function(err) {
var request = err.request;
var response = err.response;
alert("Error in Get — Request " + request + " Response " + response);
});
} else {
...
You are attempting to use the OData global object from the datajs library. This library is indeed shipped with OpenUI5, but IMO you should not use it directly (but use the methods of the OData model; there is no real guarantee that UI5 will continue shipping this third-party library in the future).
You are most likely getting the error because the library was not yet loaded by UI5. Libraries are generally lazily loaded by UI5, so you will have to request that UI5 loads it for you (in the tutorial that you have linked, it was loaded behind the scenes by the OData model). To do this, you can either use jQuery.sap.require (jQuery.sap.require("sap.ui.thirdparty.datajs")) or list the dependency inside your sap.ui.define call at the beginning of the controller (e.g. sap.ui.define(['sap/ui/thirdparty/datajs'], function(datajs){...})).
Later edit: you can also use the jQuery.sap.require("sap.ui.model.odata.datajs"); call, but the module was moved from there and it would effectively redirect you to the new location.
this is a very old example, and the used old techniques.
You should add this line to your code:
jQuery.sap.require("sap.ui.model.odata.datajs");
This should solve your oData is undefined problem.
In general you should read newer examples where the read() function of the odata model is used.
I'm currently trying to implement the fullcalendar javascript library into an angular 2 dart webapp.
I'm having problems porting this javascript code to dart though:
$('#fullCalendar').fullCalendar(
{
events: function(start, end, timezone, callback) {
var generated_events=[
{
title : 'test',
start : '2016-08-08'
}];
callback(generated_events);
},
allDaySlot: false
//More options can go here
});
I've gotten as far as being able to pass a dart function to the events parameter with this code:
context.callMethod(r'$',['#fullCalendar'])
.callMethod('fullCalendar',[new JsObject.jsify({
'events': (start, end, timezone, callback){
print("called!");
List<FullCalendarEvent> generated_events= [
new FullCalendarEvent(title: "test", start: "2016-08-08")
];
try{
callback(generated_events);
}catch(exception,stackTrace){
print("Caught exception!");
print(exception);
print(stackTrace);
}
},
'allDaySlot': false
//more options can go here
})]);
Where the FullCalendarEvent is a simple anoymous class structure:
#JS()
#anonymous
class FullCalendarEvent{
external String get title;
external set title(String v);
external String get start;
external set start(String v);
external factory FullCalendarEvent({
String title,
String start
});
}
However the callback(generated_events); throws this exception:
NoSuchMethodError: method not found: 'call$1' (callback.call$1 is not a function)
Edit:
With the help of Günter's replies I managed to fix the problem. Instead of doing callback(generated_events); I instead use callback.apply([generated_events]); Additionally instead of using
List<FullCalendarEvent> generated_events= [
new FullCalendarEvent(title: "test", start: "2016-08-08")
];
I instead use:
var generated_events = new JsObject.jsify([{'title':'test','start':'2016-08-08'}]);
My working code looks like this:
context.callMethod(r'$',['#fullCalendar'])
.callMethod('fullCalendar',[new JsObject.jsify({
'events': (start, end, timezone, callback){
print("called!");
var generated_events = new JsObject.jsify([{'title':'test','start':'2016-08-08'}]);
try{
callback.apply([generated_events]);
}catch(exception,stackTrace){
print("Caught exception!");
print(exception);
print(stackTrace);
}
},
'allDaySlot': false
//more options can go here
})]);
A JS function should be callable with
callback.apply([generated_events])
I would like to cache my records once they are received, but I can't figure out how. According to the Documentation you can just call this.store.push('model', record), but it doesn't seem to work. Ember requests the data from the server with each call of the route, I would like to do this only once and use the local store after it is fetched from the server.
If I try to debug it as suggested by the Documentation, i get that there is no cache:
Pd.__container__.lookup('store:main').recordCache
// --> undefined
This is my route (where I try to cache it):
Pd.ProductsRoute = Ember.Route.extend({
model: function () {
var promise = this.store.find('product');
var that = this;
promise.then(function(value) {
// Caching supposed to happen here
value.content.forEach(function(product){
that.store.push('product', product);
});
}, function(reason) {
// on rejection
});
return promise;
}
});
And this the according Adapter (seems to work fine):
Pd.ProductAdapter = DS.RESTAdapter.extend({
primaryKey: 'nid', // DOES NOT WORK BUT I CAN LIVE WITH THAT (SEE WORKAROUND)
findAll: function(store, type) {
var url = 'ws/rest/products';
return new Ember.RSVP.Promise(function(resolve, reject) {
jQuery.getJSON(url).then(function(data) {
Ember.Logger.debug("Received Products:"); // TRIGGERS EVERY TIME!
var srcPattern = /src=["']([^'"]+)/;
data.forEach(function(product){
product.id = product.nid;
product.field_image = srcPattern.exec(product.field_image)[1];
});
Ember.Logger.debug(data);
Ember.run(null, resolve, {product: data});
}, function(jqXHR) {
jqXHR.then = null; // tame jQuery's ill mannered promises
Ember.run(null, reject, jqXHR);
});
});
}
});
this.store.find('type') will always make a call to the server for records. If you only want to make a call to the server once do it in the ApplicationRoute and then instead of using find use the all filter inside of the route that's hit multiple times.
Pd.ApplicationRoute = Em.Route.extend({
model: function(params){
return Em.RSVP.hash({
product: this.store.find('product'),
somethingElse: otherPromise
})
}
});
Pd.ProductRoute = Em.Route.extend({
model: function(params){
return this.store.all('product');
}
});
If you just want to prep the store with your products, you don't even need to return it, or use it in the app route
Pd.ApplicationRoute = Em.Route.extend({
model: function(params){
this.store.find('product');
return {foo:'bar'}; // or return nothing, it doesn't matter
}
});
Lazy loading the models
App.ProductRoute = Ember.Route.extend({
hasPreLoaded: false,
model: function() {
if(this.get('hasPreLoaded')){
return this.store.all('product');
} else {
this.toggleProperty('hasPreLoaded');
return this.store.find('product');
}
}
});
Example
http://emberjs.jsbin.com/OxIDiVU/482/edit
You don't define the primary key on the adapter, it goes on the serializer
Pd.ProductSerializer = DS.RESTSerializer.extend({
primaryKey: 'nid'
});
The cache no longer lives there, it lives in this.store.typeMapFor(Pd.Product) or this.store.typeMaps.
The site is still referencing an older version of ember data until ember data 1.0 is released, I'll assume you're using 1.0 beta version. This document is more up to date https://github.com/emberjs/data/blob/master/TRANSITION.md
This is first time I am using a framework for development and stuck with the very first step.
I am converting a Flex application to Javascript application and using backbone as framework.
I have to load a text file which is in name value format.
<!doctype html>
<html>
<head>
<script src='http://cdnjs.cloudflare.com/ajax/libs/jquery/1.9.1/jquery.min.js'></script>
<script src='http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.4.4/underscore-min.js'></script>
<script src='http://cdnjs.cloudflare.com/ajax/libs/backbone.js/0.9.10/backbone-min.js'></script>
<script>
var ResourceBundleCollection = Backbone.Collection.extend({
url:'ResourceBundle.txt',
});
var resourceBundleCollection = new ResourceBundleCollection();
resourceBundleCollection.fetch();
</script>
</head>
<body>
</body>
</html>
The ResourceBundle.txt includes the content in following format
location_icon=../../abc/test.png
right_nav_arrow_image=assets/images/arrow.png
right_nav_arrow_image_visible=true
It is throwing following error
not well-formed
I could load the text file easily using JQuery and parse it
$.ajax({
type : "GET",
url : "ResourceBundle.txt",
datatype : "script",
success : resourceXMLLoaded
});
and parse it using the following code
var lines = txt.split("\n");
for(var i=0;i<lines.length;i++) {
if(lines[i].length > 5) {
var _arr = lines[i].split("=");
resourceBundleObj[$.trim(_arr[0])] = $.trim(_arr[1]);
}
}
Please advice how to achieve the same results in backbone.js
If you MUST use plain text to support this, you can override Backbone.Collection.parse to achieve what you need.
In addition to that, you may also want to create a ResourceBundleModel to host each item in the ResourceBundleCollection.
You can see a demo here: http://jsfiddle.net/dashk/66nkF/
Code for Model & Collection is here:
// Define a Backbone.Model that host each ResourceBundle
var ResourceBundleModel = Backbone.Model.extend({
defaults: function() {
return {
name: null,
value: null
};
}
});
// Define a collection of ResourceBundleModels.
var ResourceBundleCollection = Backbone.Collection.extend({
// Each collection should know what Model it works with, though
// not mandated, I guess this is best practice.
model: ResourceBundleModel,
// Replace this with your URL - This is just so we can demo
// this in JSFiddle.
url: '/echo/html/',
parse: function(resp) {
// Once AJAX is completed, Backbone will call this function
// as a part of 'reset' to get a list of models based on
// XHR response.
var data = [];
var lines = resp.split("\n");
// I am just reusing your parsing logic here. :)
for (var i=0; i<lines.length; i++) {
if (lines[i].length > 5) {
var _arr = lines[i].split("=");
// Instead of putting this into collection directly,
// we will create new ResourceBundleModel to contain
// the data.
data.push(new ResourceBundleModel({
name: $.trim(_arr[0]),
value: $.trim(_arr[1])
}));
}
}
// Now, you've an array of ResourceBundleModel. This set of
// data will be used to construct ResourceBundleCollection.
return data;
},
// Override .sync so we can demo the feature on JSFiddle
sync: function(method, model, options) {
// When you do a .fetch, method is 'read'
if (method === 'read') {
var me = this;
// Make an XHR request to get data
// Replace this code with your own code
Backbone.ajax({
url: this.url,
method: 'POST',
data: {
// Feed mock data into JSFiddle's mock XHR response
html: $('#mockData').text()
},
success: function(resp) {
options.success(me, resp, options);
},
error: function() {
if (options.error) {
options.error();
}
}
});
}
else {
// Call the default sync method for other sync method
Backbone.Collection.prototype.sync.apply(this, arguments);
}
}
});
Backbone is designed to work with a RESTful API through JSON natively. It however is a library that is flexible enough to fit your need, given enough customization.
By default a collection in Backbone will only accept a JSON formatted collection.
So you need to convert your input to JSON format:
[{"name": "name", "value": "value},
{"name": "name", "value": "value}, ...
]
Of course you can override the default behaviour:
Overriding backbone's parse function