diff --git a/app/views/admin/my_accounts/edit.html.haml b/app/views/admin/my_accounts/edit.html.haml index 4e8e35c1..c68c5286 100644 --- a/app/views/admin/my_accounts/edit.html.haml +++ b/app/views/admin/my_accounts/edit.html.haml @@ -1,5 +1,8 @@ - title link_to(@account.name.blank? ? @account.name_was : @account.name, '#', :rel => 'my_account_name', :title => t('.ask_for_name'), :class => 'editable') +- content_for :head do + = javascript_include_tag 'admin/account' + - content_for :submenu do = render 'admin/shared/menu/settings' diff --git a/app/views/admin/shared/_head.html.haml b/app/views/admin/shared/_head.html.haml index 1b30fad9..387d13a3 100644 --- a/app/views/admin/shared/_head.html.haml +++ b/app/views/admin/shared/_head.html.haml @@ -10,7 +10,7 @@ = stylesheet_link_tag 'admin/layout', 'admin/jquery/ui', 'admin/plugins/toggle', 'admin/menu', 'admin/buttons', 'admin/formtastic', 'admin/formtastic_changes', 'admin/application', :media => 'screen', :cache => Rails.env.production? && !Locomotive.heroku? -= javascript_include_tag 'admin/jquery', 'admin/jquery.ui', 'admin/rails', 'admin/utils', 'admin/plugins/shortcut', 'admin/plugins/toggle', 'admin/plugins/growl', 'admin/plugins/cookie', 'admin/application', 'admin/locales/datepicker_fr', :cache => Rails.env.production? && !Locomotive.heroku? += javascript_include_tag 'admin/jquery', 'admin/jquery.ui', 'admin/rails', 'admin/utils', 'admin/plugins/subscribe', 'admin/plugins/shortcut', 'admin/plugins/toggle', 'admin/plugins/growl', 'admin/plugins/cookie', 'admin/application', 'admin/locales/datepicker_fr', :cache => Rails.env.production? && !Locomotive.heroku? %script{ :type => 'text/javascript' } = find_and_preserve(growl_message) diff --git a/doc/TODO b/doc/TODO index d659d89e..487ebfd3 100644 --- a/doc/TODO +++ b/doc/TODO @@ -2,8 +2,7 @@ BOARD: - refactor slugify method (use parameterize + create a module) - send email when new content added thru api -- publish event when saving form in ajax (for instance, in order to update account name or site name) -- switch to list (theme assets / assets ?). delete all in once (with checkbox) or see details (updated_at, size, ...etc) +- page templatized (bound to a model) BACKLOG: @@ -13,6 +12,7 @@ BACKLOG: - belongs_to => association - cucumber features for admin pages - validation for custom fields +- switch to list (theme assets / assets ?). delete all in once (with checkbox) or see details (updated_at, size, ...etc) BUGS: @@ -60,4 +60,5 @@ x sitemap x refactoring admin crud (pages + layouts + snippets) x flash messages in French x save layout / snippet / page / stylesheet / javascript with CMD + S (ajax) -x change action icons according to the right action [Sacha] \ No newline at end of file +x change action icons according to the right action [Sacha] +x publish event when saving form in ajax (for instance, in order to update account name or site name) \ No newline at end of file diff --git a/public/javascripts/admin/account.js b/public/javascripts/admin/account.js new file mode 100644 index 00000000..69f39ced --- /dev/null +++ b/public/javascripts/admin/account.js @@ -0,0 +1,5 @@ +$(document).ready(function() { + $.subscribe('form.saved.success', function(event, data) { + $('#global-actions-bar a:first').html($('#my_account_name').val()); + }, []); +}); \ No newline at end of file diff --git a/public/javascripts/admin/plugins/shortcut.js b/public/javascripts/admin/plugins/shortcut.js index 1f8bd03c..6ae11956 100644 --- a/public/javascripts/admin/plugins/shortcut.js +++ b/public/javascripts/admin/plugins/shortcut.js @@ -36,6 +36,7 @@ jQuery.fn.saveWithShortcut = function() { form.find('li.error input').eq(0).focus(); } else { $.growl('success', data.notice); + $.publish('form.saved.success', [data]); } }; diff --git a/public/javascripts/admin/plugins/subscribe.js b/public/javascripts/admin/plugins/subscribe.js new file mode 100644 index 00000000..16596cf8 --- /dev/null +++ b/public/javascripts/admin/plugins/subscribe.js @@ -0,0 +1,367 @@ +/* + * jquery.subscribe.1.1 + * + * Implementation of publish/subcription framework for jQuery + * Requires use of jQuery. Tested with jQuery 1.3 and above + * + * + * Copyright (c) 2008 Eric Chijioke (obinna a-t g mail dot c o m) + * + * + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + * + * Release Notes: + * + * version 1.1: + * + * Fixed unexpected behavior which can occur when a script in a embedded page (page loaded in div,tab etc.) subscribes a handler for a topic using + * the jQuery subscribe ($.subscribe) or a no-id element but this subscribe plugin is not reloaded within that embedded page (for example, when + * script is included in containing page) . In this case, if the embedded page is reloaded without reloading the entire page (and plugin), the + * subscription could be made multiple times for the topic, which will call the handler multiple times each time the topic is published. + * Code has been added to prevent this when the subscription is made using the non-element subscribe ($.subscribe()), which assures that only one + * subscription is made for a topic for a given window/frame. To prevent this from happening for an element subscription ($elem.subscribe()), make + * sure that the element has an id attribute. + */ + + +(function($){ + + _subscribe_topics = {}; + _subscribe_handlers = {}; + + _subscribe_getDocumentWindow = function(document){ + + return document.parentWindow || document.defaultView; + }; + + $.fn.extend({ + + /** + * Creates a new topic without any subscribers. + * Not usually used explicitly + */ + createTopic : function(topic) { + if(topic && !_subscribe_topics[topic]) { + + _subscribe_topics[topic] = {}; + _subscribe_topics[topic].objects = {}; + _subscribe_topics[topic].objects['__noId__'] = []; + } + + return this; + }, + + /** + * Destroy an existing topic and unsubscribe all subscribers + */ + destroyTopic : function(topic) { + + if(topic && _subscribe_topics[topic]) { + + for(i in _subscribe_topics[topic].objects) { + + var object = _subscribe_topics[topic].objects[i]; + + if($.isArray(object)) { // handle '__noId__' elements + + if(object.length > 0) { + + for(j in object) { + + object[j].unbind(topic); + } + } + + } else { + + object.unbind(topic,data); + } + } + } + + delete _subscribe_topics[topic]; + + return this; + }, + + /** + * Subscribes an object to particular topic with a handler. + * When the topic is published, this handler will be executed. + * + * Parameters: + * -topic- is the string name of the topic + * -handler- is a handler function and is of the form function(event, data), in which the 'this' refers to the element itself. + * handler can be a function or can be a string referring to a function previously registered using the $.subscribeHandler() function + * Note: returning 'false' from the handler will prevent subsequent handlers from being executed on this element during + * this call. + * -data- (optional) is additional data that is passed to the event handler as event.data when the topic is published + * + * Note: Unexpected behavior can occur when a script in a embedded page (page loaded in div,tab etc.) subscribes a handler for a topic using + * the jQuery subscribe ($.subscribe) or a no-id element but this subscribe plugin is not reloaded within that embedded page (for example, when + * script is included in containing page) . In this case, if the embedded page is reloaded without reloading the entire page (and plugin), the + * subscription could be made multiple times for the topic, which will call the handler multiple times each time the topic is published. + * Code has been added to prevent this when the subscription is made using the non-element subscribe ($.subscribe()), which assures that only one + * subscription is made for a topic for a given window/frame. To prevent this from happening for an element subscription ($elem.subscribe()), make + * sure that the element has an id attribute. + */ + subscribe : function(topic, handler, data) { + + if(this[0] && topic && handler) { + + this.createTopic(topic); + + if(this.attr('id')) { + + _subscribe_topics[topic].objects[this.attr('id')] = this; + + } else { + + //do not subscribe the same window/frame document multiple times, this causes unexpected behavior of executing embedded scripts multiple times + var noIdObjects = _subscribe_topics[topic].objects['__noId__']; + + if(this[0].nodeType == 9) { //if document is being bound (the case for non-element jQuery subscribing ($.subscribe) + + for ( var index in noIdObjects) { + + var noIdObject = noIdObjects[index]; + + if(noIdObject[0].nodeType == 9 && _subscribe_getDocumentWindow(this[0]).frameElement == _subscribe_getDocumentWindow(noIdObject[0]).frameElement ) { + + return this; + } + } + } + + var exists = false; + for(var i = 0; i < noIdObjects.length; i++){ + if(noIdObjects[i] == this){ + exists = true; + break; + } + } + + if(!exists) { + + _subscribe_topics[topic].objects['__noId__'].push(this); + } + } + + if(typeof(handler) == 'function') { + + this.bind(topic, data, handler); + + } else if(typeof(handler) == 'string' && typeof(_subscribe_handlers[handler]) == 'function') { + + this.bind(topic, data, _subscribe_handlers[handler]); + } + } + + return this; + }, + + /** + * Remove a subscription of an element to a topic. + * This will unbind stop all handlers from executing on this element when the topic + * is published + */ + unsubscribe : function(topic) { + + if(topic) { + + if(_subscribe_topics[topic]) { + + if(this.attr('id')) { + + var object = _subscribe_topics[topic].objects[this.attr('id')]; + + if(object) { + + delete _subscribe_topics[topic].objects[this.attr('id')]; + } + + } else { + + var noIdObjects = _subscribe_topics[topic].objects['__noId__']; + + for(var i = 0; i < noIdObjects.length; i++){ + + if(noIdObjects[i] == this){ + + subscribe_topics[topic].objects['__noId__'].splice(index,1); + break; + } + } + } + } + + this.unbind(topic); + } + + return this; + }, + + /** + * Publishes a topic (triggers handlers on all topic subscribers) + * This ends up calling any subscribed handlers which are functions of the form function (event, data) + * where: event - is a standard jQuery event object + * data - is the data parameter that was passed to this publish() method + * event.data - is the data parameter passed to the subscribe() function when this published topic was subscribed to + * event.target - is the dom element that subscribed to the event (or the document element if $.subscribe() was used) + * + * Parameters: + * -topic- is the string name of the topic + * -data- (optional) is additional data that is passed to the event handler 'data' parameter when the topic is published + * handler can be a function or can be a string referring to a function previously registered using the $.subscribeHandler() function + * -originalEvent- (optional) may be passed in a reference to an event which triggered this publishing. This will be passed as the + * 'originalEvent' field of the triggered event which will allow for controlling the propagation of higher level events + * from within the topic handler. In other words, this allows one to cancel execution of all subsequent handlers on the originalEvent + * for this element by return 'false' from a handler that is subscribed to the topic published here. This can be especially useful + * in conjunction with publishOnEvent(), where a topic is published when an event executes (such as a click) and we want our + * handler logic prevent additional topics from being published (For example if our topic displays a 'delete confirm' dialog on click and + * the user cancels, we may want to prevent subsequent topics bound to the original click event from being published). + */ + publish : function(topic, data, originalEvent) { + + if(topic) { + + this.createTopic(topic); + + //if an orginal event exists, need to modify the event object to prevent execution of all + //other handlers if the result of the handler is false (which calls stopPropagation()) + + var subscriberStopPropagation = function(){ + + this.isImmediatePropagationStopped = function(){ + return true; + }; + + (new $.Event).stopPropagation(); + + if(this.originalEvent) { + + this.originalEvent.isImmediatePropagationStopped = function(){ + return true; + }; + + this.originalEvent.stopPropagation = subscriberStopPropagation; + } + } + + var event = jQuery.Event(topic); + $.extend(event,{originalEvent: originalEvent, stopPropagation: subscriberStopPropagation}); + + for(i in _subscribe_topics[topic].objects) { + + var object = _subscribe_topics[topic].objects[i]; + + if($.isArray(object)) { // handle '__noId__' elements (if any) + + if(object.length > 0) { + + for(j in object) { + + object[j].trigger( event,data); + } + } + + } else { + + object.trigger( event,data); + } + } + + } + + return this; + }, + + /** + * Binds an objects event handler to a publish call + * + * Upon the event triggering, this ends up calling any subscribed handlers which are functions of the form function (event, data) + * where: event- is a standard jQuery event object + * event.data- is the data parameter passed to the subscribe() function when this published topic was subscribed to + * data- is the data parameter that was passed to this publishOnEvent() method + * Parameters: + * -event- is the string name of the event upon which to publish the topic + * -topic- is the string name of the topic to publish when the event occurs + * -data- (optional) is additional data which will be passed in to the publish() method ant hen available as the second ('data') + * parameter to the topic handler + */ + publishOnEvent : function(event, topic, data) { + + if(event && topic) { + + this.createTopic(topic); + + this.bind(event, data, function (e) { + + $(this).publish(topic, e.data, e); + }); + } + + return this; + } + }); + + /** + * Make publish(), createTopic() and destroyTopic() callable without an element context + * Often don't need a context to subscribe, publish, create or destroy a topic. + * We will call from the document context + */ + $.extend({ + + /** + * Subscribe an event handler to a topic without an element context + * + * Note: Caution about subscribing using same document to topic multiple time (maybe by loading subscribe script multiple times) + * + */ + subscribe : function(topic, handler, data) { + return $(window).subscribe(topic, handler, data); + + }, + + /** + * Unsubscribe an event handler for a topic without an element context + * + */ + unsubscribe : function(topic, handler, data) { + + return $(window).unsubscribe(topic, handler, data); + + }, + + /** + * Register a handler function which can then be referenced by name when calling subscribe() + */ + subscribeHandler: function(name, handler) { + + if(name && handler && typeof(handler) == "function") { + + _subscribe_handlers[name] = handler; + } + + return $(window); + }, + + publish: function(topic, data) { + + return $(window).publish(topic,data); + }, + + createTopic: function(topic) { + + return $(window).createTopic(topic); + }, + + destroyTopic: function(topic) { + + return $(window).destroyTopic(topic); + } + + }); + +})(jQuery); diff --git a/public/javascripts/admin/site.js b/public/javascripts/admin/site.js index 1c5f5df3..65e65e7a 100644 --- a/public/javascripts/admin/site.js +++ b/public/javascripts/admin/site.js @@ -1,7 +1,7 @@ $(document).ready(function() { var defaultValue = $('fieldset.editable-list li.template input[type=text]').val(); - + /* __ fields ___ */ $('fieldset.editable-list li.template input[type=text]').focus(function() { if ($(this).hasClass('void') && $(this).parents('li').hasClass('template')) @@ -32,5 +32,8 @@ $(document).ready(function() { e.preventDefault(); e.stopPropagation(); }); - + + $.subscribe('form.saved.success', function(event, data) { + $('#header h1 a').html($('#current_site_name').val()); + }, []); }); \ No newline at end of file diff --git a/public/stylesheets/admin/application.css b/public/stylesheets/admin/application.css index fdb18af0..65312fe9 100644 --- a/public/stylesheets/admin/application.css +++ b/public/stylesheets/admin/application.css @@ -117,7 +117,7 @@ ul.assets li.asset.last { margin-right: 0px; } -ul.assets li.asset h4 { margin: 0px; height: 30px; } +ul.assets li.asset h4 { margin: 0px; height: 30px; border-bottom: 1px solid #C2C4D2; } ul.assets li.asset h4 a { position: relative; diff --git a/public/stylesheets/admin/formtastic_changes.css b/public/stylesheets/admin/formtastic_changes.css index 4068e5a2..9ca40a69 100644 --- a/public/stylesheets/admin/formtastic_changes.css +++ b/public/stylesheets/admin/formtastic_changes.css @@ -60,7 +60,7 @@ form.formtastic fieldset.foldable.folded legend span em { form.formtastic fieldset.foldable ol { clear: both; width: 100%; - overflow: hidden; + overflow: hidden; } form.formtastic fieldset.foldable.folded ol { display: none; } @@ -80,6 +80,7 @@ form.formtastic fieldset.inputs ol { padding-top: 15px; padding-bottom: 5px; background: #ebedf4 url(/images/admin/form/footer.png) no-repeat 0 bottom; + border-top: 1px solid #C2C4D2; } @media screen and (-webkit-min-device-pixel-ratio:0) { diff --git a/public/stylesheets/admin/page_parts.css b/public/stylesheets/admin/page_parts.css index a1e6fea6..89b9fe43 100644 --- a/public/stylesheets/admin/page_parts.css +++ b/public/stylesheets/admin/page_parts.css @@ -2,6 +2,7 @@ background: #ebedf4 url(/images/admin/form/footer.png) no-repeat 0 bottom; width: 880px; padding: 20px 20px; + border-top: 1px solid #C2C4D2; } #page-parts { @@ -12,6 +13,12 @@ height: 30px; } +#page-parts .nav { + position: relative; + top: 1px; + z-index: 990; +} + #page-parts .nav a { float: left; display: block;