Add complex markup with event handlers - slickgrid

How to put into a grid cell not just a string <span>text</span>, but a string with an event handler?
This option does not suit me:
<span onclick='function(){...}'>click me</span>
I need to add, for example, such elements in one grid cell:
var $el1 = $('<button>clck me 1</button>').click(function(){...});
var $el2 = $('<button>clck me 2</button>').click(function(){...});
...
I use slick.dataview.

Don't.
Either handle click events via SlickGrid by using the onClick event it exposes or use event delegation on a higher level (container or document) to catch it. Add an attribute to the buttons to distinguish them later and decide which handler to execute.

Related

Event Registration in UI5 - attaching multiple listeners to an event

How can I add multiple event listeners to an event in UI5?
We have a master list with a dropdown that is correctly firing a select event on its controller. Sub controllers also need to be informed that this dropdown has changed in order to reload model data.
onAllRolesChange: function(oEvent) {
var key = oEvent.getParameter("selectedItem").getProperty("text");
if (this.ScreenId != null) {
this.loadScreenByRole(key);
// I could invoke the controllers directly, but that seems wrong
// controller2.update();
// controller3.update();
}
},
I assume what I should be aiming for is to call some sort of registerForEvent() method in each of the controllers, but I don't see anything like that in the SDK. fireEvent() and attachEvent() exist, but the examples I've seen appear to be for creating custom controls, or responding to browser events that SAP hasn't implemented.
As of UI5 1.65, multiple event handlers can be assigned when creating ManagedObjects / Controls:
(...) ManagedObjects now accept an array of multiple event listeners for an event. To
distinguish this use case from the already supported array with [data, listener, this], an array with multiple listeners must use nested array notation for each listener as well [in JS]. In XMLViews, multiple listeners have to be separated by a semicolon. (source)
Syntax
In XMLView
<Button press=".myControllerMethod; .mySubController.thatMethod" />
In JS
new Button({
press: [
[ listener1 ], // 1st listener
[ data, listener2, thisArg2 ] // 2nd listener
]
});
Demo
sap.ui.getCore().attachInit(() => sap.ui.require([
"sap/ui/core/mvc/XMLView"
], XMLView => XMLView.create({
definition: `<mvc:View xmlns:mvc="sap.ui.core.mvc"
xmlns="sap.m"
height="100%"
displayBlock="true"
>
<Button text="Press" class="sapUiTinyMargin"
press="alert('1st event handler'); alert('2nd event handler')"
/>
</mvc:View>`,
}).then(view => view.placeAt("content"))));
<script id="sap-ui-bootstrap"
src="https://openui5nightly.hana.ondemand.com/resources/sap-ui-core.js"
data-sap-ui-libs="sap.ui.core, sap.m"
data-sap-ui-async="true"
data-sap-ui-compatVersion="edge"
data-sap-ui-theme="sap_fiori_3"
></script>
<body id="content" class="sapUiBody"></body>
You could use the EventBus to inform about the change, and who ever wants could listen for the change. However, if the other controllers are not yet loaded they won't get the events of course... Maybe you can combine this with promises...
You could also use a global model with 2 way binding and use it for your dropdown. When ever the dropdown changes the change is reflected in the corresponding model. At the same time, in your sub controllers you could create a sap.ui.model.Binding(...) for the same global model + path etc used for your dropdown. Additionally, you would attach a handler for the change event of the Binding... That should work as well. However, this has the same disadvantage like using the EventBus, but maybe thatÄs not an issue for you...

Hide child grid when adding new main item

I have a grid that has child grid for each item, when i add a new item to the main grid, there is a stub for the child (with the toolbar etc and an empty grid for the child), I would like to hide the child grid when adding new one, i know i need the edit event, i just dont know how to get reference to the detailgrid for the item that the row was just created for input.
edit event has e.sender, e.container, e.model, first 2 reference the main grid of course as the event is raised by the main grid
The required behavior is not supported out of the box, however you can for example attach click event handler to the expanding arrows in the Grid. In the event handler you can prevent the expanding if current model is new. Please check the example below:
//Change Employees with your grid name
//the grid should have model ID defined
$("#Employees table").on("click", ".k-hierarchy-cell a", function (e) {
dataItem = $("#Employees").data("kendoGrid").dataItem($(e.srcElement).closest("tr"));
//check if is new record
if (dataItem.isNew()) {
e.preventDefault();
e.stopImmediatePropagation();
}
})
UPDATE (as requested): The above code should be executed in script tag (wrapped in document "ready" event handler) which is placed just after the Grid initialization code.

Need help understanding jquery delegate() function

