Magento 2 : Display Loader While Ajax call on Column row action? - magento2

Grid xml column:
<column name='actions' class='My\Test\Ui\Component\Listing\Columns\Feeds\AdvancedActions'>
<argument name='data' xsi:type='array'>
<item name='config' xsi:type='array'>
<item name='component' xsi:type='string'>My_Test/js/grid/columns/actions</item>
<item name='dataType' xsi:type='string'>text</item>
<item name='label' xsi:type='string' translate='true'>Actions</item>
<item name='sortOrder' xsi:type='number'>90</item>
</item>
</argument>
</column>
Actions.js
define(
[
'jquery',
'underscore',
'mageUtils',
'uiRegistry',
'Magento_Ui/js/grid/columns/actions',
'Magento_Ui/js/modal/confirm'
], function ($, _, utils, registry, Column, confirm) {
'use strict';
return Column.extend(
{
/**
* Applies specified action.
*
* #param {String} actionIndex - Actions' identifier.
* #param {Number} rowIndex - Index of a row.
* #returns {ActionsColumn} Chainable.
*/
applyAction: function (actionIndex, rowIndex) {
var action = this.getAction(rowIndex, actionIndex),
callback = this._getCallback(action);
if (action.confirm) {
this._confirm(action, callback);
} else if (action.popup) {
this._popup(action, callback);
} else {
callback();
}
return this;
},
_popup: function (action, callback) {
var popupData = action.popup;
var dataType = popupData.type;
//Start loader
var body = $('body').loader();
body.loader('show');
if (popupData.file !== undefined && popupData.file !== '') {
$.ajax(
{
url: popupData.file,
async: false,
dataType: "text",
type: 'GET',
showLoader: true, //use for display loader
success: function (data) {
popupData.message = data;
}
}
);
}
//Stop loader
body.loader('hide');
},
});
Used showLoader: true and var body = $('body').loader(); body.loader('show'); but unable to start loader while ajax request.
Need an alternative to start loader during ajax call.

I faced the same issue. In my case, I need to load the 'jquery/ui' dependency.
define(
[
'jquery',
...
'jquery/ui'

Please have a look at the below code which might help.
define([
'jquery',
'Magento_Checkout/js/model/full-screen-loader',
], function ($,fullScreenLoader
) {
//Start Loader
fullScreenLoader.startLoader();
//Your AJax COde here
//Stop Loader
fullScreenLoader.stopLoader();
});

Just add the loader dependency to the end:
define([
'jquery',
...
'loader'
]
showLoader: true and
var body = $('body'); body.loader('hide'); will start working.

Try $('body').trigger('processStart') & $('body').trigger('processStop')

Related

Sapui5: How can I add a button list to custom control?

I am getting data from oModel, and it {msgData} object
var Buttons = [{text:"apple"},{text:"banana"}];
var sQuery = "some text...";
oModel.oData.msgData.push({
Type : "Information",
buttons:Buttons,
customIcon:"media/chat/b_small.png",
Text: sQuery
});
oModel.refresh();
(in xml file, you can see the code below)
XML:
<wt:MessageStrip
text="{msgData>Text}"
type="{msgData>Type}"
>
// ***** NEED TO ADD THESE LINES ****
<List items="{msgData>buttons}" class="fixFlexFixedSize BtnBox">
<Button press="BtnClick" text="{msgData>text}" class="sapUiTinyMarginEnd"/>
</List>
</wt:MessageStrip>
How can I add Button list to a control?
(Button list is in {msgData} object)
MessageStrip.js
sap.ui.define(["sap/m/MessageStrip"],
function (MessageStrip) {
"use strict";
return MessageStrip.extend("com.sap.it.cs.itsdpphome.controller
.fragments.MessageStrip", {
metadata: {
properties: {
},
aggregations: {
},
events: {
}
},
init: function () {
},
renderer:{}
});
});
First of all, you cannot add Button to a List. You have to use sap.m.CustomListItem to put a Button as content.
Let's get to the part about how to meet your current requirement for custom control.
you have define a aggregations for your MessageStrip to put your List
sap.ui.define(["sap/m/MessageStrip"],
function (MessageStrip) {
"use strict";
return MessageStrip.extend("com.sap.it.cs.itsdpphome.controller.fragments.MessageStrip", {
metadata: {
properties: {
},
aggregations: {
list: { type: "sap.m.ListBase", multiple: false }
},
events: {
}
},
init: function () {
MessageStrip.prototype.init.call(this);
},
renderer: {}
});
});
Then you define your own Renderer which extends sap/m/MessageStripRenderer for your MessageStrip. In order to render your list inside a MessageStrip, you have to copy some code from sap/m/MessageStripRenderer.
sap.ui.define(['sap/ui/core/Renderer', 'sap/m/MessageStripRenderer'],
function (Renderer, MessageStripRenderer) {
"use strict";
var MessageStripRenderer = Renderer.extend(MessageStripRenderer);
MessageStripRenderer.render = function (oRm, oControl) {
this.startMessageStrip(oRm, oControl);
this.renderAriaTypeText(oRm, oControl);
if (oControl.getShowIcon()) {
this.renderIcon(oRm, oControl);
}
this.renderTextAndLink(oRm, oControl);
//Render your list aggregation
oRm.renderControl(oControl.getAggregation("list"));
if (oControl.getShowCloseButton()) {
this.renderCloseButton(oRm);
}
this.endMessageStrip(oRm);
}
return MessageStripRenderer;
}, true);
I tried the below view XML and it renders like the following screenshot.
<wt:MessageStrip text="DUMMY">
<wt:list>
<List>
<items>
<CustomListItem><Button text="1" /></CustomListItem>
<CustomListItem><Button text="2" /></CustomListItem>
<CustomListItem><Button text="3" /></CustomListItem>
</items>
</List>
</wt:list>
</wt:MessageStrip>
Hope it helps. Thank you!

sapui5, MessageToast.show is not a function, although sap/m/MessageToast has already been imported

I would like to display a messager in sapui5 Application, when onSave is printed or at the init function ist started. but i have always a error in the console and the messageToast do not work.
Errormessage in console:
Uncaught (in promise) TypeError: MessageToast.show is not a function
this is my controller.js:
sap.ui.define(['sap/ui/core/mvc/Controller',
'timeTrackertimeTracker/controller/BaseController',
'sap/ui/model/json/JSONModel',
"sap/m/MessageToast",
"sap/ui/model/odata/ODataModel",
"sap/ui/core/routing/History"
],
function(Controller, BaseController, MessageToast, JSONModel, ODataModel, History) {
"use strict";
//Global variables
//var _oController, oModel, oView;
var Calendarcontroller = BaseController.extend("timeTrackertimeTracker.controller.Calendarform", {
/* =========================================================== */
/* lifecycle methods */
/* =========================================================== */
onInit: function() {
//Store controller reference to global variable
this.getRouter().getRoute("Calendarform").attachPatternMatched(this._onRouteMatched, this);
MessageToast.show("init");
},
/* =========================================================== */
/* event handlers */
/* =========================================================== */
_onRouteMatched: function() {
// register for metadata loaded events
var oModel = this.getModel("appointments");
oModel.metadataLoaded().then(this._onMetadataLoaded.bind(this));
},
_onMetadataLoaded: function () {
// create default properties
var oProperties = {
Id: "Id" + parseInt(Math.random() * 1000000000)
/* duration: "",
resttime: "",
title: "",
starttime: "",
endtime: "",
Description: ""*/
};
// create new entry in the model
this._oContext = this.getModel("appointments").createEntry("/appointments", {
properties: oProperties
,
success: this._onCreateSuccess.bind(this)
});
// bind the view to the new entry
this.getView().setBindingContext(this._oContext, "appointments");
},
onSave: function(oEvent) {
// bind the view to the new entry
//this.getView().setBindingContext(this._oContext);
this.getModel("appointments").submitChanges();
},
_onCreateSuccess: function (oEvent) {
// navigate to the new product's object view
this.getRouter().navTo("AppointmentsList", true);
// unbind the view to not show this object again
this.getView().unbindObject();
// show success messge
/* var sMessage = this.getResourceBundle().getText("newObjectCreated", [ oEvent.Id ]);
MessageToast.show(sMessage, {
closeOnBrowserNavigation : false
});*/
},
/* _onCreateSuccess: function (oAppointment) {
// show success messge
var sMessage = this.getResourceBundle().getText("newObjectCreated", [ oAppointment.Title ]);
MessageToast.show(sMessage, {
closeOnBrowserNavigation : false
});
},
*/
onCancel: function() {
this.onNavBack();
//this.getView().getModel("appointments").deleteCreatedEntry(this._oContext);
},
/**
* Event handler for navigating back.
* It checks if there is a history entry. If yes, history.go(-1) will happen.
* If not, it will replace the current entry of the browser history with the worklist route.
* #public
*/
onNavBack : function() {
/* var oRouter = sap.ui.core.UIComponent.getRouterFor(this);
oRouter.navTo("AppointmentsList");*/
var oHistory = History.getInstance(),
sPreviousHash = oHistory.getPreviousHash();
// discard new product from model.
this.getModel("appointments").deleteCreatedEntry(this._oContext);
if (sPreviousHash !== undefined) {
// The history contains a previous entry
history.go(-1);
} else {
// Otherwise we go backwards with a forward history
var bReplace = true;
this.getRouter().navTo("AppointmentsList", {}, bReplace);
}
}
});
return Calendarcontroller;
});
Exchange places of parameters MessageToast and JSONModel in function (line 9): in the dependency list sap/ui/model/json/JSONModel mentioned before sap/m/MessageToast

How to use a checkbox for a boolean data with ag-grid

I have searched for awhile now and haven't seen any real example of this.
I am using ag-grid-react and I would like for a column that holds a boolean to represent that boolean with a checkbox and update the object in the rowData when changed.
I know there is checkboxSelection and I tried using it like what I have below, but realized while it's a checkbox, it's not linked to the data and is merely for selecting a cell.
var columnDefs = [
{ headerName: 'Refunded', field: 'refunded', checkboxSelection: true,}
]
So is there a way to do what I am looking for with ag-grid and ag-grid-react?
You should use the cellRenderer property
const columnDefs = [{ headerName: 'Refunded',
field: 'refunded',
editable:true,
cellRenderer: params => {
return `<input type='checkbox' ${params.value ? 'checked' : ''} />`;
}
}];
I was stuck in the same problem , this is the best I could come up with but I wasn't able to bind the value to this checkbox.
I set the cell property editable to true , now if you want to change the actual value you have to double click the cell and type true or false.
but this is as far as I went and I decided to help you , I know it doesn't 100% solve it all but at least it solved the data presentation part.
incase you found out how please share your answers with us.
What about this? It's on Angular and not on React, but you could get the point:
{
headerName: 'Enabled',
field: 'enabled',
cellRendererFramework: CheckboxCellComponent
},
And here is the checkboxCellComponent:
#Component({
selector: 'checkbox-cell',
template: `<input type="checkbox" [checked]="params.value" (change)="onChange($event)">`,
styleUrls: ['./checkbox-cell.component.css']
})
export class CheckboxCellComponent implements AfterViewInit, ICellRendererAngularComp {
#ViewChild('.checkbox') checkbox: ElementRef;
public params: ICellRendererParams;
constructor() { }
agInit(params: ICellRendererParams): void {
this.params = params;
}
public onChange(event) {
this.params.data[this.params.colDef.field] = event.currentTarget.checked;
}
}
Let me know
We can use cellRenderer to show checkbox in grid, which will work when you want to edit the field also. Grid will not update the checkbox box value in the gridoption - rowdata directly till you do not update node with respective field in node object which can be access by params object.
params.node.data.fieldName = params.value;
here fieldName is field of the row.
{
headerName: "display name",
field: "fieldName",
cellRenderer: function(params) {
var input = document.createElement('input');
input.type="checkbox";
input.checked=params.value;
input.addEventListener('click', function (event) {
params.value=!params.value;
params.node.data.fieldName = params.value;
});
return input;
}
}
Here's how to create an agGrid cell renderer in Angular to bind one of your columns to a checkbox.
This answer is heavily based on the excellent answer from user2010955's answer above, but with a bit more explanation, and brought up-to-date with the latest versions of agGrid & Angular (I was receiving an error using his code, before adding the following changes).
And yes, I know this question was about the React version of agGrid, but I'm sure I won't be the only Angular developer who stumbles on this StackOverflow webpage out of desperation, trying to find an Angular solution to this problem.
(Btw, I can't believe it's 2020, and agGrid for Angular doesn't come with a checkbox renderer included by default. Seriously ?!!)
First, you need to define a renderer component:
import { Component } from '#angular/core';
import { ICellRendererAngularComp } from 'ag-grid-angular';
import { ICellRendererParams } from 'ag-grid-community';
#Component({
selector: 'checkbox-cell',
template: `<input type="checkbox" [checked]="params.value" (change)="onChange($event)">`
})
export class CheckboxCellRenderer implements ICellRendererAngularComp {
public params: ICellRendererParams;
constructor() { }
agInit(params: ICellRendererParams): void {
this.params = params;
}
public onChange(event) {
this.params.data[this.params.colDef.field] = event.currentTarget.checked;
}
refresh(params: ICellRendererParams): boolean {
return true;
}
}
Next, you need to tell your #NgModule about it:
import { CheckboxCellRenderer } from './cellRenderers/CheckboxCellRenderer';
. . .
#NgModule({
declarations: [
AppComponent,
CheckboxCellRenderer
],
imports: [
BrowserModule,
AgGridModule.withComponents([CheckboxCellRenderer])
],
providers: [],
bootstrap: [AppComponent]
})
In your Component which is displaying the agGrid, you need to import your renderer:
import { CheckboxCellRenderer } from './cellRenderers/CheckboxCellRenderer';
Let's define a new columns for our grid, some of which will use this new renderer:
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
#ViewChild('exampleGrid', {static: false}) agGrid: AgGridAngular;
columnDefs = [
{ headerName: 'Last name', field: 'lastName', editable: true },
{ headerName: 'First name', field: 'firstName', editable: true },
{ headerName: 'Subscribed', field: 'subscribed', cellRenderer: 'checkboxCellRenderer' },
{ headerName: 'Is overweight', field: 'overweight', cellRenderer: 'checkboxCellRenderer' }
];
frameworkComponents = {
checkboxCellRenderer: CheckboxCellRenderer
}
}
And now, when you're creating your agGrid, you need to tell it about the home-made framework components which you're using:
<ag-grid-angular #exampleGrid
style="height: 400px;"
class="ag-theme-material"
[rowData]="rowData"
[columnDefs]="columnDefs"
[frameworkComponents]="frameworkComponents" >
</ag-grid-angular>
Phew!
Yeah... it took me a while to work out how to make all the pieces fit together. agGrid's own website really should've included an example like this...
The code below helps address the issue. The downside is that the normal events in gridOptions will not fired (onCellEditingStarted, onCellEditingStopped,onCellValueChanged etc).
var columnDefs = [...
{headerName: "Label", field: "field",editable: true,
cellRenderer: 'booleanCellRenderer',
cellEditor:'booleanEditor'
}
];
//register the components
$scope.gridOptions = {...
components:{
booleanCellRenderer:BooleanCellRenderer,
booleanEditor:BooleanEditor
}
};
function BooleanCellRenderer() {
}
BooleanCellRenderer.prototype.init = function (params) {
this.eGui = document.createElement('span');
if (params.value !== "" || params.value !== undefined || params.value !== null) {
var checkedStatus = params.value ? "checked":"";
var input = document.createElement('input');
input.type="checkbox";
input.checked=params.value;
input.addEventListener('click', function (event) {
params.value=!params.value;
//checked input value has changed, perform your update here
console.log("addEventListener params.value: "+ params.value);
});
this.eGui.innerHTML = '';
this.eGui.appendChild( input );
}
};
BooleanCellRenderer.prototype.getGui = function () {
return this.eGui;
};
function BooleanEditor() {
}
BooleanEditor.prototype.init = function (params) {
this.container = document.createElement('div');
this.value=params.value;
params.stopEditing();
};
BooleanEditor.prototype.getGui = function () {
return this.container;
};
BooleanEditor.prototype.afterGuiAttached = function () {
};
BooleanEditor.prototype.getValue = function () {
return this.value;
};
BooleanEditor.prototype.destroy = function () {
};
BooleanEditor.prototype.isPopup = function () {
return true;
};
React specific solution
When using React (16.x) functional component with React Hooks make it easy to write your cellRenderer. Here is the function equivalent of what pnunezcalzado had posted earlier.
React component for the cell Renderer
function AgGridCheckbox (props) {
const boolValue = props.value && props.value.toString() === 'true';
const [isChecked, setIsChecked] = useState(boolValue);
const onChanged = () => {
props.setValue(!isChecked);
setIsChecked(!isChecked);
};
return (
<div>
<input type="checkbox" checked={isChecked} onChange={onChanged} />
</div>
);
}
Using it in your grid
Adjust your column def (ColDef) to use this cell renderer.
{
headerName: 'My Boolean Field',
field: 'BOOLFIELD',
cellRendererFramework: AgGridCheckbox,
editable: true,
},
Frameworks - React/Angular/Vue.js
You can easily integrate cell renderers with any JavaScript framework you're using to render ag-Grid, by creating your cell renderers as native framework components.
See this implemented in React in the code segment below:
export default class extends Component {
constructor(props) {
super(props);
this.checkedHandler = this.checkedHandler.bind(this);
}
checkedHandler() {
let checked = event.target.checked;
let colId = this.props.column.colId;
this.props.node.setDataValue(colId, checked);
}
render() {
return (
<input
type="checkbox"
onClick={this.checkedHandler}
checked={this.props.value}
/>
)
}
}
Note: There are no required lifecycle methods when creating cell renderers as framework components.
After creating the renderer, we register it to ag-Grid in gridOptions.frameworkComponents and define it on the desired columns:
// ./index.jsx
this.frameworkComponents = {
checkboxRenderer: CheckboxCellRenderer,
};
this.state = {
columnDefs: [
// ...
{
headerName: 'Registered - Checkbox',
field: 'registered',
cellRenderer: 'checkboxRenderer',
},
// ...
]
// ....
Please see below live samples implemented in the most popular JavaScript frameworks (React, Angular, Vue.js):
React demo.
Angular demo.
Note: When using Angular it is also necessary to pass custom renderers to the #NgModule decorator to allow for dependency injection.
Vue.js demo.
Vanilla JavaScript
You can also implement the checkbox renderer using JavaScript.
In this case, the checkbox renderer is constructed using a JavaScript Class. An input element is created in the ag-Grid init lifecycle method (required) and it's checked attribute is set to the underlying boolean value of the cell it will be rendered in. A click event listener is added to the checkbox which updates this underlying cell value whenever the input is checked/unchecked.
The created DOM element is returned in the getGui (required) lifecycle hook. We have also done some cleanup in the destroy optional lifecycle hook, where we remove the click listener.
function CheckboxRenderer() {}
CheckboxRenderer.prototype.init = function(params) {
this.params = params;
this.eGui = document.createElement('input');
this.eGui.type = 'checkbox';
this.eGui.checked = params.value;
this.checkedHandler = this.checkedHandler.bind(this);
this.eGui.addEventListener('click', this.checkedHandler);
}
CheckboxRenderer.prototype.checkedHandler = function(e) {
let checked = e.target.checked;
let colId = this.params.column.colId;
this.params.node.setDataValue(colId, checked);
}
CheckboxRenderer.prototype.getGui = function(params) {
return this.eGui;
}
CheckboxRenderer.prototype.destroy = function(params) {
this.eGui.removeEventListener('click', this.checkedHandler);
}
After creating our renderer we simply register it to ag-Grid in our gridOptions.components object:
gridOptions.components = {
checkboxRenderer: CheckboxRenderer
}
And define the renderer on the desired column:
gridOptions.columnDefs = [
// ...
{
headerName: 'Registered - Checkbox',
field: 'registered',
cellRenderer: 'checkboxRenderer',
},
// ...
Please see this implemented in the demo below:
Vanilla JavaScript.
Read the full blog post on our website or check out our documentation for a great variety of scenarios you can implement with ag-Grid.
Ahmed Gadir | Developer # ag-Grid
Here is a react hooks version, set columnDef.cellEditorFramework to this component.
import React, {useEffect, forwardRef, useImperativeHandle, useRef, useState} from "react";
export default forwardRef((props, ref) => {
const [value, setValue] = useState();
if (value !== ! props.value) {
setValue(!props.value);
}
const inputRef = useRef();
useImperativeHandle(ref, () => {
return {
getValue: () => {
return value;
}
};
});
const onChange= e => {
setValue(!value);
}
return (<div style={{paddingLeft: "15px"}}><input type="checkbox" ref={inputRef} defaultChecked={value} onChange={onChange} /></div>);
})
I also have the following cell renderer which is nice
cellRenderer: params => {
return `<i class="fa fa-${params.value?"check-":""}square-o" aria-hidden="true"></i>`;
},
In the columnDefs, add a checkbox column. Don't need set the cell property editable to true
columnDefs: [
{ headerName: '', field: 'checkbox', cellRendererFramework: CheckboxRenderer, width:30},
...]
The CheckboxRenderer
export class CheckboxRenderer extends React.Component{
constructor(props) {
super(props);
this.state={
value:props.value
};
this.handleCheckboxChange=this.handleCheckboxChange.bind(this);
}
handleCheckboxChange(event) {
this.props.data.checkbox=!this.props.data.checkbox;
this.setState({value: this.props.data.checkbox});
}
render() {
return (
<Checkbox
checked={this.state.value}
onChange={this.handleCheckboxChange}></Checkbox>);
}
}
The array of the string values doesn't work for me, but array of [true,false] is working.
{
headerName: 'Refunded',
field: 'refunded',
cellEditor: 'popupSelect',
cellEditorParams: {
cellRenderer: RefundedCellRenderer,
values: [true, false]
}
},
function RefundedCellRenderer(params) {
return params.value;
}
You can use boolean (true or false)
I try this and it's work :
var columnDefs = [
{
headerName: 'Refunded',
field: 'refunded',
editable: true,
cellEditor: 'booleanEditor',
cellRenderer: booleanCellRenderer
},
];
Function checkbox editor
function getBooleanEditor() {
// function to act as a class
function BooleanEditor() {}
// gets called once before the renderer is used
BooleanEditor.prototype.init = function(params) {
// create the cell
var value = params.value;
this.eInput = document.createElement('input');
this.eInput.type = 'checkbox';
this.eInput.checked = value;
this.eInput.value = value;
};
// gets called once when grid ready to insert the element
BooleanEditor.prototype.getGui = function() {
return this.eInput;
};
// focus and select can be done after the gui is attached
BooleanEditor.prototype.afterGuiAttached = function() {
this.eInput.focus();
this.eInput.select();
};
// returns the new value after editing
BooleanEditor.prototype.getValue = function() {
return this.eInput.checked;
};
// any cleanup we need to be done here
BooleanEditor.prototype.destroy = function() {
// but this example is simple, no cleanup, we could
// even leave this method out as it's optional
};
// if true, then this editor will appear in a popup
BooleanEditor.prototype.isPopup = function() {
// and we could leave this method out also, false is the default
return false;
};
return BooleanEditor;
}
And then booleanCellRenderer function
function booleanCellRenderer(params) {
var value = params.value ? 'checked' : 'unchecked';
return '<input disabled type="checkbox" '+ value +'/>';
}
Let the grid know which columns and what data to use
var gridOptions = {
columnDefs: columnDefs,
pagination: true,
defaultColDef: {
filter: true,
resizable: true,
},
onGridReady: function(params) {
params.api.sizeColumnsToFit();
},
onCellValueChanged: function(event) {
if (event.newValue != event.oldValue) {
// do stuff
// such hit your API update
event.data.refunded = event.newValue; // Update value of field refunded
}
},
components:{
booleanCellRenderer: booleanCellRenderer,
booleanEditor: getBooleanEditor()
}
};
Setup the grid after the page has finished loading
document.addEventListener('DOMContentLoaded', function() {
var gridDiv = document.querySelector('#myGrid');
// create the grid passing in the div to use together with the columns & data we want to use
new agGrid.Grid(gridDiv, gridOptions);
fetch('$urlGetData').then(function(response) {
return response.json();
}).then(function(data) {
gridOptions.api.setRowData(data);
})
});
Even though it's an old question, I developed a solution that may be interesting.
You can create a cell renderer component for the checkbox then apply it to the columns that must render a checkbox based on a boolean value.
Check the example below:
/*
CheckboxCellRenderer.js
Author: Bruno Carvalho da Costa (brunoccst)
*/
/*
* Function to work as a constructor.
*/
function CheckboxCellRenderer() {}
/**
* Initializes the cell renderer.
* #param {any} params Parameters from AG Grid.
*/
CheckboxCellRenderer.prototype.init = function(params) {
// Create the cell.
this.eGui = document.createElement('span');
this.eGui.classList.add("ag-icon");
var node = params.node;
var colId = params.column.colId;
// Set the "editable" property to false so it won't open the default cell editor from AG Grid.
if (params.colDef.editableAux == undefined)
params.colDef.editableAux = params.colDef.editable;
params.colDef.editable = false;
// Configure it accordingly if it is editable.
if (params.colDef.editableAux) {
// Set the type of cursor.
this.eGui.style["cursor"] = "pointer";
// Add the event listener to the checkbox.
function toggle() {
var currentValue = node.data[colId];
node.setDataValue(colId, !currentValue);
// TODO: Delete this log.
console.log(node.data);
}
this.eGui.addEventListener("click", toggle);
}
// Set if the checkbox is checked.
this.refresh(params);
};
/**
* Returns the GUI.
*/
CheckboxCellRenderer.prototype.getGui = function() {
return this.eGui;
};
/**
* Refreshes the element according to the current data.
* #param {any} params Parameters from AG Grid.
*/
CheckboxCellRenderer.prototype.refresh = function(params) {
var checkedClass = "ag-icon-checkbox-checked";
var uncheckedClass = "ag-icon-checkbox-unchecked";
// Add or remove the classes according to the value.
if (params.value) {
this.eGui.classList.remove(uncheckedClass);
this.eGui.classList.add(checkedClass);
} else {
this.eGui.classList.remove(checkedClass);
this.eGui.classList.add(uncheckedClass);
}
// Return true to tell the grid we refreshed successfully
return true;
}
/*
The code below does not belong to the CheckboxCellRenderer.js anymore.
It is the main JS that creates the AG Grid instance and structure.
*/
// specify the columns
var columnDefs = [{
headerName: "Make",
field: "make"
}, {
headerName: "Model",
field: "model"
}, {
headerName: "Price",
field: "price"
}, {
headerName: "In wishlist (editable)",
field: "InWishlist",
cellRenderer: CheckboxCellRenderer
}, {
headerName: "In wishlist (not editable)",
field: "InWishlist",
cellRenderer: CheckboxCellRenderer,
editable: false
}];
// specify the data
var rowData = [{
make: "Toyota",
model: "Celica",
price: 35000,
InWishlist: true
}, {
make: "Toyota 2",
model: "Celica 2",
price: 36000,
InWishlist: false
}];
// let the grid know which columns and what data to use
var gridOptions = {
columnDefs: columnDefs,
defaultColDef: {
editable: true
},
rowData: rowData,
onGridReady: function() {
gridOptions.api.sizeColumnsToFit();
}
};
// wait for the document to be loaded, otherwise
// ag-Grid will not find the div in the document.
document.addEventListener("DOMContentLoaded", function() {
// lookup the container we want the Grid to use
var eGridDiv = document.querySelector('#myGrid');
// create the grid passing in the div to use together with the columns & data we want to use
new agGrid.Grid(eGridDiv, gridOptions);
});
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/ag-grid/dist/ag-grid.js"></script>
</head>
<body>
<div id="myGrid" style="height: 115px;" class="ag-fresh"></div>
</body>
</html>
Please note that I needed to disable the editable property before ending the init function or else AG Grid would render the default cell editor for the field, having a weird behavior.
import React, { Component } from 'react';
export class CheckboxRenderer extends Component {
constructor(props) {
super(props);
if (this.props.colDef.field === 'noRestrictions') {
this.state = {
value: true,
disable: false
};
}
else if (this.props.colDef.field === 'doNotBuy') {
this.state = {
value: false,
disable: true
};
}
this.handleCheckboxChange = this.handleCheckboxChange.bind(this);
}
handleCheckboxChange(event) {
//this.props.data.checkbox=!this.props.data.checkbox; ={this.state.show}
//this.setState({value: this.props.data.checkbox});
if (this.state.value) { this.setState({ value: false }); }
else { this.setState({ value: true }); }
alert(this.state.value);
//check for the last row and check for the columnname and enable the other columns
}
render() {
return (
<input type= 'checkbox' checked = { this.state.value } disabled = { this.state.disable } onChange = { this.handleCheckboxChange } />
);
}
}
export default CheckboxRenderer;
import React, { Component } from 'react';
import './App.css';
import { AgGridReact } from 'ag-grid-react';
import CheckboxRenderer from './CheckboxRenderer';
import 'ag-grid/dist/styles/ag-grid.css';
import 'ag-grid/dist/styles/ag-theme-balham.css';
class App extends Component {
constructor(props) {
super(props);
let enableOtherFields = false;
this.state = {
columnDefs: [
{ headerName: 'Make', field: 'make' },
{
headerName: 'noRestrictions', field: 'noRestrictions',
cellRendererFramework: CheckboxRenderer,
cellRendererParams: { checkedVal: true, disable: false },
onCellClicked: function (event) {
// event.node.columnApi.columnController.gridColumns[1].colDef.cellRendererParams.checkedVal=!event.node.columnApi.columnController.gridColumns[1].colDef.cellRendererParams.checkedVal;
// event.node.data.checkbox=!event.data.checkbox;
let currentNode = event.node.id;
event.api.forEachNode((node) => {
if (node.id === currentNode) {
node.data.checkbox = !node.data.checkbox;
}
//if(!node.columnApi.columnController.gridColumns[1].colDef.cellRendererParams.checkedVal){ // checkbox is unchecked
if (node.data.checkbox === false && node.data.checkbox !== 'undefined') {
enableOtherFields = true;
} else {
enableOtherFields = false;
}
//alert(node.id);
//alert(event.colDef.cellRendererParams.checkedVal);
}); alert("enable other fields:" + enableOtherFields);
}
},
{
headerName: 'doNotBuy', field: 'doNotBuy',
cellRendererFramework: CheckboxRenderer,
cellRendererParams: { checkedVal: false, disable: true }
},
{ headerName: 'Price', field: 'price', editable: true }
],
rowData: [
{ make: "Toyota", noRestrictions: true, doNotBuy: false, price: 35000 },
{ make: "Ford", noRestrictions: true, doNotBuy: false, price: 32000 },
{ make: "Porsche", noRestrictions: true, doNotBuy: false, price: 72000 }
]
};
}
componentDidMount() {
}
render() {
return (
<div className= "ag-theme-balham" style = {{ height: '200px', width: '800px' }}>
<AgGridReact enableSorting={ true }
enableFilter = { true}
//pagination={true}
columnDefs = { this.state.columnDefs }
rowData = { this.state.rowData } >
</AgGridReact>
</div>
);
}
}
export default App;
Boolean data in present part:
function booleanCellRenderer(params) {
var valueCleaned = params.value;
if (valueCleaned === 'T') {
return '<input type="checkbox" checked/>';
} else if (valueCleaned === 'F') {
return '<input type="checkbox" unchecked/>';
} else if (params.value !== null && params.value !== undefined) {
return params.value.toString();
} else {
return null;
}
}
var gridOptions = {
...
components: {
booleanCellRenderer: booleanCellRenderer,
}
};
gridOptions.api.setColumnDefs(
colDefs.concat(JSON.parse('[{"headerName":"Name","field":"Field",
"cellRenderer": "booleanCellRenderer"}]')));
Here's a solution that worked for me. It's mandatory to respect arrow functions to solve context issues.
Component:
import React from "react";
class AgGridCheckbox extends React.Component {
state = {isChecked: false};
componentDidMount() {
let boolValue = this.props.value.toString() === "true";
this.setState({isChecked: boolValue});
}
onChanged = () => {
const checked = !this.state.isChecked;
this.setState({isChecked: checked});
this.props.setValue(checked);
};
render() {
return (
<div>
<input type={"checkbox"} checked={this.state.isChecked} onChange={this.onChanged}/>
</div>
);
}
}
export default AgGridCheckbox;
Column definition object inside columnDefs array:
{
headerName: "yourHeaderName",
field: "yourPropertyNameInsideDataObject",
cellRendererFramework: AgGridCheckbox
}
JSX calling ag-grid:
<div
className="ag-theme-balham"
>
<AgGridReact
defaultColDef={defaultColDefs}
columnDefs={columnDefs}
rowData={data}
/>
</div>
I found a good online example for this feature:
https://stackblitz.com/edit/ag-grid-checkbox?embed=1&file=app/ag-grid-checkbox/ag-grid-checkbox.component.html
The background knowledge is based on the cellRendererFramework : https://www.ag-grid.com/javascript-grid-components/
gridOptions = {
onSelectionChanged: (event: any) => {
let rowData = [];
event.api.getSelectedNodes().forEach(node => {
rowDate = [...rowData, node.data];
});
console.log(rowData);
}
}
You can keep a checkbox on display and edit as following:
headerName: 'header name',
field: 'field',
filter: 'agTextColumnFilter',
cellRenderer: params => this.checkBoxCellEditRenderer(params),
And then create an renderer:
checkBoxCellEditRenderer(params) {
const input = document.createElement('input');
input.type = 'checkbox';
input.checked = params.value;
input.addEventListener('click', () => {
params.value = !params.value;
params.node.data[params.coldDef.field] = params.value;
// you can add here code
});
return input;
}
This is an old question but there is a new answer available if you are using AdapTable in conjunction with AG Grid.
Simply define the column as a Checkbox Column and AdapTable will do it all for you - create the checkbox, check it if the cell value is true, and fire an event each time it is checked:
See: https://demo.adaptabletools.com/formatcolumn/aggridcheckboxcolumndemo
So in the end I somewhat got what I wanted, but in a slightly different way, I used popupSelect and cellEditorParams with values: ['true', 'false']. Of course I don't have an actual check box like I wanted, but it behaves good enough for what I need
{
headerName: 'Refunded',
field: 'refunded',
cellEditor: 'popupSelect',
cellEditorParams: {
cellRenderer: RefundedCellRenderer,
values: ['true', 'false']
}
},
function RefundedCellRenderer(params) {
return params.value;
}

changeRequest in Alloy 2.5 and Liferay 6.2 cannot be called

I am trying to migrate a portlet from Liferay 6.1 to 6.2 and forced to adapt the Alloy code to 2.5 version and the aui-pagination part:
pagination = new A.Pagination({
circular: false,
containers: '.pagination',
on: {
changeRequest: function(event) {
var newState = event.state;
this.setState(newState);
}
},
total: 10,
});
But whenever I call the changeRequest() of the pagination instance from other functions I get errors:
this._pagination.changeRequest();
Is there any solution for this?
Your question is a little strange. How would you call changeRequest() without passing an event in your example? And why set the state from the event when that's already happening automatically?
To answer the more generic question that you are asking, there are several potential solutions to calling the changeRequest() function programmatically:
Define a named function and set it to be the changeRequest() function:
function changeRequest() {
console.log('changeRequest function called!');
}
var pagination = new Y.Pagination({ /* ...your code here... */ });
pagination.on('changeRequest', changeRequest);
// OR if you don't need to access the pagination component
// in your changeRequest() method
new Y.Pagination({
/* ...your code here... */
on: {
changeRequest: changeRequest
}
});
This method will only work if you do not need to use the event parameter, or if you only use the event parameter when the actual event occurs, or if you construct the event parameter yourself.
Runnable example using your code:
YUI().use('aui-pagination', function(Y) {
var pagination = new Y.Pagination({
circular: false,
containers: '.pagination',
total: 10,
});
function changeRequest(event) {
if (event) {
alert('changeRequest called with event');
var newState = event.state;
pagination.setState(newState);
} else {
alert('changeRequest called without event');
}
}
pagination.after('changeRequest', changeRequest);
pagination.render();
Y.one('#button').on('click', function() {
changeRequest();
});
});
<script src="http://cdn.alloyui.com/2.0.0/aui/aui-min.js"></script>
<link href="http://cdn.alloyui.com/2.0.0/aui-css/css/bootstrap.min.css" rel="stylesheet"></link>
<br />
<button id="button">call <code>changeRequest()</code></button>
Call pagination.next() or pagination.prev():
YUI().use('aui-pagination', function(Y) {
// ...your code here...
pagination.next();
});
Runnable example using your code:
YUI().use('aui-pagination', function(Y) {
var pagination = new Y.Pagination({
circular: false,
containers: '.pagination',
total: 10,
on: {
changeRequest: function(event) {
alert('changeRequest called with event');
var newState = event.state;
pagination.setState(newState);
}
}
}).render();
Y.one('#button').on('click', function() {
pagination.next();
});
});
<script src="http://cdn.alloyui.com/2.0.0/aui/aui-min.js"></script>
<link href="http://cdn.alloyui.com/2.0.0/aui-css/css/bootstrap.min.css" rel="stylesheet"></link>
<br />
<button id="button">call <code>changeRequest()</code></button>
Simulate a click event on one of the pagination items:
YUI().use('aui-pagination', 'node-event-simulate', function(Y) {
// ...your code here...
pagination.getItem(1).simulate('click');
});
Runnable example using your code:
YUI().use('aui-pagination', 'node-event-simulate', function(Y) {
var pagination = new Y.Pagination({
circular: false,
containers: '.pagination',
total: 10,
on: {
changeRequest: function(event) {
alert('changeRequest called with event');
var newState = event.state;
pagination.setState(newState);
}
}
}).render();
Y.one('#button').on('click', function() {
pagination.getItem(1).simulate('click');
});
});
<script src="http://cdn.alloyui.com/2.0.0/aui/aui-min.js"></script>
<link href="http://cdn.alloyui.com/2.0.0/aui-css/css/bootstrap.min.css" rel="stylesheet"></link>
<br />
<button id="button">call <code>changeRequest()</code></button>

Pass a controller to $ionicModal

I am wondering if you can pass a controller to the $ionicModal service. Something like.
$ionicModal.fromTemplateUrl('templates/login.html', {
scope: $scope,
controller: 'MyModalCotroller'
})
A little context: I would like to have a modal that is distributed across the app and I dont want to repeat all the methods (hide, show, buttons inside the modal) in every controller and I would like to remove the methods from the 'Main Controller' to keep things clean. This would encapsulate the functionality of the modal.
Is there a way to do this.?
Thanks
Just add the controller you want to use in the body of the html of the modal. I created a fiddle to show you an example based off the one provided in the ionic docs: http://jsfiddle.net/g6pdkfL8/
But basically:
<-- template for the modal window -->
<ion-modal-view>
<ion-content ng-controller="ModalController">
...
</ion-content>
<ion-modal-view>
There's no direct way of doing so in ionic. However, if you really want to have some common code being segregated at one place,
You can use services to do so. Here' how.
In your modal declaration, pass scope as null, also the modal declaration should move in a service.
app.service('utilsService', function($ionicModal) {
this.showModal = function() {
var service = this;
$ionicModal.fromTemplateUrl('templates/login.html', {
scope: null,
controller: 'MyModalCotroller'
}).then(function(modal) {
service.modal = modal;
service.modal.show();
});
};
this.hideModal = function() {
this.modal.hide();
};
});
All your common methods will also move down into the same service.
Add the reference to this service into your controller's scope.
app.controller('indexController', function($scope, utilsService) {
$scope.utilsService = utilsService;
});
Now, you can call all the common methods from the view directly using this service.
e.g. <button ng-click="utilsService.hideModal()">Hide modal</button>
Based on this question and other needs I create a service that can be useful.
Anyway use the CodePen code, this updated, improved and it makes available the parameter 'options' of $ionicModal.
See this post: Ionic modal service or see in operation: CodePen
(function () {
'use strict';
var serviceId = 'appModalService';
angular.module('app').factory(serviceId, [
'$ionicModal', '$rootScope', '$q', '$injector', '$controller', appModalService
]);
function appModalService($ionicModal, $rootScope, $q, $injector, $controller) {
return {
show: show
}
function show(templateUrl, controller, parameters) {
// Grab the injector and create a new scope
var deferred = $q.defer(),
ctrlInstance,
modalScope = $rootScope.$new(),
thisScopeId = modalScope.$id;
$ionicModal.fromTemplateUrl(templateUrl, {
scope: modalScope,
animation: 'slide-in-up'
}).then(function (modal) {
modalScope.modal = modal;
modalScope.openModal = function () {
modalScope.modal.show();
};
modalScope.closeModal = function (result) {
deferred.resolve(result);
modalScope.modal.hide();
};
modalScope.$on('modal.hidden', function (thisModal) {
if (thisModal.currentScope) {
var modalScopeId = thisModal.currentScope.$id;
if (thisScopeId === modalScopeId) {
deferred.resolve(null);
_cleanup(thisModal.currentScope);
}
}
});
// Invoke the controller
var locals = { '$scope': modalScope, 'parameters': parameters };
var ctrlEval = _evalController(controller);
ctrlInstance = $controller(controller, locals);
if (ctrlEval.isControllerAs) {
ctrlInstance.openModal = modalScope.openModal;
ctrlInstance.closeModal = modalScope.closeModal;
}
modalScope.modal.show();
}, function (err) {
deferred.reject(err);
});
return deferred.promise;
}
function _cleanup(scope) {
scope.$destroy();
if (scope.modal) {
scope.modal.remove();
}
}
function _evalController(ctrlName) {
var result = {
isControllerAs: false,
controllerName: '',
propName: ''
};
var fragments = (ctrlName || '').trim().split(/\s+/);
result.isControllerAs = fragments.length === 3 && (fragments[1] || '').toLowerCase() === 'as';
if (result.isControllerAs) {
result.controllerName = fragments[0];
result.propName = fragments[2];
} else {
result.controllerName = ctrlName;
}
return result;
}
} // end
})();
Usage:
appModalService
.show('<templateUrl>', '<controllerName> or <controllerName as ..>', <parameters obj>)
.then(function(result) {
// result from modal controller: $scope.closeModal(result) or <as name here>.closeModal(result) [Only on template]
}, function(err) {
// error
});
You can use another service to centralize the configuration of all modals:
angular.module('app')
.factory('myModals', ['appModalService', function (appModalService){
var service = {
showLogin: showLogin,
showEditUser: showEditUser
};
function showLogin(userInfo){
// return promise resolved by '$scope.closeModal(data)'
// Use:
// myModals.showLogin(userParameters) // get this inject 'parameters' on 'loginModalCtrl'
// .then(function (result) {
// // result from closeModal parameter
// });
return appModalService.show('templates/modals/login.html', 'loginModalCtrl as vm', userInfo)
// or not 'as controller'
// return appModalService.show('templates/modals/login.html', 'loginModalCtrl', userInfo)
}
function showEditUser(address){
// return appModalService....
}
}]);
Create a directive to be used inside the modal and inside the directive you can assign the modal it's own controller and scope. If someone wants some example code I can put something up.
I was looking for a simple way to attach a controller to a modal instance and manage all modals with a single service. Also, I wanted the modal to have it's own isolated child scope. I wasn't satisfied with using ng-controller and I found other answers to be overly complicated to the point where you could easily loose track of scope and end up with circular or unidentifiable dependencies. I created the following service for my purposes.
You can pass an optional parentScope parameter to explicitly assign a parent scope to the created modal scope.
You could easily modify the instantiateModal method to accept $ionicModal options as an argument - I just didn't have the need for it.
BTW - I'm using the Webpack babel-loader for transpilation and the html-loader to load the templates. But, in it's simplest form, it's just a basic service.
/**
* nvModals
* #description A modal manager. Attaches a specified controller to an $ionicModal instance.
*/
import myModalTemplate from '../common/modals/my-modal.html';
import otherModalTemplate from '../common/modals/other-modal.html';
let nvModals = function (
$rootScope,
$controller,
$ionicModal
) {
var _self = this;
_self.methods = {
/**
* Instantiate and show a modal
*/
showMyModal: (parentScope) => {
var parentScope = parentScope || null;
_self.methods.instantiateModal('MyModalController', myModalTemplate, parentScope)
.show();
},
/**
* Instantiate and show another modal
*/
showOtherModal: (parentScope) => {
var parentScope = parentScope || null;
_self.methods.instantiateModal('OtherModalController', otherModalTemplate, parentScope)
.show();
},
/**
* Instantiate a new modal instance
*
* #param {object} controller Controller for your modal
* #param {string} template Template string
* #param {object} parentScope Optional parent scope for the modal scope
* #return {object} Modal instance
*/
instantiateModal: (controller, template, parentScope) => {
var modalScope;
if(parentScope) {
modalScope = $rootScope.$new(false, parentScope);
} else {
modalScope = $rootScope.$new(false);
}
$controller(controller, {
'$scope': modalScope
});
modalScope.modal = $ionicModal.fromTemplate(template, {
scope: modalScope,
animation: 'slide-in-up'
});
modalScope.$on('modal.hidden', (evt) => {
evt.targetScope.$destroy();
if (evt.targetScope.modal) {
evt.targetScope.modal.remove();
}
});
modalScope.hideModal = function () {
modalScope.modal.hide();
};
return modalScope.modal;
}
};
return _self.methods;
};
nvModals.$inject = [
'$rootScope',
'$controller',
'$ionicModal'
];
export default nvModals;
In your controller...
$scope.modals = nvModals;
In the associated template
ng-click="modals.showMyModal()"
In the modal template
ng-click="hideModal()"
Ok, I have seen a lot of different solutions to better handling Ionic modals because of the lack of a controller option or something similar.
After playing with React for a while I came up with another option, more declarative in my opinion. Is in ES6 and just a prototype but you can have an idea:
(function() {
'use strict';
#Inject('$scope', '$ionicModal', '$transclude', '$rootScope')
class Modal {
constructor() {
let { animation, focusFirstInput, backdropClickToClose, hardwareBackButtonClose } = this;
$transclude((clone, scope) => {
let modal = this.createModalAndAppendClone({
scope,
animation,
focusFirstInput,
backdropClickToClose,
hardwareBackButtonClose
}, clone);
this.setupScopeListeners(modal.scope);
this.createIsOpenWatcher();
this.addOnDestroyListener();
this.emitOnSetupEvent(modal.scope);
});
}
setupScopeListeners(scope) {
scope.$on('modal.shown', this.onShown);
scope.$on('modal.hidden', this.onHidden);
scope.$on('modal.removed', this.onRemoved);
}
addOnDestroyListener() {
this.$scope.$on('$destroy', () => {
this.removeModal();
});
}
createIsOpenWatcher() {
this.isOpenWatcher = this.$scope.$watch(() => this.isOpen, () => {
if (this.isOpen) {
this.modal.show();
} else {
this.modal.hide();
}
});
}
emitOnSetupEvent(scope) {
this.onSetup({
$scope: scope,
$removeModal: this.removeModal.bind(this)
});
}
createModalAndAppendClone({
scope = this.$rootScope.$new(true),
animation = 'slide-in-up',
focusFirstInput = false,
backdropClickToClose = true,
hardwareBackButtonClose = true
}, clone) {
let options = {
scope,
animation,
focusFirstInput,
backdropClickToClose,
hardwareBackButtonClose
}
this.modal = this.$ionicModal.fromTemplate('<ion-modal-view></ion-modal-view>', options);
let $modalEl = angular.element(this.modal.modalEl);
$modalEl.append(clone);
return this.modal;
}
removeModal() {
this.modal.remove();
this.isOpenWatcher();
}
}
function modal() {
return {
restrict: 'E',
transclude: true,
scope: {
'onShown': '&',
'onHidden': '&',
'onRemoved': '&',
'onSetup': '&',
'isOpen': '=',
'animation': '#',
'focusFirstInput': '=',
'backdropClickToClose': '=',
'hardwareBackButtonClose': '='
},
controller: Modal,
bindToController: true,
controllerAs: 'vm'
}
}
angular
.module('flight')
.directive('modal', modal);
})();
And then you can use it like this:
<modal is-open="vm.isOpen" on-shown="vm.onShown()" on-hidden="vm.onHidden()" on-removed="vm.onRemoved()" on-setup="vm.onSetup($scope, $removeModal)">
<div class="bar bar-header bar-clear">
<div class="button-header">
<button class="button button-positive button-clear button-icon ion-close-round button-header icon" ng-click="vm.closeModal()"></button>
</div>
</div>
<ion-content class="has-header">
<create-flight-form on-submit="vm.submit()"></create-flight-form>
</ion-content>
</modal>
You open and close the modal with a boolean value bind to is-open and then register callbacks for the different events.