keep dynamically added elements after postback - jquery-ui

i got a big problem with jquery and the postback.
i'm dynamically adding html elements to my page. e.g. JQuery UI Tabs.
but after postback ALL dynamically added elements are gone.
how can i keep all of these elements after postback and also the values of textboxes and datetimepicker?
greetz
Tobi
EDIT:
e.g. i'm adding some JqueryUI Tabs with this code:
$(function () {
var $tab_title_input = $("#tab_title"),
$tab_content_input = $("#tab_content");
var tab_counter = 1;
var $addButton = $('<li class="ui-state-default ui-corner-top add-button"><span>+</span></li>');
$addButton.click(function () { addTab(); });
var $tabs = $("#tabsTravel, #tabsWork").tabs({ autoHeight: true, fillSpace: true,
tabTemplate: "<li><a href='#{href}'>#{label}</a> <span class='ui-icon ui-icon-close'>Remove Tab</span></li>",
add: function (event, ui) {
var tab_content = $tab_content_input.val() || "Tab " + tab_counter + " content.";
$(ui.panel).append("<p>" + tab_content + "</p>");
$("#tabsTravel ul.ui-tabs-nav").append($addButton);
}
});
$("#tabsTravel ul.ui-tabs-nav").append($addButton);
// actual addTab function
function addTab() {
tab_counter++;
var tab_title = "worker " + tab_counter;
$tabs.tabs("add", "#tabsTravel-" + tab_counter, tab_title)
.tabs("select", "#tabsWork-" + tab_counter, tab_title);
}
// close icon: removing the tab on click
$("#tabsTravel span.ui-icon-close").live("click", function () {
var index = $("li", $tabs).index($(this).parent());
$tabs.tabs("remove", index);
tab_counter--;
});
$("#tabsWork span.ui-icon-close").live("click", function () {
var index = $("li", $tabs).index($(this).parent());
$tabs.tabs("remove", index);
// tab_counter--;
});
$('#button').click(function () {
addTab()
});
});
how can i implement this localStorage to this code?
greetz
Bl!tz

Normally this would be the job of your server-side code; you would save the added elements in the the session cache, or in the database if the changes need to be permanent.
You could also consider using the new HTML5 session storage, or local storage, but this approach will probably be more of a hassle; best to use the sophisticated server-side libraries of PHP, .NET, etc, if possible.
Edit
Here's a simple example. Let's say your client script adds some HTML to the page:
var html = "<div>hello world</div>";
$("body").append(html);
Now, you can save it in local storage like this:
localStorage.setItem("dynamichtml", html);
If you put something in your page startup script like this:
$(document).ready(function() {
if (localStorage["dynamichtml"]) {
$("body").append(localStorage["dynamichtml"]);
}
});
Then you will have achieved the dynamic functionality. Note that the localStorage data will remain saved until the user deletes it explicitly.

Related

Angularjs: jquery selectable

i have created a directive to handle selectable provided by Jquery
mydirectives.directive('uiSelectable', function ($parse) {
return {
link: function (scope, element, attrs, ctrl) {
element.selectable({
stop: function (evt, ui) {
var collection = scope.$eval(attrs.docArray)
var selected = element.find('div.parent.ui-selected').map(function () {
var idx = $(this).index();
return { document: collection[idx] }
}).get();
scope.selectedItems = selected;
scope.$apply()
}
});
}
}
});
to use in html
<div class="margin-top-20px" ui-selectable doc-array="documents">
where documents is an array that get returned by server in ajax response.
its working fine i can select multiple items or single item
Issue: i want to clear selection on close button
http://plnkr.co/edit/3cSef9h7MeYSM0cgYUIX?p=preview
i can write jquery in controller to remove .ui-selected class but its not recommended approach
can some one guide me whats the best practice to achieve these type of issue
Update:
i fixed the issue by broadcasting event on cancel and listening it on directive
$scope.clearSelection=function() {
$scope.selectedItems = [];
$timeout(function () {
$rootScope.$broadcast('clearselection', '');
}, 100);
}
and in directive
scope.$on('clearselection', function (event, document) {
element.find('.ui-selected').removeClass('ui-selected')
});
is this the right way of doing it or what is the best practice to solve the issue.
http://plnkr.co/edit/3cSef9h7MeYSM0cgYUIX?p=preview

