I have the following routes defined in rails:
resources :accounts do
resources :transactions
end
This results in urls like:
/accounts/123/transactions/1
Is there an easy way to map this to a backbone model set up?
Turns out backbone quite easily supports this by nesting a collection in a model as follows:
var Account = Backbone.Model.extend({
initialize: function() {
this.transactions = new TransactionsCollection;
this.transactions.url = '/account/' + this.id + '/transactions';
this.transactions.bind("reset", this.updateCounts);
},
});
This achieves exactly what I wanted.
You can read more about it here: http://documentcloud.github.com/backbone/#FAQ-nested
It might not be an easy way but I think the best way is to use url and set it to a function like this:
var Transaction = Backbone.Model.extend({
url: function(){
var url = 'accounts/"+this.account_id+"/transactions';
if (this.id !== undefined){
url += "/"+this.id
}
return url;
}
});
Or maybe in coffeescript (as it is backbone+rails):
class Transaction extends Backbone.Model
url: ->
url = "accounts/#{#account_id}/transactions"
url += "/#{#id}" if #id != undefined
url
Oh and you could do it more like this(surely with deeper nesting it's better):
var url = ["accounts", this.account_id, "transactions"]
if (this.id !== undefined) url.push(this.id)
return url.join("/")
AFAIK there is now url utility in backbone, and it's not much enough pain for me so that I would search for one in some other library :)
Backbone does not directly support the creating of nested urls. You must use a function to dynamically calculate the resulting url of your nested object. For example:
var Account = Backbone.Model.extend({
initialize: function() {
this.transactions = new TransactionsCollection();
var self = this;
this.transactions.url = function () {return self.url + self.id + '/transactions';};
// etc
},
});
More information: http://documentcloud.github.com/backbone/#FAQ-nested
Just define the url of your model or (if you use one) your collection like so:
var MyModel = Backbone.Model.extend({
url: 'accounts/123/transactions'
});
or dynamically:
mymodel.url = 'accounts/' + accountId + '/transactions';
Those models or all models of a collection that is configured this way will now generate it's backend urls accordingly.
Detailed info:
Model:
http://documentcloud.github.com/backbone/#Model-url
Collection:
http://documentcloud.github.com/backbone/#Collection-url
Related
I'm trying to show some data retrieved by Odata Model on a XML View.
In Component.js, I create a model with service Url and it works as usual.
var oDataModel = new sap.ui.model.odata.v2.ODataModel("http://server:port/sap/opu/odata/SAP/ZWYMB_SRV", {
user:"abapleader",
password: "TH123456789a#",
headers: {
"sap-client": 300
},
useBatch:false
});
this.setModel(oDataModel, "oDataModel");
So far, I've managed to get data to master using model.read() function.
Now I need to show the detail view. My code for onInit event is below:
this.router.getRoute("zwm01detail").attachPatternMatched(this._onObjectMatched.bind(this));
},
_onObjectMatched: function(oEvent) {
var that = this;
var MaWorkDoc = oEvent.getParameter("arguments").MaWorkDoc;
this.getModel("oDataModel").metadataLoaded().then(function() {
var sPath = that.getModel("oDataModel").createKey("/WorkDocList", {
MaWorkDoc: MaWorkDoc,
Pernr: "0001"
});
console.log(sPath);
that.getView().bindElement({
path:sPath
});
});
The sPath as I printed out using console.log(sPath) : /WorkDocList(MaWorkDoc='1110000001',Pernr='0001'), which I think, is correct. I also implemented and tested the back-end using this path and basically things are fine. But I don't know why I cannot show data on the view.
Detail.view.xml:
<Text text="{/MaWorkDoc}" maxLines="0"/>
Any suggestions, please?
Because you have given your model a name you will need to use that name in the binding. i.e. <Text text="{oDataModel>/MaWorkDoc}" maxLines="0"/>
So I've been working around with the docs and figure out there is model parameter which helps.
To be more specific, I add declare model in view.bindElement as below:
that.getView().bindElement({
path:sPath,
//** oDataModel = name of the model
model: "oDataModel"
});
If there is any better solution, I'd very glad to know it. Thank you.!
I do not think the detail binding code should be inside the .metadataLoaded handler. Rather it should be directly inside _onObjectMatched.
I mean like this.
_onObjectMatched: function(oEvent) {
var that = this;
var MaWorkDoc = oEvent.getParameter("arguments").MaWorkDoc;
var sPath = that.getModel("oDataModel").createKey("/WorkDocList", {
MaWorkDoc: MaWorkDoc,
Pernr: "0001"
});
console.log(sPath);
that.getView().bindElement({
path:sPath
});
}
I have an MVC controller that accepts two string parameters but I will only ever use one or the other. I'm not sure how to best handle the situation. I need to find a way to pass a NULL for which ever parameter will not be used. The way I have it set up now passes the parameters get to the Action but the unused parameter is empty, I need it to be NULL and everything will work as I need. I know I could do an "if" statement and build the #URL.Action link dynamically but that seems clunky. I also saw some posts that suggest a custom route but I would like to hear from users here before I go that route. There must be an easier way.
My function to route to the new URL:
$('#poBtn').on('click', function (e) {
var poId = $('#PoLabel').val();
var reqId = $('#ReqLabel').val();
var url = '#Url.Action("ShipmentsByPo", "Shipping", new {po = "_poId_" , reqId = "_reqId_" })';
url = url.replace('_poId_', poId);
url = url.replace('_reqId_', reqId);
window.location.href = url;Action
})
Action:
public IEnumerable<ShippingHeaderVM> ShipmentsByPo(string po, string reqID )
{
object[] parameters = { po, reqID };
var shipmentsByPo = context.Database.SqlQuery<ShippingHeaderVM>("spSelectLOG_ShippingNoticeByPo {0},{1}",parameters);
return shipmentsByPo.ToList();
}
One possible solution would be to check inside the controller action if a parameter is empty, set it to null before using it.
Another possibility is to compose one or the other url on the client:
$('#poBtn').on('click', function (e) {
e.preventDefault();
var poId = $('#PoLabel').val();
var reqId = $('#ReqLabel').val();
var url = '#Url.Action("ShipmentsByPo", "Shipping", new { po = "_poId_" })';
if (poId != '') { // Might need to adjust the condition based on your requirements
url = url.replace('_poId_', encodeURIComponent(poId));
} else {
url = '#Url.Action("ShipmentsByPo", "Shipping", new { reqId = "_reqId_" })';
url = url.replace('_reqId_', encodeURIComponent(reqId));
}
window.location.href = url;
});
My collection and model like this:
detail_userid = 0;
detail_contactid = 0;
var ContactDetail = Backbone.Model.extend({
urlRoot: URL_CONTACTS1+detail_userid+"/"+detail_contactid
});
var ContactDetailCollection = Backbone.Collection.extend({
model: ContactDetail,
url: URL_CONTACTS1+detail_userid+"/"+detail_contactid
})
The entrance is:
ContactDetailManagePageModel.prototype.init = function(m,n){
detail_userid = m;
detail_contactid = n;
var myContactDetails = new ContactDetailCollection();
var contactDetailListView = new ContactDetailListView({
collection: myContactDetails
});
myContactDetails.fetch({reset:true});
}
But when it runs,the url is :http://localhost:8080/ws/users/contacts/0/0,it means that the assignment to detail_userid and detail_contactid is unsuccessful,I don't know why.
Hope for your help.Thanks.
I think you are statically definining the urlRoot and url properties before you are running the init of the PageModel (not quite sure where you are getting m and n from though...)
Both url and urlRoot can be a function, so you can pass in options during instantiation and have them dynamically set on the model.
Simple example covering defining the collection and then creating one
var ContactDetailCollection = Backbone.Collection.extend({
model: ContactDetail,
url: function(){
return URL_CONTACTS1 + this.options.detail_userid + "/" + this.options.detail_contactid;
}
});
var myContactDetails = new ContactDetailCollection({
detail_userid: foo,
detail_contactid: bar
});
As I mentioned, I'm not sure what your init function is doing, I'm guessing it's something custom from your app that I don't need to worry about.
I'm fairly sure the main thing to take away is to set url and urlRoot dynamically
I would fulfill the accepted answer with few remarks.
First parameter when initializing Backbone.Collection is array of models, then options. To create an empty collection with options you should do next
var c = new Backbone.Collection(null, {opt1: val1, opt2: val2});
Actually, you can't access this.options in url function, bec. there are no options like in a model. What you can do, is assign required properties from options upon initialization.
initialize: function (models, options) {
// `parseInt()` is used for consistency that `id` is numeric, just to be sure
this.detail_userid = parseInt(options.detail_userid);
this.detail_contactid = parseInt(options.detail_contactid);
}
Later you can access them like this:
url: function() {
return URL_CONTACTS1 + this.detail_userid + "/" + this.detail_contactid;
}
I wanted to use the HATEOAS href from one model to fetch data of another model. It worked to simply set the url on the newly created collection instead of defining it right away in the constructor.
var DailyMeasuresCollection = Backbone.Collection.extend({
//url : set dynamically with collection.url = url
model : DailyMeasuresModel,
parse : function(data) {
return data._embedded.dailyMeasures;
}
});
var DailyMeasuresTopicListItemView = Backbone.View.extend({
//...
events : {
'click .select-topic' : 'onClick'
},
onClick : function() {
var topicMeasures = new DailyMeasuresCollection()
topicMeasures.url = this.model.attributes._links.measures.href // <- here assign
var topicMeasuresView = new DailyMeasuresListView({
collection : topicMeasures
});
topicMeasures.fetch()
}
});
In .NET MVC there is #Url.Action() and in RoR there is url_for()
I could not find similar url building helper in angularjs.
I'm already providing everything that is needed to build url to $routeProvider so something like: $routeProvider.urlFor("MyCtrl", {id: 5}) could be nice to have.
My main goal here is to avoid hardcoded urls in viewes and other places and to avoid repeating url/routes patterns twice.
UPDATE:
Seems like it's hard to explain what i want so here is exact example of what i want:
Instead of writing this in viewes:
<a ng-href="/product/5">foo</a>
I want to write this:
<a ng-href="urlFor("ProductCtrl", {id:5})">foo</a>
So if later i decide to change path of ProductCtrl I would not have to update url in this a element.
What would be good solution for my goals?
You could try with something like the following (just came up with it) inside your main module's run() block:
app.run(function($route, $rootScope)
{
$rootScope.path = function(controller, params)
{
// Iterate over all available routes
for(var path in $route.routes)
{
var pathController = $route.routes[path].controller;
if(pathController == controller) // Route found
{
var result = path;
// Construct the path with given parameters in it
for(var param in params)
{
result = result.replace(':' + param, params[param]);
}
return result;
}
}
// No such controller in route definitions
return undefined;
};
});
This will extend the root scope of the application with the new path() function - so it can be used anywhere in the application. It uses the $route service to get the controller names and the corresponding paths, so you won't have to repeat yourself.
Example usage:
{{ path('LibraryController', { bookId : 'x', chapterId : 'y' }) }}
Live example at: http://plnkr.co/edit/54DfhPK2ZDr9keCZFPhk?p=preview
There are numerous approaches...a custom directive or ng-click to modify $location, or using a function in ng-href to parse the url from object and have it placed as href in an <a> tag.
Example using ng-href:
HTML:
<li ng-repeat="item in items">
<a ng-href="{{url(item)}}">{{item.txt}}</a>
</li>
JS:
function Ctrl($scope){
$scope.items=[
{id:1,txt:'foo'},
{id:2,txt:'bar'}
];
$scope.url=function(item){
return '#/'+item.id
}
}
Example using ng-click and $location
HTML:
<a ng-click="newPath(item)">{{item.txt}}</a>
JS:
function Ctrl($scope){
$scope.items=[
{id:1,txt:'foo'},
{id:2,txt:'bar'}
];
$scope.newPath=function(item){
$location.path('/'+item.id)
}
}
DEMO: http://jsbin.com/ovemaq/3
In one of recent project I came up to this solution it helped me to solve my needs right in time. It will be interesting to help you to improve it to fit your usecase.
1. Move routes definition to config:
...
ROUTES: {
PAGE1: '/page1/:id',
PAGE2: '/page2/:id/:name'
},
...
2. Define routes, using values from config:
app.config(['$routeProvider', 'config', function ($routeProvider, config) {
$routeProvider.when('/', {
templateUrl: 'partials/home.html',
controller: 'HomeCtrl'
});
$routeProvider.when(config.ROUTES.PAGE1, {
templateUrl: 'partials/page1.html',
controller: 'PageOneCtrl'
});
...
$routeProvider.otherwise({
redirectTo: '/'
});
}]);
3. Set up service to provide functionality to create urls:
services.factory('routes', function (config) {
// populates `:param` placeholder in string with hash value
var parseString = function (string, parameters) {
if (!string) return '';
if (!parameters) return string;
for (var index in parameters) {
if (!parameters.hasOwnProperty(index)) continue;
string = string.replace(':' + index, parameters[index]);
}
return string;
};
return {
getPage1Link: function (urlParams) {
return '#' + parseString(config.ROUTES.PAGE1, urlParams);
}
};
});
== Drawbacks ==
With this approach I had to define getter for each route (there were less then 5, and development speed was vital)
It appears that what you want is the ui-route directive which has just been recently added to the angularui project. There is a nice set of out-of-the-box options.
I want to build a client for my REST-Service using Knockout.js.
I have a lot of Repositorys i want to access through different urls - so i came up with this solution using the Revealing-Prototype-Pattern.
My problem: I can not find out how to map the ItemsProperty with my "data" i receive from my service.
var Repository = function (url) {
this.Url = url;
this.Items = ko.observableArray([]);
this.PendingItems = ko.observableArray([]);
};
Repository.prototype = function () {
var
getAllItems = function () {
var self = this;
$.getJSON(self.Url, function (data) {
// data=[{"Id":1,"Name":"Thomas","LastName":"Deutsch"},{"Id":2,"Name":"Julia","LastName":"Baumeistör"}]
ko.mapping.fromJS(data, self.Items);
});
},
...
// i call it like this:
customerRepository = new Repository('http://localhost:9200/Customer');
customerRepository.getAllItems();
I think the problem is in this: ko.mapping.fromJS(data, self.Items); but i can not find the right way to do it.
Question: what am i doing wrong? i have found an example - and they are doing the same i think: http://jsfiddle.net/jearles/CGh9b/
I believe that the two argument version of fromJS is only used for objects that were previously mapped, i.e they had an implicit empty mapping options object. Since your mapping is the first time it has been run, it needs to provider that empty options object like so.
ko.mapping.fromJS(data, {}, self.Items);
http://jsfiddle.net/madcapnmckay/j7Qxh/1/
Hope this helps.