In my split app the detail view does not bind any model.
In the component.js I instantiate a named model like this:
// creation and setup of the oData model
var oConfig = {
metadataUrlParams: {},
json: true,
defaultBindingMode : "TwoWay",
defaultCountMode : "Inline",
useBatch : false
}
// ### tab-employee ###
var oModelEmpl = new sap.ui.model.odata.v2.ODataModel("/sap/opu/odata/sap/EMP_SRV"), oConfig);
oModelEmpl.attachMetadataFailed(function() {
this.getEventBus().publish("Component", "MetadataFailedEMPL");
}, this);
this.setModel(oModelEmpl, "EMPL");
The method onSelect in der master-view controller is fired by clicking on an listitem.
onSelect: function(oEvent) {
this.showDetail(oEvent.getParameter("listItem") || oEvent.getSource());
}
This will call the method showDetail
showDetail: function(oItem) {
var bReplace = jQuery.device.is.phone ? false : true;
this.getRouter().navTo("detail", {
from: "master",
entity: oItem.getBindingContext('EMPL').getPath().substr(1),
}, bReplace);
},
In the controller of the detail-view I've these two methods for updating the binding. onRouteMatched calls bindView, where I get the error-message TypeError: oView.getModel(...) is undefined.
onRouteMatched: function(oEvent) {
var oParameters = oEvent.getParameters();
jQuery.when(this.oInitialLoadFinishedDeferred).then(jQuery.proxy(function() {
var oView = this.getView();
if (oParameters.name !== "detail") {
return;
}
var sEntityPath = "/" + oParameters.arguments.entity;
this.bindView(sEntityPath);
}, this));
},
bindView: function(sEntityPath) {
var oView = this.getView();
oView.bindElement(sEntityPath);
//Check if the data is already on the client
if (!oView.getModel().getData(sEntityPath)) {
// Check that the entity specified was found.
oView.getElementBinding().attachEventOnce("dataReceived", jQuery.proxy(function() {
var oData = oView.getModel().getData(sEntityPath);
if (!oData) {
this.showEmptyView();
this.fireDetailNotFound();
} else {
this.fireDetailChanged(sEntityPath);
}
}, this));
} else {
this.fireDetailChanged(sEntityPath);
}
},
I've tried to implement this split app relative to the template generated by WebIDE. Any idea what is missing?
As you wrote yourself, you are creating a "named Model" with the name "EMPL".
In the Controller you have to use the same name to get the Model:
this.getView().getModel("EMPL");
Likewise when calling bindElement() you have to give the model name:
// Assuming sEntityPath = "/items/0"
this.getView().bindElement("EMPL>" + sEntityPath);
Related
I am currently working on an app using firebase and angularJS (ionic). Basically this is a car management app, so you have people sharing their cars with others. I tried to structure the data as flat as possible to be efficient. My issue here is that if without problem I can display the list of the car_id of the different cars shared with the logged user, I can't find a way to display the list of cars shared with the user displaying the year and the model.
Thank you in advance for your help !
{
"rules": {
"users": {
".write": true,
"$uid": {
".read": "auth != null && auth.uid == $uid"
},
"cars": {
"car_id":true,
"role":true // Owner, borower...
}
},
"cars": {
"car_id":true,
"model":true,
"year":true
}
}
}
carapp.controller("carsController", function($scope, $firebaseObject, $ionicPopup, $ionicHistory) {
$ionicHistory.clearHistory();
$scope.list = function() {
frbAuth = frb.getAuth();
if(frbAuth) {
var userObject = $firebaseObject(frb.child("users/" + frbAuth.uid));
userObject.$bindTo($scope, "user");
$scope.cars = frb.child("cars");
}}
$scope.createCar = function() {
$ionicPopup.prompt({
model: 'Create a new car',
inputType: 'text'
})
.then(function(result) {
if(result !== "") {
var newCar = $scope.cars.push({
model: result
})
var newCarId = newCar.key();
$scope.user.cars.push({car_id: newCarId, role: "owner" });
} else {
console.log("Action not completed");
}
});
}
});
<div class="list">
<a ng-repeat="car in user.cars" >
<h2>{{car.car_id}}</h2> ----> works fine !
<h2>{{car.model}}</h2> ----> How to get this working ?
<h2>{{car.year}}</h2> ----> How to get this working ?
</a>
</div>
In the users/ path, begin by storing the list of cars by index, instead of in an array. So your structure would be:
{
"users": {
"kato": {
"cars": {
"DeLorean": true
}
}
},
"cars": {
"DeLorean": {
model: "DeLorean",
year: "1975"
}
}
}
To join this using AngularFire, you have several approaches available. An AngularFire-only solution might look like this, taking advantage of $extend:
app.factory('CarsByUser', function($firebaseArray) {
return $firebaseArray.$extend({
$$added: function(snap) {
return new Car(snap);
},
$$updated: function(snap) {
// nothing to do here; the value of the index is not used
},
$$removed: function(snap) {
this.$getRecord(snap.key()).destroy();
},
// these could be implemented in a manner consistent with the
// use case and above code, for simplicity, they are disabled here
$add: readOnly,
$save: readOnly
});
var carsRef = new Firebase(...).child('cars');
function Car(snap) {
// create a reference to the data for a specific car
this.$id = snap.key();
this.ref = carsRef.child(this.$id);
// listen for changes to the data
this.ref.on('value', this.updated, this);
}
Car.prototype.updated = function(snap) {
this.model = data.model;
this.year = data.year;
}
Car.prototype.destroy = function() {
this.ref.off('value', this.meta, this);
};
function readOnly() { throw new Error('This is a read only list'); }
});
app.controller('...', function($scope, CarsByUser, authData) {
// authenticate first, preferably with resolve
var ref = new Firebase(...).child(authData.uid);
$scope.cars = CarsByUser($scope);
});
For a more sophisticated and elegant approach, one could utilize NormalizedCollection and pass that ref into the AngularFire array:
app.controller('...', function($scope, $firebaseArray) {
var ref = new Firebase(...);
var nc = new Firebase.util.NormalizedCollection(
ref.child('users/' + authData.uid),
ref.child('cars')
)
.select('cars.model', 'cars.year')
.ref();
$scope.cars = $firebaseArray(nc);
});
I am currently working on an app using firebase and angularJS (ionic). Basically this is a car management app, so you have people sharing their cars with others. I tried to structure the data as flat as possible to be efficient. My issue here is that if without problem I can display the list of the car_id of the different cars shared with the logged user, I can't find a way to display the list of cars shared with the user displaying the year and the model.
Thank you in advance for your help !
{
"rules": {
"users": {
".write": true,
"$uid": {
".read": "auth != null && auth.uid == $uid"
},
"cars": {
"car_id":true,
"role":true // Owner, borower...
}
},
"cars": {
"car_id":true,
"model":true,
"year":true
}
}
}
carapp.controller("carsController", function($scope, $firebaseObject, $ionicPopup, $ionicHistory) {
$ionicHistory.clearHistory();
$scope.list = function() {
frbAuth = frb.getAuth();
if(frbAuth) {
var userObject = $firebaseObject(frb.child("users/" + frbAuth.uid));
userObject.$bindTo($scope, "user");
$scope.cars = frb.child("cars");
}}
$scope.createCar = function() {
$ionicPopup.prompt({
model: 'Create a new car',
inputType: 'text'
})
.then(function(result) {
if(result !== "") {
var newCar = $scope.cars.push({
model: result
})
var newCarId = newCar.key();
$scope.user.cars.push({car_id: newCarId, role: "owner" });
} else {
console.log("Action not completed");
}
});
}
});
<div class="list">
<a ng-repeat="car in user.cars" >
<h2>{{car.car_id}}</h2> ----> works fine !
<h2>{{car.model}}</h2> ----> How to get this working ?
<h2>{{car.year}}</h2> ----> How to get this working ?
</a>
</div>
In the users/ path, begin by storing the list of cars by index, instead of in an array. So your structure would be:
{
"users": {
"kato": {
"cars": {
"DeLorean": true
}
}
},
"cars": {
"DeLorean": {
model: "DeLorean",
year: "1975"
}
}
}
To join this using AngularFire, you have several approaches available. An AngularFire-only solution might look like this, taking advantage of $extend:
app.factory('CarsByUser', function($firebaseArray) {
return $firebaseArray.$extend({
$$added: function(snap) {
return new Car(snap);
},
$$updated: function(snap) {
// nothing to do here; the value of the index is not used
},
$$removed: function(snap) {
this.$getRecord(snap.key()).destroy();
},
// these could be implemented in a manner consistent with the
// use case and above code, for simplicity, they are disabled here
$add: readOnly,
$save: readOnly
});
var carsRef = new Firebase(...).child('cars');
function Car(snap) {
// create a reference to the data for a specific car
this.$id = snap.key();
this.ref = carsRef.child(this.$id);
// listen for changes to the data
this.ref.on('value', this.updated, this);
}
Car.prototype.updated = function(snap) {
this.model = data.model;
this.year = data.year;
}
Car.prototype.destroy = function() {
this.ref.off('value', this.meta, this);
};
function readOnly() { throw new Error('This is a read only list'); }
});
app.controller('...', function($scope, CarsByUser, authData) {
// authenticate first, preferably with resolve
var ref = new Firebase(...).child(authData.uid);
$scope.cars = CarsByUser($scope);
});
For a more sophisticated and elegant approach, one could utilize NormalizedCollection and pass that ref into the AngularFire array:
app.controller('...', function($scope, $firebaseArray) {
var ref = new Firebase(...);
var nc = new Firebase.util.NormalizedCollection(
ref.child('users/' + authData.uid),
ref.child('cars')
)
.select('cars.model', 'cars.year')
.ref();
$scope.cars = $firebaseArray(nc);
});
I try to define some steps ( I call them pages ) in an array and each page should have the possibility to call a function on enter.
My page definition is within an array of pages and the nextHandler ( custom method ) sets the current page index and tries to call the defined function. My React class looks ( abbreviated ) like this:
var App = React.createClass({
pageDefinitions: [
{
title: "Page 1",
enterFunction: this.enterPageOne
}
],
enterPageOne: function() {
console.log("Something useful here");
},
nextHandler: function() {
var st = this.getState();
st.currentPageIndex = st.currentPageIndex + 1;
this.setState(st);
var page = this.pageDefinitions[this.state.currentPageIndex];
console.log(typeof page.enterFunction);
console.log(page.title);
if ( typeof page.enterFunction === "function") {
page.enterFunction();
}
},
getInitialState: function() {
return {
currentPageIndex = -1
};
},
render: function() {
return (
<div>Left out</div>
}
});
While title correctly prints out on console, the function is always undefined. How can I provide a function reference in my array?
Edit: As #gillesc and #justin-morgan pointed out it is a problem of scope ( this is pageDefinition, not the class )
Edit 2: Found solution, I changed pageDefinitions to getPageDefinitions() like this:
getPageDefinitions: funtion() {
var self = this;
return [
{
title: "Page 1",
enterFunction: selfenterPageOne
}
];
}
this.enterPageOne is undefined because this isn't bound to what you think it is. Try this:
var enterPageOne = function() {
console.log("Something useful here");
};
var App = React.createClass({
pageDefinitions: [
{
title: "Page 1",
enterFunction: enterPageOne
}
],
//...etc.
if you don't want a global function outside of App component, you can try this. It's not catchy but you can manage it in scope.
nextHandler: function() {
...
var page = this.pageDefinitions[this.state.currentPageIndex];
//register enterPageOne function into the array
page.enterFunction = this.enterOnePage;
console.log(typeof page.enterFunction);
console.log(page.title);
if ( typeof page.enterFunction === "function") {
page.enterFunction();
}
},
Do not define an array on class level but a function that returns an array:
getPageDefinitions: funtion() {
var self = this;
return [
{
title: "Page 1",
enterFunction: selfenterPageOne
}
];
}
Please note: As a downside , this will create every time a new array when called. This should be considered
I'm using this code to fetch a model from a server:
var id = args.model.get('id');
var options = {
action: 'getSubscriber',
address: args.model.get('address'),
silent: true
};
new Subscriber({ id: id }, options).fetch({
success: function(model, response) {
console.log(response);
console.log(model);
}
});
the response object contains all the data I need whereas model stores the data not as its direct attributes but as changed object. Is it wrong?
Usually I access model attributes with help of model.get('name') call. How do I access fresh attributes in that case? Should it be model.changed.thePropertyIwantToAccess?
You can use this change event
this.model.on('change', function () {
var changedAttributes = this.model.changedAttributes();
//Process the changed attributes
}, this);
Bind this events in the initialize function of the View
Ended up with this:
var Subscriber = Backbone.Model.extend({
defaults: {
id: null,
name: null,
status: null
// ...
},
initialize: function(attributes, options) {
var _this = this;
this.options = options || {};
this.on('change', function() {
_this.set(_this.changedAttributes()['0']);
});
}
// ...
How can I set additional data in an action function in a Meteor Application that uses IronRouter ? See comments in emailWelcome and emailContract functions below...
Code:
EmailController = RouteController.extend({
template: 'emailPage',
waitOn: function() {
return [
Meteor.subscribe('customers'),
];
},
data: function() {
var request = Requests.findOne(this.params._id);
if (!request)
return;
var customer = Customers.findOne({'_id': request.customerId});
if (!customer)
return;
return {
sender: Meteor.user(),
recipient: Customers.findOne({_id:Session.get('customerId')})
};
},
emailWelcome: function() {
// Set var in the context so that emailTemplate = 'welcomeEmail' here
this.render('emailPage');
},
emailContract: function() {
// Set var in the context so that emailTemplate = 'contractEmail' here
this.render('emailPage');
}
});
You can get access to the data with this.getData() in your action functions:
emailWelcome: function() {
var data = this.getData(); // get a reference to the data object
data.emailTemplate = 'welcomeEmail';
this.render('emailPage');
},
emailContract: function() {
var data = this.getData(); // get a reference to the data object
data.emailTemplate = 'contractEmail';
this.render('emailPage');
}
be careful not to call this.data(), as that will regenerate the
data instead of getting you a reference to the already generated data
object.
also be careful not to call this.setData(newData) within an action as that will invalidate the old data object, initiating a reactivity reload, and lead to an infinite loop!