Set Umbraco Property Editor Input to jQueryUI Datepicker

I'm close but still can't quite get this to work.
I have a new custom property editor that is loading correctly and is doing almost everything expected until I try to set the text field to be a jQuery UI element.
As soon as I add a directive in Angular for setting it to call the jQuery UI datepicker function, I get the following error suggesting it hasn't loaded the jQueryUI script library correctly:
TypeError: Object [object Object] has no method 'datepicker'
Trouble is, I can't see where I should be adding it as the logical places (to my mind, at least) seem to make no difference. Here is the code in full:
function MultipleDatePickerController($scope, assetsService) {
//tell the assetsService to load the markdown.editor libs from the markdown editors
//plugin folder
//assetsService
// .load([
// "http://code.jquery.com/ui/1.10.4/jquery-ui.min.js"
// ])
// .then(function () {
// //this function will execute when all dependencies have loaded
// });
//load the seperat css for the editor to avoid it blocking our js loading
assetsService.loadCss("/css/jquery-ui.custom.min.css");
if (!$scope.model.value) {
$scope.model.value = [];
}
//add any fields that there isn't values for
//if ($scope.model.config.min > 0) {
if ($scope.model.value.length > 0) {
for (var i = 0; i < $scope.model.value.length; i++) {
if ((i + 1) > $scope.model.value.length) {
$scope.model.value.push({ value: "" });
}
}
}
$scope.add = function () {
//if ($scope.model.config.max <= 0 || $scope.model.value.length < $scope.model.config.max) {
if ($scope.model.value.length <= 52) {
$scope.model.value.push({ value: "" });
}
};
$scope.remove = function (index) {
var remainder = [];
for (var x = 0; x < $scope.model.value.length; x++) {
if (x !== index) {
remainder.push($scope.model.value[x]);
}
}
$scope.model.value = remainder;
};
}
var datePicker = angular.module("umbraco").controller("AcuIT.MultidateController", MultipleDatePickerController);
datePicker.directive('jqdatepicker', function () {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs, ngModelCtrl) {
$(function () {
element.datepicker({
dateFormat: 'dd/mm/yy',
onSelect: function (date) {
scope.$apply(function () {
ngModelCtrl.$setViewValue(date);
});
}
});
});
}
}
});
I faced the same problem when adapting a jQuery Date Range Picker for my Date Range Picker package for Umbraco 7. It's frustrating! The problem (I think) is that Angular's ng-model listens for "input" changes to trigger events and so doesn't pick up on a jQuery triggered event.
The way around it I found was to force the input event of the element you wish to update to fire manually, using jQuery's .trigger() event.
For example, the date picker I was using had this code for when a date was changed:
updateInputText: function () {
if (this.element.is('input')) {
this.element.val(this.startDate.format(this.format) + this.separator + this.endDate.format(this.format));
}
},
I just adapted it to force an input trigger by adding this.element.trigger('input') to the code block, so it now reads:
updateInputText: function () {
if (this.element.is('input')) {
this.element.val(this.startDate.format(this.format) + this.separator + this.endDate.format(this.format));
this.element.trigger('input');
}
},
This forces Angular to "see" the change and then ng-model is updated. There may well be a more elegant way (as I'm an Angular newbie), but I know this worked for me.
Got it. This is probably a bit of a hack, but it's simple and effective so it's a win nonetheless.
The assetsService call is the key, where I've put code into the deferred .then statement to call jQueryUI's datepicker on any item that has the "jqdp" CSS class:
//tell the assetsService to load the markdown.editor libs from the markdown editors
//plugin folder
assetsService
.load([
"/App_Plugins/Multidate/jquery-ui.min.js"
])
.then(function () {
//this function will execute when all dependencies have loaded
$('.jqdp').datepicker({ dateFormat: 'dd/mm/yy' });
});
I've then gone and added that class to my view:
<input type="text" jqdatepicker name="item_{{$index}}" ng-model="item.value" class="jqdp" id="dp-{{model.alias}}-{{$index}}" />
Finally, I've added a directive to ensure that dynamically-added items also display a datepicker:
datePicker.directive('jqdatepicker', function () {
return function (scope, element, attrs) {
scope.$watch("jqdatepicker", function () {
try{
$(element).datepicker({ dateFormat: 'dd/mm/yy' });
}
catch(e)
{}
});
};
});
As I said, this is possibly a bit hacky but it achieves the right result and seems like a simple solution.

How to call Modal Dialog from Datatables row - seem to have conflict with Jquery UI

I want to create "CRUD" functions by calling a modal form by clicking on a row in Datatables.
I've been at this for hours traversing through each step of my code and it seems I'm getting a conflict between my JQ-UI and Datatables. I found several examples, including the Datatables example for "live" functions, where you can initialize a table and call a simple jquery function.
I'm using:
code.jquery.com/jquery-1.9.1.js
code.jquery.com/ui/1.10.2/jquery-ui.js
../DataTables-1.9.4/media/js/jquery.dataTables.js
This example will give me the cursor, then makes the table "jump" across the page.
Does anyone have a working example or a fiddle I can experiment with?
function openDialog() {
$("#dialog-modal").dialog({
height: 140,
modal: true
});
}
/* Init DataTables */
$('#example').dataTable();
/* Add events */
$('#example tbody tr').on('click', function () {
$('#example tbody tr').css('cursor', 'pointer');
var sTitle;
var nTds = $('td', this);
var sBrowser = $(nTds[1]).text();
var sGrade = $(nTds[4]).text();
/*
if (sGrade == "A")
sTitle = sBrowser + ' will provide a first class (A) level of CSS support.';
else if (sGrade == "C")
sTitle = sBrowser + ' will provide a core (C) level of CSS support.';
else if (sGrade == "X")
sTitle = sBrowser + ' does not provide CSS support or has a broken implementation. Block CSS.';
else
sTitle = sBrowser + ' will provide an undefined level of CSS support.';
*/
openDialog();
//alert( sTitle )
});
A little sleep and another stab at this yielded a solution that at least solves the Datatable Dialog issue, I'll have to assume that any other issues I was having lies the other add-ins that I included. So to me this is solved.
The answer was 99% in this post - thanks to the author for the great working example.
I modified their link solution, combined with Datatables "live" solution example with variables, and was able to successfully pass data to a working dialog that works with pagination as the previous link explains.
This set up would allow me to create JQuery-UI Modal Forms, pass the ID from mySQL table column, and execute the form that's handing the Server Side PHP CRUD functions I needed.
(I can't take credit for any part of this, other than time spent making sure it worked).
The working example is taken straight from Datatables "live events" example, should be easy to drop in if you remove the sAjaxsource and go with a plain Datatable..
$('#example').dataTable( {
"bProcessing": true,
"bServerSide": true,
"bJQueryUI": true,
"bStateSave": true,
"sPaginationType": "full_numbers",
"sAjaxSource": " /*your data source page here*/ "
} );
/* Add events */
$("body").on("click", "#example tbody tr", function (e) {
e.preventDefault();
var nTds = $('td', this);
//example to show any cell data can be gathered, I used to get my ID from the first coumn in my final code
var sBrowser = $(nTds[1]).text();
var sGrade = $(nTds[4]).text();
var dialogText="The info cell I need was in (col2) as:"+sBrowser+" and in (col5) as:"+sGrade+"" ;
var targetUrl = $(this).attr("href");
$('#table-dialog').dialog({
buttons: {
"Delete": function() {
window.location.href = targetUrl;
},
"Cancel": function() {
$(this).dialog("close");
}
}
});
//simple dialog example here
$('#table-dialog').text(dialogText ).dialog("open");
});

Combining knockout with sammyjs/pathjs and jquery mobile

I'm trying to combine some of JS libraries to create a mobile SPA website. I'm working with knockoutJS that misses routing engine so I take it from SammyJS or PathJS (haven't decided yet). And I'd like to use jQuery Mobile to get the controls and the mobile design from it.
The thing is that whenever I include the jquery mobile js file into my page the routing engine stops working. Actually it does work, but the window.location.hash get changed not only by me but with jquery mobile itself.
So here is how the code looks like:
in the html file I got a div that I binded to a template
(function ($) {
infuser.defaults.templateUrl = "templates";
console.log('just before pageinit');
$(document).bind('pagecreate', function () {
// disable autoInit so we can navigate to bookmarked hash url
$.mobile.autoInitializePage = false;
// let PathJS handle navigation
$.mobile.ajaxEnabled = false;
$.mobile.hashListeningEnabled = false;
$.mobile.pushStateEnabled = false;
});
$(document).bind('pagebeforechange', function (e, data) {
var to = data.toPage;
if (typeof to === 'string') {
/* var u = $.mobile.path.parseUrl(to);
to = u.hash || '#' + u.pathname;
// manually set hash so PathJS will be triggered
location.hash = to;
// prevent JQM from handling navigation*/
e.preventDefault();
}
});
$(document).bind('pagechange', function (e, data) {
});
var Model = function () {
this.items = ko.observable(null);
this.chosenItemData = ko.observable();
this.state = ko.observable('items');
this.goToItemDetails = function (item) {
location.hash = '/details/' + item.id;
};
};
window.currentModel = new Model();
ko.applyBindings(window.currentModel);
Path.map('#home').to(function () {
currentModel.state(window.templates.items);
currentModel.items(window.dummyData);
});
Path.map('#home/details/:id').to(function () {
var self = this;
$(currentModel.items()).each(function (index, item) {
if (item.id.toString() == self.params['id']) {
currentModel.chosenItemData(item);
currentModel.state(window.templates.itemDetail);
}
});
});
Path.root('#home');
$(function () {
Path.listen();
})
})(jQuery);
Now, you can see that $.mobile.hashListeningEnabled = false; is set to false so the jquery mobile should not listen or react to hash changes whatsoever.
But!
lets say I move from localhost/sammy/#home to localhost/sammy/#home/detail/1
the hash change happens and changes right away to localhost/sammy/home/detail/1
for some reason the hash itself is ommited and the route doesn't get executed.
I sorry if I didnt explain myself better. I'm working on publishing it on a server for everyone to be able to look at it, but, unfortunately it takes time.
Meanwhile, if anyone has any idea what i can do to fix this it will be awesome!
So apparently (and it's written in the jQuery Mobile website the "initmobile" event fires as the script for jquery mobile it attached. To be able attach the event the following lines should be included before the jQuery Mobile script.
<script type="text/javascript">
$(document).on('mobileinit', function () {
$.mobile.ajaxEnabled = false;
$.mobile.hashListeningEnabled = false;
$.mobile.pushStateEnabled = false;
$.mobile.linkBindingEnabled = false;
});
then the onchangehash event in jquery mobile will be disabled.

Latency issue with Primefaces overlayPanel - loads to lazy

I am using Primefaces 3.2 with jsf 2 and glassfish 3.1.2.
I have a p:dataTable of users containing avatars of the user. Whenever the user moves the mouse over the avatar a p:overlayPanel appears with more information (lazy loaded) on the user, and disappears when the user moves the cursor away - like this:
<p:overlayPanel for="avatar" dynamic="true" showEvent="mouseover" hideEvent="mouseout" ...>
This works very well - as long as the user is "slowhanded". Whenever an user moves the cursor fast above many avatars many of the overlayPanels stay visible.
For example when the user has the cursor over the position where user avatars are displayed and uses the scroll wheel of his mouse to scroll the usertable down or up.
I believe that the overlaypanel starts to load the information dynamically (dynamic="true") from the server when showEvent="mouseover" is dispatched and displays the overlaypanel after the response from the server arrives.
This way it is not possible to detect whether the cursor is already away when the overlaypanel becomes visible - so the hideEvent="mouseout" is never dispatched.
Is there a way to make the primefaces overlaypanel appear directly on mousover, showing a loading gif and update the content into the overlaypanel when the response comes from the server.
Is this a good appraoch or does anyone know any other way to solve this nasty problem?
Thanks Pete
As my first answer is already very long and contains valid information, I decided to open a new answer presenting my final approach.
Im now using Primefaces inheritance pattern making the code alot cleaner. Also I noticed that replacing/overwriting the whole bindEvents function isnt necessary, as we can remove the old event handlers. Finally this code fixs the latest issue experienced: A hide event before ajax arrival.
PrimeFaces.widget.OverlayPanel = PrimeFaces.widget.OverlayPanel
.extend({
bindEvents : function() {
this._super();
var showEvent = this.cfg.showEvent + '.ui-overlay', hideEvent = this.cfg.hideEvent
+ '.ui-overlay';
$(document).off(showEvent + ' ' + hideEvent, this.targetId).on(
showEvent, this.targetId, this, function(e) {
var _self = e.data;
clearTimeout(_self.timer);
_self.timer = setTimeout(function() {
_self.hidden = false;
_self.show();
}, 300);
}).on(hideEvent, this.targetId, this, function(e) {
var _self = e.data;
clearTimeout(_self.timer);
_self.hidden = true;
_self.hide();
});
},
_show : function() {
if (!this.cfg.dynamic || !this.hidden) {
this._super();
}
}
});
Im sorry for the poor formatting: Eclipses fault ;)
Wow, finally after a long debuging session and testing various approaches i recognized that the problem isnt the ajax request but the event handlers itself:
.on(hideEvent, this.targetId, this, function(e) {
var _self = e.data;
if(_self.isVisible()) {
_self.hide();
}
});
As you can see, the widget is just hidden if its visible before. If your moving your mouse out too fast, now two things can happen:
The widget isnt visible at all
The animation is still going on
In this case the event is discarded and the panel stays visible. As animations are queued, one simply has to remove the if statement to fix the issue. I did this by replacing the whole bindEvents method:
PrimeFaces.widget.OverlayPanel.prototype.bindEvents = function() {
//mark target and descandants of target as a trigger for a primefaces overlay
this.target.data('primefaces-overlay-target', this.id).find('*').data('primefaces-overlay-target', this.id);
//show and hide events for target
if(this.cfg.showEvent == this.cfg.hideEvent) {
var event = this.cfg.showEvent;
$(document).off(event, this.targetId).on(event, this.targetId, this, function(e) {
e.data.toggle();
});
}
else {
var showEvent = this.cfg.showEvent + '.ui-overlay',
hideEvent = this.cfg.hideEvent + '.ui-overlay';
$(document).off(showEvent + ' ' + hideEvent, this.targetId).on(showEvent, this.targetId, this, function(e) {
var _self = e.data;
if(!_self.isVisible()) {
_self.show();
}
})
.on(hideEvent, this.targetId, this, function(e) {
var _self = e.data;
_self.hide();
});
}
//enter key support for mousedown event
this.bindKeyEvents();
var _self = this;
//hide overlay when mousedown is at outside of overlay
$(document.body).bind('mousedown.ui-overlay', function (e) {
if(_self.jq.hasClass('ui-overlay-hidden')) {
return;
}
//do nothing on target mousedown
var target = $(e.target);
if(_self.target.is(target)||_self.target.has(target).length > 0) {
return;
}
//hide overlay if mousedown is on outside
var offset = _self.jq.offset();
if(e.pageX < offset.left ||
e.pageX > offset.left + _self.jq.outerWidth() ||
e.pageY < offset.top ||
e.pageY > offset.top + _self.jq.outerHeight()) {
_self.hide();
}
});
//Hide overlay on resize
var resizeNS = 'resize.' + this.id;
$(window).unbind(resizeNS).bind(resizeNS, function() {
if(_self.jq.hasClass('ui-overlay-visible')) {
_self.hide();
}
});
};
Execute this code on load and the issue should be gone.
As your replacing the js code nevertheless, you can use this oppurtunity to implement quite a nice feature. By using timeouts in the event handlers one can easily implement a little delay not just improving usability (no more thousands of popups appear) but also reducing network traffic:
$(document).off(showEvent + ' ' + hideEvent, this.targetId).on(showEvent, this.targetId, this, function(e) {
var _self = e.data;
_self.timer = setTimeout( function(){
if(!_self.isVisible()) {
_self.show();
}
}, 300);
})
.on(hideEvent, this.targetId, this, function(e) {
var _self = e.data;
clearTimeout(_self.timer);
_self.hide();
});
Ofcourse you can use a global variable to control the delay time. If you want a more flexible approach youll have to overwrite the encodeScript method in the OverlayPanelRender to transmit an additional property. You could access it then with _self.cfg.delay. Notice though that youll have to replace the component model OverlayPanel too providing it with an extra attribute.
At the same time I thank you for this brilliant solution I take the opportunity to update it for Primefaces 5.2. In our application the code broke after that upgrade.
Follows the updated code for Primefaces 5.2:
PrimeFaces.widget.OverlayPanel.prototype.bindTargetEvents = function() {
var $this = this;
//mark target and descandants of target as a trigger for a primefaces overlay
this.target.data('primefaces-overlay-target', this.id).find('*').data('primefaces-overlay-target', this.id);
//show and hide events for target
if(this.cfg.showEvent === this.cfg.hideEvent) {
var event = this.cfg.showEvent;
this.target.on(event, function(e) {
$this.toggle();
});
}
else {
var showEvent = this.cfg.showEvent + '.ui-overlaypanel',
hideEvent = this.cfg.hideEvent + '.ui-overlaypanel';
this.target
.off(showEvent + ' ' + hideEvent)
.on(showEvent, function(e) {
clearTimeout($this.timer);
$this.timer = setTimeout(function() {
$('.ui-overlaypanel').hide();
$this.hidden = false;
$this.show();
}, 500);
})
.on(hideEvent, function(e) {
clearTimeout($this.timer);
$this.timer = setTimeout(function() {
// don't hide if hovering overlay
if(! $this.jq.is(":hover")) {
$this.hide();
}
}, 100);
});
}
$this.target.off('keydown.ui-overlaypanel keyup.ui-overlaypanel').on('keydown.ui-overlaypanel', function(e) {
var keyCode = $.ui.keyCode, key = e.which;
if(key === keyCode.ENTER||key === keyCode.NUMPAD_ENTER) {
e.preventDefault();
}
})
.on('keyup.ui-overlaypanel', function(e) {
var keyCode = $.ui.keyCode, key = e.which;
if(key === keyCode.ENTER||key === keyCode.NUMPAD_ENTER) {
$this.toggle();
e.preventDefault();
}
});
};
I also added an extra feature which allows the user to move the mouse over the overlay without hiding it. It should hide when you move the mouse out of it then which I accomplished through:
<p:overlayPanel .... onShow="onShowOverlayPanel(this)" ...>
function onShowOverlayPanel(ovr) {
ovr.jq.on("mouseleave", function(e) {
ovr.jq.hide();
});
}
Hope you enjoy!
It's been a long time, but in case anyone bumps into this problem, a showDelay attribute was added to the overlayPanel to solve this problem starting from Primefaces 6.2. However, it is not in the official documentation for some reason.

Resources