I have been struggling with this one for a few days now and I thought I would put it out there and see if anyone can help. I have an MVC4 Application and I'm using the JQuery Tabs as a Menu. I have implemented this by returning partial views into the tabs. The problem is that I have no browser history and I have not maintained the MVC RESTful pages "{controller}/{action}/{id}" can anyone help me in figuring out if there is a way to change the URL based upon what the tab is clicked?
<div id="tabs">
<ul>
<li>#Html.ActionLink("Household Info", "Info", "HouseholdFees", null, new { title = "HouseholdInfo" })</li>
<li>#Html.ActionLink("Household Management", "Management", "HouseholdFees", null, new { title = "HouseholdManagement" })</li>
<li>#Html.ActionLink("Household Approval", "Approval", "HouseholdFees", null, new { title = "HouseholdApproval" })</li>
<li>#Html.ActionLink("Household Administration", "Index", "HouseholdFeesAdministration", null, new { title = "HouseholdAdministration" })</li>
</ul>
</div>
and
$("#tabs").tabs({
cache: false,
spinner: 'Loading task...',
beforeLoad: function(event, ui) {
ui.jqXHR.error(function() {
ui.panel.html(
"Loading...");
});
},
collapsible: false
});
You can do this with JavaScript as an anchor/hashing solution like so. Firstly, change the hash of the URL when a tab is selected.
$("#tabs").bind("tabsselect", function (event, ui) {
window.location.hash = ui.tab.hash;
});
Create a function to react to the current hash:
function setCurrentTabFromHash() {
$("#tabs").tabs("select", window.location.hash);
}
Call that function from:
$(document).ready(function () {
setCurrentTabFromHash();
});
And:
$(window).hashchange(function(){
setCurrentTabFromHash();
});
Alternatively, you could integrate Ben Alman's fabulous BBQ plugin for asynchronous browser history, as seen here:
http://benalman.com/projects/jquery-hashchange-plugin/
I hope this helps, and good luck with your app!
This is not really an answer Its more of an mis-understanding of the use of JS Tabs. I did't end up using the JS Tabs component at all because I think I was misusing them to try and create a menu but I did like their look and feel, so I created a new JS class that dynamically added the styling of the JS TABS component to my own menu. I lost all the ajax bit and caching ect... but I think this was a better solution for my needs.
JS Extension:
(function ($) {
$.fn.tabmenu = function() {
// Plugin code
return this.each(function() {
$(this).addClass("ui-tabs ui-widget ui-widget-content ui-corner-all");
$(this).find("ul").addClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all");
$(this).find("li").addClass("ui-state-default ui-corner-top")
.bind('mouseenter', function () {
$(this).addClass("ui-state-hover");
})
.bind('mouseout', function() {
$(this).removeClass("ui-state-hover");
});
$(this).find("#main").addClass("ui-tabs-panel ui-widget-content ui-corner-bottom");
$(this).find("a").addClass("ui-tabs-anchor");
var parts = window.location.pathname.split("/");
for (i = 0; i < parts.length; i++) {
$("a[title*='" + parts[i] + "']").parent().addClass("ui-tabs-active ui-state-active");
}
});
};
})(jQuery);
In my MVC LAYOUT page I added the following code.
<script>
$(function() {
$("#tabsmenu").tabmenu();
});
</script>
<div id="tabsmenu">
<ul>
<li>#Html.ActionLink("Household Info", "Info", "HouseholdFees", null, new {title = "HouseholdInfo"})</li>
<li>#Html.ActionLink("Household Management", "Management", "HouseholdFees", null, new {title = "HouseholdManagement"})</li>
<li>#Html.ActionLink("Household Approval", "Approval", "HouseholdFees", null, new {title = "HouseholdApproval"})</li>
<li>#Html.ActionLink("Household Administration", "Index", "Administration", null, new {title = "HouseholdAdministration"})</li>
</ul>
<div id ="main">
#RenderBody()
</div>
</div>
Related
I have somewhat of a complex requirement here (a real head-scratcher)... and I'm not sure on the best way to proceed:
Requirement:
Build a page for managing widgets (CMS content blocks) in MVC5 using AngularJS for the frontend (as per the rest of the admin UI). The problem is that each widget has its own specific set of properties. They all share some properties like Title, IsEnabled, etc.. but an HTML Widget for example will have a BodyContent field and a Slider Widget would have a collection of images, etc..
My first thought was using [UIHint] and Html.EditorFor so that each widget type will have its own markup.. I think that's pretty straightforward, but how could we get the properties from any such arbitrary widget into the AngularJS model?
Example Controller
widgetsApp.controller('widgetController', function ($scope, $http) {
$scope.emptyGuid = '00000000-0000-0000-0000-000000000000';
$scope.id = $scope.emptyGuid;
$scope.title = '';
$scope.order = 0;
$scope.enabled = false;
$scope.widgetType = '';
$scope.zoneId = $scope.emptyGuid;
// etc
// how to get properties of ANY widget type?
Is this even possible? Is there a better solution? Note, I might consider changing the code to use Knockout or some other such framework if it can support my requirements.
Edit
Note that the issue is further complicated because of the fact of needing to then pass such a model back to the server and dealing with it there. In regular MVC controllers, I can use Request.Form to inspect what other values are there, but I'm using Web API and not sure if that's possible there.
Edit 2
Okay, so I think I'm on the right track, but still having issues. Firstly, here's my progress:
I found out about .factory and made a test page like this:
<div ng-app="myApp">
<div ng-controller="controller1">
<button class="btn btn-primary" ng-click="showAllInfo()">Show Info</button>
</div>
<div ng-controller="controller2">
</div>
</div>
<script type="text/javascript">
var myApp = angular.module('myApp', []);
myApp.factory('widgetModel', function () {
return {
id: '00000000-0000-0000-0000-000000000000',
title: '',
order: 0,
enabled: false,
widgetName: '',
widgetType: '',
zoneId: '00000000-0000-0000-0000-000000000000',
displayCondition: '',
widgetValues: '',
pageId: null,
cultureCode: '',
refId: null,
};
});
// This is representative of the main controller
myApp.controller('controller1', function ($scope, widgetModel) {
$scope.emptyGuid = '00000000-0000-0000-0000-000000000000';
$scope.model = widgetModel;
$scope.model.id = $scope.emptyGuid;
$scope.showAllInfo = function () {
alert("id: " + $scope.model.id + ", New Property: " + $scope.model.myNewProperty);
};
});
// This is representative of the details controller (to add properties specific to that particular widget type)
myApp.controller('controller2', function ($scope, widgetModel) {
$scope.model = widgetModel;
$scope.model.myNewProperty = "My Awesome Widget";
});
</script>
The above test works beautifully.. however, when I use this sort of code in my real application it fails to work and the reason I believe is because the second controller is injected into the DOM later on.. here's what's happening:
I have a div as follows
<div ng-bind-html="widgetDetails"></div>
and after loading the other details, I load the html for this as such:
$http.get("/admin/widgets/get-editor-ui/" + $scope.model.id).success(function (json) {
$scope.widgetDetails = $sce.trustAsHtml(json.Content);
});
That works.. I can see my the html controls for my new properties there.. the following snippet is the HTML which is injected into the above div:
<div ng-controller="widgetDetailsController">
<div class="col-sm-12 col-md-12">
<div class="form-group">
#Html.Label("BodyContent", "Body Content", new { #class = "control-label" })
#Html.TextArea("BodyContent", null, new { #class = "form-control", ng_model = "model.bodyContent", ui_tinymce = "tinyMCEOptions_BodyContent" })
</div>
</div>
<button class="btn" ng-click="test()">Test</button>
</div>
<script type="text/javascript">
widgetsApp.controller('widgetDetailsController', function ($scope, $http, widgetModel) {
$scope.model = widgetModel;
$scope.json = angular.fromJson($scope.model.widgetValues);
$scope.model.bodyContent = $scope.json.bodyContent || "";
$scope.test = function () {
alert($scope.model.bodyContent);
};
});
</script>
When I click, the "Test" button, nothing happens...
I tried to load a controller dynamically via the method outlined at this link: http://www.bennadel.com/blog/2553-loading-angularjs-components-after-your-application-has-been-bootstrapped.htm
It doesn't work. To be honest though, I am new to AngularJS and don't really know all the ins out outs of it.. any help would be great.
IF you are just looking to get the properties and their values, then on AngularJS or Javascript side you can just iterate over the object properties to get all the properties defined over the object.
for(var key in obj){
$scope[key]=obj[key];
}
Once on scope you can bind it to the view using ng-model.
This approach would get you the data but metadata about the data such as control to render for property need would not work.
For advance scenarios you should try to send metadata about each properties that can help render it on the view.
If ng-model is setup correctly all data would be send to server.
On the server you can use the dynamic keyword as input parameter to webapi method and there should be a similar method to iterate over the payload using key value pair.
I ended up changing to KnockoutJS, partly because AngularJS ended up being a bit overkill for my needs, but also because it couldn't handle this situation very nicely (or at least there was no obvious and clean way to do it). My KnockoutJS solution is below:
In the main page, I add an html element:
<fieldset id="widget-details"></fieldset>
An example of arbitrary HTML to be injected:
<div id="widget-content" class="col-sm-12 col-md-12">
<div class="form-group">
#Html.Label("BodyContent", "Body Content", new { #class = "control-label" })
#Html.TextArea("BodyContent", null, new { #class = "form-control", data_bind = "wysiwyg: bodyContent, wysiwygConfig: tinyMCEConfig" })
</div>
</div>
<script type="text/javascript">
function updateModel() {
var data = ko.mapping.fromJSON(viewModel.widgetValues());
viewModel.bodyContent = ko.observable("");
if (data && data.BodyContent) {
viewModel.bodyContent(data.BodyContent());
}
viewModel.tinyMCEConfig = {
theme: "modern",
plugins: [
"advlist autolink lists link image charmap print preview hr anchor pagebreak",
"searchreplace wordcount visualblocks visualchars code fullscreen",
"insertdatetime media nonbreaking save table contextmenu directionality",
"emoticons template paste textcolor"
],
toolbar1: "insertfile undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image",
toolbar2: "print preview media | forecolor backcolor emoticons",
image_advtab: true,
templates: [
{ title: 'Test template 1', content: 'Test 1' },
{ title: 'Test template 2', content: 'Test 2' }
],
content_css: tinyMCEContentCss
};
};
function onBeforeSave() {
var data = {
BodyContent: viewModel.bodyContent()
};
viewModel.widgetValues(ko.mapping.toJSON(data));
};
</script>
Then in my script for the main page, I use the following:
$.ajax({
url: "/admin/widgets/get-editor-ui/" + self.id(),
type: "GET",
dataType: "json",
async: false
})
.done(function (json) {
var result = $(json.Content);
var content = $(result.filter('#widget-content')[0]);
var details = $('<div>').append(content.clone()).html();
$("#widget-details").html(details);
var scripts = result.filter('script');
scripts.appendTo('body');
// ensure the function exists before calling it...
if (typeof updateModel == 'function') {
updateModel();
var elementToBind = $("#widget-details")[0];
ko.cleanNode(elementToBind);
ko.applyBindings(viewModel, elementToBind);
}
})
.fail(function () {
$.notify("There was an error when retrieving the record.", "error");
});
and when I save, I call this code:
// ensure the function exists before calling it...
if (typeof onBeforeSave == 'function') {
onBeforeSave();
}
Works really well.
I'm using jQuery mobile 1.9.1 min on PhoneGap.
I have a list where each iten on click opens an actions popup:
function showActions(index){
selectedIndex = index;
$("#actionPopup").popup("open", {positionTo: '#list li:nth-child('+ index +')'});
}
<div data-role="popup" id="actionPopup" data-overlay-theme="a">
Close
<ul data-role="listview">
<li data-role="divider">Actions</li>
<li data-icon="false" onclick="showDetails();">action1</li>
<li data-icon="false">action2</li>
<li data-icon="false">action3</li>
<li data-icon="false">action4</li>
</ul>
</div>
When I press on action1 with showDetails() them method is called but the second popup isn't shown.
function showDetails(){
console.log("showDetails");
$("#infoPopup").popup("open");
}
<div data-role="popup" id="infoPopup">
Close
<div id="infoContent">
<table>
<tr id="eventcode">
<td>test1:</td>
<td> </td>
</tr>
<tr id="eventtype">
<td>test2:</td>
<td> </td>
</tr>
</table>
</div>
</div>
What can I do?
My solution
$.mobile.switchPopup = function(sourceElement, destinationElement, onswitched) {
var afterClose = function() {
destinationElement.popup("open");
sourceElement.off("popupafterclose", afterClose);
if (onswitched && typeof onswitched === "function"){
onswitched();
}
};
sourceElement.on("popupafterclose", afterClose);
sourceElement.popup("close");
};
I used this simple function for the work-around:
function myPopup(myPage) {
history.back();
setTimeout(function () {
$(myPage).popup('open');
}, 100);
}
#Rakoo has a nice answer. Here is a leaner version:
$.mobile.switchPopup = function(sourceElement, destinationElement, onswitched) {
sourceElement.one("popupafterclose", function() {
destinationElement.popup("open")
!onswitched || typeof onswitched !== "function" || onswitched()
}).popup("close")
};
If you don't need the onswitched feature and aren't adding it to $.mobile, It can be this short and simple:
$('#popup1').one("popupafterclose", function(){$('#popup2').popup("open")}).popup("close")
It seems that chaining popups is not possible.
The solution:
$( document ).on( "pageinit", function() {
$( '.popupParent' ).on({
popupafterclose: function() {
setTimeout( function(){ $( '.popupChild' ).popup( 'open' ) }, 100 );
}
});
});
I used this code to switch popup from popup it works fine.
function switchpop()
{
$( '#popupMenu' ).popup( 'close' );
$( "#popupMenu" ).bind({popupafterclose: function(event, ui)
{
$( "#notes" ).popup( "open" );
}
});
}
After four hours of fighting with this I reduced the problem to this:
This is in a button click function on the first popup
$('#popupCall').popup('close');
window.setTimeout(function () { $('#popupContact').popup('open') }, 50);
Got a slight problem trying to have jquery UI and knockout js to cohoperate. Basically I want to create an accordion with items being added from knockout through a foreach (or template).
The basic code is as follows:
<div id="accordion">
<div data-bind="foreach: items">
<h3></h3>
<div><a class="linkField" href="#" data-bind="text: link"></a></div>
</div>
</div>
Nothing impressive here... The problem is that if I do something like:
$('#accordion').accordion();
The accordion will be created but the inner div will be the header selector (first child, as default) so the effect is not the wanted one.
Fixing stuff with this:
$('#accordion').accordion({ header: 'h3' });
Seems to work better but actually creates 2 accordions and not one with 2 sections... weird.
I have tried to explore knockout templates and using "afterRender" to re-accordionise the div but to no avail... it seems to re-render only the first link as an accordion and not the second. Probably this is due to my beginner knowldge of jquery UI anyway.
Do you have any idea how to make everything work together?
I would go with custom bindings for such functionality.
Just like RP Niemeyer with an example of jQuery Accordion binding to knockoutjs http://jsfiddle.net/rniemeyer/MfegM/
I had tried to integrate knockout and the JQuery UI accordion and later the Bootstrap collapsible accordion. In both cases it worked, but I found that I had to implement a few workarounds to get everything to display correctly, especially when dynamically adding elements via knockout. The widgets mentioned aren't always aware of what is happening with regards to knockout and things can get messed up (div heights wrongly calculated etc...). Especially with the JQuery accordion it tends to rewrite the html as it sees fit, which can be a real pain.
So, I decided to make my own accordion widget using core JQuery and Knockout. Take a look at this working example: http://jsfiddle.net/matt_friedman/KXgPN/
Of course, using different markup and css this could be customized to whatever you need.
The nice thing is that it is entirely data driven and doesn't make any assumptions about layout beyond whatever css you decide to use. You'll notice that the markup is dead simple. This is just an example. It's meant to be customized.
Markup:
<div data-bind="foreach:groups" id="menu">
<div class="header" data-bind="text:name, accordion: openState, click: toggle"> </div>
<div class="items" data-bind="foreach:items">
<div data-bind="text:name"> </div>
</div>
</div>
Javascript:
ko.bindingHandlers.accordion = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
$(element).next().hide();
},
update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var slideUpTime = 300;
var slideDownTime = 400;
var openState = ko.utils.unwrapObservable(valueAccessor());
var focussed = openState.focussed;
var shouldOpen = openState.shouldOpen;
/*
* This following says that if this group is the one that has
* been clicked upon (gains focus) find the other groups and
* set them to unfocussed and close them.
*/
if (focussed) {
var clickedGroup = viewModel;
$.each(bindingContext.$root.groups(), function (idx, group) {
if (clickedGroup != group) {
group.openState({focussed: false, shouldOpen: false});
}
});
}
var dropDown = $(element).next();
if (focussed && shouldOpen) {
dropDown.slideDown(slideDownTime);
} else if (focussed && !shouldOpen) {
dropDown.slideUp(slideUpTime);
} else if (!focussed && !shouldOpen) {
dropDown.slideUp(slideUpTime);
}
}
};
function ViewModel() {
var self = this;
self.groups = ko.observableArray([]);
function Group(id, name) {
var self = this;
self.id = id;
self.name = name;
self.openState = ko.observable({focussed: false, shouldOpen: false});
self.items = ko.observableArray([]);
self.toggle = function (group, event) {
var shouldOpen = group.openState().shouldOpen;
self.openState({focussed: true, shouldOpen: !shouldOpen});
}
}
function Item(id, name) {
var self = this;
self.id = id;
self.name = name;
}
var g1 = new Group(1, "Group 1");
var g2 = new Group(2, "Group 2");
var g3 = new Group(3, "Group 3");
g1.items.push(new Item(1, "Item 1"));
g1.items.push(new Item(2, "Item 2"));
g2.items.push(new Item(3, "Item 3"));
g2.items.push(new Item(4, "Item 4"));
g2.items.push(new Item(5, "Item 5"));
g3.items.push(new Item(6, "Item 6"));
self.groups.push(g1);
self.groups.push(g2);
self.groups.push(g3);
}
ko.applyBindings(new ViewModel());
Is there any reason why you can't apply the accordion widget to the inner div here? For example:
<div id="accordion" data-bind="foreach: items">
<h3></h3>
<div><a class="linkField" href="#" data-bind="text: link"></a></div>
</div>
I attempted the accepted solution and it worked. Just had to make a little change since i was getting following error
Uncaught Error: cannot call methods on accordion prior to initialization; attempted to call method 'destroy'
just had to add following and it worked
if(typeof $(element).data("ui-accordion") != "undefined"){
$(element).accordion("destroy").accordion(options);
}
for details please see Knockout accordion bindings break
You could try this to template it, similar to this:
<div id="accordion" data-bind="myAccordion: { },template: { name: 'task-template', foreach: ¨Tasks, afterAdd: function(elem){$(elem).trigger('valueChanged');} }"></div>
<script type="text/html" id="task-template">
<div data-bind="attr: {'id': 'Task' + TaskId}, click: $root.SelectedTask" class="group">
<h3><b><span data-bind="text: TaskId"></span>: <input name="TaskName" data-bind="value: TaskName"/></b></h3>
<p>
<label for="Description" >Description:</label><textarea name="Description" data-bind="value: Description"></textarea>
</p>
</div>
</script>
"Tasks()" is a ko.observableArray with populated with task-s, with attributes
"TaskId", "TaskName","Description", "SelectedTask" declared as ko.observable();
"myAccordion" is a
ko.bindingHandlers.myAccordion = {
init: function (element, valueAccessor) {
var options = valueAccessor();
$(element).accordion(options);
$(element).bind("valueChanged", function () {
ko.bindingHandlers.myAccordion.update(element, valueAccessor);
});
...
}
What I did was, since my data was being loaded from AJAX and I was showing a "Loading" spinner, I attached the accordion to ajaxStop like so:
$(document).ajaxStart(function(){$("#cargando").dialog("open");}).ajaxStop(function(){$("#cargando").dialog("close");$("#acordion").accordion({heightStyle: "content"});});
Worked perfectly.
I have two actionlinks on my MVC view like this:
<%= Html.ActionLink
("click me","Partial1","Temp",new { s = "0" },new { id = "mylink" }) %>
<%= Html.ActionLink
("click me second link","Partial1", "Temp", null,
new { id = "mysecondlink" }) %>
and I have four style for these two links. if mylink is slected i want to apply #mylinkselected style to it and #mysecondlinknoselected to mysecondlink and if mysecondlink is selected, I want to apply #mysecondlinkselected to it and #mylinknotselected to mylink
I can not change the ids as I have code related to these links at top of view
<script type="text/javascript">
$(function() {
$('#mylink').click(function() {
$('#resultpartial1').load(this.href);
return false;
});
});
$(function() {
$('#mysecondlink').click(function() {
$('#resultpartial1').load(this.href, { s: "1" });
return false;
});
});
</script>
how to change styles ?
Regards,
Asif Hameed
Your best bet is probably to just set an A:active style for the links. This will apply that style to whichever link is clicked.
http://www.echoecho.com/csslinks.htm
How to generate a javascript link in asp.net mvc?
eg.
Pop it
Can I use Html.ActionLink and how to do this?
I could do something like this:
Pop it
But I just want to find out will there be some better solutions for this?
Many thanks.
Yes, you can do something like:
<%=Html.ActionLink(model.Title, "View", "PoppedView", new { Id = model.Id.ToString() }, new { target="_blank" })%>
I would look at doing this using jQuery UI and a dialog instead of a new window. You can use the open handler to load up the content into the dialog.
<%= Html.ActionLink( "Pop It",
"ItemDetail",
"Item",
new { ID = model.ID },
new { #class = "popup-link" } ) %>
<script type="text/javascript">
$(function() {
$('.popup-link').click( function() {
var href = $(this).attr('href');
$('<div><p class="popup-content"></p></div>').dialog({
autoOpen: true,
modal: true,
height: 200,
width: 400,
open: function() {
$(this).find('.popup-content').load(href);
},
close: function() {
$(this).dialog('destroy');
}
});
return false;
});
});
</script>