how to resolve optional url path using ng-resource - rest

There are restful APIs, for instance:
/players - to get list for all players
/players{/playerName} - to get info for specific player
and I already have a function using ng-resource like:
function Play() {
return $resource('/players');
}
Can I reuse this function for specific player like:
function Play(name) {
return $resource('/players/:name', {
name: name
});
}
so I want to...
send request for /players if I didn't pass name parameter.
send request for /players/someone if I passed name parameter with someone
Otherwise, I have to write another function for specific play?

Using ngResource it's very, very simple (it's basically a two-liner). You don't need even need to create any custom actions here*.
I've posted a working Plunkr here (just open Chrome Developer tools and go to the Network tab to see the results).
Service body:
return $resource('/users/:id/:name', { id:'#id', name: '#name' })
Controller:
function( $scope, Users ){
Users.query(); // GET /users (expects an array)
Users.get({id:2}); // GET /users/2
Users.get({name:'Joe'}); // GET /users/Joe
}
of course, you could, if you really wanted to :)

This is how I did it. This way you don't have to write a custom resource function for each one of your endpoints, you just add it to your list resources list. I defined a list of the endpoints I wanted to use like this.
var constants = {
"serverAddress": "foobar.com/",
"resources": {
"Foo": {
"endpoint": "foo"
},
"Bar": {
"endpoint": "bar"
}
}
}
Then created resources out of each one of them like this.
var service = angular.module('app.services', ['ngResource']);
var resourceObjects = constants.resources;
for (var resourceName in resourceObjects) {
if (resourceObjects.hasOwnProperty(resourceName)) {
addResourceFactoryToService(service, resourceName, resourceObjects[resourceName].endpoint);
}
}
function addResourceFactoryToService (service, resourceName, resourceEndpoint) {
service.factory(resourceName, function($resource) {
return $resource(
constants.serverAddress + resourceEndpoint + '/:id',
{
id: '#id',
},
{
update: {
method: 'PUT',
params: {id: '#id'}
},
}
);
});
}
The nice thing about this is that it takes 2 seconds to add a new endpoint, and I even threw in a put method for you. Then you can inject any of your resources into your controllers like this.
.controller('homeCtrl', function($scope, Foo, Bar) {
$scope.foo = Foo.query();
$scope.bar = Bar.get({id:4});
}

Use Play.query() to find all players
Use Play.get({name:$scope.name}) to find one player

Related

how to get query parameter in action2 in sails

I dont know how to set up route according to REST for the search request url like this
localhost:1337/system?id=10
After that, how can we get parameter from the above request in action 2 like this
action2(inputs,exits)
I want to follow best practice for web api in sails. Please hint me to solve and recommend any handy documents.
Thanks in advance
For the Actions2 format, you'd use inputs.id, after properly defining the inputs, of course. For more information, consult the documentation on Actions and Controllers. This would need more finishing, but it gets your input and a function. You'd want to add in your exits, and some real logic, of course, and probably some decent descriptions.
module.exports = {
inputs: {
id: {
type: 'number',
required: true
}
},
exits: {
success: { ... },
error: { ... }
},
fn: async function (inputs, exits) {
var myId = inputs.id;
return exits.success(myId);
}
}

Sails 1.0 POST route gives 403 error

In my routes.js file, I have defined a route like this:
'PUT /api/v1/entrance/login': { action: 'entrance/login' },
'POST /api/v1/entrance/signup': { action: 'entrance/signup' },
'POST /api/v1/entrance/send-password-recovery-email': { action: 'entrance/send-password-recovery-email' },
'POST /api/v1/entrance/update-password-and-login': { action: 'entrance/update-password-and-login' },
'POST /api/v1/deliver-contact-form-message': { action: 'deliver-contact-form-message' },
'POST /api/v1/getEventsForICalUrl': 'IcalController.getEvents',
I have just used the default generated code and added the last route for getEventsForIcalUrl.
I created an IcalController inside the controllers directory and it has an action getEvents which simply renders a json like this:
module.exports = {
/**
* `IcalController.getEvents()`
*/
getEvents: async function (req, res) {
console.log("hit here");
let events = [];
for (var i = 0; i < 20; i++) {
events.push({foo: "bar" + i});
}
return res.json({
events: events
});
}
};
My problem is that whenever i try to access this controller from the client side, it gives 403 forbidden error.
When I change the route from POST to GET, it works as expected(ofc I am using proper GET/POST request from client end for the route).
Not sure what is breaking.
I also checked the logs. Its printing "hit here" when I use the GET.
In my policy file looks like this(as it was generated. I did not change it):
module.exports.policies = {
'*': 'is-logged-in',
// Bypass the `is-logged-in` policy for:
'entrance/*': true,
'account/logout': true,
'view-homepage-or-redirect': true,
'deliver-contact-form-message': true,
};
And my "is-logged-in" policy file is this:
module.exports = async function (req, res, proceed) {
// If `req.me` is set, then we know that this request originated
// from a logged-in user. So we can safely proceed to the next policy--
// or, if this is the last policy, the relevant action.
// > For more about where `req.me` comes from, check out this app's
// > custom hook (`api/hooks/custom/index.js`).
console.log("req.me=" + req.me);
if (req.me) {
return proceed();
}
//--•
// Otherwise, this request did not come from a logged-in user.
return res.unauthorized();
};
I just put that console.log in this file. others are as they were by default generation from sails new.
The logs show that using POST, this one does not get hit either.(I dont see the "req.me=".. in console.logs.) But this one gets hit when Using GET.
It seems that the route is not working for POST requests. I wonder if its an error in sails js itself or I am doing something wrong.
There are at least two ways how to solve this.
Probably you are using csrf. If you do, your config probably includes this:
module.exports.security = { // With Sails <1.01 this probably is in another file
csrf: true
};
And (if you are using sails v1.01), you should make this route:
'GET /csrfToken': { action: 'security/grant-csrf-token' },
So, to get data on your frontend, you just:
function get_some_records(object_with_data) {
$.get("/csrfToken", function (data, jwres) {
if (jwres != 'success') { return false; }
msg = {
some_data: object_with_data,
_csrf: data._csrf
};
$.post("get_some_records", msg, function(data, status){});
});
}
But if you are using some background jobs, Sails wont give you csrf easily(there is some way probably). So , you just create a route like this:
'post /get_some_records': {
action: 'home/get_some_records',
csrf: false
}
You probably use a Rest client to test (like Postman). Simply disable the csrf protection. in the /config/security.js file
csrf: false

What's the correct Protractor's syntax for Page Objects?

I've come across different types of syntax for Protractor's Page Objects and I was wondering, what's their background and which way is suggested.
This is the official PageObject syntax from Protractor's tutorial. I like it the most, because it's clear and readable:
use strict;
var AngularHomepage = function() {
var nameInput = element(by.model('yourName'));
var greeting = element(by.binding('yourName'));
this.get = function() {
browser.get('http://www.angularjs.org');
};
this.setName = function(name) {
nameInput.sendKeys(name);
};
this.getGreeting = function() {
return greeting.getText();
};
};
module.exports = AngularHomepage;
However, I've also found this kind:
'use strict';
var AngularPage = function () {
browser.get('http://www.angularjs.org');
};
AngularPage.prototype = Object.create({}, {
todoText: { get: function () { return element(by.model('todoText')); }},
addButton: { get: function () { return element(by.css('[value="add"]')); }},
yourName: { get: function () { return element(by.model('yourName')); }},
greeting: { get: function () { return element(by.binding('yourName')).getText(); }},
todoList: { get: function () { return element.all(by.repeater('todo in todos')); }},
typeName: { value: function (keys) { return this.yourName.sendKeys(keys); }} ,
todoAt: { value: function (idx) { return this.todoList.get(idx).getText(); }},
addTodo: { value: function (todo) {
this.todoText.sendKeys(todo);
this.addButton.click();
}}
});
module.exports = AngularPage;
What are the pros/cons of those two approaches (apart from readability)? Is the second one up-to-date? I've seen that WebdriverIO uses that format.
I've also heard from one guy on Gitter that the first entry is inefficient. Can someone explain to me why?
Page Object Model framework becomes popular mainly because of:
Less code duplicate
Easy to maintain for long
High readability
So, generally we develop test framework(pom) for our convenience based on testing scope and needs by following suitable framework(pom) patterns. There are NO such rules which says that, strictly we should follow any framework.
NOTE: Framework is, to make our task easy, result oriented and effective
In your case, 1st one looks good and easy. And it does not leads to confusion or conflict while in maintenance phase of it.
Example: 1st case-> element locator's declaration happens at top of each page. It would be easy to change in case any element locator changed in future.
Whereas in 2nd case, locators declared in block level(scatter across the page). It would be a time taking process to identify and change the locators if required in future.
So, Choose which one you feel comfortable based on above points.
I prefer to use ES6 class syntax (http://es6-features.org/#ClassDefinition). Here, i prepared some simple example how i work with page objects using ES6 classes and some helpful tricks.
var Page = require('../Page')
var Fragment = require('../Fragment')
class LoginPage extends Page {
constructor() {
super('/login');
this.emailField = $('input.email');
this.passwordField = $('input.password');
this.submitButton = $('button.login');
this.restorePasswordButton = $('button.restore');
}
login(username, password) {
this.email.sendKeys(username);
this.passwordField.sendKeys(password);
this.submit.click();
}
restorePassword(email) {
this.restorePasswordButton.click();
new RestorePasswordModalWindow().submitEmail(email);
}
}
class RestorePasswordModalWindow extends Fragment {
constructor() {
//Passing element that will be used as this.fragment;
super($('div.modal'));
}
submitEmail(email) {
//This how you can use methods from super class, just example - it is not perfect.
this.waitUntilAppear(2000, 'Popup should appear before manipulating');
//I love to use fragments, because they provides small and reusable parts of page.
this.fragment.$('input.email').sendKeys(email);
this.fragment.$('button.submit')click();
this.waitUntilDisappear(2000, 'Popup should disappear before manipulating');
}
}
module.exports = LoginPage;
// Page.js
class Page {
constructor(url){
//this will be part of page to add to base URL.
this.url = url;
}
open() {
//getting baseURL from params object in config.
browser.get(browser.params.baseURL + this.url);
return this; // this will allow chaining methods.
}
}
module.exports = Page;
// Fragment.js
class Fragment {
constructor(fragment) {
this.fragment = fragment;
}
//Example of some general methods for all fragments. Notice that default method parameters will work only in node.js 6.x
waitUntilAppear(timeout=5000, message) {
browser.wait(this.EC.visibilityOf(this.fragment), timeout, message);
}
waitUntilDisappear(timeout=5000, message) {
browser.wait(this.EC.invisibilityOf(this.fragment), timeout, message);
}
}
module.exports = Fragment;
// Then in your test:
let loginPage = new LoginPage().open(); //chaining in action - getting LoginPage instance in return.
loginPage.restorePassword('batman#gmail.com'); // all logic is hidden in Fragment object
loginPage.login('superman#gmail.com')

Umbraco cannot find the route in backoffice

I've used Umbraco 7.3 in my project. I created a custom data type but when I want to call a Surfacecontroller in here is HelloSurfaceController or Hello2SurfaceController, I got an error in umbraco backoffice that said Request error: The URL returned a 404 (not found):
I studied some articles about routing but I couldn't solve my problem. I don't know that where I did wrong.
How can I solve this problem?
Reply.controller.js:
angular.module("umbraco")
.controller("Reply.controller", function ($scope, $http) {
$scope.SendReply = function () {
var sendTo = $("#Email").val();
var textMessage = $("#TextMessage").val();
$scope.xxx = "I'm here!";
var data = { SendTo: sendTo, TextMessage: textMessage };
// ~/Hello2Surface/ReplyMessage ---> Cannot find this URL
$http.post("~/App_Plugins/Reply/HelloSurface/ReplyMessage") // Can not find this URL
.then(function (response) {
alert("YES!");
//TODO:
});
}
});
SurfaceController
namespace Jahan.Nuts.Web.Mvc.UmbracoCms.App.App_Plugins.Reply
{
public class HelloSurfaceController : SurfaceController
{
[HttpPost][ChildActionOnly]
public ActionResult ReplyMessage()
{
//TODO: how should be write this method that be proper for getting data from angularjs?
return null;
}
}
}
package.manifest
{
propertyEditors: [
{
alias: "Send.Reply",
name: "Send Reply",
editor:{
view:"~/App_Plugins/Reply/Reply.html"
},
}
]
,
javascript:[
'~/App_Plugins/Reply/Reply.controller.js'
]
}
Reply.html
<div ng-controller="Reply.controller">
<div style="width: 100%;">
<input type="button" value="Send Reply" title="SendReply" name="Send Reply" ng-click="SendReply()" />
</div>
<div>
<input type="text" ng-model="xxx" name="message" />
</div>
Error in umbraco backoffice:
Take a closer look at the documentation - in particular the Plugin-based SurfaceControllers section:
https://our.umbraco.org/documentation/Reference/Routing/surface-controllers
try doing this (note the PluginController attribute):
namespace Jahan.Nuts.Web.Mvc.UmbracoCms.App.App_Plugins.Reply
{
[PluginController("Reply")]
public class HelloSurfaceController : SurfaceController
{
[HttpPost][ChildActionOnly]
public ActionResult ReplyMessage()
{
//TODO: how should be write this method that be proper for getting data from angularjs?
return null;
}
}
}
Other Notes:
You don't need to include "Surface" in the controller name anymore - simply calling it HelloController is enough.
Don't use a SurfaceController for Api calls if you're using it with AngularJS - Better to use an UmbracoApiController instead. Check out https://our.umbraco.org/documentation/Reference/Routing/WebApi/ for more information (including notes on where to expect the Api Endpoint to be)
You might also want to re-locate your controller so it's in a more conventional spot. There's no problem with putting it in the ~/Controllers directory even if it is a Plugin Controller.
Edit: Added "correct" way to do this:
As noted above, to implement an UmbracoApiController, you want a class looking like this - note you can use UmbracoApiController if you don't need to worry about authorization:
namespace Jahan.Nuts.Web.Mvc.UmbracoCms.App.App_Plugins.Reply
{
[PluginController("Reply")]
public class HelloApiController : UmbracoAuthorizedApiController
{
public void PostReplyMessage(string to, string message)
{
// TODO: process your message and then return something (if you want to).
}
}
}
Then in AngularJS set up a resource like this:
function replyResource($q, $http, umbDataFormatter, umbRequestHelper) {
var replyResource = {
sendMessage: function (sendTo, msg) {
return umbRequestHelper.resourcePromise(
$http.post("Backoffice/Reply/HelloApi/PostReplyMessage?" +
umbRequestHelper.dictionaryToQueryString(
[{ to: sendTo }, { message: msg }])),
'Failed to send message to ' + sendTo + ': ' + msg);
}
};
return replyResource;
}
angular.module('umbraco.resources').factory('replyResource', replyResource);
and finally your actual view controller can use this as follows:
angular.module("umbraco")
.controller("Reply.controller", function ($scope, $http, $injector) {
// Get a reference to our resource - this is why we need the $injector specified above
replyResource = $injector.get('replyResource');
$scope.SendReply = function () {
// You really shouldn't use jQuery here - learn to use AngularJS Bindings instead and bind your model properly.
var sendTo = $("#Email").val();
var textMessage = $("#TextMessage").val();
replyResource.sendMessage(sendTo, textMessage)
.then(function (response) {
// Success
}, function (err) {
// Failure
});
}
};
});
It's possible there's some errors in there; I did it mostly from memory - in particular, you may need to look into the best way to post data to the ApiController - it's not likely that it'll just accept the two parameters like that.
For a more complete example, consider reviewing the code of the Umbraco MemberListView plugin: https://github.com/robertjf/umbMemberListView
Also, you really should read up on the ASP.Net MVC fundamentals and the Umbraco Documentation for SurfaceControllers and APIControllers I've listed above already.
remove the "Surface" from the URL and include "backoffice":
angular.module("umbraco")
.controller("Reply.controller", function ($scope, $http) {
$scope.SendReply = function () {
var sendTo = $("#Email").val();
var textMessage = $("#TextMessage").val();
$scope.xxx = "I'm here!";
var data = { SendTo: sendTo, TextMessage: textMessage };
// ~/Hello2Surface/ReplyMessage ---> Cannot find this URL
$http.post("backoffice/Reply/Hello/ReplyMessage") // Can not find this URL
.then(function (response) {
alert("YES!");
//TODO:
});
}
});
Also, I'd recommend using UmbracoAuthorizedController not a surface controller as this is being used in the back end by logged in users it'll be wise to keep it secure.
So instead your controller should look something like this:
[PluginController("Reply")]
namespace Jahan.Nuts.Web.Mvc.UmbracoCms.App.App_Plugins.Reply
{
public class HelloApiController : UmbracoAuthorizedJsonController
{
public [Model-to-be-returned-to-angular] ReplyMessage()
{
//sql query etc to populate model
//return model
}
}
}

Ampersand-rest-collection using multiple url end points

In standard ampersand-rest-collection you define the url endpoint as a property and [getOr]Fetch() queries using that url.
I have two endpoints for the same model. Essentially, when I am pulling a list of objects I use one url end point, but when I am pulling individual objects I need to use a different one. Is it possible to tell fetch() which url to use in each case?
DocStore = _.extend(docs, {
url: [could this be an object or array?]
getOne: function(docId) {
return this.fetchById(docId); //should use /doc/:docId
},
getMany: function(groupId) {
return DocStore.getOrFetch(groupId); //should use /group/:groupId/docs
}
});
Like many backbone properties you can use a function for the url property instead of a string to return whatever dynamic result you need.
For example you can do something like the following
url : function () {
return this.fetchingMany ? '/group/' : '/doc'
},
getOne: function(docId) {
this.fetchingMany = false;
this.fetch(); //should use /doc/:docId
},
getMany: function(groupId) {
this.fetchingMany = true;
this.fetch(); //should use /group/:groupId/docs
}