AJAX failure after using jQuery sortable in CakePHP - jquery-ui

I used to use scriptaculous in all my CakePHP projects because of its easy helpers. In time, I got more and more into jQuery, and now I want to replace my current scriptaculous scripts with jQuery scripts. Until now, everything has been good... except jQuery sortable.
JQuery sortable runs, but the AJAX call afterwards isn't working right. Now my programmer is on holiday so I've gotta ask you guys:
Old CakePHP code (inside pages_controller.php):
function order($parent_id = 0){
$this->autoRender=false;
//Users with rights may view this
$user = $this->checkRights('pages',true);
//loop through the data sent via the ajax call
foreach ($this->params['form']['page'] as $order => $id){
$this->Page->id = $id;
if(!$this->Page->saveField('order',$order)) {
$this->flash('Really freaky errors are occuring','/');
exit();
}
}
}
My jQuery looks like:
$(".sortable-list").sortable({
update: function() {
$.post('/pages/order/0', {
data: $('.sortable-list').sortable("serialize")
});
}
});
$(".sortable-list").disableSelection();
With Firebug, I see that the AJAX post call produces something like this:
page[]=14&page[]=23&page[]=18&page[]=11&page[]=26&page[]=28
However, it doesn't seem to work. I guess the page[]=id is different that the old scriptaculous format:
pages_0[] 1
pages_0[] 3
pages_0[] 2
Does anyone know how I can adjust the CakePHP file to read the string correctly?

I don't have working php environment to test, but should work basically.
$pages = $_GET['page'];
foreach( $pages as $order => $id)
{
$this->Page->id = $id;
if(!$this->Page->saveField('order',$order)) {
$this->flash('Really freaky errors are occuring','/');
exit();
}
}
PS. Probably you have kind of problem updating "$this->params".

before foreach line, insert
debug($this->params['form']['page']);
and see how page array looks like. then iterate properly.

