I'm trying to make an EXTJS application to send email with an attachment. So I have a very basic form that include textfield for the subject, another textfield with inputType: 'file' for the attachment, and an html editor.
var panel = new Ext.form.FormPanel({
fileUpload:true,
labelAlign: 'right',
monitorValid: true,
border: false,
bodyBorder: false,
defaults:{
anchor: '100%',
labelStyle: 'font-weight:bold;'
},
items: [
{
xtype: 'textfield',
fieldLabel: 'SUBJECT',
name: 'subject',
allowBlank: false
},
{
xtype: 'textfield',
fieldLabel: 'ATTACHMENT',
name: 'file_to_upload',
anchor: '80%',
itemCls: 'attachment-field',
allowBlank: true,
inputType:'file'
},
{
xtype: 'htmleditor',
fieldLabel:'MESSAGE',
name:'msg'
}
]
});
And this form is placed in a window which will submit to the server:
var window = new Ext.Window({
title: 'Compose a message',
height: 600,
width: 800,
autoScroll: true,
border: false,
bodyBorder: false,
items: panel,
buttons:[
{
text: 'Send',
formBind: true,
handler: function() {
panel.getForm().submit({
url: *Call to the server*,
method : 'POST',
timeout: 300000, // 5min
waitMsg: 'Please wait while we send your email',
success :function(form, action) {
window.close();
}
});
}
},
{
text: 'Close',
handler: function() {
window.close();
}
}
]
});
And everything works great when I submit the form to the server using FF. But a problem occurs with IE8. IE is showing the security bar saying that I'm trying to download a file to the computer, which is exactly reverse of what I'm doing ( I'm uploading a file)!
How can I prevent triggering this security bar?
--EDIT December 18th, 2010 16:48 EST--
Is it possible that it can be caused by this: (comming from the EXTJS basicForm documentation)
File uploads are not performed using normal 'Ajax' techniques, that is they are not performed using XMLHttpRequests. Instead the form is submitted in the standard manner with the DOM element temporarily modified to have its target set to refer to a dynamically generated, hidden which is inserted into the document but removed after the return data has been gathered. The server response is parsed by the browser to create the document for the IFRAME. If the server is using JSON to send the return object, then the Content-Type header must be set to "text/html" in order to tell the browser to insert the text unchanged into the document body. Characters which are significant to an HTML parser must be sent as HTML entities, so encode "<" as "<", "&" as "&" etc. The response text is retrieved from the document, and a fake XMLHttpRequest object is created containing a responseText property in order to conform to the requirements of event handlers and callbacks. Be aware that file upload packets are sent with the content type multipart/form and some server technologies (notably JEE) may require some custom processing in order to retrieve parameter names and parameter values from the packet content.
I don't think I understand everything about there explanation...
-- END EDIT --
Thanks
Alain
On the server side, you MUST do the following, even though it looks a bit odd:
set the response type to "text/html"
send {"success": true} as JSON object
the response type makes the browser render the response in the iframe ExtJS uses
ExtJS reads that from the DOM, and interprets it as JSON, looking for the success field.
This is what Ext writes to the document.
<iframe id="ext-gen170" name="ext-gen170" class="x-hidden" src="about:blank"><html><head></head><body></body></html></iframe>
This problem can be resolve by setting a valid https src path e.g. https://yousite.com/blank.html
I haven't found yet how to modify the src. Any help would be welcome
I believe ExtJs is doing this:
if(Ext.isIE) {
frame.src = Ext.SSL_SECURE_URL;
}
Where that string is defined as:
/**
* URL to a blank file used by Ext when in secure mode for iframe src and onReady src to prevent
* the IE insecure content warning (<tt>'about:blank'</tt>, except for IE in secure mode, which is <tt>'javascript:""'</tt>).
* #type String
*/
SSL_SECURE_URL : isSecure && isIE ? 'javascript:""' : 'about:blank',
...and isSecure is:
isSecure = /^https/i.test(window.location.protocol);
So that's where your about:blank is coming from.
If that doesn't work, it sounds like you need to set that URL to something that works for you (or maybe the value of isSecure is acting up).
following your suggestions and many test, here is what I did. I just assign the SSL_SECURE_URL constants right after I included the extall library to a blank image on my https application.
Ext.SSL_SECURE_URL = 'https://mysite.com/blank.gif';
No problem anymore.
Thanks a lot.
Related
I am working on a customized share button with the elfinder, there is tutorial about how to custom the right click menu, and I have implemented it. However, there are some rules I would like to apply for the menu
1) For folder only, exclude the button for file
2) For root level only, exclude the button for sub level folder
3) For single folder only, if select more than one folder will exclude the button
Here is the current code, right now have the share button but not with the above rules:
elFinder.prototype.i18.zh_TW.messages['cmdsharefolder'] = 'Share';
elFinder.prototype._options.commands.push('sharefolder');
elFinder.prototype.commands.sharefolder = function () {
this.exec = function (hashes) {
//open share menu
}
this.getstate = function () {
return 0;
}
}
And the elfinder instance:
var elfinder = $('#elfinder').elfinder({
url: '<?= $connector; ?>',
soundPath: '<?= site_url('assets/plugins/elFinder/sounds/rm.wav'); ?>',
height: 700,
lang: 'zh_TW',
uiOptions: {
// toolbar configuration
toolbar: [
['back', 'forward'],
['reload'],
['mkdir', 'upload'],
['copy', 'cut', 'paste', 'rm'],
['rename'],
['view', 'sort']
]
},
contextmenu: {
navbar: ['open', '|', 'copy', 'cut', 'paste', 'duplicate', '|', 'rm', '|', 'info'],
cwd: ['reload', 'back', '|', 'upload', 'mkdir', 'paste', '|', 'info'],
files: [
'open', 'quicklook', 'sharefolder', '|', 'download', '|', 'copy', 'cut', 'paste', 'rm', '|', 'rename', '|', 'info'
]
},
ui: ['toolbar', 'tree', 'stat']
}).elfinder('instance');
The problems are :
1) how to apply the rules above? (if the rules can not apply, can workaround with checking and popup alert box, please suggest the way for checking, thanks)
2) are there any way to capture which folder is selected e.g. full folder path etc...
Here is the doc I have studied , the sample case is for general use:
https://github.com/Studio-42/elFinder/wiki/Custom-context-menu-command
Thanks a lot for helping.
What you're trying to accomplish is possible, but it highly depends on how your connector works.
In order to work with your rules, you either have to add code in this.exec or this.getstate. There are pros and cons for each option.
If you add the code to this.getstate your code might be executed more than once for a single action (example: when you select multiple folders, the function is executed when you click on the first folder, on the last and when you right click).
However, using this.getstate you're able to hide the option (button) on any situation that doesn't meet the requirements (rules).
Adding code to this.exec assures the code is only executed once per action, but the button is always present even if the rules don't apply.
If you choose this, you'll need to work with some kind of alert or dialog messages to the user, to inform them why the share menu isn't displayed.
In the following code, I've used this.getstate, but you can move the code to this.exec. On the Javascript side you need to use something like this:
elFinder.prototype.i18.zh_TW.messages['cmdsharefolder'] = 'Share';
elFinder.prototype._options.commands.push('sharefolder');
elFinder.prototype.commands.sharefolder = function () {
var self = this,
fm = self.fm;
this.exec = function (hashes) {
// Display the share menu
};
this.getstate = function () {
var hashes = self.fm.selected(), result = 0;
// Verifies rule nr 3: For single folder only,
// if select more than one folder will exclude the button
if (hashes.length > 1) {
return -1;
}
// Rule 1 and 2 exclude itself. By this I mean that rule nr 2
// takes precedence over rule nr 1, so you just need to check
// if the selected hash is a root folder.
$.ajax({
url: 'file-manager/check-rule-on-hash',
data: hashes,
type: 'get',
async: false,
dataType: 'json',
success: function(response) {
if (!response.isRoot) {
result = -1;
}
}
});
return result;
}
}
Explanation:
Rule nr 3 is easy, because you have access to the number of selected items through Javascript. So you just need to count the number of selected hashes. If that number is greater than 1, that means the user selected more than one item and the menu shouldn't be displayed.
Rule nr 2 is a little trickier, because you need to "validate" the selected hash, and this is why I started to say that this depends on how your connector works.
For instance, I have a custom PHP connector, where the folder structure is defined through a database table. Although all files are physically stored on the hard drive, the meta data is stored on the same table (mostly because all permissions are defined via database). In my case it's somewhat easy to perform an ajax call and check if a given hash is a root folder, because that info is stored on the database and I can retrieve that info with a simple query.
Since I can't be sure on how your connector works, the generic solution is to perform an ajax call to the server, with the selected hash, and verify if that hash is a root folder. The server should return an object with the property isRoot that's either true or false.
For now I have
require([
"dojo/on", "dgrid/OnDemandGrid","dgrid/Tree","dgrid/Editor", "dgrid/Keyboard", "dojo/_base/declare",
"dgrid/data/createHierarchicalStore", "data/projects_data",
"dojo/domReady!"
], function(
on, Grid, Tree, Editor, Keyboard, declare, createHierarchicalStore, hierarchicalCountryData
){
var count = 0; // for incrementing edits from button under 1st grid
function nbspFormatter(value){
// returns " " for blank content, to prevent cell collapsing
return value === undefined || value === "" ? " " : value;
}
var StandardGrid = declare([Grid, Keyboard, Editor, Tree]);
window.grid = new StandardGrid({
collection: createHierarchicalStore({ data: hierarchicalCountryData }, true),
columns: [
{renderExpando: true, label: "Name", field:"variant_name", sortable: false, editor: "text", editOn: "dblclick"},
{label: "Visited", field: "bool", sortable: false, editor: "checkbox"},
{label:"Project", field:"project", sortable: false, editor: "text", editOn: "dblclick"},
{label:"locked", field:"locked", editor: "text", editOn: "dblclick"},
{label:"modified", field:"modified", editor: "text", editOn: "dblclick"},
{label:"summary", field:"summary", editor: "text", editOn: "dblclick"}
]
}, "treeGrid2");
grid.on("dgrid-datachange", function(evt){
console.log("data changed: ", evt.oldValue, " -> ", evt.value);
console.log("cell: ", evt.cell.row.id, evt.cell.column.field);
});
grid.on("dgrid-editor-show", function(evt){
console.log("show editOn editor: ", evt);
});
grid.on("dgrid-editor-hide", function(evt){
console.log("hide editOn editor: ", evt);
});
});
data/projects is a js file containing the data. but how to connect this dGrid now to a MySQL database? Can't find any good information in the docs. I think might be something with JSON rest but not sure about this.
Addition:
I can show the db in an HTML Table. is there a suitable possibilty to populate dGrid from a HTML Table?
I am still missing something. Have connections from
Database -> PHP
but can't get result in a proper JS to load into dStore.
The simplest path forward is to write a service in your server-side language of choice (sounds like PHP in this case) that produces JSON output based on the data in your MySQL database. Depending on the potential size of your data, you can potentially design your data to work with one of two out-of-the-box stores in dstore: Request (and Rest if write operations are also involved), or RequestMemory.
The simpler of the two is RequestMemory, which simply combines the features of the Memory store with an up-front server request (via Request). This store will expect the service to respond with one complete data payload: an array of objects where each object is a record in your database. Something like this:
[
{
"id": 1,
"foo": "bar"
},
{
"id": 2,
"foo": "baz"
}
]
The Rest store expects data in the same format, but also expects the service to handle filtering, sorting, and ranges. Filtering and sorting are represented via query string parameters (e.g. foo=bar&baz=bim in the simplest case for filter, and sort(+foo) or sort(-foo) for sort), while ranges are typically represented via the HTTP Range header (e.g. Range: Items 0-9 for the first 10 items).
Implementing a service for the Rest store is obviously more work, but would be preferable if you're expecting your data source to potentially have thousands of items, since RequestMemory would have no choice but to request all of the items up-front.
With either of these stores, once you have a service that outputs JSON as appropriate, you can create an instance of the store with its target pointing to the service endpoint, then pass it to a grid via the collection property.
If your data is intended to represent a hierarchical structure, it should still be possible to mix dstore/Tree into dstore/RequestMemory or dstore/Request, provided that your hierarchy is represented via parent ID references. By default, Tree filters children via a parent property on each item, and reports mayHaveChildren results by inspecting a hasChildren property on each item.
I'm using jEditable to allow users to correct phone numbers and email addresses in a table, updating the database as they press enter. The problem is the response back from the server is just being interpreted as text by the client, when in fact there is some javascript code I want executed.
The way jEditable works is that you unobtrusively attach the "editable" action to a DOM element, which is working great. Watching what the client is sending to the server using tcpdump I can see that the POST is html, with X-Requested-With: XMLHttpRequest; and what comes back from the server is Content-Type: text/javascript.
The Rails controller action in question is verify_email, and since I have all the verify logic working perfectly but the client side javascript isn't, I've shortened the controller action to simply:
def verify_email
respond_to do|format|
format.js
end
end
And similarly simplified verify_email.js.erb to be just
$(function(){ console.log("hello from verify_email callback")});
You'd think I'd get a pleasant greeting in the browser console, but no, instead the value of the td changes to be the text '$(function(){ console.log("hello from verify_email callback")});'
Attaching the jEditable functionality to the table data is done by class:
$(function() {
$('.email')
.editable('http://newserver.us.org:3001/directory/review/verify_email', {
indicator : '<%= image_tag "wait18trans.gif", :title => "wait" %>',
tooltip : "Click to edit...",
}
})
})
I just can't see where the error is. I'm comparing against a control that does not use jEditable: I have a checkbox that posts on change:
$(function() {
$('.checkmark').change(function() {
$.post("http://newserver.us.org:3001/directory/review/checkmark",
{"id" : $(this).attr("id")})
});
});
and when I put the same console log message into checkmark.js.erb, I get pleasant greeting. Over the wire the headers look the same.
My current thinking is that jEditable tweaks the DOM to insert a text input field, and then removes it, this is somehow interfering with the javascript response?!?
I added a callback to the jEditable to see what is coming back, like this:
$(function() {
$('.email')
.editable('http://newserver.us.org:3001/directory/review/verify_email', {
indicator : '<%= image_tag "wait18trans.gif", :title => "wait" %>',
tooltip : "Click to edit...",
callback : function(value, settings) {
console.log(this);
console.log(value);
console.log(settings);
}
})
})
And that produces console logging of:
[Log] <td class="email" id="2-3--1347-597" title="Click to edit...">$(function(){ console.log("hello from verify_email callback")});
[Log] $(function(){ console.log("hello from verify_email callback")});
[Log] Object
ajaxoptions: Object
autoheight: true
autowidth: true
callback: function (value, settings) {
event: "click.editable"
height: 35
id: "id"
indicator: "<img alt="Wait18trans" src="/directory/images/wait18trans.gif" title="wait" />"
loaddata: Object
loadtext: "Loading..."
loadtype: "GET"
name: "value"
onblur: "cancel"
placeholder: ""
submitdata: Object
target: "http://newserver.us.org:3001/directory/review/verify_email"
tooltip: "Click to edit..."
type: "text"
width: 202
__proto__: Object
It's just not helpful that value is a string of javascript.
Ultimately what I want the javascript to do is update the id of the td. So maybe I need to grab a reference to the td before the jEditable post, to be used to find the correct element upon return?
It appears that rails console log was giving me the information I needed to figure this out.
The checkbox, which didn't use jEditible, was being seen by rails this way:
Processing by ReviewController#checkbox as */*
My email tds were being seen by rails this way:
Processing by ReviewController#verify_email as HTML
Which is somewhat understandable then why everything I was sending back was simply being interpreted as HTML (even though I only had a responds_to |format| format.js...)
So in my assignment of jEditable to elements of class 'email' I added the ajaxoption of dataType : "" (JQuery docs say dataType is intelligent... jEditable was setting it to HTML)
$(function() {
$('.email')
.editable('http://newserver.us.org:3001/directory/review/verify_email', {
indicator : '<%= image_tag "wait18trans.gif", :title => "wait" %>',
tooltip : "Click to edit...",
ajaxoptions : { dataType:""}
})
})
And now everything is working like I intended!
I have a form with a combo box in it. The combo box loads it's data via a Json store and I use form.getForm().load( url: 'URL') to load the forms data also from a Json store.
When the data is loaded into the form I can see via Firebug that it receives the right value, the combo then displays the correct corresponding display value. When I look at the HTML in FireBug for the hiddenField it says the value="DISPLAYVALUE" not value="VALUE". When I then pick any value from the combo it changes to the correct value="VALUE".
Of course if the user never changes the combo the wrong value is submitted. Is this by design/limitation or am I missing something.
Do I really need to load and verify the data for each combo before I do the getForm().load()? wouldn't it make sense for load() to just load the complete data even if that means loading the data from a store?
I've included simplified sample code that has the issue.
Ext.onReady(function(){
var frmClientRecord = {
xtype: 'form',
items: [
{
fieldLabel: 'Advisor',
xtype: 'combo',
id: 'advisorName',
displayField: 'Advisor',
valueField: 'advisorId',
hiddenName: 'advisorsId',
mode: 'remote',
store: new Ext.data.Store({
autoLoad: true,
proxy: new Ext.data.HttpProxy({
url: '/referrals/index.php/advisors/read',
method: 'POST'
}),
reader: new Ext.data.JsonReader({
root: 'results',
fields: [
{name: 'advisorId'},
{name: 'Advisor'}
]
})
})
}
]
}
frmClientRecordCmp = new Ext.FormPanel(Ext.apply(frmClientRecord));
frmClientRecordCmp.getForm().load({
url: '/referrals/index.php/clients/getbyid/100',
})
frmClientRecordCmp.render(document.body);
});
JSON FOR THE COMBO
({"results":[{"Advisor":"Chris","advisorId":33},{"Advisor":"Fawzia","advisorId":2},{"Advisor":"Kent","advisorId":3},{"Advisor":"Rob","advisorId":4},{"Advisor":"Stephanie","advisorId":5}]})
JSON FOR THE FORM
{success: true, data: {"advisorsId":33}}
This could happen if your form is loading before the combo store loads. (Given that rest of your code looks Ok)
I suspect this will resolve if you render your form before loading it.
(move frmClientRecordCmp.render(document.body); one statement up in your sample code)
EDIT
Two points to consider-
Are you sure the combo store has finished loading before the form loads?
Looking at ComboBox's valueField documentation, it looks like a call to combo.setValue may be necessary after the form loads. Something along the lines of -
frmClientRecordCmp.getForm().load({
url: '/referrals/index.php/clients/getbyid/100',
success:function(form,action){
form.findField('advisorName').setValue(action.result.data.advisorId);
}
});
It was a problem I had caused by using the id: 'advisorName'. I was returning a field also named 'advisorName' so it was filling it even though I was specifying a hiddenName value. The moral is make sure your id is unique and not a field name.
I have an ExtJS combobox with a remote data store behind it. In all browsers it works fine, except in IE (all versions that I've tested) where the combobox expands for a splitsecond, showing a "loading" icon and then it disappears again. Clicking it again after this doesn't make it expand at all anymore. Basically: it's not being populated.
On the server side, everything is fine. The Controller action is reached (using ASP.NET MVC) which returns Json data. The Json is properly formed (all other browsers swallow it at least).
A weird thing is, that when I put a breakpoint in the Controller action, the JsonStore is properly filled on the client side. This to me indicates some sort of timing problem.
Another weird thing is that, every once in a while, it works fine. Perhaps because the timing is just right by accident or something.
If I set the combobox mode to 'local' and force a .load() on the remote datastore, it works fine in IE too.
This problem is present in all comboboxes that use a remote datastore for us.
I have the following JsonStore:
var companies = new Ext.data.JsonStore({
url: '/Company/GetCompanies/',
root: 'companies',
fields: [
{ name: 'CompanyID'},
{ name: 'CompanyName'}]
});
The combobox:
new Ext.form.ComboBox({
fieldLabel: 'Company',
typeAhead: false,
triggerAction: 'all',
valueField: 'CompanyID',
hiddenName: 'CompanyID',
displayField: 'CompanyName',
mode: 'remote',
lazyRender: true,
store: companies,
allowBlank: true,
editable: false,
listeners: {
'focus': function(){
if(companies.data.length > 0)
{
debugger; // This is only ever fired after the aforementioned breakpoint method.
}
}
}
})
The Json that is returned by the Controller:
{"companies":[{"CompanyID":1,"CompanyName":"Test"},{"CompanyID":2,"CompanyName":"Test1"
},{"CompanyID":3,"CompanyName":"Test2"}]}
Figures, I work out the solution just minutes after posting a question about it.
I switched to a Store instead of a JsonStore and specified the method: 'GET' property of the proxy and it worked. I have no clue why this does work though, and why only IE had a problem with it. My Controller action accepts both GET and POST so that wasn't it.
The Store:
var companies = new Ext.data.Store({
proxy: new Ext.data.HttpProxy({ url: '/Company/GetCompanies/', method: 'GET' }),
reader: new Ext.data.JsonReader({ root: 'companies' }, [{ name: 'CompanyID', mapping: 'CompanyID' }, { name: 'CompanyName', mapping: 'CompanyName'}])
});