I'm having a hard time understanding the syntax of the .delegate function of jquery. Let's say I have the following:
$(".some_element_class").delegate("a", "click", function(){
alert($(this).html());
});
I know that the a element is the element to which the click event is applied. I know that once we click on that a element, the event click will be triggered and the callback function will be called. But what is the purpose of what comes before the .delegate? In this case, what is the purpose of .some_element_class? How do I read the above including the .some_element_class? Also, in the example above, what does $(this) represent? Does it represent the a element or does it represent .some_element_class?
Please somebody, shed some light on this.
Thank you
This reduces event binding.
This basically sets an event on a tags ONLY within the elements with class .some_element_class without actually binding an event to a tags directly.
http://api.jquery.com/delegate/
http://api.jquery.com/on/
As of jQuery 1.7, .delegate() has been superseded by the .on() method.
For earlier versions, however, it remains the most effective means to
use event delegation. More information on event binding and delegation
is in the .on() method. In general, these are the equivalent templates
for the two methods:
$(elements).delegate(selector, events, data, handler); // jQuery 1.4.3+
$(elements).on(events, selector, data, handler); // jQuery 1.7+
$(".some_element_class").on("a", "click", function(){
alert($(this).html());
});
"...what is the purpose of what comes before the .delegate?"
A delegate is bound to .some_element_class element.
That delegate is triggered for every click that takes place inside .some_element_class
That delegate tests what was clicked, so your handler function will only run if...
the actual element clicked matches the "a" selector, or
any ancestor of the actual element clicked that is a descendant of .some_element_class matches the "a" selector.
<div class="some_element_class"> <!-- delegate handler is here -->
<div>won't trigger your handler</div>
<a>will trigger your handler</a>
<a><span>will trigger</span> your handler</a>
</div>
So you can see that only one handler is bound to the container. It analyzes all clicks inside the container, and if the element clicked (or one of its nested ancestors) matches the selector argument, your function will run.
Because there's just one enclosing handler, it will work for future elements added to the container...
<div class="some_element_class"> <!-- delegate handler is here -->
<div>won't trigger your handler</div>
<a>will trigger your handler</a>
<a><span>will trigger</span> your handler</a>
<!-- this element newly added... -->
<a><span>...will also trigger</span> your handler</a>
</div>
"Also, in the example above, what does $(this) represent?"
this will represent the element that matched the "a" selector.
it means delegate() is invoked on the .some_event_class. and the a is selector string, click is event type string & function() is eventhandler function. delegate() method is used to handle the "live event" and for static events bind() is used. I hope this helps. feel free to ask if you have any doubts
Differences between bind() & delegate()
//Static event handlers for static links
$("a").bind("",linkHandler);
//Live event handlers for dynamic parts of the document
$(".dynamic").delegate("a", "mouseover", linkHandler);
Summary: they are just methods that bind event handlers to specific document elements.
The a is actually just a filtering selector, what will happen is that a normal click event is bound to .some_element_class, and anytime the event fires, the event target is traversed up to .some_element_class to see if there is an element that matches the filtering selector (tagname a). If it does, your callback is fired with this set to the first element that matched a selector in the bubbling path.
You can do something similar with bind:
$(".some_element_class").bind("click", function (e) {
var matches = $(e.target).closest("a", this);
if (matches.length) {
yourcallback.call(matches[0], e);
}
});

Browser Memory Usage Comparison: inline onClick vs. using JQuery .bind()

