implement history for AJAX

This commit is contained in:
Oliver Gorwits
2012-01-28 19:56:54 +00:00
parent d9d7aad374
commit 5bd481a36a
13 changed files with 263 additions and 233 deletions

View File

@@ -1,80 +0,0 @@
/* ========================================================
* bootstrap-tabs.js v1.4.0
* http://twitter.github.com/bootstrap/javascript.html#tabs
* ========================================================
* Copyright 2011 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ======================================================== */
!function( $ ){
"use strict"
function activate ( element, container ) {
container
.find('> .active')
.removeClass('active')
.find('> .dropdown-menu > .active')
.removeClass('active')
element.addClass('active')
if ( element.parent('.dropdown-menu') ) {
element.closest('li.dropdown').addClass('active')
}
}
function tab( e ) {
var $this = $(this)
, $ul = $this.closest('ul:not(.dropdown-menu)')
, href = $this.attr('href')
, previous
, $href
if ( /^#\w+/.test(href) ) {
e.preventDefault()
if ( $this.parent('li').hasClass('active') ) {
return
}
previous = $ul.find('.active a').last()[0]
$href = $(href)
activate($this.parent('li'), $ul)
activate($href, $href.parent())
$this.trigger({
type: 'change'
, relatedTarget: previous
})
}
}
/* TABS/PILLS PLUGIN DEFINITION
* ============================ */
$.fn.tabs = $.fn.pills = function ( selector ) {
return this.each(function () {
$(this).delegate(selector || '.tabs li > a, .pills > li > a', 'click', tab)
})
}
$(document).ready(function () {
$('body').tabs('ul[data-tabs] li > a, ul[data-pills] > li > a')
})
}( window.jQuery || window.ender );

View File

@@ -0,0 +1,99 @@
/**
* @author Kyle Florence <kyle[dot]florence[at]gmail[dot]com>
* @website https://github.com/kflorence/jquery-deserialize/
* @version 1.1.0
*
* Dual licensed under the MIT and GPLv2 licenses.
*/
(function( jQuery ) {
var push = Array.prototype.push,
rcheck = /^(radio|checkbox)$/i,
rselect = /^(option|select-one|select-multiple)$/i,
rvalue = /^(hidden|text|search|tel|url|email|password|datetime|date|month|week|time|datetime-local|number|range|color|submit|image|reset|button|textarea)$/i;
jQuery.fn.extend({
deserialize: function( data, callback ) {
if ( !this.length || !data ) {
return this;
}
var i, length,
elements = this[ 0 ].elements || this.find( ":input" ).get(),
normalized = [];
if ( !elements ) {
return this;
}
if ( jQuery.isArray( data ) ) {
normalized = data;
} else if ( jQuery.isPlainObject( data ) ) {
var key, value;
for ( key in data ) {
jQuery.isArray( value = data[ key ] ) ?
push.apply( normalized, jQuery.map( value, function( v ) {
return { name: key, value: v };
})) : push.call( normalized, { name: key, value: value } );
}
} else if ( typeof data === "string" ) {
var parts;
data = decodeURIComponent( data ).split( "&" );
for ( i = 0, length = data.length; i < length; i++ ) {
parts = data[ i ].split( "=" );
push.call( normalized, { name: parts[ 0 ], value: parts[ 1 ] } );
}
}
if ( !( length = normalized.length ) ) {
return this;
}
var current, element, item, j, len, property, type;
for ( i = 0; i < length; i++ ) {
current = normalized[ i ];
if ( !( element = elements[ current.name ] ) ) {
continue;
}
type = ( len = element.length ) ? element[ 0 ] : element;
type = type.type || type.nodeName;
property = null;
if ( rvalue.test( type ) ) {
property = "value";
} else if ( rcheck.test( type ) ) {
property = "checked";
} else if ( rselect.test( type ) ) {
property = "selected";
}
// Handle element group
if ( len ) {
for ( j = 0; j < len; j++ ) {
item = element [ j ];
if ( item.value == current.value ) {
item[ property ] = true;
}
}
} else {
element[ property ] = current.value;
}
}
if ( jQuery.isFunction( callback ) ) {
callback.call( this );
}
return this;
}
});
})( jQuery );

File diff suppressed because one or more lines are too long

View File

@@ -23,7 +23,7 @@
<div class="nd_content content">
<ul id="search_results" class="tabs" data-tabs="on">
[% FOREACH tab IN vars.tabs %]
<li[% ' class="active"' IF params.tab == tab.id %]><a href="#[% tab.id %]_pane">[% tab.label %]</a></li>
<li[% ' class="active"' IF params.tab == tab.id %]><a id="[% tab.id %]_link" href="#[% tab.id %]_pane">[% tab.label %]</a></li>
[% END %]
<li class="device_label_right">
<h3 class="inline device_label">[% d.ip %]</h3>

