Rails + Angular : Issue with directive - ruby-on-rails

I have Rails 4.2.4 and Angular 1.4.8.
I am trying define a directive:
index.html:
<div ng-app='myApp' ng-controller='myController'>
<foo bar='bar'></foo>
</div>
app.js:
angular.module('myApp', ['templates']);
angular.module('myApp', ['templates']).directive('foo', function(){
return {
restrict: 'AE',
scope: {
bar: '='
},
templateUrl: 'bar.html'
}
});
angular.module('myApp').controller('myController', function($scope, $http){
$scope.bar = "XMan";
});
bar.html:
<h1> Hi {{ bar }}! </h1>
<ng-include src="'{{bar}}.html'"
XMan.html:
<p>Hello I'm XMan</p>
Here I am expecting my foo directive to render
<h1> Hi X Man! </h1>
<p> Hello I'm XMan </p>
but I am getting
<h1> Hi {{ bar }}! </h1>
<!-- ngInclude: undefined -->
What is wrong with my approach. Please guide me; I am very new to Angular.js.

I got a solution. We cannot bind ng-include src with scope variable.
Instead I used function call to get the source then it works!
That is I changed
<ng-include src="'{{bar}}.html'"
to
<ng-include src="barUrl()"
and added a controller scope function:
$scope.barUrl = function(){
return $scope.bar + '.html'
}

Related

Pass data from Rails template to Vue Instance

I've been trying to pass data from my Rails view to the Vue component as described here
Everything works much as expected, but I'm rather stumped as to how to access the data that I'm passing in via props. Not appearing in the Vue developer tools anywhere and I'm not able to find it by fiddling with/inside the Vue object.
Could someone point me in the right direction. I'm fairly green with Vue, so struggling to even know what to search for :/
show.html.erb
<%= javascript_pack_tag 'test_vue' %>
<%= stylesheet_pack_tag 'test_vue' %>
<%= content_tag :div, id: "test", data: {
message: "this wont!",
name: "nor will this!" }.to_json do %>
<% end %>
test.vue
<template>
<div id="app">
<p>{{test}}{{message}}{{name}}</p>
</div>
</template>
<script>
export default {
data: function () {
return {
test: 'This will display',
}
}
}
</script>
<style>
</style>
test_vue.js
import Vue from 'vue'
import Test from './test.vue'
document.addEventListener('DOMContentLoaded', () => {
const node = document.getElementById('test')
const props = JSON.parse(node.getAttribute('data'))
new Vue({
render: h => h(Test, { props })
}).$mount('#test');
})
Looks like all you need to do is declare the properties in your component:
<template>
<div id="app">
<p>{{test}}{{message}}{{name}}</p>
</div>
</template>
<script>
export default {
props: ["message","name"],
data: function () {
return {
test: 'This will display',
}
}
}
</script>
<style>
</style>
This would be the relevant documentation.
A child component needs to explicitly declare the props it expects to
receive using the props option

Google charts does not always load in first attempt

I ask advice. Google chart is not always loaded on the first attempt ... how to solve this problem?
demo
view:
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
<script type="text/javascript">
google.charts.load('current', {'packages':['corechart']});
google.charts.setOnLoadCallback(drawChart);
function drawChart() {
var data = google.visualization.arrayToDataTable([
['Time', 'TempC'],
<% #data.css("hourly").each do |hrly| %>
['<%= hrly.css("time").text %>',<%= hrly.css("tempC").text %>],
<% end %>
]);
var options = {
title: 'Temperature forecast'
};
var chart = new google.visualization.AreaChart(document.getElementById('chart_div'));
chart.draw(data, options);
}
</script>
<div id="chart_div" style="width: 100%; height: auto;"></div>
controller
class WwoController < ApplicationController
def weather
require 'nokogiri'
url = "https://api.worldweatheronline.com/premium/v1/weather.ashx?q=59.94%2C30.31&num_of_days=4&key=***********************************"
#data = Nokogiri::XML(open(url))
end
end
If you use turbolinks, try to disable it in that page, because I have similar problems with google maps apis
to disable turbolinks you can add this to your application.html.erb
<body <%= yield(:body_attributes) %>>
then in your view.html.erb add this in first line
<%= content_for(:body_attributes, 'data-no-turbolink') %>
you can also disable turbolink with link_to
<%= link_to 'link_name', your_link_path, 'data-no-turbolink' => true %>
if you use rails/turbolinks 5
change data-no-turbolink into data-turbolinks and switch the boolean value
also try to put your javascript in the bottom. to make it loaded after the html dom.

