I am developing a website where I use a custom build jQuery widget to load data into multiple divs.
This is the code for the widget:
(function ($, window, document, undefined) {
$.widget ("my.contentloader", {
options: {
loading_message: true
},
_create: function () {
var that = this;
$.ajax ({
type: "POST",
url: that.options.url,
data: {data: that.options.formdata, limit: that.options.limit, offset: that.options.offset},
beforeSend: function (html) {
if (that.options.loading_message) {
$(that.options.target_area).html ("<div id='loading'>Loading</div>");
}
},
success: function (html) {
if (that.options.loading_message) {
$('#loading').remove ();
}
$(that.options.target_area).html (html);
},
error: function (html) {
$(that.options.error_area).html (html);
}
});
},
_setOption: function (key, value) {
this.options[key] = value;
$.Widget.prototype._setOption.apply (this, arguments);
}
});
})(jQuery, window, document);
I load data using the widget like this:
$('#targetdiv').contentloader ({
url: '<?php echo $action_url; ?>',
target_area: '#popup_box',
formdata: {'username' : username_email, 'password' : password}
});
I am having problems loading multiple instances on the same page.
Is there a way to not instantiate the widget on a specific div like this?
$('#targetdiv').contentloader
I think you need to assign each instance to a variable. That way, you can control each instance, or write a function that iterates over an array of instances.
var contentLoaders = [];
$('.target-div').each(function(i, data) {
contentLoaders[i] = $.widget("my.contentloader", { ... });
});
So then you should be able to operate on each loader independently, like:
for (var i in contentLoaders) {
var contentLoader = contentLoaders[i];
contentLoader.option( ... );
}
Also, you're using the DOM ID $('#loading') for multiple instances of the widget. This is wrong. You need to either use separate loaders for each widget, or else check to see if the ID exists and only insert the new node if it doesn't exist. And same for removing it.
** I've added this example block, hope it helps: **
//
// This is a way to do it if you want to explicitly define each contentloader.
// Below that, I'll write out a way to define the contentloaders in a loop.
//
var contentLoader1 = $('#targetdiv1').contentloader ({
url: '<?php echo $action_url; ?>',
target_area: '#popup_box',
formdata: {'username' : username_email, 'password' : password}
});
contentLoader1.option('url', 'http://google.com');
var contentLoader2 = $('#targetdiv2').contentloader ({
url: '<?php echo $action_url; ?>',
target_area: '#popup_box',
formdata: {'username' : username_email, 'password' : password}
});
contentLoader2.option('url', 'http:/apple.com');
// Push each widget instance into an array of widget objects
var contentLoaders = [];
contentLoaders.push(contentLoader1);
contentLoaders.push(contentLoader2);
for (var i in contentLoaders) {
console.log(i, contentLoaders[i].option('url'));
}
// Should print:
// 0 http://google.com
// 1 http://apple.com
//
//
// How to set a bunch of widgets at once from an array of content loader data
//
//
var contentLoaderData = [
{
divid: '#targetDiv1',
url: 'google.com',
formdata: {
username: 'joeshmo',
password: 'joeshmo1'
}
},
{
divid: '#targetDiv2',
url: 'apple.com',
formdata: {
username: 'plainjane',
password: 'plainjane1'
}
}
];
// Array of widget instances
var contentLoaders = [];
$.each(contentLoaderData, function(index, value) {
var contentLoader = $(this.divid).contentloader({
url: this.url,
target_area: '#popup_box',
formdata: {'username' : this.formdata.username, 'password' : this.formdata.password}
});
// Push each contentLoader instance into the contentLoaders array
contentLoaders.push(contentLoader);
});
for (var i in contentLoaders) {
console.log(i, contentLoaders[i].option('url'));
}
// Should print:
// 0 http://google.com
// 1 http://apple.com
Related
I am using a vtiger Crm system and I would like to use the pop up notification to display my own messages. I wrote an event handler that triggers after model save, in this handler I would like to call the notification box with my own message.
Here is a working example that is from the 'Products' module handlers, this code was pre-written, it checks if there were duplicate item numbers and shows a message 'LBL_DUPLICATE_item_number' in the pop-up box
class Products_DuplicateItemNumber_Handler
{
/**
* EditViewPreSave handler function.
*
* #param App\EventHandler $eventHandler
*/
public function editViewPreSave(App\EventHandler $eventHandler)
{
$recordModel = $eventHandler->getRecordModel();
$response = ['result' => true];
$fieldModel = $recordModel->getModule()->getFieldByName('item_number');
if ($fieldModel->isViewable() && ($item_number = $recordModel->get('item_number'))) {
$queryGenerator = new \App\QueryGenerator($recordModel->getModuleName());
$queryGenerator->setStateCondition('All');
$queryGenerator->setFields(['id'])->permissions = false;
$queryGenerator->addCondition($fieldModel->getName(), $item_number, 'e');
if ($recordModel->getId()) {
$queryGenerator->addCondition('id', $recordModel->getId(), 'n');
}
if ($queryGenerator->createQuery()->exists()) {
$response = [
'result' => false,
'hoverField' => 'item_number',
'message' => App\Language::translate('LBL_DUPLICATE_item_number', $recordModel->getModuleName())
];
}
}
return $response;
}
}
However, when i try to return $respone in 'editViewPreSave' of another module, nothing happens.
After some digging around i found out that the system uses 'Pnotify' library to show the pop up message, and i belive it's being called in a js file called 'edit.js' in this path 'public_html/layouts/basic/modules/Vtiger/resources'
preSaveValidation: function (form) {
const aDeferred = $.Deferred();
if (form.find('#preSaveValidation').val()) {
document.progressLoader = $.progressIndicator({
message: app.vtranslate('JS_SAVE_LOADER_INFO'),
position: 'html',
blockInfo: {
enabled: true
}
});
let formData = new FormData(form[0]);
formData.append('mode', 'preSaveValidation');
AppConnector.request({
async: false,
url: 'index.php',
type: 'POST',
data: formData,
processData: false,
contentType: false
})
.done((data) => {
document.progressLoader.progressIndicator({ mode: 'hide' });
let response = data.result;
for (let i = 0; i < response.length; i++) {
if (response[i].result !== true) {
app.showNotify({
text: response[i].message ? response[i].message : app.vtranslate('JS_ERROR'),
type: 'error'
});
if (response[i].hoverField != undefined) {
form.find('[name="' + response[i].hoverField + '"]').focus();
}
}
}
aDeferred.resolve(data.result.length <= 0);
})
.fail((textStatus, errorThrown) => {
document.progressLoader.progressIndicator({ mode: 'hide' });
app.showNotify({
text: app.vtranslate('JS_ERROR'),
type: 'error'
});
app.errorLog(textStatus, errorThrown);
aDeferred.resolve(false);
});
} else {
aDeferred.resolve(true);
}
return aDeferred.promise();
},
I believe that 'app.showNotify' is the function called to display the pop-up box, yet i'm not sure how to replicate the code for my own use, i would like to know the best approach to do this
At the moment I use two php files for my AJAX requests. One file holds the functions, the other file contains a switch function that determines which funtion should be fired. This works fine but recently I looked into a project that used php classes. The most interesting part I found was the fact that within the ajax calls, the url was set directly to the class and function.
The code is as follow:
class Score extends CI_Controller {
const INDIVIDUAL_SCORE_RANGE_SIZE = 7;
function __construct()
{
parent::__construct();
$this->load->model('benchmarkscore','',TRUE);
$this->load->model('aiinst','',TRUE);
$this->load->helper('score');
$this->load->helper('info');
}
function index()
{
show_404('score', false);
}
function accountingratio() {
if($this->session->userdata('logged_in'))
{
$session_data = $this->session->userdata('logged_in');
$ratio = strtolower($this->input->post('ratio'));
$filter = strtolower($this->input->post('filter'));
if(in_array($ratio, array('roi', 'rr', 'qr', 'cr', 'sr', 'vr'))) {
$ai_inst = $this->aiinst->findById($session_data['ICIOM1ID']);
$myScore = $this->benchmarkscore->getMyScoreByAccountingRatio($ratio, $session_data['ICIOM1ID']);
$avgScore = $this->benchmarkscore->getAverageScoreOfOthersByAccountingRatio($ratio, $session_data['ICIOM1ID'], $filter, $ai_inst);
$allScores = $this->benchmarkscore->getAllScoresByAccountingRatio($ratio, $session_data['ICIOM1ID'], $filter, $ai_inst);
// We should only show 20 scores so we have to filter
// some data.
$filterDescription = getFilterDescription(count($allScores), $filter, $ai_inst);
$filteredScores = filterRelatedScores($allScores, $ai_inst['ICIOM1ID'], Score::INDIVIDUAL_SCORE_RANGE_SIZE);
$deviation = getBenchmarkDeviation($myScore['myscore'], $avgScore['avgscore']);
$deviation = $deviation == -1 ? 'N/A' : $deviation.'%';
$result = createResultArray($myScore['myscore'], $avgScore['avgscore'], $filteredScores,
"Uw score: ".number_format($myScore['myscore'], 2, ',', '.')."\nBenchmark afwijking: ".$deviation,
wrapText("Benchmark score: ".number_format($avgScore['avgscore'], 2, ',', '.')."\n\n" .
"test".$filterDescription,35),
$filterDescription);
$this->output->set_content_type('application/json');
$this->output->set_output(json_encode($result));
}
}
}
}
The ajax call:
function getAccountingRatioData(type, filter, callback) {
showLoader();
setTimeout(function() {
var jqxhr = $.ajax({
type: 'POST',
url: '/score/accountingratio',
data: {
ratio: type,
filter: filter
},
dataType: 'json'
}).done(function(data) {
hideLoader();
if(data == null) {
bootbox.alert("error");
} else {
setTopChartDataProvider(data.benchmark);
setBottomChartDataProvider(data.allscores, "test");
setFilterInfo(data);
}
if(callback)
callback.call();
})
.fail(function() { hideLoader();bootbox.alert("error"); });
}, 500);
}
I was wondering how this is working because I learned that classes it self don't actually do anything, they just hold things and you should alwas create an object in order to "fire" the class..
Any thoughts?
I am trying to create an object that I can modify as per
Datatables - Data
I use the following code to create my object
function projectData(projid,projdesc,descdet){
this._projid = 'HTML1'
this._projdesc = 'HTML2'
for(var i=0;i<=7;i++){
this['_day'+i] = 'HTML3';
}
this.projid = function(){
return this._projid
}
this.projdesc = function(){
return this._projdesc
}
this.day0 = function(){ // More of these for day (0-7)
return this._day0
}
}
Then I use the following table initialization. (prjData is an array of New projectData objects)
var table = $('#table-ProjectHours').DataTable({
data: prjData,
"columns": [
{ data: 'projid',"visible": true},
{ data: 'projdesc',"width": "45%" },
{ data: 'day0',"orderDataType": "dom-text-numeric" },
{ data: 'day1',"orderDataType": "dom-text-numeric" },
{ data: 'day2',"orderDataType": "dom-text-numeric" },
{ data: 'day3',"orderDataType": "dom-text-numeric" },
{ data: 'day4',"orderDataType": "dom-text-numeric" },
{ data: 'day5',"orderDataType": "dom-text-numeric" },
{ data: 'day6',"orderDataType": "dom-text-numeric" },
{ data: 'day7',"orderDataType": "dom-text-numeric" }
]
});
I access my table through
var dto = $('#table-ProjectHours').DataTable().data();
I get my objects as seen here:
What I do not understand is why when I attempt to do dto[0].day0 I do not get _day0 --- I just get the function string.
I can access the data through _day0 but it seems wrong...
Try rows().data() https://datatables.net/reference/api/rows().data()
e.g.
var table = $('#example').DataTable();
var data = table
.rows()
.data();
alert( 'The table has '+data.length+' records' );
In order to set data in jquery-datatables you must use .row(index).data(obj) or .cell(selector).data(obj).
You can also use row(index).day0() and if you write a set portion of your method you can set the data like row(index).day0(thing_to_set) You must call row(index).invalidate after changing the data for it to appear correctly.
In this particular situation, .day0 must be called as .day0() since it is a method.
I have tried to reload the data populated by an ajax call but I cant get it to work, it shows the old data even after using the reload method. The thing is that if I change some variables to populate a different data and try to call the following code without refreshing the page it does not reload the updated data =/ Here is my code:
function populateDataGrid() {
$.ajaxSetup({async: false});
var gridinfo="";
$.post("lib/function.php",{activity: activity, shift: shift, date: date},
function (output){
gridinfo = JSON.parse(output);
});
$.ajaxSetup({async: true});
// INITIALIZING THE DATAGRID
var dataSource = new StaticDataSource({
columns: [
{
property: 'id',
label: '#',
sortable: true
},
{
property: 'date',
label: 'date',
sortable: true
},
....
],
formatter: function (items) {
var c=1;
$.each(items, function (index, item) {
item.select = '<input type="button" id="select'+c+'" class="select btn" value="select" onclick="">';
c=c+1;
});
},
data: gridinfo,
delay:300
});
$('#grid').datagrid({
dataSource: dataSource
});
$('#grid').datagrid('reload');
$('#modal-fast-appointment-results').modal({show:true});
}
I found a solution... I had to create a new DataSource (lets call it "AjaxDataSource") and add the ajax request functionality within the data constructor:
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define(['underscore'], factory);
} else {
root.AjaxDataSource = factory();
}
}(this, function () {
var AjaxDataSource = function (options) {
this._formatter = options.formatter;
this._columns = options.columns;
this._delay = options.delay || 0;
this._data = options.data;
};
AjaxDataSource.prototype = {
columns: function () {
return this._columns;
},
data: function (options, callback) {
var self = this;
setTimeout(function () {
var data;
$.ajax({
url: 'getdata.php',
type: 'POST',
data: 'param1:param1,param2,param2,...,paramN:paramN', // this is optional in case you have to send some params to getdata.php
dataType: 'json',
async: false,
success: function(result) {
data = result;
},
error: function(data){
//in case we want to debug and catch any possible error
// console.log(data);
}
});
// SEARCHING
if (options.search) {
data = _.filter(data, function (item) {
var match = false;
_.each(item, function (prop) {
if (_.isString(prop) || _.isFinite(prop)) {
if (prop.toString().toLowerCase().indexOf(options.search.toLowerCase()) !== -1) match = true;
}
});
return match;
});
}
var count = data.length;
// SORTING
if (options.sortProperty) {
data = _.sortBy(data, options.sortProperty);
if (options.sortDirection === 'desc') data.reverse();
}
// PAGING
var startIndex = options.pageIndex * options.pageSize;
var endIndex = startIndex + options.pageSize;
var end = (endIndex > count) ? count : endIndex;
var pages = Math.ceil(count / options.pageSize);
var page = options.pageIndex + 1;
var start = startIndex + 1;
data = data.slice(startIndex, endIndex);
if (self._formatter) self._formatter(data);
callback({ data: data, start: start, end: end, count: count, pages: pages, page: page });
}, this._delay)
}
};
return AjaxDataSource;
}));
After defining the new DataSource, we just need to create it and call the datagrid as usual:
function populateDataGrid(){
// INITIALIZING THE DATAGRID
var dataSource = new AjaxDataSource({
columns: [
{
property: 'id',
label: '#',
sortable: true
},
{
property: 'date',
label: 'date',
sortable: true
},
....
],
formatter: function (items) { // in case we want to add customized items, for example a button
var c=1;
$.each(items, function (index, item) {
item.select = '<input type="button" id="select'+c+'" class="select btn" value="select" onclick="">';
c=c+1;
});
},
delay:300
});
$('#grid').datagrid({
dataSource: dataSource
});
$('#grid').datagrid('reload');
$('#modal-results').modal({show:true});
}
So now we have our datagrid with data populated via ajax request with the ability to reload the data without refreshing the page.
Hope it helps someone!
I have a jquery context menu on my landing page where I have hardcode menu items. Now I want to get the menu items from server. Basically the idea is to show file names in a specified directory in the context menu list and open that file when user clicks it...
This is so far I have reached..
***UPDATE***
C# code
[HttpPost]
public JsonResult GetHelpFiles()
{
List<Manuals> manuals = null;
var filesPath = Server.MapPath(#"\HelpManuals");
var standardPath = new DirectoryInfo(filesPath);
if (standardPath.GetFiles().Any())
{
manuals = standardPath.GetFiles().Select(x => new Manuals
{
Name = GetFileNamewithoutExtension(x.Name),
Path = x.Name
}).ToList();
}
return Json(manuals, JsonRequestBehavior.AllowGet);
}
private string GetFileNamewithoutExtension(string filename)
{
var extension = Path.GetExtension(filename);
return filename.Substring(0, filename.Length - extension.Length);
}
JavaScript Code
$.post("/Home/GetHelpFiles", function (data) {
$.contextMenu({
selector: '#helpIcon',
trigger: 'hover',
delay: 300,
build: function($trigger, e) {
var options = {
callback: function(key) {
window.open("/HelpManuals/" + key);
},
items: {}
};
$.each(data, function (item, index) {
console.log("display name:" + index.Name);
console.log("File Path:" + index.Path);
options.items[item.Value] = {
name: index.Name,
key: index.Path
}
});
}
});
});
Thanks to Matt. Now, the build function gets fire on hover.. but im getting illegal invocation... and when iterating through json result, index.Name and this.Name gives correct result. But item.Name doesn't give anything..
to add items to the context menu dynamically you need to make a couple changes
$.contextMenu({
selector: '#helpIcon',
trigger: 'hover',
delay: 300,
build: function($trigger, e){
var options = {
callback: function (key) {
var manual;
if (key == "adminComp") {
manual = "AdminCompanion.pdf";
} else {
manual = "TeacherCompanion.pdf";
}
window.open("/HelpManuals/" + manual);
},
items: {}
}
//how to populate from model
#foreach(var temp in Model.FileList){
<text>
options.items[temp.Value] = {
name: temp.Name,
icon: 'open'
}
</text>
}
//should be able to do an ajax call here but I believe this will be called
//every time the context is triggered which may cause performance issues
$.ajax({
url: '#Url.Action("Action", "Controller")',
type: 'get',
cache: false,
async: true,
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (_result) {
if (_result.Success) {
$.each(_result, function(item, index){
options.items[item.Value] = {
name: item.Name,
icon: 'open'
}
});
}
});
return options;
}
});
so you use build and inside of that define options and put your callback in there. The items defined in there is empty and is populated in the build dynamically. We build our list off of what is passed through the model but I believe you can put the ajax call in the build like I have shown above. Hopefully this will get you on the right track at least.
I solved this problem the following way.
On a user-triggered right-click I return false in the build-function. This will prevent the context-menu from opening. Instead of opeing the context-menu I start an ajax-call to the server to get the contextMenu-entries.
When the ajax-call finishes successfully I create the items and save the items on the $trigger in a data-property.
After saving the menuItems in the data-property I open the context-menu manually.
When the build-function is executed again, I get the items from the data-property.
$.contextMenu({
build: function ($trigger, e)
{
// check if the menu-items have been saved in the previous call
if ($trigger.data("contextMenuItems") != null)
{
// get options from $trigger
var options = $trigger.data("contextMenuItems");
// clear $trigger.data("contextMenuItems"),
// so that menuitems are gotten next time user does a rightclick
// from the server again.
$trigger.data("contextMenuItems", null);
return options;
}
else
{
var options = {
callback: function (key)
{
alert(key);
},
items: {}
};
$.ajax({
url: "GetMenuItemsFromServer",
success: function (response, status, xhr)
{
// for each menu-item returned from the server
for (var i = 0; i < response.length; i++)
{
var ri = response[i];
// save the menu-item from the server in the options.items object
options.items[ri.id] = ri;
}
// save the options on the table-row;
$trigger.data("contextMenuItems", options);
// open the context-menu (reopen)
$trigger.contextMenu();
},
error: function (response, status, xhr)
{
if (xhr instanceof Error)
{
alert(xhr);
}
else
{
alert($($.parseHTML(response.responseText)).find("h2").text());
}
}
});
// This return false here is important
return false;
}
});
I have finally found a better solution after reading jquery context menu documentation, thoroughly..
C# CODE
public JsonResult GetHelpFiles()
{
List<Manuals> manuals = null;
var filesPath = Server.MapPath(#"\HelpManuals");
var standardPath = new DirectoryInfo(filesPath);
if (standardPath.GetFiles().Any())
{
manuals = standardPath.GetFiles().Select(x => new Manuals
{
Name = GetFileNamewithoutExtension(x.Name),
Path = x.Name
}).ToList();
}
return Json(manuals, JsonRequestBehavior.AllowGet);
}
HTML 5
<div id="dynamicMenu">
<menu id="html5menu" type="context" style="display: none"></menu>
</div>
JavaScript Code
$.post("/Home/GetHelpFiles", function (data) {
$.each(data, function (index, item) {
var e = '<command label="' + item.Name + '" id ="' + item.Path + '"></command>';
$("#html5menu").append(e);
});
$.contextMenu({
selector: '#helpIcon',
trigger: 'hover',
delay: 300,
items: $.contextMenu.fromMenu($('#html5menu'))
});
});
$("#dynamicMenu").on("click", "menu command", function () {
var link = $(this).attr('id');
window.open("/HelpManuals/" + link);
});
Here's my solution using deferred, important to know that this feature is supported for sub-menus only
$(function () {
$.contextMenu({
selector: '.SomeClass',
build: function ($trigger, e) {
var options = {
callback: function (key, options) {
// some call back
},
items: JSON.parse($trigger.attr('data-storage')) //this is initial static menu from HTML attribute you can use any static menu here
};
options.items['Reservations'] = {
name: $trigger.attr('data-reservations'),
icon: "checkmark",
items: loadItems($trigger) // this is AJAX loaded submenu
};
return options;
}
});
});
// Now this function loads submenu items in my case server responds with 'Reservations' object
var loadItems = function ($trigger) {
var dfd = jQuery.Deferred();
$.ajax({
type: "post",
url: "/ajax.php",
cache: false,
data: {
// request parameters are not importaint here use whatever you need to get data from your server
},
success: function (data) {
dfd.resolve(data.Reservations);
}
});
return dfd.promise();
};