I'm creating a dialogbox from ExtLib and I want to prevent users to press Escape or click on X icon.
I've checked several posts about same implementation but none of them using a Dialogbox from ExtLib.
I was able to hide icon with CSS and I'm trying with dojo.connect to prevent the use of Escape key:
XSP.addOnLoad(function(){
dojo.connect(dojo.byId("#{id:dlgMsg}"), "onkeypress", function (evt) {
if(evt.keyCode == dojo.keys.ESCAPE) {
dojo.stopEvent(evt);
}
});
});
Note I'm able to get it working only if I create my dialogbox manually and not from ExtLib; then I can use for example:
dojo.connect(dojo.byId("divDlgLock"), "onkeypress", function (evt) {
if(evt.keyCode == dojo.keys.ESCAPE) {
dojo.stopEvent(evt);
}
});
Any ideas?
By adding an output script block you can extend the existing declaration:
<xp:scriptBlock id="scriptBlockNonCloseableDialog">
<xp:this.value>
<![CDATA[
dojo.provide("extlib.dijit.OneUIDialogNonCloseableDialog");
dojo.require("extlib.dijit.Dialog");
dojo.declare(
"extlib.dijit.OneUIDialogNonCloseableDialog",
extlib.dijit.Dialog,
{
baseClass: "",
templateString: dojo.cache("extlib.dijit", "templates/OneUIDialog.html"),
disableCloseButton: true,
_onKey: function(evt){
if(this.disableCloseButton &&
evt.charOrCode == dojo.keys.ESCAPE) return;
this.inherited(arguments);
},
_updateCloseButtonState: function(){
dojo.style(this.closeButtonNode,
"display",this.disableCloseButton ? "none" : "block");
},
postCreate: function(){
this.inherited(arguments);
this._updateCloseButtonState();
dojo.query('form', dojo.body())[0].appendChild(this.domNode);
},
_setup: function() {
this.inherited(arguments);
if (this.domNode.parentNode.nodeName.toLowerCase() == 'body')
dojo.query('form', dojo.body())[0].appendChild(this.domNode);
}
}
);
// This is used by the picker dialog to grab the correct UI
XSP._dialog_type="extlib.dijit.OneUIDialogNonCloseableDialog";
]]>
</xp:this.value>
</xp:scriptBlock>
Related
After reading an article https://css-tricks.com/how-do-you-structure-javascript-the-module-pattern-edition/ I did something similar in my project - created the structure:
var SomeStructure = {
var1: $('#tag1'),
init: function() {
this.var1.on('click', function (e) {
SomeStructure.mouseClick(e);
});
},
mouseClick: function(e) {
e.preventDefault();
alert("tag clicked");
}
}
SomeStructure.init();
div {
cursor: pointer;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="tag1">Click me!</div>
My code is working, but I wondering if it's possible rewrite the code:
this.var1.on('click', function (e) {
SomeStructure.mouseClick(e);
});
so that the call of function mouseClick after clicking the $('#tag1') in some more neat way without anonymous function in one row, something like that:
this.var1.on('click', this.mouseClick);
But this way isn't right without e..
Thank you in advance.
I have to add a new function ListenEvents for events listening, so that a lot of new listeners could be added inside to make clear workflow object (as described here http://derickbailey.com/2015/08/07/making-workflow-explicit-in-javascript/) :
`var SomeStructure = {
var1: $('#tag1'),
init: function() {
this.ListenEvents();
},
ListenEvents: function() {
self = SomeStructure;
self.var1.on('click', self.mouseClick);
},
mouseClick: function() {
alert("tag clicked");
}
}
SomeStructure.init();`
If I have made to the tab through the dojo,
mycode
var tab=new dijit.layout.ContentPane({
title:formCount,
id:''+formCount,
content:cont,
class:'tab',
closable: true,
onClose:function(){
return confirm('Relly want to remove?');
}
});
dijit.byId('tabContainer').addChild(tab);
After the tab is created, i want to change the tab title dynamically through dijit/Dialog.
but I don't know how it should be implemented,Please advise me
The best way to achieve this is to create your own widget and extend from dijit/layout/ContentPane, for example:
declare([ ContentPane ], {
});
Then you can add stuff to show a dialog, for example:
declare([ ContentPane ], {
btn: domConstruct.create("button", {
innerHTML: "Change title",
}),
_createButton: function() {
domConstruct.place(this.btn, this.domNode);
on(this.btn, "click", lang.hitch(this, this._showDialog));
},
postCreate: function() {
this.inherited(arguments);
this._createButton();
this._createDialog();
}
});
The postCreate function is a part of the widget lifecycle in Dojo and is automatically executed when the widget is loaded. So, we use that to add a "Change title" button to the contentpane that, when being clicked calls a function this._showDialog() (that's what you can see in this._createButton()).
Of course, you also need to create a dijit/Dialog before you can actuall show one, so you could do something like:
declare([ ContentPane ], {
/** ... */
dialog: null,
editField: null,
okBtn: null,
_showDialog: function() {
this.editField.value = this.title;
this.dialog.show();
},
_createDialog: function() {
var div = domConstruct.create("div");
domConstruct.place(div, this.domNode);
this.dialog = new Dialog({
title: "Change title",
content: ""
}, div);
this.editField = domConstruct.create("input", {
type: "text"
});
this.okBtn = domConstruct.create("button", {
innerHTML: "OK"
});
domConstruct.place(this.editField, this.dialog.containerNode);
domConstruct.place(this.okBtn, this.dialog.containerNode);
on(this.okBtn, "click", lang.hitch(this, this._saveTitle));
},
/** .. */
});
What happens here is that we create a dialog with a simple textfield and a button (the OK button), all of that can be found in this._createDialog().
In this._showDialog() you can see that I'm first changing the value of the textfield into the title of the contentpane. Then I show the dialog we made earlier.
Now all you have to do is read that value when the OK button is pressed:
declare([ ContentPane ], {
/** ... */
_saveTitle: function() {
this.set("title", this.editField.value);
this.dialog.hide();
},
/** ... */
});
That's all you really need. You can find a working example on JSFiddle: http://jsfiddle.net/LE49K/
I have the popup working but sometimes a user clicks the back button on their browser to close the popup.
How can I make the browser back button close a 'magnific-popup' that is already open?
Thanks
After some digging found history.js and then the following
var magnificPopup = null;
jQuery(document).ready(function ($) {
var $img = $(".img-link");
if ($img.length) {
$img.magnificPopup({
type: 'image',
preloader: true,
closeOnContentClick: true,
enableEscapeKey: false,
showCloseBtn: true,
removalDelay: 100,
mainClass: 'mfp-fade',
tClose: '',
callbacks: {
open: function () {
History.Adapter.bind(window, 'statechange', closePopup);
History.pushState({ url: document.location.href }, document.title, "?large");
$(window).on('resize', closePopup);
magnificPopup = this;
},
close: function () {
$(window).unbind('statechange', closePopup)
.off('resize', closePopup);
var State = History.getState();
History.replaceState(null, document.title, State.data["url"]);
magnificPopup = null;
}
}
});
}
});
function closePopup () {
if (magnificPopup != null)
magnificPopup.close();
}
I'm using this solution:
callbacks: {
open: function() {
location.href = location.href.split('#')[0] + "#gal";
}
,close: function() {
if (location.hash) history.go(-1);
}
}
And this code:
$(window).on('hashchange',function() {
if(location.href.indexOf("#gal")<0) {
$.magnificPopup.close();
}
});
So, on gallery open I add #gal hash. When user closes I virtually click back button to remove it. If user clicks back button - everything works fine olso.
This solution does not break back button behavior and does no require any additional plugins.
Comments are welcome.
Just to add to your answer, these are the meaningful lines that I got to work for me.
callbacks:
open: ->
History.pushState({ url: document.location.href }, null, "?dialogOpen")
History.Adapter.bind(window, 'statechange', attemptToCloseDialog)
close: ->
$(window).unbind('statechange', attemptToCloseDialog)
History.replaceState(null, null, History.getState().data['url'])
With attempt being:
attemptToCloseDialog = ->
$.magnificPopup.close() if $.magnificPopup.instance
I worked with backbone before and was wondering if there's a similar way to achieve this kind of pattern in dojo. Where you have a router and pass one by one your view separately (like layers) and then you can add their intern functionality somewhere else (e.g inside the view) so the code is very modular and can be change/add new stuff very easily. This code is actually in jquery (and come from a previous project) and it's a "common" base pattern to develop single application page under jquery/backbone.js .
main.js
var AppRouter = Backbone.Router.extend({
routes: {
"home" : "home"},
home: function(){
if (!this.homeView) {
this.homeView= new HomeView();
}
$('#content').html(this.homeView.el);
this.homeView.selectMenuItem('home-link');
}};
utils.loadTemplate(['HomeView'], function() {
app = new AppRouter();
Backbone.history.start();
});
utils.js
loadTemplate: function(views, callback) {
var deferreds = [];
$.each(views, function(index, view) {
if (window[view]) {
deferreds.push($.get('tpl/' + view + '.html', function(data) {
window[view].prototype.template = _.template(data);
}));
} else {
alert(view + " not found");
}
});
$.when.apply(null, deferreds).done(callback);
}};
HomeView.js
window.HomeView = Backbone.View.extend({
initialize:function () {
this.render();
},
render:function () {
$(this.el).html(this.template());
return this;
}
});
And basically, you just pass the html template. This pattern can be called anywhere with this link:
<li class="active"><i class="icon-home"></i> Dashboard</li>
Or, what is the best way to implement this using dojo boilerplate.
The 'boilerplate' on this subject is a dojox.mvc app. Reference is here.
From another aspect, see my go at it a while back, ive setup an abstract for 'controller' which then builds a view in its implementation.
Abstract
Then i have an application controller, which does following on its menu.onClick
which fires loading icon,
unloads current pane (if forms are not dirty)
loads modules it needs (defined 'routes' in a main-menu-store)
setup view pane with a new, requested one
Each view is either simply a server-html page or built with a declared 'oocms' controller module. Simplest example of abstract implementation here . Each implements an unload feature and a startup feature where we would want to dereference stores or eventhooks in teardown - and in turn, assert stores gets loaded etc in the setup.
If you wish to use templates, then base your views on the dijit._TemplatedMixin
edit
Here is a simplified clarification of my oocms setup, where instead of basing it on BorderLayout, i will make it ContentPanes:
Example JSON for the menu, with a single item representing the above declared view
{
identifier: 'view',
label: 'name',
items: [
{ name: 'myForm', view: 'App.view.MyForm', extraParams: { foo: 'bar' } }
]
}
Base Application Controller in file 'AppPackagePath/Application.js'
Note, the code has not been tested but should give a good impression of how such a setup can be implemented
define(['dojo/_base/declare',
"dojo/_base/lang",
"dijit/registry",
"OoCmS/messagebus", // dependency mixin which will monitor 'notify/progress' topics'
"dojo/topic",
"dojo/data/ItemFileReadStore",
"dijit/tree/ForestStoreModel",
"dijit/Tree"
], function(declare, lang, registry, msgbus, dtopic, itemfilereadstore, djforestmodel, djtree) {
return declare("App.Application", [msgbus], {
paneContainer: NULL,
treeContainer: NULL,
menuStoreUrl: '/path/to/url-list',
_widgetInUse: undefined,
defaultPaneProps: {},
loading: false, // ismple mutex
constructor: function(args) {
lang.mixin(this, args);
if(!this.treeContainer || !this.paneContainer) {
console.error("Dont know where to place components")
}
this.defaultPaneProps = {
id: 'mainContentPane'
}
this.buildRendering();
},
buildRendering: function() {
this.menustore = new itemfilereadstore({
id: 'appMenuStore',
url:this.menuStoreUrl
});
this.menumodel = new djforestmodel({
id: 'appMenuModel',
store: this.menustore
});
this.menu = new djtree( {
model: this.menumodel,
showRoot: false,
autoExpand: true,
onClick: lang.hitch(this, this.paneRequested) // passes the item
})
// NEEDS a construct ID HERE
this.menu.placeAt(this.treeContainer)
},
paneRequested: function(item) {
if(this.loading || !item) {
console.warn("No pane to load, give me a menustore item");
return false;
}
if(!this._widgetInUse || !this._widgetInUse.isDirty()) {
dtopic.publish("notify/progress/loading");
this.loading = true;
}
if(typeof this._widgetInUse != "undefined") {
if(!this._widgetInUse.unload()) {
// bail out if widget says 'no' (isDirty)
return false;
}
this._widgetInUse.destroyRecursive();
delete this._widgetInUse;
}
var self = this,
modules = [this.menustore.getValue(item, 'view')];
require(modules, function(viewPane) {
self._widgetInUse = new viewPane(self.defaultProps);
// NEEDS a construct ID HERE
self._widgetInUse.placeAt(this.paneContainer)
self._widgetInUse.ready.then(function() {
self.paneLoaded();
})
});
return true;
},
paneLoaded: function() {
// hide ajax icons
dtopic.publish("notify/progress/done");
// assert widget has started
this._widgetInUse.startup();
this.loading = false;
}
})
})
AbstractView in file 'AppPackagePath/view/AbstractView.js':
define(["dojo/_base/declare",
"dojo/_base/Deferred",
"dojo/_base/lang",
"dijit/registry",
"dijit/layout/ContentPane"], function(declare, deferred, lang, registry, contentpane) {
return declare("App.view.AbstractView", [contentpane], {
observers: [], // all programmatic events handles should be stored for d/c on unload
parseOnLoad: false,
constructor: function(args) {
lang.mixin(this, args)
// setup ready.then resolve
this.ready = new deferred();
// once ready, create
this.ready.then(lang.hitch(this, this.postCreate));
// the above is actually not nescessary, since we could simply use onLoad in contentpane
if(typeof this.content != "undefined") {
this.set("content", this.content);
this.onLoad();
} else if(typeof 'href' == "undefined") {
console.warn("No contents nor href set in construct");
}
},
startup : function startup() {
this.inherited(arguments);
},
// if you override this, make sure to this.inherited(arguments);
onLoad: function() {
dojo.parser.parse(this.contentNode);
// alert the application, that loading is done
this.ready.resolve(null);
// and call render
this.render();
},
render: function() {
console.info('no custom rendering performed in ' + this.declaredClass)
},
isDirty: function() { return false; },
unload: function() {
dojo.forEach(this.observers, dojo.disconnect);
return true;
},
addObserver: function() {
// simple passthrough, adding the connect to handles
var handle = dojo.connect.call(dojo.window.get(dojo.doc),
arguments[0], arguments[1], arguments[2]);
this.observers.push(handle);
}
});
});
View implementation sample in file 'AppPackagePath/view/MyForm.js':
define(["dojo/_base/declare",
"dojo/_base/lang",
"App/view/AbstractView",
// the contentpane href will pull in some html
// in the html can be markup, which will be renderered when ready
// pull in requirements here
"dijit/form/Form", // markup require
"dijit/form/Button" // markup require
], function(declare, lang, baseinterface) {
return declare("App.view.MyForm", [baseinterface], {
// using an external HTML file
href: 'dojoform.html',
_isDirty : false,
isDirty: function() {
return this._isDirty;
},
render: function() {
var self = this;
this.formWidget = dijit.byId('embeddedForm') // hook up with loaded markup
// observer for children
dojo.forEach(this.formWidget._getDescendantFormWidgets(), function(widget){
if(! lang.isFunction(widget.onChange) )
console.log('unable to observe ' + widget.id);
self.addObserver(widget, 'onChange', function() {
self._isDirty = true;
});
});
//
},
// #override
unload: function() {
if(this.isDirty()) {
var go = confirm("Sure you wish to leave page before save?")
if(!go) return false;
}
return this.inherited(arguments);
}
})
});
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.