Display notification pop-up box on open-source CRM - javascript

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

Related

Add Ajax Add To Cart Button to Front Page

I'm trying to add ajax to add to cart button on front page.
The setup is using Divi. Divi's woo product module does not display Add-to-cart button. I use the below to display add-to-cart button on front page. That works but the only issue is the Ajax is not working on front page. I've enabled "Enable AJAX add to basket buttons on archives" from Woocommerce settings.
add_action('template_redirect', 'work_only_on_front_page', 10);
function work_only_on_front_page(){
if ( is_front_page() ) {
add_action('woocommerce_after_shop_loop_item_title', 'woocommerce_template_loop_add_to_cart', 10);
}
}
Below works well on other pages other than the front page.
(function ($) {
$(document).on('click', '.single_add_to_cart_button', function (e) {
e.preventDefault();
var $thisbutton = $(this),
$form = $thisbutton.closest('form.cart'),
id = $thisbutton.val(),
product_qty = $form.find('input[name=quantity]').val() || 1,
product_id = $form.find('input[name=product_id]').val() || id,
variation_id = $form.find('input[name=variation_id]').val() || 0;
var data = {
action: 'woocommerce_ajax_add_to_cart',
product_id: product_id,
product_sku: '',
quantity: product_qty,
variation_id: variation_id,
};
$(document.body).trigger('adding_to_cart', [$thisbutton, data]);
$.ajax({
type: 'post',
url: wc_add_to_cart_params.ajax_url,
data: data,
beforeSend: function (response) {
$thisbutton.removeClass('added').addClass('loading');
},
complete: function (response) {
$thisbutton.addClass('added').removeClass('loading');
},
success: function (response) {
if (response.error && response.product_url) {
window.location = response.product_url;
return;
} else {
$(document.body).trigger('added_to_cart', [response.fragments, response.cart_hash, $thisbutton]);
}
},
});
return false;
});
})(jQuery);
function woocommerce_ajax_add_to_cart_js() {
if (is_product() || is_product_category() || is_shop() || is_front_page()) {
wp_enqueue_script('woocommerce-ajax-add-to-cart', get_stylesheet_directory_uri() . '/assets/js/ajax-add-to-cart.js', array('jquery'), '', true);
}
}
add_action('wp_enqueue_scripts', 'woocommerce_ajax_add_to_cart_js', 99);
add_action('wp_ajax_woocommerce_ajax_add_to_cart', 'woocommerce_ajax_add_to_cart');
add_action('wp_ajax_nopriv_woocommerce_ajax_add_to_cart', 'woocommerce_ajax_add_to_cart');
function woocommerce_ajax_add_to_cart() {
$product_id = apply_filters('woocommerce_add_to_cart_product_id', absint($_POST['product_id']));
$quantity = empty($_POST['quantity']) ? 1 : wc_stock_amount($_POST['quantity']);
$variation_id = absint($_POST['variation_id']);
$passed_validation = apply_filters('woocommerce_add_to_cart_validation', true, $product_id, $quantity);
$product_status = get_post_status($product_id);
if ($passed_validation && WC()->cart->add_to_cart($product_id, $quantity, $variation_id) && 'publish' === $product_status) {
do_action('woocommerce_ajax_added_to_cart', $product_id);
if ('yes' === get_option('woocommerce_cart_redirect_after_add')) {
wc_add_to_cart_message(array($product_id => $quantity), true);
}
WC_AJAX :: get_refreshed_fragments();
} else {
$data = array(
'error' => true,
'product_url' => apply_filters('woocommerce_cart_redirect_after_error', get_permalink($product_id), $product_id));
echo wp_send_json($data);
}
wp_die();
}
Another thing to check is to ensure that the function woocommerce_ajax_add_to_cart_js() is being called and the script is being enqueued on the front page.
You can check the browser's console to see if there are any errors related to the Javascript code not being loaded.

Javascript - prevent navigation during file upload

