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
}
}
}
Related
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')
I am having a bit of difficulty figuring out why I am getting 401 Unauthorized status from service framework. At the moment I have it setup to allow everyone to do as they please but that because when I try to enable authorisation I get 401 error code.
//[SupportedModules("Boards")]
//[DnnModuleAuthorize(AccessLevel = SecurityAccessLevel.View)]
[AllowAnonymous]
public class BoardsServiceController : DnnApiController
{ ... }
The strange thing is I have another module which is more than happy to work away with DnnModuleAuthorize
[SupportedModules("Assignments")]
[DnnModuleAuthorize(AccessLevel = SecurityAccessLevel.View)]
public class AsgnsServiceController : DnnApiController
{ ... }
In both cases I have checked to make sure the user has permissions to view the page on which the module lives.
I have cross referenced both projects and everything seems to be spot on. Yet one is working away just fine and the other one returns 401.
Any suggestions?
Update
For Assignments module I am mostly using jQuery style of ajax request just because I haven't got around to revising the module. So a typical GET request would look something like this:
$.ajax({
type: "GET",
url: sf.getServiceRoot( "Assignments" ) + "AsgnsService/GetAssignments",
data: data,
beforeSend: sf.setModuleHeaders
}).done( function ( items ) {
//removed for brevity
}).fail( function ( xhr, result, status ) {
//removed for brevity
});
As for Boards module the code structure is slightly different due knockout implementation. There is a dedicated ServiceCaller but it all boils down to the same ajax call to the server except that instead of having full blown ajax call defined as above it looks much neater.
var that = this;
that.serviceCaller = new dnn.boards.ServiceCaller($, this.moduleId, 'BoardsService');
var success = function (model) {
if (typeof model !== "undefined" && model != null) {
viewModel = new boardViewModel(model.colLists);
ko.bindingHandlers.sortable.beforeMove = viewModel.verifyAssignments;
ko.bindingHandlers.sortable.afterMove = viewModel.updateLastAction;
// normally, we apply moduleScope as a second parameter
ko.applyBindings(viewModel, settings.moduleScope);
}
//console.log('success', model);
};
var failure = function (response, status) {
console.log('request failure: ' + status);
};
var params = {
BoardId: this.boardId
};
that.serviceCaller.get('GetBoardLists', params, success, failure);
And the ServiceCaller ajax function itself looks like this:
function (httpMethod, method, params, success, failure, synchronous) {
var options = {
url: that.getRoot() + method,
beforeSend: that.services.setModuleHeaders,
type: httpMethod,
async: synchronous == false,
success: function (d) {
if (typeof (success) != 'undefined') {
success(d || {});
}
},
error: function (xhr, textStatus, errorThrown) {
if (typeof (failure) != 'undefined') {
var message = undefined;
if (xhr.getResponseHeader('Content-Type').indexOf('application/json') == 0) {
try {
message = $.parseJSON(xhr.responseText).Message;
} catch (e) {
}
}
failure(xhr, message || errorThrown);
}
}
};
if (httpMethod == 'GET') {
options.data = params;
} else {
options.contentType = 'application/json; charset=utf-8';
options.data = ko.toJSON(params);
options.dataType = 'json';
}
$.ajax(options);
};
This would be the two GET requests from two different modules where one is happy and the other throws a status 401 when I enable the same annotations.
Does this provide any clues?
Update
Now in saying all of the above if one takes a look at the original Boards module code base one will notice [DnnAuthorize] annotation attached to every function.
During module revision I removed all instances of [DnnAuthorize] annotation and replaced it with two of my own on the service class itself.
When I add [DnnAuthorize] as annotation on service class itself things work as expected. So why [SupportedModules("Boards")] and [DnnModuleAuthorize(AccessLevel = SecurityAccessLevel.View)] combination doesn't !?
I am not sure but working with the WebAPI you have to register the Service Framework anti forgery stuff
ServicesFramework.Instance.RequestAjaxAntiForgerySupport();
This is part of asking the API to work with a specific module.
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
Originally in my app, I created controllers with very basic $http calls to get a resource by getting the ID of an object from the url ($routeParams). Ng-repeat display the results correctly.
However, I noticed refreshing in a later view (different controller) wiped out the data and broke the page. So, I created a function on the service to be used in multiple controllers, to check whether the data has is available and to react as follows:
1) If the resource is defined, return it (no API call)
2) If the resource is not defined, get the id from the url and get it from the API
3) If the resource is not defined & you can't get the ID, just return false.
However, this broke the code: the template rendered before the service returned the data, and ng-repeat did not update. The code looks like this:
angular.module('myApp', ['ngCookies'])
.config(...)
.service('myService', ['$cookies', '$http', function($cookies, $http) {
myData = {};
return {
getData:function(dataID) {
if(myData.name) {return myData);
else if (dataID && dataID !== '') {
$http.get('/api/data/' + dataID)
.success(function(data) {
myData = data.object;
$cookies.dataID = data.object.id;
return myData;
}
}
else { return false; }
}
}
}]);
function myCtrl($scope, $http, $routeParams, myService) {
$scope.data = myService.getData($routeParams.dataID);
...
}
And here's the template. It's in jade, which means rather than angle brackets, you just list the element with parameters in parenthesis right after, and content after the parenthesis.
h2 My heading
ul
li(ng-repeat='option in data')
a(href="#", ng-click='someFuncInCtrl(option.name)') {{ option.name }}
When the controller did the $http.get itself, the ng-repeat worked fine because the $scope was updated in the ".success" callback. Now that there's a service that returns the data after a slight delay, "$scope.data" is just undefined, the ng-repeat list is empty.
I used a console.log to check myData right before return "return myData", and the myData is working, it just isn't returned in time, and for whatever reason the list is not updating whenever $scope does get the data.
I looked a using $routeProvider's resolve... but that makes getting the ID from the url challenging, as the resolve object doesn't seem to have access to $routeParams. I know that $scope.$apply is supposed to help update the scope when it's altered by outside functions... but I have no clue where to put it. The most similar problem on SO didn't use a service.
I tried:
$scope.$apply($scope.data = myService.getData($routeParams.dataID));
And
$scope.$apply(function() {
$scope.data = myService($routeParams.dataID);
});
Both times I only got Error: $digest already in progress.
The problem is on the way you interact with the service. Since your getData function can return both synchronous and/or asynchronous information, you can't just use normal return(s).
$http.get('/api/data/' + dataID)
.success(function(data) {
myData = data.object;
$cookies.dataID = data.object.id;
return myData;
});
The return on the above snippet will not return anything from getData because it will be executed on the context of the $http.get success callback (and not on the getData call stack).
The best approach for handling sync and async service requests is to use promises.
Your getData function should look something like this:
getData:function(dataID) {
var deferred = $q.defer();
if(myData.name) {
deferred.resolve(myData);
} else if (dataID && dataID !== '') {
$http.get('/api/data/' + dataID)
.success(function(data) {
myData = data.object;
$cookies.dataID = data.object.id;
deferred.resolve(myData);
// update angular's scopes
$rootScope.$$phase || $rootScope.$apply();
});
} else {
deferred.reject();
}
return deferred.promise;
}
Note: You need to inject the $rootScope on your service.
And on your controller:
function myCtrl($scope, $http, $routeParams, myService) {
myService.getData($routeParams.dataID).then(function(data) {
// request was successful
$scope.data = data;
}, function() {
// request failed (same as your 'return false')
$scope.data = undefined;
});
}
lets say I have a Backbone Model and I create an instance of a model like this:
var User = Backbone.Model.extend({ ... });
var John = new User({ name : 'John', age : 33 });
I wonder if it is possible when I use John.save() to target /user/create when I use John.save() on second time (update/PUT) to target /user/update when I use John.fetch() to target /user/get and when I use John.remove() to target /user/remove
I know that I could define John.url each time before I trigger any method but I'm wondering if it could be happen automatically some how without overriding any Backbone method.
I know that I could use one url like /user/handle and handle the request based on request method (GET/POST/PUT/DELETE) but I'm just wondering if there is a way to have different url per action in Backbone.
Thanks!
Methods .fetch(), .save() and .destroy() on Backbone.Model are checking if the model has .sync() defined and if yes it will get called otherwise Backbone.sync() will get called (see the last lines of the linked source code).
So one of the solutions is to implement .sync() method.
Example:
var User = Backbone.Model.extend({
// ...
methodToURL: {
'read': '/user/get',
'create': '/user/create',
'update': '/user/update',
'delete': '/user/remove'
},
sync: function(method, model, options) {
options = options || {};
options.url = model.methodToURL[method.toLowerCase()];
return Backbone.sync.apply(this, arguments);
}
}
To abstract dzejkej's solution one level further, you might wrap the Backbone.sync function to query the model for method-specific URLs.
function setDefaultUrlOptionByMethod(syncFunc)
return function sync (method, model, options) {
options = options || {};
if (!options.url)
options.url = _.result(model, method + 'Url'); // Let Backbone.sync handle model.url fallback value
return syncFunc.call(this, method, model, options);
}
}
Then you could define the model with:
var User = Backbone.Model.extend({
sync: setDefaultUrlOptionByMethod(Backbone.sync),
readUrl: '/user/get',
createUrl: '/user/create',
updateUrl: '/user/update',
deleteUrl: '/user/delete'
});
Are you dealing with a REST implementation that isn't to spec or needs some kind of workaround?
Instead, consider using the emulateHTTP option found here:
http://documentcloud.github.com/backbone/#Sync
Otherwise, you'll probably just need to override the default Backbone.sync method and you'll be good to go if you want to get real crazy with that... but I don't suggest that. It'd be best to just use a true RESTful interface.
No you can't do this by default with backbone. What you could to is to add to the model that will change the model url on every event the model trigger. But then you have always the problem that bckbone will use POST add the first time the model was saved and PUT for every call afterward. So you need to override the save() method or Backbone.sync as well.
After all it seems not a good idea to do this cause it break the REST pattern Backbone is build on.
I got inspired by this solution, where you just create your own ajax call for the methods that are not for fetching the model. Here is a trimmed down version of it:
var Backbone = require("backbone");
var $ = require("jquery");
var _ = require("underscore");
function _request(url, method, data, callback) {
$.ajax({
url: url,
contentType: "application/json",
dataType: "json",
type: method,
data: JSON.stringify( data ),
success: function (response) {
if ( !response.error ) {
if ( callback && _.isFunction(callback.success) ) {
callback.success(response);
}
} else {
if ( callback && _.isFunction(callback.error) ) {
callback.error(response);
}
}
},
error: function(mod, response){
if ( callback && _.isFunction(callback.error) ) {
callback.error(response);
}
}
});
}
var User = Backbone.Model.extend({
initialize: function() {
_.bindAll(this, "login", "logout", "signup");
},
login: function (data, callback) {
_request("api/auth/login", "POST", data, callback);
},
logout: function (callback) {
if (this.isLoggedIn()) {
_request("api/auth/logout", "GET", null, callback);
}
},
signup: function (data, callback) {
_request(url, "POST", data, callback);
},
url: "api/auth/user"
});
module.exports = User;
And then you can use it like this:
var user = new User();
// user signup
user.signup(data, {
success: function (response) {
// signup success
}
});
// user login
user.login(data, {
success: function (response) {
// login success
}
});
// user logout
user.login({
success: function (response) {
// logout success
}
});
// fetch user details
user.fetch({
success: function () {
// logged in, go to home
window.location.hash = "";
},
error: function () {
// logged out, go to signin
window.location.hash = "signin";
}
});