Ah, finally.. It turned out that jquery's output was: data: '&page_0[]=1etc'. I had to make it page_0 instead of data and its fixed!
So:
$(".sortable-list").sortable({
update: function() {
$.post('/pages/order/0/, $('#pages_0').sortable("serialize", {key: 'pages_0[]'}))
}
});
$(".sortable-list").disableSelection();
I removed the {} from the second argument of $.post and it turned out to be the winner! thx for the help guys!

Related

Why do I lose draggable after drag?

I just migrated to backbone and have a strange behaviour.
I attach draggable to an element which is created by a script, thus not directly available in DOM.
EDIT:
The element that is created is .nav, $("#viewer") as container is already in the DOM.
In plain jQuery i used .on and mousemove event for this and it worked.
With backbone I use the same in the initialize method:
initialize: function(options) {
this.viewer = $("#viewer");
this.viewer.on("mousemove", '.nav', function() {
$(this).draggable();
});
This seems to work, but only one time.
After dragging the element one time, I can't drag it anymore.
Are there conflicts with the events? Am I missing something?
You have to refer to $('#viewer') after you've called render(). initialize is called before render, and so the DOM element doesn't exist.
Also, use this.$('#viewer'), and it will grab the element (after render) even if it hasn't been appended to your page's DOM.
myView = new ExampleView({ model: myModel });
$(body).append(myView.render().el);
myView.onRender();
// -------------
// Now on your view:
onRender: function() {
this.viewer = this.$('#viewer');
this.viewer.on("mousemove", '.nav', function() {
$(this).draggable();
});
},
UPDATE
You can also, to make such things simpler, customize Backbone to automatically call the onRender() function after rendering, by triggering an event or something.
Marionette.js (a Backbone.js extension) has this built in and I use it all the time.
The solution finally was pretty easy:
make sure you dont use outdated versions of backbone.js and underscore!!
After i updated the versions to latest I made it work with:
render: function() {
this.viewer.on("mouseover", '.nav', function() {
if (!$(this).data("init")) {
$(this).data("init", true);
$(this).draggable();
}
});
Probably still not very elegant but i couldnt made the suggested onRender method from dc2 work.

JQuery table sorter pager with AJAX - sorting not working ok

I have a problem with JQuery table sorter pager plugin.
I am trying to use paging with AJAX call. It seems to work OK for most cases, but I am unable to get the sorting working.
The problem is that when the request is made it always looks like (assuming the code from example here):
http:/mydatabase.com?page=0&size=100&{sortList:col}
just like the last parameter {sortList:col} is never actually replaced with proper sorting column.
If I'm reading the example correctly the request for sortList = [[2,0],[3,0]] should actually look like:
http:/mydatabase.com?page=0&size=100&col[2]=0&col[3]=0
But in my case it never is. Also when I click on the header the sorting is performed, but no request is made.
Is there anything I am missing in the linked example?
EDIT
The first part of my problem has been solved by replacing the plugin with the newest version.
For the second part, I still cannot get a request when clicking (sorting) columns. The ajaxProcessing function is almost exactly the same as in the example here, only headers variable is renamed.
As Mottie sugested I am posting an example result from AJAX call (MS MVC JsonResult):
{
"total_rows":1,
"headers":["Id.","Date","User name","File name","status","Hash","Link"],
"rows":[
{
"Id":"21",
"ReceiveDate":"02.12.2012",
"UserName":"John Doe",
"FileName":"test.txt",
"Status":"",
"Hash":"4A71FD2E12F7E04ED0C04E17476BD1BC5F823C8F",
"FileNameLink":"\u003ca style=\"padding-left:10px\" href=\"GetFile?Id=21&fileName=test.txt\"\u003eSave\u003c/a\u003e"
}]
}
Regards
I apologize, but I didn't get a chance to thoroughly test the latest changes to the pager. Some of the code was written by someone else, so I might have missed something obvious.
Anyway, this might be the bug that is causing you problems. It resets the table to the first page after each sort... I'll have fixed in the next update.
In the pager plugin, lines 460-470 is this code:
.bind('filterEnd.pager sortEnd.pager', function() {
//Prevent infinite event loops from occuring by setting this in all moveToPage calls and catching it here.
if ($.data(table, 'pagerUpdateTriggered')) {
$.data(table, 'pagerUpdateTriggered', false);
return;
}
c.page = 0;
updatePageDisplay(table, c);
moveToPage(table, c);
changeHeight(table, c);
})
Just change the c.page = 0 to this:
if (e.type === 'filterEnd') { c.page = 0; }
and add an e to the function(e).

Jquery calls not working in $viewContentLoaded of Angular

Unable to call jquery functions in $viewContentLoaded event of Angular controller, here is the code for the same.
$scope.$on('$viewContentLoaded', function() {
jQuery.growlUI('Growl Notification', 'Saved Succesfully');
jQuery('#category').tree()
});
Is any configuration required here?? I tried even noConflict(); var $jq = jQuery.noConflict();
Does it require any other configuration?
Thanks,
Abdul
First thing first, don't do DOM manipulation from controller. Instead do it from directives.
You can do same thing in directive link method. You can access the element on which directive is applied.
Make sure you load jquery before angularjs scripts, then grawlUI, three, angularJS and finally your application script. Below is directive sample
var app = angular.module("someModule", []);
app.directive("myDirective", function () {
return function (scope, element, attrs) {
$.growlUI('Growl Notification', 'Saved Succesfully');
element.tree();
};
});
angularjs has built in jQuery lite.
if you load full jquery after angular, since jQuery is already defined, the full jquery script will skip execution.
==Update after your comment==
I reviewed again your question after comment and realised that content which is loaded trough ajax is appended to some div in your angular view. Then you want to apply element.tree() jquery plugin to that content. Unfortunately example above will not work since it is fired on linking which happened before your content from ajax response is appended to element with directive I showed to you. But don't worry, there is a way :) tho it is quick and dirty but it is just for demo.
Let's say this is your controller
function ContentCtrl($scope, $http){
$scope.trees=[];
$scope.submitSomethingToServer=function(something){
$http.post("/article/1.html", something)
.success(function(response,status){
// don't forget to set correct order of jquery, angular javascript lib load
$.growlUI('Growl Notification', 'Saved Succesfully');
$scope.trees.push(response); // append response, I hope it is HTML
});
}
}
Now, directive which is in controller scope (it uses same scope as controller)
var app = angular.module("someModule", []);
app.directive("myDirective", function () {
return function (scope, element, attrs) {
scope.$watch("trees", function(){
var newParagraph=$("<p>" + scope.trees[scope.trees.length-1] + "</p>" ); // I hope this is ul>li>ul>li...or what ever you want to make as tree
element.append(newParagraph);
newParagraph.tree(); //it will apply tree plugin after content is appended to DOM in view
});
};
});
The second approach would be to $broadcast or $emit event from controller (depends where directive is, out or in scope of controller) after your ajax completes and you get content from server. Then directive should be subscribed to this event and handle it by receiving passed data (data=content as string) and do the rest as I showed you above.
The thing is, threat that content from ajax as data all the way it comes to directive, then inject it to element in which you want to render it and apply tree plugin to that content.

Rails 3, bootstrap modal multiple layers of remote calling

I've been racking my head against this for 2 days now. I'm massively frustrated, and I can't seem to find any information on this with searching.
The issue. I'm using a :remote => true link to load some html from a different controller.
$('.managed_locations').bind('ajax:complete', function(evt, xhr, status){
$('#locations_modal').modal('show')
$('#locations_modal').html(xhr.responseText);
});
So it gets the html, dumps it into the bootstrap modal and displays the modal. This is working fine.
But inside of the modal I ALSO have a form which also uses :remote => true. Now to make life harder, when a button is pressed I clone the form and display it. So the user could have many forms.
Now the issue. Whenever the form is submitted it just loads it like a normal page. It's as if the :remote => true is being ignored. But this only in the modal. If I just load the modal controller by itself it works just fine. I also had this developed before using another jquery lightbox where it was working fine. I'm just switching in bootstrap for consistency.
So my initial thoughts are that the jquery_ujs.js isn't finding the new forms. So I added some code to output the form elements.
$("#log_events").click(function () {
$(document).find(".new_stored_physical_location").each(function() {
console.log( $(this).data() );
console.log( $(this).data('events') );
});
return false;
});
Which outputs in the console:
Object { type="html", remote=true}
Object { ajax:complete=[1]}
So I see that the events are being set in jQuery. Each of these forms has :remote => true and has the ajax event for when the request is complete. But it's just not doing an ajax request when I hit submit.
Is there something I'm missing that is required to make sure an ajax request will happen from the form???? The data() looks fine, the data('events') look fine. But is there some other event/binding that I need to look at?
The html that is loaded in from the modal right now is loading a layout. But i've done it both with a layout, without a layout. It's driving me nuts. Thanks for the help guys.
Edit: Some extra weirdness. The modal also loads some additional remote links, all of which are working correctly. It's only the form links which don't seem to work.
I got a solution. The big issue was within jquery_ujs.js Especially this line:
$(document).delegate(rails.formSubmitSelector, 'submit.rails', function(e) {
FYI, rails.formSubmitSelector = 'form'. So this code found all of the forms in the document, overwrote the submit with this function. But the issue was that once you loaded in some ajax, and that ajax contained a it wouldn't add this fancy event to it. You need to re-add it.
So this is what I did.
Inside of jquery_ujs there is a bunch of functions that are accessible outside of it using $.rails. So things like: $.rails.enableElement, $.rails.nonBlankInputs. And the code for the submit event was sitting around all willy nilly. It only executes once when the page is loaded. So I put that in a function addSubmitEvent():
// Add the form submit event
addSubmitEvent: function(element) {
//$(element) was before $(document) but I changed it
$(element).delegate(rails.formSubmitSelector, 'submit.rails', function(e) {
var form = $(this),
remote = form.data('remote') !== undefined,
blankRequiredInputs = rails.blankInputs(form, rails.requiredInputSelector),
nonBlankFileInputs = rails.nonBlankInputs(form, rails.fileInputSelector);
if (!rails.allowAction(form)) return rails.stopEverything(e);
// skip other logic when required values are missing or file upload is present
if (blankRequiredInputs && form.attr("novalidate") == undefined && rails.fire(form, 'ajax:aborted:required', [blankRequiredInputs])) {
return rails.stopEverything(e);
}
if (remote) {
if (nonBlankFileInputs) {
return rails.fire(form, 'ajax:aborted:file', [nonBlankFileInputs]);
}
// If browser does not support submit bubbling, then this live-binding will be called before direct
// bindings. Therefore, we should directly call any direct bindings before remotely submitting form.
if (!$.support.submitBubbles && $().jquery < '1.7' && rails.callFormSubmitBindings(form, e) === false) return rails.stopEverything(e);
rails.handleRemote(form);
return false;
} else {
// slight timeout so that the submit button gets properly serialized
setTimeout(function(){ rails.disableFormElements(form); }, 13);
}
});
}
This is basically the exact same code. But now it's $(element) instead of $(document). This was changed because now I can sniff for when the modal has loaded in the html. Then I can call:
$.rails.addSubmitEvent('#my_modal');
I then had an issue of it adding the event too many times from when I opened/closed the modal multiple times. So I just put a simple true/false if around it to call it once only.

jQuery UI Sortable: Revert changes if update callback makes an AJAX call that fails?

I am using the sortable widget to re-order a list of items. After an item is dragged to a new location, I kick off an AJAX form post to the server to save the new order. How can I undo the sort (e.g. return the drag item to its original position in the list) if I receive an error message from the server?
Basically, I only want the re-order to "stick" if the server confirms that the changes were saved.
Try the following:
$(this).sortable('cancel');
I just encountered this same issue, and for the sake of a complete answer, I wanted to share my solution to this problem:
$('.list').sortable({
items:'.list:not(.loading)',
start: function(event,ui) {
var element = $(ui.item[0]);
element.data('lastParent', element.parent());
},
update: function(event,ui) {
var element = $(ui.item[0]);
if (element.hasClass('loading')) return;
element.addClass('loading');
$.ajax({
url:'/ajax',
context:element,
complete:function(xhr,status) {
$(this).removeClass('loading');
if (xhr.status != 200) {
$($(this).data('lastParent')).append(this);
}
},
});
}
});
You'll need to modify it to suit your codebase, but this is a completely multithread safe solution that works very well for me.
I'm pretty sure that sortable doesn't have any undo-last-drop function -- but it's a great idea!
In the mean time, though, I think your best bet is to write some sort of start that stores the ordering, and then on failure call a revert function. I.e. something like this:
$("list-container").sortable({
start: function () {
/* stash current order of sorted elements in an array */
},
update: function () {
/* ajax call; on failure, re-order based on the stashed order */
}
});
Would love to know if others have a better answer, though.

Resources