Displaying div tags conditionally - ruby-on-rails

I have a view that lists an item, then any reviews it may/may not have. The issue is that there will be an arbitrary (possibly 0) number of reviews. I format each review in its own div element, so I'll have to display 'n' div elements. How can this be done?
Edit, sample element contents:
<div id="promo_item">
Promotion:
<br>
<br>
<table>
#rows for item name, price, list-date
#rows for the values of item name, price, list-date
</table>
#comment goes here
</div>
This is just an example, I probably won't implement it exactly like this, but I do know there has to be a table for the values.

<% #post.reviews.each do |r| %>
<div>stuf here <%= r.user.name #etc.... %></div>
<% end %>

Use javascript to change the properties on the div elements.
You can put this script into your HTML:
How to Show & Hide your Divs
<script language="javascript">
function toggle() {
var el = document.getElementById("toggleText");
var text = document.getElementById("displayText");
if(el.style.display == "block") {
el.style.display = "none";
text.innerHTML = "show";
}
else {
ele.style.display = "block";
text.innerHTML = "hide";
}
}
</script>

Related

TypeError: Cannot set property 'chat_room_id' of undefined when using ng-if

I'm running into a bit of a road block here.
I'm working on a chat feature, which is currently within a rails partial in the application.html.erb file.
What I'm looking to do is have a list of a user's friends display in the chat area initially. When the user clicks on a friend's name, the corresponding chat room opens and the messages between the user and friend are displayed. If the user wishes to exit the chat, and view his friends list again, the user would simply click a button (currently "View Friends").
I am currently toggling between friends and rooms/messages using the ng-if directive.
I have not completely set this up yet, so I know there are bugs. I have created user friendships within rails, set up my REST API in rails, and can GET and POST resources via Angular and Restangular.
However, the issue at hand is that ever since I implemented the ng-if directives in the partial, I am getting the following error:
TypeError: Cannot set property 'chat_room_id' of undefined
If I am to remove the ng-if directives and all of the corresponding $scope.messagesVisible and $scope.friendsVisible variable references within the controller, the form submit works, so I know it has something to do with my implementation of the ng-if directives, and the fact that newMessage is undefined, but I'm not sure why.
I suspect it has something to do with Angular promises or my lack of understanding in regards to both Angular promises and the ng-if directive, but if anyone could shed some light on why this may be happening (and offer a solution) that would be amazing.
Thanks, guys!
Code below (please excuse terrible styling - it's a placeholder)
rails_partial:
<section ng-app="atmosphere" ng-controller="AtmoChatCtrl" style="position:relative; top:32%; float:right; right:5px; margin-bottom:10px; margin-top:30px;">
<div ng-if="friendsVisible" style="position:relative; left:35px; width:100px;">
<% user_friendships.each do |friendship| %>
<ul>
<% friend = friendship.friend %>
<% if friendship.accepted? %>
<li style="cursor:pointer;" ng-click="setChatAttributes(<%= friend.id %>, <%= current_user.id %> );"><%= friend.name %></li>
</ul>
<% end %>
<% end %>
</div>
<div ng-if="messagesVisible" style="position:absolute; top:60%; width: 150px; height:40%; right:-80px; margin-top:100px">
<div>
<p ng-repeat="message in messages">{{message.user_id}}: {{message.body}}</p>
<form ng-submit="saveMessage();">
<input type="text" ng-model="newMessage.body">
</form>
</div>
</div>
<div style="position:absolute; top:300px;">
<button ng-if="messagesVisible" ng-click="viewFriends();">View Friends</button>
</div>
</section>
<%= javascript_include_tag "angular/application" %>
<%= javascript_include_tag "http://ajax.googleapis.com/ajax/libs/angularjs/1.0.6/angular-resource.min.js" %>
angular_controller:
angular.module('AtmoChatCtrl', [])
.controller('AtmoChatCtrl', ['$scope', '$resource', '$interval','Pusher','Restangular', function ($scope, $resource, $interval, Pusher, Restangular) {
$scope.baseMessages = Restangular.one('api/chat_rooms', 2).all('chat_messages');
$scope.roomId = '2';
$scope.messagesVisible = false;
$scope.friendsVisible = true;
console.log("SET ROOM");
$interval(function(){
$scope.baseMessages.getList().then(function(messages) {
$scope.messages = messages;
});
console.log("POLLING");
}, 1000);
$scope.saveMessage = function() {
$scope.newMessage.chat_room_id = $scope.roomId;
$scope.newMessage.user_id = $scope.userId;
$scope.baseMessages.post($scope.newMessage).then(function(newMessage){
$scope.messages.push(newMessage);
console.log("SAVED");
})
}
$scope.setChatAttributes = function(roomId, userId) {
$scope.baseMessages = Restangular.one('api/chat_rooms', roomId).all('chat_messages');
$scope.roomId = roomId;
$scope.userId = userId;
$scope.messagesVisible = true;
$scope.friendsVisible = false;
console.log(roomId)
console.log(userId)
}
$scope.viewFriends = function() {
$scope.friendsVisible = true;
$scope.messagesVisible = false;
}
}]);
NVM. I'm an idiot. Solved by setting $scope.newMessage = {}; after the first $scope.friendsVisible = true;

Render partial later after page loads

Using Rails 3.2. Let's say I have the following view:
<div class="content">
<div class="main">
<h1><%= #shop.name %></h1>
<p><%= #shop.description %></p>
</div>
<div class="sidebar">
<%= render 'teasers' %>
</div>
</div>
Is there a way to just load the page first, then load the teasers later? Reason being so is because teasers takes some time to query (I have already optimized the query).
I personaly have a pre-defined system for this kind of behavior:
This (coffeescript) Javascript code is executed at each rendering of a page:
$('.ajax_load').each (index, element) ->
e = $(element)
$.get e.data('url'), (data) =>
$(document).replace(e, data)
So each element in my page responding to the class "ajax_load" is actually called by ajax, example:
%div.ajax_load{ data: { url: users_path } }
This will display at first a div with a class ajax_load, and will send a request to users_path and replace the div with the response's content.
This is translated coffeescript:
$('.ajax_load').each(function(index, element) {
var e,
_this = this;
e = $(element);
return $.get(e.data('url'), function(data) {
return $(document).replace(e, data);
});
});
How about removing the teasers partial from the view and initially just displaying the page without it. Then using javascript, make an AJAX call to the server.
You can use wiselinks to simplify your life.

<li> doesn't work. MVC

I have a view in which I am trying to display text as bulleted items (<ul> <li>) but it doesn't seem work. I tried <ul><li> without enclosing them in <div> still no luck. What is preventing it display the bullets in front of the text.
#model UDP.Models.MCVM
#{
ViewBag.Title = "MCReport";
Layout = "~/Views/Shared/_reportsLayout.cshtml";
}
<div class="span-24 last" >
#* code for filling a grid goes here*#
</div>
<div class="prepend-1 span-22">
<strong> Notes : </strong>
<ul type="circle" >
<li> All listings are sorted according to the user defined sort, and may not display in the order used to determine the median values.</li>
<li> Time ranges are based on a 360-day year commonly called the 'banking year'.</li>
<li> Listings are 'disqualified' from the median value calculations when their Selling, Expiration, or Inactive Date is more than 360 days from the current date.</li>
<li> The Sales Price/List Price calculations will be calculated using the original list price.</li>
</ul>
</div>
<script type="text/javascript">
function loadAndInit() {
$(".dvloading").hide();
if ($.browser.mozilla) {
if (location.pathname == "/udp/Reports") { // This is for local env.
$("#prntCss").attr("href", "../../../Content/SitePrint_FF.css");
}
else { // This is for DEV/QA/STAGE/PROD env.
$("#prntCss").attr("href", "../../Content/SitePrint_FF.css");
}
}
}
</script>