I have a vue component for video upload, where I am warning a user when he tries to navigate away during the video upload that he will lose the file if he does so, like this:
ready() {
window.onbeforeunload = () => {
if (this.uploading && !this.uploadingComplete && !this.failed) {
this.confirm('Are you sure you want to navigate away? Your video won't be uploaded if you do so!');
}
}
}
I am using sweetalert to alert the user about it. But how can I then make it stay on the same page, and prevent the navigation away before he confirms that he wants to navigate away?
This is the whole component:
<script>
function initialState (){
return {
uid: null,
uploading: false,
uploadingComplete: false,
failed: false,
title: null,
link: null,
description: null,
visibility: 'private',
saveStatus: null,
fileProgress: 0
}
}
export default {
data: function (){
return initialState();
},
methods: {
fileInputChange() {
this.uploading = true;
this.failed = false;
this.file = document.getElementById('video').files[0];
this.store().then(() => {
var form = new FormData();
form.append('video', this.file);
form.append('uid', this.uid);
this.$http.post('/upload', form, {
progress: (e) => {
if (e.lengthComputable) {
this.updateProgress(e)
}
}
}).then(() => {
this.uploadingComplete = true
}, () => {
this.failed = true
});
}, () => {
this.failed = true
})
},
store() {
return this.$http.post('/videos', {
title: this.title,
description: this.description,
visibility: this.visibility,
extension: this.file.name.split('.').pop()
}).then((response) => {
this.uid = response.json().data.uid;
});
},
update() {
this.saveStatus = 'Saving changes.';
return this.$http.put('/videos/' + this.uid, {
link: this.link,
title: this.title,
description: this.description,
visibility: this.visibility
}).then((response) => {
this.saveStatus = 'Changes saved.';
setTimeout(() => {
this.saveStatus = null
}, 3000)
}, () => {
this.saveStatus = 'Failed to save changes.';
});
},
updateProgress(e) {
e.percent = (e.loaded / e.total) * 100;
this.fileProgress = e.percent;
},
confirm(message) {
swal({
title: message,
text: null,
type: "warning",
showCancelButton: true,
cancelButtonText: "Cancel",
cancelButtonColor: '#FFF',
confirmButtonColor: "#2E112D",
confirmButtonText: "Yes, delete"
}).then(function(){
this.$data = initialState();
}.bind(this), function(dismiss) {
// dismiss can be 'overlay', 'cancel', 'close', 'esc', 'timer'
if (dismiss === 'cancel') { // you might also handle 'close' or 'timer' if you used those
// ignore
} else {
throw dismiss;
}
})
}
},
ready() {
window.onbeforeunload = () => {
if (this.uploading && !this.uploadingComplete && !this.failed) {
this.confirm('Are you sure you want to navigate away? Your video won't be uploaded if you do so!');
}
}
}
}
</script>
Mozilla documentation suggests
window.onbeforeunload = function(e) {
var dialogText = 'Dialog text here';
e.returnValue = dialogText;
return dialogText;
};
and also states that:
Since 25 May 2011, the HTML5 specification states that calls to window.alert(), window.confirm(), and window.prompt() methods may be ignored during this event. See the HTML5 specification for more details.
Source contains many other details regarding reasons and what to expect from modern browsers.
This question seems to be a duplicate of yours.
This answer suggests that to avoid weird browser behaviour you should set handler only when it's to prevent something (that is while navigating away should trigger a confirmation dialog)
But how can I then make it stay on the same page, and prevent the navigation away before he confirms that he wants to navigate away?
Add return false; to stop the event.
if (this.uploading && !this.uploadingComplete && !this.failed) {
this.confirm("Are you sure you want to navigate away? Your video won't be uploaded if you do so!");
return false; // <==== add this
}
return false; does 3 separate things when you call it :
event.preventDefault(); – It stops the browsers default behaviour.
event.stopPropagation(); – It prevents the event from propagating (or “bubbling up”) the DOM.
Stops callback execution and returns immediately when called.

performcallback gridview after combobox filtering

can you help me to fix my problem, I made gridview, and I use combobox to filter data in gridview and get gridview performcallback after filter, but gridview not callback after filter. how can I do ? Help me Please?
this is some code :
controller gridview filter :
[HttpPost]
public ActionResult FilterTypePro(String typePro)
{
//Session["typePro"] = typePro;
var model = Model._ProposalObject.ListDataProposal();
if (typePro != null && typePro != string.Empty)
{
model = Model._ProposalObject.ListDataProposal(typePro);
}
return PartialView("_gvPartialViewProposals", model);
}
and this is code to get value filter combobox :
function OnClickFilter(type) {
type = cbTypeProposal.GetValue();
$.ajax({
type: "POST",
cache: false,
async: false,
url: '#Url.Action("FilterTypePro", "App")',
data: { 'typePro': type },
success: function (data) {
/*gvPartialViewProposals.AdjustControl();
try {
gvPartialViewProposals.PerformCallback();
}
catch(er){
}*/
gvPartialViewProposals.Refresh(data);
}
});
}
and this is code to view cshtml :
groupItem.Items.Add(item =>
{
item.Caption = "Type Of Proposal";
item.Width = 400;
item.SetNestedContent(() =>
{
ViewContext.Writer.Write("<table><tr><td>");
Html.DevExpress().ComboBox(cmbSettings =>
{
cmbSettings.Name = "cbTypeProposal";
cmbSettings.Width = 100;
cmbSettings.Properties.DropDownStyle = DropDownStyle.DropDownList;
cmbSettings.ShowModelErrors = true;
cmbSettings.Properties.Items.Add("ATL", "ATL");
cmbSettings.Properties.Items.Add("BTL", "BTL");
cmbSettings.Properties.ClientSideEvents.SelectedIndexChanged = "function(s, e) { OnClickFilter();}";
}).Render();
ViewContext.Writer.Write("</td></tr></table>");
});
}); ;
}).GetHtml();
I hope you can help me.
Modify your javascript function as follows:
function OnClickFilter(type){
type = cbTypeProposal.GetValue();
gvPartialViewProposals.PerformCallback({
'typePro': type
});
}
Modify your grid settings as follows:
settings.Name = "gvPartialViewProposals";
settings.CustomActionRouteValues = new { Controller = "App", Action = "FilerTypePro" };

Jquery Context Menu ajax fetch menu items

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();
};

Defining multiple instances of a jquery ui widget on a single page

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

Categories

Resources