I am trying to bind a MetroUI modal dialog to an angular controller property. This way I can show and hide the dialog using binding.
DIRECTIVE
appMod.directive('showDialog', ['$timeout', function ($timeout): ng.IDirective {
return {
restrict: 'A',
link: function (scope, element, attrs, ngModel) {
scope.$watch(attrs.showDialog, function (value) {
if (value) {
element.show();
}
else {
element.hide();
}
});
}
}
}]);
HTML:
<div class="padding20 dialog" id="dialog9"
data-role="dialog" data-close-button="true"
data-overlay="true" data-overlay-color="op-dark"
show-dialog="vm.isDialogVisible">
This way I can control opening the dialog by setting the vm.isDialogVisible Boolean on my controller.
Problem is that I am trying to update the vm.isDialogVisible attribute when the user closes the dialog (via the close button). Anyone has some ideas how to fix that?
It is always cool to find your own solution (took me a day :-)). I made a mistake to use the show / hide features of the element. I should have used the data attribute of the element. That way I am able to access the
onDialogClose
function, which enable me to update the scope. Below my solution
appMod.directive(showDialog, ['$timeout','$parse',function ($timeout, $parse){
return {
restrict: 'A',
scope:false,
link: function (scope, element, attrs) {
var e1 = theDialog.data('dialog');
$timeout(() => {
e1.options.onDialogClose = (dialog) => {
var model = $parse(attrs.showDialog);
model.assign(scope, false);
scope.$digest();
};
}, 0);
scope.$watch(attrs.showDialog, function (value) {
if (value) {
e1.open();
}
else {
e1.close();
}
});
}
}
}]);
Related
Built in browser elements have event attributes which execute arbitrary javascript as described below
Is there any way to create a custom element with a similar behaving custom event handler attribute, and is there a standard pattern for doing so? Where the {some custom eventType}="{some code}" executes with the correct values in scope and the this binding set correctly.
<some-custom-element oncustomevent="alert('worked')" />
First question is: Do you really want to allow executing code from a string? Because it requires eval()
There is nothing wrong with using eval() when you understand the implications:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode
Trigger dynamic (string) code:
from a Custom Element attribute onevent
from a Custom Element setter onevent
from a Custom Event onevent detail (see connectedCallback)
function triggerEvent(name, code = "console.warn('No code parameter')") {
console.log(name, this);
if (this === window) console.warn('no element scope');
try {
if (typeof code === "string") eval(code);
else console.warn("code is not a string")
} catch (e) { console.error(e) }
}
customElements.define("my-element", class extends HTMLElement {
static get observedAttributes() {
return ['onevent'];
}
constructor() {
super();
this.onclick = () => triggerEvent.call(this, "element Click", "console.log('from element click')");
}
connectedCallback() {
document.addEventListener("onevent", evt => {
triggerEvent.call(this, "EventListener", evt.detail);
})
}
attributeChangedCallback(name, oldValue, newValue) {
if (name === "onevent") {
triggerEvent.call(this, "attributeChangedCallback", newValue);
}
}
set onevent(newValue) {
triggerEvent.call(this, "setter", newValue);
}
});
setTimeout(() => document.dispatchEvent(new CustomEvent("onevent", {
detail: "console.log('from dispatchEvent detail')"
})), 2000); //execute after elements listen
<my-element onevent="console.log('from element attribute')">click my-element</my-element>
<button onclick="document.querySelector('my-element').onevent='console.log("from button")'"
>Call setter</button>
JSFiddle playground at: https://jsfiddle.net/WebComponents/ezacw5xL/
I have a form with dynamic fields. In the afterrender event of the form I want to set the afterLabelTextTpl property. I can set this property but I can't see it change in my form. How can I achieve this?
Snippet:
listeners: {
beforerender: function () {
var fields = me.getForm().getFields();
Ext.each(fields.items, function (f, idx) {
f.afterLabelTextTpl = requiredTpl;
console.log(f.afterLabelTextTpl);
}); //eo Ext.each
}
}
Edit:
I was looking for the beforerender method
Try
f.labelEl.dom.innerHTML = "LABEL:<span style='color:red;font-weight:bold' data-qtip='Required'>*</span>";
You can not use this property after the component is already rendered.
The initRenderTpl (which makes use of the label templates) method is run only if the component is not yet rendered. Once its rendered it will not run again.
You will need to update the DOM directly.
I would recomend something like this in your form:
setRequired: function(field, index) {
field.afterLabelTextTpl = requiredTpl;
},
initComponent: function(arguments) {
var me = this;
this.on('beforeadd', function(me, field){
var fields;
if (field.isXType('fieldset')) {
fields = field.query('field');
Ext.each(fields, me.setRequired);
} else {
me.setRequired(field);
}
});
// rest of logic
me.callParent(arguments);
},
I want to create a mouseenter / mouseleave state for hovering over group headers. There doesn't seem to be any hover events being thrown within the Ext.grid.feature.Grouping class.
Mouse enter, of the grouping header, would change the ^ to white
Mouse leave, of the grouping header, would change the ^ back to #999
Suggestions?
I couldn't figure out a solution to delegate to the mouseenter / mouseleave events properly using ExtJS terrible event binding.
Additionally, I couldn't figure out how to component query / query the grid.feature.Grouping feature itself.
However, I did figure out how to add delegation listeners to the mouseover / mouseout events. Kind of ugly that you have to wait to the render event to get the components element first. Then you have to bind using this.mon (terribly named) to addManagedListener on a delegated element.
Ext.create('Ext.grid.Panel', {
// ...
listeners: {
render: function (cmp, eOpts) {
this.mon(cmp.el, 'mouseover', function (event, html, eOpts) {
var class_names = this.getGroupClassNamesWithoutOver(html);
class_names.push('x-over');
html.className = class_names.join(' ');
}, cmp, {
delegate: '.x-grid-group-hd'
});
this.mon(cmp.el, 'mouseout', function (event, html, eOpts) {
var class_names = this.getGroupClassNamesWithoutOver(html);
html.className = class_names.join(' ');
}, cmp, {
delegate: '.x-grid-group-hd'
});
},
getGroupClassNamesWithoutOver: function (html) {
var class_names = html.className.split(' '),
class_names_length = class_names.length,
new_class_names = [];
while (class_names_length--) {
var class_name = class_names[class_names_length];
if (class_name != 'x-over') {
new_class_names.push(class_name);
}
}
return new_class_names;
}
});
This solution is much cleaner that using the super nested, non scoped version:
listeners: {
el: {
mouseover: {
delegate: '.x-grid-group-hd',
fn: function (event, html, eOpts) {
// ...
}
}
}
}
I want a button in column header dropdown menu of grid in extjs4.
so that i can add or delete columns which are linked in database.
Any help will be appreciated...
Thankyou..:)
Couple of months ago I had the same problem. I've managed to solve it by extending Ext.grid.header.Container (I've overrided getMenuItems method). However, recently, I've found another solution which requires less coding: just add menu item manualy after grid widget is created.
I'll post the second solution here:
Ext.create('Ext.grid.Panel', {
// ...
listeners: {
afterrender: function() {
var menu = this.headerCt.getMenu();
menu.add([{
text: 'Custom Item',
handler: function() {
var columnDataIndex = menu.activeHeader.dataIndex;
alert('custom item for column "'+columnDataIndex+'" was pressed');
}
}]);
}
}
});
Here is demo.
UPDATE
Here is demo for ExtJs4.1.
From what I have been seeing, you should avoid the afterrender event.
Context:
The application I am building uses a store with a dynamic model. I want my grid to have a customizable model that is fetched from the server (So I can have customizable columns for my customizable grid).
Since the header wasn't available to be modified (since the store gets reloaded and destroys the existing menu that I modified - using the example above). An alternate solution that has the same effect can be executed as such:
Ext.create('Ext.grid.Panel', {
// ...
initComponent: function () {
// renders the header and header menu
this.callParent(arguments);
// now you have access to the header - set an event on the header itself
this.headerCt.on('menucreate', function (cmp, menu, eOpts) {
this.createHeaderMenu(menu);
}, this);
},
createHeaderMenu: function (menu) {
menu.removeAll();
menu.add([
// { custom item here }
// { custom item here }
// { custom item here }
// { custom item here }
]);
}
});
For people who would like to have not just one "standard" column menu but have an individual columnwise like me, may use the following
initComponent: function ()
{
// renders the header and header menu
this.callParent(arguments);
// now you have access to the header - set an event on the header itself
this.headerCt.on('menucreate', function (cmp, menu, eOpts) {
menu.on('beforeshow', this.showHeaderMenu);
}, this);
},
showHeaderMenu: function (menu, eOpts)
{
//define array to store added compoents in
if(this.myAddedComponents === undefined)
{
this.myAddedComponents = new Array();
}
var columnDataIndex = menu.activeHeader.dataIndex,
customMenuComponents = this.myAddedComponents.length;
//remove components if any added
if(customMenuComponents > 0)
{
for(var i = 0; i < customMenuComponents; i++)
{
menu.remove(this.myAddedComponents[i][0].getItemId());
}
this.myAddedComponents.splice(0, customMenuComponents);
}
//add components by column index
switch(columnDataIndex)
{
case 'xyz': this.myAddedComponents.push(menu.add([{
text: 'Custom Item'}]));
break;
}
}
I took #nobbler's answer an created a plugin for this:
Ext.define('Ext.grid.CustomGridColumnMenu', {
extend: 'Ext.AbstractPlugin',
init: function (component) {
var me = this;
me.customMenuItemsCache = [];
component.headerCt.on('menucreate', function (cmp, menu) {
menu.on('beforeshow', me.showHeaderMenu, me);
}, me);
},
showHeaderMenu: function (menu) {
var me = this;
me.removeCustomMenuItems(menu);
me.addCustomMenuitems(menu);
},
removeCustomMenuItems: function (menu) {
var me = this,
menuItem;
while (menuItem = me.customMenuItemsCache.pop()) {
menu.remove(menuItem.getItemId(), false);
}
},
addCustomMenuitems: function (menu) {
var me = this,
renderedItems;
var menuItems = menu.activeHeader.customMenu || [];
if (menuItems.length > 0) {
if (menu.activeHeader.renderedCustomMenuItems === undefined) {
renderedItems = menu.add(menuItems);
menu.activeHeader.renderedCustomMenuItems = renderedItems;
} else {
renderedItems = menu.activeHeader.renderedCustomMenuItems;
menu.add(renderedItems);
}
Ext.each(renderedItems, function (renderedMenuItem) {
me.customMenuItemsCache.push(renderedMenuItem);
});
}
}
});
This is the way you use it (customMenu in the column config let you define your menu):
Ext.define('MyGrid', {
extend: 'Ext.grid.Panel',
plugins: ['Ext.grid.CustomGridColumnMenu'],
columns: [
{
dataIndex: 'name',
customMenu: [
{
text: 'My menu item',
menu: [
{
text: 'My submenu item'
}
]
}
]
}
]
});
The way this plugin works also solves an issue, that the other implementations ran into. Since the custom menu items are created only once for each column (caching of the already rendered version) it will not forget if it was checked before or not.
i need to create a Dojo widget that lets users specify date & time. i found a sample implementation attached to an entry in the Dojo bug tracker. It looks nice and mostly works, but when i submit the form, the value sent by the client is not the user-selected value but the value sent from the server.
What changes do i need to make to get the widget to submit the date & time value?
Sample usage is to render a JSP with basic HTML tags (form & input), then
dojo.addOnLoad a function which selects the basic elements by ID, adds dojoType
attribute, and dojo.parser.parse()-es the page.
Thanks in advance.
The widget is implemented in two files. The application uses Dojo 1.3.
File 1: DateTimeCombo.js
dojo.provide("dojox.form.DateTimeCombo");
dojo.require("dojox.form._DateTimeCombo");
dojo.require("dijit.form._DateTimeTextBox");
dojo.declare(
"dojox.form.DateTimeCombo",
dijit.form._DateTimeTextBox,
{
baseClass: "dojoxformDateTimeCombo dijitTextBox",
popupClass: "dojox.form._DateTimeCombo",
pickerPostOpen: "pickerPostOpen_fn",
_selector: 'date',
constructor: function (argv) {},
postMixInProperties: function()
{
dojo.mixin(this.constraints, {
/*
datePattern: 'MM/dd/yyyy HH:mm:ss',
timePattern: 'HH:mm:ss',
*/
datePattern: 'MM/dd/yyyy HH:mm',
timePattern: 'HH:mm',
clickableIncrement:'T00:15:00',
visibleIncrement:'T00:15:00',
visibleRange:'T01:00:00'
});
this.inherited(arguments);
},
_open: function ()
{
this.inherited(arguments);
if (this._picker!==null && (this.pickerPostOpen!==null && this.pickerPostOpen!==""))
{
if (this._picker.pickerPostOpen_fn!==null)
{
this._picker.pickerPostOpen_fn(this);
}
}
}
}
);
File 2: _DateTimeCombo.js
dojo.provide("dojox.form._DateTimeCombo");
dojo.require("dojo.date.stamp");
dojo.require("dijit._Widget");
dojo.require("dijit._Templated");
dojo.require("dijit._Calendar");
dojo.require("dijit.form.TimeTextBox");
dojo.require("dijit.form.Button");
dojo.declare("dojox.form._DateTimeCombo",
[dijit._Widget, dijit._Templated],
{
// invoked only if time picker is empty
defaultTime: function () {
var res= new Date();
res.setHours(0,0,0);
return res;
},
// id of this table below is the same as this.id
templateString:
" <table class=\"dojoxDateTimeCombo\" waiRole=\"presentation\">\
<tr class=\"dojoxTDComboCalendarContainer\">\
<td>\
<center><input dojoAttachPoint=\"calendar\" dojoType=\"dijit._Calendar\"></input></center>\
</td>\
</tr>\
<tr class=\"dojoxTDComboTimeTextBoxContainer\">\
<td>\
<center><input dojoAttachPoint=\"timePicker\" dojoType=\"dijit.form.TimeTextBox\"></input></center>\
</td>\
</tr>\
<tr><td><center><button dojoAttachPoint=\"ctButton\" dojoType=\"dijit.form.Button\">Ok</button></center></td></tr>\
</table>\
",
widgetsInTemplate: true,
constructor: function(arg) {},
postMixInProperties: function() {
this.inherited(arguments);
},
postCreate: function() {
this.inherited(arguments);
this.connect(this.ctButton, "onClick", "_onValueSelected");
},
// initialize pickers to calendar value
pickerPostOpen_fn: function (parent_inst) {
var parent_value = parent_inst.attr('value');
if (parent_value !== null) {
this.setValue(parent_value);
}
},
// expects a valid date object
setValue: function(value) {
if (value!==null) {
this.calendar.attr('value', value);
this.timePicker.attr('value', value);
}
},
// return a Date constructed date in calendar & time in time picker.
getValue: function() {
var value = this.calendar.attr('value');
var result=value;
if (this.timePicker.value !== null) {
if ((this.timePicker.value instanceof Date) === true) {
result.setHours(this.timePicker.value.getHours(),
this.timePicker.value.getMinutes(),
this.timePicker.value.getSeconds());
return result;
}
} else {
var defTime=this.defaultTime();
result.setHours(defTime.getHours(),
defTime.getMinutes(),
defTime.getSeconds());
return result;
}
},
_onValueSelected: function() {
var value = this.getValue();
this.onValueSelected(value);
},
onValueSelected: function(value) {}
});
It sounds like you want to use getValue. The convention now is to use _getValueAttr and then call attr("value") but I think that started in Dojo 1.4 and this code would need to be ported to use those new patterns.
Noe that value should be a Javascript Date object which would best be sent to the server using dojo.date.stamp.toISOString()
This began to work fine after i added a "serialize" method to DateTimeCombo.js which builds exactly the output format i want.
This seems odd to me, since there is already a serialize implementation in _DateTimeTextBox.js that should output the value in the required ISO format.