Can we dynamically change the URL with Ajaxy call on ASP.NET MVC page?

I have an ASP.NET MVC application that has a view called Products.
This Products View has a Left Menu Navigation that is implemented using NavMenuProducts.ascx Partial View.
This Menu is implemented using JQuery Treeview so that it has the list of ProductNames as the Parent Node and it is expandable(For Example: 10 Products).
Each of these Products have a ChildNode as DocTypeName and it is a hyperlink(For Example: 3 DocTypeNames).
When the user clicks ChildNode Hyperlink all the matching Documents are displayed and is implemented using Ajaxy call. So that the user has better UI experience.
But the problem with this is the url always is static(Example: http://DocShare )
But based on the Node that is clicked, I want the url like http://DocShare/Products/Product1/Letter
I am wondering how to make this dynamic url using Ajaxy call.
NOTE: If I am using HTML.ActionLINK, then I am getting dynamic URL. But this ActionLink, while the Page is loading, we are getting random treeview screen. TO avoid that flickering tree effect, I am making Ajax call for better UI experience.
Any solution would be appreciated for getting dynamic url with Ajaxy call.
Here is the Code:
NavigationProducts.ascx Page:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<MedInfoDS.Controllers.ProductViewModel>" %>
<script type="text/javascript">
$(document).ready(function () {
$(".docId").click(function () {
alert("DocTypeName: " + this.id);
$("#docDetails").load('<%= Url.Action("DocumentDetails") %>', { ProductName: "darbepoetin alfa", DocTypeName: this.id }, function (responseText, status) {
});
return false;
});
});
<div id="treecontrol">
<a title="Collapse the entire tree below" href="#">Collapse All</a> | <a title="Expand the entire tree below"
href="#">Expand All</a> | <a title="Toggle the tree below, opening closed branches, closing open branches"
href="#">Toggle All</a>
</div>
<div id="divByProduct">
<ul id="red" class="treeview-red">
<% foreach (var item in Model.Products)
{ %>
<li><span>
<%=item.Name%></span>
<ul>
<%foreach (var item1 in Model.DocTypes) { %>
<li><span>
<%= Html.ActionLink(item1.DocTypeName, "Products", new { ProductName = item.Name, DocTypeName = item1.DocTypeName })%>
<br />
<a class="docId" href="#" id="<%=item1.DocTypeName%>"><%= item1.DocTypeName%></a>
<%= Html.Hidden("ProductName", item.Name)%>
</span></li>
<% } %>
</ul>
</li>
<% } %>
</ul>
</div>
Controller Method:
// Response to AJAXy call to populate details for given ProductName and DocType
[HttpPost]
public virtual ActionResult DocumentDetails(string ProductName, string DocTypeName)
{
var entities = new MIDSContainer();
if (ProductName == null) return View();
int ProductId = (entities.Products.FirstOrDefault(p => p.Name == ProductName)).ProductId;
int DocTypeId = (entities.DocTypes.FirstOrDefault(d => d.DocTypeName == DocTypeName)).DocTypeId;
var documents = (from d in entities.Documents.Where(p => p.ProductId == ProductId && p.DocTypeId == DocTypeId && p.AvailableForUse == true && p.Status == "Approved") orderby (d.Description) select d).ToList();
return View(documents);
}
There's a pretty comprehensive solution here: http://ajaxpatterns.org/Unique_URLs
I assume you want to "change the url" so that the Back button works in the browser to navigate between the different products. You can't actually change the URL without a postback, but you can change the url's Hash value. By changing the Hash value you will be able to support the browsers Back button just like the URL itself was changed but without any postbacks.
In the following URL:
http://site/page#SomeValue
The Hash value is "#SomeValue".
You can set the Hash using "document.Hash" in JavaScript.
To make it much easier to work with the "document.Hash" value and setup a function to get notified when it changes, I create the jHash project.
You'll probably want to look at jHash to help you do what you're looking for.
http://jhash.codeplex.com/

Dynamically adding dropdowns to a form and validating them in ASP.NET MVC

I have a form with various inputs. I have a bunch of optional parameters that have some number of choices. I'd like to allow the user to select these optional parameters in the following way:
First, the user clicks the Add Component button at the bottom of the form and two new dropdowns appear above the button. The first dropdown has a list of Types that can be selected and the second one will be disabled. When the user selects a valid choice in the first dropdown, I want to populate the second dropdown with some Values that are specific to the specified Type. The user should be able to continue adding new Components (the pair of dropdowns) until all the desired optional Components are added. Ideally the form wouldn't be posted until all the fields have been filled out and the desired Components added.
My question is this: How do I design this so that when the form is submitted and there are errors, that the dynamically added fields (the Components) will remain on the page and display the correct values?
I was planning on having the Add Component button be an Ajax.ActionLink that retrieves a partialview:
<div id="divComponentHolder"></div>
<%= Ajax.ActionLink("Add a Component", "GetComponentSelector", new AjaxOptions { UpdateTargetId = "divComponentHolder", InsertionMode = InsertionMode.InsertAfter}) %>
This partial view would look something like this:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<MVCAndWebFormsTest.Models.ComponentSelectorModel>" %>
<%= Html.Encode("Type:")%>
<%= Html.DropDownList("ComponentType", Model.ComponentTypes, "", new {onchange = "updateCompValues(this);"}) %>
<%= Html.Encode("File/Folder:")%>
<div id="selectdiv">
<% Html.RenderPartial("ComponentValueSelector", Model.ComponentValues); %>
</div>
<br/>
<script type="text/javascript" language="javascript">
function updateCompValues(obj) {
$.ajax({
url: <% Url.Action("GetCompValues") %>,
async: true,
type: 'POST',
data: { type: obj.value },
dataType: 'text',
success: function(data) { $("#selectdiv").html(data); },
error: function() {
console.log('Erreur');
}
});
}
</script>
And the ComponentValueSelector partial would be pretty simple:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<MVCAndWebFormsTest.Controllers.ViewModels.ComponentValueModel>" %>
<%= Html.DropDownList("CompValue", Model.SelectList) %>
Take a look at submitting list in MVC, here are a few useful sites:
http://blogs.teamb.com/craigstuntz/2009/02/11/38013/
http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx
http://www.hanselman.com/blog/ASPNETWireFormatForModelBindingToArraysListsCollectionsDictionaries.aspx
This is useful for submitting your dynamic DOM you are building up.
Another way instead of making an ajax call to render a partial view you could always directly add elements to the DOM with jquery. For example use the jquery clone ( $('element').clone(); ) method that would copy your list boxes then do some regex to change the id's of the input boxes so they have unique id/names.
As you are passing through a List of these 'choices' to your controller, you would then have to set them back in your Model and have your View iterate through them to display the correct amount of choices added.
Here is a bare bones example. This may not be the best implementation for yourself or someone else may have better ideas.
View
<% for (int i = 0; i < in Model.Results.Count; i++) { %>
//render better HTML but you should get the point!
<%= Html.Hidden("choices[" + i + "].ID", i) %>
<%= Html.DropDownList("choices[" + i + "].Choice1", ...) %>
<%= Html.DropDownList("choices[" + i + "].Choice2", ...) %>
<% } %>
- add button
jQuery
$('#addButton').click(function()
{
//say if your choice drop downs were in a table then take the last
//row and clone it
var row = $('table tr:last').clone(true);
var newId = //work out the new id from how many rows in the table
//make sure to update the id and name parameters of inputs
//of the cloned row
row.find(':input')
.attr('id', function()
{
return $(this).attr('id').replace(/\[[\d+]\]/g, '[' + newlId + ']');
//this replaces the cloned [id] with a new id
})
.attr('name', function()
{
return $(this).attr('name').replace(/\[[\d+]\]/g, '[' + newId + ']');
});
row.find(':hidden').val(newId); //update the value of the hidden input
//alert(row.html()); //debug to check your cloned html is correct!
//TODO: setup ajax call for 1st drop down list to render 2nd drop down
$('table tr:last').after(row);//add the row
return false;
});
Controller
public ActionResult YourMethod(IList<YourObject> choices, any other parameters)
{
YourViewModel model = new YourViewModel();
model.Results = choices; //where Results is IList<YourObject>
return View(model);
}
Based on advice from David Liddle, I found a different design that was a bit more elegant. It uses more jQuery and fewer partial views and Ajax requests.
Instead of adding a bunch of DropDownLists, I decided to go with a table, a pair of dropdowns and an "Add" button. When the user selects a Type option in the first dropdown, ajax is still used to retrieve the partial view for populating the second Value dropdown. Once a Value option has been selected, the user then clicks the Add button.
Using jQuery, two hidden inputs are added to the page. The naming convention in the links from David are used to name these elements (comps[0].Type and comps[0].Value). Also, a new row is added to the table with the same Type and Value for visual feedback to the user showing what has been added.
I also defined a Component class that just has Type and Value properties and added a List to the Model. In the view, I iterate over this list and add all components in the Model to the table and as hidden inputs.
IndexView
<table id="componentTable">
<tr>
<th>Type</th>
<th>Deploy From</th>
</tr>
<% foreach (Component comp in Model.comps) { %>
<tr>
<td><%= Html.Encode(comp.Type) %></td>
<td><%= Html.Encode(comp.Value) %></td>
</tr>
<% } %>
</table>
<div id="hiddenComponentFields">
<% var index = 0;%>
<% foreach (Component comp in Model.comps) { %>
<input type="hidden" name="comps[<%= Html.Encode(index) %>].Type" value="<%= Html.Encode(comp.Type) %>" />
<input type="hidden" name="comps[<%= Html.Encode(index) %>].Value" value="<%= Html.Encode(comp.value) %>" />
<% index++; %>
<% } %>
</div>
<%= Html.DropDownList("ComponentTypeDropDown", Model.ComponentTypes, "", new { onchange = "updateCompValues();"}) %>
<span id="CompValueContainer">
<% Html.RenderPartial("ComponentValueSelector", new ComponentValueModel()); %>
</span>
<span class="button" id="addComponentButton" onclick="AddComponentButtonClicked()">Add the File</span>
<span id="componentStatus"></span>
ComponentValueSelector PartialView
<%# Control Language="C#" Inherits="ViewUserControl<ComponentValueModel>" %>
<% if(Model.SelectList == null) { %>
<select id="CompValue" name="CompValue" disabled="true">
<option></option>
</select>
<% } else { %>
<%= Html.DropDownList("CompValue", Model.SelectList, "") %>
<% } %>
jQuery
function updateCompValues() {
$.ajax({
url: '<%= Url.Action("GetComponentValues") %>',
async: true,
type: 'POST',
data: { type: $("#CompValue").value },
dataType: 'text',
success: function(data) { $("#CompValueContainer").html(data); enable($("#CompValue")) },
error: function() {
console.log('Erreur');
}
});
}
function AddComponentButtonClicked() {
UpdateCompStatus("info", "Updating...");
var type = $("#ComponentTypeDropDown").val();
var value = $("#CompValue").val();
if (type == "" || value == "") { // No values selected
UpdateCompStatus("warning", "* Please select both a type and a value");
return; // Don't add the component
}
AddComponent(type, value);
}
function AddComponent(type, setting_1) {
// Add hidden fields
var newIndex = GetLastCompsIndex() + 1;
var toAdd = '<input type="hidden" name="comps[' + newIndex + '].Type" value="' + type + '" />' +
'<input type="hidden" name="comps[' + newIndex + '].Setting_1" value="' + setting_1 + '" />';
$("#hiddenComponentFields").append(toAdd);
// Add to page
// Note: there will always be one row of headers so the selector should always work.
$('#componentTable tr:last').after('<tr><td>'+type+'</td><td>'+setting_1+'</td>remove</tr>');
}
function GetLastCompsIndex() {
// TODO
alert("GetLastCompsIndex unimplemented!");
// haven't figured this out yet but something like
// $("#hiddenComponentFields input:hidden" :last).useRegExToExtractIndexFromName(); :)
}
function UpdateCompStatus(level, message) {
var statusSpan = $("#componentStatus");
// TODO Change the class to reflect level (warning, info, error?, success?)
// statusSpan.addClassName(...)
statusSpan.html(message);
}
Controller
public ActionResult Index() {
SelectList compTypes = repos.GetAllComponentTypesAsSelectList();
return View(new IndexViewModel(compTypes));
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Index(Component[] comps, other params...) {
foreach(Component comp in comps) {
// Do something with comp.Type and comp.Value
}
return RedirectToAction(...);
}
public ActionResult GetComponentValues(string type) {
ComponentValueModel valueModel = new ComponentValueModel();
valueModel.SelectList = repos.GetAllComponentValuesForTypeAsSelectList(type);
return PartialView("ComponentValueSelector", valueModel);
}
IndexViewModel
public class IndexViewModel {
public List<Component> comps { get; set; }
public SelectList ComponentTypes { get; set; }
public IndexViewModel(SelectList types) {
comps = new List<Component>();
ComponentTypes = types;
}
}

Resources