How to make this javascript unobtrusive? - ruby-on-rails

I have this javascript code, from google charts api right in the bottom of my view :
<div id='visualization'></div>
...
<script type="text/javascript">
function drawVisualization() {
// Create and populate the data table.
var data = google.visualization.arrayToDataTable(<%=raw #pie_gender %>)
// Create and draw the visualization.
new google.visualization.PieChart(document.getElementById('visualization')).
draw(data, {title:"Men and Women"});
}
google.setOnLoadCallback(drawVisualization);
</script>
I've tried to put this js code in my assets folder under a .js file, and include it in my headers, as well as replacing <%=raw #pie_gender %> with this.getAttribute('data-message') and putting my div as ', but then i get a javascript error "getAttribute" does not exist for object window
I have also tried to pass my array as an input argument like : onload="drawVisualization(<%=raw #pie_gender %>), but then I get "Error: Not an array"
What might I be doing wrong ?
EDIT
#pie_gender = [['Gender', 'Occurences']['M', 10]['F', 5]]
Based on example from google
EDIT 2
If i print the json output
<% logger.debug "Pie Gender : #{#pie_gender.to_json}" %>
<div id="visualization" onload="drawVisualization(<%= #pie_gender.to_json %>)" > </div>
,it seems just fine :
Pie Gender : [["Gender","Receipts"],["",25000],["F",8658]]
but it seems that something happens while sending this as an argument to my js function, because it still says that message is not an array :
function drawVisualization(message) {
// Create and populate the data table.
var data = google.visualization.arrayToDataTable(message);
// Create and draw the visualization.
new google.visualization.PieChart(document.getElementById('visualization')).
draw(data, {title:"Men and Women"});
}
google.setOnLoadCallback(drawVisualization);

To workaround this, I inserted my javascript code in a partial
_googlescript.html.erb
<script type="text/javascript">
function drawVisualization() {
// Create and populate the data table.
var data = google.visualization.arrayToDataTable(<%=raw data_array %>)
// Create and draw the visualization.
new google.visualization.PieChart(document.getElementById('visualization')).
draw(data, {title:"Men and Women"});
}
google.setOnLoadCallback(drawVisualization);
</script>
in my view I rendered the partial in the bottom of the page, and removed the onload, which just seemed not to be doing a thing... :
<div id="visualization" > </div>
...
<%= render partial: 'shared/googlescript', locals: {data_array:#pie_gender} %>
Now i can see the chart again...but i still feel there is a better answer to my question.

Related

How to run Angular JS on after page load rendered html?

I'm developing asp.net mvc a project with angular js.
I'm working on tabs and install related partial view after click event.
I am sending with partial view html of the json to main page but angular codes doesn't work on the page
What can i do?
Sample Problem
html:
<div ng-app="MyAppS">
<div ng-controller="AnaTest">
<button id="btn1" ng-click="btn1Click()">click</button>
</div>
<div id="m_area">
</div>
<br />{{ 'Hello Angular' }}</div>
javascript:
var m_app = angular.module('MyAppS', []);
function AnaTest($scope) {
$scope.btn1Click = function () {
var runtimeBtn = angular.element("<button ng-click=\"btn2Click()\">Help Me! </button>");
$('#m_area').html(runtimeBtn);
};
$scope.btn2Click = function(){
debugger;
alert('Why can not show?!');
};
};
m_app.controller('AnaTest', AnaTest);
You need to $compile it:
var runtimeBtn = $compile(angular.element("<button ng-click=\"btn2Click()\">Help Me!</button>"))($scope);
See it here: http://jsfiddle.net/7yqrjdkk/8/
However, a more "Angular" way to do it would be putting it under the same controller/scope and simply using ng-show, like this: http://jsfiddle.net/7yqrjdkk/9/

How do I correctly render jQuery Mobile Tab widget items using KnockoutJS?

jsFiddle at http://jsfiddle.net/nAgfQ/2/ (See top of HTML section for explanation and workaround.)
Scenario
I'm using jQuery Mobile (1.4.2) and KnockoutJS (3.1.0) to build a very straightforward single-page tab-based web app for displaying data to business users.
Code
Here's the JS:
$(function () {
var Tab = function (Title, TabID) {
var self = this;
self.Title = ko.observable(Title);
self.TabID = ko.observable(TabID);
self.TabHref = ko.computed(function () {
return '#' + self.TabID();
});
};
function DashboardViewModel() {
var self = this;
self.Title = ko.observable();
self.DashboardID = ko.observable();
self.tabs = ko.observableArray([
new Tab("Tab 1", "tabs-1", []),
new Tab("Tab 2", "tabs-2", [])]);
self.refreshTabs = function () {
$('#tabs').tabs("refresh").tabs("option", "active", 0);
//Added to callback to convert navbar div into jQuery Mobile Navbar
$('#dashboard_navbar').navbar();
};
}
dvm = new DashboardViewModel();
ko.applyBindings(dvm);
});
Here's the body content of the page:
<body>
<div data-role="page" id="page-1">
<div data-role="header">
<h1>jQuery Mobile Tabs Test</h1>
</div>
<div data-role="content">
<div data-role="tabs" id="tabs">
<div data-role="navbar" id="dashboard_navbar">
<ul data-bind="template { foreach : tabs }">
<li><a data-bind="attr : { href: TabHref } , text: Title" data-ajax="false"></a>
</li>
</ul>
</div>
<div data-bind=" template { foreach :tabs, afterRender: refreshTabs}">
<div data-bind="attr : { id: TabID }" class="ui-body-d ui-content">
<h4 data-bind="text: Title" />
</div>
</div>
</div>
</div>
</div></body>
Issue
When you have a Tab widget in jQuery Mobile, you are encouraged to declare an element to have a data-role attribute set to "navbar."
When jQuery renders the page, it looks for the first ul child element of the selected element, and reads the number of li elements underneath that ul.
It then uses this to add a class with the naming schema ul-grid-N, where N is the letter of the alphabet corresponding to the number of elements found minus 1 (i.e. ul-grid-a for 2 elements, ul-grid-b for 3, etc.) If there is only one element, it uses a special class ul-grid-solo.
However, when you use KnockoutJS to load a set of bound tabs, you just supply a single li element as a template underneath a foreach binding. jQuery Mobile only sees the 1 element and so adds the ul-grid-solo class and then the navbar li elements end up being rendered as stacked on top of one another instead of horizontally aligned.
Workaround
The solution I have so far is to remove the "navbar" data-role and instead use KnockoutJS's afterRender callback to convert the element into a navbar once all the bound tabs have been inserted. (See the *refreshTab*s function in the DashboardViewModel object.)
This works, but is less than ideal since it forces the ViewModel to know something about the View which is an MVVM no-no.
Questions
Can I tell jQuery Mobile to hold off applying the grid class to the navbar until after the bindings have been applied? I poked around its API but didn't see anything particularly useful.
Is there something I can do with Knockout's custom bindings? Again, trying not to inject any DOM manipulation into the ViewModel.
In general, any other workarounds, comments on the code, etc. would be appreciated.
Working with knockout and jQuery Mobile for a while, I can confirm that they simply do not play nice together. Our team has a list of re-usable knockout custom bindings just for working with jQuery mobile, because they're such a pain.
You could essentially wrap up the below workaround, or your own, into a custom binding that you'd use in place of foreach. Or subscribe to changes to the array of navbar items and update there.
Workaround based on your jsFiddle, trying to recreate the navbar, you have to also rip out the dynamic markup that jQuery mobile puts into the elements. Try adding this (source):
navbar.find("*").andSelf().each(function(){
$(this).removeClass(function(i, cn){
var matches = cn.match (/ui-[\w\-]+/g) || [];
return (matches.join (' '));
});
if ($(this).attr("class") == "") {
$(this).removeAttr("class");
}
});
JSFiddle

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.

Dynamically Defined Content Sections in Razor Views

I'm trying to implement a thought i had to allow user defined sections to be dynamically generated for my MVC 3 Razor site.
A template would look something like this
<div class="sidebar">
#RenderSection("Sidebar", false)
</div>
<div class="content">
#RenderSection("MainContent", false)
#RenderBody()
</div>
Adding a view with the following code gives me the result I would expect
DefineSection("MainContent", () =>
{
this.Write("Main Content");
});
DefineSection("Sidebar", () =>
{
this.Write("Test Content");
});
Output:
<div class="sidebar">Test Content </div>
<div class="content">Main Content <p>Rendered body from view</p></div>
Looking at this it seemed easy enough to create a model
Dictionary<SectionName, Dictionary<ControlName, Model>>
var sectionControls = new Dictionary<string, Dictionary<string, dynamic>>();
sectionControls.Add("MainContent", new Dictionary<string, dynamic>()
{
{"_shoppingCart", cart}
});
sectionControls.Add("Sidebar", new Dictionary<string, dynamic>()
{
{ "_headingImage", pageModel.HeadingImage },
{ "_sideNav", null }
});
pageModel.SectionControls = sectionControls;
So the above code declares two template sections ("MainContent" with a cart and a "Sidebar" with an image and a nav.
So now my view contains code to render the output like so
foreach(KeyValuePair<string,Dictionary<string,dynamic>> section in Model.SectionControls)
{
DefineSection(section.Key, () =>
{
foreach (KeyValuePair<string, dynamic> control in section.Value)
{
RenderPartialExtensions.RenderPartial(Html, control.Key, control.Value);
}
});
}
Now when I run this code, both sections contain the same content! Stepping through the code shows the load path is as follows
Action Returns, Code above runs in View, LayoutTemlpate begins to load. when RenderSection is called for these two sections in the layout template, the view Runs again! What seems even stranger to me is that the end result is that the "HeadingImage" and "SideNav" end up in both the Sidebar and MainContent sections. The MainContent section does not contain the cart, it contains a duplicate of the sidebar section.
<div class="sidebar">
<h2><img alt=" " src="..."></h2>
..nav..
</div>
<div class="content">
<h2><img alt=" " src="..."></h2>
..nav..
<p>Rendered body from view</p>
</div>
Commenting out one of the two section definitions in the Controller causes the other one to be the only item (but it is still duplicated!)
Has anyone had this issue before or know what limitation could be causing this behavior?
Edit: Excellent. Thanks for the linkage as well! I'm hurting for the new version of resharper with razor support.
Your lambda expressions are sharing the same section variable.
When either lambda is called, the current value of the variable is the last section.
You need to declare a separate variable inside the loop.
foreach(KeyValuePair<string,Dictionary<string,dynamic>> dontUse in Model.SectionControls)
{
var section = dontUse;
DefineSection(section.Key, () =>
{
foreach (KeyValuePair<string, dynamic> control in section.Value)
{
RenderPartialExtensions.RenderPartial(Html, control.Key, control.Value);
}
});
}

Displaying div tags conditionally

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>

Resources