View File

@@ -31,79 +31,6 @@
collapseHtml: '<label class="nd_collapser">Legend<div class="arrow-down"></div></label>',
});
// parameterised for the active tab - submits search form and injects
// HTML response into the tab pane, or an error/empty-results message
function do_search (event, tab) {
var form = '#' + tab + '_form';
var target = '#' + tab + '_pane';
var mark = '#' + tab + '_bookmark';
// stop form from submitting normally
event.preventDefault();
// get the form params
var query = $(form).serialize();
// in case of slow data load, let the user know
$(target).html(
'<div class="span3 alert-message notice"><p>Waiting for results...</p></div>'
);
// submit the query and put results into the tab pane
$(target).load( '[% uri_for('/ajax/content/device') %]/' + tab + '?' + query,
function(response, status, xhr) {
if (status !== "success") {
$(target).html(
'<div class="span6 alert-message error">' +
'<p>Search failed! Please contact your site administrator.</p></div>'
);
return;
}
if (response === "") {
$(target).html(
'<div class="span3 alert-message info"><p>No matching records.</p></div>'
);
}
// looks good, update the bookmark for this search
$(mark).attr('href', '[% uri_for('/device') %]?' + query);
// enable collapser on any large vlan lists
$('.nd_collapse_vlans').collapser({
target: 'next',
effect: 'slide',
changeText: true,
expandHtml: '<div class="cell-arrow-up"></div><div class="nd_collapser">Show VLANs</div>',
collapseHtml: '<div class="cell-arrow-down"></div><div class="nd_collapser">Hide VLANs</div>',
});
}
);
}
// on tab change, hide previous tab's search form and show new tab's
// search form. also trigger to load the content for the newly active tab.
$('#search_results').bind('change', function(e) {
var to = $(e.target).attr('href').replace(/^#/,"").replace(/_pane$/,"");
var from = $(e.relatedTarget).attr('href').replace(/^#/,"").replace(/_pane$/,"");
$('#' + from + '_search').toggleClass('active');
$('#' + to + '_search').toggleClass('active');
var to_form = '#' + to + '_form';
var from_form = '#' + from + '_form';
// copy current search string to new form's input box
$(to_form).find("input[name=q]").val(
$(from_form).find("input[name=q]").val()
);
$(to_form).trigger("submit");
});
// fix green background on search checkboxes
// https://github.com/twitter/bootstrap/issues/742
syncCheckBox = function() {
$(this).parents('.add-on').toggleClass('active', $(this).is(':checked'));
};
$('.add-on :checkbox').each(syncCheckBox).click(syncCheckBox);
// show or hide sweeping brush icon when field has content
var sweep = $('#ports_form').find("input[name=q]");
@@ -128,18 +55,21 @@
$('#ports_form').trigger('submit');
});
// search hook for each tab
[% FOREACH tab IN vars.tabs %]
$('[% "#${tab.id}_form" %]').submit(function(event){ do_search(event, '[% tab.id %]'); });
[% END %]
// on page load, load the content for the active tab
[% IF params.tab %]
$('#[% params.tab %]_form').trigger("submit");
[% END %]
// everything starts hidden and then we show defaults
$('#nd_collapse_legend').click();
function inner_view_processing() {
// enable collapser on any large vlan lists
$('.nd_collapse_vlans').collapser({
target: 'next',
effect: 'slide',
changeText: true,
expandHtml: '<div class="cell-arrow-up"></div><div class="nd_collapser">Show VLANs</div>',
collapseHtml: '<div class="cell-arrow-down"></div><div class="nd_collapser">Hide VLANs</div>',
});
}
[%+ INCLUDE 'js/tabs.js' path="device" -%]
[%+ INCLUDE 'js/sidebar.js' -%]
[%+ INCLUDE 'js/fixes.js' -%]
});

View File

@@ -0,0 +1,6 @@
// fix green background on search checkboxes
// https://github.com/twitter/bootstrap/issues/742
syncCheckBox = function() {
$(this).parents('.add-on').toggleClass('active', $(this).is(':checked'));
};
$('.add-on :checkbox').each(syncCheckBox).click(syncCheckBox);

View File

