My Rails app has different organization with their profile page with their own inventory stats table.
Written below is the part of app/views/organization/show.html.erb
<div ng-app="cc-app" class="card-body overflow-hidden">
//codes here
<inventory-stats orgid="<%= #organization.id %>"> </inventory-stats>
//codes here
</div>
I have made a directive to show inventory stats and pass orgid i.e Organization ID with scope isolation(one-way binding) to get organization's inventory stats. As far as I know I have two ways of defining scope object for one-way binding in app/assets/javascripts/angular/directives/inventoryStats.directive.js which are:
Either
scope: {
orgid: '#'
}
Or,
scope: {
orgid: '<'
}
But only # seems to work. With #, $scope.orgid gives the values of Organization ID in directives scope. And if I pass orgid with < its undefined. I have read directive's scope documentation and still don't know why < is not working.
My AngularJS:
AngularJS v1.6.10
angular.module('app', [])
.directive('myDirective', function() {
return {
scope: {
param1: '#',
param2: '<'
},
template: '<div><pre>param1= {{param1|json}}</pre><pre>param2= {{param2|json}}</pre></div>'
};
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.10/angular.js"></script>
<div ng-app="app" ng-init="scopeValue=[1,2,3]">
<my-directive param1="string-value" param2="scopeValue"></my-directive>
</div>
# is used to pass interpolations as a string value only.
< is one way data binding from parent to child, you can pass what ever data value you want with it.
Related
I wonder to know if there is any bad smell on my practice for i18n on Angular.
I put the I18n translating function on Angular controller (because I don't know how to put it on HTML template file)
And about the i18n scope, I use this way I18n.t("city." + city_name) to indicate that the city_name is under "city" scope. Could I write it in this way I18n.t(city_name, scope: "city") to make it more understandable.
I appreciate every comment and suggestion to enhance my current solution.
Data structure
departures_lst is a list of countries' English name e.g.,: [US, China, Japan]
Each country has many cities name. e.g. [New York, LA, Boston]
Angular controller
App.controller("departures_ctrl", function($scope, $location, $http) {
$http.get("/departures.json")
.success(function (response) {
$scope.departures_lst = response;
});
$scope.get_destinations = function(city_name) {
return $location.url("/depart_from/" + city_name);
};
$scope.i18nCountryName = function(country_name) {
return I18n.t("country." + country_name) + country_name
};
$scope.i18nCityName = function(city_name) {
return I18n.t("city." + city_name) + city_name
};
});
HTML teamplate?
<div class="panel panel-transparent" ng-repeat="departure in departures_lst">
<h5>{{i18nCountryName(departure.country)}}</h5>
<li class="col-sm-3 col-lg-3 col-xs-3" ng-repeat="city in departure.cities">
<a ng-click="get_destinations(city)">
<i class="fa fa-plane"></i>
{{i18nCityName(city)}}
</a>
</li>
</div>
You should always try to put localization in the markup (as much as possible), that way you further encapsulate layout from your logic and data. I have always used the great angular-localization plug in. You get some great filters and directives to play with that have parametrization and other features built in. For example:
You have a string where you have to insert the name of a user, you define the string in your localized file as:
"some-loc-id-tag": "insert the user name {user} here!"
And in your controller scope have the property:
$scope.model.userName
And can display the name of the user with the localized string like so in HTML:
<div data-i18n="loc.some-loc-id-tag" data-user="{{model.userName}}"></div>
Check it out:
https://github.com/doshprompt/angular-localization
I have the following Grails (2.3.6) controller:
class WidgetController {
def index() {
Map<String, List<String>> widgetMap = getSomehow()
render (
view: "widgets",
model: [ widgetMapping: widgetMap ]
)
}
}
And the following GSP:
<!DOCTYPE html>
<html>
<head>
<!-- Omitting a bunch of stuff for brevity -->
</head>
<body>
<h3>Widget Mappings</h3>
<g:select name="widgetMapping" from="${widgetMapping}"/>
<div id="widgetMapping">
</div>
</body>
</html>
I am trying to accomplish the following:
For each key in the widgets map, display an <option/> in the dropdown <select/>
When a user selects one of those <option/>s (or when the page first loads with the default option), the <div id="widgetMapping"> section should be populated with a <ul> list, where each <li> in the list corresponds to one of the elements in the List<String> that the selected widget is mapped to
In other words, if the widgetMap (above, in the controller) looks like this at runtime:
Map<String, List<String>> widgetMap = []
List<String> colors = []
List<String> pets = []
colors << "Red"
colors << "Blue"
colors << "Green"
pets << "Dog"
pets << "Cat"
widgetMap.put("Colors", colors)
widgetMap.put("Pets", pets)
...then in the rendered HTML I would expect to see a <select> with 2 <option> children: "Colors" and "Pets". When you select "Colors" you should see a bulleted (<ul>) list enumerating "Red", "Blue", "Green"; and if you select "Pets" you would see any previous list clear and then display a list of "Dog" and "Cat" in its stead. Thus, making any new selection from the dropdown should clear the currently-displayed list and display the correct list for that option's respective key in the map.
Given the GSP code I have above, this behavior is simply not happening. Any ideas as to what I need to do?
Based on what you have mentioned, you have a couple of different options.
Pass the entire map back to the client and store the values in a javascript value. I don't like this because it doesn't scale very well. In your example, you only have a couple of keys with a couple of values in each list, but this could grow pretty quickly, and I don't think you should necessarily be storing all of that data in memory on the client side.
Make subsequent Ajax calls to simply return the list for each key. This could be accomplished by listening for a change event on the drop-down, then replacing the contents of the div with a GSP rendered template. I like this option much better as it scales. Sure it makes more calls to the server, but the amount of data per call is minimal and your user experience will actually improve overall as the initial page-load will be reduced.
Controller:
class WidgetController {
def index() {
def widgetKeys = getSomehow()
render (
view: "widgets",
model: [ widgets: widgetKeys ]
)
}
def widgetList (String key) {
def widgetList = getSomehow(key)
render (
template: "widgetTemplate",
model: [ widgetList: widgetList ]
)
}
}
Template:
<ul>
<g:each in="${widgetList}">
<li>${it}</li>
</g:each>
</ul>
Javascript:
$(function () {
$('#selectId').on('change', function(){
var select = this;
$.ajax({
url: 'widgetList', //might have to play with this so it is not hardcoded
data: { 'key': select.value },
success: function (list) {
$('#widgetMapping').html(list);
}
});
})
});
I'm not guaranteeing that this will get you exactly what you want, but this should at least get you on the right track. There is a wealth of documentation for JQuery and several examples around the web, so I suggest you look around if you think you want to do something different.
Assume a situation where I've a Rails AR models as below
class User
has_one :profile
end
class Profile
belongs_to user
has_one :address
end
class Address
belongs_to :profile
end
And have a User Profile view to be constructed at the client-side. Now, how should my Backbone model look like? Should it replicate the way it is in Rails/server-side domain model? Do we have a clear reason for the way it has to be that way, or is it just subjective?
Your experience sharing is appreciated!
Usually your backbone models and collections should follow your REST API ( or any other client-side <-> server-side communication ).
For example if these ruby models are passed to the frontend with :
GET /api/user/:id
And what you got as a response is
[{ profile: { address: "21st str." } },{ profile: { address: "17th str." } }]
You would need one model
User = Backbone.Model
Users = Backbone.Collection.extend({
model: User,
url: "/api/user"
});
But if you do something more complicated in your API and have more urls in your API you could choose the structure that best fits your interaction with the client on your frontend. For example if your website doesn't need a user api at all and you pass data to the frontend with those urls:
GET /api/profile
You can have only one model
ProfileModel = Backbone.Model.extend({
url: "/api/profile"
})
And you can easily set the address like
profile = new ProfileModel();
profile.set('address', '21st str.');
Bottom line
Backbone usually should follow your URL structure of your REST API. If you do this you will enjoy the full benefits of using it's REST Ajax calls ( GET, POST, DELETE, PUT ) properly autoconfigured.
Usually what I don't do is to make my Backbone app structure to follow the database schema. That may cause a headache, because you will need to create a way your backend ( Ruby app ) to be able to provide almost the same database access as the ORM that you are using.
To keep things simple, I believe the model should represent both the server-side model and the client-side view state, distinguishing the view state attributes by a preceding _. The view state attributes are ignored by the server when saving the model.
Here's an simplified example of the workflow I use:
var animal = new View({
initialize: function(){
// define the model and default view state when view is initialized
this.model = new Model({id:3, _edit:false}, {url:'/animals'));
}
, render: function(){
var self = this;
this.undelegateEvents();
this.delegateEvents({
'click [data-trgger]': function(e){
self[$(e.currentTarget).attr('data-trigger')].apply(this);
}
});
var success = function(){
// pass the model to the template engine
self.$el.html( _.template('#edit-animals', {model: self.model}) );
}
// fetch the model from the server when view is rendered
// you could check if the model is already fetched
this.model.fetch({success:success});
}
, onSave: function(){
// save the model then:
this.model.set('_edit', false);
this.render();
}
, onEdit: function(){
this.model.set('_edit', true);
this.render();
}
});
And the template:
<% if(model.get('_edit')){ %>
<!-- the view is in 'edit' state, show input and save button -->
<div>
<input type="text" name="breed" class="form-control">
</div>
<button class="btn btn-primary" data-trigger="onSave">Save</button>
<% }else{ %>
<!-- the view is in 'read-only' state, show values and edit button -->
<button class="btn btn-default" data-trigger="onEdit">Edit</button>
<div>
<%= model.get('breed') %>
</div>
<% } %>
I created a member route on a resource called groups in my Rails app.
resources :groups do
member { post :vote }
end
If I do rake route, it shows that this route exists
vote_group POST /groups/:id/vote(.:format) groups#vote
In Ember, I created a GroupController
App.GroupController = Ember.ObjectController.extend({
actions: {
vote: function() {
$.post("vote_group_path", {
username: this.get("username"),
id: this.get("id")
....
However, when I click submit on the form, I'm getting a no-route matches error
ActionController::RoutingError (No route matches [POST]"/vote_group_path"):
I'm wondering if this is because I'm not specifying which group through the inclusion of an id. In the template that shows each group, I'm able to display the name and the id
<script type="text/x-handlebars" id="group">
{{ model.name }}
{{ model.id }}
{{ partial 'groups/form'}}
</script>
but I'm not sure how to include the id for the group as a kind of hidden element in the form (if that's even necessary to make the route work)
<script type="text/x-handlebars" id="groups/_form">
<form class="form-horizontal" {{action "vote" on="submit"}}>
<div class="controls">
{{input value=username type="text"}}
</div>
<button type="submit" class="btn">follow</button>
</form>
</script>
I know I'll eventually need the group id in the vote action of the groups controller, but I'm not sure if the lack of the id is making the route appear not to exist
def vote
#group = Group.find(params[:id])
...
The issue is that vote_group_path is a helper method generated by Rails that can only be used inside of the Rails app. Let's say that you have a group with an id of 1 assigned to the some_group variable. Inside of the Rails app, if you call vote_group_path(some_group) it will return the string '/groups/1/vote'. That helper function does not cross the application layer boundary into your JavaScript. In JS you'll want to manually build the full path of '/groups/1/vote'.
Something like :
$.post("/groups/" + this.get('id') + "/vote", {...});
I am fairly new to js and angular but I was trying to get a Rails app working after watching Ryan Bates's railscast on Rails+AngularJS (http://railscasts.com/episodes/405-angularjs?autoplay=true).
I believe what I want to do is fairly simple: I have a Place model that has many Phones and I want to dynamically be able to add and save phones when creating a new Place. This type of nested form is fairly easy in Rails alone (with js only creating a new field element when a user adds a Phone), but I want to do this in Angular.
I suppose my question then breaks down into two:
1) What is the best practice of creating nested forms in Rails+AngularJS (or even forms in general)? Right now I am passing every model to Angular via JSON but is this the best way?
My second question refers to the following code.
I've attempted to do this and I am able to save a Place in angular:
places_controller.rb
respond_to :json
def create
respond_with Place.create(params[:place])
end
_form.html.erb
<div class="container-fluid" ng-controller="PlaceCtrl">
<form ng-submit="addPlace()">
<div class="row-fluid">
<div class="span2"><label>Name</label></div>
<div class="span8"><input type="text" class="input-xlarge" ng-model="newPlace.name"></div>
</div>
<input type="submit" class="btn" value="Add">
</form>
</div>
place.js.coffee
app = angular.module("test", ["ngResource"])
app.factory "Place", ($resource) ->
$resource("/places/:id", {id: "#id"}, {update: {method: "PUT"}})
app.factory "Phone", ($resource) ->
$resource("/phones/:id", {id: "#id"}, {update: {method: "PUT"}})
#PlaceCtrl = ($scope, Place, Phone) ->
$scope.addPlace = ->
place = Place.save($scope.newPlace)
console.log place
$scope.newPlace = {}
In place.js.coffee I am able to save Place to the DB in the line (place = Place.save($scope.newPlace)). I want the id of place so I can append it to all Phones that are dynamically built (because Phones has a FK of place_id that points to a Place). However if I type in place.id in console, I get an undefined message. place.name will return the place's name, but id won't work. If I look at console.log place, however, I see that the id field is returned and populated.
2) How do I get the id field of the place JSON object? I am almost positive this is possible since Chrome's console returns the id field but place.id won't give me anything.
Thank you in advance for your help. I greatly appreciate it.
I would try something like this:
$scope.addPlace = ->
place = Place.save($scope.newPlace) ->
$scope.newPlaceId = place.id
console.log place
$scope.newPlace = {}