I am working on an umbraco project, which has been a very steep learning curve, there doesn't seem to be to much good documentation around for v7 and mvc in particular.
I have built out a simple list of items with pagination and ordering using ling with query strings. Not particularly nice. I want to work out a way of making all of this clientside and what the options are and best practices are within umbraco.
Any help gratefully appreciated.
Thanks in advance
Richard
There are many ways to do this, here is a simple one. In summary, I reccomend using a "Surface Controller" called by ajax and then manipulate your DOM accordingly.
Here is a sample Surface Controller (I have a bunch of useless usings there...):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Globalization;
using System.Net;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Configuration;
using System.IO;
using Newtonsoft.Json;
using System.Text;
using Umbraco.Web; // AncestorOrSelf
using Umbraco.Core; // FindIndex
using Umbraco.Core.Models;
namespace MyFunWebsite.SurfaceControllers
{
public class MyFunSCController : Umbraco.Web.Mvc.SurfaceController
{
[System.Web.Mvc.HttpPost]
public ActionResult GetPage() {
// Pick up params
var pageNumber = -1;
if (!int.TryParse(Request.Params["pageNumber"], out pageNumber))
{
pageNumber = -1;
}
var parentNodeId = -1;
if (!int.TryParse(Request.Params["parentNodeId"], out parentNodeId))
{
parentNodeId = -1;
}
if (parentNodeId == -1 || pageNumber == -1)
{
return Content("no - invalid parentNodeId or pageNumber");
}
var itemsPerPage = 10;
var parentNode = Umbraco.TypedContent(parentNodeId);
var nodes = parentNode.Children.Skip(itemsPerPage * pageNumber).Take(itemsPerPage);
ViewBag.specialViewBagInformation = "Fun with Umbraco";
ViewBag.nodes = nodes;
return PartialView("Partials/MyListingOfNodes");
}
}
}
Then that partial view goes like this:
#using Umbraco.Core;
#{
var nodes = (IEnumerable<IPublishedContent>)ViewBag.nodes;
foreach (var node in nodes) {
<div>title: #node.AsDynamic().title</div>
}
}
Now your normal template, the one seem to already have can also call this partial. That template has to display page buttons also based on counts and items per page and stuff. But then basically, when a page button runs, you run some javascript similar to this:
function GetPage(pageNumber, parendNodeId) {
$.ajax({
type: "POST",
url: "/Umbraco/Surface/MyFunSCController/GetPage/",
data: "pageNumber=" + pageNumber + "&parentNodeId=" + parentNodeId,
cache: false,
dataType: "text",
success: function (result) {
$("#myFunTable").html(result);
},
error: function (result) {
// console.log("Ajax error: " + result);
}
});
}
Notice the url for the ajax post "/Umbraco/Surface/", controllers you create that extend "Umbraco.Web.Mvc.SurfaceController" will be accessible there. Very convinient for ajax calls. This javascript will prevent your page from requiring to reload, keeping things clientside but still using the serverside to get the content you want. From here you can add a lot of functionnality and validations.
Here is a page button:
Page 3
Hope this helps you work with Umbraco, I think it's a very powerful product.
Related
I'm using ajax within the play (scala) framework to run some code after a click event occurs on a page. I want to return the results of that code execution on the same page.
EDIT: the goal of this is to display some rows from the database, along with form elements to fill out to update cells of the databse, all without reloading/or going to a new page
Is there a way to pass html + (scala) values into
Ok(...).as(HTML)
to display without directing to new a page?
I know Ok can take things like:
Ok(<h1>it works</h1>).as(HTML)
but I can't figure out how to combine that with scala values as well. Is there a smarter way to do this?
EDIT: the ajax looks like this:
var ajaxCall = function() {
var ajaxCallBack = {
success : onSuccess,
error : onError
}
jsRoutes.controllers.Application.scrape().ajax(ajaxCallBack);
};
var onSuccess = function(data) {
$("#results").append(data);
}
var onError = function(error) {
alert(error);
}
For sure you can. You can use TWIRL templates:
Create a template "views/Application/contacts.scala.html
#(users: List[User])
<h1>Users</h1>
<ul>
#for(user <- users) {
<li>#user.name</li>
}
</ul>
in the controller:
Ok(views.html.Application.contacts(users))
Okay...I am coming from C# MVC using partial views/ajax, etc...
Here's what I have: 1 main page with a target ID that renders the default information using a page include.
What I want to do is onclick of a button target the original ID and render a different page as the include. Kind of like RenderPartials in C# MVC.
Can Spring (using Maven) MVC do this or is there a way to go about this that is strait forward?
Thanks,
You don't need to implement partial views just have an empty div and on click of a button make an ajax request get the content and update the div. something like below.
function button_onclick() {
var params = {}; //put parameter if any
$.ajax({
headers: {
Accept : "text/plain; charset=utf-8","Content-Type": "text/plain; charset=utf-8"},
url: "/controller",
method:"GET",
data:params,
success:function(data, textStatus,response) {
var text = response.responseText;
if (text == "") {
return;
}
$( "#DIV_ID" ).text(text);
},
error: function(response) {
alert(response);
// terminate the script
}
});
}
As far as I can tell, Backbone.js view represents DOM element. I take it from existing DOM or create it on the fly in el attribute.
In my case, I want to take it from server with AJAX request because I'm using Django templates and don't want to rewrite everything to JavaScript templates.
So I define el function that performs AJAX request.
el: function() {
model.fetch().success(function(response) {
return response.template
})
}
Of course, it does NOT work because AJAX request is executed asynchronous.
This means that I don't have el attribute and events does NOT work neither. Can I fix it?
Maybe the Backbone.js framework isn't the right tool for my needs? The reason I want to use that was to have some structure for the code.
P.S. I'm new to Backbone.js.
Do your ajax request from another view, or directly after the page load using jquery directly, and after you've downloaded your template, THEN instantiate your backbone view class with the proper id/el or whatever (depending on where you stored your ajax fetched template). Depending on your use-case, this may or may not be a sensible approach.
Another, perhaps more typical approach, would be to set up your view with some placeholder element (saying "loading" or whatever), then fire off the ajax, and after the updated template has been retrieved, then update your view accordingly (replace the placeholder with the actual template you requested).
When/if you update your view with new/other DOM elements, you need to call the view's delegateEvents method to rebind your events to the new elements, see:
http://backbonejs.org/#View-delegateEvents
I came across a similar requirement. In my instance, I was running asp.net and wanted to pull my templates from user controls. The first thing I would recommend is looking into Marionette because it will save you from writing a lot of boiler plate code in Backbone. The next step is to override how your templates are loaded. In this case I created a function that uses Ajax to retrieve the HTML from the server. I found an example of this function where they were using it to pull down html pages so I did a little modification so I can make MVC type requests. I can't remember where I found the idea from; otherwise, I would give the link here.
function JackTemplateLoader(params) {
if (typeof params === 'undefined') params = {};
var TEMPLATE_DIR = params.dir || '';
var file_cache = {};
function get_filename(name) {
if (name.indexOf('-') > -1) name = name.substring(0, name.indexOf('-'));
return TEMPLATE_DIR + name;
}
this.get_template = function (name) {
var template;
var file = get_filename(name);
var file_content;
var result;
if (!(file_content = file_cache[name])) {
$.ajax({
url: file,
async: false,
success: function (data) {
file_content = data; // wrap top-level templates for selection
file_cache[name] = file_content;
}
});
}
//return file_content.find('#' + name).html();
return file_content;
}
this.clear_cache = function () {
template_cache = {};
};
}
The third step would be to override Marionette's method to load templates. I did this in the app.addInitializer method. Here I am initializing my template loader and setting it's directory to a route handler. So when I want to load a template, I just set the template: "templatename" in my view and Backbone will load the template from api/ApplicationScreens/templatename. I am also overriding my template compiling to use Handlebars because ASP.net is not impressed with the <%= %> syntax.
app.JackTemplateLoader = new JackTemplateLoader({ dir: "/api/ApplicationScreens/", ext: '' });
Backbone.Marionette.TemplateCache.prototype.loadTemplate = function (name) {
if (name == undefined) {
return "";
} else {
var template = app.JackTemplateLoader.get_template(name);
return template;
}
};
// compiling
Backbone.Marionette.TemplateCache.prototype.compileTemplate = function (rawTemplate) {
var compiled = Handlebars.compile(rawTemplate);
return compiled;
};
// rendering
Backbone.Marionette.Renderer.render = function (template, data) {
var template = Marionette.TemplateCache.get(template);
return template(data);
}
Hopefully this helps. I've been working on a large dynamic website and it is coming along very nicely. I am constantly being surprised by the overall functionality and flow of using Marionette and Backbone.js.
I have a Link element in the Master Page. This element is initially invisible.
Upon a certain action in one of the content page, I need to make that Link element visible.
I am looking into ways of how this could be done. Can I do it from my Controller method or will I have to do it via ajax?
To manipulate the UI in mvc you need to use javascript. If your "certain action" are involved with the server I think you need to use ajax:
$.ajax({
url: "yourController/yourAction",
dataType: 'json',
success: function(json){
var domEl = document.getElementById('theIdOfyourLink');
if(json.show){
domEl.style.display = 'block';
}else{
domEl.style.display = 'none';
}
}
});
If you action are only involved with the client you can use only javascript. At the end you can playing with css to make visible or not your link:
var yourfun = function(show){
var domEl = document.getElementById('theIdOfyourLink');
if(show){
domEl.style.display = 'block';
}else{
domEl.style.display = 'none';
}
}
One option would be to use TempData or ViewData.
Your 'certain action' in your controller could be like
public ActionResult MyCertainAction()
{
TempData["ShowLink"] = true;
}
Then, you masterpage could have the following
#if (TempData["ShowLink"] != null && (bool)TempData["ShowLink"])
{
My link
}
Find control on master pages
Found this, might help.
From your code behind you can just find the control, cast it, and manipulate it.
Panel pnlWelcome2 = this.Master.FindControl("pnlWelcome") as Panel;
In our MVC application we use jQuery autocomplete control on several pages. This works fine on Create, but I can't make it work on Edit.
Effectively, I don't know how to make the autocomplete controls preload the data from model and still behave as an autocomplete in case the user wants to change the value.
Also how can I make sure that the value is displayed in the same format that is used in Create calls?
All our autocomplete controls have corresponding controllers and all parse Json results.
Let's Try this! Alright Do this:
Suppose you had a list of countries you needed to filter
Auto Complete knows how to some default things by default but suppose you really wanted CountryName and also you know every keypress does an ajax call to the URL you specify.
Create an action method like so:
public ActionResult LookupCountry(string q, int limit)
{
var list = GetListOfCountries(q, 0, limit);
var data = from s in list select new {s.CountryName};
return Json(data);
}
Here is the Jquery:
$(document).ready( function() {
$('#txtCountryName').autocomplete('<%=Url.Action("LookupCountry", "MyController") %>', {
dataType: 'json',
parse: function(data) {
var rows = new Array();
for(var i=0; i<data.length; i++){
rows[i] = { data:data[i], value:data[i].CountryName, result:data[i].CountryName};
}
return rows;
},
formatItem: function(row, i, n) {
return row.CountryName;
},
width: 300,
mustMatch: true,
});
});
Here is the Html
<html><head></head><body>#Html.TextBox("txtCountryName",Model.CountryName)</body></html>
Basically, The magic is in the call to LookUpCountry
The GetCountriesList(string query, int startindex, int limit)
Returns MyCountries.Where(c => c.CountryName.SubString(startindex, limit).Contains(query)).ToList();
So you are making your own trimming function because JQuery has no idea what CountryName is or how to trim it. How ever if it was a javascript object I am not quite sure but do
var jsonString = #Html.GetListOfCountries() //Or Model.Countries.ToJSONString()
var json = JSON.stringify(jsonString); //also JSON.Parse(jsonString) if stringify won't work
which would return the necessary countries as a Html Helper Extension method. And perhaps as a list of javascript objects it would be smart enough to handle it that way in it's native language. However the first approach works for me.