@@ -1,71 +1,4 @@
$(document).ready(function() {
// parameterised for the active tab - submits search form and injects
// HTML response into the tab pane, or an error/empty-results message
function do_search (event, tab) {
var form = '#' + tab + '_form';
var target = '#' + tab + '_pane';
var mark = '#' + tab + '_bookmark';
// stop form from submitting normally
event.preventDefault();
// get the form params
var query = $(form).serialize();
// in case of slow data load, let the user know
$(target).html(
'<div class="span3 alert-message notice"><p>Waiting for results...</p></div>'
);
// submit the query and put results into the tab pane
$(target).load( '[% uri_for('/ajax/content/search') %]/' + tab + '?' + query,
function(response, status, xhr) {
if (status !== "success") {
$(target).html(
'<div class="span6 alert-message error">' +
'<p>Search failed! Please contact your site administrator.</p></div>'
);
return;
}
if (response === "") {
$(target).html(
'<div class="span3 alert-message info"><p>No matching records.</p></div>'
);
}
// looks good, update the bookmark for this search
$(mark).attr('href', '[% uri_for('/search') %]?' + query);
}
);
}
// search hook for each tab
[% FOREACH tab IN vars.tabs %]
$('[% "#${tab.id}_form" %]').submit(function(event){ do_search(event, '[% tab.id %]'); });
[% END %]
// on page load, load the content for the active tab
[% IF params.tab %]
$('#[% params.tab %]_form').trigger("submit");
[% END %]
// on tab change, hide previous tab's search form and show new tab's
// search form. also trigger to load the content for the newly active tab.
$('#search_results').bind('change', function(e) {
var to = $(e.target).attr('href').replace(/^#/,"").replace(/_pane$/,"");
var from = $(e.relatedTarget).attr('href').replace(/^#/,"").replace(/_pane$/,"");
$('#' + from + '_search').toggleClass('active');
$('#' + to + '_search').toggleClass('active');
var to_form = '#' + to + '_form';
var from_form = '#' + from + '_form';
// copy current search string to new form's input box
$(to_form).find("input[name=q]").val(
$(from_form).find("input[name=q]").val()
);
$(to_form).trigger("submit");
});
// fix green background on search checkboxes
// https://github.com/twitter/bootstrap/issues/742
syncCheckBox = function() {
@@ -105,5 +38,9 @@
}
});
function inner_view_processing() {} // noop
[%+ INCLUDE 'js/tabs.js' path="search" -%]
[%+ INCLUDE 'js/sidebar.js' -%]
[%+ INCLUDE 'js/fixes.js' -%]
});

133
Netdisco/views/js/tabs.js Normal file
View File