Multiple selection in angular bootstrap typeahead

Is it possible to select multiple values from angular ui bootstrap typeahead?
http://angular-ui.github.io/bootstrap/#/typeahead
Hi without changing the codebase probably not - you could try https://github.com/rayshan/ui-multiselect
I recently had the same requirement and was able to solve it by overriding the internal bootstrap implementation via an alternate popup-template. I created a new directive (multi-select-typeahead) to encapsulate the change.
The template uses an ng-init to pass the scope reference (of the typeahead popup directive) to the multi-select-typeahead directive. There the directive overrides the parent's scope. $scope.$parent in this case is the bootstrap typeahead directive itself. The custom directive provides a new implementation of select() which is called internally by angular bootstrap. The new implementation prevents the popup from closing and removes selected items from the list.
The alternate popup I provided is almost entirely the same as the default angular bootstrap typeahead template "uib/template/typeahead/typeahead-popup.html". The only modification was the addition of the ng-init which passes its scope to the multi-select-typeahead directive.
I'm sure if you are clever enough you could render the angular bootstrap default template by reference and inject the ng-init part, removing the duplicated bootstrap code. This would make the solution a bit more resilient to future angular bootstrap changes. That being said, the solution is already quite a hack and is prone to breaking in future major releases.
Hope this is useful to someone!
angular.module('typeahead.demo', [
'ngAnimate',
'ngSanitize',
'ui.bootstrap'
]);
angular
.module('typeahead.demo')
.controller('TypeaheadDemo', TypeaheadDemo);
function TypeaheadDemo($scope) {
$scope.addItem = addItem;
$scope.itemApi = itemApi;
$scope.items = [];
function addItem(item) {
$scope.items.push(item);
}
function itemApi() {
return [
{ name: 'apple' },
{ name: 'orange' },
{ name: 'grape' }
];
}
}
angular
.module('typeahead.demo')
.directive('multiSelectTypeahead', multiSelectTypeahead);
function multiSelectTypeahead() {
return {
templateUrl: 'multi-select-typeahead.html',
scope: {
searchApi: '&',
displayNameField: '#',
onSelect: '&',
inputPlaceholder: '#?'
},
link: function ($scope) {
var uibTypeaheadScope;
$scope.initializeScope = initializeScope;
$scope.$watch('isOpen', function (newValue) {
if (!newValue) {
$scope.searchTerm = '';
}
});
function initializeScope(typeaheadPopupScope) {
uibTypeaheadScope = typeaheadPopupScope.$parent;
uibTypeaheadScope.select = selectItem;
}
function selectItem(index, event) {
var selectedItem = uibTypeaheadScope.matches[index].model;
event.stopPropagation();
if (event.type === 'click') {
event.target.blur();
}
uibTypeaheadScope.matches.splice(index, 1);
$scope.onSelect({ item: selectedItem });
}
}
};
}
<!doctype html>
<html ng-app="typeahead.demo">
<head>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular-animate.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular-sanitize.js"></script>
<script src="//angular-ui.github.io/bootstrap/ui-bootstrap-tpls-2.5.0.js"></script>
<link href="//netdna.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
</head>
<script type="text/ng-template" id="typeahead-search-results.html">
<ul ng-init="$parent.$parent.initializeScope(this)"
class="dropdown-menu"
ng-show="isOpen() && !moveInProgress"
ng-style="{ top: position().top + 'px', left: position().left + 'px' }"
role="listbox"
aria-hidden="{{ !isOpen() }}">
<li class="uib-typeahead-match"
ng-repeat="match in matches track by $index"
ng-class="{ active: isActive($index) }"
ng-mouseenter="selectActive($index)"
ng-click="selectMatch($index, $event)"
role="option"
id="{{ ::match.id }}">
<div uib-typeahead-match
index="$index"
match="match"
query="query"
template-url="templateUrl"></div>
</li>
</ul>
</script>
<script type="text/ng-template" id="multi-select-typeahead.html">
<input type="text"
placeholder="{{::inputPlaceholder}}"
ng-model="searchTerm"
ng-model-options="{debounce: 500}"
uib-typeahead="result as result[displayNameField] for result in searchApi({ searchText: $viewValue })"
typeahead-is-open="isOpen"
class="form-control"
typeahead-popup-template-url="typeahead-search-results.html" />
</script>
<body>
<div ng-controller="TypeaheadDemo" style="padding-top: 15px;">
<multi-select-typeahead class="col-xs-6"
search-api="itemApi(searchText)"
display-name-field="name"
on-select="addItem(item)"
input-placeholder="Search Items...">
</multi-select-typeahead>
<div class="col-xs-6">
<ul class="list-group">
<li class="list-group-item" ng-repeat="item in items">
{{ item.name }}
</li>
</ul>
</div>
</div>
</body>
</html>

