Rails / Trix Editor save changes via AJAX to server - ruby-on-rails

I am using the very simple to implement Trix Editor provided from Basecamp in an "Edit View".
How would one save automatically changes, without having the user to interact through the update button?
I am thinking about something like this:
(OLD SCRIPT)
window.setInterval(function() {
localStorage["editorState"] = JSON.stringify(element.editor)
}, 5000);
What I actually want to do:
post a ajax "post" request to the rails server. something like:
$('trix-editor').on('blur', function() {
var sendname = $('#note_name').val();
var sendlink = $('#linkinput').val();
var sendnote = $('input[name="note[note]"]').val();
$.ajax({
type: "POST",
url: "/notes",
data: { note: { name: sendname, link: sendlink, note: sendnote } },
success: function(data) {
alert(data.id);
return false;
},
error: function(data) {
return false;
}
});
(There is as well the problem with authentification and devise. Only if you are loged in you should be able to send an ajax post request ..??)
Even better would be to save changes only when the user changes some data, and then wait 5s and then push the updated data via json to the server. I have no clue how to do that...
PS: would have loved to tag this question with a "trix-editor" tag, sorry have not enought rep for doing so...

If you are using plain JavaScript, use a hidden input field:
<form>
<input type="hidden" id="noticeEditorContent"/>
<trix-editor input="noticeEditorContent" id="x" style="min-height: 200px;"></trix-editor>
</form>
Now you have access to the element with the ID x.
Which means, with getElementById, you can do something like that:
var richTex = document.getElementById("x");
With this variable, you can either set an interval as you already explained, or you are using jQuery to do the job:
$('#x').on('input', function() {
localStorage["editorState"] = JSON.stringify($('#x').val());
});
Just a suggestion. You can write this code a bit nicer and cleaner.
Now it depends. Is setting an interval every 5 seconds better or writing every change to the LocalStorage?
Suggestion:
Save the input when the user deselects the field:
$('#x').on('blur', function() {
localStorage["editorState"] = JSON.stringify($('#x').val());
});
Update: Here is a working JSFiddle.

so I came up with this code which saves via ajax on 'trix-blur' (which fires when the user disselects the trix-editor). There is only the question left if this code is secure enought with devise, or if now anyone can send data to be saved?!?
I have the authentification in the notes controller like that:
before_action :authenticate_user!
and here is the javascript part (with a custom messages functionality):
$('trix-editor').on('trix-blur', function() {
var sendname = $('#note_name').val();
var sendlink = $('#linkinput').val();
var sendnote = $('input[name="note[note]"]').val();
var sendid = $('#note_id').val();
$.ajax({
type: "PUT",
url: "/notes/" + sendid,
dataType: "json",
data: { note: { name: sendname, link: sendlink, note: sendnote }, id: sendid, commit: "Update Note" },
success: function(data) {
addMessage('auto saved ...', 'msg-success');
return false;
},
error: function(data) {
alert('error');
return false;
}
});
var addMessage = function(msg, msgclass) {
$('#notifications').append('<div id="msg" class="msg '+msgclass+'">'+msg+'</div>');
setTimeout(function() {
$('#msg:last-child').addClass('msgvisible');
}, 100);
displayMessage();
};
var displayMessage = function() {
setTimeout(function() {
hideMessage();
}, 2000);
};
var hideMessage = function() {
$('#msg').addClass('msghide');
setTimeout( function() {
deleteMessage();
}, 300);
};
var deleteMessage = function() {
$('#msg').remove();
if ($('#notificatosn').find('#msg') > 1) {
displayMessage();
}
};
});

Per the Trix project page the trix-editor emits different events on specific conditions.
The trix-change event is what you need; it fires whenever the editor’s contents has changed.
So, the first line of your JavaScript code could be
$('trix-editor').on('trix-change', function() {
/* Here will be your code to save the editor's contents. */
})

Related

Opening multiple jQueryUI dialogs

I wish to open multiple jQuery dialogs, one after the other. But only the first one opens.
jsFiddle
Dlg 1 -- Presents a YES / NO question.
Dlg 2 -- If YES, stick the word "go" into the hidden field id="frmtrig" and then trigger a change event. When this event is caught, it runs AJAX to populate the $('#message') div with html received via ajax success function, and then displays that in a dialog.
Dlg 3 -- After closing, present one final dialog.
Below code breaks at step 2. The alert('bonk') doesn't even happen...
I also deleted everything between alert('bonk') and the closing } of the success function, and the bonk alert still does not display. Can anyone spot a problem with my AJAX code?
Oddly, I've done a ton of (relatively simple) AJAX, so I'm puzzled that I'm getting stuck here. Note that it might be necessary to copy/paste my code blocks into two or 3 files on your own server to test this out, since jsFiddle won't work for troubleshooting AJAX.
I have been unsuccessful finding an example of doing this, and my own efforts over the past two days have not worked.
HTML
<div id="hot_link">Hover over this line to begin it all</div>
<div id="message"></div>
<div id="alert"></div>
<input type="hidden" id="frmtrig">
JAVASCRIPT
$(function(){
var cnt = 0;
var ans = 0;
var seen = 0;
var dlg = $('#message');
dlg.dialog({
title: '"The Big Test',
autoOpen:false,
modal:true,
width: 500,
close: function() {
if (seen==0 && ans > 0) {
cnt++;
seen++;
dlg.dialog('open');
}
}
});
$('#hot_link').hover(
function() {
//Hover-in
if (cnt < 1 || (cnt % 3 == 0)) {
var msg = '<p>If you say NO, the Dialog will just close. If you press YES, an AJAX call will happen.</p>';
dlg.html(msg);
dlg.dialog(
'option',
'buttons',
[{
text: 'Download',
click: function() {
ans++;
//seen = 5;
$('#frmtrig').val('go').change();
$(this).dialog('close');
}
},
{
text: 'Not now',
click: function() {
ans = 0;
$(this).dialog('close');
}
}]
);
dlg.dialog('open');
//alert('hi');
}
cnt++;
},
function() {
//Hover-out
//need this one to prevent duplicating hover-in code
}
);
$('#frmtrig').change(function() {
alert('boink');
$.ajax({
type: "POST",
async: false,
url: "ajax/ax_all_ajax_fns.php",
data: 'request=freegive_99',
success:function(data){
alert('bonk');
alert(data);
var dlg = $('#message');
alert('me be here');
dlg.html(data);
dlg.dialog(
'option',
'buttons',
[{
text: 'OK',
click: function() {
$(this).dialog('close');
}
}]
);
dlg.dialog('open');
}
});
});
}); //END document.ready
AX_ALL_AJAX_FNS.PHP
if ($_POST['request'] == 'freegive_99') {
echo 'THIS_LINE_WORKS';
}
I have tried your code on my machine and it is working fine.
If the second dialog is not opened, it means that success in your ajax request is not reached.
Are you sure that the problem doesn't come from a wrong path?
url: "ajax/ax_all_ajax_fns.php", // Check this path

Dynamically added link action produces 'This request has been blocked...' error

When I add category in controller action I return JSON object:
return Json(new { categoryName = category.Name, isPrimary = isPrim ? "1" : "-1", categoryId = categoryId }, JsonRequestBehavior.AllowGet);
In JS handler function I add item on page:
...
var totalLink = "<li style='color: #bbbbbb;'>" + result.categoryName + "<a class='removeCategoryButton' href='#lnk#'>remove</a></li>";
var lnk = '#Url.Action("RemoveCategoryFromLocation", "Location", new{locationId = Model.Location.TicketId, categoryId=-1})';
totalLink = totalLink.replace('#lnk#', lnk);
totalLink = totalLink.replace('-1', result.categoryId);
$('#otherCategories').append(totalLink);
...
When I click on remove link I call the following function:
$(function () {
$('.removeCategoryButton').click(function (event) {
event.preventDefault();
$.ajax({
url: this.href,
type: 'POST',
context: this,
success: function (result) {
if(result.categoryName == 1) {
$(this).closest('li').remove();
}
}
});
return false;
});
});
But I get the following error:
This request has been blocked because sensitive information could be disclosed to third party web sites when this is used in a GET request. To allow GET requests, set JsonRequestBehavior to AllowGet.
This error happens only when I add item and want to remove it as soon after add on page. If I refresh page and click on remove link it works without problem.
Just to note when I get the error from above category is removed, so call works it just from some reason pop this error.
You seem to be adding the remove links dynamically and yet you have subscribed to the .click event handler only once when the DOM is ready. So make sure you do it in a lively manner. But since the .live() method is deprecated, depending on the jQuery version that you are using you should use either .delegate() or the .on() methods.
So with the latest version of jQuery it is recommended to use .on():
$(document).on(events, selector, data, handler);
$(document).on('click', '.removeCategoryButton', function () {
$.ajax({
url: this.href,
type: 'POST',
context: this,
success: function (result) {
if(result.categoryName == 1) {
$(this).closest('li').remove();
}
}
});
return false;
});
Notice that you no longer need to wrap this in a document.ready callback.

TipTip only working on second hover after ajaxpost

Situation:
My tooltips show up on my page. Opening my fancybox works. Doing the ajax post from that fancybox works.
But my tooltips don't work in that fancybox. And they don't work after my ajax post.
I tried to reinitialize TipTip with the callbacks of fancybox.
EDIT
Title changes
So I found a way to let it run on the second hover after post but not on first hover.
I also found some explanations here but it still didn't fix my problem. Probably doing it wrong.
EDIT 2
Tootip in fancybox working use afterShow only.
Changes
added this in $(function () { so that it calls this function instead of initTipTip.
$(".tooltip").live('mouseover', function () {
$(this).tipTip();
});
Code of my function that does the post thing and closes my fancybox.
var reservation = MakeReservation();
var oldDateSplit = $("#resDate").val().split('/');
var newDateSplit = $("#dateEditReservation").val().split('/');
var oldDate = new Date(oldDateSplit[2], oldDateSplit[1] - 1, oldDateSplit[0]);
var newDate = new Date(newDateSplit[2], newDateSplit[1] - 1, newDateSplit[0]);
var time = $("#txtTime");
$.ajax({
url: ResolveUrl('~/Reservation/CheckSettings'),
data: "JSONString=" + reservation + "&hasJavaScriptMethod=" + true
}).done(function (data) {
if (data.length == 0 || oldDate.getTime() == newDate.getTime()) {
$.fancybox.close();
var id = $("#reservationId").val();
$("#reservationList").load(ResolveUrl('~/Reservation/reservationList',
function () { initTipTip(); }));
$("#reservationDetail").load(ResolveUrl('~/Reservation/DetailInfo',
function () { initTipTip(); }), { reservationId: id });
$("#reservationList").on("hover", " .tooltip", function () { $(this).tipTip(); });
}
else {
$(".errorDiv").removeClass("hidden");
$(".errorDiv").html(data);
$(".btnReservations").removeAttr('disabled');
}
});
NEW
$(".tooltip").live('mouseover', function () {
$(this).tipTip();
});
}
Still the same as before the edit.
Code initialization for TipTip
function initTipTip () {
$(".tooltip").tipTip();
}
Code of fancybox
function openFancy() {
$("a.inline").fancybox({
'type': 'ajax',
'afterShow': function () {
return initTipTip();
}
});
$("a.inlineBlockedDate").fancybox({
'type': 'ajax',
'ajax': { cache: false },
'afterShow': function () {
return initTipTip();
}
});
}
I found the solution for this.
So I used my .live in $(function(){ like in my question but I did not use ".tooltip" here but the table itself. I also use initTipTip here instead of $(this).tipTip();
So this solves the Tooltip from TipTip.
Explanation: This is because the tooltip.live only gets triggered on first hover and not when the table 'refreshes'. So now you add that event on that refresh of the table
Correct me if I'm wrong here.
So no need for any other .tiptip stuff or InitTipTip then in $(function(){
$("#reservationList").live('mouseover', function () {
initTipTip();
});
I hope your problem gets solved with this question.

Replace the Ajax.ActionLink by the same functionality with jQuery

With asp.net mvc we can do an ajax call like this:
#{
var ajaxOpts = new AjaxOptions { UpdateTargetId = "main-content", OnBegin = "fctTabLoading", OnComplete = "fctTabLoaded", InsertionMode = InsertionMode.Replace };
}
#Ajax.ActionLink("my link text", "MyAction", "MyController", new { id = Model.RequestID }, ajaxOpts)
Which produce the following html:
<a data-ajax="true" data-ajax-begin="fctTabLoading" data-ajax-complete="fctTabLoaded" data-ajax-mode="replace" data-ajax-update="#main-content" href="/MyController/MyAction/19">my link text</a>
Now I would like to execute the same ajax call but from jQuery and I don't know how to proceed!
I would like something like:
$.ajax({
type: "Post",
url: myURL,
begin: fctTabLoading,
complete: fctTabLoaded,
mode: "replace",
update: "#main-content",
cache: false,
success: function () { alert('success'); }
});
I know the above ajax script won't work because 'mode' and 'update' are not recognized. So I am blocked.
It drives me crazy :(
Why I cannot use the MVC ActionLink? Because I first need to show a jquery dialog to let the user confirm then only do the ajax call in order to refresh a specific div on my page.
Any help is greatly appreciated.
Thanks.
You could start by replacing your Ajax link with a normal link:
#Html.ActionLink(
"my link text", // linkText
"MyAction", // actionName
"MyController", // controllerName
new { id = Model.RequestID }, // routeValues
new { id = "mylink" } // htmlAttributes
)
which will produce the following markup:
my link text
and then in a separate js file unobtrusively AJAXify it:
$(function() {
$('#mylink').click(function() {
$.ajax({
url: this.href,
type: 'POST',
beforeSend: fctTabLoading, // corresponds to your OnBegin callback
complete: fctTabLoaded, // corresponds to your OnComplete callback
success: function(result) {
$('#main-content').html(result);
}
});
return false;
});
});
As you know, the Ajax.ActionLink uses jquery.unobtrusive-ajax.js to execute the ajax links.
If you look at that file, you will see that the event handlers use jquery's live event binder. This binds the event listener to the document object. So, if you wanted to confirm before this event was triggered, you could bind directly to the element like the following:
$('#YOUR_ELEMENT').click(function () {
var confirmed = confirm("CONFIRM_MESSAGE");
if (!confirmed ) {
return false;
}
return true;
});
To use jquery dialog you could do the following:
function confirmDialog () {
$('#YOUR_DIALOG').dialog(
{ buttons: { "Ok": function() { return true; },
{ "Cancel": function() {return false;}
}
});
}
and then you would set confirmed in the previous function to confirmDialog().
***The dialog options may not be exactly what you want, but this should get you going.

How do I pass an extra parameter to Jquery Autocomplete field?

I'm using the JQuery Autocomplete in one of my forms.
The basic form selects products from my database. This works great, but I'd like to further develop so that only products shipped from a certain zipcode are returned. I've got the backend script figured out. I just need to work out the best way to pass the zipcode to this script.
This is how my form looks.
<form>
<select id="zipcode">
<option value="2000">2000</option>
<option value="3000">3000</option>
<option value="4000">4000</option>
</select>
<input type="text" id="product"/>
<input type="submit"/>
</form>
And here is the JQuery code:
$("#product").autocomplete
({
source:"product_auto_complete.php?postcode=" + $('#zipcode').val() +"&",
minLength: 2,
select: function(event, ui){
//action
}
});
This code works to an extent. But only returns the first zipcode value regardless of which value is actually selected. I guess what's happening is that the source URL is primed on page load rather than when the select menu is changed. Is there a way around this? Or is there a better way overall to achieve the result I'm after?
You need to use a different approach for the source call, like this:
$("#product").autocomplete({
source: function(request, response) {
$.getJSON("product_auto_complete.php", { postcode: $('#zipcode').val() },
response);
},
minLength: 2,
select: function(event, ui){
//action
}
});
This format lets you pass whatever the value is when it's run, as opposed to when it's bound.
This is not to complicated men:
$(document).ready(function() {
src = 'http://domain.com/index.php';
// Load the cities straight from the server, passing the country as an extra param
$("#city_id").autocomplete({
source: function(request, response) {
$.ajax({
url: src,
dataType: "json",
data: {
term : request.term,
country_id : $("#country_id").val()
},
success: function(data) {
response(data);
}
});
},
min_length: 3,
delay: 300
});
});
jQuery("#whatJob").autocomplete(ajaxURL,{
width: 260,
matchContains: true,
selectFirst: false,
minChars: 2,
extraParams: { //to pass extra parameter in ajax file.
"auto_dealer": "yes",
},
});
I believe you are correct in thinking your call to $("#product").autocomplete is firing on page load. Perhaps you can assign an onchange() handler to the select menu:
$("#zipcode").change(resetAutocomplete);
and have it invalidate the #product autocomplete() call and create a new one.
function resetAutocomplete() {
$("#product").autocomplete("destroy");
$("#product").autocomplete({
source:"product_auto_complete.php?postcode=" + $('#zipcode').val(),
minLength: 2,
select: function(event, ui){... }
});
}
You may want your resetAutocomplete() call to be a little smarter -- like checking if the zip code actually differs from the last value -- to save a few server calls.
This work for me. Override the event search:
jQuery('#Distribuidor_provincia_nombre').autocomplete({
'minLength':0,
'search':function(event,ui){
var newUrl="/conf/general/provincias?pais="+$("#Distribuidor_pais_id").val();
$(this).autocomplete("option","source",newUrl)
},
'source':[]
});
Hope this one will help someone:
$("#txt_venuename").autocomplete({
source: function(request, response) {
$.getJSON('<?php echo base_url(); ?>admin/venue/venues_autocomplete',
{
user_id: <?php echo $user_param_id; ?>,
term: request.term
},
response);
},
minLength: 3,
select: function (a, b) {
var selected_venue_id = b.item.v_id;
var selected_venue_name = b.item.label;
$("#h_venueid").val(selected_venue_id);
console.log(selected_venue_id);
}
});
The default 'term' will be replaced by the new parameters list, so you will require to add again.
$('#product').setOptions({
extraParams: {
extra_parameter_name_to_send: function(){
return $("#source_of_extra_parameter_name").val();
}
}
})
$('#txtCropname').autocomplete('Handler/CropSearch.ashx', {
extraParams: {
test: 'new'
}
});

Resources