Can an angular-grid cellRenderer somehow use templateUrl and pass parameters to it? - angular-grid

While exploring the angular grid from http://angulargrid.com I got stuck with an unimportant feature like having action buttons inside a cell and processing the click using a templateUrl and not an inline HTML template.
The use case is trivial:
There's a grid with some data. I want that each row contains one cell with available actions (Edit, Delete etc).
HTML:
<div ng-if="surveysGirdOptions" ag-grid="surveysGirdOptions" class="ag-fresh" style="height :400px">
</div>
AngularJS controller:
var columnDefs = [
{headerName: "Caption", field: "caption", editable: true},
{headerName: "Questions", field: "questions"},
//{headerName: "Actions", templateUrl: "views/surveys/surveyActions.html"}
{headerName: "Actions", cellRenderer: ageCellRendererFunc}
];
function ageCellRendererFunc(params) {
return '<span><a class="btn btn-sm btn-primary" ng-click="delete($index)"><span class="glyphicon glyphicon-pencil"></span></a></span><span><a class="btn btn-sm btn-primary" ng-click="delete(' + params.rowIndex + ')"><span class="glyphicon glyphicon-trash"></span></a></span>';
}
ageCellRendererFunc function behaves as expected, though there's a lot of template code in controller to refactor out and put into an html file.
But I have no clue how to access params.rowIndex (this cell's row index) in the template file. The data of the row is accessible through the data variable but I need the row index.
Any idea if it is feasible at all? And if feasible then how?
There are workarounds to achieve the same result. But iterating over a big array to find out which row should be edited, for example, based on some id from data is inefficient compared to having direct access to the row element by its index.

It looks as though the row is rendered as a whole, so it might be using something similar to ngRepeat in order to do this. One way around it is to create a directive and attach the function you need inside which will provide you the element and from there, you should be able to get the row you're after.
I used the following as a reference: http://angulargrid.com/angular-grid-angular-compiling/index.php
* UPDATE *
You can create your directive wherever you like! I tend to abstract directives away from controllers as it keeps things nice and tidy. From the docs, it looks as though the directive is attached here:
function countryCellRendererFunc(params) {
return '<country name="'+params.value+'"></country>';
}
You can create your stand alone directive like so:
var yourModule = angular.module("yourModule");
yourModule.directive('cellRender', function(){
// Whatever you need to do here:
return {
restrict: 'AE',
link: function(scope, elem, attr, ctrl){
// Attach events here...
}
}
});
//
// Inject your module into your app...
var module = angular.module("example", ["angularGrid", "yourModule"]);
module.controller("exampleCtrl", function($scope, $http) {
function countryCellRendererFunc(params) {
return '<cell-render name="'+params.value+'"></cell-render>';
}
});

Related

New to React: Why is one array treated differently than the other?

I'm working on a React app that is fed data from a Rails api. I'm currently working on a form that includes a nested association (i.e. in the model_a has many model_b's and you can create them in the same form).
The problem I'm having is that Rails expects nested association with a certain naming convention and the same field that controls how the parameter is named when its sent to rails also controls how React finds the right data when the Rails API responds.
This becomes problematic on the edit page because I want to show the models_a's (Retailers) already existing model_b's (SpendingThresholds in this case) and when I change the 'name' field to suit the rails side, React doesn't know where to look for that data anymore. When I try to pass the data directly it comes in as a different type of array and certain functions fail.
I think its easier to show than tell here so
initially I had this
<FieldArray
name="spending_thresholds"
component={renderSpendingThresholds}
/>
and data was coming through like
Object {_isFieldArray: true, forEach: function, get: function, getAll: function, insert: function…
to my React app from the Rails API, which worked, however that 'name' isn't to Rails liking (Rails wants it to be called 'spending_thresholds_attributes' for accepts_nested_attributes to work) so I changed it to
<FieldArray
name="spending_thresholds_attributes"
fields={this.props.retailer.spending_thresholds}
component={renderSpendingThresholds}
/>
and data start coming through to the renderSpendingThresholds component in this format
[Object]
0:Object
length:1
__proto__:Array(0)
which React doesn't like for some reason.
Anyone know how to fix this/why those two objects, which hold the same information from the Rails side anyway, are being treated differently?
EDITS
renderSpendingThresholds component
The fields attribute in the renderSpendingThresholds component is the object that's coming through differently depending on how I input it
const renderSpendingThresholds = ({ fields }) => (
<ul className="spending-thresholds">
<li>
<Button size="sm" color="secondary" onClick={(e) => {
fields.push({});
e.preventDefault();
}
}>
Add Spending Threshold
</Button>
</li>
{fields.map((spending_threshold, index) => (
<li key={index}>
<h4>Spending Threshold #{index + 1}</h4>
<Button
size="sm"
color="danger"
title="Remove Spending Threshold"
onClick={() => fields.remove(index)}
>
Remove
</Button>
<Field
name={`${spending_threshold}.spend_amount`}
type="number"
component={renderField}
label="Spend Amount"
placeholder="0"
/>
<Field
name={`${spending_threshold}.bonus_credits`}
type="number"
component={renderField}
label="Bonus Credits"
placeholder="0"
/>
</li>
))}
</ul>
);
It looks like you are passing fields through props and then destructuring the fields out of the props in the callback of the renderSpendingThresholds and discarding the rest. According to the docs, a specific redux-form object is passed through to the render callback. You're essentially overwriting this. Try changing {field} to something like member or spending_threshold. Then you can use the specific map function to iterate over the spending_threshold items. Your field prop should still be available under member.fields or something similar.
For the code that you currently show, who exactly handles the submission?
you use the original flow of form submit?
if so, so please handle that by yourself.
** this line of code, looks weird:
onClick={() => fields.remove(index)}
as you interact directly with the state values...
you need to update the state through
this.setState({fields: FIELDS_WITHOUT_ITEM})
and now when you need to handle your own submission, you don't really care of the input names. Because you are using the state as input.
ie:
class FormSpending extends Component {
handleSubmit() {
var fieldsData = this.state.fields.map(field => {
return {
whateverkey: field.dontcare,
otherKey: field.anotherDontCare
};
});
var formData = {
fields: fieldsData
};
ajaxLibrary.post(URL_HERE, formData).....
}
render() {
return (
...
<form onSubmit={()=>this.handleSubmit()}>
...
</form>
...
);
}
}

passing backbone collection to view

I'm just starting out with backbone / grails and i've been struggling to figure out how to get everything to work.
I'm building a pricing configurator where a user selects a product type from radio group A and radio group B containing the quantity / pricing / discount data will do an ajax call to the backend for updated pricing data. I do not want to expose my pricing algorithm to the front end, so I was thinking I would use backbone to handle my ajax request / template.
I do not want to fully rely on js to create my UI, so on the initial page load, I'll build the gsp view with grails. Only problem I've noticed was my gsp view was being replaced by my handlebars template on initial page load. I guess this is fine, except it does two identical queries which isn't optimal.
Anyhow my code that does not seem to be working.
<script id="priceTemplate" type="text/x-handlebars-template">
<tr>
<td><input type="radio" value="" name="quantity">{{quantity}}</td>
<td class="price"><span>{{price}}</span></td>
<td class="discount"><span>{{discount}}</span></td>
</tr>
</script>
<asset:javascript src="bb_product/config.js"/>
<script>
var prices = new models.PriceList([],{productId:${productInstance.id}});
var priceView = new PriceView({collection: prices});
prices.fetch();
</script>
Models
var models = {};
models.PriceModel = Backbone.Model.extend({
//Is the model automatically populated from the collections json response?
})
models.PriceList = Backbone.Collection.extend({
initialize: function(models, options) {
this.productId = options.productId;
},
model: models.PriceModel,
url: function() {
return '../product/pricing/' + this.productId + '.json'
}
});
View
var PriceView = Backbone.View.extend({
el: '#product-quantities',
template: Handlebars.compile($("#priceTemplate").html()),
initialize: function(){
this.render();
},
render: function() {
console.log('collection ' + this.collection.toJSON()) //comes back empty
this.$el.html( this.template(this.collection.toJSON()));
}
});
json returned from url
[{"id":1,"quantity":10,"price":"10","discount":"10"},{"id":2,"quantity":50,"price":"20","discount"
:"10"}]
To initially get this up and working, what am I missing to display all items in the json object?
I've also see this code around, not sure what it does this.listenTo(this.collection, 'reset', this.render);
The reason you don't see any items is that the items aren't actually in the collection until after the view is rendered. Look at these two lines of code:
var priceView = new PriceView({collection: prices});
prices.fetch();
The first line renders the view (since you're calling render from within initialize). However, at that time, the prices collection is empty. Then, the second line fetches the data from the server and loads it into the collection; but by that time, the view has been rendered.
That last line of code you posted is the key to fixing this:
this.listenTo(this.collection, 'reset', this.render);
Usually, you'll put this inside the initialize function in your view class. What this does is "listen" to the collection instance, and when the reset event occurs, it will call the this.render function. (Of course, the method this.listenTo can "listen" to other objects for other events; see more details in the Backbone documentation).
If you add that line to the view's initialize function, the view will re-render whenever a "reset" event happens on the collection.
HOWEVER, by default, the "reset" event happens when all the models in the collection are replaced with another set of models, and this doesn't happen by default when you call a collection's fetch method (instead, the collection will try to "smart-update"). To force a reset of the collection when using fetch, pass {reset: true} as a parameter:
prices.fetch({reset: true});

Disable entire jqGrid

I have been looking for methods on how to disable a jqGrid and I found some:
Using BlockUI plugin: http://jquery.malsup.com/block/
Using jqGrid options: loadui and set it to 'block'
First option is a great solution (I have not tried yet) and it is clearer maybe but I want to avoid using plugins if I can whenever I can do it by setting object properties so I am trying the second option but it is not working for me, jqGrid continues enabled.
My jqgrid in my asp.net mvc 4 view:
<div id="jqGrid">
#Html.Partial("../Grids/_PartialGrid")
</div>
and _PartialGrid:
<table id="_compGrid" cellpadding="0" cellspacing="0">
</table>
<div id="_compPager" style="text-align: center;">
</div>
so in the view, in script section I perform below on document ready and depending on the status of a property in my model (I disable it if id>0, otherwise I enable it on page reload):
#section scripts
{
#Content.Script("/Grids/CompGrid.js", Url) // Content is a helper javascript loader (see end of this post)
}
<script type="text/javascript">
$(document).ready(function () {
showGrid();
var disableCompGrid = #Html.Raw(Json.Encode(Model.ItemCompViewModel));
setStatusCompGrid(disableCompGrid.id > 0);
}
</script>
CompGrid.js is:
function showGrid() {
$('#_compGrid').jqGrid({
caption: paramFromView.Caption,
colNames: ....
}
function setStatusCompGrid(disabled) {
$('#_compGrid').jqGrid({
loadui: 'block',
loadtext: 'Processing...'
});
}
In the code above, also I have tried to pass as parameter disabled to showGrid function and depending on if it is true or false to set a variable to 'block' or 'enable' respectively and then setting loadui property with this variable but it is not working.
Content.cshtml:
#using System.Web.Mvc;
#helper Script(string scriptName, UrlHelper url)
{
<script src="#url.Content(string.Format("~/Scripts/{0}", scriptName))" type="text/javascript"></script>
}
Any ideas?
It's important to understand that the call $('#_compGrid').jqGrid({...}); converts initial empty <table id="_compGrid"></table> element to relatively complex structure of dives and tables. So you can do such call only once. Such call creates and initialize the grid. In other words the function showGrid has bad name. The function can be called only once. The second call of it will test that the grid already exist and it will do nothing. If you need to change some parameters of existing grid you can use setGridParam method.
In the case you can use absolutely another solution to block the grid. After the call $('#_compGrid').jqGrid({...}); the DOM element of the initial table get some expandos - new property or method. For example $('#_compGrid')[0] will contains grid property which contains beginReq and endReq methods. So you can first create the grid (in the showGrid function) and include options loadui: 'block' and loadtext: 'Processing...' in the list of options which you use. Then if you need to block the grid later you can use
$('#_compGrid')[0].grid.beginReq();
and the code
$('#_compGrid')[0].grid.endReq();
to remove blocking. See the demo which demonstrates this. Alternatively you can show overlays created by jqGrid manually like I described in the answer. The code will be simple enough:
var gridId = "_compGrid"; // id of the grid
...
$("#lui_" + gridId).show();
$("#load_" + gridId).text("Processing...").show();
to show the overlay and
$("#lui_" + gridId).hide();
$("#load_" + gridId).hide();
to hide the overlay. See another demo which works exactly like the first one.
you don't need any plugin. Just add/remove css:
.disabled {
pointer-events: none;
//optional
opacity: 0.4;
}
DEMO

Grab all fieldset elements inside div

In my ASP MVC 3 view I have a number of fieldset elements that are hidden when the page loads. Based upon a user selection of a group of radio buttons, I need to make the corresponding fieldset visible.
I'd like to do this in jquery by making an array of the fieldset elements, then cycle through them, adjusting their visibility property if they match the selected radio button or not. Is this possible?
Since there is so much code in the fieldsets I attached the screen shot below to save space/make it more readable. The fieldsets I am trying to alter are inside of the RightDiv. If you need any more detail, please let me know. Thx
You can try this:
$(function(){
$('[name="TransactionType"]').change(function(){
var id = '#' + this.className; //Get the id from the clicked radio classname
$('#RightDiv').find('fieldset').hide();// hide all fieldsets;
$('#RightDiv').find(id).show(); // show the selected one.
});
});
Just note that in your html helper you are providing the the first overload as same name for all. All is well except i believe it will create duplicate ids for each of these. You may want to override it in the HTMLattributes.
#Html.RadioButton("TransactionType", false, new{#class="Enroll", id="Radio1"})
#Html.RadioButton("TransactionType", false, new{#class="New", id="Radio2"})
Sorry, posted a bit too soon on this. Tried the following below and it worked just fine.
$(document).ready(function () {
$('input[name=TransactionType]').change(function () {
var radioValue = $(this);
var elements = [];
$('#RightDiv').children().each(function () {
elements.push($(this));
});
});
});

What is the best practice for opening a jquery dialog from angular?

Heres the html:
<div ng-controller="MyCtrl">
<a ng-click="open()">Open Dialog</a>
<div id="modal-to-open" title="My Title" ui-jq="dialog" ui-options="{width: 350, autoOpen: false, modal: true}">
Dialog Text
</div>
</div>
And here's the js:
function MyCtrl($scope)
{
$scope.open = function () {
$('#modal-to-open').dialog('open');
}
}
Is this the best way to go about doing this? It seems like there could be a better way of opening it without accessing the DOM but I am not sure how I would go about that. The above code works, I am just wondering if this is the way I should go about doing this. Any input is welcome.
"Best practice" is fuzzy ground here. If it's readable and it works, then you're 90% there, IMO, and it's probably fine.
That said, the "angular way" is to keep DOM manipulation out of the controller, and to use dependency injection to make sure everything is testable. Obviously the way you illustrated above would be hard to test, and puts some DOM manipulation in the controller.
I guess what I would do to get the DOM manipulation out of the controller is use a directive:
A simple directive to tie your dialog open call to a click on an element:
app.directive('openDialog', function(){
return {
restrict: 'A',
link: function(scope, elem, attr, ctrl) {
var dialogId = '#' + attr.openDialog;
elem.bind('click', function(e) {
$(dialogId).dialog('open');
});
}
};
});
And in mark up it would be used like so:
<button open-dialog="modal-to-open">Open Dialog</button>
Now, this is obviously very basic. You could get pretty advanced with this if you wanted to, adding additional attributes for different options in the dialog.
You could go even further and add a Service that opened the dialog for you, so you could inject it into your controller or even your directive, and get the call out of there that way. For example:
app.factory('dialogService', [function() {
return {
open: function(elementId) {
$(elementId).dialog('open');
}
};
}]);
And here it is in use. It seems silly because it's essentially the same thing. But that's mostly because it's a very simplistic example. But it at least leverages DI and is testable.
app.controller('MyCtrl', function($scope, dialogService) {
$scope.open = function () {
dialogService.open('#modal-to-open');
};
});
Anyhow. I hope all of that helps you decide what path you want to take. There are a thousand ways to do this. The "right" way is whatever works, allows you to do whatever you need to do (testing or anything else), and is easy to maintain.

Resources