@@ -0,0 +1,133 @@
// parameterised for the active tab - submits search form and injects
// HTML response into the tab pane, or an error/empty-results message
function do_search (event, tab) {
var form = '#' + tab + '_form';
var target = '#' + tab + '_pane';
var mark = '#' + tab + '_bookmark';
// stop form from submitting normally
event.preventDefault();
// copy current search string to other forms' input box
$('form').find("input[name=q]").each( function() {
$(this).val( $(form).find("input[name=q]").val() );
});
// get the form params
var query = $(form).serialize();
if (window.History.enabled) {
is_from_history_plugin = 1;
window.History.replaceState(
{name: tab, fields: $(form).serializeArray()},
'Netdisco - '+ tab.charAt(0).toUpperCase() + tab.slice(1),
'[% uri_for('/' _ path) %]?' + query
);
is_from_history_plugin = 0;
}
// in case of slow data load, let the user know
$(target).html(
'<div class="span3 alert-message notice"><p>Waiting for results...</p></div>'
);
// submit the query and put results into the tab pane
$(target).load( '[% uri_for('/ajax/content/' _ path) %]/' + tab + '?' + query,
function(response, status, xhr) {
if (status !== "success") {
$(target).html(
'<div class="span6 alert-message error">' +
'<p>Search failed! Please contact your site administrator.</p></div>'
);
return;
}
if (response === "") {
$(target).html(
'<div class="span3 alert-message info"><p>No matching records.</p></div>'
);
}
// looks good, update the bookmark for this search
$(mark).attr('href', '[% uri_for('/' _ path) %]?' + query);
inner_view_processing();
}
);
}
// the history.js plugin is great, but fires statechange at pushState
// so we have these semaphpores to help avoid messing the History.
// set true when faking a user click on a tab
var is_from_state_event = 0;
// set true when the history plugin does pushState - to prevent loop
var is_from_history_plugin = 0;
// handler for ajax navigation
if (window.History.enabled) {
var History = window.History;
History.Adapter.bind(window, "statechange", function() {
if (is_from_history_plugin == 0) {
is_from_state_event = 1;
var State = History.getState();
// History.log(State.data.name, State.title, State.url);
$('#'+ State.data.name + '_form').deserialize(State.data.fields);
$('#'+ State.data.name + '_link').click();
is_from_state_event = 0;
}
});
}
// on tab change, hide previous tab's search form and show new tab's
// search form. also trigger to load the content for the newly active tab.
function update_content(from, to) {
$('#' + from + '_search').toggleClass('active');
$('#' + to + '_search').toggleClass('active');
var to_form = '#' + to + '_form';
var from_form = '#' + from + '_form';
if (window.History.enabled && is_from_state_event == 0) {
is_from_history_plugin = 1;
window.History.pushState(
{name: to, fields: $(to_form).serializeArray()},
'Netdisco '+ $(to_form).find("input[name=ip]").val() +' '+ to.charAt(0).toUpperCase() + to.slice(1),
'[% uri_for('/' _ path) %]?' + $(to_form).serialize()
);
is_from_history_plugin = 0;
}
$(to_form).trigger("submit");
}
// could not get twitter bootstrap tabs to behave, so implemented this
// but warning! will probably not work for dropdowns in tabs
$('#search_results li').delegate('a', 'click', function(event) {
event.preventDefault();
var from_li = $('.tabs').find('> .active').first();
var to_li = $(this).parent('li')
from_li.removeClass('active');
to_li.addClass('active');
var from = from_li.find('a').attr('href');
var to = $(this).attr('href');
$(from).toggleClass('active');
$(to).toggleClass('active');
update_content(
from.replace(/^#/,"").replace(/_pane$/,""),
to.replace(/^#/,"").replace(/_pane$/,"")
);
});
// search hook for each tab
[% FOREACH tab IN vars.tabs %]
$('[% "#${tab.id}_form" %]').submit(function(event){ do_search(event, '[% tab.id %]'); });
[% END %]
// on page load, load the content for the active tab
[% IF params.tab %]
$('#[% params.tab %]_form').trigger("submit");
[% END %]

View File

@@ -13,10 +13,11 @@
<script type="text/javascript" src="[% uri_base %]/javascripts/jquery-latest.min.js"></script>
<script type="text/javascript" src="[% uri_base %]/javascripts/jquery-collapser.min.js"></script>
<script type="text/javascript" src="[% uri_base %]/javascripts/jquery-history.js"></script>
<script type="text/javascript" src="[% uri_base %]/javascripts/jquery-deserialize.js"></script>
<script type="text/javascript" src="[% uri_base %]/javascripts/bootstrap-alerts.js"></script>
<script type="text/javascript" src="[% uri_base %]/javascripts/bootstrap-twipsy.js"></script>
<script type="text/javascript" src="[% uri_base %]/javascripts/bootstrap-dropdown.js"></script>
<script type="text/javascript" src="[% uri_base %]/javascripts/bootstrap-tabs.js"></script>
<script type="text/javascript">
$(document).ready(function() {

View File

@@ -32,7 +32,7 @@
<div class="nd_content content">
<ul id="search_results" class="tabs" data-tabs="on">
[% FOREACH tab IN vars.tabs %]
<li[% ' class="active"' IF params.tab == tab.id %]><a href="#[% tab.id %]_pane">[% tab.label %]</a></li>
<li[% ' class="active"' IF params.tab == tab.id %]><a id="[% tab.id %]_link" href="#[% tab.id %]_pane">[% tab.label %]</a></li>
[% END %]
</ul>
<div class="tab-content">

View File

@@ -1,5 +1,6 @@
<form id="[% tab.id %]_form" class="nd_sidesearchform form-stacked" method="get" action="[% uri_for('/device') %]">
<input name="tab" value="[% tab.id %]" type="hidden"/>
<input name="ip" value="[% params.ip %]" type="hidden"/>
<input name="q" value="[% params.q %]" type="hidden"/>
</form>

View File

@@ -1,5 +1,6 @@
<form id="[% tab.id %]_form" class="nd_sidesearchform form-stacked" method="get" action="[% uri_for('/device') %]">
<input name="tab" value="[% tab.id %]" type="hidden"/>
<input name="ip" value="[% params.ip %]" type="hidden"/>
<input name="q" value="[% params.q %]" type="hidden"/>
</form>

View File

@@ -1,5 +1,6 @@
<form id="[% tab.id %]_form" class="nd_sidesearchform form-stacked" method="get" action="[% uri_for('/device') %]">
<input name="tab" value="[% tab.id %]" type="hidden"/>
<input name="ip" value="[% params.ip %]" type="hidden"/>
<input name="q" value="[% params.q %]" type="hidden"/>
</form>