I have ~400 elements on a page that have click events tied to them (4 different types of buttons with 100 instances of each, each type's click events performing the same function but with different parameters).
I need to minimize any impacts on performance that this may have. What kind of performance hit am I taking (memory etc) by binding click events to each of these individually (using JQuery's bind())? Would it be more efficient to have an inline onclick calling the function on each button instead?
Edit for clarification :):
I actually have a table (generated using JQGrid) and each row has data columns followed by 4 icon 'button' columns- delete & three other business functions that make AJAX calls back to the server:
|id|description|__more data_|_X__|_+__|____|____|
-------------------------------------------------
| 1|___data____|____data____|icon|icon|icon|icon|
| 2|___data____|____data____|icon|icon|icon|icon|
| 3|___data____|____data____|icon|icon|icon|icon|
| 4|___data____|____data____|icon|icon|icon|icon|
I am using JQGrid's custom formatter (http://www.trirand.com/jqgridwsiki/doku.php?id=wiki:custom_formatter) to build the icon 'buttons' in each row (I cannot retrieve button HTML from server).
It is here in my custom formatter function that I can easily just build the icon HTML and code in an inline onclick calling the appropriate functions with the appropriate parameters (data from other columns in that row). I use the data in the row columns as parameters for my functions.
function removeFormatter(cellvalue, options, rowObject) {
return "<img src='img/favoritesAdd.gif' onclick='remove(\"" + options.rowId + "\")' title='Remove' style='cursor:pointer' />";
}
So, I can think of two options:
1) inline onclick as I explained above
--or--
2) delegate() (as mentioned in below answers (thank you so much!))
Build the icon image (each icon type has its own class name) using the custom formatter.Set the icon's data() to its parameters in the afterInsertRow JQGrid event. Apply the delegate() handler to buttons of specific classes (as #KenRedler said below)
> $('#container').delegate('.your_buttons','click',function(e){
> e.preventDefault();
> var your_param = $(this).data('something'); // store your params in data, perhaps
> do_something_with( your_param );
> }); //(code snippet via #KenRedler)
I'm not sure how browser-intensive option #2 is I guess...but I do like keeping the Javascript away from my DOM elements :)
Because you need not only a general solution with some container objects, but the solution for jqGrid I can suggest you one more way.
The problem is that jqGrid make already some onClick bindings. So you will not spend more resources if you just use existing in jqGrid event handler. Two event handler can be useful for you: onCellSelect and beforeSelectRow. To have mostly close behavior to what you currently have I suggest you to use beforeSelectRow event. It's advantage is that if the user will click on one from your custom buttons the row selection can stay unchanged. With the onCellSelect the row will be first selected and then the onCellSelect event handler called.
You can define the columns with buttons like following
{ name: 'add', width: 18, sortable: false, search: false,
formatter:function(){
return "<span class='ui-icon ui-icon-plus'></span>"
}}
In the code above I do use custom formatter of jqGrid, but without any event binding. The code of
beforeSelectRow: function (rowid, e) {
var iCol = $.jgrid.getCellIndex(e.target);
if (iCol >= firstButtonColumnIndex) {
alert("rowid="+rowid+"\nButton name: "+buttonNames[iCol]);
}
// prevent row selection if one click on the button
return (iCol >= firstButtonColumnIndex)? false: true;
}
where firstButtonColumnIndex = 8 and buttonNames = {8:'Add',9:'Edit',10:'Remove',11:'Details'}. In your code you can replace the alert to the corresponding function call.
If you want select the row always on the button click you can simplify the code till the following
onCellSelect: function (rowid,iCol/*,cellcontent,e*/) {
if (iCol >= firstButtonColumnIndex) {
alert("rowid="+rowid+"\nButton name: "+buttonNames[iCol]);
}
}
In the way you use one existing click event handler bound to the whole table (see the source code) and just say jqGrid which handle you want to use.
I recommend you additionally always use gridview:true which speed up the building of jqGrid, but which can not be used if you use afterInsertRow function which you considered to use as an option.
You can see the demo here.
UPDATED: One more option which you have is to use formatter:'actions' see the demo prepared for the answer. If you look at the code of the 'actions' formatter is work mostly like your current code if you look at it from the event binding side.
UPDATED 2: The updated version of the code you can see here.
You should use the .delegate() method to bind a single click handler for all elements ,through jQuery, to a parent element of all buttons.
For the different parameters you could use data- attributes to each element, and retrieve them with the .data() method.
Have you considered using delegate()? You'd have one handler on a container element rather than hundreds. Something like this:
$('#container').delegate('.your_buttons','click',function(e){
e.preventDefault();
var your_param = $(this).data('something'); // store your params in data, perhaps
do_something_with( your_param );
});
Assuming a general layout like this:
<div id="container">
<!--- stuff here --->
<a class="your_buttons" href="#" data-something="foo">Alpha</a>
<a class="your_buttons" href="#" data-something="bar">Beta</a>
<a class="your_buttons" href="#" data-something="baz">Gamma</a>
<a class="something-else" href="#" data-something="baz">Omega</a>
<!--- hundreds more --->
</div>

How to mimic stopPropagation using jQuery.live

So I know that one of the downsides of using jQuery.live is the unavailability of .stopPropagation(). But I need it badly.
Here's my use case. I have a checkbox is that is currently bound to a click. However, other checkboxes appear on-screen via an AJAX call, meaning I really need .live('click', fn). Unfortunately, the checkbox is situated atop another clickable element, requiring .stopPropagation(). This works fine with .bind('click', fn), but the inability to use it with .live() is hampering me. Using return false doesn't work as the checkbox will not be checked.
Any ideas on how to mimic .stopPropagation() when using .live() without returning false?
Instead of binding a .live handler to the checkboxes, bind a smarter event handler to the container, with behaviour dependent on which element is the target of the event.
$("#container").click(function(e) {
var ele = e.target;
if(ele.tagName.toLowerCase() == 'input'
&& ele.type.toLowerCase() == 'checkbox') {
e.stopPropagation();
// do something special for contained checkboxes
// e.g.:
var val = $(ele).val();
}
});
Here is something of an example to show how this can be used.

Resources