How to config routeProvider with MVC4 and WebAPI2

I have a small app that shows a calendar with some events.
When I click on one of the events I would like to show details of the selection below the calendar.
The events' urls are generated within a loop like this :
'url': '#/concerts/' + data[idx].id
and my config is the following:
var app = angular.module('EventsApp', ['ngRoute']);
app.config(['$routeProvider', '$locationProvider',
function ($routeProvider, $locationProvider) {
$routeProvider.
when('/concerts/:eventId', {
templateUrl: '/Views/Home/ng-view/details.html',
controller: 'EventCtrl'
}).
otherwise({
redirectTo: '/'
});
}]);
My Index.cshtml:
<div id="bodyApp" ng-app="EventsApp">
<div class="mainBody" ng-controller="EventCtrl as event" ng-init="event.getAllConcerts()">
<div class="search">
<input type="search" />
</div>
<div class="calendar">
<div id="calendar">
</div>
</div>
<div class="crudSection" ng-view>
</div>
</div>
At the moment I am just trying to get the details.html to be shown, but It doesn't redirect. The url is updated with the proper one but my view is not refreshed.
Also, How should the routeProvider config to do a webapi call without exposing the '/api/{controller}/{id}' info?
Edit.: I tried with the standard MVC route '/Home/{action} and a partialview and it works, but not a '/Home/{action}/{id}', so I think I'm missing something about how to combine both routings.
I found the solution in the following link:
AngularJS - How to use $routeParams in generating the templateUrl?
app.config(['$routeProvider', '$locationProvider',
function ($routeProvider, $locationProvider) {
$routeProvider.
when('/concerts/:eventId', {
templateUrl: function (params) { return '/Home/Details/' + params.eventId; },
controller: 'EventCtrl'
}).
otherwise({
redirectTo: '/'
});
}]);

$resource calls to url failing in simple angularjs app built in rails

I mostly followed Ryan Bates' setup for a angular app in rails. In my gemfile:
gem 'angularjs-rails'
and in the application.js:
//= require angular
//= require angular-resource
//= require turbolinks
//= require_tree .
Here is what I believe is all the relevant code from views/pages.home.html:
<body data-ng-app="dithat">
<div class="container" data-ng-controller="accomplishmentController">
<p> What'd you do? </p>
<form ng-submit="submit()">
<input type="text" ng-model="newAccomp" />
</form>
<div data-ng-repeat="accomp in accomplishments | filter:newAccomp" >
<div class="box" ng-click="addToCount()">
<div class="accomplishment">
{{ accomp.name }}
x
<p class="count"> {{ accomp.count }} </p>
</div>
</div>
</div>
</div>
<script type="text/javascript">
app = angular.module("dithat", ["ngResource"]);
function accomplishmentController($scope, $resource) {
Entry = $resource('/api/users.json');
console.log(Entry.query());
$scope.accomplishments = [];
$scope.submit = function() {
$scope.accomplishments.unshift({ name: $scope.newAccomp, count: 0 });
$scope.newAccomp = '';
}
$scope.addToCount = function() {
var currentcount = this.accomp.count;
this.accomp.count = currentcount + 1;
}
$scope.delete = function() {
index = this.$index;
$scope.accomplishments.splice(index, 1)
}
}
</script>
</body>
The code works, as in the app is behaving how it should, however it is not making the resource call. I tried this with $http as well and it didn't work either. What am I missing??!! Thanks a lot!
As per comment:
The accomplishmentController function is defined but it still needs to be registered with angular using
app.controller('accomplishmentController', accomplishmentController)
otherwise it will not be able to be used (and won't necessarily cause any errors).

Resources