Scenario: I have a standard dropdown list and when the value in that dropdownlist changes I want to update another dropdownlist that exists in a tinyMCE control.
Currently it does what I want when I open the page (i.e. the first time)...
function changeParent() {
}
tinymce.create('tinymce.plugins.MoePlugin', {
createControl: function(n, cm) {
switch (n) {
case 'mylistbox':
var mlb = cm.createListBox('mylistbox', {
title: 'Inserts',
onselect: function(v) {
tinyMCE.execCommand("mceInsertContent",false,v);
}
});
<% foreach (var insert in (ViewData["Inserts"] as List<String>)) { %> // This is .NET
yourobject = '<%= insert %>'; // This is JS AND .NET
mlb.add(yourobject, yourobject); // This is JavaScript
<% } %>
// Return the new listbox instance
return mlb;
}
return null;
}
});
<%= Html.DropDownList(Model.Record[184].ModelEntity.ModelEntityId.ToString(), ViewData["Containers"] as SelectList, new { onchange = "changeParent(); return false;" })%>
I am thinking the way to accomplish this (in the ChangeParentFunction) is to call a controller action to get a new list, then grab the 'mylistbox' object and reassign it, but am unsure how to put it all together.
As far as updating the TinyMCE listbox goes, you can try using a tinymce.ui.NativeListBox instead of the standard tinymce.ui.ListBox. You can do this by setting the last argument to cm.createListBox to tinymce.ui.NativeListBox. This way, you'll have a regular old <select> that you can update as you normally would.
The downside is that it looks like you'll need to manually hook up your own onchange listener since NativeListBox maintains its own list of items internally.
EDIT:
I played around a bit with this last night and here's what I've come up with.
First, here's how to use a native list box and wire up our own onChange handler, the TinyMCE way:
// Create a NativeListBox so we can easily modify the contents of the list.
var mlb = cm.createListBox('mylistbox', {
title: 'Inserts'
}, tinymce.ui.NativeListBox);
// Set our own change handler.
mlb.onPostRender.add(function(t) {
tinymce.dom.Event.add(t.id, 'change', function(e) {
var v = e.target.options[e.target.selectedIndex].value;
tinyMCE.activeEditor.execCommand("mceInsertContent", false, v);
e.target.selectedIndex = 0;
});
});
As far as updating the list box at runtime, your idea of calling a controller action to get the new items is sound; I'm not familiar with ASP.NET, so I can't really help you there.
The ID of the <select> that TinyMCE creates takes the form editorId_controlId, where in your case controlId is "mylistbox". Firebug in Firefox is the easiest way to find the ID of the <select> :)
Here's the test button I added to my page to check if the above code was working:
<script type="text/javascript">
function doFoo() {
// Change "myEditor" below to the ID of your TinyMCE instance.
var insertsElem = document.getElementById("myEditor_mylistbox");
insertsElem.options.length = 1; // Remove all but the first option.
var optElem = document.createElement("option");
optElem.value = "1";
optElem.text = "Foo";
insertsElem.add(optElem, null);
optElem = document.createElement("option");
optElem.value = "2";
optElem.text = "Bar";
insertsElem.add(optElem, null);
}
</script>
<button onclick="doFoo();">FOO</button>
Hope this helps, or at least gets you started.
Step 1 - Provide a JsonResult in your controller
public JsonResult GetInserts(int containerId)
{
//some code to get list of inserts here
List<string> somedata = doSomeStuff();
return Json(somedata);
}
Step 2 - Create javascript function to get Json results
function getInserts() {
var params = {};
params.containerId = $("#184").val();
$.getJSON("GetInserts", params, updateInserts);
};
updateInserts = function(data) {
var insertsElem = document.getElementById("183_mylistbox");
insertsElem.options.length = 1; // Remove all but the first option.
var optElem = document.createElement("option");
for (var item in data) {
optElem = document.createElement("option");
optElem.value = item;
optElem.text = data[item];
try {
insertsElem.add(optElem, null); // standards compliant browsers
}
catch(ex) {
insertsElem.add(optElem, item+1); // IE only (second paramater is the items position in the list)
}
}
};
Step 3 - Create NativeListBox (code above provided by ZoogieZork above)
var mlb = cm.createListBox('mylistbox', {
title: 'Inserts'
}, tinymce.ui.NativeListBox);
// Set our own change handler.
mlb.onPostRender.add(function(t) {
tinymce.dom.Event.add(t.id, 'change', function(e) {
var v = e.target.options[e.target.selectedIndex].value;
tinyMCE.activeEditor.execCommand("mceInsertContent", false, v);
e.target.selectedIndex = 0;
});
});
//populate inserts on listbox create
getInserts();
Related
I am working on an ASP.NET MVC app (ASP.NET NOT ASP.NET Core).
When a View is rendered, the user can click on some buttons on the page to collapse or show divs associated with each button. The div changes its class depending on whether it is collapsed or shown. I am using bootstrap attributes for this, and it works fine.
Now I have a "Save" button on the page. When the user clicks on this button, I need to retrieve the ids and classes of the divs, and pass them TO the Controller (in an array/collection/dictionary whatever).
Is there a way/method in ASP.NET to send to the Controller the attributes (ids, classes, etc) of the DOM elements on the client's browser ?
Thanks
If you want to send some attributes of DOM to Controller, I have a way.
HTML:
<div id="demo-1" class="chosendiv other-className" data-code ="abc">Lorem Ipsum</div>
<div id="demo-2" class="chosendiv other-className" data-code ="xyz">Lorem Ipsum</div>
<div id="demo-3" class="other-className" data-code ="mnt">Lorem Ipsum</div>
<button id="btn-save" onclick="Save()">SAVE</button>
Javascript
<script>
function Save(){
var cds = document.getElementsByClassName('chosendiv');
var finder = [];
if(cds != null){
for(i = 0; i< cds.length; i++){
finder.push({
ID: cds[i].getAttribute('id'),
ClassName: cds[i].getAttribute('class'),
Code: cds[i].getAttribute('data-code')
})
}
}
//
// Send finder to Controller. You can use Ajax...
// A simple ajax call:
//
$.ajax({
url: '/Home/YourAction',
type: 'GET', //<---- you can use POST method.
data:{
myDiv: JSON.stringify(finder)
},
success: function(response){
// Your code
}
})
}
</script>
Your Controller
public class HomeController: Controller
{
public HomeController(){}
[HttpGet]
public void YourAction(string myDiv)
{
//A lot of ways for converting string to Object, such as: creating new class for model, ...
// I use Dictionary Class
List<Dictionary<string, string>> temp = new List<Dictionary<string, string>>();
if(!string.IsNullOrEmpty(myDiv))
{
try
{
temp = Newtonsoft.Json.JsonConvert.DeserializeObject<List<Dictionary<string, string>>>(myDiv);
}
catch { // Do something if it catches error. }
}
// Get a element (at index) from temp if temp.Count()>0
// var id = temp.ElementAt(index)["ID"];
// var className = temp.ElementAt(index)["ClassName"];
// var code = temp.ElementAt(index)["Code"];
//
//Your code
//
}
//......
}
It would be great if my answer could solve your problem.
Based on the answer provided by #Gia Khang
I made few changes in order to avoid the issue of the length of the URL exceeding the maximum limit.
Instead of adding the element's classes to an array using JS, I add them to a string :
function Save() {
var cds = document.getElementsByClassName('chosendiv');
// I use as string instead of an array
var finder = "";
if(cds != null){
for(i = 0; i< cds.length; i++){
finder = finder + "id=" + cds[i].getAttribute('id') + "class=" + cds[i].getAttribute('class') + "data-code=" +cds[i].getAttribute('data-code')
}
}
// Send finder to Controller. You can use Ajax...
// A simple ajax call:
var myURL = "/{Controller}/{Action}"
$.ajax({
url: myURL,
type: "POST",
data: { ids:finder },
success: function (response) {
}
})
}
In the Controller Action I add a parameter named "ids" (this must be the same name as the identifier of the data object in the post request)and I extract the id, class, and data value from the ids string by a method in one of my Models classes (sorry I work with VB.NET not with C# and it will take me a lot of time to convert the code to C#. I use the Split method in VB to split the ids string several times: a first one by using "id=" as delimiter, then spiting each element in the resulting array by the second delimiter "class=", etc. I add the resulting elements to a collection)
The Controller Action looks like this:
public class HomeController: Controller
{
public HomeController(){}
[HttpPost]
public void YourAction(string ids)
{
Models.myClass.splitStringMethod(ids)
Return View()
}
}
I'm working on a requirement where I have a datasource named 'emailSearchResults' where I search for email messages metadata and load the results in the datasource.
The fields in the datasource are not relevant, however I set the datasource to have 50 records per page as per the below screenshot:
The script I used to load the datasource is shown in the query field, that call the following script:
function getMessageDetails(userId, msgID)
{
var messageDetails = [];
var messageData;
var msgID_,subject_,from_,date_;
messageData=Gmail.Users.Messages.get(userId,msgID,{format:"metadata", metadataHeaders:["Message-ID", "Subject", "From", "Date"]});
console.log(messageData.payload.headers);
//console.log(msgID);
//console.log(messageData.payload.headers[3].value);
date_="<na>";
from_="<na>";
subject_="<na>";
msgID_="<na>";
for (var counter =0;counter<4;counter++)
{
if (messageData.payload.headers[counter].name=="Message-ID")
{
msgID_=messageData.payload.headers[counter].value;
}
if (messageData.payload.headers[counter].name=="Subject")
{
subject_=messageData.payload.headers[counter].value;
}
if (messageData.payload.headers[counter].name=="From")
{
from_=messageData.payload.headers[counter].value;
}
if (messageData.payload.headers[counter].name=="Date")
{
date_=messageData.payload.headers[counter].value;
}
}
messageDetails.push(date_);
messageDetails.push(from_);
messageDetails.push(subject_);
messageDetails.push(msgID_);
return messageDetails;
}
function searchMessages(userId,condition)
{
//
// first we build the conditions
// we can make it fixed
// or we can make it dynamic
var searchResult;
var deleteResult;
var currentMessage;
var results = [];
var pageToken;
var params = {};
var _stat;
var options = {
includeSpamTrash: "true",
pageToken: pageToken
};
var msgRecord = [];
do
{
searchResult=Gmail.Users.Messages.list(userId,options);
for (var i = 0; i < searchResult.messages.length; i++)
{
var record=app.models.emailSearchResults.newRecord();
msgRecord=getMessageDetails(userId,searchResult.messages[i].id);
record.msgMainID=searchResult.messages[i].id;
record.msgID=msgRecord[3];
record.subject=msgRecord[2];
record.senderAddress=msgRecord[1];
record.msgDate=msgRecord[0];
/*console.log(searchResult.messages[i].id);
console.log(msgRecord[3]);
console.log(msgRecord[2]);
console.log(msgRecord[1]);
console.log(msgRecord[0]);
return;*/
results.push(record);
msgRecord=null;
}
if (searchResult.nextPageToken) {
options.pageToken = searchResult.nextPageToken;
}
} while (searchResult.pageToken);
searchResult=null;
return results;
}
On the main page I put a table and linked it to the datasource, and I enabled pagination on the table, so I get the pager buttons at the bottom of the table as below:
When I execute the app and the datasource is filled, I see the first page results in a correct way, however when I want to move to the next page, I click the next page button and once the loading is complete I find out that I still see the same results from the first page on the table.
I am not familiar with how to make the table show the results of the second page then the third page, and I am going in circles on this...
Hope the explanation is clear and addresses the issue..
I would really appreciate any help on this!
Regards
Currently pagination isn't working as expected with calculated datasources. You can, however, build your own. There are several changes you'll need to make to accomplish this. First you'll want to refactor your searchMessages function to something like this:
function searchMessages(userId, pageToken){
var results = [];
var options = {
includeSpamTrash: "true",
pageToken: pageToken,
maxResults: 50
};
var searchResult = Gmail.Users.Messages.list(userId, options);
for (var i = 0; i < searchResult.messages.length; i++){
var record = app.models.emailSearchResults.newRecord();
var msgRecord = getMessageDetails(userId,searchResult.messages[i].id);
record.msgMainID = searchResult.messages[i].id;
record.msgID = msgRecord[3];
record.subject = msgRecord[2];
record.senderAddress = msgRecord[1];
record.msgDate = msgRecord[0];
results.push(record);
}
return {records: results, nextPageToken: searchResult.nextPageToken};
}
Then you'll want to change your datasource query. You'll need to add a number parameter called page.
var cache = CacheService.getUserCache();
var page = query.parameters.page || 1;
var pageToken;
if(page > 1){
pageToken = cache.get('pageToken' + page.toString());
}
var results = searchMessages('me', pageToken);
var nextPage = (page + 1).toString();
cache.put('pageToken' + nextPage, results.nextPageToken);
return results.records;
You'll need to modify the pagination widget's various attributes. Here are the previous/next click functions:
Previous:
widget.datasource.query.pageIndex--;
widget.datasource.query.parameters.page = widget.datasource.query.pageIndex;
widget.datasource.load();
Next:
widget.datasource.query.pageIndex++;
widget.datasource.query.parameters.page = widget.datasource.query.pageIndex;
widget.datasource.load();
You should be able to take it from there.
As I would like to create documents by merging the entries in a list into a Google Docs template. I have therefore integrated the DocumentMerge method from my previous question into a printButton in a list widget.
Clicking on the printButton should produce a document that merges the contents of the current row into the document template. But when I click on the printButton the method fails due to a circular reference. How can I fix that? The print method goes like this ...
function printReview(widget) {
var review = app.models.Review.getRecord(widget.datasource.item._key);
var templateId = 'templateId';
var filename = 'Review for ...' + new Date();
var copyFile = DriveApp.getFileById(templateId).makeCopy(filename);
var copyDoc = DocumentApp.openById(copyFile.getId());
var copyBody = copyDoc.getBody();
var fields = app.metadata.models.Review.fields;
for (var i in fields) {
var text = '$$' + fields[i].name + '$$';
var data = review[fields[i].name];
copyBody.replaceText(text, data);
}
copyDoc.saveAndClose();
}
As Morfinismo noticed you are getting the error because you are trying to pass complex object from client to server and serializer fails to handle it. In order to fix that you need to adjust your code:
// onClick button's event handler (client script)
function onPrintClick(button) {
var reviewKey = button.datasource.item._key;
google.script.run
.withSuccessHandler(function() { /* TODO */ })
.withFailureHandler(function() { /* TODO */ })
.printReview(reviewKey);
}
// server script
function printReview(reviewKey) {
var review = app.models.Review.getRecord(reviewKey);
...
}
I have a web form which load 100 000 of data from the database.I Have 50 dropdown which is populated with respect to selectedindex change of dropdown .so to bind dropdown i am using ajax code .
I have written nearly about 200 line of ajax code in a separate js file.I am using 3 tier artitecture .I am not returning dataset from the bal class, am returning generic class to bind gridview.also i have created a class to bind the gridview.Also I am not using any update panel.
Is this approach will improve my performance.??
But there is a problem for me,i have to write code in js file to bind dropdown like this.
function GetAppStoreLnk(id) {
var txtnameid = document.getElementById(id);
CreateXmlHttp();
var requestUrl = "Default2.aspx?id="+txtnameid+"";
if (XmlHttp) {
XmlHttp.onreadystatechange = function() { getschemename(txtnameid) };
XmlHttp.open("GET", requestUrl, true);
XmlHttp.send(null);
}
}
function getschemename(id)
{
// To make sure receiving response data from server is completed
if(XmlHttp.readyState == 4) {
// To make sure valid response is received from the server, 200 means response received is OK
if(XmlHttp.status == 200) {
var strData = XmlHttp.responseText;
if(strData != "") {
var arrscheme = strData.split("|");
id.length = 0;
for(i=0; i<arrscheme.length-1; i++) {
var strscheme = arrscheme[i];
var arrschnm = strscheme.split("~");
id.options[i] = new Option();
id.options[i].value = arrschnm[0];
id.options[i].text = arrschnm[1];
}
} else {
id.length = 0;
id.options[0] = new Option();
id.options[0].value = "";
id.options[0].text = "Scheme Name is not available";
}
document.body.style.cursor = "auto";
}
else {
id.length = 0;
id.options[0] = new Option();
id.options[0].value = "";
id.options[0].text = "server is not ready";
document.body.style.cursor = "auto";
}
}
}
but if i make class to bind the dropdown this will reduce my js file code line .How will i find the ID of the dropdown in the different page ie Default2.aspx .
Please help me .
How will i find the ID of the dropdown in the different page ie Default2.aspx .??Also i want dont want to use usercontrol or masterpage.
I don't understand your question. You are trying to access the Asp.net drop down in page Default.aspx in the page Default2.aspx right?
Could you please clarify your requirement?
Is there a plugin for the ExtJS Grid that automatically hides "empty" columns?
A column should be deemed "empty" if the value of the mapped field for all Records in the underlying store matches a given "emptiness" criteria (a given value or, better, a function).
Run-time add/remove/update operations on the underlying store should hide/un-hide columns accordingly.
Thanks!
I had to do something similar to this... here is a "Hiding Column Model" that will hide/show columns based on the return value of the "fieldHasData" method... it is probably a close start to what you were asking
Ext.ux.grid.HidingColumnModel = function() {
var Class = Ext.extend(Ext.grid.ColumnModel, {
constructor:function(config) {
Class.superclass.constructor.call(this, config);
},
onGridStoreLoad:function(store, records, options) {
store.fields.each(function(item, index, length) {
var colIndex = this.findColumnIndex(item.name);
if (colIndex >= 0) {
this.setHidden(colIndex, !this.fieldHasData(item.name, records));
}
}, this);
},
fieldHasData:function(field, records) {
var hasData = false;
Ext.each(Ext.pluck(records, "data"), function(item, index, allItems) {
if (item[field]) {
hasData = true;
}
});
return hasData;
}
});
return Class;
}();
And then in your grid... do add the listener on the column model
var columnModel = new Ext.ux.grid.HidingColumnModel(),
store = ... {create your store},
gridPanel = new Ext.grid.GridPanel({
...
store:store,
columnModel:columnModel,
...
});
store.on('load', columnModel.onGridStoreLoad, columnModel);
Since I could not find a similar plugin anywhere, I just implemented it myself :)
The code is on the extjs/sencha forums here:
http://www.sencha.com/forum/showthread.php?140685-Grid-Plugin-dynamically-hiding-columns-matching-quot-emptiness-quot-criteria&p=626670#post626670